diff --git a/.gitignore b/.gitignore index 1f62fb87aeac8e9a7e9cfb4572dc49a2a4bee886..eedfb36fac1f95dfd83a7a5fc4692fdfb89361d6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,9 +11,11 @@ bin/* .*.sw? .*.pyc -.project -.cproject -.settings +.project +.cproject +.settings + +compile_flags.txt STM32F429zi_Discovery.xml ._* @@ -25,4 +27,9 @@ build .vscode/* store.json -scripts/homeone/event_header_generator/generated/ +**/generated/ +core + +__pycache__ +/scripts/generators/generated +/scripts/generators/scxmls diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e594a367a6e9c46c5d42ba386c78ef1dc0fe1a29..7bb4feafb39ac38529aa0e8a452288f7ca546dbb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,35 +7,28 @@ variables: GIT_SUBMODULE_STRATEGY: recursive -before_script: - - echo "Before script section" - - echo "For example you might run an update here or install a build dependency" - - echo "Or perhaps you might print out some debugging details" +# before_script: +# - echo "Before script section" +# - echo "For example you might run an update here or install a build dependency" +# - echo "Or perhaps you might print out some debugging details" -after_script: - - echo "After script section" - - echo "For example you might do some cleanup here" +# after_script: +# - echo "After script section" +# - echo "For example you might do some cleanup here" -build1: +build: stage: build only: - master - testing script: - - echo "Do your build here" + - echo "Running complete build" - python3 sbs -test1: +linter: stage: test script: - - echo "Do a test here" - - echo "For example run a test suite" - -test2: - stage: test - script: - - echo "Do another parallel test here" - - echo "For example run a lint test" + - echo "Running linter" - ./scripts/linter.sh documentation: @@ -48,7 +41,7 @@ documentation: - mkdir -p /srv/code_documentation/${CI_PROJECT_NAME}/ - mv doc/output/html /srv/code_documentation/${CI_PROJECT_NAME}/${CI_COMMIT_REF_NAME} -deploy1: - stage: deploy - script: - - echo "Do your deploy here" +# deploy1: +# stage: deploy +# script: +# - echo "Do your deploy here" diff --git a/.gitmodules b/.gitmodules index e6d4a9415a0bc8bff6682c1980d7f092eee019bf..178056d3aa2116ce8cfe956cdcbd2bc9c67ba6ab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,9 @@ [submodule "libs/simple-template-matrix"] path = libs/simple-template-matrix url = https://github.com/fedetft/simple-template-matrix.git +[submodule "libs/eigen"] + path = libs/eigen + url = https://gitlab.com/libeigen/eigen.git +[submodule "libs/mxgui"] + path = libs/mxgui + url = ../../scs/mxgui.git diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index 145d9c1878b407d0eccd1fb657e44bd0e510723c..ec8b4984d562776585652543cecae42e177f0bbb 100644 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -55,6 +55,7 @@ flags = [ '-Ilibs/miosix-kernel/miosix/arch/common', '-Ilibs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common', '-Ilibs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f429zi_stm32f4discovery', + '-Ilibs/eigen', '-Imiosix', '-Imiosix-kernel', '-Isrc/shared', diff --git a/Makefile.template b/Makefile.template index 368ff88bd7deae79c0757d0ae9792fe323fa17ff..b9b7c91c62ba592b0a35dc5c153943d3a9b7a22a 100644 --- a/Makefile.template +++ b/Makefile.template @@ -3,7 +3,7 @@ SBS_BASE = {SBS_BASE_PATH} ## ## Makefile for Miosix embedded OS ## -MAKEFILE_VERSION := 1.07S +MAKEFILE_VERSION := 1.09S ## Path to kernel directory (edited by init_project_out_of_git_repo.pl) KPATH := $(SBS_BASE)/libs/miosix-kernel/miosix ## Path to config directory (edited by init_project_out_of_git_repo.pl) @@ -15,7 +15,7 @@ include $(CONFPATH)/config/Makefile.inc ## ## List here subdirectories which contains makefiles ## -SUBDIRS := $(KPATH) {SBS_PROJECT_SUBDIRS} +SUBDIRS := $(KPATH) {SBS_PROJECT_SUBDIRS} {SBS_LIB_SUBDIR} ## ## List here your source files (both .s, .c and .cpp) @@ -25,13 +25,13 @@ SRC := {SBS_SOURCE_FILES} ## ## List here additional static libraries with relative path ## -LIBS := {SBS_PROJECT_LIBS} +LIBS := {SBS_PROJECT_LIBS} {SBS_LIB_LIBS} ## ## List here additional include directories (in the form -Iinclude_dir) ## INCLUDE_DIRS := -I$(SBS_BASE)/libs -I$(SBS_BASE)/libs/miosix-kernel/miosix \ - {SBS_PROJECT_INCLUDES} + {SBS_PROJECT_INCLUDES} {SBS_LIB_INCLUDES} ############################################################################## ## You should not need to modify anything below ## @@ -54,10 +54,12 @@ OBJ := $(addprefix $(OBJDIR)/, $(addsuffix .o, $(basename $(SRC)))) ## Always include CONFPATH first, as it overrides the config file location CXXFLAGS := $(CXXFLAGS_BASE) -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC) \ -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC) \ - -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -pedantic + -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -Wcast-align \ + -pedantic CFLAGS := $(CFLAGS_BASE) -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC) \ -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC) \ - -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -pedantic + -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -Wcast-align \ + -pedantic AFLAGS := $(AFLAGS_BASE) LFLAGS := $(LFLAGS_BASE) -Wl,--gc-sections,-Map,$(BINDIR)/${SBS_BIN_NAME}.map DFLAGS := -MMD -MP @@ -109,11 +111,11 @@ $(OBJDIR)/%.o: %.s $(OBJDIR)/%.o : %.c $(ECHO) "[CC] " $< - $(Q) $(CC) $(DFLAGS) $(CFLAGS) {SBS_CUSTOM_DEFINES} $< -o $@ + $(Q) $(CC) $(DFLAGS) $(CFLAGS) {SBS_CUSTOM_DEFINES} {SBS_LIB_DEFINES} $< -o $@ $(OBJDIR)/%.o : %.cpp $(ECHO) "[CXX] " $< - $(Q) $(CXX) $(DFLAGS) $(CXXFLAGS) {SBS_CUSTOM_DEFINES} $< -o $@ + $(Q) $(CXX) $(DFLAGS) $(CXXFLAGS) {SBS_CUSTOM_DEFINES} {SBS_LIB_DEFINES} $< -o $@ folders: $(Q) mkdir -pv $(dir $(OBJ)) $(BINDIR) diff --git a/data/gdb/stm32f4-stlinv2.cfg b/data/gdb/stm32f4-stlinv2.cfg new file mode 100644 index 0000000000000000000000000000000000000000..4d4718023ed13220fa7b779294ca87824d2bd0f8 --- /dev/null +++ b/data/gdb/stm32f4-stlinv2.cfg @@ -0,0 +1,9 @@ +# This is an STM32F4 discovery board with a single STM32F407VGT6 chip. +# http://www.st.com/internet/evalboard/product/252419.jsp + +source [find interface/stlink-v2-1.cfg] + +source [find target/stm32f4x.cfg] + +# use hardware reset, connect under reset +reset_config srst_only srst_nogate \ No newline at end of file diff --git a/ide/vscode/c_cpp_properties.json b/ide/vscode/c_cpp_properties.json index abf33c87fab5ad668eab706beecadcea033a4e45..d33c461d53fc16f1c5f8110d0e6816c49e48197e 100755 --- a/ide/vscode/c_cpp_properties.json +++ b/ide/vscode/c_cpp_properties.json @@ -8,6 +8,7 @@ "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common", "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common", "${workspaceFolder}/libs/miosix-kernel/miosix", + "${workspaceFolder}/libs/eigen", "${workspaceFolder}/libs", "${workspaceFolder}/src/shared", "${workspaceFolder}/src/tests", @@ -46,9 +47,9 @@ "${workspaceFolder}/libs/miosix-kernel/miosix/*", "${workspaceFolder}/libs/mavlink_skyward_lib", "${workspaceFolder}/libs/tscpp", + "${workspaceFolder}/libs/eigen", "${workspaceFolder}/src/shared", "${workspaceFolder}/src/tests", - "${workspaceFolder}/*", "${workspaceFolder}/src", "${workspaceFolder}/*" ], @@ -63,6 +64,7 @@ "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common", "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common", "${workspaceFolder}/libs/miosix-kernel/miosix", + "${workspaceFolder}/libs/eigen", "${workspaceFolder}/libs", "${workspaceFolder}/src/shared", "${workspaceFolder}/src/tests", @@ -101,10 +103,11 @@ "${workspaceFolder}/libs/miosix-kernel/miosix/*", "${workspaceFolder}/libs/mavlink_skyward_lib", "${workspaceFolder}/libs/tscpp", + "${workspaceFolder}/libs/eigen", "${workspaceFolder}/src/shared", "${workspaceFolder}/src/tests", - "${workspaceFolder}/*", - "${workspaceFolder}/src" + "${workspaceFolder}/src", + "${workspaceFolder}/*" ], "limitSymbolsToIncludedHeaders": true } @@ -117,6 +120,7 @@ "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common", "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common", "${workspaceFolder}/libs/miosix-kernel/miosix", + "${workspaceFolder}/libs/eigen", "${workspaceFolder}/libs", "${workspaceFolder}/src/shared", "${workspaceFolder}/src/tests", @@ -156,17 +160,72 @@ "${workspaceFolder}/libs/mavlink_skyward_lib", "${workspaceFolder}/libs/mxgui", "${workspaceFolder}/libs/tscpp", + "${workspaceFolder}/libs/eigen", "${workspaceFolder}/libs/*", "${workspaceFolder}/src/shared", "${workspaceFolder}/src/tests", - "${workspaceFolder}/*", "${workspaceFolder}/src", "${workspaceFolder}/*" ], "limitSymbolsToIncludedHeaders": true } - } + }, + { + "name": "stm32f407vg_skyward_tortellino", + "includePath": [ + "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino", + "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino", + "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common", + "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common", + "${workspaceFolder}/libs/miosix-kernel/miosix", + "${workspaceFolder}/libs/eigen", + "${workspaceFolder}/libs", + "${workspaceFolder}/src/shared", + "${workspaceFolder}/src/tests", + "${workspaceFolder}" + ], + "compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++", + + "defines": [ + "DEBUG", + "_ARCH_CORTEXM4_STM32F4", + "_BOARD_STM32F407VG_SKYWARD_TORTELLINO", + "_MIOSIX_BOARDNAME=stm32f407vg_skyward_tortellino", + "HSE_VALUE=8000000", + "SYSCLK_FREQ_168MHz=168000000", + "_MIOSIX", + "__cplusplus=201103L" + ], + "cStandard": "c11", + "cppStandard": "c++11", + + "browse": { + "path" : [ + "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino/interfaces-impl", + "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino", + "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino", + "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino", + "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common", + "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common", + "${workspaceFolder}/libs/miosix-kernel/miosix/interfaces", + "${workspaceFolder}/libs/miosix-kernel/miosix/kernel", + "${workspaceFolder}/libs/miosix-kernel/miosix/util", + "${workspaceFolder}/libs/miosix-kernel/miosix/e20", + "${workspaceFolder}/libs/miosix-kernel/miosix/filesystem", + "${workspaceFolder}/libs/miosix-kernel/miosix/stdlib_integration", + "${workspaceFolder}/libs/miosix-kernel/miosix/*", + "${workspaceFolder}/libs/mavlink_skyward_lib", + "${workspaceFolder}/libs/tscpp", + "${workspaceFolder}/libs/eigen", + "${workspaceFolder}/src/shared", + "${workspaceFolder}/src/tests", + "${workspaceFolder}/src", + "${workspaceFolder}/*" + ], + "limitSymbolsToIncludedHeaders": true + } + } ], "version": 4 } \ No newline at end of file diff --git a/ide/vscode/launch.json b/ide/vscode/launch.json index f07981bfab285ff228026edb5840654b0d08f3f9..3e9272fde8f6474f08b9803f2c3e3cf547050d0f 100644 --- a/ide/vscode/launch.json +++ b/ide/vscode/launch.json @@ -9,9 +9,10 @@ "type": "cortex-debug", "servertype": "openocd", "device": "STM32F429ZI", - "svdFile": "/home/luca/drive/Skyward/STM32F429.svd", + "armToolchainPath": "/opt/arm-miosix-eabi/bin", + "toolchainPrefix": "arm-miosix-eabi", "configFiles": [ - "/home/luca/test.cfg" + "${workspaceFolder}/data/gdb/stm32f4-stlinv2.cfg" ] }, { @@ -22,6 +23,8 @@ "type": "cortex-debug", "servertype": "openocd", "device": "STM32F429ZI", + "armToolchainPath": "/opt/arm-miosix-eabi/bin", + "toolchainPrefix": "arm-miosix-eabi", "configFiles": [ "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f429zi_skyward_death_stack/death_stack.cfg" ] diff --git a/libs/eigen b/libs/eigen new file mode 160000 index 0000000000000000000000000000000000000000..0dd9643ad547d3dd2e23ded1d3376d0f7bdc8ada --- /dev/null +++ b/libs/eigen @@ -0,0 +1 @@ +Subproject commit 0dd9643ad547d3dd2e23ded1d3376d0f7bdc8ada diff --git a/libs/fmt/.clang-format b/libs/fmt/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..df55d340f018f5faae470f58fb06507148728e2c --- /dev/null +++ b/libs/fmt/.clang-format @@ -0,0 +1,8 @@ +# Run manually to reformat a file: +# clang-format -i --style=file <file> +Language: Cpp +BasedOnStyle: Google +IndentPPDirectives: AfterHash +IndentCaseLabels: false +AlwaysBreakTemplateDeclarations: false +DerivePointerAlignment: false diff --git a/libs/fmt/.gitignore b/libs/fmt/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7fcbb0e3771b4d12a12a4c529143b9cb85021300 --- /dev/null +++ b/libs/fmt/.gitignore @@ -0,0 +1,36 @@ +.vscode/ +.vs/ + +*.iml +.idea/ +.externalNativeBuild/ +.gradle/ +gradle/ +gradlew* +local.properties +build/ +support/.cxx + +bin/ +/_CPack_Packages +/CMakeScripts +/doc/doxyxml +/doc/html +/doc/node_modules +virtualenv +/Testing +/install_manifest.txt +*~ +*.a +*.so* +*.xcodeproj +*.zip +cmake_install.cmake +CPack*.cmake +fmt-*.cmake +CTestTestfile.cmake +CMakeCache.txt +CMakeFiles +FMT.build +run-msbuild.bat +fmt.pc diff --git a/libs/fmt/CMakeLists.txt b/libs/fmt/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ff6d009373d22a6b008c7d7c64087a920e0b188b --- /dev/null +++ b/libs/fmt/CMakeLists.txt @@ -0,0 +1,365 @@ +cmake_minimum_required(VERSION 3.1...3.18) + +# Fallback for using newer policies on CMake <3.12. +if(${CMAKE_VERSION} VERSION_LESS 3.12) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif() + +# Determine if fmt is built as a subproject (using add_subdirectory) +# or if it is the master project. +if (NOT DEFINED FMT_MASTER_PROJECT) + set(FMT_MASTER_PROJECT OFF) + if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(FMT_MASTER_PROJECT ON) + message(STATUS "CMake version: ${CMAKE_VERSION}") + endif () +endif () + +# Joins arguments and places the results in ${result_var}. +function(join result_var) + set(result ) + foreach (arg ${ARGN}) + set(result "${result}${arg}") + endforeach () + set(${result_var} "${result}" PARENT_SCOPE) +endfunction() + +include(CMakeParseArguments) + +# Sets a cache variable with a docstring joined from multiple arguments: +# set(<variable> <value>... CACHE <type> <docstring>...) +# This allows splitting a long docstring for readability. +function(set_verbose) + # cmake_parse_arguments is broken in CMake 3.4 (cannot parse CACHE) so use + # list instead. + list(GET ARGN 0 var) + list(REMOVE_AT ARGN 0) + list(GET ARGN 0 val) + list(REMOVE_AT ARGN 0) + list(REMOVE_AT ARGN 0) + list(GET ARGN 0 type) + list(REMOVE_AT ARGN 0) + join(doc ${ARGN}) + set(${var} ${val} CACHE ${type} ${doc}) +endfunction() + +# Set the default CMAKE_BUILD_TYPE to Release. +# This should be done before the project command since the latter can set +# CMAKE_BUILD_TYPE itself (it does so for nmake). +if (FMT_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) + set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING + "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or " + "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") +endif () + +project(FMT CXX) +include(GNUInstallDirs) +set_verbose(FMT_INC_DIR ${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.") + +option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF) +option(FMT_WERROR "Halt the compilation with an error on compiler warnings." + OFF) + +# Options that control generation of various targets. +option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT}) +option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT}) +option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT}) +option(FMT_FUZZ "Generate the fuzz target." OFF) +option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) +option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) + +# Get version from core.h +file(READ include/fmt/core.h core_h) +if (NOT core_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])") + message(FATAL_ERROR "Cannot get FMT_VERSION from core.h.") +endif () +# Use math to skip leading zeros if any. +math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1}) +math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2}) +math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3}) +join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}. + ${CPACK_PACKAGE_VERSION_PATCH}) +message(STATUS "Version: ${FMT_VERSION}") + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +endif () + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") + +include(cxx14) +include(CheckCXXCompilerFlag) +include(JoinPaths) + +list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index) +if (${index} GREATER -1) + # Use cxx_variadic_templates instead of more appropriate cxx_std_11 for + # compatibility with older CMake versions. + set(FMT_REQUIRED_FEATURES cxx_variadic_templates) +endif () +message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}") + +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic + -Wold-style-cast -Wundef + -Wredundant-decls -Wwrite-strings -Wpointer-arith + -Wcast-qual -Wformat=2 -Wmissing-include-dirs + -Wcast-align + -Wctor-dtor-privacy -Wdisabled-optimization + -Winvalid-pch -Woverloaded-virtual + -Wconversion -Wswitch-enum -Wundef + -Wno-ctor-dtor-privacy -Wno-format-nonliteral) + if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6) + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} + -Wno-dangling-else -Wno-unused-local-typedefs) + endif () + if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion + -Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast + -Wvector-operation-performance -Wsized-deallocation -Wshadow) + endif () + if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2 + -Wnull-dereference -Wduplicated-cond) + endif () + set(WERROR_FLAG -Werror) +endif () + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef + -Wdeprecated -Wweak-vtables -Wshadow + -Wno-gnu-zero-variadic-macro-arguments) + check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING) + if (HAS_NULLPTR_WARNING) + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} + -Wzero-as-null-pointer-constant) + endif () + set(WERROR_FLAG -Werror) +endif () + +if (MSVC) + set(PEDANTIC_COMPILE_FLAGS /W3) + set(WERROR_FLAG /WX) +endif () + +if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio") + # If Microsoft SDK is installed create script run-msbuild.bat that + # calls SetEnv.cmd to set up build environment and runs msbuild. + # It is useful when building Visual Studio projects with the SDK + # toolchain rather than Visual Studio. + include(FindSetEnv) + if (WINSDK_SETENV) + set(MSBUILD_SETUP "call \"${WINSDK_SETENV}\"") + endif () + # Set FrameworkPathOverride to get rid of MSB3644 warnings. + join(netfxpath + "C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\" + ".NETFramework\\v4.0") + file(WRITE run-msbuild.bat " + ${MSBUILD_SETUP} + ${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*") +endif () + +set(strtod_l_headers stdlib.h) +if (APPLE) + set(strtod_l_headers ${strtod_l_headers} xlocale.h) +endif () + +include(CheckSymbolExists) +if (WIN32) + check_symbol_exists(_strtod_l "${strtod_l_headers}" HAVE_STRTOD_L) +else () + check_symbol_exists(strtod_l "${strtod_l_headers}" HAVE_STRTOD_L) +endif () + +function(add_headers VAR) + set(headers ${${VAR}}) + foreach (header ${ARGN}) + set(headers ${headers} include/fmt/${header}) + endforeach() + set(${VAR} ${headers} PARENT_SCOPE) +endfunction() + +# Define the fmt library, its includes and the needed defines. +add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h + format-inl.h locale.h os.h ostream.h printf.h ranges.h) +if (FMT_OS) + set(FMT_SOURCES src/format.cc src/os.cc) +else() + set(FMT_SOURCES src/format.cc) +endif () + +add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) +add_library(fmt::fmt ALIAS fmt) + +if (HAVE_STRTOD_L) + target_compile_definitions(fmt PUBLIC FMT_LOCALE) +endif () + +if (MINGW) + check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ) + if (${FMT_HAS_MBIG_OBJ}) + target_compile_options(fmt PUBLIC "-Wa,-mbig-obj") + endif() +endif () + +if (FMT_WERROR) + target_compile_options(fmt PRIVATE ${WERROR_FLAG}) +endif () +if (FMT_PEDANTIC) + target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) +endif () + +target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) + +target_include_directories(fmt PUBLIC + $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:${FMT_INC_DIR}>) + +set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") + +set_target_properties(fmt PROPERTIES + VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} + DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") + +# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target +# property because it's not set by default. +set(FMT_LIB_NAME fmt) +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX}) +endif () + +if (BUILD_SHARED_LIBS) + if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND + NOT EMSCRIPTEN) + # Fix rpmlint warning: + # unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6. + target_link_libraries(fmt -Wl,--as-needed) + endif () + target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED) +endif () +if (FMT_SAFE_DURATION_CAST) + target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST) +endif() + +add_library(fmt-header-only INTERFACE) +add_library(fmt::fmt-header-only ALIAS fmt-header-only) + +target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) +target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) + +target_include_directories(fmt-header-only INTERFACE + $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:${FMT_INC_DIR}>) + +# Install targets. +if (FMT_INSTALL) + include(CMakePackageConfigHelpers) + set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING + "Installation directory for cmake files, a relative path that " + "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute " + "path.") + set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) + set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) + set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc) + set(targets_export_name fmt-targets) + + set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING + "Installation directory for libraries, a relative path that " + "will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.") + + set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH + "Installation directory for pkgconfig (.pc) files, a relative " + "path that will be joined with ${CMAKE_INSTALL_PREFIX} or an " + "absolute path.") + + # Generate the version, config and target files into the build directory. + write_basic_package_version_file( + ${version_config} + VERSION ${FMT_VERSION} + COMPATIBILITY AnyNewerVersion) + + join_paths(libdir_for_pc_file "\${exec_prefix}" "${FMT_LIB_DIR}") + join_paths(includedir_for_pc_file "\${prefix}" "${FMT_INC_DIR}") + + configure_file( + "${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in" + "${pkgconfig}" + @ONLY) + configure_package_config_file( + ${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in + ${project_config} + INSTALL_DESTINATION ${FMT_CMAKE_DIR}) + + set(INSTALL_TARGETS fmt fmt-header-only) + + # Install the library and headers. + install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} + LIBRARY DESTINATION ${FMT_LIB_DIR} + ARCHIVE DESTINATION ${FMT_LIB_DIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + + # Use a namespace because CMake provides better diagnostics for namespaced + # imported targets. + export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt:: + FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) + + # Install version, config and target files. + install( + FILES ${project_config} ${version_config} + DESTINATION ${FMT_CMAKE_DIR}) + install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} + NAMESPACE fmt::) + + install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}> + DESTINATION ${FMT_LIB_DIR} OPTIONAL) + install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt") + install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}") +endif () + +if (FMT_DOC) + add_subdirectory(doc) +endif () + +if (FMT_TEST) + enable_testing() + add_subdirectory(test) +endif () + +# Control fuzzing independent of the unit tests. +if (FMT_FUZZ) + add_subdirectory(test/fuzzing) + + # The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing + # mode and make fuzzing practically possible. It is similar to + # FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to + # avoid interfering with fuzzing of projects that use {fmt}. + # See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode. + target_compile_definitions(fmt PUBLIC FMT_FUZZ) +endif () + +set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore) +if (FMT_MASTER_PROJECT AND EXISTS ${gitignore}) + # Get the list of ignored files from .gitignore. + file (STRINGS ${gitignore} lines) + list(REMOVE_ITEM lines /doc/html) + foreach (line ${lines}) + string(REPLACE "." "[.]" line "${line}") + string(REPLACE "*" ".*" line "${line}") + set(ignored_files ${ignored_files} "${line}$" "${line}/") + endforeach () + set(ignored_files ${ignored_files} + /.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees) + + set(CPACK_SOURCE_GENERATOR ZIP) + set(CPACK_SOURCE_IGNORE_FILES ${ignored_files}) + set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION}) + set(CPACK_PACKAGE_NAME fmt) + set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst) + include(CPack) +endif () \ No newline at end of file diff --git a/libs/fmt/CONTRIBUTING.md b/libs/fmt/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..b82f145069a8c61601ce12604b00fd06a8050488 --- /dev/null +++ b/libs/fmt/CONTRIBUTING.md @@ -0,0 +1,20 @@ +Contributing to {fmt} +===================== + +By submitting a pull request or a patch, you represent that you have the right +to license your contribution to the {fmt} project owners and the community, +agree that your contributions are licensed under the {fmt} license, and agree +to future changes to the licensing. + +All C++ code must adhere to [Google C++ Style Guide]( +https://google.github.io/styleguide/cppguide.html) with the following +exceptions: + +* Exceptions are permitted +* snake_case should be used instead of UpperCamelCase for function and type + names + +All documentation must adhere to the [Google Developer Documentation Style +Guide](https://developers.google.com/style). + +Thanks for contributing! diff --git a/libs/fmt/ChangeLog.rst b/libs/fmt/ChangeLog.rst new file mode 100644 index 0000000000000000000000000000000000000000..9c171af0505955ecabd835f76d1a48ed2c17c9d8 --- /dev/null +++ b/libs/fmt/ChangeLog.rst @@ -0,0 +1,3665 @@ +7.1.3 - 2020-11-24 +------------------ + +* Fixed handling of buffer boundaries in ``format_to_n`` + (`#1996 <https://github.com/fmtlib/fmt/issues/1996>`_, + `#2029 <https://github.com/fmtlib/fmt/issues/2029>`_). + +* Fixed linkage errors when linking with a shared library + (`#2011 <https://github.com/fmtlib/fmt/issues/2011>`_). + +* Reintroduced ostream support to range formatters + (`#2014 <https://github.com/fmtlib/fmt/issues/2014>`_). + +* Worked around an issue with mixing std versions in gcc + (`#2017 <https://github.com/fmtlib/fmt/issues/2017>`_). + +7.1.2 - 2020-11-04 +------------------ + +* Fixed floating point formatting with large precision + (`#1976 <https://github.com/fmtlib/fmt/issues/1976>`_). + +7.1.1 - 2020-11-01 +------------------ + +* Fixed ABI compatibility with 7.0.x + (`#1961 <https://github.com/fmtlib/fmt/issues/1961>`_). + +* Added the ``FMT_ARM_ABI_COMPATIBILITY`` macro to work around ABI + incompatibility between GCC and Clang on ARM + (`#1919 <https://github.com/fmtlib/fmt/issues/1919>`_). + +* Worked around a SFINAE bug in GCC 8 + (`#1957 <https://github.com/fmtlib/fmt/issues/1957>`_). + +* Fixed linkage errors when building with GCC's LTO + (`#1955 <https://github.com/fmtlib/fmt/issues/1955>`_). + +* Fixed a compilation error when building without ``__builtin_clz`` or equivalent + (`#1968 <https://github.com/fmtlib/fmt/pull/1968>`_). + Thanks `@tohammer (Tobias Hammer) <https://github.com/tohammer>`_. + +* Fixed a sign conversion warning + (`#1964 <https://github.com/fmtlib/fmt/pull/1964>`_). + Thanks `@OptoCloud <https://github.com/OptoCloud>`_. + +7.1.0 - 2020-10-25 +------------------ + +* Switched from `Grisu3 + <https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf>`_ + to `Dragonbox <https://github.com/jk-jeon/dragonbox>`_ for the default + floating-point formatting which gives the shortest decimal representation + with round-trip guarantee and correct rounding + (`#1882 <https://github.com/fmtlib/fmt/pull/1882>`_, + `#1887 <https://github.com/fmtlib/fmt/pull/1887>`_, + `#1894 <https://github.com/fmtlib/fmt/pull/1894>`_). This makes {fmt} up to + 20-30x faster than common implementations of ``std::ostringstream`` and + ``sprintf`` on `dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_ + and faster than double-conversion and Ryū: + + .. image:: https://user-images.githubusercontent.com/576385/ + 95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png + + It is possible to get even better performance at the cost of larger binary + size by compiling with the ``FMT_USE_FULL_CACHE_DRAGONBOX`` macro set to 1. + + Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_. + +* Added an experimental unsynchronized file output API which, together with + `format string compilation <https://fmt.dev/latest/api.html#compile-api>`_, + can give `5-9 times speed up compared to fprintf + <https://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_ + on common platforms (`godbolt <https://godbolt.org/z/nsTcG8>`__): + + .. code:: c++ + + #include <fmt/os.h> + + int main() { + auto f = fmt::output_file("guide"); + f.print("The answer is {}.", 42); + } + +* Added a formatter for ``std::chrono::time_point<system_clock>`` + (`#1819 <https://github.com/fmtlib/fmt/issues/1819>`_, + `#1837 <https://github.com/fmtlib/fmt/pull/1837>`_). For example + (`godbolt <https://godbolt.org/z/c4M6fh>`__): + + .. code:: c++ + + #include <fmt/chrono.h> + + int main() { + auto now = std::chrono::system_clock::now(); + fmt::print("The time is {:%H:%M:%S}.\n", now); + } + + Thanks `@adamburgess (Adam Burgess) <https://github.com/adamburgess>`_. + +* Added support for ranges with non-const ``begin``/``end`` to ``fmt::join`` + (`#1784 <https://github.com/fmtlib/fmt/issues/1784>`_, + `#1786 <https://github.com/fmtlib/fmt/pull/1786>`_). For example + (`godbolt <https://godbolt.org/z/jP63Tv>`__): + + .. code:: c++ + + #include <fmt/ranges.h> + #include <range/v3/view/filter.hpp> + + int main() { + using std::literals::string_literals::operator""s; + auto strs = std::array{"a"s, "bb"s, "ccc"s}; + auto range = strs | ranges::views::filter( + [] (const std::string &x) { return x.size() != 2; } + ); + fmt::print("{}\n", fmt::join(range, "")); + } + + prints "accc". + + Thanks `@tonyelewis (Tony E Lewis) <https://github.com/tonyelewis>`_. + +* Added a ``memory_buffer::append`` overload that takes a range + (`#1806 <https://github.com/fmtlib/fmt/pull/1806>`_). + Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_. + +* Improved handling of single code units in ``FMT_COMPILE``. For example: + + .. code:: c++ + + #include <fmt/compile.h> + + char* f(char* buf) { + return fmt::format_to(buf, FMT_COMPILE("x{}"), 42); + } + + compiles to just (`godbolt <https://godbolt.org/z/5vncz3>`__): + + .. code:: asm + + _Z1fPc: + movb $120, (%rdi) + xorl %edx, %edx + cmpl $42, _ZN3fmt2v76detail10basic_dataIvE23zero_or_powers_of_10_32E+8(%rip) + movl $3, %eax + seta %dl + subl %edx, %eax + movzwl _ZN3fmt2v76detail10basic_dataIvE6digitsE+84(%rip), %edx + cltq + addq %rdi, %rax + movw %dx, -2(%rax) + ret + + Here a single ``mov`` instruction writes ``'x'`` (``$120``) to the output + buffer. + +* Added dynamic width support to format string compilation + (`#1809 <https://github.com/fmtlib/fmt/issues/1809>`_). + +* Improved error reporting for unformattable types: now you'll get the type name + directly in the error message instead of the note: + + .. code:: c++ + + #include <fmt/core.h> + + struct how_about_no {}; + + int main() { + fmt::print("{}", how_about_no()); + } + + Error (`godbolt <https://godbolt.org/z/GoxM4e>`__): + + ``fmt/core.h:1438:3: error: static_assert failed due to requirement + 'fmt::v7::formattable<how_about_no>()' "Cannot format an argument. + To make type T formattable provide a formatter<T> specialization: + https://fmt.dev/latest/api.html#udt" + ...`` + +* Added the `make_args_checked <https://fmt.dev/7.1.0/api.html#argument-lists>`_ + function template that allows you to write formatting functions with + compile-time format string checks and avoid binary code bloat + (`godbolt <https://godbolt.org/z/PEf9qr>`__): + + .. code:: c++ + + void vlog(const char* file, int line, fmt::string_view format, + fmt::format_args args) { + fmt::print("{}: {}: ", file, line); + fmt::vprint(format, args); + } + + template <typename S, typename... Args> + void log(const char* file, int line, const S& format, Args&&... args) { + vlog(file, line, format, + fmt::make_args_checked<Args...>(format, args...)); + } + + #define MY_LOG(format, ...) \ + log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__) + + MY_LOG("invalid squishiness: {}", 42); + +* Replaced ``snprintf`` fallback with a faster internal IEEE 754 ``float`` and + ``double`` formatter for arbitrary precision. For example + (`godbolt <https://godbolt.org/z/dPhWvj>`__): + + .. code:: c++ + + #include <fmt/core.h> + + int main() { + fmt::print("{:.500}\n", 4.9406564584124654E-324); + } + + prints + + ``4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599237979656469544571773092665671035593979639877479601078187812630071319031140452784581716784898210368871863605699873072305000638740915356498438731247339727316961514003171538539807412623856559117102665855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332452475714786901472678015935523861155013480352649347201937902681071074917033322268447533357208324319360923829e-324``. + +* Made ``format_to_n`` and ``formatted_size`` part of the `core API + <https://fmt.dev/latest/api.html#core-api>`__ + (`godbolt <https://godbolt.org/z/sPjY1K>`__): + + .. code:: c++ + + #include <fmt/core.h> + + int main() { + char buffer[10]; + auto result = fmt::format_to_n(buffer, sizeof(buffer), "{}", 42); + } + +* Added ``fmt::format_to_n`` overload with format string compilation + (`#1764 <https://github.com/fmtlib/fmt/issues/1764>`_, + `#1767 <https://github.com/fmtlib/fmt/pull/1767>`_, + `#1869 <https://github.com/fmtlib/fmt/pull/1869>`_). For example + (`godbolt <https://godbolt.org/z/93h86q>`__): + + .. code:: c++ + + #include <fmt/compile.h> + + int main() { + char buffer[8]; + fmt::format_to_n(buffer, sizeof(buffer), FMT_COMPILE("{}"), 42); + } + + Thanks `@Kurkin (Dmitry Kurkin) <https://github.com/Kurkin>`_, + `@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_. + +* Added ``fmt::format_to`` overload that take ``text_style`` + (`#1593 <https://github.com/fmtlib/fmt/issues/1593>`_, + `#1842 <https://github.com/fmtlib/fmt/issues/1842>`_, + `#1843 <https://github.com/fmtlib/fmt/pull/1843>`_). For example + (`godbolt <https://godbolt.org/z/91153r>`__): + + .. code:: c++ + + #include <fmt/color.h> + + int main() { + std::string out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), + "The answer is {}.", 42); + } + + Thanks `@Naios (Denis Blank) <https://github.com/Naios>`_. + +* Made the ``#`` specifier emit trailing zeros in addition to the decimal point + (`#1797 <https://github.com/fmtlib/fmt/issues/1797>`_). For example + (`godbolt <https://godbolt.org/z/bhdcW9>`__): + + .. code:: c++ + + #include <fmt/core.h> + + int main() { + fmt::print("{:#.2g}", 0.5); + } + + prints ``0.50``. + +* Changed the default floating point format to not include ``.0`` for + consistency with ``std::format`` and ``std::to_chars`` + (`#1893 <https://github.com/fmtlib/fmt/issues/1893>`_, + `#1943 <https://github.com/fmtlib/fmt/issues/1943>`_). It is possible to get + the decimal point and trailing zero with the ``#`` specifier. + +* Fixed an issue with floating-point formatting that could result in addition of + a non-significant trailing zero in rare cases e.g. ``1.00e-34`` instead of + ``1.0e-34`` (`#1873 <https://github.com/fmtlib/fmt/issues/1873>`_, + `#1917 <https://github.com/fmtlib/fmt/issues/1917>`_). + +* Made ``fmt::to_string`` fallback on ``ostream`` insertion operator if + the ``formatter`` specialization is not provided + (`#1815 <https://github.com/fmtlib/fmt/issues/1815>`_, + `#1829 <https://github.com/fmtlib/fmt/pull/1829>`_). + Thanks `@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_. + +* Added support for the append mode to the experimental file API and + improved ``fcntl.h`` detection. + (`#1847 <https://github.com/fmtlib/fmt/pull/1847>`_, + `#1848 <https://github.com/fmtlib/fmt/pull/1848>`_). + Thanks `@t-wiser <https://github.com/t-wiser>`_. + +* Fixed handling of types that have both an implicit conversion operator and + an overloaded ``ostream`` insertion operator + (`#1766 <https://github.com/fmtlib/fmt/issues/1766>`_). + +* Fixed a slicing issue in an internal iterator type + (`#1822 <https://github.com/fmtlib/fmt/pull/1822>`_). + Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_. + +* Fixed an issue in locale-specific integer formatting + (`#1927 <https://github.com/fmtlib/fmt/issues/1927>`_). + +* Fixed handling of exotic code unit types + (`#1870 <https://github.com/fmtlib/fmt/issues/1870>`_, + `#1932 <https://github.com/fmtlib/fmt/issues/1932>`_). + +* Improved ``FMT_ALWAYS_INLINE`` + (`#1878 <https://github.com/fmtlib/fmt/pull/1878>`_). + Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_. + +* Removed dependency on ``windows.h`` + (`#1900 <https://github.com/fmtlib/fmt/pull/1900>`_). + Thanks `@bernd5 (Bernd Baumanns) <https://github.com/bernd5>`_. + +* Optimized counting of decimal digits on MSVC + (`#1890 <https://github.com/fmtlib/fmt/pull/1890>`_). + Thanks `@mwinterb <https://github.com/mwinterb>`_. + +* Improved documentation + (`#1772 <https://github.com/fmtlib/fmt/issues/1772>`_, + `#1775 <https://github.com/fmtlib/fmt/pull/1775>`_, + `#1792 <https://github.com/fmtlib/fmt/pull/1792>`_, + `#1838 <https://github.com/fmtlib/fmt/pull/1838>`_, + `#1888 <https://github.com/fmtlib/fmt/pull/1888>`_, + `#1918 <https://github.com/fmtlib/fmt/pull/1918>`_, + `#1939 <https://github.com/fmtlib/fmt/pull/1939>`_). + Thanks `@leolchat (Léonard Gérard) <https://github.com/leolchat>`_, + `@pepsiman (Malcolm Parsons) <https://github.com/pepsiman>`_, + `@Klaim (Joël Lamotte) <https://github.com/Klaim>`_, + `@ravijanjam (Ravi J) <https://github.com/ravijanjam>`_, + `@francesco-st <https://github.com/francesco-st>`_, + `@udnaan (Adnan) <https://github.com/udnaan>`_. + +* Added the ``FMT_REDUCE_INT_INSTANTIATIONS`` CMake option that reduces the + binary code size at the cost of some integer formatting performance. This can + be useful for extremely memory-constrained embedded systems + (`#1778 <https://github.com/fmtlib/fmt/issues/1778>`_, + `#1781 <https://github.com/fmtlib/fmt/pull/1781>`_). + Thanks `@kammce (Khalil Estell) <https://github.com/kammce>`_. + +* Added the ``FMT_USE_INLINE_NAMESPACES`` macro to control usage of inline + namespaces (`#1945 <https://github.com/fmtlib/fmt/pull/1945>`_). + Thanks `@darklukee <https://github.com/darklukee>`_. + +* Improved build configuration + (`#1760 <https://github.com/fmtlib/fmt/pull/1760>`_, + `#1770 <https://github.com/fmtlib/fmt/pull/1770>`_, + `#1779 <https://github.com/fmtlib/fmt/issues/1779>`_, + `#1783 <https://github.com/fmtlib/fmt/pull/1783>`_, + `#1823 <https://github.com/fmtlib/fmt/pull/1823>`_). + Thanks `@dvetutnev (Dmitriy Vetutnev) <https://github.com/dvetutnev>`_, + `@xvitaly (Vitaly Zaitsev) <https://github.com/xvitaly>`_, + `@tambry (Raul Tambre) <https://github.com/tambry>`_, + `@medithe <https://github.com/medithe>`_, + `@martinwuehrer (Martin Wührer) <https://github.com/martinwuehrer>`_. + +* Fixed various warnings and compilation issues + (`#1790 <https://github.com/fmtlib/fmt/pull/1790>`_, + `#1802 <https://github.com/fmtlib/fmt/pull/1802>`_, + `#1808 <https://github.com/fmtlib/fmt/pull/1808>`_, + `#1810 <https://github.com/fmtlib/fmt/issues/1810>`_, + `#1811 <https://github.com/fmtlib/fmt/issues/1811>`_, + `#1812 <https://github.com/fmtlib/fmt/pull/1812>`_, + `#1814 <https://github.com/fmtlib/fmt/pull/1814>`_, + `#1816 <https://github.com/fmtlib/fmt/pull/1816>`_, + `#1817 <https://github.com/fmtlib/fmt/pull/1817>`_, + `#1818 <https://github.com/fmtlib/fmt/pull/1818>`_, + `#1825 <https://github.com/fmtlib/fmt/issues/1825>`_, + `#1836 <https://github.com/fmtlib/fmt/pull/1836>`_, + `#1855 <https://github.com/fmtlib/fmt/pull/1855>`_, + `#1856 <https://github.com/fmtlib/fmt/pull/1856>`_, + `#1860 <https://github.com/fmtlib/fmt/pull/1860>`_, + `#1877 <https://github.com/fmtlib/fmt/pull/1877>`_, + `#1879 <https://github.com/fmtlib/fmt/pull/1879>`_, + `#1880 <https://github.com/fmtlib/fmt/pull/1880>`_, + `#1896 <https://github.com/fmtlib/fmt/issues/1896>`_, + `#1897 <https://github.com/fmtlib/fmt/pull/1897>`_, + `#1898 <https://github.com/fmtlib/fmt/pull/1898>`_, + `#1904 <https://github.com/fmtlib/fmt/issues/1904>`_, + `#1908 <https://github.com/fmtlib/fmt/pull/1908>`_, + `#1911 <https://github.com/fmtlib/fmt/issues/1911>`_, + `#1912 <https://github.com/fmtlib/fmt/issues/1912>`_, + `#1928 <https://github.com/fmtlib/fmt/issues/1928>`_, + `#1929 <https://github.com/fmtlib/fmt/pull/1929>`_, + `#1935 <https://github.com/fmtlib/fmt/issues/1935>`_ + `#1937 <https://github.com/fmtlib/fmt/pull/1937>`_, + `#1942 <https://github.com/fmtlib/fmt/pull/1942>`_, + `#1949 <https://github.com/fmtlib/fmt/issues/1949>`_). + Thanks `@TheQwertiest <https://github.com/TheQwertiest>`_, + `@medithe <https://github.com/medithe>`_, + `@martinwuehrer (Martin Wührer) <https://github.com/martinwuehrer>`_, + `@n16h7hunt3r <https://github.com/n16h7hunt3r>`_, + `@Othereum (Seokjin Lee) <https://github.com/Othereum>`_, + `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_, + `@AlexanderLanin (Alexander Lanin) <https://github.com/AlexanderLanin>`_, + `@gcerretani (Giovanni Cerretani) <https://github.com/gcerretani>`_, + `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_, + `@noizefloor (Jan Schwers) <https://github.com/noizefloor>`_, + `@akohlmey (Axel Kohlmeyer) <https://github.com/akohlmey>`_, + `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_, + `@rimathia <https://github.com/rimathia>`_, + `@rglarix (Riccardo Ghetta (larix)) <https://github.com/rglarix>`_, + `@moiwi <https://github.com/moiwi>`_, + `@heckad (Kazantcev Andrey) <https://github.com/heckad>`_, + `@MarcDirven <https://github.com/MarcDirven>`_. + `@BartSiwek (Bart Siwek) <https://github.com/BartSiwek>`_, + `@darklukee <https://github.com/darklukee>`_. + +7.0.3 - 2020-08-06 +------------------ + +* Worked around broken ``numeric_limits`` for 128-bit integers + (`#1787 <https://github.com/fmtlib/fmt/issues/1787>`_). + +* Added error reporting on missing named arguments + (`#1796 <https://github.com/fmtlib/fmt/issues/1796>`_). + +* Stopped using 128-bit integers with clang-cl + (`#1800 <https://github.com/fmtlib/fmt/pull/1800>`_). + Thanks `@Kingcom <https://github.com/Kingcom>`_. + +* Fixed issues in locale-specific integer formatting + (`#1782 <https://github.com/fmtlib/fmt/issues/1782>`_, + `#1801 <https://github.com/fmtlib/fmt/issues/1801>`_). + +7.0.2 - 2020-07-29 +------------------ + +* Worked around broken ``numeric_limits`` for 128-bit integers + (`#1725 <https://github.com/fmtlib/fmt/issues/1725>`_). + +* Fixed compatibility with CMake 3.4 + (`#1779 <https://github.com/fmtlib/fmt/issues/1779>`_). + +* Fixed handling of digit separators in locale-specific formatting + (`#1782 <https://github.com/fmtlib/fmt/issues/1782>`_). + +7.0.1 - 2020-07-07 +------------------ + +* Updated the inline version namespace name. + +* Worked around a gcc bug in mangling of alias templates + (`#1753 <https://github.com/fmtlib/fmt/issues/1753>`_). + +* Fixed a linkage error on Windows + (`#1757 <https://github.com/fmtlib/fmt/issues/1757>`_). + Thanks `@Kurkin (Dmitry Kurkin) <https://github.com/Kurkin>`_. + +* Fixed minor issues with the documentation. + +7.0.0 - 2020-07-05 +------------------ + +* Reduced the library size. For example, on macOS a stripped test binary + statically linked with {fmt} `shrank from ~368k to less than 100k + <http://www.zverovich.net/2020/05/21/reducing-library-size.html>`_. + +* Added a simpler and more efficient `format string compilation API + <https://fmt.dev/7.0.0/api.html#compile-api>`_: + + .. code:: c++ + + #include <fmt/compile.h> + + // Converts 42 into std::string using the most efficient method and no + // runtime format string processing. + std::string s = fmt::format(FMT_COMPILE("{}"), 42); + + The old ``fmt::compile`` API is now deprecated. + +* Optimized integer formatting: ``format_to`` with format string compilation + and a stack-allocated buffer is now `faster than to_chars on both + libc++ and libstdc++ + <http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_. + +* Optimized handling of small format strings. For example, + + .. code:: c++ + + fmt::format("Result: {}: ({},{},{},{})", str1, str2, str3, str4, str5) + + is now ~40% faster (`#1685 <https://github.com/fmtlib/fmt/issues/1685>`_). + +* Applied extern templates to improve compile times when using the core API + and ``fmt/format.h`` (`#1452 <https://github.com/fmtlib/fmt/issues/1452>`_). + For example, on macOS with clang the compile time of a test translation unit + dropped from 2.3s to 0.3s with ``-O2`` and from 0.6s to 0.3s with the default + settings (``-O0``). + + Before (``-O2``):: + + % time c++ -c test.cc -I include -std=c++17 -O2 + c++ -c test.cc -I include -std=c++17 -O2 2.22s user 0.08s system 99% cpu 2.311 total + + After (``-O2``):: + + % time c++ -c test.cc -I include -std=c++17 -O2 + c++ -c test.cc -I include -std=c++17 -O2 0.26s user 0.04s system 98% cpu 0.303 total + + Before (default):: + + % time c++ -c test.cc -I include -std=c++17 + c++ -c test.cc -I include -std=c++17 0.53s user 0.06s system 98% cpu 0.601 total + + After (default):: + + % time c++ -c test.cc -I include -std=c++17 + c++ -c test.cc -I include -std=c++17 0.24s user 0.06s system 98% cpu 0.301 total + + It is still recommended to use ``fmt/core.h`` instead of ``fmt/format.h`` but + the compile time difference is now smaller. Thanks + `@alex3d <https://github.com/alex3d>`_ for the suggestion. + +* Named arguments are now stored on stack (no dynamic memory allocations) and + the compiled code is more compact and efficient. For example + + .. code:: c++ + + #include <fmt/core.h> + + int main() { + fmt::print("The answer is {answer}\n", fmt::arg("answer", 42)); + } + + compiles to just (`godbolt <https://godbolt.org/z/NcfEp_>`__) + + .. code:: asm + + .LC0: + .string "answer" + .LC1: + .string "The answer is {answer}\n" + main: + sub rsp, 56 + mov edi, OFFSET FLAT:.LC1 + mov esi, 23 + movabs rdx, 4611686018427387905 + lea rax, [rsp+32] + lea rcx, [rsp+16] + mov QWORD PTR [rsp+8], 1 + mov QWORD PTR [rsp], rax + mov DWORD PTR [rsp+16], 42 + mov QWORD PTR [rsp+32], OFFSET FLAT:.LC0 + mov DWORD PTR [rsp+40], 0 + call fmt::v6::vprint(fmt::v6::basic_string_view<char>, + fmt::v6::format_args) + xor eax, eax + add rsp, 56 + ret + + .L.str.1: + .asciz "answer" + +* Implemented compile-time checks for dynamic width and precision + (`#1614 <https://github.com/fmtlib/fmt/issues/1614>`_): + + .. code:: c++ + + #include <fmt/format.h> + + int main() { + fmt::print(FMT_STRING("{0:{1}}"), 42); + } + + now gives a compilation error because argument 1 doesn't exist:: + + In file included from test.cc:1: + include/fmt/format.h:2726:27: error: constexpr variable 'invalid_format' must be + initialized by a constant expression + FMT_CONSTEXPR_DECL bool invalid_format = + ^ + ... + include/fmt/core.h:569:26: note: in call to + '&checker(s, {}).context_->on_error(&"argument not found"[0])' + if (id >= num_args_) on_error("argument not found"); + ^ + +* Added sentinel support to ``fmt::join`` + (`#1689 <https://github.com/fmtlib/fmt/pull/1689>`_) + + .. code:: c++ + + struct zstring_sentinel {}; + bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; } + bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; } + + struct zstring { + const char* p; + const char* begin() const { return p; } + zstring_sentinel end() const { return {}; } + }; + + auto s = fmt::format("{}", fmt::join(zstring{"hello"}, "_")); + // s == "h_e_l_l_o" + + Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_. + +* Added support for named arguments, ``clear`` and ``reserve`` to + ``dynamic_format_arg_store`` + (`#1655 <https://github.com/fmtlib/fmt/issues/1655>`_, + `#1663 <https://github.com/fmtlib/fmt/pull/1663>`_, + `#1674 <https://github.com/fmtlib/fmt/pull/1674>`_, + `#1677 <https://github.com/fmtlib/fmt/pull/1677>`_). + Thanks `@vsolontsov-ll (Vladimir Solontsov) + <https://github.com/vsolontsov-ll>`_. + +* Added support for the ``'c'`` format specifier to integral types for + compatibility with ``std::format`` + (`#1652 <https://github.com/fmtlib/fmt/issues/1652>`_). + +* Replaced the ``'n'`` format specifier with ``'L'`` for compatibility with + ``std::format`` (`#1624 <https://github.com/fmtlib/fmt/issues/1624>`_). + The ``'n'`` specifier can be enabled via the ``FMT_DEPRECATED_N_SPECIFIER`` + macro. + +* The ``'='`` format specifier is now disabled by default for compatibility with + ``std::format``. It can be enabled via the ``FMT_DEPRECATED_NUMERIC_ALIGN`` + macro. + +* Removed the following deprecated APIs: + + * ``FMT_STRING_ALIAS`` and ``fmt`` macros - replaced by ``FMT_STRING`` + * ``fmt::basic_string_view::char_type`` - replaced by + ``fmt::basic_string_view::value_type`` + * ``convert_to_int`` + * ``format_arg_store::types`` + * ``*parse_context`` - replaced by ``*format_parse_context`` + * ``FMT_DEPRECATED_INCLUDE_OS`` + * ``FMT_DEPRECATED_PERCENT`` - incompatible with ``std::format`` + * ``*writer`` - replaced by compiled format API + +* Renamed the ``internal`` namespace to ``detail`` + (`#1538 <https://github.com/fmtlib/fmt/issues/1538>`_). The former is still + provided as an alias if the ``FMT_USE_INTERNAL`` macro is defined. + +* Improved compatibility between ``fmt::printf`` with the standard specs + (`#1595 <https://github.com/fmtlib/fmt/issues/1595>`_, + `#1682 <https://github.com/fmtlib/fmt/pull/1682>`_, + `#1683 <https://github.com/fmtlib/fmt/pull/1683>`_, + `#1687 <https://github.com/fmtlib/fmt/pull/1687>`_, + `#1699 <https://github.com/fmtlib/fmt/pull/1699>`_). + Thanks `@rimathia <https://github.com/rimathia>`_. + +* Fixed handling of ``operator<<`` overloads that use ``copyfmt`` + (`#1666 <https://github.com/fmtlib/fmt/issues/1666>`_). + +* Added the ``FMT_OS`` CMake option to control inclusion of OS-specific APIs + in the fmt target. This can be useful for embedded platforms + (`#1654 <https://github.com/fmtlib/fmt/issues/1654>`_, + `#1656 <https://github.com/fmtlib/fmt/pull/1656>`_). + Thanks `@kwesolowski (Krzysztof Wesolowski) + <https://github.com/kwesolowski>`_. + +* Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ`` + macro to prevent interferring with fuzzing of projects using {fmt} + (`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_). + Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_. + +* Fixed compatibility with emscripten + (`#1636 <https://github.com/fmtlib/fmt/issues/1636>`_, + `#1637 <https://github.com/fmtlib/fmt/pull/1637>`_). + Thanks `@ArthurSonzogni (Arthur Sonzogni) + <https://github.com/ArthurSonzogni>`_. + +* Improved documentation + (`#704 <https://github.com/fmtlib/fmt/issues/704>`_, + `#1643 <https://github.com/fmtlib/fmt/pull/1643>`_, + `#1660 <https://github.com/fmtlib/fmt/pull/1660>`_, + `#1681 <https://github.com/fmtlib/fmt/pull/1681>`_, + `#1691 <https://github.com/fmtlib/fmt/pull/1691>`_, + `#1706 <https://github.com/fmtlib/fmt/pull/1706>`_, + `#1714 <https://github.com/fmtlib/fmt/pull/1714>`_, + `#1721 <https://github.com/fmtlib/fmt/pull/1721>`_, + `#1739 <https://github.com/fmtlib/fmt/pull/1739>`_, + `#1740 <https://github.com/fmtlib/fmt/pull/1740>`_, + `#1741 <https://github.com/fmtlib/fmt/pull/1741>`_, + `#1751 <https://github.com/fmtlib/fmt/pull/1751>`_). + Thanks `@senior7515 (Alexander Gallego) <https://github.com/senior7515>`_, + `@lsr0 (Lindsay Roberts) <https://github.com/lsr0>`_, + `@puetzk (Kevin Puetz) <https://github.com/puetzk>`_, + `@fpelliccioni (Fernando Pelliccioni) <https://github.com/fpelliccioni>`_, + Alexey Kuzmenko, `@jelly (jelle van der Waa) <https://github.com/jelly>`_, + `@claremacrae (Clare Macrae) <https://github.com/claremacrae>`_, + `@jiapengwen (文佳鹏) <https://github.com/jiapengwen>`_, + `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_, + `@alexey-milovidov <https://github.com/alexey-milovidov>`_. + +* Implemented various build configuration fixes and improvements + (`#1603 <https://github.com/fmtlib/fmt/pull/1603>`_, + `#1657 <https://github.com/fmtlib/fmt/pull/1657>`_, + `#1702 <https://github.com/fmtlib/fmt/pull/1702>`_, + `#1728 <https://github.com/fmtlib/fmt/pull/1728>`_). + Thanks `@scramsby (Scott Ramsby) <https://github.com/scramsby>`_, + `@jtojnar (Jan Tojnar) <https://github.com/jtojnar>`_, + `@orivej (Orivej Desh) <https://github.com/orivej>`_, + `@flagarde <https://github.com/flagarde>`_. + +* Fixed various warnings and compilation issues + (`#1616 <https://github.com/fmtlib/fmt/pull/1616>`_, + `#1620 <https://github.com/fmtlib/fmt/issues/1620>`_, + `#1622 <https://github.com/fmtlib/fmt/issues/1622>`_, + `#1625 <https://github.com/fmtlib/fmt/issues/1625>`_, + `#1627 <https://github.com/fmtlib/fmt/pull/1627>`_, + `#1628 <https://github.com/fmtlib/fmt/issues/1628>`_, + `#1629 <https://github.com/fmtlib/fmt/pull/1629>`_, + `#1631 <https://github.com/fmtlib/fmt/issues/1631>`_, + `#1633 <https://github.com/fmtlib/fmt/pull/1633>`_, + `#1649 <https://github.com/fmtlib/fmt/pull/1649>`_, + `#1658 <https://github.com/fmtlib/fmt/issues/1658>`_, + `#1661 <https://github.com/fmtlib/fmt/pull/1661>`_, + `#1667 <https://github.com/fmtlib/fmt/pull/1667>`_, + `#1668 <https://github.com/fmtlib/fmt/issues/1668>`_, + `#1669 <https://github.com/fmtlib/fmt/pull/1669>`_, + `#1692 <https://github.com/fmtlib/fmt/issues/1692>`_, + `#1696 <https://github.com/fmtlib/fmt/pull/1696>`_, + `#1697 <https://github.com/fmtlib/fmt/pull/1697>`_, + `#1707 <https://github.com/fmtlib/fmt/issues/1707>`_, + `#1712 <https://github.com/fmtlib/fmt/pull/1712>`_, + `#1716 <https://github.com/fmtlib/fmt/pull/1716>`_, + `#1722 <https://github.com/fmtlib/fmt/pull/1722>`_, + `#1724 <https://github.com/fmtlib/fmt/issues/1724>`_, + `#1729 <https://github.com/fmtlib/fmt/pull/1729>`_, + `#1738 <https://github.com/fmtlib/fmt/pull/1738>`_, + `#1742 <https://github.com/fmtlib/fmt/issues/1742>`_, + `#1743 <https://github.com/fmtlib/fmt/issues/1743>`_, + `#1744 <https://github.com/fmtlib/fmt/pull/1744>`_, + `#1747 <https://github.com/fmtlib/fmt/issues/1747>`_, + `#1750 <https://github.com/fmtlib/fmt/pull/1750>`_). + Thanks `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_, + `@gabime (Gabi Melman) <https://github.com/gabime>`_, + `@johnor (Johan) <https://github.com/johnor>`_, + `@Kurkin (Dmitry Kurkin) <https://github.com/Kurkin>`_, + `@invexed (James Beach) <https://github.com/invexed>`_, + `@peterbell10 <https://github.com/peterbell10>`_, + `@daixtrose (Markus Werle) <https://github.com/daixtrose>`_, + `@petrutlucian94 (Lucian Petrut) <https://github.com/petrutlucian94>`_, + `@Neargye (Daniil Goncharov) <https://github.com/Neargye>`_, + `@ambitslix (Attila M. Szilagyi) <https://github.com/ambitslix>`_, + `@gabime (Gabi Melman) <https://github.com/gabime>`_, + `@erthink (Leonid Yuriev) <https://github.com/erthink>`_, + `@tohammer (Tobias Hammer) <https://github.com/tohammer>`_, + `@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_. + +6.2.1 - 2020-05-09 +------------------ + +* Fixed ostream support in ``sprintf`` + (`#1631 <https://github.com/fmtlib/fmt/issues/1631>`_). + +* Fixed type detection when using implicit conversion to ``string_view`` and + ostream ``operator<<`` inconsistently + (`#1662 <https://github.com/fmtlib/fmt/issues/1662>`_). + +6.2.0 - 2020-04-05 +------------------ + +* Improved error reporting when trying to format an object of a non-formattable + type: + + .. code:: c++ + + fmt::format("{}", S()); + + now gives:: + + include/fmt/core.h:1015:5: error: static_assert failed due to requirement + 'formattable' "Cannot format argument. To make type T formattable provide a + formatter<T> specialization: + https://fmt.dev/latest/api.html#formatting-user-defined-types" + static_assert( + ^ + ... + note: in instantiation of function template specialization + 'fmt::v6::format<char [3], S, char>' requested here + fmt::format("{}", S()); + ^ + + if ``S`` is not formattable. + +* Reduced the library size by ~10%. + +* Always print decimal point if ``#`` is specified + (`#1476 <https://github.com/fmtlib/fmt/issues/1476>`_, + `#1498 <https://github.com/fmtlib/fmt/issues/1498>`_): + + .. code:: c++ + + fmt::print("{:#.0f}", 42.0); + + now prints ``42.`` + +* Implemented the ``'L'`` specifier for locale-specific numeric formatting to + improve compatibility with ``std::format``. The ``'n'`` specifier is now + deprecated and will be removed in the next major release. + +* Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to + ``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically + include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be + disabled in the next major release. + +* Added precision overflow detection in floating-point formatting. + +* Implemented detection of invalid use of ``fmt::arg``. + +* Used ``type_identity`` to block unnecessary template argument deduction. + Thanks Tim Song. + +* Improved UTF-8 handling + (`#1109 <https://github.com/fmtlib/fmt/issues/1109>`_): + + .. code:: c++ + + fmt::print("┌{0:─^{2}}┐\n" + "│{1: ^{2}}│\n" + "└{0:─^{2}}┘\n", "", "Привет, мир!", 20); + + now prints:: + + ┌────────────────────┐ + │ Привет, мир! │ + └────────────────────┘ + + on systems that support Unicode. + +* Added experimental dynamic argument storage + (`#1170 <https://github.com/fmtlib/fmt/issues/1170>`_, + `#1584 <https://github.com/fmtlib/fmt/pull/1584>`_): + + .. code:: c++ + + fmt::dynamic_format_arg_store<fmt::format_context> store; + store.push_back("answer"); + store.push_back(42); + fmt::vprint("The {} is {}.\n", store); + + prints:: + + The answer is 42. + + Thanks `@vsolontsov-ll (Vladimir Solontsov) + <https://github.com/vsolontsov-ll>`_. + +* Made ``fmt::join`` accept ``initializer_list`` + (`#1591 <https://github.com/fmtlib/fmt/pull/1591>`_). + Thanks `@Rapotkinnik (Nikolay Rapotkin) <https://github.com/Rapotkinnik>`_. + +* Fixed handling of empty tuples + (`#1588 <https://github.com/fmtlib/fmt/issues/1588>`_). + +* Fixed handling of output iterators in ``format_to_n`` + (`#1506 <https://github.com/fmtlib/fmt/issues/1506>`_). + +* Fixed formatting of ``std::chrono::duration`` types to wide output + (`#1533 <https://github.com/fmtlib/fmt/pull/1533>`_). + Thanks `@zeffy (pilao) <https://github.com/zeffy>`_. + +* Added const ``begin`` and ``end`` overload to buffers + (`#1553 <https://github.com/fmtlib/fmt/pull/1553>`_). + Thanks `@dominicpoeschko <https://github.com/dominicpoeschko>`_. + +* Added the ability to disable floating-point formatting via ``FMT_USE_FLOAT``, + ``FMT_USE_DOUBLE`` and ``FMT_USE_LONG_DOUBLE`` macros for extremely + memory-constrained embedded system + (`#1590 <https://github.com/fmtlib/fmt/pull/1590>`_). + Thanks `@albaguirre (Alberto Aguirre) <https://github.com/albaguirre>`_. + +* Made ``FMT_STRING`` work with ``constexpr`` ``string_view`` + (`#1589 <https://github.com/fmtlib/fmt/pull/1589>`_). + Thanks `@scramsby (Scott Ramsby) <https://github.com/scramsby>`_. + +* Implemented a minor optimization in the format string parser + (`#1560 <https://github.com/fmtlib/fmt/pull/1560>`_). + Thanks `@IkarusDeveloper <https://github.com/IkarusDeveloper>`_. + +* Improved attribute detection + (`#1469 <https://github.com/fmtlib/fmt/pull/1469>`_, + `#1475 <https://github.com/fmtlib/fmt/pull/1475>`_, + `#1576 <https://github.com/fmtlib/fmt/pull/1576>`_). + Thanks `@federico-busato (Federico) <https://github.com/federico-busato>`_, + `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_, + `@refnum <https://github.com/refnum>`_. + +* Improved documentation + (`#1481 <https://github.com/fmtlib/fmt/pull/1481>`_, + `#1523 <https://github.com/fmtlib/fmt/pull/1523>`_). + Thanks `@JackBoosY (Jack·Boos·Yu) <https://github.com/JackBoosY>`_, + `@imba-tjd (谭九鼎) <https://github.com/imba-tjd>`_. + +* Fixed symbol visibility on Linux when compiling with ``-fvisibility=hidden`` + (`#1535 <https://github.com/fmtlib/fmt/pull/1535>`_). + Thanks `@milianw (Milian Wolff) <https://github.com/milianw>`_. + +* Implemented various build configuration fixes and improvements + (`#1264 <https://github.com/fmtlib/fmt/issues/1264>`_, + `#1460 <https://github.com/fmtlib/fmt/issues/1460>`_, + `#1534 <https://github.com/fmtlib/fmt/pull/1534>`_, + `#1536 <https://github.com/fmtlib/fmt/issues/1536>`_, + `#1545 <https://github.com/fmtlib/fmt/issues/1545>`_, + `#1546 <https://github.com/fmtlib/fmt/pull/1546>`_, + `#1566 <https://github.com/fmtlib/fmt/issues/1566>`_, + `#1582 <https://github.com/fmtlib/fmt/pull/1582>`_, + `#1597 <https://github.com/fmtlib/fmt/issues/1597>`_, + `#1598 <https://github.com/fmtlib/fmt/pull/1598>`_). + Thanks `@ambitslix (Attila M. Szilagyi) <https://github.com/ambitslix>`_, + `@jwillikers (Jordan Williams) <https://github.com/jwillikers>`_, + `@stac47 (Laurent Stacul) <https://github.com/stac47>`_. + +* Fixed various warnings and compilation issues + (`#1433 <https://github.com/fmtlib/fmt/pull/1433>`_, + `#1461 <https://github.com/fmtlib/fmt/issues/1461>`_, + `#1470 <https://github.com/fmtlib/fmt/pull/1470>`_, + `#1480 <https://github.com/fmtlib/fmt/pull/1480>`_, + `#1485 <https://github.com/fmtlib/fmt/pull/1485>`_, + `#1492 <https://github.com/fmtlib/fmt/pull/1492>`_, + `#1493 <https://github.com/fmtlib/fmt/issues/1493>`_, + `#1504 <https://github.com/fmtlib/fmt/issues/1504>`_, + `#1505 <https://github.com/fmtlib/fmt/pull/1505>`_, + `#1512 <https://github.com/fmtlib/fmt/pull/1512>`_, + `#1515 <https://github.com/fmtlib/fmt/issues/1515>`_, + `#1516 <https://github.com/fmtlib/fmt/pull/1516>`_, + `#1518 <https://github.com/fmtlib/fmt/pull/1518>`_, + `#1519 <https://github.com/fmtlib/fmt/pull/1519>`_, + `#1520 <https://github.com/fmtlib/fmt/pull/1520>`_, + `#1521 <https://github.com/fmtlib/fmt/pull/1521>`_, + `#1522 <https://github.com/fmtlib/fmt/pull/1522>`_, + `#1524 <https://github.com/fmtlib/fmt/issues/1524>`_, + `#1530 <https://github.com/fmtlib/fmt/pull/1530>`_, + `#1531 <https://github.com/fmtlib/fmt/issues/1531>`_, + `#1532 <https://github.com/fmtlib/fmt/pull/1532>`_, + `#1539 <https://github.com/fmtlib/fmt/issues/1539>`_, + `#1547 <https://github.com/fmtlib/fmt/issues/1547>`_, + `#1548 <https://github.com/fmtlib/fmt/issues/1548>`_, + `#1554 <https://github.com/fmtlib/fmt/pull/1554>`_, + `#1567 <https://github.com/fmtlib/fmt/issues/1567>`_, + `#1568 <https://github.com/fmtlib/fmt/pull/1568>`_, + `#1569 <https://github.com/fmtlib/fmt/pull/1569>`_, + `#1571 <https://github.com/fmtlib/fmt/pull/1571>`_, + `#1573 <https://github.com/fmtlib/fmt/pull/1573>`_, + `#1575 <https://github.com/fmtlib/fmt/pull/1575>`_, + `#1581 <https://github.com/fmtlib/fmt/pull/1581>`_, + `#1583 <https://github.com/fmtlib/fmt/issues/1583>`_, + `#1586 <https://github.com/fmtlib/fmt/issues/1586>`_, + `#1587 <https://github.com/fmtlib/fmt/issues/1587>`_, + `#1594 <https://github.com/fmtlib/fmt/issues/1594>`_, + `#1596 <https://github.com/fmtlib/fmt/pull/1596>`_, + `#1604 <https://github.com/fmtlib/fmt/issues/1604>`_, + `#1606 <https://github.com/fmtlib/fmt/pull/1606>`_, + `#1607 <https://github.com/fmtlib/fmt/issues/1607>`_, + `#1609 <https://github.com/fmtlib/fmt/issues/1609>`_). + Thanks `@marti4d (Chris Martin) <https://github.com/marti4d>`_, + `@iPherian <https://github.com/iPherian>`_, + `@parkertomatoes <https://github.com/parkertomatoes>`_, + `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_, + `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_, + `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_, + `@torsten48 <https://github.com/torsten48>`_, + `@tohammer (Tobias Hammer) <https://github.com/tohammer>`_, + `@lefticus (Jason Turner) <https://github.com/lefticus>`_, + `@ryusakki (Haise) <https://github.com/ryusakki>`_, + `@adnsv (Alex Denisov) <https://github.com/adnsv>`_, + `@fghzxm <https://github.com/fghzxm>`_, + `@refnum <https://github.com/refnum>`_, + `@pramodk (Pramod Kumbhar) <https://github.com/pramodk>`_, + `@Spirrwell <https://github.com/Spirrwell>`_, + `@scramsby (Scott Ramsby) <https://github.com/scramsby>`_. + +6.1.2 - 2019-12-11 +------------------ + +* Fixed ABI compatibility with ``libfmt.so.6.0.0`` + (`#1471 <https://github.com/fmtlib/fmt/issues/1471>`_). + +* Fixed handling types convertible to ``std::string_view`` + (`#1451 <https://github.com/fmtlib/fmt/pull/1451>`_). + Thanks `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_. + +* Made CUDA test an opt-in enabled via the ``FMT_CUDA_TEST`` CMake option. + +* Fixed sign conversion warnings + (`#1440 <https://github.com/fmtlib/fmt/pull/1440>`_). + Thanks `@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_. + +6.1.1 - 2019-12-04 +------------------ + +* Fixed shared library build on Windows + (`#1443 <https://github.com/fmtlib/fmt/pull/1443>`_, + `#1445 <https://github.com/fmtlib/fmt/issues/1445>`_, + `#1446 <https://github.com/fmtlib/fmt/pull/1446>`_, + `#1450 <https://github.com/fmtlib/fmt/issues/1450>`_). + Thanks `@egorpugin (Egor Pugin) <https://github.com/egorpugin>`_, + `@bbolli (Beat Bolli) <https://github.com/bbolli>`_. + +* Added a missing decimal point in exponent notation with trailing zeros. + +* Removed deprecated ``format_arg_store::TYPES``. + +6.1.0 - 2019-12-01 +------------------ + +* {fmt} now formats IEEE 754 ``float`` and ``double`` using the shortest decimal + representation with correct rounding by default: + + .. code:: c++ + + #include <cmath> + #include <fmt/core.h> + + int main() { + fmt::print("{}", M_PI); + } + + prints ``3.141592653589793``. + +* Made the fast binary to decimal floating-point formatter the default, + simplified it and improved performance. {fmt} is now 15 times faster than + libc++'s ``std::ostringstream``, 11 times faster than ``printf`` and 10% + faster than double-conversion on `dtoa-benchmark + <https://github.com/fmtlib/dtoa-benchmark>`_: + + ================== ========= ======= + Function Time (ns) Speedup + ================== ========= ======= + ostringstream 1,346.30 1.00x + ostrstream 1,195.74 1.13x + sprintf 995.08 1.35x + doubleconv 99.10 13.59x + fmt 88.34 15.24x + ================== ========= ======= + + .. image:: https://user-images.githubusercontent.com/576385/ + 69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png + +* {fmt} no longer converts ``float`` arguments to ``double``. In particular this + improves the default (shortest) representation of floats and makes + ``fmt::format`` consistent with ``std::format`` specs + (`#1336 <https://github.com/fmtlib/fmt/issues/1336>`_, + `#1353 <https://github.com/fmtlib/fmt/issues/1353>`_, + `#1360 <https://github.com/fmtlib/fmt/pull/1360>`_, + `#1361 <https://github.com/fmtlib/fmt/pull/1361>`_): + + .. code:: c++ + + fmt::print("{}", 0.1f); + + prints ``0.1`` instead of ``0.10000000149011612``. + + Thanks `@orivej (Orivej Desh) <https://github.com/orivej>`_. + +* Made floating-point formatting output consistent with ``printf``/iostreams + (`#1376 <https://github.com/fmtlib/fmt/issues/1376>`_, + `#1417 <https://github.com/fmtlib/fmt/issues/1417>`_). + +* Added support for 128-bit integers + (`#1287 <https://github.com/fmtlib/fmt/pull/1287>`_): + + .. code:: c++ + + fmt::print("{}", std::numeric_limits<__int128_t>::max()); + + prints ``170141183460469231731687303715884105727``. + + Thanks `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_. + +* The overload of ``print`` that takes ``text_style`` is now atomic, i.e. the + output from different threads doesn't interleave + (`#1351 <https://github.com/fmtlib/fmt/pull/1351>`_). + Thanks `@tankiJong (Tanki Zhang) <https://github.com/tankiJong>`_. + +* Made compile time in the header-only mode ~20% faster by reducing the number + of template instantiations. ``wchar_t`` overload of ``vprint`` was moved from + ``fmt/core.h`` to ``fmt/format.h``. + +* Added an overload of ``fmt::join`` that works with tuples + (`#1322 <https://github.com/fmtlib/fmt/issues/1322>`_, + `#1330 <https://github.com/fmtlib/fmt/pull/1330>`_): + + .. code:: c++ + + #include <tuple> + #include <fmt/ranges.h> + + int main() { + std::tuple<char, int, float> t{'a', 1, 2.0f}; + fmt::print("{}", t); + } + + prints ``('a', 1, 2.0)``. + + Thanks `@jeremyong (Jeremy Ong) <https://github.com/jeremyong>`_. + +* Changed formatting of octal zero with prefix from "00" to "0": + + .. code:: c++ + + fmt::print("{:#o}", 0); + + prints ``0``. + +* The locale is now passed to ostream insertion (``<<``) operators + (`#1406 <https://github.com/fmtlib/fmt/pull/1406>`_): + + .. code:: c++ + + #include <fmt/locale.h> + #include <fmt/ostream.h> + + struct S { + double value; + }; + + std::ostream& operator<<(std::ostream& os, S s) { + return os << s.value; + } + + int main() { + auto s = fmt::format(std::locale("fr_FR.UTF-8"), "{}", S{0.42}); + // s == "0,42" + } + + Thanks `@dlaugt (Daniel Laügt) <https://github.com/dlaugt>`_. + +* Locale-specific number formatting now uses grouping + (`#1393 <https://github.com/fmtlib/fmt/issues/1393>`_ + `#1394 <https://github.com/fmtlib/fmt/pull/1394>`_). + Thanks `@skrdaniel <https://github.com/skrdaniel>`_. + +* Fixed handling of types with deleted implicit rvalue conversion to + ``const char**`` (`#1421 <https://github.com/fmtlib/fmt/issues/1421>`_): + + .. code:: c++ + + struct mystring { + operator const char*() const&; + operator const char*() &; + operator const char*() const&& = delete; + operator const char*() && = delete; + }; + mystring str; + fmt::print("{}", str); // now compiles + +* Enums are now mapped to correct underlying types instead of ``int`` + (`#1286 <https://github.com/fmtlib/fmt/pull/1286>`_). + Thanks `@agmt (Egor Seredin) <https://github.com/agmt>`_. + +* Enum classes are no longer implicitly converted to ``int`` + (`#1424 <https://github.com/fmtlib/fmt/issues/1424>`_). + +* Added ``basic_format_parse_context`` for consistency with C++20 + ``std::format`` and deprecated ``basic_parse_context``. + +* Fixed handling of UTF-8 in precision + (`#1389 <https://github.com/fmtlib/fmt/issues/1389>`_, + `#1390 <https://github.com/fmtlib/fmt/pull/1390>`_). + Thanks `@tajtiattila (Attila Tajti) <https://github.com/tajtiattila>`_. + +* {fmt} can now be installed on Linux, macOS and Windows with + `Conda <https://docs.conda.io/en/latest/>`__ using its + `conda-forge <https://conda-forge.org>`__ + `package <https://github.com/conda-forge/fmt-feedstock>`__ + (`#1410 <https://github.com/fmtlib/fmt/pull/1410>`_):: + + conda install -c conda-forge fmt + + Thanks `@tdegeus (Tom de Geus) <https://github.com/tdegeus>`_. + +* Added a CUDA test (`#1285 <https://github.com/fmtlib/fmt/pull/1285>`_, + `#1317 <https://github.com/fmtlib/fmt/pull/1317>`_). + Thanks `@luncliff (Park DongHa) <https://github.com/luncliff>`_ and + `@risa2000 <https://github.com/risa2000>`_. + +* Improved documentation (`#1276 <https://github.com/fmtlib/fmt/pull/1276>`_, + `#1291 <https://github.com/fmtlib/fmt/issues/1291>`_, + `#1296 <https://github.com/fmtlib/fmt/issues/1296>`_, + `#1315 <https://github.com/fmtlib/fmt/pull/1315>`_, + `#1332 <https://github.com/fmtlib/fmt/pull/1332>`_, + `#1337 <https://github.com/fmtlib/fmt/pull/1337>`_, + `#1395 <https://github.com/fmtlib/fmt/issues/1395>`_ + `#1418 <https://github.com/fmtlib/fmt/pull/1418>`_). + Thanks + `@waywardmonkeys (Bruce Mitchener) <https://github.com/waywardmonkeys>`_, + `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_, + `@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_. + +* Various code improvements + (`#1358 <https://github.com/fmtlib/fmt/pull/1358>`_, + `#1407 <https://github.com/fmtlib/fmt/pull/1407>`_). + Thanks `@orivej (Orivej Desh) <https://github.com/orivej>`_, + `@dpacbach (David P. Sicilia) <https://github.com/dpacbach>`_, + +* Fixed compile-time format string checks for user-defined types + (`#1292 <https://github.com/fmtlib/fmt/issues/1292>`_). + +* Worked around a false positive in ``unsigned-integer-overflow`` sanitizer + (`#1377 <https://github.com/fmtlib/fmt/issues/1377>`_). + +* Fixed various warnings and compilation issues + (`#1273 <https://github.com/fmtlib/fmt/issues/1273>`_, + `#1278 <https://github.com/fmtlib/fmt/pull/1278>`_, + `#1280 <https://github.com/fmtlib/fmt/pull/1280>`_, + `#1281 <https://github.com/fmtlib/fmt/issues/1281>`_, + `#1288 <https://github.com/fmtlib/fmt/issues/1288>`_, + `#1290 <https://github.com/fmtlib/fmt/pull/1290>`_, + `#1301 <https://github.com/fmtlib/fmt/pull/1301>`_, + `#1305 <https://github.com/fmtlib/fmt/issues/1305>`_, + `#1306 <https://github.com/fmtlib/fmt/issues/1306>`_, + `#1309 <https://github.com/fmtlib/fmt/issues/1309>`_, + `#1312 <https://github.com/fmtlib/fmt/pull/1312>`_, + `#1313 <https://github.com/fmtlib/fmt/issues/1313>`_, + `#1316 <https://github.com/fmtlib/fmt/issues/1316>`_, + `#1319 <https://github.com/fmtlib/fmt/issues/1319>`_, + `#1320 <https://github.com/fmtlib/fmt/pull/1320>`_, + `#1326 <https://github.com/fmtlib/fmt/pull/1326>`_, + `#1328 <https://github.com/fmtlib/fmt/pull/1328>`_, + `#1344 <https://github.com/fmtlib/fmt/issues/1344>`_, + `#1345 <https://github.com/fmtlib/fmt/pull/1345>`_, + `#1347 <https://github.com/fmtlib/fmt/pull/1347>`_, + `#1349 <https://github.com/fmtlib/fmt/pull/1349>`_, + `#1354 <https://github.com/fmtlib/fmt/issues/1354>`_, + `#1362 <https://github.com/fmtlib/fmt/issues/1362>`_, + `#1366 <https://github.com/fmtlib/fmt/issues/1366>`_, + `#1364 <https://github.com/fmtlib/fmt/pull/1364>`_, + `#1370 <https://github.com/fmtlib/fmt/pull/1370>`_, + `#1371 <https://github.com/fmtlib/fmt/pull/1371>`_, + `#1385 <https://github.com/fmtlib/fmt/issues/1385>`_, + `#1388 <https://github.com/fmtlib/fmt/issues/1388>`_, + `#1397 <https://github.com/fmtlib/fmt/pull/1397>`_, + `#1414 <https://github.com/fmtlib/fmt/pull/1414>`_, + `#1416 <https://github.com/fmtlib/fmt/pull/1416>`_, + `#1422 <https://github.com/fmtlib/fmt/issues/1422>`_ + `#1427 <https://github.com/fmtlib/fmt/pull/1427>`_, + `#1431 <https://github.com/fmtlib/fmt/issues/1431>`_, + `#1433 <https://github.com/fmtlib/fmt/pull/1433>`_). + Thanks `@hhb <https://github.com/hhb>`_, + `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_, + `@gabime (Gabi Melman) <https://github.com/gabime>`_, + `@neheb (Rosen Penev) <https://github.com/neheb>`_, + `@vedranmiletic (Vedran Miletić) <https://github.com/vedranmiletic>`_, + `@dkavolis (Daumantas Kavolis) <https://github.com/dkavolis>`_, + `@mwinterb <https://github.com/mwinterb>`_, + `@orivej (Orivej Desh) <https://github.com/orivej>`_, + `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_ + `@leonklingele <https://github.com/leonklingele>`_, + `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_, + `@kent-tri <https://github.com/kent-tri>`_, + `@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_, + `@marti4d (Chris Martin) <https://github.com/marti4d>`_. + +6.0.0 - 2019-08-26 +------------------ + +* Switched to the `MIT license + <https://github.com/fmtlib/fmt/blob/5a4b24613ba16cc689977c3b5bd8274a3ba1dd1f/LICENSE.rst>`_ + with an optional exception that allows distributing binary code without + attribution. + +* Floating-point formatting is now locale-independent by default: + + .. code:: c++ + + #include <locale> + #include <fmt/core.h> + + int main() { + std::locale::global(std::locale("ru_RU.UTF-8")); + fmt::print("value = {}", 4.2); + } + + prints "value = 4.2" regardless of the locale. + + For locale-specific formatting use the ``n`` specifier: + + .. code:: c++ + + std::locale::global(std::locale("ru_RU.UTF-8")); + fmt::print("value = {:n}", 4.2); + + prints "value = 4,2". + +* Added an experimental Grisu floating-point formatting algorithm + implementation (disabled by default). To enable it compile with the + ``FMT_USE_GRISU`` macro defined to 1: + + .. code:: c++ + + #define FMT_USE_GRISU 1 + #include <fmt/format.h> + + auto s = fmt::format("{}", 4.2); // formats 4.2 using Grisu + + With Grisu enabled, {fmt} is 13x faster than ``std::ostringstream`` (libc++) + and 10x faster than ``sprintf`` on `dtoa-benchmark + <https://github.com/fmtlib/dtoa-benchmark>`_ (`full results + <https://fmt.dev/unknown_mac64_clang10.0.html>`_): + + .. image:: https://user-images.githubusercontent.com/576385/ + 54883977-9fe8c000-4e28-11e9-8bde-272d122e7c52.jpg + +* Separated formatting and parsing contexts for consistency with + `C++20 std::format <http://eel.is/c++draft/format>`_, removing the + undocumented ``basic_format_context::parse_context()`` function. + +* Added `oss-fuzz <https://github.com/google/oss-fuzz>`_ support + (`#1199 <https://github.com/fmtlib/fmt/pull/1199>`_). + Thanks `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_. + +* ``formatter`` specializations now always take precedence over ``operator<<`` + (`#952 <https://github.com/fmtlib/fmt/issues/952>`_): + + .. code:: c++ + + #include <iostream> + #include <fmt/ostream.h> + + struct S {}; + + std::ostream& operator<<(std::ostream& os, S) { + return os << 1; + } + + template <> + struct fmt::formatter<S> : fmt::formatter<int> { + auto format(S, format_context& ctx) { + return formatter<int>::format(2, ctx); + } + }; + + int main() { + std::cout << S() << "\n"; // prints 1 using operator<< + fmt::print("{}\n", S()); // prints 2 using formatter + } + +* Introduced the experimental ``fmt::compile`` function that does format string + compilation (`#618 <https://github.com/fmtlib/fmt/issues/618>`_, + `#1169 <https://github.com/fmtlib/fmt/issues/1169>`_, + `#1171 <https://github.com/fmtlib/fmt/pull/1171>`_): + + .. code:: c++ + + #include <fmt/compile.h> + + auto f = fmt::compile<int>("{}"); + std::string s = fmt::format(f, 42); // can be called multiple times to + // format different values + // s == "42" + + It moves the cost of parsing a format string outside of the format function + which can be beneficial when identically formatting many objects of the same + types. Thanks `@stryku (Mateusz Janek) <https://github.com/stryku>`_. + +* Added experimental ``%`` format specifier that formats floating-point values + as percentages (`#1060 <https://github.com/fmtlib/fmt/pull/1060>`_, + `#1069 <https://github.com/fmtlib/fmt/pull/1069>`_, + `#1071 <https://github.com/fmtlib/fmt/pull/1071>`_): + + .. code:: c++ + + auto s = fmt::format("{:.1%}", 0.42); // s == "42.0%" + + Thanks `@gawain-bolton (Gawain Bolton) <https://github.com/gawain-bolton>`_. + +* Implemented precision for floating-point durations + (`#1004 <https://github.com/fmtlib/fmt/issues/1004>`_, + `#1012 <https://github.com/fmtlib/fmt/pull/1012>`_): + + .. code:: c++ + + auto s = fmt::format("{:.1}", std::chrono::duration<double>(1.234)); + // s == 1.2s + + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Implemented ``chrono`` format specifiers ``%Q`` and ``%q`` that give the value + and the unit respectively (`#1019 <https://github.com/fmtlib/fmt/pull/1019>`_): + + .. code:: c++ + + auto value = fmt::format("{:%Q}", 42s); // value == "42" + auto unit = fmt::format("{:%q}", 42s); // unit == "s" + + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Fixed handling of dynamic width in chrono formatter: + + .. code:: c++ + + auto s = fmt::format("{0:{1}%H:%M:%S}", std::chrono::seconds(12345), 12); + // ^ width argument index ^ width + // s == "03:25:45 " + + Thanks Howard Hinnant. + +* Removed deprecated ``fmt/time.h``. Use ``fmt/chrono.h`` instead. + +* Added ``fmt::format`` and ``fmt::vformat`` overloads that take ``text_style`` + (`#993 <https://github.com/fmtlib/fmt/issues/993>`_, + `#994 <https://github.com/fmtlib/fmt/pull/994>`_): + + .. code:: c++ + + #include <fmt/color.h> + + std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + "The answer is {}.", 42); + + Thanks `@Naios (Denis Blank) <https://github.com/Naios>`_. + +* Removed the deprecated color API (``print_colored``). Use the new API, namely + ``print`` overloads that take ``text_style`` instead. + +* Made ``std::unique_ptr`` and ``std::shared_ptr`` formattable as pointers via + ``fmt::ptr`` (`#1121 <https://github.com/fmtlib/fmt/pull/1121>`_): + + .. code:: c++ + + std::unique_ptr<int> p = ...; + fmt::print("{}", fmt::ptr(p)); // prints p as a pointer + + Thanks `@sighingnow (Tao He) <https://github.com/sighingnow>`_. + +* Made ``print`` and ``vprint`` report I/O errors + (`#1098 <https://github.com/fmtlib/fmt/issues/1098>`_, + `#1099 <https://github.com/fmtlib/fmt/pull/1099>`_). + Thanks `@BillyDonahue (Billy Donahue) <https://github.com/BillyDonahue>`_. + +* Marked deprecated APIs with the ``[[deprecated]]`` attribute and removed + internal uses of deprecated APIs + (`#1022 <https://github.com/fmtlib/fmt/pull/1022>`_). + Thanks `@eliaskosunen (Elias Kosunen) <https://github.com/eliaskosunen>`_. + +* Modernized the codebase using more C++11 features and removing workarounds. + Most importantly, ``buffer_context`` is now an alias template, so + use ``buffer_context<T>`` instead of ``buffer_context<T>::type``. + These features require GCC 4.8 or later. + +* ``formatter`` specializations now always take precedence over implicit + conversions to ``int`` and the undocumented ``convert_to_int`` trait + is now deprecated. + +* Moved the undocumented ``basic_writer``, ``writer``, and ``wwriter`` types + to the ``internal`` namespace. + +* Removed deprecated ``basic_format_context::begin()``. Use ``out()`` instead. + +* Disallowed passing the result of ``join`` as an lvalue to prevent misuse. + +* Refactored the undocumented structs that represent parsed format specifiers + to simplify the API and allow multibyte fill. + +* Moved SFINAE to template parameters to reduce symbol sizes. + +* Switched to ``fputws`` for writing wide strings so that it's no longer + required to call ``_setmode`` on Windows + (`#1229 <https://github.com/fmtlib/fmt/issues/1229>`_, + `#1243 <https://github.com/fmtlib/fmt/pull/1243>`_). + Thanks `@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_. + +* Improved literal-based API + (`#1254 <https://github.com/fmtlib/fmt/pull/1254>`_). + Thanks `@sylveon (Charles Milette) <https://github.com/sylveon>`_. + +* Added support for exotic platforms without ``uintptr_t`` such as IBM i + (AS/400) which has 128-bit pointers and only 64-bit integers + (`#1059 <https://github.com/fmtlib/fmt/issues/1059>`_). + +* Added `Sublime Text syntax highlighting config + <https://github.com/fmtlib/fmt/blob/master/support/C%2B%2B.sublime-syntax>`_ + (`#1037 <https://github.com/fmtlib/fmt/issues/1037>`_). + Thanks `@Kronuz (Germán Méndez Bravo) <https://github.com/Kronuz>`_. + +* Added the ``FMT_ENFORCE_COMPILE_STRING`` macro to enforce the use of + compile-time format strings + (`#1231 <https://github.com/fmtlib/fmt/pull/1231>`_). + Thanks `@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_. + +* Stopped setting ``CMAKE_BUILD_TYPE`` if {fmt} is a subproject + (`#1081 <https://github.com/fmtlib/fmt/issues/1081>`_). + +* Various build improvements + (`#1039 <https://github.com/fmtlib/fmt/pull/1039>`_, + `#1078 <https://github.com/fmtlib/fmt/pull/1078>`_, + `#1091 <https://github.com/fmtlib/fmt/pull/1091>`_, + `#1103 <https://github.com/fmtlib/fmt/pull/1103>`_, + `#1177 <https://github.com/fmtlib/fmt/pull/1177>`_). + Thanks `@luncliff (Park DongHa) <https://github.com/luncliff>`_, + `@jasonszang (Jason Shuo Zang) <https://github.com/jasonszang>`_, + `@olafhering (Olaf Hering) <https://github.com/olafhering>`_, + `@Lecetem <https://github.com/Lectem>`_, + `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_. + +* Improved documentation + (`#1049 <https://github.com/fmtlib/fmt/issues/1049>`_, + `#1051 <https://github.com/fmtlib/fmt/pull/1051>`_, + `#1083 <https://github.com/fmtlib/fmt/pull/1083>`_, + `#1113 <https://github.com/fmtlib/fmt/pull/1113>`_, + `#1114 <https://github.com/fmtlib/fmt/pull/1114>`_, + `#1146 <https://github.com/fmtlib/fmt/issues/1146>`_, + `#1180 <https://github.com/fmtlib/fmt/issues/1180>`_, + `#1250 <https://github.com/fmtlib/fmt/pull/1250>`_, + `#1252 <https://github.com/fmtlib/fmt/pull/1252>`_, + `#1265 <https://github.com/fmtlib/fmt/pull/1265>`_). + Thanks `@mikelui (Michael Lui) <https://github.com/mikelui>`_, + `@foonathan (Jonathan Müller) <https://github.com/foonathan>`_, + `@BillyDonahue (Billy Donahue) <https://github.com/BillyDonahue>`_, + `@jwakely (Jonathan Wakely) <https://github.com/jwakely>`_, + `@kaisbe (Kais Ben Salah) <https://github.com/kaisbe>`_, + `@sdebionne (Samuel Debionne) <https://github.com/sdebionne>`_. + +* Fixed ambiguous formatter specialization in ``fmt/ranges.h`` + (`#1123 <https://github.com/fmtlib/fmt/issues/1123>`_). + +* Fixed formatting of a non-empty ``std::filesystem::path`` which is an + infinitely deep range of its components + (`#1268 <https://github.com/fmtlib/fmt/issues/1268>`_). + +* Fixed handling of general output iterators when formatting characters + (`#1056 <https://github.com/fmtlib/fmt/issues/1056>`_, + `#1058 <https://github.com/fmtlib/fmt/pull/1058>`_). + Thanks `@abolz (Alexander Bolz) <https://github.com/abolz>`_. + +* Fixed handling of output iterators in ``formatter`` specialization for + ranges (`#1064 <https://github.com/fmtlib/fmt/issues/1064>`_). + +* Fixed handling of exotic character types + (`#1188 <https://github.com/fmtlib/fmt/issues/1188>`_). + +* Made chrono formatting work with exceptions disabled + (`#1062 <https://github.com/fmtlib/fmt/issues/1062>`_). + +* Fixed DLL visibility issues + (`#1134 <https://github.com/fmtlib/fmt/pull/1134>`_, + `#1147 <https://github.com/fmtlib/fmt/pull/1147>`_). + Thanks `@denchat <https://github.com/denchat>`_. + +* Disabled the use of UDL template extension on GCC 9 + (`#1148 <https://github.com/fmtlib/fmt/issues/1148>`_). + +* Removed misplaced ``format`` compile-time checks from ``printf`` + (`#1173 <https://github.com/fmtlib/fmt/issues/1173>`_). + +* Fixed issues in the experimental floating-point formatter + (`#1072 <https://github.com/fmtlib/fmt/issues/1072>`_, + `#1129 <https://github.com/fmtlib/fmt/issues/1129>`_, + `#1153 <https://github.com/fmtlib/fmt/issues/1153>`_, + `#1155 <https://github.com/fmtlib/fmt/pull/1155>`_, + `#1210 <https://github.com/fmtlib/fmt/issues/1210>`_, + `#1222 <https://github.com/fmtlib/fmt/issues/1222>`_). + Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_. + +* Fixed bugs discovered by fuzzing or during fuzzing integration + (`#1124 <https://github.com/fmtlib/fmt/issues/1124>`_, + `#1127 <https://github.com/fmtlib/fmt/issues/1127>`_, + `#1132 <https://github.com/fmtlib/fmt/issues/1132>`_, + `#1135 <https://github.com/fmtlib/fmt/pull/1135>`_, + `#1136 <https://github.com/fmtlib/fmt/issues/1136>`_, + `#1141 <https://github.com/fmtlib/fmt/issues/1141>`_, + `#1142 <https://github.com/fmtlib/fmt/issues/1142>`_, + `#1178 <https://github.com/fmtlib/fmt/issues/1178>`_, + `#1179 <https://github.com/fmtlib/fmt/issues/1179>`_, + `#1194 <https://github.com/fmtlib/fmt/issues/1194>`_). + Thanks `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_. + +* Fixed building tests on FreeBSD and Hurd + (`#1043 <https://github.com/fmtlib/fmt/issues/1043>`_). + Thanks `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_. + +* Fixed various warnings and compilation issues + (`#998 <https://github.com/fmtlib/fmt/pull/998>`_, + `#1006 <https://github.com/fmtlib/fmt/pull/1006>`_, + `#1008 <https://github.com/fmtlib/fmt/issues/1008>`_, + `#1011 <https://github.com/fmtlib/fmt/issues/1011>`_, + `#1025 <https://github.com/fmtlib/fmt/issues/1025>`_, + `#1027 <https://github.com/fmtlib/fmt/pull/1027>`_, + `#1028 <https://github.com/fmtlib/fmt/pull/1028>`_, + `#1029 <https://github.com/fmtlib/fmt/pull/1029>`_, + `#1030 <https://github.com/fmtlib/fmt/pull/1030>`_, + `#1031 <https://github.com/fmtlib/fmt/pull/1031>`_, + `#1054 <https://github.com/fmtlib/fmt/pull/1054>`_, + `#1063 <https://github.com/fmtlib/fmt/issues/1063>`_, + `#1068 <https://github.com/fmtlib/fmt/pull/1068>`_, + `#1074 <https://github.com/fmtlib/fmt/pull/1074>`_, + `#1075 <https://github.com/fmtlib/fmt/pull/1075>`_, + `#1079 <https://github.com/fmtlib/fmt/pull/1079>`_, + `#1086 <https://github.com/fmtlib/fmt/pull/1086>`_, + `#1088 <https://github.com/fmtlib/fmt/issues/1088>`_, + `#1089 <https://github.com/fmtlib/fmt/pull/1089>`_, + `#1094 <https://github.com/fmtlib/fmt/pull/1094>`_, + `#1101 <https://github.com/fmtlib/fmt/issues/1101>`_, + `#1102 <https://github.com/fmtlib/fmt/pull/1102>`_, + `#1105 <https://github.com/fmtlib/fmt/issues/1105>`_, + `#1107 <https://github.com/fmtlib/fmt/pull/1107>`_, + `#1115 <https://github.com/fmtlib/fmt/issues/1115>`_, + `#1117 <https://github.com/fmtlib/fmt/issues/1117>`_, + `#1118 <https://github.com/fmtlib/fmt/issues/1118>`_, + `#1120 <https://github.com/fmtlib/fmt/issues/1120>`_, + `#1123 <https://github.com/fmtlib/fmt/issues/1123>`_, + `#1139 <https://github.com/fmtlib/fmt/pull/1139>`_, + `#1140 <https://github.com/fmtlib/fmt/issues/1140>`_, + `#1143 <https://github.com/fmtlib/fmt/issues/1143>`_, + `#1144 <https://github.com/fmtlib/fmt/pull/1144>`_, + `#1150 <https://github.com/fmtlib/fmt/pull/1150>`_, + `#1151 <https://github.com/fmtlib/fmt/pull/1151>`_, + `#1152 <https://github.com/fmtlib/fmt/issues/1152>`_, + `#1154 <https://github.com/fmtlib/fmt/issues/1154>`_, + `#1156 <https://github.com/fmtlib/fmt/issues/1156>`_, + `#1159 <https://github.com/fmtlib/fmt/pull/1159>`_, + `#1175 <https://github.com/fmtlib/fmt/issues/1175>`_, + `#1181 <https://github.com/fmtlib/fmt/issues/1181>`_, + `#1186 <https://github.com/fmtlib/fmt/issues/1186>`_, + `#1187 <https://github.com/fmtlib/fmt/pull/1187>`_, + `#1191 <https://github.com/fmtlib/fmt/pull/1191>`_, + `#1197 <https://github.com/fmtlib/fmt/issues/1197>`_, + `#1200 <https://github.com/fmtlib/fmt/issues/1200>`_, + `#1203 <https://github.com/fmtlib/fmt/issues/1203>`_, + `#1205 <https://github.com/fmtlib/fmt/issues/1205>`_, + `#1206 <https://github.com/fmtlib/fmt/pull/1206>`_, + `#1213 <https://github.com/fmtlib/fmt/issues/1213>`_, + `#1214 <https://github.com/fmtlib/fmt/issues/1214>`_, + `#1217 <https://github.com/fmtlib/fmt/pull/1217>`_, + `#1228 <https://github.com/fmtlib/fmt/issues/1228>`_, + `#1230 <https://github.com/fmtlib/fmt/pull/1230>`_, + `#1232 <https://github.com/fmtlib/fmt/issues/1232>`_, + `#1235 <https://github.com/fmtlib/fmt/pull/1235>`_, + `#1236 <https://github.com/fmtlib/fmt/pull/1236>`_, + `#1240 <https://github.com/fmtlib/fmt/issues/1240>`_). + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_, + `@mwinterb <https://github.com/mwinterb>`_, + `@eliaskosunen (Elias Kosunen) <https://github.com/eliaskosunen>`_, + `@morinmorin <https://github.com/morinmorin>`_, + `@ricco19 (Brian Ricciardelli) <https://github.com/ricco19>`_, + `@waywardmonkeys (Bruce Mitchener) <https://github.com/waywardmonkeys>`_, + `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_, + `@remyabel <https://github.com/remyabel>`_, + `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_, + `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_, + `@rcane (Ronny Krüger) <https://github.com/rcane>`_, + `@mocabe <https://github.com/mocabe>`_, + `@denchat <https://github.com/denchat>`_, + `@cjdb (Christopher Di Bella) <https://github.com/cjdb>`_, + `@HazardyKnusperkeks (Björn Schäpers) <https://github.com/HazardyKnusperkeks>`_, + `@vedranmiletic (Vedran Miletić) <https://github.com/vedranmiletic>`_, + `@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_, + `@DaanDeMeyer (Daan De Meyer) <https://github.com/DaanDeMeyer>`_, + `@starkmapper (Mark Stapper) <https://github.com/starkmapper>`_. + +5.3.0 - 2018-12-28 +------------------ + +* Introduced experimental chrono formatting support: + + .. code:: c++ + + #include <fmt/chrono.h> + + int main() { + using namespace std::literals::chrono_literals; + fmt::print("Default format: {} {}\n", 42s, 100ms); + fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); + } + + prints:: + + Default format: 42s 100ms + strftime-like format: 03:15:30 + +* Added experimental support for emphasis (bold, italic, underline, + strikethrough), colored output to a file stream, and improved colored + formatting API + (`#961 <https://github.com/fmtlib/fmt/pull/961>`_, + `#967 <https://github.com/fmtlib/fmt/pull/967>`_, + `#973 <https://github.com/fmtlib/fmt/pull/973>`_): + + .. code:: c++ + + #include <fmt/color.h> + + int main() { + print(fg(fmt::color::crimson) | fmt::emphasis::bold, + "Hello, {}!\n", "world"); + print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | + fmt::emphasis::underline, "Hello, {}!\n", "мир"); + print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, + "Hello, {}!\n", "世界"); + } + + prints the following on modern terminals with RGB color support: + + .. image:: https://user-images.githubusercontent.com/576385/ + 50405788-b66e7500-076e-11e9-9592-7324d1f951d8.png + + Thanks `@Rakete1111 (Nicolas) <https://github.com/Rakete1111>`_. + +* Added support for 4-bit terminal colors + (`#968 <https://github.com/fmtlib/fmt/issues/968>`_, + `#974 <https://github.com/fmtlib/fmt/pull/974>`_) + + .. code:: c++ + + #include <fmt/color.h> + + int main() { + print(fg(fmt::terminal_color::red), "stop\n"); + } + + Note that these colors vary by terminal: + + .. image:: https://user-images.githubusercontent.com/576385/ + 50405925-dbfc7e00-0770-11e9-9b85-333fab0af9ac.png + + Thanks `@Rakete1111 (Nicolas) <https://github.com/Rakete1111>`_. + +* Parameterized formatting functions on the type of the format string + (`#880 <https://github.com/fmtlib/fmt/issues/880>`_, + `#881 <https://github.com/fmtlib/fmt/pull/881>`_, + `#883 <https://github.com/fmtlib/fmt/pull/883>`_, + `#885 <https://github.com/fmtlib/fmt/pull/885>`_, + `#897 <https://github.com/fmtlib/fmt/pull/897>`_, + `#920 <https://github.com/fmtlib/fmt/issues/920>`_). + Any object of type ``S`` that has an overloaded ``to_string_view(const S&)`` + returning ``fmt::string_view`` can be used as a format string: + + .. code:: c++ + + namespace my_ns { + inline string_view to_string_view(const my_string& s) { + return {s.data(), s.length()}; + } + } + + std::string message = fmt::format(my_string("The answer is {}."), 42); + + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Made ``std::string_view`` work as a format string + (`#898 <https://github.com/fmtlib/fmt/pull/898>`_): + + .. code:: c++ + + auto message = fmt::format(std::string_view("The answer is {}."), 42); + + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Added wide string support to compile-time format string checks + (`#924 <https://github.com/fmtlib/fmt/pull/924>`_): + + .. code:: c++ + + print(fmt(L"{:f}"), 42); // compile-time error: invalid type specifier + + Thanks `@XZiar <https://github.com/XZiar>`_. + +* Made colored print functions work with wide strings + (`#867 <https://github.com/fmtlib/fmt/pull/867>`_): + + .. code:: c++ + + #include <fmt/color.h> + + int main() { + print(fg(fmt::color::red), L"{}\n", 42); + } + + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Introduced experimental Unicode support + (`#628 <https://github.com/fmtlib/fmt/issues/628>`_, + `#891 <https://github.com/fmtlib/fmt/pull/891>`_): + + .. code:: c++ + + using namespace fmt::literals; + auto s = fmt::format("{:*^5}"_u, "🤡"_u); // s == "**🤡**"_u + +* Improved locale support: + + .. code:: c++ + + #include <fmt/locale.h> + + struct numpunct : std::numpunct<char> { + protected: + char do_thousands_sep() const override { return '~'; } + }; + + std::locale loc; + auto s = fmt::format(std::locale(loc, new numpunct()), "{:n}", 1234567); + // s == "1~234~567" + +* Constrained formatting functions on proper iterator types + (`#921 <https://github.com/fmtlib/fmt/pull/921>`_). + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Added ``make_printf_args`` and ``make_wprintf_args`` functions + (`#934 <https://github.com/fmtlib/fmt/pull/934>`_). + Thanks `@tnovotny <https://github.com/tnovotny>`_. + +* Deprecated ``fmt::visit``, ``parse_context``, and ``wparse_context``. + Use ``fmt::visit_format_arg``, ``format_parse_context``, and + ``wformat_parse_context`` instead. + +* Removed undocumented ``basic_fixed_buffer`` which has been superseded by the + iterator-based API + (`#873 <https://github.com/fmtlib/fmt/issues/873>`_, + `#902 <https://github.com/fmtlib/fmt/pull/902>`_). + Thanks `@superfunc (hollywood programmer) <https://github.com/superfunc>`_. + +* Disallowed repeated leading zeros in an argument ID: + + .. code:: c++ + + fmt::print("{000}", 42); // error + +* Reintroduced support for gcc 4.4. + +* Fixed compilation on platforms with exotic ``double`` + (`#878 <https://github.com/fmtlib/fmt/issues/878>`_). + +* Improved documentation + (`#164 <https://github.com/fmtlib/fmt/issues/164>`_, + `#877 <https://github.com/fmtlib/fmt/issues/877>`_, + `#901 <https://github.com/fmtlib/fmt/pull/901>`_, + `#906 <https://github.com/fmtlib/fmt/pull/906>`_, + `#979 <https://github.com/fmtlib/fmt/pull/979>`_). + Thanks `@kookjr (Mathew Cucuzella) <https://github.com/kookjr>`_, + `@DarkDimius (Dmitry Petrashko) <https://github.com/DarkDimius>`_, + `@HecticSerenity <https://github.com/HecticSerenity>`_. + +* Added pkgconfig support which makes it easier to consume the library from + meson and other build systems + (`#916 <https://github.com/fmtlib/fmt/pull/916>`_). + Thanks `@colemickens (Cole Mickens) <https://github.com/colemickens>`_. + +* Various build improvements + (`#909 <https://github.com/fmtlib/fmt/pull/909>`_, + `#926 <https://github.com/fmtlib/fmt/pull/926>`_, + `#937 <https://github.com/fmtlib/fmt/pull/937>`_, + `#953 <https://github.com/fmtlib/fmt/pull/953>`_, + `#959 <https://github.com/fmtlib/fmt/pull/959>`_). + Thanks `@tchaikov (Kefu Chai) <https://github.com/tchaikov>`_, + `@luncliff (Park DongHa) <https://github.com/luncliff>`_, + `@AndreasSchoenle (Andreas Schönle) <https://github.com/AndreasSchoenle>`_, + `@hotwatermorning <https://github.com/hotwatermorning>`_, + `@Zefz (JohanJansen) <https://github.com/Zefz>`_. + +* Improved ``string_view`` construction performance + (`#914 <https://github.com/fmtlib/fmt/pull/914>`_). + Thanks `@gabime (Gabi Melman) <https://github.com/gabime>`_. + +* Fixed non-matching char types + (`#895 <https://github.com/fmtlib/fmt/pull/895>`_). + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Fixed ``format_to_n`` with ``std::back_insert_iterator`` + (`#913 <https://github.com/fmtlib/fmt/pull/913>`_). + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Fixed locale-dependent formatting + (`#905 <https://github.com/fmtlib/fmt/issues/905>`_). + +* Fixed various compiler warnings and errors + (`#882 <https://github.com/fmtlib/fmt/pull/882>`_, + `#886 <https://github.com/fmtlib/fmt/pull/886>`_, + `#933 <https://github.com/fmtlib/fmt/pull/933>`_, + `#941 <https://github.com/fmtlib/fmt/pull/941>`_, + `#931 <https://github.com/fmtlib/fmt/issues/931>`_, + `#943 <https://github.com/fmtlib/fmt/pull/943>`_, + `#954 <https://github.com/fmtlib/fmt/pull/954>`_, + `#956 <https://github.com/fmtlib/fmt/pull/956>`_, + `#962 <https://github.com/fmtlib/fmt/pull/962>`_, + `#965 <https://github.com/fmtlib/fmt/issues/965>`_, + `#977 <https://github.com/fmtlib/fmt/issues/977>`_, + `#983 <https://github.com/fmtlib/fmt/pull/983>`_, + `#989 <https://github.com/fmtlib/fmt/pull/989>`_). + Thanks `@Luthaf (Guillaume Fraux) <https://github.com/Luthaf>`_, + `@stevenhoving (Steven Hoving) <https://github.com/stevenhoving>`_, + `@christinaa (Kristina Brooks) <https://github.com/christinaa>`_, + `@lgritz (Larry Gritz) <https://github.com/lgritz>`_, + `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_, + `@0x8000-0000 (Sign Bit) <https://github.com/0x8000-0000>`_, + `@liuping1997 <https://github.com/liuping1997>`_. + +5.2.1 - 2018-09-21 +------------------ + +* Fixed ``visit`` lookup issues on gcc 7 & 8 + (`#870 <https://github.com/fmtlib/fmt/pull/870>`_). + Thanks `@medithe <https://github.com/medithe>`_. + +* Fixed linkage errors on older gcc. + +* Prevented ``fmt/range.h`` from specializing ``fmt::basic_string_view`` + (`#865 <https://github.com/fmtlib/fmt/issues/865>`_, + `#868 <https://github.com/fmtlib/fmt/pull/868>`_). + Thanks `@hhggit (dual) <https://github.com/hhggit>`_. + +* Improved error message when formatting unknown types + (`#872 <https://github.com/fmtlib/fmt/pull/872>`_). + Thanks `@foonathan (Jonathan Müller) <https://github.com/foonathan>`_, + +* Disabled templated user-defined literals when compiled under nvcc + (`#875 <https://github.com/fmtlib/fmt/pull/875>`_). + Thanks `@CandyGumdrop (Candy Gumdrop) <https://github.com/CandyGumdrop>`_, + +* Fixed ``format_to`` formatting to ``wmemory_buffer`` + (`#874 <https://github.com/fmtlib/fmt/issues/874>`_). + +5.2.0 - 2018-09-13 +------------------ + +* Optimized format string parsing and argument processing which resulted in up + to 5x speed up on long format strings and significant performance boost on + various benchmarks. For example, version 5.2 is 2.22x faster than 5.1 on + decimal integer formatting with ``format_to`` (macOS, clang-902.0.39.2): + + ================== ======= ======= + Method Time, s Speedup + ================== ======= ======= + fmt::format 5.1 0.58 + fmt::format 5.2 0.35 1.66x + fmt::format_to 5.1 0.51 + fmt::format_to 5.2 0.23 2.22x + sprintf 0.71 + std::to_string 1.01 + std::stringstream 1.73 + ================== ======= ======= + +* Changed the ``fmt`` macro from opt-out to opt-in to prevent name collisions. + To enable it define the ``FMT_STRING_ALIAS`` macro to 1 before including + ``fmt/format.h``: + + .. code:: c++ + + #define FMT_STRING_ALIAS 1 + #include <fmt/format.h> + std::string answer = format(fmt("{}"), 42); + +* Added compile-time format string checks to ``format_to`` overload that takes + ``fmt::memory_buffer`` (`#783 <https://github.com/fmtlib/fmt/issues/783>`_): + + .. code:: c++ + + fmt::memory_buffer buf; + // Compile-time error: invalid type specifier. + fmt::format_to(buf, fmt("{:d}"), "foo"); + +* Moved experimental color support to ``fmt/color.h`` and enabled the + new API by default. The old API can be enabled by defining the + ``FMT_DEPRECATED_COLORS`` macro. + +* Added formatting support for types explicitly convertible to + ``fmt::string_view``: + + .. code:: c++ + + struct foo { + explicit operator fmt::string_view() const { return "foo"; } + }; + auto s = format("{}", foo()); + + In particular, this makes formatting function work with + ``folly::StringPiece``. + +* Implemented preliminary support for ``char*_t`` by replacing the ``format`` + function overloads with a single function template parameterized on the string + type. + +* Added support for dynamic argument lists + (`#814 <https://github.com/fmtlib/fmt/issues/814>`_, + `#819 <https://github.com/fmtlib/fmt/pull/819>`_). + Thanks `@MikePopoloski (Michael Popoloski) + <https://github.com/MikePopoloski>`_. + +* Reduced executable size overhead for embedded targets using newlib nano by + making locale dependency optional + (`#839 <https://github.com/fmtlib/fmt/pull/839>`_). + Thanks `@teajay-fr (Thomas Benard) <https://github.com/teajay-fr>`_. + +* Keep ``noexcept`` specifier when exceptions are disabled + (`#801 <https://github.com/fmtlib/fmt/issues/801>`_, + `#810 <https://github.com/fmtlib/fmt/pull/810>`_). + Thanks `@qis (Alexej Harm) <https://github.com/qis>`_. + +* Fixed formatting of user-defined types providing ``operator<<`` with + ``format_to_n`` + (`#806 <https://github.com/fmtlib/fmt/pull/806>`_). + Thanks `@mkurdej (Marek Kurdej) <https://github.com/mkurdej>`_. + +* Fixed dynamic linkage of new symbols + (`#808 <https://github.com/fmtlib/fmt/issues/808>`_). + +* Fixed global initialization issue + (`#807 <https://github.com/fmtlib/fmt/issues/807>`_): + + .. code:: c++ + + // This works on compilers with constexpr support. + static const std::string answer = fmt::format("{}", 42); + +* Fixed various compiler warnings and errors + (`#804 <https://github.com/fmtlib/fmt/pull/804>`_, + `#809 <https://github.com/fmtlib/fmt/issues/809>`_, + `#811 <https://github.com/fmtlib/fmt/pull/811>`_, + `#822 <https://github.com/fmtlib/fmt/issues/822>`_, + `#827 <https://github.com/fmtlib/fmt/pull/827>`_, + `#830 <https://github.com/fmtlib/fmt/issues/830>`_, + `#838 <https://github.com/fmtlib/fmt/pull/838>`_, + `#843 <https://github.com/fmtlib/fmt/issues/843>`_, + `#844 <https://github.com/fmtlib/fmt/pull/844>`_, + `#851 <https://github.com/fmtlib/fmt/issues/851>`_, + `#852 <https://github.com/fmtlib/fmt/pull/852>`_, + `#854 <https://github.com/fmtlib/fmt/pull/854>`_). + Thanks `@henryiii (Henry Schreiner) <https://github.com/henryiii>`_, + `@medithe <https://github.com/medithe>`_, and + `@eliasdaler (Elias Daler) <https://github.com/eliasdaler>`_. + +5.1.0 - 2018-07-05 +------------------ + +* Added experimental support for RGB color output enabled with + the ``FMT_EXTENDED_COLORS`` macro: + + .. code:: c++ + + #define FMT_EXTENDED_COLORS + #define FMT_HEADER_ONLY // or compile fmt with FMT_EXTENDED_COLORS defined + #include <fmt/format.h> + + fmt::print(fmt::color::steel_blue, "Some beautiful text"); + + The old API (the ``print_colored`` and ``vprint_colored`` functions and the + ``color`` enum) is now deprecated. + (`#762 <https://github.com/fmtlib/fmt/issues/762>`_ + `#767 <https://github.com/fmtlib/fmt/pull/767>`_). + thanks `@Remotion (Remo) <https://github.com/Remotion>`_. + +* Added quotes to strings in ranges and tuples + (`#766 <https://github.com/fmtlib/fmt/pull/766>`_). + Thanks `@Remotion (Remo) <https://github.com/Remotion>`_. + +* Made ``format_to`` work with ``basic_memory_buffer`` + (`#776 <https://github.com/fmtlib/fmt/issues/776>`_). + +* Added ``vformat_to_n`` and ``wchar_t`` overload of ``format_to_n`` + (`#764 <https://github.com/fmtlib/fmt/issues/764>`_, + `#769 <https://github.com/fmtlib/fmt/issues/769>`_). + +* Made ``is_range`` and ``is_tuple_like`` part of public (experimental) API + to allow specialization for user-defined types + (`#751 <https://github.com/fmtlib/fmt/issues/751>`_, + `#759 <https://github.com/fmtlib/fmt/pull/759>`_). + Thanks `@drrlvn (Dror Levin) <https://github.com/drrlvn>`_. + +* Added more compilers to continuous integration and increased ``FMT_PEDANTIC`` + warning levels + (`#736 <https://github.com/fmtlib/fmt/pull/736>`_). + Thanks `@eliaskosunen (Elias Kosunen) <https://github.com/eliaskosunen>`_. + +* Fixed compilation with MSVC 2013. + +* Fixed handling of user-defined types in ``format_to`` + (`#793 <https://github.com/fmtlib/fmt/issues/793>`_). + +* Forced linking of inline ``vformat`` functions into the library + (`#795 <https://github.com/fmtlib/fmt/issues/795>`_). + +* Fixed incorrect call to on_align in ``'{:}='`` + (`#750 <https://github.com/fmtlib/fmt/issues/750>`_). + +* Fixed floating-point formatting to a non-back_insert_iterator with sign & + numeric alignment specified + (`#756 <https://github.com/fmtlib/fmt/issues/756>`_). + +* Fixed formatting to an array with ``format_to_n`` + (`#778 <https://github.com/fmtlib/fmt/issues/778>`_). + +* Fixed formatting of more than 15 named arguments + (`#754 <https://github.com/fmtlib/fmt/issues/754>`_). + +* Fixed handling of compile-time strings when including ``fmt/ostream.h``. + (`#768 <https://github.com/fmtlib/fmt/issues/768>`_). + +* Fixed various compiler warnings and errors + (`#742 <https://github.com/fmtlib/fmt/issues/742>`_, + `#748 <https://github.com/fmtlib/fmt/issues/748>`_, + `#752 <https://github.com/fmtlib/fmt/issues/752>`_, + `#770 <https://github.com/fmtlib/fmt/issues/770>`_, + `#775 <https://github.com/fmtlib/fmt/pull/775>`_, + `#779 <https://github.com/fmtlib/fmt/issues/779>`_, + `#780 <https://github.com/fmtlib/fmt/pull/780>`_, + `#790 <https://github.com/fmtlib/fmt/pull/790>`_, + `#792 <https://github.com/fmtlib/fmt/pull/792>`_, + `#800 <https://github.com/fmtlib/fmt/pull/800>`_). + Thanks `@Remotion (Remo) <https://github.com/Remotion>`_, + `@gabime (Gabi Melman) <https://github.com/gabime>`_, + `@foonathan (Jonathan Müller) <https://github.com/foonathan>`_, + `@Dark-Passenger (Dhruv Paranjape) <https://github.com/Dark-Passenger>`_, and + `@0x8000-0000 (Sign Bit) <https://github.com/0x8000-0000>`_. + +5.0.0 - 2018-05-21 +------------------ + +* Added a requirement for partial C++11 support, most importantly variadic + templates and type traits, and dropped ``FMT_VARIADIC_*`` emulation macros. + Variadic templates are available since GCC 4.4, Clang 2.9 and MSVC 18.0 (2013). + For older compilers use {fmt} `version 4.x + <https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which continues to be + maintained and works with C++98 compilers. + +* Renamed symbols to follow standard C++ naming conventions and proposed a subset + of the library for standardization in `P0645R2 Text Formatting + <https://wg21.link/P0645>`_. + +* Implemented ``constexpr`` parsing of format strings and `compile-time format + string checks + <https://fmt.dev/latest/api.html#compile-time-format-string-checks>`_. For + example + + .. code:: c++ + + #include <fmt/format.h> + + std::string s = format(fmt("{:d}"), "foo"); + + gives a compile-time error because ``d`` is an invalid specifier for strings + (`godbolt <https://godbolt.org/g/rnCy9Q>`__):: + + ... + <source>:4:19: note: in instantiation of function template specialization 'fmt::v5::format<S, char [4]>' requested here + std::string s = format(fmt("{:d}"), "foo"); + ^ + format.h:1337:13: note: non-constexpr function 'on_error' cannot be used in a constant expression + handler.on_error("invalid type specifier"); + + Compile-time checks require relaxed ``constexpr`` (C++14 feature) support. If + the latter is not available, checks will be performed at runtime. + +* Separated format string parsing and formatting in the extension API to enable + compile-time format string processing. For example + + .. code:: c++ + + struct Answer {}; + + namespace fmt { + template <> + struct formatter<Answer> { + constexpr auto parse(parse_context& ctx) { + auto it = ctx.begin(); + spec = *it; + if (spec != 'd' && spec != 's') + throw format_error("invalid specifier"); + return ++it; + } + + template <typename FormatContext> + auto format(Answer, FormatContext& ctx) { + return spec == 's' ? + format_to(ctx.begin(), "{}", "fourty-two") : + format_to(ctx.begin(), "{}", 42); + } + + char spec = 0; + }; + } + + std::string s = format(fmt("{:x}"), Answer()); + + gives a compile-time error due to invalid format specifier (`godbolt + <https://godbolt.org/g/2jQ1Dv>`__):: + + ... + <source>:12:45: error: expression '<throw-expression>' is not a constant expression + throw format_error("invalid specifier"); + +* Added `iterator support + <https://fmt.dev/latest/api.html#output-iterator-support>`_: + + .. code:: c++ + + #include <vector> + #include <fmt/format.h> + + std::vector<char> out; + fmt::format_to(std::back_inserter(out), "{}", 42); + +* Added the `format_to_n + <https://fmt.dev/latest/api.html#_CPPv2N3fmt11format_to_nE8OutputItNSt6size_tE11string_viewDpRK4Args>`_ + function that restricts the output to the specified number of characters + (`#298 <https://github.com/fmtlib/fmt/issues/298>`_): + + .. code:: c++ + + char out[4]; + fmt::format_to_n(out, sizeof(out), "{}", 12345); + // out == "1234" (without terminating '\0') + +* Added the `formatted_size + <https://fmt.dev/latest/api.html#_CPPv2N3fmt14formatted_sizeE11string_viewDpRK4Args>`_ + function for computing the output size: + + .. code:: c++ + + #include <fmt/format.h> + + auto size = fmt::formatted_size("{}", 12345); // size == 5 + +* Improved compile times by reducing dependencies on standard headers and + providing a lightweight `core API <https://fmt.dev/latest/api.html#core-api>`_: + + .. code:: c++ + + #include <fmt/core.h> + + fmt::print("The answer is {}.", 42); + + See `Compile time and code bloat + <https://github.com/fmtlib/fmt#compile-time-and-code-bloat>`_. + +* Added the `make_format_args + <https://fmt.dev/latest/api.html#_CPPv2N3fmt16make_format_argsEDpRK4Args>`_ + function for capturing formatting arguments: + + .. code:: c++ + + // Prints formatted error message. + void vreport_error(const char *format, fmt::format_args args) { + fmt::print("Error: "); + fmt::vprint(format, args); + } + template <typename... Args> + void report_error(const char *format, const Args & ... args) { + vreport_error(format, fmt::make_format_args(args...)); + } + +* Added the ``make_printf_args`` function for capturing ``printf`` arguments + (`#687 <https://github.com/fmtlib/fmt/issues/687>`_, + `#694 <https://github.com/fmtlib/fmt/pull/694>`_). + Thanks `@Kronuz (Germán Méndez Bravo) <https://github.com/Kronuz>`_. + +* Added prefix ``v`` to non-variadic functions taking ``format_args`` to + distinguish them from variadic ones: + + .. code:: c++ + + std::string vformat(string_view format_str, format_args args); + + template <typename... Args> + std::string format(string_view format_str, const Args & ... args); + +* Added experimental support for formatting ranges, containers and tuple-like + types in ``fmt/ranges.h`` (`#735 <https://github.com/fmtlib/fmt/pull/735>`_): + + .. code:: c++ + + #include <fmt/ranges.h> + + std::vector<int> v = {1, 2, 3}; + fmt::print("{}", v); // prints {1, 2, 3} + + Thanks `@Remotion (Remo) <https://github.com/Remotion>`_. + +* Implemented ``wchar_t`` date and time formatting + (`#712 <https://github.com/fmtlib/fmt/pull/712>`_): + + .. code:: c++ + + #include <fmt/time.h> + + std::time_t t = std::time(nullptr); + auto s = fmt::format(L"The date is {:%Y-%m-%d}.", *std::localtime(&t)); + + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Provided more wide string overloads + (`#724 <https://github.com/fmtlib/fmt/pull/724>`_). + Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + +* Switched from a custom null-terminated string view class to ``string_view`` + in the format API and provided ``fmt::string_view`` which implements a subset + of ``std::string_view`` API for pre-C++17 systems. + +* Added support for ``std::experimental::string_view`` + (`#607 <https://github.com/fmtlib/fmt/pull/607>`_): + + .. code:: c++ + + #include <fmt/core.h> + #include <experimental/string_view> + + fmt::print("{}", std::experimental::string_view("foo")); + + Thanks `@virgiliofornazin (Virgilio Alexandre Fornazin) + <https://github.com/virgiliofornazin>`__. + +* Allowed mixing named and automatic arguments: + + .. code:: c++ + + fmt::format("{} {two}", 1, fmt::arg("two", 2)); + +* Removed the write API in favor of the `format API + <https://fmt.dev/latest/api.html#format-api>`_ with compile-time handling of + format strings. + +* Disallowed formatting of multibyte strings into a wide character target + (`#606 <https://github.com/fmtlib/fmt/pull/606>`_). + +* Improved documentation + (`#515 <https://github.com/fmtlib/fmt/pull/515>`_, + `#614 <https://github.com/fmtlib/fmt/issues/614>`_, + `#617 <https://github.com/fmtlib/fmt/pull/617>`_, + `#661 <https://github.com/fmtlib/fmt/pull/661>`_, + `#680 <https://github.com/fmtlib/fmt/pull/680>`_). + Thanks `@ibell (Ian Bell) <https://github.com/ibell>`_, + `@mihaitodor (Mihai Todor) <https://github.com/mihaitodor>`_, and + `@johnthagen <https://github.com/johnthagen>`_. + +* Implemented more efficient handling of large number of format arguments. + +* Introduced an inline namespace for symbol versioning. + +* Added debug postfix ``d`` to the ``fmt`` library name + (`#636 <https://github.com/fmtlib/fmt/issues/636>`_). + +* Removed unnecessary ``fmt/`` prefix in includes + (`#397 <https://github.com/fmtlib/fmt/pull/397>`_). + Thanks `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_. + +* Moved ``fmt/*.h`` to ``include/fmt/*.h`` to prevent irrelevant files and + directories appearing on the include search paths when fmt is used as a + subproject and moved source files to the ``src`` directory. + +* Added qmake project file ``support/fmt.pro`` + (`#641 <https://github.com/fmtlib/fmt/pull/641>`_). + Thanks `@cowo78 (Giuseppe Corbelli) <https://github.com/cowo78>`_. + +* Added Gradle build file ``support/build.gradle`` + (`#649 <https://github.com/fmtlib/fmt/pull/649>`_). + Thanks `@luncliff (Park DongHa) <https://github.com/luncliff>`_. + +* Removed ``FMT_CPPFORMAT`` CMake option. + +* Fixed a name conflict with the macro ``CHAR_WIDTH`` in glibc + (`#616 <https://github.com/fmtlib/fmt/pull/616>`_). + Thanks `@aroig (Abdó Roig-Maranges) <https://github.com/aroig>`_. + +* Fixed handling of nested braces in ``fmt::join`` + (`#638 <https://github.com/fmtlib/fmt/issues/638>`_). + +* Added ``SOURCELINK_SUFFIX`` for compatibility with Sphinx 1.5 + (`#497 <https://github.com/fmtlib/fmt/pull/497>`_). + Thanks `@ginggs (Graham Inggs) <https://github.com/ginggs>`_. + +* Added a missing ``inline`` in the header-only mode + (`#626 <https://github.com/fmtlib/fmt/pull/626>`_). + Thanks `@aroig (Abdó Roig-Maranges) <https://github.com/aroig>`_. + +* Fixed various compiler warnings + (`#640 <https://github.com/fmtlib/fmt/pull/640>`_, + `#656 <https://github.com/fmtlib/fmt/pull/656>`_, + `#679 <https://github.com/fmtlib/fmt/pull/679>`_, + `#681 <https://github.com/fmtlib/fmt/pull/681>`_, + `#705 <https://github.com/fmtlib/fmt/pull/705>`__, + `#715 <https://github.com/fmtlib/fmt/issues/715>`_, + `#717 <https://github.com/fmtlib/fmt/pull/717>`_, + `#720 <https://github.com/fmtlib/fmt/pull/720>`_, + `#723 <https://github.com/fmtlib/fmt/pull/723>`_, + `#726 <https://github.com/fmtlib/fmt/pull/726>`_, + `#730 <https://github.com/fmtlib/fmt/pull/730>`_, + `#739 <https://github.com/fmtlib/fmt/pull/739>`_). + Thanks `@peterbell10 <https://github.com/peterbell10>`_, + `@LarsGullik <https://github.com/LarsGullik>`_, + `@foonathan (Jonathan Müller) <https://github.com/foonathan>`_, + `@eliaskosunen (Elias Kosunen) <https://github.com/eliaskosunen>`_, + `@christianparpart (Christian Parpart) <https://github.com/christianparpart>`_, + `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_, + and `@mwinterb <https://github.com/mwinterb>`_. + +* Worked around an MSVC bug and fixed several warnings + (`#653 <https://github.com/fmtlib/fmt/pull/653>`_). + Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_. + +* Worked around GCC bug 67371 + (`#682 <https://github.com/fmtlib/fmt/issues/682>`_). + +* Fixed compilation with ``-fno-exceptions`` + (`#655 <https://github.com/fmtlib/fmt/pull/655>`_). + Thanks `@chenxiaolong (Andrew Gunnerson) <https://github.com/chenxiaolong>`_. + +* Made ``constexpr remove_prefix`` gcc version check tighter + (`#648 <https://github.com/fmtlib/fmt/issues/648>`_). + +* Renamed internal type enum constants to prevent collision with poorly written + C libraries (`#644 <https://github.com/fmtlib/fmt/issues/644>`_). + +* Added detection of ``wostream operator<<`` + (`#650 <https://github.com/fmtlib/fmt/issues/650>`_). + +* Fixed compilation on OpenBSD + (`#660 <https://github.com/fmtlib/fmt/pull/660>`_). + Thanks `@hubslave <https://github.com/hubslave>`_. + +* Fixed compilation on FreeBSD 12 + (`#732 <https://github.com/fmtlib/fmt/pull/732>`_). + Thanks `@dankm <https://github.com/dankm>`_. + +* Fixed compilation when there is a mismatch between ``-std`` options between + the library and user code + (`#664 <https://github.com/fmtlib/fmt/issues/664>`_). + +* Fixed compilation with GCC 7 and ``-std=c++11`` + (`#734 <https://github.com/fmtlib/fmt/issues/734>`_). + +* Improved generated binary code on GCC 7 and older + (`#668 <https://github.com/fmtlib/fmt/issues/668>`_). + +* Fixed handling of numeric alignment with no width + (`#675 <https://github.com/fmtlib/fmt/issues/675>`_). + +* Fixed handling of empty strings in UTF8/16 converters + (`#676 <https://github.com/fmtlib/fmt/pull/676>`_). + Thanks `@vgalka-sl (Vasili Galka) <https://github.com/vgalka-sl>`_. + +* Fixed formatting of an empty ``string_view`` + (`#689 <https://github.com/fmtlib/fmt/issues/689>`_). + +* Fixed detection of ``string_view`` on libc++ + (`#686 <https://github.com/fmtlib/fmt/issues/686>`_). + +* Fixed DLL issues (`#696 <https://github.com/fmtlib/fmt/pull/696>`_). + Thanks `@sebkoenig <https://github.com/sebkoenig>`_. + +* Fixed compile checks for mixing narrow and wide strings + (`#690 <https://github.com/fmtlib/fmt/issues/690>`_). + +* Disabled unsafe implicit conversion to ``std::string`` + (`#729 <https://github.com/fmtlib/fmt/issues/729>`_). + +* Fixed handling of reused format specs (as in ``fmt::join``) for pointers + (`#725 <https://github.com/fmtlib/fmt/pull/725>`_). + Thanks `@mwinterb <https://github.com/mwinterb>`_. + +* Fixed installation of ``fmt/ranges.h`` + (`#738 <https://github.com/fmtlib/fmt/pull/738>`_). + Thanks `@sv1990 <https://github.com/sv1990>`_. + +4.1.0 - 2017-12-20 +------------------ + +* Added ``fmt::to_wstring()`` in addition to ``fmt::to_string()`` + (`#559 <https://github.com/fmtlib/fmt/pull/559>`_). + Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_. + +* Added support for C++17 ``std::string_view`` + (`#571 <https://github.com/fmtlib/fmt/pull/571>`_ and + `#578 <https://github.com/fmtlib/fmt/pull/578>`_). + Thanks `@thelostt (Mário Feroldi) <https://github.com/thelostt>`_ and + `@mwinterb <https://github.com/mwinterb>`_. + +* Enabled stream exceptions to catch errors + (`#581 <https://github.com/fmtlib/fmt/issues/581>`_). + Thanks `@crusader-mike <https://github.com/crusader-mike>`_. + +* Allowed formatting of class hierarchies with ``fmt::format_arg()`` + (`#547 <https://github.com/fmtlib/fmt/pull/547>`_). + Thanks `@rollbear (Björn Fahller) <https://github.com/rollbear>`_. + +* Removed limitations on character types + (`#563 <https://github.com/fmtlib/fmt/pull/563>`_). + Thanks `@Yelnats321 (Elnar Dakeshov) <https://github.com/Yelnats321>`_. + +* Conditionally enabled use of ``std::allocator_traits`` + (`#583 <https://github.com/fmtlib/fmt/pull/583>`_). + Thanks `@mwinterb <https://github.com/mwinterb>`_. + +* Added support for ``const`` variadic member function emulation with + ``FMT_VARIADIC_CONST`` (`#591 <https://github.com/fmtlib/fmt/pull/591>`_). + Thanks `@ludekvodicka (Ludek Vodicka) <https://github.com/ludekvodicka>`_. + +* Various bugfixes: bad overflow check, unsupported implicit type conversion + when determining formatting function, test segfaults + (`#551 <https://github.com/fmtlib/fmt/issues/551>`_), ill-formed macros + (`#542 <https://github.com/fmtlib/fmt/pull/542>`_) and ambiguous overloads + (`#580 <https://github.com/fmtlib/fmt/issues/580>`_). + Thanks `@xylosper (Byoung-young Lee) <https://github.com/xylosper>`_. + +* Prevented warnings on MSVC (`#605 <https://github.com/fmtlib/fmt/pull/605>`_, + `#602 <https://github.com/fmtlib/fmt/pull/602>`_, and + `#545 <https://github.com/fmtlib/fmt/pull/545>`_), + clang (`#582 <https://github.com/fmtlib/fmt/pull/582>`_), + GCC (`#573 <https://github.com/fmtlib/fmt/issues/573>`_), + various conversion warnings (`#609 <https://github.com/fmtlib/fmt/pull/609>`_, + `#567 <https://github.com/fmtlib/fmt/pull/567>`_, + `#553 <https://github.com/fmtlib/fmt/pull/553>`_ and + `#553 <https://github.com/fmtlib/fmt/pull/553>`_), and added ``override`` and + ``[[noreturn]]`` (`#549 <https://github.com/fmtlib/fmt/pull/549>`_ and + `#555 <https://github.com/fmtlib/fmt/issues/555>`_). + Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_, + `@virgiliofornazin (Virgilio Alexandre Fornazin) + <https://gihtub.com/virgiliofornazin>`_, + `@alexanderbock (Alexander Bock) <https://github.com/alexanderbock>`_, + `@yumetodo <https://github.com/yumetodo>`_, + `@VaderY (Császár Mátyás) <https://github.com/VaderY>`_, + `@jpcima (JP Cimalando) <https://github.com/jpcima>`_, + `@thelostt (Mário Feroldi) <https://github.com/thelostt>`_, and + `@Manu343726 (Manu Sánchez) <https://github.com/Manu343726>`_. + +* Improved CMake: Used ``GNUInstallDirs`` to set installation location + (`#610 <https://github.com/fmtlib/fmt/pull/610>`_) and fixed warnings + (`#536 <https://github.com/fmtlib/fmt/pull/536>`_ and + `#556 <https://github.com/fmtlib/fmt/pull/556>`_). + Thanks `@mikecrowe (Mike Crowe) <https://github.com/mikecrowe>`_, + `@evgen231 <https://github.com/evgen231>`_ and + `@henryiii (Henry Schreiner) <https://github.com/henryiii>`_. + +4.0.0 - 2017-06-27 +------------------ + +* Removed old compatibility headers ``cppformat/*.h`` and CMake options + (`#527 <https://github.com/fmtlib/fmt/pull/527>`_). + Thanks `@maddinat0r (Alex Martin) <https://github.com/maddinat0r>`_. + +* Added ``string.h`` containing ``fmt::to_string()`` as alternative to + ``std::to_string()`` as well as other string writer functionality + (`#326 <https://github.com/fmtlib/fmt/issues/326>`_ and + `#441 <https://github.com/fmtlib/fmt/pull/441>`_): + + .. code:: c++ + + #include "fmt/string.h" + + std::string answer = fmt::to_string(42); + + Thanks to `@glebov-andrey (Andrey Glebov) + <https://github.com/glebov-andrey>`_. + +* Moved ``fmt::printf()`` to new ``printf.h`` header and allowed ``%s`` as + generic specifier (`#453 <https://github.com/fmtlib/fmt/pull/453>`_), + made ``%.f`` more conformant to regular ``printf()`` + (`#490 <https://github.com/fmtlib/fmt/pull/490>`_), added custom writer + support (`#476 <https://github.com/fmtlib/fmt/issues/476>`_) and implemented + missing custom argument formatting + (`#339 <https://github.com/fmtlib/fmt/pull/339>`_ and + `#340 <https://github.com/fmtlib/fmt/pull/340>`_): + + .. code:: c++ + + #include "fmt/printf.h" + + // %s format specifier can be used with any argument type. + fmt::printf("%s", 42); + + Thanks `@mojoBrendan <https://github.com/mojoBrendan>`_, + `@manylegged (Arthur Danskin) <https://github.com/manylegged>`_ and + `@spacemoose (Glen Stark) <https://github.com/spacemoose>`_. + See also `#360 <https://github.com/fmtlib/fmt/issues/360>`_, + `#335 <https://github.com/fmtlib/fmt/issues/335>`_ and + `#331 <https://github.com/fmtlib/fmt/issues/331>`_. + +* Added ``container.h`` containing a ``BasicContainerWriter`` + to write to containers like ``std::vector`` + (`#450 <https://github.com/fmtlib/fmt/pull/450>`_). + Thanks `@polyvertex (Jean-Charles Lefebvre) <https://github.com/polyvertex>`_. + +* Added ``fmt::join()`` function that takes a range and formats + its elements separated by a given string + (`#466 <https://github.com/fmtlib/fmt/pull/466>`_): + + .. code:: c++ + + #include "fmt/format.h" + + std::vector<double> v = {1.2, 3.4, 5.6}; + // Prints "(+01.20, +03.40, +05.60)". + fmt::print("({:+06.2f})", fmt::join(v.begin(), v.end(), ", ")); + + Thanks `@olivier80 <https://github.com/olivier80>`_. + +* Added support for custom formatting specifications to simplify customization + of built-in formatting (`#444 <https://github.com/fmtlib/fmt/pull/444>`_). + Thanks `@polyvertex (Jean-Charles Lefebvre) <https://github.com/polyvertex>`_. + See also `#439 <https://github.com/fmtlib/fmt/issues/439>`_. + +* Added ``fmt::format_system_error()`` for error code formatting + (`#323 <https://github.com/fmtlib/fmt/issues/323>`_ and + `#526 <https://github.com/fmtlib/fmt/pull/526>`_). + Thanks `@maddinat0r (Alex Martin) <https://github.com/maddinat0r>`_. + +* Added thread-safe ``fmt::localtime()`` and ``fmt::gmtime()`` + as replacement for the standard version to ``time.h`` + (`#396 <https://github.com/fmtlib/fmt/pull/396>`_). + Thanks `@codicodi <https://github.com/codicodi>`_. + +* Internal improvements to ``NamedArg`` and ``ArgLists`` + (`#389 <https://github.com/fmtlib/fmt/pull/389>`_ and + `#390 <https://github.com/fmtlib/fmt/pull/390>`_). + Thanks `@chronoxor <https://github.com/chronoxor>`_. + +* Fixed crash due to bug in ``FormatBuf`` + (`#493 <https://github.com/fmtlib/fmt/pull/493>`_). + Thanks `@effzeh <https://github.com/effzeh>`_. See also + `#480 <https://github.com/fmtlib/fmt/issues/480>`_ and + `#491 <https://github.com/fmtlib/fmt/issues/491>`_. + +* Fixed handling of wide strings in ``fmt::StringWriter``. + +* Improved compiler error messages + (`#357 <https://github.com/fmtlib/fmt/issues/357>`_). + +* Fixed various warnings and issues with various compilers + (`#494 <https://github.com/fmtlib/fmt/pull/494>`_, + `#499 <https://github.com/fmtlib/fmt/pull/499>`_, + `#483 <https://github.com/fmtlib/fmt/pull/483>`_, + `#485 <https://github.com/fmtlib/fmt/pull/485>`_, + `#482 <https://github.com/fmtlib/fmt/pull/482>`_, + `#475 <https://github.com/fmtlib/fmt/pull/475>`_, + `#473 <https://github.com/fmtlib/fmt/pull/473>`_ and + `#414 <https://github.com/fmtlib/fmt/pull/414>`_). + Thanks `@chronoxor <https://github.com/chronoxor>`_, + `@zhaohuaxishi <https://github.com/zhaohuaxishi>`_, + `@pkestene (Pierre Kestener) <https://github.com/pkestene>`_, + `@dschmidt (Dominik Schmidt) <https://github.com/dschmidt>`_ and + `@0x414c (Alexey Gorishny) <https://github.com/0x414c>`_ . + +* Improved CMake: targets are now namespaced + (`#511 <https://github.com/fmtlib/fmt/pull/511>`_ and + `#513 <https://github.com/fmtlib/fmt/pull/513>`_), supported header-only + ``printf.h`` (`#354 <https://github.com/fmtlib/fmt/pull/354>`_), fixed issue + with minimal supported library subset + (`#418 <https://github.com/fmtlib/fmt/issues/418>`_, + `#419 <https://github.com/fmtlib/fmt/pull/419>`_ and + `#420 <https://github.com/fmtlib/fmt/pull/420>`_). + Thanks `@bjoernthiel (Bjoern Thiel) <https://github.com/bjoernthiel>`_, + `@niosHD (Mario Werner) <https://github.com/niosHD>`_, + `@LogicalKnight (Sean LK) <https://github.com/LogicalKnight>`_ and + `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_. + +* Improved documentation. Thanks to + `@pwm1234 (Phil) <https://github.com/pwm1234>`_ for + `#393 <https://github.com/fmtlib/fmt/pull/393>`_. + +3.0.2 - 2017-06-14 +------------------ + +* Added ``FMT_VERSION`` macro + (`#411 <https://github.com/fmtlib/fmt/issues/411>`_). + +* Used ``FMT_NULL`` instead of literal ``0`` + (`#409 <https://github.com/fmtlib/fmt/pull/409>`_). + Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_. + +* Added extern templates for ``format_float`` + (`#413 <https://github.com/fmtlib/fmt/issues/413>`_). + +* Fixed implicit conversion issue + (`#507 <https://github.com/fmtlib/fmt/issues/507>`_). + +* Fixed signbit detection (`#423 <https://github.com/fmtlib/fmt/issues/423>`_). + +* Fixed naming collision (`#425 <https://github.com/fmtlib/fmt/issues/425>`_). + +* Fixed missing intrinsic for C++/CLI + (`#457 <https://github.com/fmtlib/fmt/pull/457>`_). + Thanks `@calumr (Calum Robinson) <https://github.com/calumr>`_ + +* Fixed Android detection (`#458 <https://github.com/fmtlib/fmt/pull/458>`_). + Thanks `@Gachapen (Magnus Bjerke Vik) <https://github.com/Gachapen>`_. + +* Use lean ``windows.h`` if not in header-only mode + (`#503 <https://github.com/fmtlib/fmt/pull/503>`_). + Thanks `@Quentin01 (Quentin Buathier) <https://github.com/Quentin01>`_. + +* Fixed issue with CMake exporting C++11 flag + (`#445 <https://github.com/fmtlib/fmt/pull/455>`_). + Thanks `@EricWF (Eric) <https://github.com/EricWF>`_. + +* Fixed issue with nvcc and MSVC compiler bug and MinGW + (`#505 <https://github.com/fmtlib/fmt/issues/505>`_). + +* Fixed DLL issues (`#469 <https://github.com/fmtlib/fmt/pull/469>`_ and + `#502 <https://github.com/fmtlib/fmt/pull/502>`_). + Thanks `@richardeakin (Richard Eakin) <https://github.com/richardeakin>`_ and + `@AndreasSchoenle (Andreas Schönle) <https://github.com/AndreasSchoenle>`_. + +* Fixed test compilation under FreeBSD + (`#433 <https://github.com/fmtlib/fmt/issues/433>`_). + +* Fixed various warnings (`#403 <https://github.com/fmtlib/fmt/pull/403>`_, + `#410 <https://github.com/fmtlib/fmt/pull/410>`_ and + `#510 <https://github.com/fmtlib/fmt/pull/510>`_). + Thanks `@Lecetem <https://github.com/Lectem>`_, + `@chenhayat (Chen Hayat) <https://github.com/chenhayat>`_ and + `@trozen <https://github.com/trozen>`_. + +* Worked around a broken ``__builtin_clz`` in clang with MS codegen + (`#519 <https://github.com/fmtlib/fmt/issues/519>`_). + +* Removed redundant include + (`#479 <https://github.com/fmtlib/fmt/issues/479>`_). + +* Fixed documentation issues. + +3.0.1 - 2016-11-01 +------------------ +* Fixed handling of thousands separator + (`#353 <https://github.com/fmtlib/fmt/issues/353>`_). + +* Fixed handling of ``unsigned char`` strings + (`#373 <https://github.com/fmtlib/fmt/issues/373>`_). + +* Corrected buffer growth when formatting time + (`#367 <https://github.com/fmtlib/fmt/issues/367>`_). + +* Removed warnings under MSVC and clang + (`#318 <https://github.com/fmtlib/fmt/issues/318>`_, + `#250 <https://github.com/fmtlib/fmt/issues/250>`_, also merged + `#385 <https://github.com/fmtlib/fmt/pull/385>`_ and + `#361 <https://github.com/fmtlib/fmt/pull/361>`_). + Thanks `@jcelerier (Jean-Michaël Celerier) <https://github.com/jcelerier>`_ + and `@nmoehrle (Nils Moehrle) <https://github.com/nmoehrle>`_. + +* Fixed compilation issues under Android + (`#327 <https://github.com/fmtlib/fmt/pull/327>`_, + `#345 <https://github.com/fmtlib/fmt/issues/345>`_ and + `#381 <https://github.com/fmtlib/fmt/pull/381>`_), + FreeBSD (`#358 <https://github.com/fmtlib/fmt/pull/358>`_), + Cygwin (`#388 <https://github.com/fmtlib/fmt/issues/388>`_), + MinGW (`#355 <https://github.com/fmtlib/fmt/issues/355>`_) as well as other + issues (`#350 <https://github.com/fmtlib/fmt/issues/350>`_, + `#366 <https://github.com/fmtlib/fmt/issues/355>`_, + `#348 <https://github.com/fmtlib/fmt/pull/348>`_, + `#402 <https://github.com/fmtlib/fmt/pull/402>`_, + `#405 <https://github.com/fmtlib/fmt/pull/405>`_). + Thanks to `@dpantele (Dmitry) <https://github.com/dpantele>`_, + `@hghwng (Hugh Wang) <https://github.com/hghwng>`_, + `@arvedarved (Tilman Keskinöz) <https://github.com/arvedarved>`_, + `@LogicalKnight (Sean) <https://github.com/LogicalKnight>`_ and + `@JanHellwig (Jan Hellwig) <https://github.com/janhellwig>`_. + +* Fixed some documentation issues and extended specification + (`#320 <https://github.com/fmtlib/fmt/issues/320>`_, + `#333 <https://github.com/fmtlib/fmt/pull/333>`_, + `#347 <https://github.com/fmtlib/fmt/issues/347>`_, + `#362 <https://github.com/fmtlib/fmt/pull/362>`_). + Thanks to `@smellman (Taro Matsuzawa aka. btm) + <https://github.com/smellman>`_. + +3.0.0 - 2016-05-07 +------------------ + +* The project has been renamed from C++ Format (cppformat) to fmt for + consistency with the used namespace and macro prefix + (`#307 <https://github.com/fmtlib/fmt/issues/307>`_). + Library headers are now located in the ``fmt`` directory: + + .. code:: c++ + + #include "fmt/format.h" + + Including ``format.h`` from the ``cppformat`` directory is deprecated + but works via a proxy header which will be removed in the next major version. + + The documentation is now available at https://fmt.dev. + +* Added support for `strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_-like + `date and time formatting <https://fmt.dev/3.0.0/api.html#date-and-time-formatting>`_ + (`#283 <https://github.com/fmtlib/fmt/issues/283>`_): + + .. code:: c++ + + #include "fmt/time.h" + + std::time_t t = std::time(nullptr); + // Prints "The date is 2016-04-29." (with the current date) + fmt::print("The date is {:%Y-%m-%d}.", *std::localtime(&t)); + +* ``std::ostream`` support including formatting of user-defined types that provide + overloaded ``operator<<`` has been moved to ``fmt/ostream.h``: + + .. code:: c++ + + #include "fmt/ostream.h" + + class Date { + int year_, month_, day_; + public: + Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} + + friend std::ostream &operator<<(std::ostream &os, const Date &d) { + return os << d.year_ << '-' << d.month_ << '-' << d.day_; + } + }; + + std::string s = fmt::format("The date is {}", Date(2012, 12, 9)); + // s == "The date is 2012-12-9" + +* Added support for `custom argument formatters + <https://fmt.dev/3.0.0/api.html#argument-formatters>`_ + (`#235 <https://github.com/fmtlib/fmt/issues/235>`_). + +* Added support for locale-specific integer formatting with the ``n`` specifier + (`#305 <https://github.com/fmtlib/fmt/issues/305>`_): + + .. code:: c++ + + std::setlocale(LC_ALL, "en_US.utf8"); + fmt::print("cppformat: {:n}\n", 1234567); // prints 1,234,567 + +* Sign is now preserved when formatting an integer with an incorrect ``printf`` + format specifier (`#265 <https://github.com/fmtlib/fmt/issues/265>`_): + + .. code:: c++ + + fmt::printf("%lld", -42); // prints -42 + + Note that it would be an undefined behavior in ``std::printf``. + +* Length modifiers such as ``ll`` are now optional in printf formatting + functions and the correct type is determined automatically + (`#255 <https://github.com/fmtlib/fmt/issues/255>`_): + + .. code:: c++ + + fmt::printf("%d", std::numeric_limits<long long>::max()); + + Note that it would be an undefined behavior in ``std::printf``. + +* Added initial support for custom formatters + (`#231 <https://github.com/fmtlib/fmt/issues/231>`_). + +* Fixed detection of user-defined literal support on Intel C++ compiler + (`#311 <https://github.com/fmtlib/fmt/issues/311>`_, + `#312 <https://github.com/fmtlib/fmt/pull/312>`_). + Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_ and + `@speth (Ray Speth) <https://github.com/speth>`_. + +* Reduced compile time + (`#243 <https://github.com/fmtlib/fmt/pull/243>`_, + `#249 <https://github.com/fmtlib/fmt/pull/249>`_, + `#317 <https://github.com/fmtlib/fmt/issues/317>`_): + + .. image:: https://cloud.githubusercontent.com/assets/4831417/11614060/ + b9e826d2-9c36-11e5-8666-d4131bf503ef.png + + .. image:: https://cloud.githubusercontent.com/assets/4831417/11614080/ + 6ac903cc-9c37-11e5-8165-26df6efae364.png + + Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_. + +* Compile test fixes (`#313 <https://github.com/fmtlib/fmt/pull/313>`_). + Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_. + +* Documentation fixes (`#239 <https://github.com/fmtlib/fmt/pull/239>`_, + `#248 <https://github.com/fmtlib/fmt/issues/248>`_, + `#252 <https://github.com/fmtlib/fmt/issues/252>`_, + `#258 <https://github.com/fmtlib/fmt/pull/258>`_, + `#260 <https://github.com/fmtlib/fmt/issues/260>`_, + `#301 <https://github.com/fmtlib/fmt/issues/301>`_, + `#309 <https://github.com/fmtlib/fmt/pull/309>`_). + Thanks to `@ReadmeCritic <https://github.com/ReadmeCritic>`_ + `@Gachapen (Magnus Bjerke Vik) <https://github.com/Gachapen>`_ and + `@jwilk (Jakub Wilk) <https://github.com/jwilk>`_. + +* Fixed compiler and sanitizer warnings + (`#244 <https://github.com/fmtlib/fmt/issues/244>`_, + `#256 <https://github.com/fmtlib/fmt/pull/256>`_, + `#259 <https://github.com/fmtlib/fmt/pull/259>`_, + `#263 <https://github.com/fmtlib/fmt/issues/263>`_, + `#274 <https://github.com/fmtlib/fmt/issues/274>`_, + `#277 <https://github.com/fmtlib/fmt/pull/277>`_, + `#286 <https://github.com/fmtlib/fmt/pull/286>`_, + `#291 <https://github.com/fmtlib/fmt/issues/291>`_, + `#296 <https://github.com/fmtlib/fmt/issues/296>`_, + `#308 <https://github.com/fmtlib/fmt/issues/308>`_) + Thanks to `@mwinterb <https://github.com/mwinterb>`_, + `@pweiskircher (Patrik Weiskircher) <https://github.com/pweiskircher>`_, + `@Naios <https://github.com/Naios>`_. + +* Improved compatibility with Windows Store apps + (`#280 <https://github.com/fmtlib/fmt/issues/280>`_, + `#285 <https://github.com/fmtlib/fmt/pull/285>`_) + Thanks to `@mwinterb <https://github.com/mwinterb>`_. + +* Added tests of compatibility with older C++ standards + (`#273 <https://github.com/fmtlib/fmt/pull/273>`_). + Thanks to `@niosHD <https://github.com/niosHD>`_. + +* Fixed Android build (`#271 <https://github.com/fmtlib/fmt/pull/271>`_). + Thanks to `@newnon <https://github.com/newnon>`_. + +* Changed ``ArgMap`` to be backed by a vector instead of a map. + (`#261 <https://github.com/fmtlib/fmt/issues/261>`_, + `#262 <https://github.com/fmtlib/fmt/pull/262>`_). + Thanks to `@mwinterb <https://github.com/mwinterb>`_. + +* Added ``fprintf`` overload that writes to a ``std::ostream`` + (`#251 <https://github.com/fmtlib/fmt/pull/251>`_). + Thanks to `nickhutchinson (Nicholas Hutchinson) <https://github.com/nickhutchinson>`_. + +* Export symbols when building a Windows DLL + (`#245 <https://github.com/fmtlib/fmt/pull/245>`_). + Thanks to `macdems (Maciek Dems) <https://github.com/macdems>`_. + +* Fixed compilation on Cygwin (`#304 <https://github.com/fmtlib/fmt/issues/304>`_). + +* Implemented a workaround for a bug in Apple LLVM version 4.2 of clang + (`#276 <https://github.com/fmtlib/fmt/issues/276>`_). + +* Implemented a workaround for Google Test bug + `#705 <https://github.com/google/googletest/issues/705>`_ on gcc 6 + (`#268 <https://github.com/fmtlib/fmt/issues/268>`_). + Thanks to `octoploid <https://github.com/octoploid>`_. + +* Removed Biicode support because the latter has been discontinued. + +2.1.1 - 2016-04-11 +------------------ + +* The install location for generated CMake files is now configurable via + the ``FMT_CMAKE_DIR`` CMake variable + (`#299 <https://github.com/fmtlib/fmt/pull/299>`_). + Thanks to `@niosHD <https://github.com/niosHD>`_. + +* Documentation fixes (`#252 <https://github.com/fmtlib/fmt/issues/252>`_). + +2.1.0 - 2016-03-21 +------------------ + +* Project layout and build system improvements + (`#267 <https://github.com/fmtlib/fmt/pull/267>`_): + + * The code have been moved to the ``cppformat`` directory. + Including ``format.h`` from the top-level directory is deprecated + but works via a proxy header which will be removed in the next + major version. + + * C++ Format CMake targets now have proper interface definitions. + + * Installed version of the library now supports the header-only + configuration. + + * Targets ``doc``, ``install``, and ``test`` are now disabled if C++ Format + is included as a CMake subproject. They can be enabled by setting + ``FMT_DOC``, ``FMT_INSTALL``, and ``FMT_TEST`` in the parent project. + + Thanks to `@niosHD <https://github.com/niosHD>`_. + +2.0.1 - 2016-03-13 +------------------ + +* Improved CMake find and package support + (`#264 <https://github.com/fmtlib/fmt/issues/264>`_). + Thanks to `@niosHD <https://github.com/niosHD>`_. + +* Fix compile error with Android NDK and mingw32 + (`#241 <https://github.com/fmtlib/fmt/issues/241>`_). + Thanks to `@Gachapen (Magnus Bjerke Vik) <https://github.com/Gachapen>`_. + +* Documentation fixes + (`#248 <https://github.com/fmtlib/fmt/issues/248>`_, + `#260 <https://github.com/fmtlib/fmt/issues/260>`_). + +2.0.0 - 2015-12-01 +------------------ + +General +~~~~~~~ + +* [Breaking] Named arguments + (`#169 <https://github.com/fmtlib/fmt/pull/169>`_, + `#173 <https://github.com/fmtlib/fmt/pull/173>`_, + `#174 <https://github.com/fmtlib/fmt/pull/174>`_): + + .. code:: c++ + + fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); + + Thanks to `@jamboree <https://github.com/jamboree>`_. + +* [Experimental] User-defined literals for format and named arguments + (`#204 <https://github.com/fmtlib/fmt/pull/204>`_, + `#206 <https://github.com/fmtlib/fmt/pull/206>`_, + `#207 <https://github.com/fmtlib/fmt/pull/207>`_): + + .. code:: c++ + + using namespace fmt::literals; + fmt::print("The answer is {answer}.", "answer"_a=42); + + Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_. + +* [Breaking] Formatting of more than 16 arguments is now supported when using + variadic templates + (`#141 <https://github.com/fmtlib/fmt/issues/141>`_). + Thanks to `@Shauren <https://github.com/Shauren>`_. + +* Runtime width specification + (`#168 <https://github.com/fmtlib/fmt/pull/168>`_): + + .. code:: c++ + + fmt::format("{0:{1}}", 42, 5); // gives " 42" + + Thanks to `@jamboree <https://github.com/jamboree>`_. + +* [Breaking] Enums are now formatted with an overloaded ``std::ostream`` insertion + operator (``operator<<``) if available + (`#232 <https://github.com/fmtlib/fmt/issues/232>`_). + +* [Breaking] Changed default ``bool`` format to textual, "true" or "false" + (`#170 <https://github.com/fmtlib/fmt/issues/170>`_): + + .. code:: c++ + + fmt::print("{}", true); // prints "true" + + To print ``bool`` as a number use numeric format specifier such as ``d``: + + .. code:: c++ + + fmt::print("{:d}", true); // prints "1" + +* ``fmt::printf`` and ``fmt::sprintf`` now support formatting of ``bool`` with the + ``%s`` specifier giving textual output, "true" or "false" + (`#223 <https://github.com/fmtlib/fmt/pull/223>`_): + + .. code:: c++ + + fmt::printf("%s", true); // prints "true" + + Thanks to `@LarsGullik <https://github.com/LarsGullik>`_. + +* [Breaking] ``signed char`` and ``unsigned char`` are now formatted as integers by default + (`#217 <https://github.com/fmtlib/fmt/pull/217>`_). + +* [Breaking] Pointers to C strings can now be formatted with the ``p`` specifier + (`#223 <https://github.com/fmtlib/fmt/pull/223>`_): + + .. code:: c++ + + fmt::print("{:p}", "test"); // prints pointer value + + Thanks to `@LarsGullik <https://github.com/LarsGullik>`_. + +* [Breaking] ``fmt::printf`` and ``fmt::sprintf`` now print null pointers as ``(nil)`` + and null strings as ``(null)`` for consistency with glibc + (`#226 <https://github.com/fmtlib/fmt/pull/226>`_). + Thanks to `@LarsGullik <https://github.com/LarsGullik>`_. + +* [Breaking] ``fmt::(s)printf`` now supports formatting of objects of user-defined types + that provide an overloaded ``std::ostream`` insertion operator (``operator<<``) + (`#201 <https://github.com/fmtlib/fmt/issues/201>`_): + + .. code:: c++ + + fmt::printf("The date is %s", Date(2012, 12, 9)); + +* [Breaking] The ``Buffer`` template is now part of the public API and can be used + to implement custom memory buffers + (`#140 <https://github.com/fmtlib/fmt/issues/140>`_). + Thanks to `@polyvertex (Jean-Charles Lefebvre) <https://github.com/polyvertex>`_. + +* [Breaking] Improved compatibility between ``BasicStringRef`` and + `std::experimental::basic_string_view + <http://en.cppreference.com/w/cpp/experimental/basic_string_view>`_ + (`#100 <https://github.com/fmtlib/fmt/issues/100>`_, + `#159 <https://github.com/fmtlib/fmt/issues/159>`_, + `#183 <https://github.com/fmtlib/fmt/issues/183>`_): + + - Comparison operators now compare string content, not pointers + - ``BasicStringRef::c_str`` replaced by ``BasicStringRef::data`` + - ``BasicStringRef`` is no longer assumed to be null-terminated + + References to null-terminated strings are now represented by a new class, + ``BasicCStringRef``. + +* Dependency on pthreads introduced by Google Test is now optional + (`#185 <https://github.com/fmtlib/fmt/issues/185>`_). + +* New CMake options ``FMT_DOC``, ``FMT_INSTALL`` and ``FMT_TEST`` to control + generation of ``doc``, ``install`` and ``test`` targets respectively, on by default + (`#197 <https://github.com/fmtlib/fmt/issues/197>`_, + `#198 <https://github.com/fmtlib/fmt/issues/198>`_, + `#200 <https://github.com/fmtlib/fmt/issues/200>`_). + Thanks to `@maddinat0r (Alex Martin) <https://github.com/maddinat0r>`_. + +* ``noexcept`` is now used when compiling with MSVC2015 + (`#215 <https://github.com/fmtlib/fmt/pull/215>`_). + Thanks to `@dmkrepo (Dmitriy) <https://github.com/dmkrepo>`_. + +* Added an option to disable use of ``windows.h`` when ``FMT_USE_WINDOWS_H`` + is defined as 0 before including ``format.h`` + (`#171 <https://github.com/fmtlib/fmt/issues/171>`_). + Thanks to `@alfps (Alf P. Steinbach) <https://github.com/alfps>`_. + +* [Breaking] ``windows.h`` is now included with ``NOMINMAX`` unless + ``FMT_WIN_MINMAX`` is defined. This is done to prevent breaking code using + ``std::min`` and ``std::max`` and only affects the header-only configuration + (`#152 <https://github.com/fmtlib/fmt/issues/152>`_, + `#153 <https://github.com/fmtlib/fmt/pull/153>`_, + `#154 <https://github.com/fmtlib/fmt/pull/154>`_). + Thanks to `@DevO2012 <https://github.com/DevO2012>`_. + +* Improved support for custom character types + (`#171 <https://github.com/fmtlib/fmt/issues/171>`_). + Thanks to `@alfps (Alf P. Steinbach) <https://github.com/alfps>`_. + +* Added an option to disable use of IOStreams when ``FMT_USE_IOSTREAMS`` + is defined as 0 before including ``format.h`` + (`#205 <https://github.com/fmtlib/fmt/issues/205>`_, + `#208 <https://github.com/fmtlib/fmt/pull/208>`_). + Thanks to `@JodiTheTigger <https://github.com/JodiTheTigger>`_. + +* Improved detection of ``isnan``, ``isinf`` and ``signbit``. + +Optimization +~~~~~~~~~~~~ + +* Made formatting of user-defined types more efficient with a custom stream buffer + (`#92 <https://github.com/fmtlib/fmt/issues/92>`_, + `#230 <https://github.com/fmtlib/fmt/pull/230>`_). + Thanks to `@NotImplemented <https://github.com/NotImplemented>`_. + +* Further improved performance of ``fmt::Writer`` on integer formatting + and fixed a minor regression. Now it is ~7% faster than ``karma::generate`` + on Karma's benchmark + (`#186 <https://github.com/fmtlib/fmt/issues/186>`_). + +* [Breaking] Reduced `compiled code size + <https://github.com/fmtlib/fmt#compile-time-and-code-bloat>`_ + (`#143 <https://github.com/fmtlib/fmt/issues/143>`_, + `#149 <https://github.com/fmtlib/fmt/pull/149>`_). + +Distribution +~~~~~~~~~~~~ + +* [Breaking] Headers are now installed in + ``${CMAKE_INSTALL_PREFIX}/include/cppformat`` + (`#178 <https://github.com/fmtlib/fmt/issues/178>`_). + Thanks to `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_. + +* [Breaking] Changed the library name from ``format`` to ``cppformat`` + for consistency with the project name and to avoid potential conflicts + (`#178 <https://github.com/fmtlib/fmt/issues/178>`_). + Thanks to `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_. + +* C++ Format is now available in `Debian <https://www.debian.org/>`_ GNU/Linux + (`stretch <https://packages.debian.org/source/stretch/cppformat>`_, + `sid <https://packages.debian.org/source/sid/cppformat>`_) and + derived distributions such as + `Ubuntu <https://launchpad.net/ubuntu/+source/cppformat>`_ 15.10 and later + (`#155 <https://github.com/fmtlib/fmt/issues/155>`_):: + + $ sudo apt-get install libcppformat1-dev + + Thanks to `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_. + +* `Packages for Fedora and RHEL <https://admin.fedoraproject.org/pkgdb/package/cppformat/>`_ + are now available. Thanks to Dave Johansen. + +* C++ Format can now be installed via `Homebrew <http://brew.sh/>`_ on OS X + (`#157 <https://github.com/fmtlib/fmt/issues/157>`_):: + + $ brew install cppformat + + Thanks to `@ortho <https://github.com/ortho>`_, Anatoliy Bulukin. + +Documentation +~~~~~~~~~~~~~ + +* Migrated from ReadTheDocs to GitHub Pages for better responsiveness + and reliability + (`#128 <https://github.com/fmtlib/fmt/issues/128>`_). + New documentation address is http://cppformat.github.io/. + + +* Added `Building the documentation + <https://fmt.dev/2.0.0/usage.html#building-the-documentation>`_ + section to the documentation. + +* Documentation build script is now compatible with Python 3 and newer pip versions. + (`#189 <https://github.com/fmtlib/fmt/pull/189>`_, + `#209 <https://github.com/fmtlib/fmt/issues/209>`_). + Thanks to `@JodiTheTigger <https://github.com/JodiTheTigger>`_ and + `@xentec <https://github.com/xentec>`_. + +* Documentation fixes and improvements + (`#36 <https://github.com/fmtlib/fmt/issues/36>`_, + `#75 <https://github.com/fmtlib/fmt/issues/75>`_, + `#125 <https://github.com/fmtlib/fmt/issues/125>`_, + `#160 <https://github.com/fmtlib/fmt/pull/160>`_, + `#161 <https://github.com/fmtlib/fmt/pull/161>`_, + `#162 <https://github.com/fmtlib/fmt/issues/162>`_, + `#165 <https://github.com/fmtlib/fmt/issues/165>`_, + `#210 <https://github.com/fmtlib/fmt/issues/210>`_). + Thanks to `@syohex (Syohei YOSHIDA) <https://github.com/syohex>`_ and + bug reporters. + +* Fixed out-of-tree documentation build + (`#177 <https://github.com/fmtlib/fmt/issues/177>`_). + Thanks to `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_. + +Fixes +~~~~~ + +* Fixed ``initializer_list`` detection + (`#136 <https://github.com/fmtlib/fmt/issues/136>`_). + Thanks to `@Gachapen (Magnus Bjerke Vik) <https://github.com/Gachapen>`_. + +* [Breaking] Fixed formatting of enums with numeric format specifiers in + ``fmt::(s)printf`` + (`#131 <https://github.com/fmtlib/fmt/issues/131>`_, + `#139 <https://github.com/fmtlib/fmt/issues/139>`_): + + .. code:: c++ + + enum { ANSWER = 42 }; + fmt::printf("%d", ANSWER); + + Thanks to `@Naios <https://github.com/Naios>`_. + +* Improved compatibility with old versions of MinGW + (`#129 <https://github.com/fmtlib/fmt/issues/129>`_, + `#130 <https://github.com/fmtlib/fmt/pull/130>`_, + `#132 <https://github.com/fmtlib/fmt/issues/132>`_). + Thanks to `@cstamford (Christopher Stamford) <https://github.com/cstamford>`_. + +* Fixed a compile error on MSVC with disabled exceptions + (`#144 <https://github.com/fmtlib/fmt/issues/144>`_). + +* Added a workaround for broken implementation of variadic templates in MSVC2012 + (`#148 <https://github.com/fmtlib/fmt/issues/148>`_). + +* Placed the anonymous namespace within ``fmt`` namespace for the header-only + configuration + (`#171 <https://github.com/fmtlib/fmt/issues/171>`_). + Thanks to `@alfps (Alf P. Steinbach) <https://github.com/alfps>`_. + +* Fixed issues reported by Coverity Scan + (`#187 <https://github.com/fmtlib/fmt/issues/187>`_, + `#192 <https://github.com/fmtlib/fmt/issues/192>`_). + +* Implemented a workaround for a name lookup bug in MSVC2010 + (`#188 <https://github.com/fmtlib/fmt/issues/188>`_). + +* Fixed compiler warnings + (`#95 <https://github.com/fmtlib/fmt/issues/95>`_, + `#96 <https://github.com/fmtlib/fmt/issues/96>`_, + `#114 <https://github.com/fmtlib/fmt/pull/114>`_, + `#135 <https://github.com/fmtlib/fmt/issues/135>`_, + `#142 <https://github.com/fmtlib/fmt/issues/142>`_, + `#145 <https://github.com/fmtlib/fmt/issues/145>`_, + `#146 <https://github.com/fmtlib/fmt/issues/146>`_, + `#158 <https://github.com/fmtlib/fmt/issues/158>`_, + `#163 <https://github.com/fmtlib/fmt/issues/163>`_, + `#175 <https://github.com/fmtlib/fmt/issues/175>`_, + `#190 <https://github.com/fmtlib/fmt/issues/190>`_, + `#191 <https://github.com/fmtlib/fmt/pull/191>`_, + `#194 <https://github.com/fmtlib/fmt/issues/194>`_, + `#196 <https://github.com/fmtlib/fmt/pull/196>`_, + `#216 <https://github.com/fmtlib/fmt/issues/216>`_, + `#218 <https://github.com/fmtlib/fmt/pull/218>`_, + `#220 <https://github.com/fmtlib/fmt/pull/220>`_, + `#229 <https://github.com/fmtlib/fmt/pull/229>`_, + `#233 <https://github.com/fmtlib/fmt/issues/233>`_, + `#234 <https://github.com/fmtlib/fmt/issues/234>`_, + `#236 <https://github.com/fmtlib/fmt/pull/236>`_, + `#281 <https://github.com/fmtlib/fmt/issues/281>`_, + `#289 <https://github.com/fmtlib/fmt/issues/289>`_). + Thanks to `@seanmiddleditch (Sean Middleditch) <https://github.com/seanmiddleditch>`_, + `@dixlorenz (Dix Lorenz) <https://github.com/dixlorenz>`_, + `@CarterLi (李通洲) <https://github.com/CarterLi>`_, + `@Naios <https://github.com/Naios>`_, + `@fmatthew5876 (Matthew Fioravante) <https://github.com/fmatthew5876>`_, + `@LevskiWeng (Levski Weng) <https://github.com/LevskiWeng>`_, + `@rpopescu <https://github.com/rpopescu>`_, + `@gabime (Gabi Melman) <https://github.com/gabime>`_, + `@cubicool (Jeremy Moles) <https://github.com/cubicool>`_, + `@jkflying (Julian Kent) <https://github.com/jkflying>`_, + `@LogicalKnight (Sean L) <https://github.com/LogicalKnight>`_, + `@inguin (Ingo van Lil) <https://github.com/inguin>`_ and + `@Jopie64 (Johan) <https://github.com/Jopie64>`_. + +* Fixed portability issues (mostly causing test failures) on ARM, ppc64, ppc64le, + s390x and SunOS 5.11 i386 + (`#138 <https://github.com/fmtlib/fmt/issues/138>`_, + `#179 <https://github.com/fmtlib/fmt/issues/179>`_, + `#180 <https://github.com/fmtlib/fmt/issues/180>`_, + `#202 <https://github.com/fmtlib/fmt/issues/202>`_, + `#225 <https://github.com/fmtlib/fmt/issues/225>`_, + `Red Hat Bugzilla Bug 1260297 <https://bugzilla.redhat.com/show_bug.cgi?id=1260297>`_). + Thanks to `@Naios <https://github.com/Naios>`_, + `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_ and Dave Johansen. + +* Fixed a name conflict with macro ``free`` defined in + ``crtdbg.h`` when ``_CRTDBG_MAP_ALLOC`` is set + (`#211 <https://github.com/fmtlib/fmt/issues/211>`_). + +* Fixed shared library build on OS X + (`#212 <https://github.com/fmtlib/fmt/pull/212>`_). + Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_. + +* Fixed an overload conflict on MSVC when ``/Zc:wchar_t-`` option is specified + (`#214 <https://github.com/fmtlib/fmt/pull/214>`_). + Thanks to `@slavanap (Vyacheslav Napadovsky) <https://github.com/slavanap>`_. + +* Improved compatibility with MSVC 2008 + (`#236 <https://github.com/fmtlib/fmt/pull/236>`_). + Thanks to `@Jopie64 (Johan) <https://github.com/Jopie64>`_. + +* Improved compatibility with bcc32 + (`#227 <https://github.com/fmtlib/fmt/issues/227>`_). + +* Fixed ``static_assert`` detection on Clang + (`#228 <https://github.com/fmtlib/fmt/pull/228>`_). + Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_. + +1.1.0 - 2015-03-06 +------------------ + +* Added ``BasicArrayWriter``, a class template that provides operations for + formatting and writing data into a fixed-size array + (`#105 <https://github.com/fmtlib/fmt/issues/105>`_ and + `#122 <https://github.com/fmtlib/fmt/issues/122>`_): + + .. code:: c++ + + char buffer[100]; + fmt::ArrayWriter w(buffer); + w.write("The answer is {}", 42); + +* Added `0 A.D. <http://play0ad.com/>`_ and `PenUltima Online (POL) + <http://www.polserver.com/>`_ to the list of notable projects using C++ Format. + +* C++ Format now uses MSVC intrinsics for better formatting performance + (`#115 <https://github.com/fmtlib/fmt/pull/115>`_, + `#116 <https://github.com/fmtlib/fmt/pull/116>`_, + `#118 <https://github.com/fmtlib/fmt/pull/118>`_ and + `#121 <https://github.com/fmtlib/fmt/pull/121>`_). + Previously these optimizations where only used on GCC and Clang. + Thanks to `@CarterLi <https://github.com/CarterLi>`_ and + `@objectx <https://github.com/objectx>`_. + +* CMake install target (`#119 <https://github.com/fmtlib/fmt/pull/119>`_). + Thanks to `@TrentHouliston <https://github.com/TrentHouliston>`_. + + You can now install C++ Format with ``make install`` command. + +* Improved `Biicode <http://www.biicode.com/>`_ support + (`#98 <https://github.com/fmtlib/fmt/pull/98>`_ and + `#104 <https://github.com/fmtlib/fmt/pull/104>`_). Thanks to + `@MariadeAnton <https://github.com/MariadeAnton>`_ and + `@franramirez688 <https://github.com/franramirez688>`_. + +* Improved support for building with `Android NDK + <https://developer.android.com/tools/sdk/ndk/index.html>`_ + (`#107 <https://github.com/fmtlib/fmt/pull/107>`_). + Thanks to `@newnon <https://github.com/newnon>`_. + + The `android-ndk-example <https://github.com/fmtlib/android-ndk-example>`_ + repository provides and example of using C++ Format with Android NDK: + + .. image:: https://raw.githubusercontent.com/fmtlib/android-ndk-example/ + master/screenshot.png + +* Improved documentation of ``SystemError`` and ``WindowsError`` + (`#54 <https://github.com/fmtlib/fmt/issues/54>`_). + +* Various code improvements + (`#110 <https://github.com/fmtlib/fmt/pull/110>`_, + `#111 <https://github.com/fmtlib/fmt/pull/111>`_ + `#112 <https://github.com/fmtlib/fmt/pull/112>`_). + Thanks to `@CarterLi <https://github.com/CarterLi>`_. + +* Improved compile-time errors when formatting wide into narrow strings + (`#117 <https://github.com/fmtlib/fmt/issues/117>`_). + +* Fixed ``BasicWriter::write`` without formatting arguments when C++11 support + is disabled (`#109 <https://github.com/fmtlib/fmt/issues/109>`_). + +* Fixed header-only build on OS X with GCC 4.9 + (`#124 <https://github.com/fmtlib/fmt/issues/124>`_). + +* Fixed packaging issues (`#94 <https://github.com/fmtlib/fmt/issues/94>`_). + +* Added `changelog <https://github.com/fmtlib/fmt/blob/master/ChangeLog.rst>`_ + (`#103 <https://github.com/fmtlib/fmt/issues/103>`_). + +1.0.0 - 2015-02-05 +------------------ + +* Add support for a header-only configuration when ``FMT_HEADER_ONLY`` is + defined before including ``format.h``: + + .. code:: c++ + + #define FMT_HEADER_ONLY + #include "format.h" + +* Compute string length in the constructor of ``BasicStringRef`` + instead of the ``size`` method + (`#79 <https://github.com/fmtlib/fmt/issues/79>`_). + This eliminates size computation for string literals on reasonable optimizing + compilers. + +* Fix formatting of types with overloaded ``operator <<`` for ``std::wostream`` + (`#86 <https://github.com/fmtlib/fmt/issues/86>`_): + + .. code:: c++ + + fmt::format(L"The date is {0}", Date(2012, 12, 9)); + +* Fix linkage of tests on Arch Linux + (`#89 <https://github.com/fmtlib/fmt/issues/89>`_). + +* Allow precision specifier for non-float arguments + (`#90 <https://github.com/fmtlib/fmt/issues/90>`_): + + .. code:: c++ + + fmt::print("{:.3}\n", "Carpet"); // prints "Car" + +* Fix build on Android NDK + (`#93 <https://github.com/fmtlib/fmt/issues/93>`_) + +* Improvements to documentation build procedure. + +* Remove ``FMT_SHARED`` CMake variable in favor of standard `BUILD_SHARED_LIBS + <http://www.cmake.org/cmake/help/v3.0/variable/BUILD_SHARED_LIBS.html>`_. + +* Fix error handling in ``fmt::fprintf``. + +* Fix a number of warnings. + +0.12.0 - 2014-10-25 +------------------- + +* [Breaking] Improved separation between formatting and buffer management. + ``Writer`` is now a base class that cannot be instantiated directly. + The new ``MemoryWriter`` class implements the default buffer management + with small allocations done on stack. So ``fmt::Writer`` should be replaced + with ``fmt::MemoryWriter`` in variable declarations. + + Old code: + + .. code:: c++ + + fmt::Writer w; + + New code: + + .. code:: c++ + + fmt::MemoryWriter w; + + If you pass ``fmt::Writer`` by reference, you can continue to do so: + + .. code:: c++ + + void f(fmt::Writer &w); + + This doesn't affect the formatting API. + +* Support for custom memory allocators + (`#69 <https://github.com/fmtlib/fmt/issues/69>`_) + +* Formatting functions now accept `signed char` and `unsigned char` strings as + arguments (`#73 <https://github.com/fmtlib/fmt/issues/73>`_): + + .. code:: c++ + + auto s = format("GLSL version: {}", glGetString(GL_VERSION)); + +* Reduced code bloat. According to the new `benchmark results + <https://github.com/fmtlib/fmt#compile-time-and-code-bloat>`_, + cppformat is close to ``printf`` and by the order of magnitude better than + Boost Format in terms of compiled code size. + +* Improved appearance of the documentation on mobile by using the `Sphinx + Bootstrap theme <http://ryan-roemer.github.io/sphinx-bootstrap-theme/>`_: + + .. |old| image:: https://cloud.githubusercontent.com/assets/576385/4792130/ + cd256436-5de3-11e4-9a62-c077d0c2b003.png + + .. |new| image:: https://cloud.githubusercontent.com/assets/576385/4792131/ + cd29896c-5de3-11e4-8f59-cac952942bf0.png + + +-------+-------+ + | Old | New | + +-------+-------+ + | |old| | |new| | + +-------+-------+ + +0.11.0 - 2014-08-21 +------------------- + +* Safe printf implementation with a POSIX extension for positional arguments: + + .. code:: c++ + + fmt::printf("Elapsed time: %.2f seconds", 1.23); + fmt::printf("%1$s, %3$d %2$s", weekday, month, day); + +* Arguments of ``char`` type can now be formatted as integers + (Issue `#55 <https://github.com/fmtlib/fmt/issues/55>`_): + + .. code:: c++ + + fmt::format("0x{0:02X}", 'a'); + +* Deprecated parts of the API removed. + +* The library is now built and tested on MinGW with Appveyor in addition to + existing test platforms Linux/GCC, OS X/Clang, Windows/MSVC. + +0.10.0 - 2014-07-01 +------------------- + +**Improved API** + +* All formatting methods are now implemented as variadic functions instead + of using ``operator<<`` for feeding arbitrary arguments into a temporary + formatter object. This works both with C++11 where variadic templates are + used and with older standards where variadic functions are emulated by + providing lightweight wrapper functions defined with the ``FMT_VARIADIC`` + macro. You can use this macro for defining your own portable variadic + functions: + + .. code:: c++ + + void report_error(const char *format, const fmt::ArgList &args) { + fmt::print("Error: {}"); + fmt::print(format, args); + } + FMT_VARIADIC(void, report_error, const char *) + + report_error("file not found: {}", path); + + Apart from a more natural syntax, this also improves performance as there + is no need to construct temporary formatter objects and control arguments' + lifetimes. Because the wrapper functions are very lightweight, this doesn't + cause code bloat even in pre-C++11 mode. + +* Simplified common case of formatting an ``std::string``. Now it requires a + single function call: + + .. code:: c++ + + std::string s = format("The answer is {}.", 42); + + Previously it required 2 function calls: + + .. code:: c++ + + std::string s = str(Format("The answer is {}.") << 42); + + Instead of unsafe ``c_str`` function, ``fmt::Writer`` should be used directly + to bypass creation of ``std::string``: + + .. code:: c++ + + fmt::Writer w; + w.write("The answer is {}.", 42); + w.c_str(); // returns a C string + + This doesn't do dynamic memory allocation for small strings and is less error + prone as the lifetime of the string is the same as for ``std::string::c_str`` + which is well understood (hopefully). + +* Improved consistency in naming functions that are a part of the public API. + Now all public functions are lowercase following the standard library + conventions. Previously it was a combination of lowercase and + CapitalizedWords. + Issue `#50 <https://github.com/fmtlib/fmt/issues/50>`_. + +* Old functions are marked as deprecated and will be removed in the next + release. + +**Other Changes** + +* Experimental support for printf format specifications (work in progress): + + .. code:: c++ + + fmt::printf("The answer is %d.", 42); + std::string s = fmt::sprintf("Look, a %s!", "string"); + +* Support for hexadecimal floating point format specifiers ``a`` and ``A``: + + .. code:: c++ + + print("{:a}", -42.0); // Prints -0x1.5p+5 + print("{:A}", -42.0); // Prints -0X1.5P+5 + +* CMake option ``FMT_SHARED`` that specifies whether to build format as a + shared library (off by default). + +0.9.0 - 2014-05-13 +------------------ + +* More efficient implementation of variadic formatting functions. + +* ``Writer::Format`` now has a variadic overload: + + .. code:: c++ + + Writer out; + out.Format("Look, I'm {}!", "variadic"); + +* For efficiency and consistency with other overloads, variadic overload of + the ``Format`` function now returns ``Writer`` instead of ``std::string``. + Use the ``str`` function to convert it to ``std::string``: + + .. code:: c++ + + std::string s = str(Format("Look, I'm {}!", "variadic")); + +* Replaced formatter actions with output sinks: ``NoAction`` -> ``NullSink``, + ``Write`` -> ``FileSink``, ``ColorWriter`` -> ``ANSITerminalSink``. + This improves naming consistency and shouldn't affect client code unless + these classes are used directly which should be rarely needed. + +* Added ``ThrowSystemError`` function that formats a message and throws + ``SystemError`` containing the formatted message and system-specific error + description. For example, the following code + + .. code:: c++ + + FILE *f = fopen(filename, "r"); + if (!f) + ThrowSystemError(errno, "Failed to open file '{}'") << filename; + + will throw ``SystemError`` exception with description + "Failed to open file '<filename>': No such file or directory" if file + doesn't exist. + +* Support for AppVeyor continuous integration platform. + +* ``Format`` now throws ``SystemError`` in case of I/O errors. + +* Improve test infrastructure. Print functions are now tested by redirecting + the output to a pipe. + +0.8.0 - 2014-04-14 +------------------ + +* Initial release diff --git a/libs/fmt/LICENSE.rst b/libs/fmt/LICENSE.rst new file mode 100644 index 0000000000000000000000000000000000000000..f0ec3db4d2a03368a9f1357b38e79486bdf41076 --- /dev/null +++ b/libs/fmt/LICENSE.rst @@ -0,0 +1,27 @@ +Copyright (c) 2012 - present, Victor Zverovich + +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. + +--- Optional exception to the license --- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into a machine-executable object form of such +source code, you may redistribute such embedded portions in such object form +without including the above copyright and permission notices. diff --git a/libs/fmt/Makefile b/libs/fmt/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3572ff166fe02c07e4066002af5b0cee90765758 --- /dev/null +++ b/libs/fmt/Makefile @@ -0,0 +1,12 @@ +all: libfmt.a + +libfmt.a: format.o + arm-miosix-eabi-ar rvs libfmt.a format.o + +format.o: src/format.cc + arm-miosix-eabi-g++ -s -c -o format.o src/format.cc -O2 \ + -D_GLIBCXX_USE_WCHAR_T \ + -DFMT_UNICODE=0 -DFMT_STATIC_THOUSANDS_SEPARATOR=0\ + -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Iinclude +clean: + rm -f format.o libfmt.a \ No newline at end of file diff --git a/libs/fmt/README.rst b/libs/fmt/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..4f33b627686ca8cd55859725e7a4b6c681cc1df6 --- /dev/null +++ b/libs/fmt/README.rst @@ -0,0 +1,524 @@ +{fmt} +===== + +.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg + :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux + +.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg + :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos + +.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg + :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows + +.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v + :target: https://ci.appveyor.com/project/vitaut/fmt + +.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg + :alt: fmt is continuously fuzzed at oss-fuzz + :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\ + colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\ + Summary&q=proj%3Dfmt&can=1 + +.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg + :alt: Ask questions at StackOverflow with the tag fmt + :target: https://stackoverflow.com/questions/tagged/fmt + +**{fmt}** is an open-source formatting library providing a fast and safe +alternative to C stdio and C++ iostreams. + +If you like this project, please consider donating to BY_Help, +an initiative to help victims of political repressions in Belarus: +https://www.facebook.com/donate/199475051809330/. + +`Documentation <https://fmt.dev>`__ + +Q&A: ask questions on `StackOverflow with the tag fmt +<https://stackoverflow.com/questions/tagged/fmt>`_. + +Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_. + +Features +-------- + +* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments + for localization +* Implementation of `C++20 std::format + <https://en.cppreference.com/w/cpp/utility/format>`__ +* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's + `format <https://docs.python.org/3/library/stdtypes.html#str.format>`_ +* Fast IEEE 754 floating-point formatter with correct rounding, shortness and + round-trip guarantees +* Safe `printf implementation + <https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX + extension for positional arguments +* Extensibility: `support for user-defined types + <https://fmt.dev/latest/api.html#formatting-user-defined-types>`_ +* High performance: faster than common standard library implementations of + ``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_ + and `Converting a hundred million integers to strings per second + <http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_ +* Small code size both in terms of source code with the minimum configuration + consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``, + and compiled code; see `Compile time and code bloat`_ +* Reliability: the library has an extensive set of `tests + <https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed + <https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20 + Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_ +* Safety: the library is fully type safe, errors in format strings can be + reported at compile time, automatic memory management prevents buffer overflow + errors +* Ease of use: small self-contained code base, no external dependencies, + permissive MIT `license + <https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_ +* `Portability <https://fmt.dev/latest/index.html#portability>`_ with + consistent output across platforms and support for older compilers +* Clean warning-free codebase even on high warning levels such as + ``-Wall -Wextra -pedantic`` +* Locale-independence by default +* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro + +See the `documentation <https://fmt.dev>`_ for more details. + +Examples +-------- + +**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_) + +.. code:: c++ + + #include <fmt/core.h> + + int main() { + fmt::print("Hello, world!\n"); + } + +**Format a string** (`run <https://godbolt.org/z/oK8h33>`_) + +.. code:: c++ + + std::string s = fmt::format("The answer is {}.", 42); + // s == "The answer is 42." + +**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_) + +.. code:: c++ + + std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy"); + // s == "I'd rather be happy than right." + +**Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_) + +.. code:: c++ + + #include <fmt/chrono.h> + + int main() { + using namespace std::literals::chrono_literals; + fmt::print("Default format: {} {}\n", 42s, 100ms); + fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); + } + +Output:: + + Default format: 42s 100ms + strftime-like format: 03:15:30 + +**Print a container** (`run <https://godbolt.org/z/MjsY7c>`_) + +.. code:: c++ + + #include <vector> + #include <fmt/ranges.h> + + int main() { + std::vector<int> v = {1, 2, 3}; + fmt::print("{}\n", v); + } + +Output:: + + {1, 2, 3} + +**Check a format string at compile time** + +.. code:: c++ + + std::string s = fmt::format(FMT_STRING("{:d}"), "I am not a number"); + +This gives a compile-time error because ``d`` is an invalid format specifier for +a string. + +**Write a file from a single thread** + +.. code:: c++ + + #include <fmt/os.h> + + int main() { + auto out = fmt::output_file("guide.txt"); + out.print("Don't {}", "Panic"); + } + +This can be `5 to 9 times faster than fprintf +<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_. + +**Print with colors and text styles** + +.. code:: c++ + + #include <fmt/color.h> + + int main() { + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, + "Hello, {}!\n", "world"); + fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | + fmt::emphasis::underline, "Hello, {}!\n", "мир"); + fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, + "Hello, {}!\n", "世界"); + } + +Output on a modern terminal: + +.. image:: https://user-images.githubusercontent.com/ + 576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png + +Benchmarks +---------- + +Speed tests +~~~~~~~~~~~ + +================= ============= =========== +Library Method Run Time, s +================= ============= =========== +libc printf 1.04 +libc++ std::ostream 3.05 +{fmt} 6.1.1 fmt::print 0.75 +Boost Format 1.67 boost::format 7.24 +Folly Format folly::format 2.23 +================= ============= =========== + +{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``. + +The above results were generated by building ``tinyformat_test.cpp`` on macOS +10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the +best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"`` +or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for +further details refer to the `source +<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_. + +{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on +floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_) +and faster than `double-conversion <https://github.com/google/double-conversion>`_ and +`ryu <https://github.com/ulfjack/ryu>`_: + +.. image:: https://user-images.githubusercontent.com/576385/ + 95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png + :target: https://fmt.dev/unknown_mac64_clang12.0.html + +Compile time and code bloat +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The script `bloat-test.py +<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_ +from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_ +tests compile time and code bloat for nontrivial projects. +It generates 100 translation units and uses ``printf()`` or its alternative +five times in each to simulate a medium sized project. The resulting +executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42), +macOS Sierra, best of three) is shown in the following tables. + +**Optimized build (-O3)** + +============= =============== ==================== ================== +Method Compile Time, s Executable size, KiB Stripped size, KiB +============= =============== ==================== ================== +printf 2.6 29 26 +printf+string 16.4 29 26 +iostreams 31.1 59 55 +{fmt} 19.0 37 34 +Boost Format 91.9 226 203 +Folly Format 115.7 101 88 +============= =============== ==================== ================== + +As you can see, {fmt} has 60% less overhead in terms of resulting binary code +size compared to iostreams and comes pretty close to ``printf``. Boost Format +and Folly Format have the largest overheads. + +``printf+string`` is the same as ``printf`` but with extra ``<string>`` +include to measure the overhead of the latter. + +**Non-optimized build** + +============= =============== ==================== ================== +Method Compile Time, s Executable size, KiB Stripped size, KiB +============= =============== ==================== ================== +printf 2.2 33 30 +printf+string 16.0 33 30 +iostreams 28.3 56 52 +{fmt} 18.2 59 50 +Boost Format 54.1 365 303 +Folly Format 79.9 445 430 +============= =============== ==================== ================== + +``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to +compare formatting function overhead only. Boost Format is a +header-only library so it doesn't provide any linkage options. + +Running the tests +~~~~~~~~~~~~~~~~~ + +Please refer to `Building the library`__ for the instructions on how to build +the library and run the unit tests. + +__ https://fmt.dev/latest/usage.html#building-the-library + +Benchmarks reside in a separate repository, +`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_, +so to run the benchmarks you first need to clone this repository and +generate Makefiles with CMake:: + + $ git clone --recursive https://github.com/fmtlib/format-benchmark.git + $ cd format-benchmark + $ cmake . + +Then you can run the speed test:: + + $ make speed-test + +or the bloat test:: + + $ make bloat-test + +Migrating code +-------------- + +`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang +tidy checks for converting occurrences of ``printf`` and ``fprintf`` to +``fmt::print``. + +Projects using this library +--------------------------- + +* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform + real-time strategy game + +* `2GIS <https://2gis.ru/>`_: free business listings with a city map + +* `AMPL/MP <https://github.com/ampl/mp>`_: + an open-source library for mathematical programming + +* `Aseprite <https://github.com/aseprite/aseprite>`_: + animated sprite editor & pixel art tool + +* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft + operations suite + +* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform + +* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space + +* `Ceph <https://ceph.com/>`_: a scalable distributed storage system + +* `ccache <https://ccache.dev/>`_: a compiler cache + +* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database + management system + +* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater + vehicle + +* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox + for nonlinear dynamical systems (MIT) + +* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus + (Lyft) + +* `FiveM <https://fivem.net/>`_: a modification framework for GTA V + +* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style + logging library with latency in nanoseconds + +* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library + +* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_: + Player vs Player Gaming Network with tweaks + +* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server + engine + +* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows + +* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software + +* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node + +* `Microsoft Verona <https://github.com/microsoft/verona>`_: + research programming language for concurrent ownership + +* `MongoDB <https://mongodb.com/>`_: distributed document database + +* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to + generate randomized datasets + +* `OpenSpace <https://openspaceproject.com/>`_: an open-source + astrovisualization framework + +* `PenUltima Online (POL) <https://www.polserver.com/>`_: + an MMO server, compatible with most Ultima Online clients + +* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine + learning library + +* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance, + associative database + +* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library + +* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify + navigation, and executing complex multi-line terminal command sequences + +* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster + proxy + +* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement + for mission critical systems written in C++ + +* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client + library + +* `Salesforce Analytics Cloud + <https://www.salesforce.com/analytics-cloud/overview/>`_: + business intelligence software + +* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store + that can handle 1 million transactions per second on a single server + +* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++ + framework for high-performance server applications on modern hardware + +* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library + +* `Stellar <https://www.stellar.org/>`_: financial platform + +* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator + +* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source + MMORPG framework + +* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows + terminal + +`More... <https://github.com/search?q=fmtlib&type=Code>`_ + +If you are aware of other projects using this library, please let me know +by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an +`issue <https://github.com/fmtlib/fmt/issues>`_. + +Motivation +---------- + +So why yet another formatting library? + +There are plenty of methods for doing this task, from standard ones like +the printf family of function and iostreams to Boost Format and FastFormat +libraries. The reason for creating a new library is that every existing +solution that I found either had serious issues or didn't provide +all the features I needed. + +printf +~~~~~~ + +The good thing about ``printf`` is that it is pretty fast and readily available +being a part of the C standard library. The main drawback is that it +doesn't support user-defined types. ``printf`` also has safety issues although +they are somewhat mitigated with `__attribute__ ((format (printf, ...)) +<https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC. +There is a POSIX extension that adds positional arguments required for +`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_ +to ``printf`` but it is not a part of C99 and may not be available on some +platforms. + +iostreams +~~~~~~~~~ + +The main issue with iostreams is best illustrated with an example: + +.. code:: c++ + + std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n"; + +which is a lot of typing compared to printf: + +.. code:: c++ + + printf("%.2f\n", 1.23456); + +Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams +don't support positional arguments by design. + +The good part is that iostreams support user-defined types and are safe although +error handling is awkward. + +Boost Format +~~~~~~~~~~~~ + +This is a very powerful library which supports both ``printf``-like format +strings and positional arguments. Its main drawback is performance. According to +various, benchmarks it is much slower than other methods considered here. Boost +Format also has excessive build times and severe code bloat issues (see +`Benchmarks`_). + +FastFormat +~~~~~~~~~~ + +This is an interesting library which is fast, safe and has positional arguments. +However, it has significant limitations, citing its author: + + Three features that have no hope of being accommodated within the + current design are: + + * Leading zeros (or any other non-space padding) + * Octal/hexadecimal encoding + * Runtime width/alignment specification + +It is also quite big and has a heavy dependency, STLSoft, which might be too +restrictive for using it in some projects. + +Boost Spirit.Karma +~~~~~~~~~~~~~~~~~~ + +This is not really a formatting library but I decided to include it here for +completeness. As iostreams, it suffers from the problem of mixing verbatim text +with arguments. The library is pretty fast, but slower on integer formatting +than ``fmt::format_to`` with format string compilation on Karma's own benchmark, +see `Converting a hundred million integers to strings per second +<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_. + +License +------- + +{fmt} is distributed under the MIT `license +<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_. + +Documentation License +--------------------- + +The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_ +section in the documentation is based on the one from Python `string module +documentation <https://docs.python.org/3/library/string.html#module-string>`_. +For this reason the documentation is distributed under the Python Software +Foundation license available in `doc/python-license.txt +<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_. +It only applies if you distribute the documentation of {fmt}. + +Maintainers +----------- + +The {fmt} library is maintained by Victor Zverovich (`vitaut +<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan +<https://github.com/foonathan>`_) with contributions from many other people. +See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and +`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names. +Let us know if your contribution is not listed or mentioned incorrectly and +we'll make it right. diff --git a/libs/fmt/README_SKYWARD.md b/libs/fmt/README_SKYWARD.md new file mode 100644 index 0000000000000000000000000000000000000000..ed82a56a800cb1d4e37d164029222f6106c148ff --- /dev/null +++ b/libs/fmt/README_SKYWARD.md @@ -0,0 +1,4 @@ +# Compiling + +A custom makefile is provided instead of the default cmake build script. Please use the makefile and not cmake. +The header `format.h` has been slightly modified as it was not compiling with miosix-g++. \ No newline at end of file diff --git a/libs/fmt/doc/CMakeLists.txt b/libs/fmt/doc/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..06848450369c3cc3587ffd1e3b921b58c608651b --- /dev/null +++ b/libs/fmt/doc/CMakeLists.txt @@ -0,0 +1,17 @@ +find_program(DOXYGEN doxygen) +if (NOT DOXYGEN) + message(STATUS "Target 'doc' disabled (requires doxygen)") + return () +endif () + +find_package(PythonInterp QUIET REQUIRED) + +add_custom_target(doc + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py + ${FMT_VERSION} + SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html) + +include(GNUInstallDirs) +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/ + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL + PATTERN ".doctrees" EXCLUDE) diff --git a/libs/fmt/doc/_static/bootstrap.min.js b/libs/fmt/doc/_static/bootstrap.min.js new file mode 100644 index 0000000000000000000000000000000000000000..c8f82e592a181afd01014546f58218e213429f6c --- /dev/null +++ b/libs/fmt/doc/_static/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.4 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27|32)/.test(b.which)&&!/input|textarea/i.test(b.target.tagName)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g&&27!=b.which||g&&27==b.which)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(b.target);38==b.which&&j>0&&j--,40==b.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="menu"]',g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="listbox"]',g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in").attr("aria-hidden",!1),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a('<div class="modal-backdrop '+e+'" />').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.3.4",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-m<p.top?"bottom":"right"==h&&k.right+l>p.width?"left":"left"==h&&k.left-l<p.left?"right":h,f.removeClass(n).addClass(h)}var q=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(q,h);var r=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",r).emulateTransitionEnd(c.TRANSITION_DURATION):r()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top=b.top+g,b.left=b.left+h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.4",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){ +var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/libs/fmt/doc/_static/breathe.css b/libs/fmt/doc/_static/breathe.css new file mode 100644 index 0000000000000000000000000000000000000000..2a1534b0aedd5625352b8802fe4c9de761775225 --- /dev/null +++ b/libs/fmt/doc/_static/breathe.css @@ -0,0 +1,28 @@ + +/* -- breathe specific styles ----------------------------------------------- */ + +/* So enum value descriptions are displayed inline to the item */ +.breatheenumvalues li tt + p { + display: inline; +} + +/* So parameter descriptions are displayed inline to the item */ +.breatheparameterlist li tt + p { + display: inline; +} + +.container .breathe-sectiondef { + width: inherit; +} + +.github-btn { + border: 0; + overflow: hidden; +} + +.jumbotron { + background-size: 100% 4px; + background-repeat: repeat-y; + color: white; + text-align: center; +} diff --git a/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.eot b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..4a4ca865d67e86f961bc6e2ef00bffa4e34bb9ed Binary files /dev/null and b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.eot differ diff --git a/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.svg b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000000000000000000000000000000000000..e3e2dc739dd851f2d7d291be032e30b909e3e95f --- /dev/null +++ b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > +<svg xmlns="http://www.w3.org/2000/svg"> +<metadata></metadata> +<defs> +<font id="glyphicons_halflingsregular" horiz-adv-x="1200" > +<font-face units-per-em="1200" ascent="960" descent="-240" /> +<missing-glyph horiz-adv-x="500" /> +<glyph /> +<glyph /> +<glyph unicode="
" /> +<glyph unicode=" " /> +<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" /> +<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" /> +<glyph unicode=" " /> +<glyph unicode=" " horiz-adv-x="652" /> +<glyph unicode=" " horiz-adv-x="1304" /> +<glyph unicode=" " horiz-adv-x="652" /> +<glyph unicode=" " horiz-adv-x="1304" /> +<glyph unicode=" " horiz-adv-x="434" /> +<glyph unicode=" " horiz-adv-x="326" /> +<glyph unicode=" " horiz-adv-x="217" /> +<glyph unicode=" " horiz-adv-x="217" /> +<glyph unicode=" " horiz-adv-x="163" /> +<glyph unicode=" " horiz-adv-x="260" /> +<glyph unicode=" " horiz-adv-x="72" /> +<glyph unicode=" " horiz-adv-x="260" /> +<glyph unicode=" " horiz-adv-x="326" /> +<glyph unicode="€" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" /> +<glyph unicode="−" d="M200 400h900v300h-900v-300z" /> +<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" /> +<glyph unicode="☁" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" /> +<glyph unicode="✉" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" /> +<glyph unicode="✏" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" /> +<glyph unicode="" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" /> +<glyph unicode="" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" /> +<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" /> +<glyph unicode="" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" /> +<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" /> +<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" /> +<glyph unicode="" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" /> +<glyph unicode="" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" /> +<glyph unicode="" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" /> +<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" /> +<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" /> +<glyph unicode="" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" /> +<glyph unicode="" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" /> +<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" /> +<glyph unicode="" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" /> +<glyph unicode="" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" /> +<glyph unicode="" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" /> +<glyph unicode="" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" /> +<glyph unicode="" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" /> +<glyph unicode="" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" /> +<glyph unicode="" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" /> +<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" /> +<glyph unicode="" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" /> +<glyph unicode="" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" /> +<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" /> +<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" /> +<glyph unicode="" d="M0 25v475l200 700h800l199 -700l1 -475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" /> +<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" /> +<glyph unicode="" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" /> +<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" /> +<glyph unicode="" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" /> +<glyph unicode="" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" /> +<glyph unicode="" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" /> +<glyph unicode="" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" /> +<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" /> +<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" /> +<glyph unicode="" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" /> +<glyph unicode="" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" /> +<glyph unicode="" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" /> +<glyph unicode="" d="M0 700l1 475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" /> +<glyph unicode="" d="M1 700l1 475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" /> +<glyph unicode="" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" /> +<glyph unicode="" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" /> +<glyph unicode="" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" /> +<glyph unicode="" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" /> +<glyph unicode="" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" /> +<glyph unicode="" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v71l471 -1q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" /> +<glyph unicode="" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" /> +<glyph unicode="" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " /> +<glyph unicode="" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" /> +<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" /> +<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" /> +<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" /> +<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" /> +<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" /> +<glyph unicode="" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" /> +<glyph unicode="" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" /> +<glyph unicode="" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" /> +<glyph unicode="" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " /> +<glyph unicode="" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" /> +<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" /> +<glyph unicode="" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 139t-64 210zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" /> +<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" /> +<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" /> +<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q61 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l567 567l-137 137l-430 -431l-146 147z" /> +<glyph unicode="" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" /> +<glyph unicode="" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" /> +<glyph unicode="" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" /> +<glyph unicode="" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" /> +<glyph unicode="" d="M200 0l900 550l-900 550v-1100z" /> +<glyph unicode="" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" /> +<glyph unicode="" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" /> +<glyph unicode="" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" /> +<glyph unicode="" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" /> +<glyph unicode="" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" /> +<glyph unicode="" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" /> +<glyph unicode="" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" /> +<glyph unicode="" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" /> +<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" /> +<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h600v200h-600v-200z" /> +<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141 z" /> +<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" /> +<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM364 700h143q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5 q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5t-53.5 -74.5t-19 -114zM500 300h200v100h-200 v-100z" /> +<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" /> +<glyph unicode="" d="M0 500v200h195q31 125 98.5 199.5t206.5 100.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200v-206 q149 48 201 206h-201v200h200q-25 74 -75.5 127t-124.5 77v-204h-200v203q-75 -23 -130 -77t-79 -126h209v-200h-210z" /> +<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" /> +<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" /> +<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" /> +<glyph unicode="" d="M0 547l600 453v-300h600v-300h-600v-301z" /> +<glyph unicode="" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" /> +<glyph unicode="" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" /> +<glyph unicode="" d="M104 600h296v600h300v-600h298l-449 -600z" /> +<glyph unicode="" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" /> +<glyph unicode="" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" /> +<glyph unicode="" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" /> +<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5h-207q-21 0 -33 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" /> +<glyph unicode="" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111q1 1 1 6.5t-1.5 15t-3.5 17.5l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6 h-111v-100zM100 0h400v400h-400v-400zM200 900q-3 0 14 48t36 96l18 47l213 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" /> +<glyph unicode="" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" /> +<glyph unicode="" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" /> +<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" /> +<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" /> +<glyph unicode="" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 34 -48 36.5t-48 -29.5l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" /> +<glyph unicode="" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" /> +<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" /> +<glyph unicode="" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" /> +<glyph unicode="" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" /> +<glyph unicode="" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" /> +<glyph unicode="" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" /> +<glyph unicode="" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" /> +<glyph unicode="" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" /> +<glyph unicode="" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" /> +<glyph unicode="" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" /> +<glyph unicode="" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" /> +<glyph unicode="" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" /> +<glyph unicode="" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" /> +<glyph unicode="" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" /> +<glyph unicode="" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" /> +<glyph unicode="" d="M74 350q0 21 13.5 35.5t33.5 14.5h18l117 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-8 -3 -23 -8.5 t-65 -20t-103 -25t-132.5 -19.5t-158.5 -9q-125 0 -245.5 20.5t-178.5 40.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" /> +<glyph unicode="" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" /> +<glyph unicode="" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q124 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 213l100 212h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" /> +<glyph unicode="" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q124 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" /> +<glyph unicode="" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" /> +<glyph unicode="" d="M-101 651q0 72 54 110t139 38l302 -1l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 17 -10.5t26.5 -26t16.5 -36.5v-526q0 -13 -86 -93.5t-94 -80.5h-341q-16 0 -29.5 20t-19.5 41l-130 339h-107q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l107 89v502l-343 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM1000 201v600h200v-600h-200z" /> +<glyph unicode="" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6.5v7.5v6.5v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" /> +<glyph unicode="" d="M2 585q-16 -31 6 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM77 565l236 339h503 l89 -100v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" /> +<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM298 701l2 -201h300l-2 -194l402 294l-402 298v-197h-300z" /> +<glyph unicode="" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l402 -294l-2 194h300l2 201h-300v197z" /> +<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" /> +<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" /> +<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60 q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5 t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5 q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 39 2 44q31 -13 58 -14.5t39 3.5l11 4q7 36 -16.5 53.5t-64.5 28.5t-56 23q-19 -3 -37 0 q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -24 17 -66.5t17 -43.5 q-9 2 -31 5t-36 5t-32 8t-30 14zM692 1003h1h-1z" /> +<glyph unicode="" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" /> +<glyph unicode="" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" /> +<glyph unicode="" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" /> +<glyph unicode="" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" /> +<glyph unicode="" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" /> +<glyph unicode="" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM514 609q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" /> +<glyph unicode="" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -78.5 -16.5t-67.5 -51.5l-389 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23 q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60 l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" /> +<glyph unicode="" d="M80 784q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100q-71 70 -104.5 105.5t-77 89.5t-61 99 t-17.5 91zM250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-105 48.5q-74 0 -132 -83l-118 -171l-114 174q-51 80 -123 80q-60 0 -109.5 -49.5t-49.5 -118.5z" /> +<glyph unicode="" d="M57 353q0 -95 66 -159l141 -142q68 -66 159 -66q93 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-8 9 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q7 -7 19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -17q47 -49 77 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" /> +<glyph unicode="" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" /> +<glyph unicode="" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" /> +<glyph unicode="" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5 zM700 237q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" /> +<glyph unicode="" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q33 1 103 -16t103 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z" /> +<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" /> +<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" /> +<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" /> +<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" /> +<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" /> +<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" /> +<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" /> +<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" /> +<glyph unicode="" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" /> +<glyph unicode="" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" /> +<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" /> +<glyph unicode="" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" /> +<glyph unicode="" d="M217 519q8 -19 31 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8h9q14 0 26 15q11 13 274.5 321.5t264.5 308.5q14 19 5 36q-8 17 -31 17l-301 -1q1 4 78 219.5t79 227.5q2 15 -5 27l-9 9h-9q-15 0 -25 -16q-4 -6 -98 -111.5t-228.5 -257t-209.5 -237.5q-16 -19 -6 -41 z" /> +<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " /> +<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" /> +<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" /> +<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" /> +<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" /> +<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" /> +<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 400l697 1l3 699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" /> +<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l249 -237l-1 697zM900 150h100v50h-100v-50z" /> +<glyph unicode="" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" /> +<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" /> +<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" /> +<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" /> +<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" /> +<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" /> +<glyph unicode="" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" /> +<glyph unicode="" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" /> +<glyph unicode="" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -116q-25 -17 -43.5 -51.5t-18.5 -65.5v-359z" /> +<glyph unicode="" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" /> +<glyph unicode="" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" /> +<glyph unicode="" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q17 18 13.5 41t-22.5 37l-192 136q-19 14 -45 12t-42 -19l-118 -118q-142 101 -268 227t-227 268l118 118q17 17 20 41.5t-11 44.5 l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" /> +<glyph unicode="" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-20 0 -35 14.5t-15 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" /> +<glyph unicode="" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" /> +<glyph unicode="" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" /> +<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" /> +<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" /> +<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" /> +<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" /> +<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" /> +<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" /> +<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" /> +<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" /> +<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" /> +<glyph unicode="" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" /> +<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300h200 l-300 -300z" /> +<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104.5t60.5 178.5q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" /> +<glyph unicode="" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" /> +<glyph unicode="" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -11.5t1 -11.5q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" /> +</font> +</defs></svg> \ No newline at end of file diff --git a/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.ttf b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..67fa00bf83801d2fa568546b982c80d27f6ef74e Binary files /dev/null and b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.ttf differ diff --git a/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.woff b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..8c54182aa5d4d1ab3c9171976b615c1dcb1dc187 Binary files /dev/null and b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.woff differ diff --git a/libs/fmt/doc/_templates/layout.html b/libs/fmt/doc/_templates/layout.html new file mode 100644 index 0000000000000000000000000000000000000000..333a606c88fa1bc872595842e1e13485c78b639f --- /dev/null +++ b/libs/fmt/doc/_templates/layout.html @@ -0,0 +1,148 @@ +{% extends "!layout.html" %} + +{% block extrahead %} +<meta name="description" content="Small, safe and fast formatting library"> +<meta name="keywords" content="C++, formatting, printf, string, library"> +<meta name="author" content="Victor Zverovich"> +<link rel="stylesheet" href="_static/fmt.css"> +{# Google Analytics #} +<script async src="https://www.googletagmanager.com/gtag/js?id=UA-20116650-4"></script> +<script> + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + + gtag('config', 'UA-20116650-4'); +</script> +{% endblock %} + +{%- macro searchform(classes, button) %} +<form class="{{classes}}" role="search" action="{{ pathto('search') }}" + method="get"> + <div class="form-group"> + <input type="text" name="q" class="form-control" + {{ 'placeholder="Search"' if not button }} > + </div> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + {% if button %} + <input type="submit" class="btn btn-default" value="search"> + {% endif %} +</form> +{%- endmacro %} + +{% block header %} +<nav class="navbar navbar-inverse"> + <div class="tb-container"> + <div class="row"> + <div class="navbar-content"> + {# Brand and toggle get grouped for better mobile display #} + <div class="navbar-header"> + <button type="button" class="navbar-toggle collapsed" + data-toggle="collapse" data-target=".navbar-collapse"> + <span class="sr-only">Toggle navigation</span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + <a class="navbar-brand" href="index.html">{fmt}</a> + </div> + + {# Collect the nav links, forms, and other content for toggling #} + <div class="collapse navbar-collapse"> + <ul class="nav navbar-nav"> + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown" + role="button" aria-expanded="false">{{ version }} + <span class="caret"></span></a> + <ul class="dropdown-menu" role="menu"> + {% for v in versions.split(',') %} + <li><a href="https://fmt.dev/{{v}}">{{v}}</a></li> + {% endfor %} + </ul> + </li> + {% for name in ['Contents', 'Usage', 'API', 'Syntax'] %} + {% if pagename == name.lower() %} + <li class="active"><a href="{{name.lower()}}.html">{{name}} + <span class="sr-only">(current)</span></a></li> + {%else%} + <li><a href="{{name.lower()}}.html">{{name}}</a></li> + {%endif%} + {% endfor %} + </ul> + {% if pagename != 'search' %} + {{ searchform('navbar-form navbar-right', False) }} + {%endif%} + </div> {# /.navbar-collapse #} + </div> {# /.col-md-offset-2 #} + </div> {# /.row #} + </div> {# /.tb-container #} +</nav> +{% if pagename == "index" %} +{% set download_url = 'https://github.com/fmtlib/fmt/releases/download' %} +<div class="jumbotron"> + <div class="tb-container"> + <h1>{fmt}</h1> + <p class="lead">A modern formatting library</p> + <div class="btn-group" role="group"> + {% set name = 'fmt' if version.split('.')[0]|int >= 3 else 'cppformat' %} + <a class="btn btn-success" + href="{{download_url}}/{{version}}/{{name}}-{{version}}.zip"> + <span class="glyphicon glyphicon-download"></span> Download + </a> + <button type="button" class="btn btn-success dropdown-toggle" + data-toggle="dropdown"><span class="caret"></span></button> + <ul class="dropdown-menu"> + {% for v in versions.split(',') %} + {% set name = 'fmt' if v.split('.')[0]|int >= 3 else 'cppformat' %} + <li><a href="{{download_url}}/{{v}}/{{name}}-{{v}}.zip">Version {{v}} + </a></li> + {% endfor %} + </ul> + </div> + </div> +</div> +{% endif %} +{% endblock %} + +{# Disable relbars. #} +{% block relbar1 %} +{% endblock %} +{% block relbar2 %} +{% endblock %} + +{% block content %} +<div class="tb-container"> + <div class="row"> + {# Sidebar is currently disabled. + <div class="bs-sidebar"> + <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> + <div class="sphinxsidebarwrapper"> + {%- block sidebarlogo %} + {%- if logo %} + <p class="logo"><a href="{{ pathto(master_doc) }}"> + <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" + alt="Logo"/> + </a></p> + {%- endif %} + {%- endblock %} + {%- for sidebartemplate in sidebars %} + {%- include sidebartemplate %} + {%- endfor %} + </div> + </div> + </div> + #} + + <div class="content"> + {% block body %} {% endblock %} + </div> + </div> +</div> +{% endblock %} + +{% block footer %} +{{ super() }} +{# Placed at the end of the document so the pages load faster. #} +<script src="_static/bootstrap.min.js"></script> +{% endblock %} diff --git a/libs/fmt/doc/_templates/search.html b/libs/fmt/doc/_templates/search.html new file mode 100644 index 0000000000000000000000000000000000000000..488e0b5d255af5a803b7339bf40beba4eb6d0aa5 --- /dev/null +++ b/libs/fmt/doc/_templates/search.html @@ -0,0 +1,55 @@ +{# + basic/search.html + ~~~~~~~~~~~~~~~~~ + + Template for the search page. + + :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- extends "layout.html" %} +{% set title = _('Search') %} +{% set script_files = script_files + ['_static/searchtools.js'] %} +{% block extrahead %} + <script type="text/javascript"> + jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); }); + </script> + {# this is used when loading the search index using $.ajax fails, + such as on Chrome for documents on localhost #} + <script type="text/javascript" id="searchindexloader"></script> + {{ super() }} +{% endblock %} +{% block body %} + <h1 id="search-documentation">{{ _('Search') }}</h1> + <div id="fallback" class="admonition warning"> + <script type="text/javascript">$('#fallback').hide();</script> + <p> + {% trans %}Please activate JavaScript to enable the search + functionality.{% endtrans %} + </p> + </div> + <p> + {% trans %}From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list.{% endtrans %} + </p> + {{ searchform('form-inline', True) }} + {% if search_performed %} + <h2>{{ _('Search Results') }}</h2> + {% if not search_results %} + <p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p> + {% endif %} + {% endif %} + <div id="search-results"> + {% if search_results %} + <ul> + {% for href, caption, context in search_results %} + <li><a href="{{ pathto(item.href) }}">{{ caption }}</a> + <div class="context">{{ context|e }}</div> + </li> + {% endfor %} + </ul> + {% endif %} + </div> +{% endblock %} diff --git a/libs/fmt/doc/api.rst b/libs/fmt/doc/api.rst new file mode 100644 index 0000000000000000000000000000000000000000..2e1bce3fc89c2bca0662c4a65a4c8ec29d3c0cc9 --- /dev/null +++ b/libs/fmt/doc/api.rst @@ -0,0 +1,544 @@ +.. _string-formatting-api: + +************* +API Reference +************* + +The {fmt} library API consists of the following parts: + +* :ref:`fmt/core.h <core-api>`: the core API providing argument handling + facilities and a lightweight subset of formatting functions +* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time + format string checks, wide string, output iterator and user-defined type + support +* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges + and tuples +* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting +* :ref:`fmt/compile.h <compile-api>`: format string compilation +* :ref:`fmt/color.h <color-api>`: terminal color and text style +* :ref:`fmt/os.h <os-api>`: system APIs +* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support +* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting + +All functions and types provided by the library reside in namespace ``fmt`` and +macros have prefix ``FMT_``. + +.. _core-api: + +Core API +======== + +``fmt/core.h`` defines the core API which provides argument handling facilities +and a lightweight subset of formatting functions. It is only beneficial when +using {fmt} as a library and not in the header-only mode. + +The following functions use :ref:`format string syntax <syntax>` +similar to that of Python's `str.format +<https://docs.python.org/3/library/stdtypes.html#str.format>`_. +They take *format_str* and *args* as arguments. + +*format_str* is a format string that contains literal text and replacement +fields surrounded by braces ``{}``. The fields are replaced with formatted +arguments in the resulting string. A function taking *format_str* doesn't +participate in an overload resolution if the latter is not a string. + +*args* is an argument list representing objects to be formatted. + +.. _format: + +.. doxygenfunction:: format(const S &format_str, Args&&... args) +.. doxygenfunction:: vformat(const S &format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args) + +.. doxygenfunction:: format_to(OutputIt out, const S&, const Args&... args) +.. doxygenfunction:: format_to_n(OutputIt out, size_t n, const S&, const Args&... args) +.. doxygenfunction:: formatted_size(string_view format_str, Args&&... args) + +.. doxygenstruct:: fmt::format_to_n_result + :members: + +.. _print: + +.. doxygenfunction:: fmt::print(const S &format_str, Args&&... args) +.. doxygenfunction:: vprint(string_view, format_args) + +.. doxygenfunction:: print(std::FILE *f, const S &format_str, Args&&... args) +.. doxygenfunction:: vprint(std::FILE*, string_view, format_args) + +Named Arguments +--------------- + +.. doxygenfunction:: fmt::arg(const S&, const T&) + +Named arguments are not supported in compile-time checks at the moment. + +Argument Lists +-------------- + +You can create your own formatting function with compile-time checks and small +binary footprint, for example (https://godbolt.org/z/oba4Mc): + +.. code:: c++ + + #include <fmt/format.h> + + void vlog(const char* file, int line, fmt::string_view format, + fmt::format_args args) { + fmt::print("{}: {}: ", file, line); + fmt::vprint(format, args); + } + + template <typename S, typename... Args> + void log(const char* file, int line, const S& format, Args&&... args) { + vlog(file, line, format, + fmt::make_args_checked<Args...>(format, args...)); + } + + #define MY_LOG(format, ...) \ + log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__) + + MY_LOG("invalid squishiness: {}", 42); + +Note that ``vlog`` is not parameterized on argument types which improves compile +times and reduces binary code size compared to a fully parameterized version. + +.. doxygenfunction:: fmt::make_args_checked(const S&, const remove_reference_t<Args>&...) + +.. doxygenfunction:: fmt::make_format_args(const Args&...) + +.. doxygenclass:: fmt::format_arg_store + :members: + +.. doxygenclass:: fmt::dynamic_format_arg_store + :members: + +.. doxygenclass:: fmt::basic_format_args + :members: + +.. doxygenstruct:: fmt::format_args + +.. doxygenclass:: fmt::basic_format_arg + :members: + +.. doxygenclass:: fmt::basic_format_context + :members: + +.. doxygentypedef:: fmt::format_context +.. doxygentypedef:: fmt::wformat_context + +Compatibility +------------- + +.. doxygenclass:: fmt::basic_string_view + :members: + +.. doxygentypedef:: fmt::string_view +.. doxygentypedef:: fmt::wstring_view + +Locale +------ + +All formatting is locale-independent by default. Use the ``'L'`` format +specifier to insert the appropriate number separator characters from the +locale:: + + #include <fmt/core.h> + #include <locale> + + std::locale::global(std::locale("en_US.UTF-8")); + auto s = fmt::format("{:L}", 1000000); // s == "1,000,000" + +.. _format-api: + +Format API +========== + +``fmt/format.h`` defines the full format API providing compile-time format +string checks, wide string, output iterator and user-defined type support. + +Compile-time Format String Checks +--------------------------------- + +Compile-time checks are enabled when using ``FMT_STRING``. They support built-in +and string types as well as user-defined types with ``constexpr`` ``parse`` +functions in their ``formatter`` specializations. + +.. doxygendefine:: FMT_STRING + +To force the use of compile-time checks, define the preprocessor variable +``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING`` +will fail to compile with regular strings. Runtime-checked +formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc. + +.. _udt: + +Formatting User-defined Types +----------------------------- + +To make a user-defined type formattable, specialize the ``formatter<T>`` struct +template and implement ``parse`` and ``format`` methods:: + + #include <fmt/format.h> + + struct point { double x, y; }; + + template <> + struct fmt::formatter<point> { + // Presentation format: 'f' - fixed, 'e' - exponential. + char presentation = 'f'; + + // Parses format specifications of the form ['f' | 'e']. + constexpr auto parse(format_parse_context& ctx) { + // auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) // c++11 + // [ctx.begin(), ctx.end()) is a character range that contains a part of + // the format string starting from the format specifications to be parsed, + // e.g. in + // + // fmt::format("{:f} - point of interest", point{1, 2}); + // + // the range will contain "f} - point of interest". The formatter should + // parse specifiers until '}' or the end of the range. In this example + // the formatter should parse the 'f' specifier and return an iterator + // pointing to '}'. + + // Parse the presentation format and store it in the formatter: + auto it = ctx.begin(), end = ctx.end(); + if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++; + + // Check if reached the end of the range: + if (it != end && *it != '}') + throw format_error("invalid format"); + + // Return an iterator past the end of the parsed range: + return it; + } + + // Formats the point p using the parsed format specification (presentation) + // stored in this formatter. + template <typename FormatContext> + auto format(const point& p, FormatContext& ctx) { + // auto format(const point &p, FormatContext &ctx) -> decltype(ctx.out()) // c++11 + // ctx.out() is an output iterator to write to. + return format_to( + ctx.out(), + presentation == 'f' ? "({:.1f}, {:.1f})" : "({:.1e}, {:.1e})", + p.x, p.y); + } + }; + +Then you can pass objects of type ``point`` to any formatting function:: + + point p = {1, 2}; + std::string s = fmt::format("{:f}", p); + // s == "(1.0, 2.0)" + +You can also reuse existing formatters via inheritance or composition, for +example:: + + enum class color {red, green, blue}; + + template <> struct fmt::formatter<color>: formatter<string_view> { + // parse is inherited from formatter<string_view>. + template <typename FormatContext> + auto format(color c, FormatContext& ctx) { + string_view name = "unknown"; + switch (c) { + case color::red: name = "red"; break; + case color::green: name = "green"; break; + case color::blue: name = "blue"; break; + } + return formatter<string_view>::format(name, ctx); + } + }; + +Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize +all string format specifications, for example + +.. code-block:: c++ + + fmt::format("{:>10}", color::blue) + +will return ``" blue"``. + +You can also write a formatter for a hierarchy of classes:: + + #include <type_traits> + #include <fmt/format.h> + + struct A { + virtual ~A() {} + virtual std::string name() const { return "A"; } + }; + + struct B : A { + virtual std::string name() const { return "B"; } + }; + + template <typename T> + struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> : + fmt::formatter<std::string> { + template <typename FormatCtx> + auto format(const A& a, FormatCtx& ctx) { + return fmt::formatter<std::string>::format(a.name(), ctx); + } + }; + + int main() { + B b; + A& a = b; + fmt::print("{}", a); // prints "B" + } + +If a type provides both a ``formatter`` specialization and an implicit +conversion to a formattable type, the specialization takes precedence over the +conversion. + +.. doxygenclass:: fmt::basic_format_parse_context + :members: + +Literal-based API +----------------- + +The following user-defined literals are defined in ``fmt/format.h``. + +.. doxygenfunction:: operator""_format(const char *s, size_t n) + +.. doxygenfunction:: operator""_a(const char *s, size_t) + +Utilities +--------- + +.. doxygenstruct:: fmt::is_char + +.. doxygentypedef:: fmt::char_t + +.. doxygenfunction:: fmt::ptr(const T *p) +.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) +.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) +.. doxygenfunction:: fmt::ptr(T (*fn)(Args...)) + +.. doxygenfunction:: fmt::to_string(const T &value) + +.. doxygenfunction:: fmt::to_wstring(const T &value) + +.. doxygenfunction:: fmt::to_string_view(const Char *s) + +.. doxygenfunction:: fmt::join(Range &&range, string_view sep) + +.. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep) + +.. doxygenclass:: fmt::detail::buffer + :members: + +.. doxygenclass:: fmt::basic_memory_buffer + :protected-members: + :members: + +System Errors +------------- + +fmt does not use ``errno`` to communicate errors to the user, but it may call +system functions which set ``errno``. Users should not make any assumptions about +the value of ``errno`` being preserved by library functions. + +.. doxygenclass:: fmt::system_error + :members: + +.. doxygenfunction:: fmt::format_system_error + +.. doxygenclass:: fmt::windows_error + :members: + +Custom Allocators +----------------- + +The {fmt} library supports custom dynamic memory allocators. +A custom allocator class can be specified as a template argument to +:class:`fmt::basic_memory_buffer`:: + + using custom_memory_buffer = + fmt::basic_memory_buffer<char, fmt::inline_buffer_size, custom_allocator>; + +It is also possible to write a formatting function that uses a custom +allocator:: + + using custom_string = + std::basic_string<char, std::char_traits<char>, custom_allocator>; + + custom_string vformat(custom_allocator alloc, fmt::string_view format_str, + fmt::format_args args) { + custom_memory_buffer buf(alloc); + fmt::vformat_to(buf, format_str, args); + return custom_string(buf.data(), buf.size(), alloc); + } + + template <typename ...Args> + inline custom_string format(custom_allocator alloc, + fmt::string_view format_str, + const Args& ... args) { + return vformat(alloc, format_str, fmt::make_format_args(args...)); + } + +The allocator will be used for the output container only. Formatting functions +normally don't do any allocations for built-in and string types except for +non-default floating-point formatting that occasionally falls back on +``sprintf``. + +.. _ranges-api: + +Ranges and Tuple Formatting +=========================== + +The library also supports convenient formatting of ranges and tuples:: + + #include <fmt/ranges.h> + + std::tuple<char, int, float> t{'a', 1, 2.0f}; + // Prints "('a', 1, 2.0)" + fmt::print("{}", t); + + +NOTE: currently, the overload of ``fmt::join`` for iterables exists in the main +``format.h`` header, but expect this to change in the future. + +Using ``fmt::join``, you can separate tuple elements with a custom separator:: + + #include <fmt/ranges.h> + + std::tuple<int, char> t = {1, 'a'}; + // Prints "1, a" + fmt::print("{}", fmt::join(t, ", ")); + +.. _chrono-api: + +Date and Time Formatting +======================== + +``fmt/chrono.h`` provides formatters for + +* `std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_ +* `std::chrono::time_point + <https://en.cppreference.com/w/cpp/chrono/time_point>`_ +* `std::tm <https://en.cppreference.com/w/cpp/chrono/c/tm>`_ + +The format syntax is described in :ref:`chrono-specs`. + +**Example**:: + + #include <fmt/chrono.h> + + int main() { + std::time_t t = std::time(nullptr); + + // Prints "The date is 2020-11-07." (with the current date): + fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); + + using namespace std::literals::chrono_literals; + + // Prints "Default format: 42s 100ms": + fmt::print("Default format: {} {}\n", 42s, 100ms); + + // Prints "strftime-like format: 03:15:30": + fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); + } + +.. doxygenfunction:: localtime(std::time_t time) + +.. doxygenfunction:: gmtime(std::time_t time) + +.. _compile-api: + +Format string compilation +========================= + +``fmt/compile.h`` provides format string compilation support when using +``FMT_COMPILE``. Format strings are parsed, checked and converted into efficient +formatting code at compile-time. This supports arguments of built-in and string +types as well as user-defined types with ``constexpr`` ``parse`` functions in +their ``formatter`` specializations. Format string compilation can generate more +binary code compared to the default API and is only recommended in places where +formatting is a performance bottleneck. + +.. doxygendefine:: FMT_COMPILE + +.. _color-api: + +Terminal color and text style +============================= + +``fmt/color.h`` provides support for terminal color and text style output. + +.. doxygenfunction:: print(const text_style &ts, const S &format_str, const Args&... args) + +.. doxygenfunction:: fg(detail::color_type) + +.. doxygenfunction:: bg(detail::color_type) + +.. _os-api: + +System APIs +=========== + +.. doxygenclass:: fmt::ostream + :members: + +.. _ostream-api: + +``std::ostream`` Support +======================== + +``fmt/ostream.h`` provides ``std::ostream`` support including formatting of +user-defined types that have an overloaded insertion operator (``operator<<``):: + + #include <fmt/ostream.h> + + class date { + int year_, month_, day_; + public: + date(int year, int month, int day): year_(year), month_(month), day_(day) {} + + friend std::ostream& operator<<(std::ostream& os, const date& d) { + return os << d.year_ << '-' << d.month_ << '-' << d.day_; + } + }; + + std::string s = fmt::format("The date is {}", date(2012, 12, 9)); + // s == "The date is 2012-12-9" + +{fmt} only supports insertion operators that are defined in the same namespaces +as the types they format and can be found with the argument-dependent lookup. + +.. doxygenfunction:: print(std::basic_ostream<Char> &os, const S &format_str, Args&&... args) + +.. _printf-api: + +``printf`` Formatting +===================== + +The header ``fmt/printf.h`` provides ``printf``-like formatting functionality. +The following functions use `printf format string syntax +<https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with +the POSIX extension for positional arguments. Unlike their standard +counterparts, the ``fmt`` functions are type-safe and throw an exception if an +argument type doesn't match its format specification. + +.. doxygenfunction:: printf(const S &format_str, const Args&... args) + +.. doxygenfunction:: fprintf(std::FILE *f, const S &format, const Args&... args) + +.. doxygenfunction:: fprintf(std::basic_ostream<Char> &os, const S &format_str, const Args&... args) + +.. doxygenfunction:: sprintf(const S&, const Args&...) + +Compatibility with C++20 ``std::format`` +======================================== + +{fmt} implements nearly all of the `C++20 formatting library +<https://en.cppreference.com/w/cpp/utility/format>`_ with the following +differences: + +* Names are defined in the ``fmt`` namespace instead of ``std`` to avoid + collisions with standard library implementations. +* Width calculation doesn't use grapheme clusterization. The latter has been + implemented in a separate branch but hasn't been integrated yet. +* Chrono formatting doesn't support C++20 date types since they are not provided + by standard library implementations. diff --git a/libs/fmt/doc/basic-bootstrap/README b/libs/fmt/doc/basic-bootstrap/README new file mode 100644 index 0000000000000000000000000000000000000000..ba67700dfd0229e1883f5a10ba3dce838269c4a5 --- /dev/null +++ b/libs/fmt/doc/basic-bootstrap/README @@ -0,0 +1,2 @@ +Sphinx basic theme with Bootstrap support. Modifications are kept to +a minimum to simplify integration in case of changes to Sphinx theming. diff --git a/libs/fmt/doc/basic-bootstrap/layout.html b/libs/fmt/doc/basic-bootstrap/layout.html new file mode 100644 index 0000000000000000000000000000000000000000..a257b03d3e60349917e3a3bbe2cc452c074bb947 --- /dev/null +++ b/libs/fmt/doc/basic-bootstrap/layout.html @@ -0,0 +1,206 @@ +{# + basic/layout.html + ~~~~~~~~~~~~~~~~~ + + Master layout template for Sphinx themes. + + :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- block doctype -%} +<!DOCTYPE html> +{%- endblock %} +{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} +{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} +{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and + (sidebars != []) %} +{%- set url_root = pathto('', 1) %} +{# XXX necessary? #} +{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} +{%- if not embedded and docstitle %} + {%- set titlesuffix = " — "|safe + docstitle|e %} +{%- else %} + {%- set titlesuffix = "" %} +{%- endif %} + +{%- macro relbar() %} + <div class="related" role="navigation" aria-label="related navigation"> + <h3>{{ _('Navigation') }}</h3> + <ul> + {%- for rellink in rellinks %} + <li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}> + <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}" + {{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a> + {%- if not loop.first %}{{ reldelim2 }}{% endif %}</li> + {%- endfor %} + {%- block rootrellink %} + <li class="nav-item nav-item-0"><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li> + {%- endblock %} + {%- for parent in parents %} + <li class="nav-item nav-item-{{ loop.index }}"><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li> + {%- endfor %} + {%- block relbaritems %} {% endblock %} + </ul> + </div> +{%- endmacro %} + +{%- macro sidebar() %} + {%- if render_sidebar %} + <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> + <div class="sphinxsidebarwrapper"> + {%- block sidebarlogo %} + {%- if logo %} + <p class="logo"><a href="{{ pathto(master_doc) }}"> + <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/> + </a></p> + {%- endif %} + {%- endblock %} + {%- if sidebars != None %} + {#- new style sidebar: explicitly include/exclude templates #} + {%- for sidebartemplate in sidebars %} + {%- include sidebartemplate %} + {%- endfor %} + {%- else %} + {#- old style sidebars: using blocks -- should be deprecated #} + {%- block sidebartoc %} + {%- include "localtoc.html" %} + {%- endblock %} + {%- block sidebarrel %} + {%- include "relations.html" %} + {%- endblock %} + {%- block sidebarsourcelink %} + {%- include "sourcelink.html" %} + {%- endblock %} + {%- if customsidebar %} + {%- include customsidebar %} + {%- endif %} + {%- block sidebarsearch %} + {%- include "searchbox.html" %} + {%- endblock %} + {%- endif %} + </div> + </div> + {%- endif %} +{%- endmacro %} + +{%- macro script() %} + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '{{ url_root }}', + VERSION: '{{ release|e }}', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}', + HAS_SOURCE: {{ has_source|lower }}, + SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}' + }; + </script> + {%- for scriptfile in script_files %} + <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script> + {%- endfor %} +{%- endmacro %} + +{%- macro css() %} + <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" /> + <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" /> + {%- for cssfile in css_files %} + <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" /> + {%- endfor %} +{%- endmacro %} + +<html lang="en"> + <head> + <meta charset="{{ encoding }}"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + {# The above 3 meta tags *must* come first in the head; any other head content + must come *after* these tags. #} + {{ metatags }} + {%- block htmltitle %} + <title>{{ title|striptags|e }}{{ titlesuffix }}</title> + {%- endblock %} + {{ css() }} + {%- if not embedded %} + {{ script() }} + {%- if use_opensearch %} + <link rel="search" type="application/opensearchdescription+xml" + title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" + href="{{ pathto('_static/opensearch.xml', 1) }}"/> + {%- endif %} + {%- if favicon %} + <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/> + {%- endif %} + {%- endif %} +{%- block linktags %} + {%- if hasdoc('about') %} + <link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" /> + {%- endif %} + {%- if hasdoc('genindex') %} + <link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" /> + {%- endif %} + {%- if hasdoc('search') %} + <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" /> + {%- endif %} + {%- if hasdoc('copyright') %} + <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" /> + {%- endif %} + {%- if parents %} + <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" /> + {%- endif %} + {%- if next %} + <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" /> + {%- endif %} + {%- if prev %} + <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" /> + {%- endif %} +{%- endblock %} +{%- block extrahead %} {% endblock %} + </head> + <body role="document"> +{%- block header %}{% endblock %} + +{%- block relbar1 %}{{ relbar() }}{% endblock %} + +{%- block content %} + {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %} + + <div class="document"> + {%- block document %} + <div class="documentwrapper"> + {%- if render_sidebar %} + <div class="bodywrapper"> + {%- endif %} + <div class="body" role="main"> + {% block body %} {% endblock %} + </div> + {%- if render_sidebar %} + </div> + {%- endif %} + </div> + {%- endblock %} + + {%- block sidebar2 %}{{ sidebar() }}{% endblock %} + <div class="clearer"></div> + </div> +{%- endblock %} + +{%- block relbar2 %}{{ relbar() }}{% endblock %} + +{%- block footer %} + <div class="footer" role="contentinfo"> + {%- if show_copyright %} + {%- if hasdoc('copyright') %} + {% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %} + {%- else %} + {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} + {%- endif %} + {%- endif %} + {%- if last_updated %} + {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} + {%- endif %} + {%- if show_sphinx %} + {% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %} + {%- endif %} + </div> +{%- endblock %} + </body> +</html> diff --git a/libs/fmt/doc/basic-bootstrap/theme.conf b/libs/fmt/doc/basic-bootstrap/theme.conf new file mode 100644 index 0000000000000000000000000000000000000000..89e03bbdac5e2ff5c08eb4bc4918067516932c5c --- /dev/null +++ b/libs/fmt/doc/basic-bootstrap/theme.conf @@ -0,0 +1,2 @@ +[theme] +inherit = basic diff --git a/libs/fmt/doc/bootstrap/alerts.less b/libs/fmt/doc/bootstrap/alerts.less new file mode 100644 index 0000000000000000000000000000000000000000..c4199db927e795b6d7d35a39d475ef3087e2dfec --- /dev/null +++ b/libs/fmt/doc/bootstrap/alerts.less @@ -0,0 +1,73 @@ +// +// Alerts +// -------------------------------------------------- + + +// Base styles +// ------------------------- + +.alert { + padding: @alert-padding; + margin-bottom: @line-height-computed; + border: 1px solid transparent; + border-radius: @alert-border-radius; + + // Headings for larger alerts + h4 { + margin-top: 0; + // Specified for the h4 to prevent conflicts of changing @headings-color + color: inherit; + } + + // Provide class for links that match alerts + .alert-link { + font-weight: @alert-link-font-weight; + } + + // Improve alignment and spacing of inner content + > p, + > ul { + margin-bottom: 0; + } + + > p + p { + margin-top: 5px; + } +} + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0. +.alert-dismissible { + padding-right: (@alert-padding + 20); + + // Adjust close link position + .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; + } +} + +// Alternate styles +// +// Generate contextual modifier classes for colorizing the alert. + +.alert-success { + .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text); +} + +.alert-info { + .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text); +} + +.alert-warning { + .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text); +} + +.alert-danger { + .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); +} diff --git a/libs/fmt/doc/bootstrap/badges.less b/libs/fmt/doc/bootstrap/badges.less new file mode 100644 index 0000000000000000000000000000000000000000..c70bb939ebe253a17a39419d595d0038c7a683bc --- /dev/null +++ b/libs/fmt/doc/bootstrap/badges.less @@ -0,0 +1,66 @@ +// +// Badges +// -------------------------------------------------- + + +// Base class +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: @font-size-small; + font-weight: @badge-font-weight; + color: @badge-color; + line-height: @badge-line-height; + vertical-align: baseline; + white-space: nowrap; + text-align: center; + background-color: @badge-bg; + border-radius: @badge-border-radius; + + // Empty badges collapse automatically (not available in IE8) + &:empty { + display: none; + } + + // Quick fix for badges in buttons + .btn & { + position: relative; + top: -1px; + } + + .btn-xs &, + .btn-group-xs > .btn & { + top: 0; + padding: 1px 5px; + } + + // Hover state, but only for links + a& { + &:hover, + &:focus { + color: @badge-link-hover-color; + text-decoration: none; + cursor: pointer; + } + } + + // Account for badges in navs + .list-group-item.active > &, + .nav-pills > .active > a > & { + color: @badge-active-color; + background-color: @badge-active-bg; + } + + .list-group-item > & { + float: right; + } + + .list-group-item > & + & { + margin-right: 5px; + } + + .nav-pills > li > a > & { + margin-left: 3px; + } +} diff --git a/libs/fmt/doc/bootstrap/bootstrap.less b/libs/fmt/doc/bootstrap/bootstrap.less new file mode 100644 index 0000000000000000000000000000000000000000..61b77474f9ad6ec9f9b5dda2ed67703f7580fde2 --- /dev/null +++ b/libs/fmt/doc/bootstrap/bootstrap.less @@ -0,0 +1,50 @@ +// Core variables and mixins +@import "variables.less"; +@import "mixins.less"; + +// Reset and dependencies +@import "normalize.less"; +@import "print.less"; +@import "glyphicons.less"; + +// Core CSS +@import "scaffolding.less"; +@import "type.less"; +@import "code.less"; +@import "grid.less"; +@import "tables.less"; +@import "forms.less"; +@import "buttons.less"; + +// Components +@import "component-animations.less"; +@import "dropdowns.less"; +@import "button-groups.less"; +@import "input-groups.less"; +@import "navs.less"; +@import "navbar.less"; +@import "breadcrumbs.less"; +@import "pagination.less"; +@import "pager.less"; +@import "labels.less"; +@import "badges.less"; +@import "jumbotron.less"; +@import "thumbnails.less"; +@import "alerts.less"; +@import "progress-bars.less"; +@import "media.less"; +@import "list-group.less"; +@import "panels.less"; +@import "responsive-embed.less"; +@import "wells.less"; +@import "close.less"; + +// Components w/ JavaScript +@import "modals.less"; +@import "tooltip.less"; +@import "popovers.less"; +@import "carousel.less"; + +// Utility classes +@import "utilities.less"; +@import "responsive-utilities.less"; diff --git a/libs/fmt/doc/bootstrap/breadcrumbs.less b/libs/fmt/doc/bootstrap/breadcrumbs.less new file mode 100644 index 0000000000000000000000000000000000000000..cb01d503fbe5f7615e53c989bbee20c07c86e7ef --- /dev/null +++ b/libs/fmt/doc/bootstrap/breadcrumbs.less @@ -0,0 +1,26 @@ +// +// Breadcrumbs +// -------------------------------------------------- + + +.breadcrumb { + padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal; + margin-bottom: @line-height-computed; + list-style: none; + background-color: @breadcrumb-bg; + border-radius: @border-radius-base; + + > li { + display: inline-block; + + + li:before { + content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space + padding: 0 5px; + color: @breadcrumb-color; + } + } + + > .active { + color: @breadcrumb-active-color; + } +} diff --git a/libs/fmt/doc/bootstrap/button-groups.less b/libs/fmt/doc/bootstrap/button-groups.less new file mode 100644 index 0000000000000000000000000000000000000000..13c1800deaaacf739cdb3a4eaba95ee986f656c4 --- /dev/null +++ b/libs/fmt/doc/bootstrap/button-groups.less @@ -0,0 +1,243 @@ +// +// Button groups +// -------------------------------------------------- + +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; // match .btn alignment given font-size hack above + > .btn { + position: relative; + float: left; + // Bring the "active" button to the front + &:hover, + &:focus, + &:active, + &.active { + z-index: 2; + } + } +} + +// Prevent double borders when buttons are next to each other +.btn-group { + .btn + .btn, + .btn + .btn-group, + .btn-group + .btn, + .btn-group + .btn-group { + margin-left: -1px; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + margin-left: -5px; // Offset the first child's margin + &:extend(.clearfix all); + + .btn-group, + .input-group { + float: left; + } + > .btn, + > .btn-group, + > .input-group { + margin-left: 5px; + } +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + &:not(:last-child):not(.dropdown-toggle) { + .border-right-radius(0); + } +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + .border-left-radius(0); +} + +// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group) +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + .border-right-radius(0); + } +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + .border-left-radius(0); +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-xs > .btn { &:extend(.btn-xs); } +.btn-group-sm > .btn { &:extend(.btn-sm); } +.btn-group-lg > .btn { &:extend(.btn-lg); } + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} + +// The clickable button for toggling the menu +// Remove the gradient and set the same inset shadow as the :active state +.btn-group.open .dropdown-toggle { + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + .box-shadow(none); + } +} + + +// Reposition the caret +.btn .caret { + margin-left: 0; +} +// Carets in other button sizes +.btn-lg .caret { + border-width: @caret-width-large @caret-width-large 0; + border-bottom-width: 0; +} +// Upside down carets for .dropup +.dropup .btn-lg .caret { + border-width: 0 @caret-width-large @caret-width-large; +} + + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + > .btn, + > .btn-group, + > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; + } + + // Clear floats so dropdown menus can be properly placed + > .btn-group { + &:extend(.clearfix all); + > .btn { + float: none; + } + } + + > .btn + .btn, + > .btn + .btn-group, + > .btn-group + .btn, + > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; + } +} + +.btn-group-vertical > .btn { + &:not(:first-child):not(:last-child) { + border-radius: 0; + } + &:first-child:not(:last-child) { + border-top-right-radius: @border-radius-base; + .border-bottom-radius(0); + } + &:last-child:not(:first-child) { + border-bottom-left-radius: @border-radius-base; + .border-top-radius(0); + } +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + .border-bottom-radius(0); + } +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + .border-top-radius(0); +} + + +// Justified button groups +// ---------------------- + +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; + > .btn, + > .btn-group { + float: none; + display: table-cell; + width: 1%; + } + > .btn-group .btn { + width: 100%; + } + + > .btn-group .dropdown-menu { + left: auto; + } +} + + +// Checkbox and radio options +// +// In order to support the browser's form validation feedback, powered by the +// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use +// `display: none;` or `visibility: hidden;` as that also hides the popover. +// Simply visually hiding the inputs via `opacity` would leave them clickable in +// certain cases which is prevented by using `clip` and `pointer-events`. +// This way, we ensure a DOM element is visible to position the popover from. +// +// See https://github.com/twbs/bootstrap/pull/12794 and +// https://github.com/twbs/bootstrap/pull/14559 for more information. + +[data-toggle="buttons"] { + > .btn, + > .btn-group > .btn { + input[type="radio"], + input[type="checkbox"] { + position: absolute; + clip: rect(0,0,0,0); + pointer-events: none; + } + } +} diff --git a/libs/fmt/doc/bootstrap/buttons.less b/libs/fmt/doc/bootstrap/buttons.less new file mode 100644 index 0000000000000000000000000000000000000000..5a746049e3cea5c5f912144e11850d56a36cf0d2 --- /dev/null +++ b/libs/fmt/doc/bootstrap/buttons.less @@ -0,0 +1,160 @@ +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +.btn { + display: inline-block; + margin-bottom: 0; // For input.btn + font-weight: @btn-font-weight; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + white-space: nowrap; + .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base); + .user-select(none); + + &, + &:active, + &.active { + &:focus, + &.focus { + .tab-focus(); + } + } + + &:hover, + &:focus, + &.focus { + color: @btn-default-color; + text-decoration: none; + } + + &:active, + &.active { + outline: 0; + background-image: none; + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + cursor: @cursor-disabled; + pointer-events: none; // Future-proof disabling of clicks + .opacity(.65); + .box-shadow(none); + } +} + + +// Alternate buttons +// -------------------------------------------------- + +.btn-default { + .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border); +} +.btn-primary { + .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border); +} +// Success appears as green +.btn-success { + .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border); +} +// Info appears as blue-green +.btn-info { + .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border); +} +// Warning appears as orange +.btn-warning { + .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border); +} +// Danger and error appear as red +.btn-danger { + .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border); +} + + +// Link buttons +// ------------------------- + +// Make a button look and behave like a link +.btn-link { + color: @link-color; + font-weight: normal; + border-radius: 0; + + &, + &:active, + &.active, + &[disabled], + fieldset[disabled] & { + background-color: transparent; + .box-shadow(none); + } + &, + &:hover, + &:focus, + &:active { + border-color: transparent; + } + &:hover, + &:focus { + color: @link-hover-color; + text-decoration: @link-hover-decoration; + background-color: transparent; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @btn-link-disabled-color; + text-decoration: none; + } + } +} + + +// Button Sizes +// -------------------------------------------------- + +.btn-lg { + // line-height: ensure even-numbered height of button next to large input + .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large); +} +.btn-sm { + // line-height: ensure proper height of button next to small input + .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small); +} +.btn-xs { + .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small); +} + + +// Block button +// -------------------------------------------------- + +.btn-block { + display: block; + width: 100%; +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} diff --git a/libs/fmt/doc/bootstrap/carousel.less b/libs/fmt/doc/bootstrap/carousel.less new file mode 100644 index 0000000000000000000000000000000000000000..4bbe946d3aa8f36a49c9dd77306e02888634be87 --- /dev/null +++ b/libs/fmt/doc/bootstrap/carousel.less @@ -0,0 +1,269 @@ +// +// Carousel +// -------------------------------------------------- + + +// Wrapper for the slide container and indicators +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; + + > .item { + display: none; + position: relative; + .transition(.6s ease-in-out left); + + // Account for jankitude on images + > img, + > a > img { + &:extend(.img-responsive); + line-height: 1; + } + + // WebKit CSS3 transforms for supported devices + @media all and (transform-3d), (-webkit-transform-3d) { + .transition-transform(~'0.6s ease-in-out'); + .backface-visibility(~'hidden'); + .perspective(1000); + + &.next, + &.active.right { + .translate3d(100%, 0, 0); + left: 0; + } + &.prev, + &.active.left { + .translate3d(-100%, 0, 0); + left: 0; + } + &.next.left, + &.prev.right, + &.active { + .translate3d(0, 0, 0); + left: 0; + } + } + } + + > .active, + > .next, + > .prev { + display: block; + } + + > .active { + left: 0; + } + + > .next, + > .prev { + position: absolute; + top: 0; + width: 100%; + } + + > .next { + left: 100%; + } + > .prev { + left: -100%; + } + > .next.left, + > .prev.right { + left: 0; + } + + > .active.left { + left: -100%; + } + > .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: @carousel-control-width; + .opacity(@carousel-control-opacity); + font-size: @carousel-control-font-size; + color: @carousel-control-color; + text-align: center; + text-shadow: @carousel-text-shadow; + // We can't have this transition here because WebKit cancels the carousel + // animation if you trip this while in the middle of another animation. + + // Set gradients for backgrounds + &.left { + #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001)); + } + &.right { + left: auto; + right: 0; + #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5)); + } + + // Hover/focus state + &:hover, + &:focus { + outline: 0; + color: @carousel-control-color; + text-decoration: none; + .opacity(.9); + } + + // Toggles + .icon-prev, + .icon-next, + .glyphicon-chevron-left, + .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + } + .icon-prev, + .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; + } + .icon-next, + .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; + } + .icon-prev, + .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + line-height: 1; + font-family: serif; + } + + + .icon-prev { + &:before { + content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039) + } + } + .icon-next { + &:before { + content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A) + } + } +} + +// Optional indicator pips +// +// Add an unordered list with the following class and add a list item for each +// slide your carousel holds. + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; + + li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid @carousel-indicator-border-color; + border-radius: 10px; + cursor: pointer; + + // IE8-9 hack for event handling + // + // Internet Explorer 8-9 does not support clicks on elements without a set + // `background-color`. We cannot use `filter` since that's not viewed as a + // background color by the browser. Thus, a hack is needed. + // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer + // + // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we + // set alpha transparency for the best results possible. + background-color: #000 \9; // IE8 + background-color: rgba(0,0,0,0); // IE9 + } + .active { + margin: 0; + width: 12px; + height: 12px; + background-color: @carousel-indicator-active-bg; + } +} + +// Optional captions +// ----------------------------- +// Hidden by default for smaller viewports +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: @carousel-caption-color; + text-align: center; + text-shadow: @carousel-text-shadow; + & .btn { + text-shadow: none; // No shadow for button elements in carousel-caption + } +} + + +// Scale up controls for tablets and up +@media screen and (min-width: @screen-sm-min) { + + // Scale up the controls a smidge + .carousel-control { + .glyphicon-chevron-left, + .glyphicon-chevron-right, + .icon-prev, + .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; + } + .glyphicon-chevron-left, + .icon-prev { + margin-left: -15px; + } + .glyphicon-chevron-right, + .icon-next { + margin-right: -15px; + } + } + + // Show and left align the captions + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + + // Move up the indicators + .carousel-indicators { + bottom: 20px; + } +} diff --git a/libs/fmt/doc/bootstrap/close.less b/libs/fmt/doc/bootstrap/close.less new file mode 100644 index 0000000000000000000000000000000000000000..6d5bfe087aed3342270b1de9d70d034c474a3779 --- /dev/null +++ b/libs/fmt/doc/bootstrap/close.less @@ -0,0 +1,34 @@ +// +// Close icons +// -------------------------------------------------- + + +.close { + float: right; + font-size: (@font-size-base * 1.5); + font-weight: @close-font-weight; + line-height: 1; + color: @close-color; + text-shadow: @close-text-shadow; + .opacity(.2); + + &:hover, + &:focus { + color: @close-color; + text-decoration: none; + cursor: pointer; + .opacity(.5); + } + + // Additional properties for button version + // iOS requires the button element instead of an anchor tag. + // If you want the anchor version, it requires `href="#"`. + // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + button& { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; + } +} diff --git a/libs/fmt/doc/bootstrap/code.less b/libs/fmt/doc/bootstrap/code.less new file mode 100644 index 0000000000000000000000000000000000000000..a08b4d48c4c8715dac93a5422f0d340ae9cc6bff --- /dev/null +++ b/libs/fmt/doc/bootstrap/code.less @@ -0,0 +1,69 @@ +// +// Code (inline and block) +// -------------------------------------------------- + + +// Inline and block code styles +code, +kbd, +pre, +samp { + font-family: @font-family-monospace; +} + +// Inline code +code { + padding: 2px 4px; + font-size: 90%; + color: @code-color; + background-color: @code-bg; + border-radius: @border-radius-base; +} + +// User input typically entered via keyboard +kbd { + padding: 2px 4px; + font-size: 90%; + color: @kbd-color; + background-color: @kbd-bg; + border-radius: @border-radius-small; + box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); + + kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + box-shadow: none; + } +} + +// Blocks of code +pre { + display: block; + padding: ((@line-height-computed - 1) / 2); + margin: 0 0 (@line-height-computed / 2); + font-size: (@font-size-base - 1); // 14px to 13px + line-height: @line-height-base; + word-break: break-all; + word-wrap: break-word; + color: @pre-color; + background-color: @pre-bg; + border: 1px solid @pre-border-color; + border-radius: @border-radius-base; + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: @pre-scrollable-max-height; + overflow-y: scroll; +} diff --git a/libs/fmt/doc/bootstrap/component-animations.less b/libs/fmt/doc/bootstrap/component-animations.less new file mode 100644 index 0000000000000000000000000000000000000000..0bcee910ac5f2c249cd6280d1e69fd03d178b24e --- /dev/null +++ b/libs/fmt/doc/bootstrap/component-animations.less @@ -0,0 +1,33 @@ +// +// Component animations +// -------------------------------------------------- + +// Heads up! +// +// We don't use the `.opacity()` mixin here since it causes a bug with text +// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552. + +.fade { + opacity: 0; + .transition(opacity .15s linear); + &.in { + opacity: 1; + } +} + +.collapse { + display: none; + + &.in { display: block; } + tr&.in { display: table-row; } + tbody&.in { display: table-row-group; } +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + .transition-property(~"height, visibility"); + .transition-duration(.35s); + .transition-timing-function(ease); +} diff --git a/libs/fmt/doc/bootstrap/dropdowns.less b/libs/fmt/doc/bootstrap/dropdowns.less new file mode 100644 index 0000000000000000000000000000000000000000..af344607bd967350aab19a91d8f069eee20b4230 --- /dev/null +++ b/libs/fmt/doc/bootstrap/dropdowns.less @@ -0,0 +1,214 @@ +// +// Dropdown menus +// -------------------------------------------------- + + +// Dropdown arrow/caret +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: @caret-width-base dashed; + border-right: @caret-width-base solid transparent; + border-left: @caret-width-base solid transparent; +} + +// The dropdown wrapper (div) +.dropup, +.dropdown { + position: relative; +} + +// Prevent the focus on the dropdown toggle when closing dropdowns +.dropdown-toggle:focus { + outline: 0; +} + +// The dropdown menu (ul) +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: @zindex-dropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + font-size: @font-size-base; + text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) + background-color: @dropdown-bg; + border: 1px solid @dropdown-fallback-border; // IE8 fallback + border: 1px solid @dropdown-border; + border-radius: @border-radius-base; + .box-shadow(0 6px 12px rgba(0,0,0,.175)); + background-clip: padding-box; + + // Aligns the dropdown menu to right + // + // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]` + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + .nav-divider(@dropdown-divider-bg); + } + + // Links within the dropdown menu + > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: @line-height-base; + color: @dropdown-link-color; + white-space: nowrap; // prevent links from randomly breaking onto new lines + } +} + +// Hover/Focus state +.dropdown-menu > li > a { + &:hover, + &:focus { + text-decoration: none; + color: @dropdown-link-hover-color; + background-color: @dropdown-link-hover-bg; + } +} + +// Active state +.dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: @dropdown-link-active-color; + text-decoration: none; + outline: 0; + background-color: @dropdown-link-active-bg; + } +} + +// Disabled state +// +// Gray out text and ensure the hover/focus state remains gray + +.dropdown-menu > .disabled > a { + &, + &:hover, + &:focus { + color: @dropdown-link-disabled-color; + } + + // Nuke hover/focus effects + &:hover, + &:focus { + text-decoration: none; + background-color: transparent; + background-image: none; // Remove CSS gradient + .reset-filter(); + cursor: @cursor-disabled; + } +} + +// Open state for the dropdown +.open { + // Show the menu + > .dropdown-menu { + display: block; + } + + // Remove the outline when :focus is triggered + > a { + outline: 0; + } +} + +// Menu positioning +// +// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown +// menu with the parent. +.dropdown-menu-right { + left: auto; // Reset the default from `.dropdown-menu` + right: 0; +} +// With v3, we enabled auto-flipping if you have a dropdown within a right +// aligned nav component. To enable the undoing of that, we provide an override +// to restore the default dropdown menu alignment. +// +// This is only for left-aligning a dropdown menu within a `.navbar-right` or +// `.pull-right` nav component. +.dropdown-menu-left { + left: 0; + right: auto; +} + +// Dropdown section headers +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: @font-size-small; + line-height: @line-height-base; + color: @dropdown-header-color; + white-space: nowrap; // as with > li > a +} + +// Backdrop to catch body clicks on mobile, etc. +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: (@zindex-dropdown - 10); +} + +// Right aligned dropdowns +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? + +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + border-top: 0; + border-bottom: @caret-width-base solid; + content: ""; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; + } +} + + +// Component alignment +// +// Reiterate per navbar.less and the modified component alignment there. + +@media (min-width: @grid-float-breakpoint) { + .navbar-right { + .dropdown-menu { + .dropdown-menu-right(); + } + // Necessary for overrides of the default right aligned menu. + // Will remove come v4 in all likelihood. + .dropdown-menu-left { + .dropdown-menu-left(); + } + } +} diff --git a/libs/fmt/doc/bootstrap/forms.less b/libs/fmt/doc/bootstrap/forms.less new file mode 100644 index 0000000000000000000000000000000000000000..e4b50629e97cd9bbac46c4c2d9d36a55f3f9686d --- /dev/null +++ b/libs/fmt/doc/bootstrap/forms.less @@ -0,0 +1,574 @@ +// +// Forms +// -------------------------------------------------- + + +// Normalize non-controls +// +// Restyle and baseline non-control form elements. + +fieldset { + padding: 0; + margin: 0; + border: 0; + // Chrome and Firefox set a `min-width: min-content;` on fieldsets, + // so we reset that to ensure it behaves more like a standard block element. + // See https://github.com/twbs/bootstrap/issues/12359. + min-width: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: @line-height-computed; + font-size: (@font-size-base * 1.5); + line-height: inherit; + color: @legend-color; + border: 0; + border-bottom: 1px solid @legend-border-color; +} + +label { + display: inline-block; + max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141) + margin-bottom: 5px; + font-weight: bold; +} + + +// Normalize form controls +// +// While most of our form styles require extra classes, some basic normalization +// is required to ensure optimum display with or without those classes to better +// address browser inconsistencies. + +// Override content-box in Normalize (* isn't specific enough) +input[type="search"] { + .box-sizing(border-box); +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; // IE8-9 + line-height: normal; +} + +// Set the height of file controls to match text inputs +input[type="file"] { + display: block; +} + +// Make range inputs behave like textual form controls +input[type="range"] { + display: block; + width: 100%; +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for file, radio, and checkbox +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + .tab-focus(); +} + +// Adjust output element +output { + display: block; + padding-top: (@padding-base-vertical + 1); + font-size: @font-size-base; + line-height: @line-height-base; + color: @input-color; +} + + +// Common form controls +// +// Shared size and type resets for form controls. Apply `.form-control` to any +// of the following form controls: +// +// select +// textarea +// input[type="text"] +// input[type="password"] +// input[type="datetime"] +// input[type="datetime-local"] +// input[type="date"] +// input[type="month"] +// input[type="time"] +// input[type="week"] +// input[type="number"] +// input[type="email"] +// input[type="url"] +// input[type="search"] +// input[type="tel"] +// input[type="color"] + +.form-control { + display: block; + width: 100%; + height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + padding: @padding-base-vertical @padding-base-horizontal; + font-size: @font-size-base; + line-height: @line-height-base; + color: @input-color; + background-color: @input-bg; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid @input-border; + border-radius: @input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS. + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + .transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s"); + + // Customize the `:focus` state to imitate native WebKit styles. + .form-control-focus(); + + // Placeholder + .placeholder(); + + // Disabled and read-only inputs + // + // HTML5 says that controls under a fieldset > legend:first-child won't be + // disabled if the fieldset is disabled. Due to implementation difficulty, we + // don't honor that edge case; we style them as disabled anyway. + &[disabled], + &[readonly], + fieldset[disabled] & { + background-color: @input-bg-disabled; + opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655 + } + + &[disabled], + fieldset[disabled] & { + cursor: @cursor-disabled; + } + + // Reset height for `textarea`s + textarea& { + height: auto; + } +} + + +// Search inputs in iOS +// +// This overrides the extra rounded corners on search inputs in iOS so that our +// `.form-control` class can properly style them. Note that this cannot simply +// be added to `.form-control` as it's not specific enough. For details, see +// https://github.com/twbs/bootstrap/issues/11586. + +input[type="search"] { + -webkit-appearance: none; +} + + +// Special styles for iOS temporal inputs +// +// In Mobile Safari, setting `display: block` on temporal inputs causes the +// text within the input to become vertically misaligned. As a workaround, we +// set a pixel line-height that matches the given height of the input, but only +// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848 + +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"], + input[type="time"], + input[type="datetime-local"], + input[type="month"] { + line-height: @input-height-base; + + &.input-sm, + .input-group-sm & { + line-height: @input-height-small; + } + + &.input-lg, + .input-group-lg & { + line-height: @input-height-large; + } + } +} + + +// Form groups +// +// Designed to help with the organization and spacing of vertical forms. For +// horizontal forms, use the predefined grid classes. + +.form-group { + margin-bottom: @form-group-margin-bottom; +} + + +// Checkboxes and radios +// +// Indent the labels to position radios/checkboxes as hanging controls. + +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; + + label { + min-height: @line-height-computed; // Ensure the input doesn't jump when there is no text + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; + } +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px \9; +} + +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing +} + +// Radios and checkboxes on same line +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; // space out consecutive inline controls +} + +// Apply same disabled cursor tweak as for inputs +// Some special care is needed because <label>s don't inherit their parent's `cursor`. +// +// Note: Neither radios nor checkboxes can be readonly. +input[type="radio"], +input[type="checkbox"] { + &[disabled], + &.disabled, + fieldset[disabled] & { + cursor: @cursor-disabled; + } +} +// These classes are used directly on <label>s +.radio-inline, +.checkbox-inline { + &.disabled, + fieldset[disabled] & { + cursor: @cursor-disabled; + } +} +// These classes are used on elements with <label> descendants +.radio, +.checkbox { + &.disabled, + fieldset[disabled] & { + label { + cursor: @cursor-disabled; + } + } +} + + +// Static form control text +// +// Apply class to a `p` element to make any string of text align with labels in +// a horizontal form layout. + +.form-control-static { + // Size it appropriately next to real form controls + padding-top: (@padding-base-vertical + 1); + padding-bottom: (@padding-base-vertical + 1); + // Remove default margin from `p` + margin-bottom: 0; + min-height: (@line-height-computed + @font-size-base); + + &.input-lg, + &.input-sm { + padding-left: 0; + padding-right: 0; + } +} + + +// Form control sizing +// +// Build on `.form-control` with modifier classes to decrease or increase the +// height and font-size of form controls. +// +// The `.form-group-* form-control` variations are sadly duplicated to avoid the +// issue documented in https://github.com/twbs/bootstrap/issues/15074. + +.input-sm { + .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small); +} +.form-group-sm { + .form-control { + .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small); + } + .form-control-static { + height: @input-height-small; + padding: @padding-small-vertical @padding-small-horizontal; + font-size: @font-size-small; + line-height: @line-height-small; + min-height: (@line-height-computed + @font-size-small); + } +} + +.input-lg { + .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large); +} +.form-group-lg { + .form-control { + .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large); + } + .form-control-static { + height: @input-height-large; + padding: @padding-large-vertical @padding-large-horizontal; + font-size: @font-size-large; + line-height: @line-height-large; + min-height: (@line-height-computed + @font-size-large); + } +} + + +// Form control feedback states +// +// Apply contextual and semantic states to individual form controls. + +.has-feedback { + // Enable absolute positioning + position: relative; + + // Ensure icons don't overlap text + .form-control { + padding-right: (@input-height-base * 1.25); + } +} +// Feedback icon (requires .glyphicon classes) +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; // Ensure icon is above input groups + display: block; + width: @input-height-base; + height: @input-height-base; + line-height: @input-height-base; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback { + width: @input-height-large; + height: @input-height-large; + line-height: @input-height-large; +} +.input-sm + .form-control-feedback { + width: @input-height-small; + height: @input-height-small; + line-height: @input-height-small; +} + +// Feedback states +.has-success { + .form-control-validation(@state-success-text; @state-success-text; @state-success-bg); +} +.has-warning { + .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg); +} +.has-error { + .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg); +} + +// Reposition feedback icon if input has visible label above +.has-feedback label { + + & ~ .form-control-feedback { + top: (@line-height-computed + 5); // Height of the `label` and its margin + } + &.sr-only ~ .form-control-feedback { + top: 0; + } +} + + +// Help text +// +// Apply to any element you wish to create light text for placement immediately +// below a form control. Use for general help, formatting, or instructional text. + +.help-block { + display: block; // account for any element using help-block + margin-top: 5px; + margin-bottom: 10px; + color: lighten(@text-color, 25%); // lighten the text some for contrast +} + + +// Inline forms +// +// Make forms appear inline(-block) by adding the `.form-inline` class. Inline +// forms begin stacked on extra small (mobile) devices and then go inline when +// viewports reach <768px. +// +// Requires wrapping inputs and labels with `.form-group` for proper display of +// default HTML form controls and our custom form controls (e.g., input groups). +// +// Heads up! This is mixin-ed into `.navbar-form` in navbars.less. + +.form-inline { + + // Kick in the inline + @media (min-width: @screen-sm-min) { + // Inline-block all the things for "inline" + .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + + // In navbar-form, allow folks to *not* use `.form-group` + .form-control { + display: inline-block; + width: auto; // Prevent labels from stacking above inputs in `.form-group` + vertical-align: middle; + } + + // Make static controls behave like regular ones + .form-control-static { + display: inline-block; + } + + .input-group { + display: inline-table; + vertical-align: middle; + + .input-group-addon, + .input-group-btn, + .form-control { + width: auto; + } + } + + // Input groups need that 100% width though + .input-group > .form-control { + width: 100%; + } + + .control-label { + margin-bottom: 0; + vertical-align: middle; + } + + // Remove default margin on radios/checkboxes that were used for stacking, and + // then undo the floating of radios and checkboxes to match. + .radio, + .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + + label { + padding-left: 0; + } + } + .radio input[type="radio"], + .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + + // Re-override the feedback icon. + .has-feedback .form-control-feedback { + top: 0; + } + } +} + + +// Horizontal forms +// +// Horizontal forms are built on grid classes and allow you to create forms with +// labels on the left and inputs on the right. + +.form-horizontal { + + // Consistent vertical alignment of radios and checkboxes + // + // Labels also get some reset styles, but that is scoped to a media query below. + .radio, + .checkbox, + .radio-inline, + .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: (@padding-base-vertical + 1); // Default padding plus a border + } + // Account for padding we're adding to ensure the alignment and of help text + // and other content below items + .radio, + .checkbox { + min-height: (@line-height-computed + (@padding-base-vertical + 1)); + } + + // Make form groups behave like rows + .form-group { + .make-row(); + } + + // Reset spacing and right align labels, but scope to media queries so that + // labels on narrow viewports stack the same as a default form example. + @media (min-width: @screen-sm-min) { + .control-label { + text-align: right; + margin-bottom: 0; + padding-top: (@padding-base-vertical + 1); // Default padding plus a border + } + } + + // Validation states + // + // Reposition the icon because it's now within a grid column and columns have + // `position: relative;` on them. Also accounts for the grid gutter padding. + .has-feedback .form-control-feedback { + right: (@grid-gutter-width / 2); + } + + // Form group sizes + // + // Quick utility class for applying `.input-lg` and `.input-sm` styles to the + // inputs and labels within a `.form-group`. + .form-group-lg { + @media (min-width: @screen-sm-min) { + .control-label { + padding-top: ((@padding-large-vertical * @line-height-large) + 1); + } + } + } + .form-group-sm { + @media (min-width: @screen-sm-min) { + .control-label { + padding-top: (@padding-small-vertical + 1); + } + } + } +} diff --git a/libs/fmt/doc/bootstrap/glyphicons.less b/libs/fmt/doc/bootstrap/glyphicons.less new file mode 100644 index 0000000000000000000000000000000000000000..335d80aa6bfebfc5bd64b60f75a9d49ea79687be --- /dev/null +++ b/libs/fmt/doc/bootstrap/glyphicons.less @@ -0,0 +1,305 @@ +// +// Glyphicons for Bootstrap +// +// Since icons are fonts, they can be placed anywhere text is placed and are +// thus automatically sized to match the surrounding child. To use, create an +// inline element with the appropriate classes, like so: +// +// <a href="#"><span class="glyphicon glyphicon-star"></span> Star</a> + +// Import the fonts +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('@{icon-font-path}@{icon-font-name}.eot'); + src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'), + url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'), + url('@{icon-font-path}@{icon-font-name}.woff') format('woff'), + url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'), + url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg'); +} + +// Catchall baseclass +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +// Individual icons +.glyphicon-asterisk { &:before { content: "\2a"; } } +.glyphicon-plus { &:before { content: "\2b"; } } +.glyphicon-euro, +.glyphicon-eur { &:before { content: "\20ac"; } } +.glyphicon-minus { &:before { content: "\2212"; } } +.glyphicon-cloud { &:before { content: "\2601"; } } +.glyphicon-envelope { &:before { content: "\2709"; } } +.glyphicon-pencil { &:before { content: "\270f"; } } +.glyphicon-glass { &:before { content: "\e001"; } } +.glyphicon-music { &:before { content: "\e002"; } } +.glyphicon-search { &:before { content: "\e003"; } } +.glyphicon-heart { &:before { content: "\e005"; } } +.glyphicon-star { &:before { content: "\e006"; } } +.glyphicon-star-empty { &:before { content: "\e007"; } } +.glyphicon-user { &:before { content: "\e008"; } } +.glyphicon-film { &:before { content: "\e009"; } } +.glyphicon-th-large { &:before { content: "\e010"; } } +.glyphicon-th { &:before { content: "\e011"; } } +.glyphicon-th-list { &:before { content: "\e012"; } } +.glyphicon-ok { &:before { content: "\e013"; } } +.glyphicon-remove { &:before { content: "\e014"; } } +.glyphicon-zoom-in { &:before { content: "\e015"; } } +.glyphicon-zoom-out { &:before { content: "\e016"; } } +.glyphicon-off { &:before { content: "\e017"; } } +.glyphicon-signal { &:before { content: "\e018"; } } +.glyphicon-cog { &:before { content: "\e019"; } } +.glyphicon-trash { &:before { content: "\e020"; } } +.glyphicon-home { &:before { content: "\e021"; } } +.glyphicon-file { &:before { content: "\e022"; } } +.glyphicon-time { &:before { content: "\e023"; } } +.glyphicon-road { &:before { content: "\e024"; } } +.glyphicon-download-alt { &:before { content: "\e025"; } } +.glyphicon-download { &:before { content: "\e026"; } } +.glyphicon-upload { &:before { content: "\e027"; } } +.glyphicon-inbox { &:before { content: "\e028"; } } +.glyphicon-play-circle { &:before { content: "\e029"; } } +.glyphicon-repeat { &:before { content: "\e030"; } } +.glyphicon-refresh { &:before { content: "\e031"; } } +.glyphicon-list-alt { &:before { content: "\e032"; } } +.glyphicon-lock { &:before { content: "\e033"; } } +.glyphicon-flag { &:before { content: "\e034"; } } +.glyphicon-headphones { &:before { content: "\e035"; } } +.glyphicon-volume-off { &:before { content: "\e036"; } } +.glyphicon-volume-down { &:before { content: "\e037"; } } +.glyphicon-volume-up { &:before { content: "\e038"; } } +.glyphicon-qrcode { &:before { content: "\e039"; } } +.glyphicon-barcode { &:before { content: "\e040"; } } +.glyphicon-tag { &:before { content: "\e041"; } } +.glyphicon-tags { &:before { content: "\e042"; } } +.glyphicon-book { &:before { content: "\e043"; } } +.glyphicon-bookmark { &:before { content: "\e044"; } } +.glyphicon-print { &:before { content: "\e045"; } } +.glyphicon-camera { &:before { content: "\e046"; } } +.glyphicon-font { &:before { content: "\e047"; } } +.glyphicon-bold { &:before { content: "\e048"; } } +.glyphicon-italic { &:before { content: "\e049"; } } +.glyphicon-text-height { &:before { content: "\e050"; } } +.glyphicon-text-width { &:before { content: "\e051"; } } +.glyphicon-align-left { &:before { content: "\e052"; } } +.glyphicon-align-center { &:before { content: "\e053"; } } +.glyphicon-align-right { &:before { content: "\e054"; } } +.glyphicon-align-justify { &:before { content: "\e055"; } } +.glyphicon-list { &:before { content: "\e056"; } } +.glyphicon-indent-left { &:before { content: "\e057"; } } +.glyphicon-indent-right { &:before { content: "\e058"; } } +.glyphicon-facetime-video { &:before { content: "\e059"; } } +.glyphicon-picture { &:before { content: "\e060"; } } +.glyphicon-map-marker { &:before { content: "\e062"; } } +.glyphicon-adjust { &:before { content: "\e063"; } } +.glyphicon-tint { &:before { content: "\e064"; } } +.glyphicon-edit { &:before { content: "\e065"; } } +.glyphicon-share { &:before { content: "\e066"; } } +.glyphicon-check { &:before { content: "\e067"; } } +.glyphicon-move { &:before { content: "\e068"; } } +.glyphicon-step-backward { &:before { content: "\e069"; } } +.glyphicon-fast-backward { &:before { content: "\e070"; } } +.glyphicon-backward { &:before { content: "\e071"; } } +.glyphicon-play { &:before { content: "\e072"; } } +.glyphicon-pause { &:before { content: "\e073"; } } +.glyphicon-stop { &:before { content: "\e074"; } } +.glyphicon-forward { &:before { content: "\e075"; } } +.glyphicon-fast-forward { &:before { content: "\e076"; } } +.glyphicon-step-forward { &:before { content: "\e077"; } } +.glyphicon-eject { &:before { content: "\e078"; } } +.glyphicon-chevron-left { &:before { content: "\e079"; } } +.glyphicon-chevron-right { &:before { content: "\e080"; } } +.glyphicon-plus-sign { &:before { content: "\e081"; } } +.glyphicon-minus-sign { &:before { content: "\e082"; } } +.glyphicon-remove-sign { &:before { content: "\e083"; } } +.glyphicon-ok-sign { &:before { content: "\e084"; } } +.glyphicon-question-sign { &:before { content: "\e085"; } } +.glyphicon-info-sign { &:before { content: "\e086"; } } +.glyphicon-screenshot { &:before { content: "\e087"; } } +.glyphicon-remove-circle { &:before { content: "\e088"; } } +.glyphicon-ok-circle { &:before { content: "\e089"; } } +.glyphicon-ban-circle { &:before { content: "\e090"; } } +.glyphicon-arrow-left { &:before { content: "\e091"; } } +.glyphicon-arrow-right { &:before { content: "\e092"; } } +.glyphicon-arrow-up { &:before { content: "\e093"; } } +.glyphicon-arrow-down { &:before { content: "\e094"; } } +.glyphicon-share-alt { &:before { content: "\e095"; } } +.glyphicon-resize-full { &:before { content: "\e096"; } } +.glyphicon-resize-small { &:before { content: "\e097"; } } +.glyphicon-exclamation-sign { &:before { content: "\e101"; } } +.glyphicon-gift { &:before { content: "\e102"; } } +.glyphicon-leaf { &:before { content: "\e103"; } } +.glyphicon-fire { &:before { content: "\e104"; } } +.glyphicon-eye-open { &:before { content: "\e105"; } } +.glyphicon-eye-close { &:before { content: "\e106"; } } +.glyphicon-warning-sign { &:before { content: "\e107"; } } +.glyphicon-plane { &:before { content: "\e108"; } } +.glyphicon-calendar { &:before { content: "\e109"; } } +.glyphicon-random { &:before { content: "\e110"; } } +.glyphicon-comment { &:before { content: "\e111"; } } +.glyphicon-magnet { &:before { content: "\e112"; } } +.glyphicon-chevron-up { &:before { content: "\e113"; } } +.glyphicon-chevron-down { &:before { content: "\e114"; } } +.glyphicon-retweet { &:before { content: "\e115"; } } +.glyphicon-shopping-cart { &:before { content: "\e116"; } } +.glyphicon-folder-close { &:before { content: "\e117"; } } +.glyphicon-folder-open { &:before { content: "\e118"; } } +.glyphicon-resize-vertical { &:before { content: "\e119"; } } +.glyphicon-resize-horizontal { &:before { content: "\e120"; } } +.glyphicon-hdd { &:before { content: "\e121"; } } +.glyphicon-bullhorn { &:before { content: "\e122"; } } +.glyphicon-bell { &:before { content: "\e123"; } } +.glyphicon-certificate { &:before { content: "\e124"; } } +.glyphicon-thumbs-up { &:before { content: "\e125"; } } +.glyphicon-thumbs-down { &:before { content: "\e126"; } } +.glyphicon-hand-right { &:before { content: "\e127"; } } +.glyphicon-hand-left { &:before { content: "\e128"; } } +.glyphicon-hand-up { &:before { content: "\e129"; } } +.glyphicon-hand-down { &:before { content: "\e130"; } } +.glyphicon-circle-arrow-right { &:before { content: "\e131"; } } +.glyphicon-circle-arrow-left { &:before { content: "\e132"; } } +.glyphicon-circle-arrow-up { &:before { content: "\e133"; } } +.glyphicon-circle-arrow-down { &:before { content: "\e134"; } } +.glyphicon-globe { &:before { content: "\e135"; } } +.glyphicon-wrench { &:before { content: "\e136"; } } +.glyphicon-tasks { &:before { content: "\e137"; } } +.glyphicon-filter { &:before { content: "\e138"; } } +.glyphicon-briefcase { &:before { content: "\e139"; } } +.glyphicon-fullscreen { &:before { content: "\e140"; } } +.glyphicon-dashboard { &:before { content: "\e141"; } } +.glyphicon-paperclip { &:before { content: "\e142"; } } +.glyphicon-heart-empty { &:before { content: "\e143"; } } +.glyphicon-link { &:before { content: "\e144"; } } +.glyphicon-phone { &:before { content: "\e145"; } } +.glyphicon-pushpin { &:before { content: "\e146"; } } +.glyphicon-usd { &:before { content: "\e148"; } } +.glyphicon-gbp { &:before { content: "\e149"; } } +.glyphicon-sort { &:before { content: "\e150"; } } +.glyphicon-sort-by-alphabet { &:before { content: "\e151"; } } +.glyphicon-sort-by-alphabet-alt { &:before { content: "\e152"; } } +.glyphicon-sort-by-order { &:before { content: "\e153"; } } +.glyphicon-sort-by-order-alt { &:before { content: "\e154"; } } +.glyphicon-sort-by-attributes { &:before { content: "\e155"; } } +.glyphicon-sort-by-attributes-alt { &:before { content: "\e156"; } } +.glyphicon-unchecked { &:before { content: "\e157"; } } +.glyphicon-expand { &:before { content: "\e158"; } } +.glyphicon-collapse-down { &:before { content: "\e159"; } } +.glyphicon-collapse-up { &:before { content: "\e160"; } } +.glyphicon-log-in { &:before { content: "\e161"; } } +.glyphicon-flash { &:before { content: "\e162"; } } +.glyphicon-log-out { &:before { content: "\e163"; } } +.glyphicon-new-window { &:before { content: "\e164"; } } +.glyphicon-record { &:before { content: "\e165"; } } +.glyphicon-save { &:before { content: "\e166"; } } +.glyphicon-open { &:before { content: "\e167"; } } +.glyphicon-saved { &:before { content: "\e168"; } } +.glyphicon-import { &:before { content: "\e169"; } } +.glyphicon-export { &:before { content: "\e170"; } } +.glyphicon-send { &:before { content: "\e171"; } } +.glyphicon-floppy-disk { &:before { content: "\e172"; } } +.glyphicon-floppy-saved { &:before { content: "\e173"; } } +.glyphicon-floppy-remove { &:before { content: "\e174"; } } +.glyphicon-floppy-save { &:before { content: "\e175"; } } +.glyphicon-floppy-open { &:before { content: "\e176"; } } +.glyphicon-credit-card { &:before { content: "\e177"; } } +.glyphicon-transfer { &:before { content: "\e178"; } } +.glyphicon-cutlery { &:before { content: "\e179"; } } +.glyphicon-header { &:before { content: "\e180"; } } +.glyphicon-compressed { &:before { content: "\e181"; } } +.glyphicon-earphone { &:before { content: "\e182"; } } +.glyphicon-phone-alt { &:before { content: "\e183"; } } +.glyphicon-tower { &:before { content: "\e184"; } } +.glyphicon-stats { &:before { content: "\e185"; } } +.glyphicon-sd-video { &:before { content: "\e186"; } } +.glyphicon-hd-video { &:before { content: "\e187"; } } +.glyphicon-subtitles { &:before { content: "\e188"; } } +.glyphicon-sound-stereo { &:before { content: "\e189"; } } +.glyphicon-sound-dolby { &:before { content: "\e190"; } } +.glyphicon-sound-5-1 { &:before { content: "\e191"; } } +.glyphicon-sound-6-1 { &:before { content: "\e192"; } } +.glyphicon-sound-7-1 { &:before { content: "\e193"; } } +.glyphicon-copyright-mark { &:before { content: "\e194"; } } +.glyphicon-registration-mark { &:before { content: "\e195"; } } +.glyphicon-cloud-download { &:before { content: "\e197"; } } +.glyphicon-cloud-upload { &:before { content: "\e198"; } } +.glyphicon-tree-conifer { &:before { content: "\e199"; } } +.glyphicon-tree-deciduous { &:before { content: "\e200"; } } +.glyphicon-cd { &:before { content: "\e201"; } } +.glyphicon-save-file { &:before { content: "\e202"; } } +.glyphicon-open-file { &:before { content: "\e203"; } } +.glyphicon-level-up { &:before { content: "\e204"; } } +.glyphicon-copy { &:before { content: "\e205"; } } +.glyphicon-paste { &:before { content: "\e206"; } } +// The following 2 Glyphicons are omitted for the time being because +// they currently use Unicode codepoints that are outside the +// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle +// non-BMP codepoints in CSS string escapes, and thus can't display these two icons. +// Notably, the bug affects some older versions of the Android Browser. +// More info: https://github.com/twbs/bootstrap/issues/10106 +// .glyphicon-door { &:before { content: "\1f6aa"; } } +// .glyphicon-key { &:before { content: "\1f511"; } } +.glyphicon-alert { &:before { content: "\e209"; } } +.glyphicon-equalizer { &:before { content: "\e210"; } } +.glyphicon-king { &:before { content: "\e211"; } } +.glyphicon-queen { &:before { content: "\e212"; } } +.glyphicon-pawn { &:before { content: "\e213"; } } +.glyphicon-bishop { &:before { content: "\e214"; } } +.glyphicon-knight { &:before { content: "\e215"; } } +.glyphicon-baby-formula { &:before { content: "\e216"; } } +.glyphicon-tent { &:before { content: "\26fa"; } } +.glyphicon-blackboard { &:before { content: "\e218"; } } +.glyphicon-bed { &:before { content: "\e219"; } } +.glyphicon-apple { &:before { content: "\f8ff"; } } +.glyphicon-erase { &:before { content: "\e221"; } } +.glyphicon-hourglass { &:before { content: "\231b"; } } +.glyphicon-lamp { &:before { content: "\e223"; } } +.glyphicon-duplicate { &:before { content: "\e224"; } } +.glyphicon-piggy-bank { &:before { content: "\e225"; } } +.glyphicon-scissors { &:before { content: "\e226"; } } +.glyphicon-bitcoin { &:before { content: "\e227"; } } +.glyphicon-btc { &:before { content: "\e227"; } } +.glyphicon-xbt { &:before { content: "\e227"; } } +.glyphicon-yen { &:before { content: "\00a5"; } } +.glyphicon-jpy { &:before { content: "\00a5"; } } +.glyphicon-ruble { &:before { content: "\20bd"; } } +.glyphicon-rub { &:before { content: "\20bd"; } } +.glyphicon-scale { &:before { content: "\e230"; } } +.glyphicon-ice-lolly { &:before { content: "\e231"; } } +.glyphicon-ice-lolly-tasted { &:before { content: "\e232"; } } +.glyphicon-education { &:before { content: "\e233"; } } +.glyphicon-option-horizontal { &:before { content: "\e234"; } } +.glyphicon-option-vertical { &:before { content: "\e235"; } } +.glyphicon-menu-hamburger { &:before { content: "\e236"; } } +.glyphicon-modal-window { &:before { content: "\e237"; } } +.glyphicon-oil { &:before { content: "\e238"; } } +.glyphicon-grain { &:before { content: "\e239"; } } +.glyphicon-sunglasses { &:before { content: "\e240"; } } +.glyphicon-text-size { &:before { content: "\e241"; } } +.glyphicon-text-color { &:before { content: "\e242"; } } +.glyphicon-text-background { &:before { content: "\e243"; } } +.glyphicon-object-align-top { &:before { content: "\e244"; } } +.glyphicon-object-align-bottom { &:before { content: "\e245"; } } +.glyphicon-object-align-horizontal{ &:before { content: "\e246"; } } +.glyphicon-object-align-left { &:before { content: "\e247"; } } +.glyphicon-object-align-vertical { &:before { content: "\e248"; } } +.glyphicon-object-align-right { &:before { content: "\e249"; } } +.glyphicon-triangle-right { &:before { content: "\e250"; } } +.glyphicon-triangle-left { &:before { content: "\e251"; } } +.glyphicon-triangle-bottom { &:before { content: "\e252"; } } +.glyphicon-triangle-top { &:before { content: "\e253"; } } +.glyphicon-console { &:before { content: "\e254"; } } +.glyphicon-superscript { &:before { content: "\e255"; } } +.glyphicon-subscript { &:before { content: "\e256"; } } +.glyphicon-menu-left { &:before { content: "\e257"; } } +.glyphicon-menu-right { &:before { content: "\e258"; } } +.glyphicon-menu-down { &:before { content: "\e259"; } } +.glyphicon-menu-up { &:before { content: "\e260"; } } diff --git a/libs/fmt/doc/bootstrap/grid.less b/libs/fmt/doc/bootstrap/grid.less new file mode 100644 index 0000000000000000000000000000000000000000..2e61c942611cbfcdb79293a3fe86e6e938db0e66 --- /dev/null +++ b/libs/fmt/doc/bootstrap/grid.less @@ -0,0 +1,84 @@ +// +// Grid system +// -------------------------------------------------- + + +// Container widths +// +// Set the container width, and override it for fixed navbars in media queries. + +.tb-container { + .container-fixed(); + + @media (min-width: @screen-sm-min) { + width: @container-sm; + } + @media (min-width: @screen-md-min) { + width: @container-md; + } + @media (min-width: @screen-lg-min) { + width: @container-lg; + } +} + + +// Fluid container +// +// Utilizes the mixin meant for fixed width containers, but without any defined +// width for fluid, full width layouts. + +.container-fluid { + .container-fixed(); +} + + +// Row +// +// Rows contain and clear the floats of your columns. + +.row { + .make-row(); +} + + +// Columns +// +// Common styles for small and large grid columns + +.make-grid-columns(); + + +// Extra small grid +// +// Columns, offsets, pushes, and pulls for extra small devices like +// smartphones. + +.make-grid(xs); + + +// Small grid +// +// Columns, offsets, pushes, and pulls for the small device range, from phones +// to tablets. + +@media (min-width: @screen-sm-min) { + .make-grid(sm); +} + + +// Medium grid +// +// Columns, offsets, pushes, and pulls for the desktop device range. + +@media (min-width: @screen-md-min) { + .make-grid(md); +} + + +// Large grid +// +// Columns, offsets, pushes, and pulls for the large desktop device range. + +@media (min-width: @screen-lg-min) { + .make-grid(lg); +} diff --git a/libs/fmt/doc/bootstrap/input-groups.less b/libs/fmt/doc/bootstrap/input-groups.less new file mode 100644 index 0000000000000000000000000000000000000000..a8712f25b935f6d13e3d7ea9471d354a7bccd4df --- /dev/null +++ b/libs/fmt/doc/bootstrap/input-groups.less @@ -0,0 +1,166 @@ +// +// Input groups +// -------------------------------------------------- + +// Base styles +// ------------------------- +.input-group { + position: relative; // For dropdowns + display: table; + border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table + + // Undo padding and float of grid classes + &[class*="col-"] { + float: none; + padding-left: 0; + padding-right: 0; + } + + .form-control { + // Ensure that the input is always above the *appended* addon button for + // proper border colors. + position: relative; + z-index: 2; + + // IE9 fubars the placeholder attribute in text inputs and the arrows on + // select elements in input groups. To fix it, we float the input. Details: + // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855 + float: left; + + width: 100%; + margin-bottom: 0; + } +} + +// Sizing options +// +// Remix the default form control sizing classes into new ones for easier +// manipulation. + +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + .input-lg(); +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + .input-sm(); +} + + +// Display as table-cell +// ------------------------- +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; + + &:not(:first-child):not(:last-child) { + border-radius: 0; + } +} +// Addon and addon wrapper for buttons +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; // Match the inputs +} + +// Text input groups +// ------------------------- +.input-group-addon { + padding: @padding-base-vertical @padding-base-horizontal; + font-size: @font-size-base; + font-weight: normal; + line-height: 1; + color: @input-color; + text-align: center; + background-color: @input-group-addon-bg; + border: 1px solid @input-group-addon-border-color; + border-radius: @border-radius-base; + + // Sizing + &.input-sm { + padding: @padding-small-vertical @padding-small-horizontal; + font-size: @font-size-small; + border-radius: @border-radius-small; + } + &.input-lg { + padding: @padding-large-vertical @padding-large-horizontal; + font-size: @font-size-large; + border-radius: @border-radius-large; + } + + // Nuke default margins from checkboxes and radios to vertically center within. + input[type="radio"], + input[type="checkbox"] { + margin-top: 0; + } +} + +// Reset rounded corners +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + .border-right-radius(0); +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + .border-left-radius(0); +} +.input-group-addon:last-child { + border-left: 0; +} + +// Button input groups +// ------------------------- +.input-group-btn { + position: relative; + // Jankily prevent input button groups from wrapping with `white-space` and + // `font-size` in combination with `inline-block` on buttons. + font-size: 0; + white-space: nowrap; + + // Negative margin for spacing, position for bringing hovered/focused/actived + // element above the siblings. + > .btn { + position: relative; + + .btn { + margin-left: -1px; + } + // Bring the "active" button to the front + &:hover, + &:focus, + &:active { + z-index: 2; + } + } + + // Negative margin to only have a 1px border between the two + &:first-child { + > .btn, + > .btn-group { + margin-right: -1px; + } + } + &:last-child { + > .btn, + > .btn-group { + margin-left: -1px; + } + } +} diff --git a/libs/fmt/doc/bootstrap/jumbotron.less b/libs/fmt/doc/bootstrap/jumbotron.less new file mode 100644 index 0000000000000000000000000000000000000000..909580f9810298dfd9922f1c24f1422a0f51f0b2 --- /dev/null +++ b/libs/fmt/doc/bootstrap/jumbotron.less @@ -0,0 +1,50 @@ +// +// Jumbotron +// -------------------------------------------------- + + +.jumbotron { + padding: @jumbotron-padding (@jumbotron-padding / 2); + margin-bottom: @jumbotron-padding; + color: @jumbotron-color; + background-color: @jumbotron-bg; + + h1, + .h1 { + color: @jumbotron-heading-color; + } + + p { + margin-bottom: (@jumbotron-padding / 2); + font-size: @jumbotron-font-size; + font-weight: 200; + } + + > hr { + border-top-color: darken(@jumbotron-bg, 10%); + } + + .tb-container &, + .container-fluid & { + border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container + } + + .tb-container { + max-width: 100%; + } + + @media screen and (min-width: @screen-sm-min) { + padding: (@jumbotron-padding * 1.6) 0; + + .tb-container &, + .container-fluid & { + padding-left: (@jumbotron-padding * 2); + padding-right: (@jumbotron-padding * 2); + } + + h1, + .h1 { + font-size: (@font-size-base * 4.5); + } + } +} diff --git a/libs/fmt/doc/bootstrap/labels.less b/libs/fmt/doc/bootstrap/labels.less new file mode 100644 index 0000000000000000000000000000000000000000..9a5a27006a51f4ac0d350cf11060aa548cd6ef6b --- /dev/null +++ b/libs/fmt/doc/bootstrap/labels.less @@ -0,0 +1,64 @@ +// +// Labels +// -------------------------------------------------- + +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: @label-color; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; + + // Add hover effects, but only for links + a& { + &:hover, + &:focus { + color: @label-link-hover-color; + text-decoration: none; + cursor: pointer; + } + } + + // Empty labels collapse automatically (not available in IE8) + &:empty { + display: none; + } + + // Quick fix for labels in buttons + .btn & { + position: relative; + top: -1px; + } +} + +// Colors +// Contextual variations (linked labels get darker on :hover) + +.label-default { + .label-variant(@label-default-bg); +} + +.label-primary { + .label-variant(@label-primary-bg); +} + +.label-success { + .label-variant(@label-success-bg); +} + +.label-info { + .label-variant(@label-info-bg); +} + +.label-warning { + .label-variant(@label-warning-bg); +} + +.label-danger { + .label-variant(@label-danger-bg); +} diff --git a/libs/fmt/doc/bootstrap/list-group.less b/libs/fmt/doc/bootstrap/list-group.less new file mode 100644 index 0000000000000000000000000000000000000000..1462ce16b325082f98f78d8e28535fe96e86f1da --- /dev/null +++ b/libs/fmt/doc/bootstrap/list-group.less @@ -0,0 +1,124 @@ +// +// List groups +// -------------------------------------------------- + + +// Base class +// +// Easily usable on <ul>, <ol>, or <div>. + +.list-group { + // No need to set list-style: none; since .list-group-item is block level + margin-bottom: 20px; + padding-left: 0; // reset padding because ul and ol +} + + +// Individual list items +// +// Use on `li`s or `div`s within the `.list-group` parent. + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + // Place the border on the list items and negative margin up for better styling + margin-bottom: -1px; + background-color: @list-group-bg; + border: 1px solid @list-group-border; + + // Round the first and last items + &:first-child { + .border-top-radius(@list-group-border-radius); + } + &:last-child { + margin-bottom: 0; + .border-bottom-radius(@list-group-border-radius); + } +} + + +// Linked list items +// +// Use anchor elements instead of `li`s or `div`s to create linked list items. +// Includes an extra `.active` modifier class for showing selected items. + +a.list-group-item { + color: @list-group-link-color; + + .list-group-item-heading { + color: @list-group-link-heading-color; + } + + // Hover state + &:hover, + &:focus { + text-decoration: none; + color: @list-group-link-hover-color; + background-color: @list-group-hover-bg; + } +} + +.list-group-item { + // Disabled state + &.disabled, + &.disabled:hover, + &.disabled:focus { + background-color: @list-group-disabled-bg; + color: @list-group-disabled-color; + cursor: @cursor-disabled; + + // Force color to inherit for custom content + .list-group-item-heading { + color: inherit; + } + .list-group-item-text { + color: @list-group-disabled-text-color; + } + } + + // Active class on item itself, not parent + &.active, + &.active:hover, + &.active:focus { + z-index: 2; // Place active items above their siblings for proper border styling + color: @list-group-active-color; + background-color: @list-group-active-bg; + border-color: @list-group-active-border; + + // Force color to inherit for custom content + .list-group-item-heading, + .list-group-item-heading > small, + .list-group-item-heading > .small { + color: inherit; + } + .list-group-item-text { + color: @list-group-active-text-color; + } + } +} + + +// Contextual variants +// +// Add modifier classes to change text and background color on individual items. +// Organizationally, this must come after the `:hover` states. + +.list-group-item-variant(success; @state-success-bg; @state-success-text); +.list-group-item-variant(info; @state-info-bg; @state-info-text); +.list-group-item-variant(warning; @state-warning-bg; @state-warning-text); +.list-group-item-variant(danger; @state-danger-bg; @state-danger-text); + + +// Custom content options +// +// Extra classes for creating well-formatted content within `.list-group-item`s. + +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} diff --git a/libs/fmt/doc/bootstrap/media.less b/libs/fmt/doc/bootstrap/media.less new file mode 100644 index 0000000000000000000000000000000000000000..a6bf78192469c48166b7ddcd494ba2cbec376233 --- /dev/null +++ b/libs/fmt/doc/bootstrap/media.less @@ -0,0 +1,61 @@ +.media { + // Proper spacing between instances of .media + margin-top: 15px; + + &:first-child { + margin-top: 0; + } +} + +.media, +.media-body { + zoom: 1; + overflow: hidden; +} + +.media-body { + width: 10000px; +} + +.media-object { + display: block; +} + +.media-right, +.media > .pull-right { + padding-left: 10px; +} + +.media-left, +.media > .pull-left { + padding-right: 10px; +} + +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} + +.media-middle { + vertical-align: middle; +} + +.media-bottom { + vertical-align: bottom; +} + +// Reset margins on headings for tighter default spacing +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} + +// Media list variation +// +// Undo default ul/ol styles +.media-list { + padding-left: 0; + list-style: none; +} diff --git a/libs/fmt/doc/bootstrap/mixins.less b/libs/fmt/doc/bootstrap/mixins.less new file mode 100644 index 0000000000000000000000000000000000000000..af4408fc2d6846b5656d9d0d12f2f4cbf4404cf6 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins.less @@ -0,0 +1,39 @@ +// Mixins +// -------------------------------------------------- + +// Utilities +@import "mixins/hide-text.less"; +@import "mixins/opacity.less"; +@import "mixins/image.less"; +@import "mixins/labels.less"; +@import "mixins/reset-filter.less"; +@import "mixins/resize.less"; +@import "mixins/responsive-visibility.less"; +@import "mixins/size.less"; +@import "mixins/tab-focus.less"; +@import "mixins/text-emphasis.less"; +@import "mixins/text-overflow.less"; +@import "mixins/vendor-prefixes.less"; + +// Components +@import "mixins/alerts.less"; +@import "mixins/buttons.less"; +@import "mixins/panels.less"; +@import "mixins/pagination.less"; +@import "mixins/list-group.less"; +@import "mixins/nav-divider.less"; +@import "mixins/forms.less"; +@import "mixins/progress-bar.less"; +@import "mixins/table-row.less"; + +// Skins +@import "mixins/background-variant.less"; +@import "mixins/border-radius.less"; +@import "mixins/gradients.less"; + +// Layout +@import "mixins/clearfix.less"; +@import "mixins/center-block.less"; +@import "mixins/nav-vertical-align.less"; +@import "mixins/grid-framework.less"; +@import "mixins/grid.less"; diff --git a/libs/fmt/doc/bootstrap/mixins/alerts.less b/libs/fmt/doc/bootstrap/mixins/alerts.less new file mode 100644 index 0000000000000000000000000000000000000000..396196f438fd31e7757a0874ece4a99fe0cf9fd9 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/alerts.less @@ -0,0 +1,14 @@ +// Alerts + +.alert-variant(@background; @border; @text-color) { + background-color: @background; + border-color: @border; + color: @text-color; + + hr { + border-top-color: darken(@border, 5%); + } + .alert-link { + color: darken(@text-color, 10%); + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/background-variant.less b/libs/fmt/doc/bootstrap/mixins/background-variant.less new file mode 100644 index 0000000000000000000000000000000000000000..556e490d45d004c4e4504153133c5c1050207b65 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/background-variant.less @@ -0,0 +1,8 @@ +// Contextual backgrounds + +.bg-variant(@color) { + background-color: @color; + a&:hover { + background-color: darken(@color, 10%); + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/border-radius.less b/libs/fmt/doc/bootstrap/mixins/border-radius.less new file mode 100644 index 0000000000000000000000000000000000000000..ca05dbf457000d122a3b8afcc3847e44cffea421 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/border-radius.less @@ -0,0 +1,18 @@ +// Single side border-radius + +.border-top-radius(@radius) { + border-top-right-radius: @radius; + border-top-left-radius: @radius; +} +.border-right-radius(@radius) { + border-bottom-right-radius: @radius; + border-top-right-radius: @radius; +} +.border-bottom-radius(@radius) { + border-bottom-right-radius: @radius; + border-bottom-left-radius: @radius; +} +.border-left-radius(@radius) { + border-bottom-left-radius: @radius; + border-top-left-radius: @radius; +} diff --git a/libs/fmt/doc/bootstrap/mixins/buttons.less b/libs/fmt/doc/bootstrap/mixins/buttons.less new file mode 100644 index 0000000000000000000000000000000000000000..92d8a056cd3b73c82937b0c9d478a86baa6d142a --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/buttons.less @@ -0,0 +1,52 @@ +// Button variants +// +// Easily pump out default styles, as well as :hover, :focus, :active, +// and disabled options for all buttons + +.button-variant(@color; @background; @border) { + color: @color; + background-color: @background; + border-color: @border; + + &:hover, + &:focus, + &.focus, + &:active, + &.active, + .open > .dropdown-toggle& { + color: @color; + background-color: darken(@background, 10%); + border-color: darken(@border, 12%); + } + &:active, + &.active, + .open > .dropdown-toggle& { + background-image: none; + } + &.disabled, + &[disabled], + fieldset[disabled] & { + &, + &:hover, + &:focus, + &.focus, + &:active, + &.active { + background-color: @background; + border-color: @border; + } + } + + .badge { + color: @background; + background-color: @color; + } +} + +// Button sizes +.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + line-height: @line-height; + border-radius: @border-radius; +} diff --git a/libs/fmt/doc/bootstrap/mixins/center-block.less b/libs/fmt/doc/bootstrap/mixins/center-block.less new file mode 100644 index 0000000000000000000000000000000000000000..d18d6de9ed6a7af49415277cb72e57b7dbd42d0f --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/center-block.less @@ -0,0 +1,7 @@ +// Center-align a block level element + +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} diff --git a/libs/fmt/doc/bootstrap/mixins/clearfix.less b/libs/fmt/doc/bootstrap/mixins/clearfix.less new file mode 100644 index 0000000000000000000000000000000000000000..3f7a3820c1cf7756322917982646352e77119f46 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/clearfix.less @@ -0,0 +1,22 @@ +// Clearfix +// +// For modern browsers +// 1. The space content is one way to avoid an Opera bug when the +// contenteditable attribute is included anywhere else in the document. +// Otherwise it causes space to appear at the top and bottom of elements +// that are clearfixed. +// 2. The use of `table` rather than `block` is only necessary if using +// `:before` to contain the top-margins of child elements. +// +// Source: http://nicolasgallagher.com/micro-clearfix-hack/ + +.clearfix() { + &:before, + &:after { + content: " "; // 1 + display: table; // 2 + } + &:after { + clear: both; + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/forms.less b/libs/fmt/doc/bootstrap/mixins/forms.less new file mode 100644 index 0000000000000000000000000000000000000000..6f55ed967082caf4ef515f88c9d591f95036a470 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/forms.less @@ -0,0 +1,85 @@ +// Form validation states +// +// Used in forms.less to generate the form validation CSS for warnings, errors, +// and successes. + +.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) { + // Color the label and help text + .help-block, + .control-label, + .radio, + .checkbox, + .radio-inline, + .checkbox-inline, + &.radio label, + &.checkbox label, + &.radio-inline label, + &.checkbox-inline label { + color: @text-color; + } + // Set the border and box shadow on specific inputs to match + .form-control { + border-color: @border-color; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken(@border-color, 10%); + @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%); + .box-shadow(@shadow); + } + } + // Set validation states also for addons + .input-group-addon { + color: @text-color; + border-color: @border-color; + background-color: @background-color; + } + // Optional feedback icon + .form-control-feedback { + color: @text-color; + } +} + + +// Form control focus state +// +// Generate a customized focus state and for any input with the specified color, +// which defaults to the `@input-border-focus` variable. +// +// We highly encourage you to not customize the default value, but instead use +// this to tweak colors on an as-needed basis. This aesthetic change is based on +// WebKit's default styles, but applicable to a wider range of browsers. Its +// usability and accessibility should be taken into account with any change. +// +// Example usage: change the default blue border and shadow to white for better +// contrast against a dark gray background. +.form-control-focus(@color: @input-border-focus) { + @color-rgba: rgba(red(@color), green(@color), blue(@color), .6); + &:focus { + border-color: @color; + outline: 0; + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}"); + } +} + +// Form control sizing +// +// Relative text size, padding, and border-radii changes for form controls. For +// horizontal sizing, wrap controls in the predefined grid classes. `<select>` +// element gets special love because it's special, and that's a fact! +.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { + height: @input-height; + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + line-height: @line-height; + border-radius: @border-radius; + + select& { + height: @input-height; + line-height: @input-height; + } + + textarea&, + select[multiple]& { + height: auto; + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/gradients.less b/libs/fmt/doc/bootstrap/mixins/gradients.less new file mode 100644 index 0000000000000000000000000000000000000000..0b88a89cc56a63effe45558845122b077de03979 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/gradients.less @@ -0,0 +1,59 @@ +// Gradients + +#gradient { + + // Horizontal gradient, from left to right + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12 + background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + // Vertical gradient, from top to bottom + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12 + background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + .directional(@start-color: #555; @end-color: #333; @deg: 45deg) { + background-repeat: repeat-x; + background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12 + background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + } + .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); + background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@inner-color: #555; @outer-color: #333) { + background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color); + background-image: radial-gradient(circle, @inner-color, @outer-color); + background-repeat: no-repeat; + } + .striped(@color: rgba(255,255,255,.15); @angle: 45deg) { + background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/grid-framework.less b/libs/fmt/doc/bootstrap/mixins/grid-framework.less new file mode 100644 index 0000000000000000000000000000000000000000..f3b3929d6d0e55b9fffe1c2dbde5747b21eb0014 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/grid-framework.less @@ -0,0 +1,91 @@ +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `@grid-columns`. + +.make-grid-columns() { + // Common styles for all sizes of grid columns, widths 1-12 + .col(@index) { // initial + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col((@index + 1), @item); + } + .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col((@index + 1), ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index > @grid-columns) { // terminal + @{list} { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: (@grid-gutter-width / 2); + padding-right: (@grid-gutter-width / 2); + } + } + .col(1); // kickstart it +} + +.float-grid-columns(@class) { + .col(@index) { // initial + @item: ~".col-@{class}-@{index}"; + .col((@index + 1), @item); + } + .col(@index, @list) when (@index =< @grid-columns) { // general + @item: ~".col-@{class}-@{index}"; + .col((@index + 1), ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index > @grid-columns) { // terminal + @{list} { + float: left; + } + } + .col(1); // kickstart it +} + +.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) { + .col-@{class}-@{index} { + width: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) { + .col-@{class}-push-@{index} { + left: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) { + .col-@{class}-push-0 { + left: auto; + } +} +.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) { + .col-@{class}-pull-@{index} { + right: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) { + .col-@{class}-pull-0 { + right: auto; + } +} +.calc-grid-column(@index, @class, @type) when (@type = offset) { + .col-@{class}-offset-@{index} { + margin-left: percentage((@index / @grid-columns)); + } +} + +// Basic looping in LESS +.loop-grid-columns(@index, @class, @type) when (@index >= 0) { + .calc-grid-column(@index, @class, @type); + // next iteration + .loop-grid-columns((@index - 1), @class, @type); +} + +// Create grid for specific class +.make-grid(@class) { + .float-grid-columns(@class); + .loop-grid-columns(@grid-columns, @class, width); + .loop-grid-columns(@grid-columns, @class, pull); + .loop-grid-columns(@grid-columns, @class, push); + .loop-grid-columns(@grid-columns, @class, offset); +} diff --git a/libs/fmt/doc/bootstrap/mixins/grid.less b/libs/fmt/doc/bootstrap/mixins/grid.less new file mode 100644 index 0000000000000000000000000000000000000000..cae5eaff924fed1e4d5978952d9c8490faf8500c --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/grid.less @@ -0,0 +1,122 @@ +// Grid system +// +// Generate semantic grid columns with these mixins. + +// Centered container element +.container-fixed(@gutter: @grid-gutter-width) { + margin-right: auto; + margin-left: auto; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + &:extend(.clearfix all); +} + +// Creates a wrapper for a series of columns +.make-row(@gutter: @grid-gutter-width) { + margin-left: (@gutter / -2); + margin-right: (@gutter / -2); + &:extend(.clearfix all); +} + +// Generate the extra small columns +.make-xs-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + float: left; + width: percentage((@columns / @grid-columns)); + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); +} +.make-xs-column-offset(@columns) { + margin-left: percentage((@columns / @grid-columns)); +} +.make-xs-column-push(@columns) { + left: percentage((@columns / @grid-columns)); +} +.make-xs-column-pull(@columns) { + right: percentage((@columns / @grid-columns)); +} + +// Generate the small columns +.make-sm-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-sm-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-offset(@columns) { + @media (min-width: @screen-sm-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-push(@columns) { + @media (min-width: @screen-sm-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-pull(@columns) { + @media (min-width: @screen-sm-min) { + right: percentage((@columns / @grid-columns)); + } +} + +// Generate the medium columns +.make-md-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-md-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-md-column-offset(@columns) { + @media (min-width: @screen-md-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-push(@columns) { + @media (min-width: @screen-md-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-pull(@columns) { + @media (min-width: @screen-md-min) { + right: percentage((@columns / @grid-columns)); + } +} + +// Generate the large columns +.make-lg-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-lg-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-offset(@columns) { + @media (min-width: @screen-lg-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-push(@columns) { + @media (min-width: @screen-lg-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-pull(@columns) { + @media (min-width: @screen-lg-min) { + right: percentage((@columns / @grid-columns)); + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/hide-text.less b/libs/fmt/doc/bootstrap/mixins/hide-text.less new file mode 100644 index 0000000000000000000000000000000000000000..c2315e572f83385f10407cae46253526e0bb3dd7 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/hide-text.less @@ -0,0 +1,21 @@ +// CSS image replacement +// +// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for +// mixins being reused as classes with the same name, this doesn't hold up. As +// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. +// +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 + +// Deprecated as of v3.0.1 (will be removed in v4) +.hide-text() { + font: ~"0/0" a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +// New mixin to use as of v3.0.1 +.text-hide() { + .hide-text(); +} diff --git a/libs/fmt/doc/bootstrap/mixins/image.less b/libs/fmt/doc/bootstrap/mixins/image.less new file mode 100644 index 0000000000000000000000000000000000000000..f233cb3e199f21ac194212e10ebb1e12feb0682b --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/image.less @@ -0,0 +1,33 @@ +// Image Mixins +// - Responsive image +// - Retina image + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. +.img-responsive(@display: block) { + display: @display; + max-width: 100%; // Part 1: Set a maximum relative to the parent + height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching +} + + +// Retina image +// +// Short retina mixin for setting background-image and -size. Note that the +// spelling of `min--moz-device-pixel-ratio` is intentional. +.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) { + background-image: url("@{file-1x}"); + + @media + only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and ( min--moz-device-pixel-ratio: 2), + only screen and ( -o-min-device-pixel-ratio: 2/1), + only screen and ( min-device-pixel-ratio: 2), + only screen and ( min-resolution: 192dpi), + only screen and ( min-resolution: 2dppx) { + background-image: url("@{file-2x}"); + background-size: @width-1x @height-1x; + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/labels.less b/libs/fmt/doc/bootstrap/mixins/labels.less new file mode 100644 index 0000000000000000000000000000000000000000..9f7a67ee3d08a03ad10277d703805d315cd43d54 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/labels.less @@ -0,0 +1,12 @@ +// Labels + +.label-variant(@color) { + background-color: @color; + + &[href] { + &:hover, + &:focus { + background-color: darken(@color, 10%); + } + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/list-group.less b/libs/fmt/doc/bootstrap/mixins/list-group.less new file mode 100644 index 0000000000000000000000000000000000000000..8b5b065cb84cec3364d028e046b3fd24878f20b1 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/list-group.less @@ -0,0 +1,29 @@ +// List Groups + +.list-group-item-variant(@state; @background; @color) { + .list-group-item-@{state} { + color: @color; + background-color: @background; + + a& { + color: @color; + + .list-group-item-heading { + color: inherit; + } + + &:hover, + &:focus { + color: @color; + background-color: darken(@background, 5%); + } + &.active, + &.active:hover, + &.active:focus { + color: #fff; + background-color: @color; + border-color: @color; + } + } + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/nav-divider.less b/libs/fmt/doc/bootstrap/mixins/nav-divider.less new file mode 100644 index 0000000000000000000000000000000000000000..feb1e9ed0dad8d0db00ebb93b175591a88abf707 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/nav-divider.less @@ -0,0 +1,10 @@ +// Horizontal dividers +// +// Dividers (basically an hr) within dropdowns and nav lists + +.nav-divider(@color: #e5e5e5) { + height: 1px; + margin: ((@line-height-computed / 2) - 1) 0; + overflow: hidden; + background-color: @color; +} diff --git a/libs/fmt/doc/bootstrap/mixins/nav-vertical-align.less b/libs/fmt/doc/bootstrap/mixins/nav-vertical-align.less new file mode 100644 index 0000000000000000000000000000000000000000..d458c78613e6dc082141783ef45d56a5a7d601c3 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/nav-vertical-align.less @@ -0,0 +1,9 @@ +// Navbar vertical align +// +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. + +.navbar-vertical-align(@element-height) { + margin-top: ((@navbar-height - @element-height) / 2); + margin-bottom: ((@navbar-height - @element-height) / 2); +} diff --git a/libs/fmt/doc/bootstrap/mixins/opacity.less b/libs/fmt/doc/bootstrap/mixins/opacity.less new file mode 100644 index 0000000000000000000000000000000000000000..33ed25ce6763de7f8ae195628c1eb168379d8435 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/opacity.less @@ -0,0 +1,8 @@ +// Opacity + +.opacity(@opacity) { + opacity: @opacity; + // IE8 filter + @opacity-ie: (@opacity * 100); + filter: ~"alpha(opacity=@{opacity-ie})"; +} diff --git a/libs/fmt/doc/bootstrap/mixins/pagination.less b/libs/fmt/doc/bootstrap/mixins/pagination.less new file mode 100644 index 0000000000000000000000000000000000000000..7deb505d25f09cfc939beff303c0f6d52e83a9a2 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/pagination.less @@ -0,0 +1,23 @@ +// Pagination + +.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) { + > li { + > a, + > span { + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + } + &:first-child { + > a, + > span { + .border-left-radius(@border-radius); + } + } + &:last-child { + > a, + > span { + .border-right-radius(@border-radius); + } + } + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/panels.less b/libs/fmt/doc/bootstrap/mixins/panels.less new file mode 100644 index 0000000000000000000000000000000000000000..49ee10d4ad39de45e0983d01cbbc50949a1dac2e --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/panels.less @@ -0,0 +1,24 @@ +// Panels + +.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) { + border-color: @border; + + & > .panel-heading { + color: @heading-text-color; + background-color: @heading-bg-color; + border-color: @heading-border; + + + .panel-collapse > .panel-body { + border-top-color: @border; + } + .badge { + color: @heading-bg-color; + background-color: @heading-text-color; + } + } + & > .panel-footer { + + .panel-collapse > .panel-body { + border-bottom-color: @border; + } + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/progress-bar.less b/libs/fmt/doc/bootstrap/mixins/progress-bar.less new file mode 100644 index 0000000000000000000000000000000000000000..f07996a34dbc19a1acc1c024a2410eb2b1b25271 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/progress-bar.less @@ -0,0 +1,10 @@ +// Progress bars + +.progress-bar-variant(@color) { + background-color: @color; + + // Deprecated parent class requirement as of v3.2.0 + .progress-striped & { + #gradient > .striped(); + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/reset-filter.less b/libs/fmt/doc/bootstrap/mixins/reset-filter.less new file mode 100644 index 0000000000000000000000000000000000000000..68cdb5e18602af3ab8e3ddba57a06045ef1c65bb --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/reset-filter.less @@ -0,0 +1,8 @@ +// Reset filters for IE +// +// When you need to remove a gradient background, do not forget to use this to reset +// the IE filter for IE9 and below. + +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} diff --git a/libs/fmt/doc/bootstrap/mixins/resize.less b/libs/fmt/doc/bootstrap/mixins/resize.less new file mode 100644 index 0000000000000000000000000000000000000000..3acd3afdbacbb7f642e04f7ff434c18107faad69 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/resize.less @@ -0,0 +1,6 @@ +// Resize anything + +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` +} diff --git a/libs/fmt/doc/bootstrap/mixins/responsive-visibility.less b/libs/fmt/doc/bootstrap/mixins/responsive-visibility.less new file mode 100644 index 0000000000000000000000000000000000000000..f7951c3d75cbe6bb72ea5f7493920611a2800797 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/responsive-visibility.less @@ -0,0 +1,15 @@ +// Responsive utilities + +// +// More easily include all the states for responsive-utilities.less. +.responsive-visibility() { + display: block !important; + table& { display: table; } + tr& { display: table-row !important; } + th&, + td& { display: table-cell !important; } +} + +.responsive-invisibility() { + display: none !important; +} diff --git a/libs/fmt/doc/bootstrap/mixins/size.less b/libs/fmt/doc/bootstrap/mixins/size.less new file mode 100644 index 0000000000000000000000000000000000000000..a8be65089606863ad878173e951b62fb387daf52 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/size.less @@ -0,0 +1,10 @@ +// Sizing shortcuts + +.size(@width; @height) { + width: @width; + height: @height; +} + +.square(@size) { + .size(@size; @size); +} diff --git a/libs/fmt/doc/bootstrap/mixins/tab-focus.less b/libs/fmt/doc/bootstrap/mixins/tab-focus.less new file mode 100644 index 0000000000000000000000000000000000000000..1f1f05ab054412684539a94423c097d6cdadd8ba --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/tab-focus.less @@ -0,0 +1,9 @@ +// WebKit-style focus + +.tab-focus() { + // Default + outline: thin dotted; + // WebKit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} diff --git a/libs/fmt/doc/bootstrap/mixins/table-row.less b/libs/fmt/doc/bootstrap/mixins/table-row.less new file mode 100644 index 0000000000000000000000000000000000000000..0f287f1a8bdf51607e91d0c8aa6a5be51d6df36e --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/table-row.less @@ -0,0 +1,28 @@ +// Tables + +.table-row-variant(@state; @background) { + // Exact selectors below required to override `.table-striped` and prevent + // inheritance to nested tables. + .table > thead > tr, + .table > tbody > tr, + .table > tfoot > tr { + > td.@{state}, + > th.@{state}, + &.@{state} > td, + &.@{state} > th { + background-color: @background; + } + } + + // Hover states for `.table-hover` + // Note: this is not available for cells or rows within `thead` or `tfoot`. + .table-hover > tbody > tr { + > td.@{state}:hover, + > th.@{state}:hover, + &.@{state}:hover > td, + &:hover > .@{state}, + &.@{state}:hover > th { + background-color: darken(@background, 5%); + } + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/text-emphasis.less b/libs/fmt/doc/bootstrap/mixins/text-emphasis.less new file mode 100644 index 0000000000000000000000000000000000000000..0868ef9f2caaf05c2fdbe9f3f3da1616fe221080 --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/text-emphasis.less @@ -0,0 +1,8 @@ +// Typography + +.text-emphasis-variant(@color) { + color: @color; + a&:hover { + color: darken(@color, 10%); + } +} diff --git a/libs/fmt/doc/bootstrap/mixins/text-overflow.less b/libs/fmt/doc/bootstrap/mixins/text-overflow.less new file mode 100644 index 0000000000000000000000000000000000000000..c11ad2fb7471dd29ae781f1aefdfb8bb5e4a7d6c --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/text-overflow.less @@ -0,0 +1,8 @@ +// Text overflow +// Requires inline-block or block for proper styling + +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/libs/fmt/doc/bootstrap/mixins/vendor-prefixes.less b/libs/fmt/doc/bootstrap/mixins/vendor-prefixes.less new file mode 100644 index 0000000000000000000000000000000000000000..afd3331c31da856f226b354916e21ebad9d3311a --- /dev/null +++ b/libs/fmt/doc/bootstrap/mixins/vendor-prefixes.less @@ -0,0 +1,227 @@ +// Vendor Prefixes +// +// All vendor mixins are deprecated as of v3.2.0 due to the introduction of +// Autoprefixer in our Gruntfile. They will be removed in v4. + +// - Animations +// - Backface visibility +// - Box shadow +// - Box sizing +// - Content columns +// - Hyphens +// - Placeholder text +// - Transformations +// - Transitions +// - User Select + + +// Animations +.animation(@animation) { + -webkit-animation: @animation; + -o-animation: @animation; + animation: @animation; +} +.animation-name(@name) { + -webkit-animation-name: @name; + animation-name: @name; +} +.animation-duration(@duration) { + -webkit-animation-duration: @duration; + animation-duration: @duration; +} +.animation-timing-function(@timing-function) { + -webkit-animation-timing-function: @timing-function; + animation-timing-function: @timing-function; +} +.animation-delay(@delay) { + -webkit-animation-delay: @delay; + animation-delay: @delay; +} +.animation-iteration-count(@iteration-count) { + -webkit-animation-iteration-count: @iteration-count; + animation-iteration-count: @iteration-count; +} +.animation-direction(@direction) { + -webkit-animation-direction: @direction; + animation-direction: @direction; +} +.animation-fill-mode(@fill-mode) { + -webkit-animation-fill-mode: @fill-mode; + animation-fill-mode: @fill-mode; +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden` + +.backface-visibility(@visibility){ + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Drop shadows +// +// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's +// supported browsers that have box shadow capabilities now support it. + +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1 + box-shadow: @shadow; +} + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// CSS3 Content Columns +.content-columns(@column-count; @column-gap: @grid-gutter-width) { + -webkit-column-count: @column-count; + -moz-column-count: @column-count; + column-count: @column-count; + -webkit-column-gap: @column-gap; + -moz-column-gap: @column-gap; + column-gap: @column-gap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; // IE10+ + -o-hyphens: @mode; + hyphens: @mode; +} + +// Placeholder text +.placeholder(@color: @input-color-placeholder) { + // Firefox + &::-moz-placeholder { + color: @color; + opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526 + } + &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+ + &::-webkit-input-placeholder { color: @color; } // Safari and Chrome +} + +// Transformations +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -ms-transform: scale(@ratio); // IE9 only + -o-transform: scale(@ratio); + transform: scale(@ratio); +} +.scale(@ratioX; @ratioY) { + -webkit-transform: scale(@ratioX, @ratioY); + -ms-transform: scale(@ratioX, @ratioY); // IE9 only + -o-transform: scale(@ratioX, @ratioY); + transform: scale(@ratioX, @ratioY); +} +.scaleX(@ratio) { + -webkit-transform: scaleX(@ratio); + -ms-transform: scaleX(@ratio); // IE9 only + -o-transform: scaleX(@ratio); + transform: scaleX(@ratio); +} +.scaleY(@ratio) { + -webkit-transform: scaleY(@ratio); + -ms-transform: scaleY(@ratio); // IE9 only + -o-transform: scaleY(@ratio); + transform: scaleY(@ratio); +} +.skew(@x; @y) { + -webkit-transform: skewX(@x) skewY(@y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ + -o-transform: skewX(@x) skewY(@y); + transform: skewX(@x) skewY(@y); +} +.translate(@x; @y) { + -webkit-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); // IE9 only + -o-transform: translate(@x, @y); + transform: translate(@x, @y); +} +.translate3d(@x; @y; @z) { + -webkit-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); // IE9 only + -o-transform: rotate(@degrees); + transform: rotate(@degrees); +} +.rotateX(@degrees) { + -webkit-transform: rotateX(@degrees); + -ms-transform: rotateX(@degrees); // IE9 only + -o-transform: rotateX(@degrees); + transform: rotateX(@degrees); +} +.rotateY(@degrees) { + -webkit-transform: rotateY(@degrees); + -ms-transform: rotateY(@degrees); // IE9 only + -o-transform: rotateY(@degrees); + transform: rotateY(@degrees); +} +.perspective(@perspective) { + -webkit-perspective: @perspective; + -moz-perspective: @perspective; + perspective: @perspective; +} +.perspective-origin(@perspective) { + -webkit-perspective-origin: @perspective; + -moz-perspective-origin: @perspective; + perspective-origin: @perspective; +} +.transform-origin(@origin) { + -webkit-transform-origin: @origin; + -moz-transform-origin: @origin; + -ms-transform-origin: @origin; // IE9 only + transform-origin: @origin; +} + + +// Transitions + +.transition(@transition) { + -webkit-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +.transition-property(@transition-property) { + -webkit-transition-property: @transition-property; + transition-property: @transition-property; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} +.transition-duration(@transition-duration) { + -webkit-transition-duration: @transition-duration; + transition-duration: @transition-duration; +} +.transition-timing-function(@timing-function) { + -webkit-transition-timing-function: @timing-function; + transition-timing-function: @timing-function; +} +.transition-transform(@transition) { + -webkit-transition: -webkit-transform @transition; + -moz-transition: -moz-transform @transition; + -o-transition: -o-transform @transition; + transition: transform @transition; +} + + +// User select +// For selecting text on the page + +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; // IE10+ + user-select: @select; +} diff --git a/libs/fmt/doc/bootstrap/modals.less b/libs/fmt/doc/bootstrap/modals.less new file mode 100644 index 0000000000000000000000000000000000000000..1de622050e5ae83742ed1cfa93751c8a39377de2 --- /dev/null +++ b/libs/fmt/doc/bootstrap/modals.less @@ -0,0 +1,150 @@ +// +// Modals +// -------------------------------------------------- + +// .modal-open - body class for killing the scroll +// .modal - container to scroll within +// .modal-dialog - positioning shell for the actual modal +// .modal-content - actual modal w/ bg and corners and shit + +// Kill the scroll on the body +.modal-open { + overflow: hidden; +} + +// Container that the modal scrolls within +.modal { + display: none; + overflow: hidden; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindex-modal; + -webkit-overflow-scrolling: touch; + + // Prevent Chrome on Windows from adding a focus outline. For details, see + // https://github.com/twbs/bootstrap/pull/10951. + outline: 0; + + // When fading in the modal, animate it to slide down + &.fade .modal-dialog { + .translate(0, -25%); + .transition-transform(~"0.3s ease-out"); + } + &.in .modal-dialog { .translate(0, 0) } +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +// Shell div to position the modal with bottom padding +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} + +// Actual modal +.modal-content { + position: relative; + background-color: @modal-content-bg; + border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc) + border: 1px solid @modal-content-border-color; + border-radius: @border-radius-large; + .box-shadow(0 3px 9px rgba(0,0,0,.5)); + background-clip: padding-box; + // Remove focus outline from opened modal + outline: 0; +} + +// Modal background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindex-modal-background; + background-color: @modal-backdrop-bg; + // Fade for backdrop + &.fade { .opacity(0); } + &.in { .opacity(@modal-backdrop-opacity); } +} + +// Modal header +// Top section of the modal w/ title and dismiss +.modal-header { + padding: @modal-title-padding; + border-bottom: 1px solid @modal-header-border-color; + min-height: (@modal-title-padding + @modal-title-line-height); +} +// Close icon +.modal-header .close { + margin-top: -2px; +} + +// Title text within header +.modal-title { + margin: 0; + line-height: @modal-title-line-height; +} + +// Modal body +// Where all modal content resides (sibling of .modal-header and .modal-footer) +.modal-body { + position: relative; + padding: @modal-inner-padding; +} + +// Footer (for actions) +.modal-footer { + padding: @modal-inner-padding; + text-align: right; // right align buttons + border-top: 1px solid @modal-footer-border-color; + &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons + + // Properly space out buttons + .btn + .btn { + margin-left: 5px; + margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs + } + // but override that for button groups + .btn-group .btn + .btn { + margin-left: -1px; + } + // and override it for block buttons as well + .btn-block + .btn-block { + margin-left: 0; + } +} + +// Measure scrollbar width for padding body during modal show/hide +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +// Scale up the modal +@media (min-width: @screen-sm-min) { + // Automatically set modal's width for larger viewports + .modal-dialog { + width: @modal-md; + margin: 30px auto; + } + .modal-content { + .box-shadow(0 5px 15px rgba(0,0,0,.5)); + } + + // Modal sizes + .modal-sm { width: @modal-sm; } +} + +@media (min-width: @screen-md-min) { + .modal-lg { width: @modal-lg; } +} diff --git a/libs/fmt/doc/bootstrap/navbar.less b/libs/fmt/doc/bootstrap/navbar.less new file mode 100644 index 0000000000000000000000000000000000000000..03f4e2ac7a14bb0cce7e2a01d104ebcb2b0ff13b --- /dev/null +++ b/libs/fmt/doc/bootstrap/navbar.less @@ -0,0 +1,660 @@ +// +// Navbars +// -------------------------------------------------- + + +// Wrapper and base class +// +// Provide a static navbar from which we expand to create full-width, fixed, and +// other navbar variations. + +.navbar { + position: relative; + min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode) + margin-bottom: @navbar-margin-bottom; + border: 1px solid transparent; + + // Prevent floats from breaking the navbar + &:extend(.clearfix all); + + @media (min-width: @grid-float-breakpoint) { + border-radius: @navbar-border-radius; + } +} + + +// Navbar heading +// +// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy +// styling of responsive aspects. + +.navbar-header { + &:extend(.clearfix all); + + @media (min-width: @grid-float-breakpoint) { + float: left; + } +} + + +// Navbar collapse (body) +// +// Group your navbar content into this for easy collapsing and expanding across +// various device sizes. By default, this content is collapsed when <768px, but +// will expand past that for a horizontal display. +// +// To start (on mobile devices) the navbar links, forms, and buttons are stacked +// vertically and include a `max-height` to overflow in case you have too much +// content for the user's viewport. + +.navbar-collapse { + overflow-x: visible; + padding-right: @navbar-padding-horizontal; + padding-left: @navbar-padding-horizontal; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255,255,255,.1); + &:extend(.clearfix all); + -webkit-overflow-scrolling: touch; + + &.in { + overflow-y: auto; + } + + @media (min-width: @grid-float-breakpoint) { + width: auto; + border-top: 0; + box-shadow: none; + + &.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; // Override default setting + overflow: visible !important; + } + + &.in { + overflow-y: visible; + } + + // Undo the collapse side padding for navbars with containers to ensure + // alignment of right-aligned contents. + .navbar-fixed-top &, + .navbar-static-top &, + .navbar-fixed-bottom & { + padding-left: 0; + padding-right: 0; + } + } +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + .navbar-collapse { + max-height: @navbar-collapse-max-height; + + @media (max-device-width: @screen-xs-min) and (orientation: landscape) { + max-height: 200px; + } + } +} + + +// Both navbar header and collapse +// +// When a container is present, change the behavior of the header and collapse. + +.tb-container, +.container-fluid { + > .navbar-header, + > .navbar-collapse { + margin-right: -@navbar-padding-horizontal; + margin-left: -@navbar-padding-horizontal; + + @media (min-width: @grid-float-breakpoint) { + margin-right: 0; + margin-left: 0; + } + } +} + + +// +// Navbar alignment options +// +// Display the navbar across the entirety of the page or fixed it to the top or +// bottom of the page. + +// Static top (unfixed, but 100% wide) navbar +.navbar-static-top { + z-index: @zindex-navbar; + border-width: 0 0 1px; + + @media (min-width: @grid-float-breakpoint) { + border-radius: 0; + } +} + +// Fix the top/bottom navbars when screen real estate supports it +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: @zindex-navbar-fixed; + + // Undo the rounded corners + @media (min-width: @grid-float-breakpoint) { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; // override .navbar defaults + border-width: 1px 0 0; +} + + +// Brand/project name + +.navbar-brand { + float: left; + padding: @navbar-padding-vertical @navbar-padding-horizontal; + font-size: @font-size-large; + line-height: @line-height-computed; + height: @navbar-height; + + &:hover, + &:focus { + text-decoration: none; + } + + > img { + display: block; + } + + @media (min-width: @grid-float-breakpoint) { + .navbar > .tb-container &, + .navbar > .container-fluid & { + margin-left: -@navbar-padding-horizontal; + } + } +} + + +// Navbar toggle +// +// Custom button for toggling the `.navbar-collapse`, powered by the collapse +// JavaScript plugin. + +.navbar-toggle { + position: relative; + float: right; + margin-right: @navbar-padding-horizontal; + padding: 9px 10px; + .navbar-vertical-align(34px); + background-color: transparent; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + border-radius: @border-radius-base; + + // We remove the `outline` here, but later compensate by attaching `:hover` + // styles to `:focus`. + &:focus { + outline: 0; + } + + // Bars + .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; + } + .icon-bar + .icon-bar { + margin-top: 4px; + } + + @media (min-width: @grid-float-breakpoint) { + display: none; + } +} + + +// Navbar nav links +// +// Builds on top of the `.nav` components with its own modifier class to make +// the nav the full height of the horizontal nav (above 768px). + +.navbar-nav { + margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal; + + > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: @line-height-computed; + } + + @media (max-width: @grid-float-breakpoint-max) { + // Dropdowns get custom display when collapsed + .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + > li > a, + .dropdown-header { + padding: 5px 15px 5px 25px; + } + > li > a { + line-height: @line-height-computed; + &:hover, + &:focus { + background-image: none; + } + } + } + } + + // Uncollapse the nav + @media (min-width: @grid-float-breakpoint) { + float: left; + margin: 0; + + > li { + float: left; + > a { + padding-top: @navbar-padding-vertical; + padding-bottom: @navbar-padding-vertical; + } + } + } +} + + +// Navbar form +// +// Extension of the `.form-inline` with some extra flavor for optimum display in +// our navbars. + +.navbar-form { + margin-left: -@navbar-padding-horizontal; + margin-right: -@navbar-padding-horizontal; + padding: 10px @navbar-padding-horizontal; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1); + .box-shadow(@shadow); + + // Mixin behavior for optimum display + .form-inline(); + + .form-group { + @media (max-width: @grid-float-breakpoint-max) { + margin-bottom: 5px; + + &:last-child { + margin-bottom: 0; + } + } + } + + // Vertically center in expanded, horizontal navbar + .navbar-vertical-align(@input-height-base); + + // Undo 100% width for pull classes + @media (min-width: @grid-float-breakpoint) { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + .box-shadow(none); + } +} + + +// Dropdown menus + +// Menu position and menu carets +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + .border-top-radius(0); +} +// Menu position and menu caret support for dropups via extra dropup class +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + .border-top-radius(@navbar-border-radius); + .border-bottom-radius(0); +} + + +// Buttons in navbars +// +// Vertically center a button within a navbar (when *not* in a form). + +.navbar-btn { + .navbar-vertical-align(@input-height-base); + + &.btn-sm { + .navbar-vertical-align(@input-height-small); + } + &.btn-xs { + .navbar-vertical-align(22); + } +} + + +// Text in navbars +// +// Add a class to make any element properly align itself vertically within the navbars. + +.navbar-text { + .navbar-vertical-align(@line-height-computed); + + @media (min-width: @grid-float-breakpoint) { + float: left; + margin-left: @navbar-padding-horizontal; + margin-right: @navbar-padding-horizontal; + } +} + + +// Component alignment +// +// Repurpose the pull utilities as their own navbar utilities to avoid specificity +// issues with parents and chaining. Only do this when the navbar is uncollapsed +// though so that navbar contents properly stack and align in mobile. +// +// Declared after the navbar components to ensure more specificity on the margins. + +@media (min-width: @grid-float-breakpoint) { + .navbar-left { .pull-left(); } + .navbar-right { + .pull-right(); + margin-right: -@navbar-padding-horizontal; + + ~ .navbar-right { + margin-right: 0; + } + } +} + + +// Alternate navbars +// -------------------------------------------------- + +// Default navbar +.navbar-default { + background-color: @navbar-default-bg; + border-color: @navbar-default-border; + + .navbar-brand { + color: @navbar-default-brand-color; + &:hover, + &:focus { + color: @navbar-default-brand-hover-color; + background-color: @navbar-default-brand-hover-bg; + } + } + + .navbar-text { + color: @navbar-default-color; + } + + .navbar-nav { + > li > a { + color: @navbar-default-link-color; + + &:hover, + &:focus { + color: @navbar-default-link-hover-color; + background-color: @navbar-default-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-active-color; + background-color: @navbar-default-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-disabled-color; + background-color: @navbar-default-link-disabled-bg; + } + } + } + + .navbar-toggle { + border-color: @navbar-default-toggle-border-color; + &:hover, + &:focus { + background-color: @navbar-default-toggle-hover-bg; + } + .icon-bar { + background-color: @navbar-default-toggle-icon-bar-bg; + } + } + + .navbar-collapse, + .navbar-form { + border-color: @navbar-default-border; + } + + // Dropdown menu items + .navbar-nav { + // Remove background color from open dropdown + > .open > a { + &, + &:hover, + &:focus { + background-color: @navbar-default-link-active-bg; + color: @navbar-default-link-active-color; + } + } + + @media (max-width: @grid-float-breakpoint-max) { + // Dropdowns get custom display when collapsed + .open .dropdown-menu { + > li > a { + color: @navbar-default-link-color; + &:hover, + &:focus { + color: @navbar-default-link-hover-color; + background-color: @navbar-default-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-active-color; + background-color: @navbar-default-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-disabled-color; + background-color: @navbar-default-link-disabled-bg; + } + } + } + } + } + + + // Links in navbars + // + // Add a class to ensure links outside the navbar nav are colored correctly. + + .navbar-link { + color: @navbar-default-link-color; + &:hover { + color: @navbar-default-link-hover-color; + } + } + + .btn-link { + color: @navbar-default-link-color; + &:hover, + &:focus { + color: @navbar-default-link-hover-color; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @navbar-default-link-disabled-color; + } + } + } +} + +// Inverse navbar + +.navbar-inverse { + background-color: @navbar-inverse-bg; + border-color: @navbar-inverse-border; + + .navbar-brand { + color: @navbar-inverse-brand-color; + &:hover, + &:focus { + color: @navbar-inverse-brand-hover-color; + background-color: @navbar-inverse-brand-hover-bg; + } + } + + .navbar-text { + color: @navbar-inverse-color; + } + + .navbar-nav { + > li > a { + color: @navbar-inverse-link-color; + + &:hover, + &:focus { + color: @navbar-inverse-link-hover-color; + background-color: @navbar-inverse-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-active-color; + background-color: @navbar-inverse-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-disabled-color; + background-color: @navbar-inverse-link-disabled-bg; + } + } + } + + // Darken the responsive nav toggle + .navbar-toggle { + border-color: @navbar-inverse-toggle-border-color; + &:hover, + &:focus { + background-color: @navbar-inverse-toggle-hover-bg; + } + .icon-bar { + background-color: @navbar-inverse-toggle-icon-bar-bg; + } + } + + .navbar-collapse, + .navbar-form { + border-color: darken(@navbar-inverse-bg, 7%); + } + + // Dropdowns + .navbar-nav { + > .open > a { + &, + &:hover, + &:focus { + background-color: @navbar-inverse-link-active-bg; + color: @navbar-inverse-link-active-color; + } + } + + @media (max-width: @grid-float-breakpoint-max) { + // Dropdowns get custom display + .open .dropdown-menu { + > .dropdown-header { + border-color: @navbar-inverse-border; + } + .divider { + background-color: @navbar-inverse-border; + } + > li > a { + color: @navbar-inverse-link-color; + &:hover, + &:focus { + color: @navbar-inverse-link-hover-color; + background-color: @navbar-inverse-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-active-color; + background-color: @navbar-inverse-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-disabled-color; + background-color: @navbar-inverse-link-disabled-bg; + } + } + } + } + } + + .navbar-link { + color: @navbar-inverse-link-color; + &:hover { + color: @navbar-inverse-link-hover-color; + } + } + + .btn-link { + color: @navbar-inverse-link-color; + &:hover, + &:focus { + color: @navbar-inverse-link-hover-color; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @navbar-inverse-link-disabled-color; + } + } + } +} diff --git a/libs/fmt/doc/bootstrap/navs.less b/libs/fmt/doc/bootstrap/navs.less new file mode 100644 index 0000000000000000000000000000000000000000..a3d11b136211f3ee4a4d3beeb19c5ffba203b15b --- /dev/null +++ b/libs/fmt/doc/bootstrap/navs.less @@ -0,0 +1,242 @@ +// +// Navs +// -------------------------------------------------- + + +// Base class +// -------------------------------------------------- + +.nav { + margin-bottom: 0; + padding-left: 0; // Override default ul/ol + list-style: none; + &:extend(.clearfix all); + + > li { + position: relative; + display: block; + + > a { + position: relative; + display: block; + padding: @nav-link-padding; + &:hover, + &:focus { + text-decoration: none; + background-color: @nav-link-hover-bg; + } + } + + // Disabled state sets text to gray and nukes hover/tab effects + &.disabled > a { + color: @nav-disabled-link-color; + + &:hover, + &:focus { + color: @nav-disabled-link-hover-color; + text-decoration: none; + background-color: transparent; + cursor: @cursor-disabled; + } + } + } + + // Open dropdowns + .open > a { + &, + &:hover, + &:focus { + background-color: @nav-link-hover-bg; + border-color: @link-color; + } + } + + // Nav dividers (deprecated with v3.0.1) + // + // This should have been removed in v3 with the dropping of `.nav-list`, but + // we missed it. We don't currently support this anywhere, but in the interest + // of maintaining backward compatibility in case you use it, it's deprecated. + .nav-divider { + .nav-divider(); + } + + // Prevent IE8 from misplacing imgs + // + // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 + > li > a > img { + max-width: none; + } +} + + +// Tabs +// ------------------------- + +// Give the tabs something to sit on +.nav-tabs { + border-bottom: 1px solid @nav-tabs-border-color; + > li { + float: left; + // Make the list-items overlay the bottom border + margin-bottom: -1px; + + // Actual tabs (as links) + > a { + margin-right: 2px; + line-height: @line-height-base; + border: 1px solid transparent; + border-radius: @border-radius-base @border-radius-base 0 0; + &:hover { + border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color; + } + } + + // Active state, and its :hover to override normal :hover + &.active > a { + &, + &:hover, + &:focus { + color: @nav-tabs-active-link-hover-color; + background-color: @nav-tabs-active-link-hover-bg; + border: 1px solid @nav-tabs-active-link-hover-border-color; + border-bottom-color: transparent; + cursor: default; + } + } + } + // pulling this in mainly for less shorthand + &.nav-justified { + .nav-justified(); + .nav-tabs-justified(); + } +} + + +// Pills +// ------------------------- +.nav-pills { + > li { + float: left; + + // Links rendered as pills + > a { + border-radius: @nav-pills-border-radius; + } + + li { + margin-left: 2px; + } + + // Active state + &.active > a { + &, + &:hover, + &:focus { + color: @nav-pills-active-link-hover-color; + background-color: @nav-pills-active-link-hover-bg; + } + } + } +} + + +// Stacked pills +.nav-stacked { + > li { + float: none; + + li { + margin-top: 2px; + margin-left: 0; // no need for this gap between nav items + } + } +} + + +// Nav variations +// -------------------------------------------------- + +// Justified nav links +// ------------------------- + +.nav-justified { + width: 100%; + + > li { + float: none; + > a { + text-align: center; + margin-bottom: 5px; + } + } + + > .dropdown .dropdown-menu { + top: auto; + left: auto; + } + + @media (min-width: @screen-sm-min) { + > li { + display: table-cell; + width: 1%; + > a { + margin-bottom: 0; + } + } + } +} + +// Move borders to anchors instead of bottom of list +// +// Mixin for adding on top the shared `.nav-justified` styles for our tabs +.nav-tabs-justified { + border-bottom: 0; + + > li > a { + // Override margin from .nav-tabs + margin-right: 0; + border-radius: @border-radius-base; + } + + > .active > a, + > .active > a:hover, + > .active > a:focus { + border: 1px solid @nav-tabs-justified-link-border-color; + } + + @media (min-width: @screen-sm-min) { + > li > a { + border-bottom: 1px solid @nav-tabs-justified-link-border-color; + border-radius: @border-radius-base @border-radius-base 0 0; + } + > .active > a, + > .active > a:hover, + > .active > a:focus { + border-bottom-color: @nav-tabs-justified-active-link-border-color; + } + } +} + + +// Tabbable tabs +// ------------------------- + +// Hide tabbable panes to start, show them when `.active` +.tab-content { + > .tab-pane { + display: none; + } + > .active { + display: block; + } +} + + +// Dropdowns +// ------------------------- + +// Specific dropdowns +.nav-tabs .dropdown-menu { + // make dropdown border overlap tab border + margin-top: -1px; + // Remove the top rounded corners here since there is a hard edge above the menu + .border-top-radius(0); +} diff --git a/libs/fmt/doc/bootstrap/normalize.less b/libs/fmt/doc/bootstrap/normalize.less new file mode 100644 index 0000000000000000000000000000000000000000..62a085a4841a536094f22943c9557240e2d8c19d --- /dev/null +++ b/libs/fmt/doc/bootstrap/normalize.less @@ -0,0 +1,427 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +// +// 1. Set default font family to sans-serif. +// 2. Prevent iOS text size adjust after orientation change, without disabling +// user zoom. +// + +html { + font-family: sans-serif; // 1 + -ms-text-size-adjust: 100%; // 2 + -webkit-text-size-adjust: 100%; // 2 +} + +// +// Remove default margin. +// + +body { + margin: 0; +} + +// HTML5 display definitions +// ========================================================================== + +// +// Correct `block` display not defined for any HTML5 element in IE 8/9. +// Correct `block` display not defined for `details` or `summary` in IE 10/11 +// and Firefox. +// Correct `block` display not defined for `main` in IE 11. +// + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +// +// 1. Correct `inline-block` display not defined in IE 8/9. +// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. +// + +audio, +canvas, +progress, +video { + display: inline-block; // 1 + vertical-align: baseline; // 2 +} + +// +// Prevent modern browsers from displaying `audio` without controls. +// Remove excess height in iOS 5 devices. +// + +audio:not([controls]) { + display: none; + height: 0; +} + +// +// Address `[hidden]` styling not present in IE 8/9/10. +// Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. +// + +[hidden], +template { + display: none; +} + +// Links +// ========================================================================== + +// +// Remove the gray background color from active links in IE 10. +// + +a { + background-color: transparent; +} + +// +// Improve readability when focused and also mouse hovered in all browsers. +// + +a:active, +a:hover { + outline: 0; +} + +// Text-level semantics +// ========================================================================== + +// +// Address styling not present in IE 8/9/10/11, Safari, and Chrome. +// + +abbr[title] { + border-bottom: 1px dotted; +} + +// +// Address style set to `bolder` in Firefox 4+, Safari, and Chrome. +// + +b, +strong { + font-weight: bold; +} + +// +// Address styling not present in Safari and Chrome. +// + +dfn { + font-style: italic; +} + +// +// Address variable `h1` font-size and margin within `section` and `article` +// contexts in Firefox 4+, Safari, and Chrome. +// + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +// +// Address styling not present in IE 8/9. +// + +mark { + background: #ff0; + color: #000; +} + +// +// Address inconsistent and variable font size in all browsers. +// + +small { + font-size: 80%; +} + +// +// Prevent `sub` and `sup` affecting `line-height` in all browsers. +// + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +// Embedded content +// ========================================================================== + +// +// Remove border when inside `a` element in IE 8/9/10. +// + +img { + border: 0; +} + +// +// Correct overflow not hidden in IE 9/10/11. +// + +svg:not(:root) { + overflow: hidden; +} + +// Grouping content +// ========================================================================== + +// +// Address margin not present in IE 8/9 and Safari. +// + +figure { + margin: 1em 40px; +} + +// +// Address differences between Firefox and other browsers. +// + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +// +// Contain overflow in all browsers. +// + +pre { + overflow: auto; +} + +// +// Address odd `em`-unit font size rendering in all browsers. +// + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +// Forms +// ========================================================================== + +// +// Known limitation: by default, Chrome and Safari on OS X allow very limited +// styling of `select`, unless a `border` property is set. +// + +// +// 1. Correct color not being inherited. +// Known issue: affects color of disabled elements. +// 2. Correct font properties not being inherited. +// 3. Address margins set differently in Firefox 4+, Safari, and Chrome. +// + +button, +input, +optgroup, +select, +textarea { + color: inherit; // 1 + font: inherit; // 2 + margin: 0; // 3 +} + +// +// Address `overflow` set to `hidden` in IE 8/9/10/11. +// + +button { + overflow: visible; +} + +// +// Address inconsistent `text-transform` inheritance for `button` and `select`. +// All other form control elements do not inherit `text-transform` values. +// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. +// Correct `select` style inheritance in Firefox. +// + +button, +select { + text-transform: none; +} + +// +// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` +// and `video` controls. +// 2. Correct inability to style clickable `input` types in iOS. +// 3. Improve usability and consistency of cursor style between image-type +// `input` and others. +// + +button, +html input[type="button"], // 1 +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; // 2 + cursor: pointer; // 3 +} + +// +// Re-set default cursor for disabled elements. +// + +button[disabled], +html input[disabled] { + cursor: default; +} + +// +// Remove inner padding and border in Firefox 4+. +// + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +// +// Address Firefox 4+ setting `line-height` on `input` using `!important` in +// the UA stylesheet. +// + +input { + line-height: normal; +} + +// +// It's recommended that you don't attempt to style these elements. +// Firefox's implementation doesn't respect box-sizing, padding, or width. +// +// 1. Address box sizing set to `content-box` in IE 8/9/10. +// 2. Remove excess padding in IE 8/9/10. +// + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; // 1 + padding: 0; // 2 +} + +// +// Fix the cursor style for Chrome's increment/decrement buttons. For certain +// `font-size` values of the `input`, it causes the cursor style of the +// decrement button to change from `default` to `text`. +// + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +// +// 1. Address `appearance` set to `searchfield` in Safari and Chrome. +// 2. Address `box-sizing` set to `border-box` in Safari and Chrome +// (include `-moz` to future-proof). +// + +input[type="search"] { + -webkit-appearance: textfield; // 1 + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; // 2 + box-sizing: content-box; +} + +// +// Remove inner padding and search cancel button in Safari and Chrome on OS X. +// Safari (but not Chrome) clips the cancel button when the search input has +// padding (and `textfield` appearance). +// + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +// +// Define consistent border, margin, and padding. +// + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +// +// 1. Correct `color` not being inherited in IE 8/9/10/11. +// 2. Remove padding so people aren't caught out if they zero out fieldsets. +// + +legend { + border: 0; // 1 + padding: 0; // 2 +} + +// +// Remove default vertical scrollbar in IE 8/9/10/11. +// + +textarea { + overflow: auto; +} + +// +// Don't inherit the `font-weight` (applied by a rule above). +// NOTE: the default cannot safely be changed in Chrome and Safari on OS X. +// + +optgroup { + font-weight: bold; +} + +// Tables +// ========================================================================== + +// +// Remove most spacing between table cells. +// + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/libs/fmt/doc/bootstrap/pager.less b/libs/fmt/doc/bootstrap/pager.less new file mode 100644 index 0000000000000000000000000000000000000000..41abaaadc5dc605c75de869dab943599ef31fbbd --- /dev/null +++ b/libs/fmt/doc/bootstrap/pager.less @@ -0,0 +1,54 @@ +// +// Pager pagination +// -------------------------------------------------- + + +.pager { + padding-left: 0; + margin: @line-height-computed 0; + list-style: none; + text-align: center; + &:extend(.clearfix all); + li { + display: inline; + > a, + > span { + display: inline-block; + padding: 5px 14px; + background-color: @pager-bg; + border: 1px solid @pager-border; + border-radius: @pager-border-radius; + } + + > a:hover, + > a:focus { + text-decoration: none; + background-color: @pager-hover-bg; + } + } + + .next { + > a, + > span { + float: right; + } + } + + .previous { + > a, + > span { + float: left; + } + } + + .disabled { + > a, + > a:hover, + > a:focus, + > span { + color: @pager-disabled-color; + background-color: @pager-bg; + cursor: @cursor-disabled; + } + } +} diff --git a/libs/fmt/doc/bootstrap/pagination.less b/libs/fmt/doc/bootstrap/pagination.less new file mode 100644 index 0000000000000000000000000000000000000000..38c4c3d346f974278503a4596a6b6e96b845c0af --- /dev/null +++ b/libs/fmt/doc/bootstrap/pagination.less @@ -0,0 +1,88 @@ +// +// Pagination (multiple pages) +// -------------------------------------------------- +.pagination { + display: inline-block; + padding-left: 0; + margin: @line-height-computed 0; + border-radius: @border-radius-base; + + > li { + display: inline; // Remove list-style and block-level defaults + > a, + > span { + position: relative; + float: left; // Collapse white-space + padding: @padding-base-vertical @padding-base-horizontal; + line-height: @line-height-base; + text-decoration: none; + color: @pagination-color; + background-color: @pagination-bg; + border: 1px solid @pagination-border; + margin-left: -1px; + } + &:first-child { + > a, + > span { + margin-left: 0; + .border-left-radius(@border-radius-base); + } + } + &:last-child { + > a, + > span { + .border-right-radius(@border-radius-base); + } + } + } + + > li > a, + > li > span { + &:hover, + &:focus { + color: @pagination-hover-color; + background-color: @pagination-hover-bg; + border-color: @pagination-hover-border; + } + } + + > .active > a, + > .active > span { + &, + &:hover, + &:focus { + z-index: 2; + color: @pagination-active-color; + background-color: @pagination-active-bg; + border-color: @pagination-active-border; + cursor: default; + } + } + + > .disabled { + > span, + > span:hover, + > span:focus, + > a, + > a:hover, + > a:focus { + color: @pagination-disabled-color; + background-color: @pagination-disabled-bg; + border-color: @pagination-disabled-border; + cursor: @cursor-disabled; + } + } +} + +// Sizing +// -------------------------------------------------- + +// Large +.pagination-lg { + .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large); +} + +// Small +.pagination-sm { + .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small); +} diff --git a/libs/fmt/doc/bootstrap/panels.less b/libs/fmt/doc/bootstrap/panels.less new file mode 100644 index 0000000000000000000000000000000000000000..04eb2b39ca18d69945ed1e60e725fb30544bde3f --- /dev/null +++ b/libs/fmt/doc/bootstrap/panels.less @@ -0,0 +1,265 @@ +// +// Panels +// -------------------------------------------------- + + +// Base class +.panel { + margin-bottom: @line-height-computed; + background-color: @panel-bg; + border: 1px solid transparent; + border-radius: @panel-border-radius; + .box-shadow(0 1px 1px rgba(0,0,0,.05)); +} + +// Panel contents +.panel-body { + padding: @panel-body-padding; + &:extend(.clearfix all); +} + +// Optional heading +.panel-heading { + padding: @panel-heading-padding; + border-bottom: 1px solid transparent; + .border-top-radius((@panel-border-radius - 1)); + + > .dropdown .dropdown-toggle { + color: inherit; + } +} + +// Within heading, strip any `h*` tag of its default margins for spacing. +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: ceil((@font-size-base * 1.125)); + color: inherit; + + > a, + > small, + > .small, + > small > a, + > .small > a { + color: inherit; + } +} + +// Optional footer (stays gray in every modifier class) +.panel-footer { + padding: @panel-footer-padding; + background-color: @panel-footer-bg; + border-top: 1px solid @panel-inner-border; + .border-bottom-radius((@panel-border-radius - 1)); +} + + +// List groups in panels +// +// By default, space out list group content from panel headings to account for +// any kind of custom content between the two. + +.panel { + > .list-group, + > .panel-collapse > .list-group { + margin-bottom: 0; + + .list-group-item { + border-width: 1px 0; + border-radius: 0; + } + + // Add border top radius for first one + &:first-child { + .list-group-item:first-child { + border-top: 0; + .border-top-radius((@panel-border-radius - 1)); + } + } + // Add border bottom radius for last one + &:last-child { + .list-group-item:last-child { + border-bottom: 0; + .border-bottom-radius((@panel-border-radius - 1)); + } + } + } +} +// Collapse space between when there's no additional content. +.panel-heading + .list-group { + .list-group-item:first-child { + border-top-width: 0; + } +} +.list-group + .panel-footer { + border-top-width: 0; +} + +// Tables in panels +// +// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and +// watch it go full width. + +.panel { + > .table, + > .table-responsive > .table, + > .panel-collapse > .table { + margin-bottom: 0; + + caption { + padding-left: @panel-body-padding; + padding-right: @panel-body-padding; + } + } + // Add border top radius for first one + > .table:first-child, + > .table-responsive:first-child > .table:first-child { + .border-top-radius((@panel-border-radius - 1)); + + > thead:first-child, + > tbody:first-child { + > tr:first-child { + border-top-left-radius: (@panel-border-radius - 1); + border-top-right-radius: (@panel-border-radius - 1); + + td:first-child, + th:first-child { + border-top-left-radius: (@panel-border-radius - 1); + } + td:last-child, + th:last-child { + border-top-right-radius: (@panel-border-radius - 1); + } + } + } + } + // Add border bottom radius for last one + > .table:last-child, + > .table-responsive:last-child > .table:last-child { + .border-bottom-radius((@panel-border-radius - 1)); + + > tbody:last-child, + > tfoot:last-child { + > tr:last-child { + border-bottom-left-radius: (@panel-border-radius - 1); + border-bottom-right-radius: (@panel-border-radius - 1); + + td:first-child, + th:first-child { + border-bottom-left-radius: (@panel-border-radius - 1); + } + td:last-child, + th:last-child { + border-bottom-right-radius: (@panel-border-radius - 1); + } + } + } + } + > .panel-body + .table, + > .panel-body + .table-responsive, + > .table + .panel-body, + > .table-responsive + .panel-body { + border-top: 1px solid @table-border-color; + } + > .table > tbody:first-child > tr:first-child th, + > .table > tbody:first-child > tr:first-child td { + border-top: 0; + } + > .table-bordered, + > .table-responsive > .table-bordered { + border: 0; + > thead, + > tbody, + > tfoot { + > tr { + > th:first-child, + > td:first-child { + border-left: 0; + } + > th:last-child, + > td:last-child { + border-right: 0; + } + } + } + > thead, + > tbody { + > tr:first-child { + > td, + > th { + border-bottom: 0; + } + } + } + > tbody, + > tfoot { + > tr:last-child { + > td, + > th { + border-bottom: 0; + } + } + } + } + > .table-responsive { + border: 0; + margin-bottom: 0; + } +} + + +// Collapsable panels (aka, accordion) +// +// Wrap a series of panels in `.panel-group` to turn them into an accordion with +// the help of our collapse JavaScript plugin. + +.panel-group { + margin-bottom: @line-height-computed; + + // Tighten up margin so it's only between panels + .panel { + margin-bottom: 0; + border-radius: @panel-border-radius; + + + .panel { + margin-top: 5px; + } + } + + .panel-heading { + border-bottom: 0; + + + .panel-collapse > .panel-body, + + .panel-collapse > .list-group { + border-top: 1px solid @panel-inner-border; + } + } + + .panel-footer { + border-top: 0; + + .panel-collapse .panel-body { + border-bottom: 1px solid @panel-inner-border; + } + } +} + + +// Contextual variations +.panel-default { + .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border); +} +.panel-primary { + .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border); +} +.panel-success { + .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border); +} +.panel-info { + .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border); +} +.panel-warning { + .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border); +} +.panel-danger { + .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border); +} diff --git a/libs/fmt/doc/bootstrap/popovers.less b/libs/fmt/doc/bootstrap/popovers.less new file mode 100644 index 0000000000000000000000000000000000000000..2748eead13fa69dc8e7d32a1f0f64e6edb71e6e5 --- /dev/null +++ b/libs/fmt/doc/bootstrap/popovers.less @@ -0,0 +1,135 @@ +// +// Popovers +// -------------------------------------------------- + + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: @zindex-popover; + display: none; + max-width: @popover-max-width; + padding: 1px; + // Reset font and text properties given new insertion method + font-family: @font-family-base; + font-size: @font-size-base; + font-weight: normal; + line-height: @line-height-base; + text-align: left; + background-color: @popover-bg; + background-clip: padding-box; + border: 1px solid @popover-fallback-border-color; + border: 1px solid @popover-border-color; + border-radius: @border-radius-large; + .box-shadow(0 5px 10px rgba(0,0,0,.2)); + + // Overrides for proper insertion + white-space: normal; + + // Offset the popover to account for the popover arrow + &.top { margin-top: -@popover-arrow-width; } + &.right { margin-left: @popover-arrow-width; } + &.bottom { margin-top: @popover-arrow-width; } + &.left { margin-left: -@popover-arrow-width; } +} + +.popover-title { + margin: 0; // reset heading margin + padding: 8px 14px; + font-size: @font-size-base; + background-color: @popover-title-bg; + border-bottom: 1px solid darken(@popover-title-bg, 5%); + border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0; +} + +.popover-content { + padding: 9px 14px; +} + +// Arrows +// +// .arrow is outer, .arrow:after is inner + +.popover > .arrow { + &, + &:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + } +} +.popover > .arrow { + border-width: @popover-arrow-outer-width; +} +.popover > .arrow:after { + border-width: @popover-arrow-width; + content: ""; +} + +.popover { + &.top > .arrow { + left: 50%; + margin-left: -@popover-arrow-outer-width; + border-bottom-width: 0; + border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-top-color: @popover-arrow-outer-color; + bottom: -@popover-arrow-outer-width; + &:after { + content: " "; + bottom: 1px; + margin-left: -@popover-arrow-width; + border-bottom-width: 0; + border-top-color: @popover-arrow-color; + } + } + &.right > .arrow { + top: 50%; + left: -@popover-arrow-outer-width; + margin-top: -@popover-arrow-outer-width; + border-left-width: 0; + border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-right-color: @popover-arrow-outer-color; + &:after { + content: " "; + left: 1px; + bottom: -@popover-arrow-width; + border-left-width: 0; + border-right-color: @popover-arrow-color; + } + } + &.bottom > .arrow { + left: 50%; + margin-left: -@popover-arrow-outer-width; + border-top-width: 0; + border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-bottom-color: @popover-arrow-outer-color; + top: -@popover-arrow-outer-width; + &:after { + content: " "; + top: 1px; + margin-left: -@popover-arrow-width; + border-top-width: 0; + border-bottom-color: @popover-arrow-color; + } + } + + &.left > .arrow { + top: 50%; + right: -@popover-arrow-outer-width; + margin-top: -@popover-arrow-outer-width; + border-right-width: 0; + border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-left-color: @popover-arrow-outer-color; + &:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: @popover-arrow-color; + bottom: -@popover-arrow-width; + } + } +} diff --git a/libs/fmt/doc/bootstrap/print.less b/libs/fmt/doc/bootstrap/print.less new file mode 100644 index 0000000000000000000000000000000000000000..94ca58f12a93ea0b3fb378b73eec63d8c9ee7372 --- /dev/null +++ b/libs/fmt/doc/bootstrap/print.less @@ -0,0 +1,107 @@ +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ + +// ========================================================================== +// Print styles. +// Inlined to avoid the additional HTTP request: h5bp.com/r +// ========================================================================== + +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; // Black prints faster: h5bp.com/s + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + // Don't show links that are fragment identifiers, + // or use the `javascript:` pseudo protocol + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; // h5bp.com/t + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } + + // Bootstrap specific changes start + // + // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245 + // Once fixed, we can just straight up remove this. + select { + background: #fff !important; + } + + // Bootstrap components + .navbar { + display: none; + } + .btn, + .dropup > .btn { + > .caret { + border-top-color: #000 !important; + } + } + .label { + border: 1px solid #000; + } + + .table { + border-collapse: collapse !important; + + td, + th { + background-color: #fff !important; + } + } + .table-bordered { + th, + td { + border: 1px solid #ddd !important; + } + } + + // Bootstrap specific changes end +} diff --git a/libs/fmt/doc/bootstrap/progress-bars.less b/libs/fmt/doc/bootstrap/progress-bars.less new file mode 100644 index 0000000000000000000000000000000000000000..8868a1feef0f3a7a4b0fabe7548435bfeb481df2 --- /dev/null +++ b/libs/fmt/doc/bootstrap/progress-bars.less @@ -0,0 +1,87 @@ +// +// Progress bars +// -------------------------------------------------- + + +// Bar animations +// ------------------------- + +// WebKit +@-webkit-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Spec and IE10+ +@keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + + +// Bar itself +// ------------------------- + +// Outer container +.progress { + overflow: hidden; + height: @line-height-computed; + margin-bottom: @line-height-computed; + background-color: @progress-bg; + border-radius: @progress-border-radius; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); +} + +// Bar of progress +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: @font-size-small; + line-height: @line-height-computed; + color: @progress-bar-color; + text-align: center; + background-color: @progress-bar-bg; + .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); + .transition(width .6s ease); +} + +// Striped bars +// +// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the +// `.progress-bar-striped` class, which you just add to an existing +// `.progress-bar`. +.progress-striped .progress-bar, +.progress-bar-striped { + #gradient > .striped(); + background-size: 40px 40px; +} + +// Call animation for the active one +// +// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the +// `.progress-bar.active` approach. +.progress.active .progress-bar, +.progress-bar.active { + .animation(progress-bar-stripes 2s linear infinite); +} + + +// Variations +// ------------------------- + +.progress-bar-success { + .progress-bar-variant(@progress-bar-success-bg); +} + +.progress-bar-info { + .progress-bar-variant(@progress-bar-info-bg); +} + +.progress-bar-warning { + .progress-bar-variant(@progress-bar-warning-bg); +} + +.progress-bar-danger { + .progress-bar-variant(@progress-bar-danger-bg); +} diff --git a/libs/fmt/doc/bootstrap/responsive-embed.less b/libs/fmt/doc/bootstrap/responsive-embed.less new file mode 100644 index 0000000000000000000000000000000000000000..080a5118fe9ab2af331e6b1444fff4be840ecc03 --- /dev/null +++ b/libs/fmt/doc/bootstrap/responsive-embed.less @@ -0,0 +1,35 @@ +// Embeds responsive +// +// Credit: Nicolas Gallagher and SUIT CSS. + +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; + + .embed-responsive-item, + iframe, + embed, + object, + video { + position: absolute; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 100%; + border: 0; + } +} + +// Modifier class for 16:9 aspect ratio +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} + +// Modifier class for 4:3 aspect ratio +.embed-responsive-4by3 { + padding-bottom: 75%; +} diff --git a/libs/fmt/doc/bootstrap/responsive-utilities.less b/libs/fmt/doc/bootstrap/responsive-utilities.less new file mode 100644 index 0000000000000000000000000000000000000000..b1db31d7bfc19a757d02fd773db765c4273549a1 --- /dev/null +++ b/libs/fmt/doc/bootstrap/responsive-utilities.less @@ -0,0 +1,194 @@ +// +// Responsive: Utility classes +// -------------------------------------------------- + + +// IE10 in Windows (Phone) 8 +// +// Support for responsive views via media queries is kind of borked in IE10, for +// Surface/desktop in split view and for Windows Phone 8. This particular fix +// must be accompanied by a snippet of JavaScript to sniff the user agent and +// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at +// our Getting Started page for more information on this bug. +// +// For more information, see the following: +// +// Issue: https://github.com/twbs/bootstrap/issues/10497 +// Docs: http://getbootstrap.com/getting-started/#support-ie10-width +// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/ +// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ + +@-ms-viewport { + width: device-width; +} + + +// Visibility utilities +// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0 +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + .responsive-invisibility(); +} + +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} + +.visible-xs { + @media (max-width: @screen-xs-max) { + .responsive-visibility(); + } +} +.visible-xs-block { + @media (max-width: @screen-xs-max) { + display: block !important; + } +} +.visible-xs-inline { + @media (max-width: @screen-xs-max) { + display: inline !important; + } +} +.visible-xs-inline-block { + @media (max-width: @screen-xs-max) { + display: inline-block !important; + } +} + +.visible-sm { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + .responsive-visibility(); + } +} +.visible-sm-block { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + display: block !important; + } +} +.visible-sm-inline { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + display: inline !important; + } +} +.visible-sm-inline-block { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + display: inline-block !important; + } +} + +.visible-md { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + .responsive-visibility(); + } +} +.visible-md-block { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + display: block !important; + } +} +.visible-md-inline { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + display: inline !important; + } +} +.visible-md-inline-block { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + display: inline-block !important; + } +} + +.visible-lg { + @media (min-width: @screen-lg-min) { + .responsive-visibility(); + } +} +.visible-lg-block { + @media (min-width: @screen-lg-min) { + display: block !important; + } +} +.visible-lg-inline { + @media (min-width: @screen-lg-min) { + display: inline !important; + } +} +.visible-lg-inline-block { + @media (min-width: @screen-lg-min) { + display: inline-block !important; + } +} + +.hidden-xs { + @media (max-width: @screen-xs-max) { + .responsive-invisibility(); + } +} +.hidden-sm { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + .responsive-invisibility(); + } +} +.hidden-md { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + .responsive-invisibility(); + } +} +.hidden-lg { + @media (min-width: @screen-lg-min) { + .responsive-invisibility(); + } +} + + +// Print utilities +// +// Media queries are placed on the inside to be mixin-friendly. + +// Note: Deprecated .visible-print as of v3.2.0 +.visible-print { + .responsive-invisibility(); + + @media print { + .responsive-visibility(); + } +} +.visible-print-block { + display: none !important; + + @media print { + display: block !important; + } +} +.visible-print-inline { + display: none !important; + + @media print { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; + + @media print { + display: inline-block !important; + } +} + +.hidden-print { + @media print { + .responsive-invisibility(); + } +} diff --git a/libs/fmt/doc/bootstrap/scaffolding.less b/libs/fmt/doc/bootstrap/scaffolding.less new file mode 100644 index 0000000000000000000000000000000000000000..47b1d5a1aab384e993b689aaaec4212a85260e88 --- /dev/null +++ b/libs/fmt/doc/bootstrap/scaffolding.less @@ -0,0 +1,162 @@ +// +// Scaffolding +// -------------------------------------------------- + + +// Reset the box-sizing +// +// Heads up! This reset may cause conflicts with some third-party widgets. +// For recommendations on resolving such conflicts, see +// http://getbootstrap.com/getting-started/#third-box-sizing +* { + .box-sizing(border-box); +} +*:before, +*:after { + .box-sizing(border-box); +} + + +// Body reset + +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0,0,0,0); +} + +body { + font-family: @font-family-base; + font-size: @font-size-base; + line-height: @line-height-base; + color: @text-color; + background-color: @body-bg; +} + +// Reset fonts for relevant elements +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + + +// Links + +a { + color: @link-color; + text-decoration: none; + + &:hover, + &:focus { + color: @link-hover-color; + text-decoration: @link-hover-decoration; + } + + &:focus { + .tab-focus(); + } +} + + +// Figures +// +// We reset this here because previously Normalize had no `figure` margins. This +// ensures we don't break anyone's use of the element. + +figure { + margin: 0; +} + + +// Images + +img { + vertical-align: middle; +} + +// Responsive images (ensure images don't scale beyond their parents) +.img-responsive { + .img-responsive(); +} + +// Rounded corners +.img-rounded { + border-radius: @border-radius-large; +} + +// Image thumbnails +// +// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`. +.img-thumbnail { + padding: @thumbnail-padding; + line-height: @line-height-base; + background-color: @thumbnail-bg; + border: 1px solid @thumbnail-border; + border-radius: @thumbnail-border-radius; + .transition(all .2s ease-in-out); + + // Keep them at most 100% wide + .img-responsive(inline-block); +} + +// Perfect circle +.img-circle { + border-radius: 50%; // set radius in percents +} + + +// Horizontal rules + +hr { + margin-top: @line-height-computed; + margin-bottom: @line-height-computed; + border: 0; + border-top: 1px solid @hr-border; +} + + +// Only display content to screen readers +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// Credit: HTML5 Boilerplate + +.sr-only-focusable { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} + + +// iOS "clickable elements" fix for role="button" +// +// Fixes "clickability" issue (and more generally, the firing of events such as focus as well) +// for traditionally non-focusable elements with role="button" +// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile +// Upstream patch for normalize.css submitted: https://github.com/necolas/normalize.css/pull/379 - remove this fix once that is merged + +[role="button"] { + cursor: pointer; +} \ No newline at end of file diff --git a/libs/fmt/doc/bootstrap/tables.less b/libs/fmt/doc/bootstrap/tables.less new file mode 100644 index 0000000000000000000000000000000000000000..2242c036868d7358ad684313ccab2e4d4704ee94 --- /dev/null +++ b/libs/fmt/doc/bootstrap/tables.less @@ -0,0 +1,234 @@ +// +// Tables +// -------------------------------------------------- + + +table { + background-color: @table-bg; +} +caption { + padding-top: @table-cell-padding; + padding-bottom: @table-cell-padding; + color: @text-muted; + text-align: left; +} +th { + text-align: left; +} + + +// Baseline styles + +.table { + width: 100%; + max-width: 100%; + margin-bottom: @line-height-computed; + // Cells + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + padding: @table-cell-padding; + line-height: @line-height-base; + vertical-align: top; + border-top: 1px solid @table-border-color; + } + } + } + // Bottom align for column headings + > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid @table-border-color; + } + // Remove top border from thead by default + > caption + thead, + > colgroup + thead, + > thead:first-child { + > tr:first-child { + > th, + > td { + border-top: 0; + } + } + } + // Account for multiple tbody instances + > tbody + tbody { + border-top: 2px solid @table-border-color; + } + + // Nesting + .table { + background-color: @body-bg; + } +} + + +// Condensed table w/ half padding + +.table-condensed { + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + padding: @table-condensed-cell-padding; + } + } + } +} + + +// Bordered version +// +// Add borders all around the table and between all the columns. + +.table-bordered { + border: 1px solid @table-border-color; + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + border: 1px solid @table-border-color; + } + } + } + > thead > tr { + > th, + > td { + border-bottom-width: 2px; + } + } +} + + +// Zebra-striping +// +// Default zebra-stripe styles (alternating gray and transparent backgrounds) + +.table-striped { + > tbody > tr:nth-of-type(odd) { + background-color: @table-bg-accent; + } +} + + +// Hover effect +// +// Placed here since it has to come after the potential zebra striping + +.table-hover { + > tbody > tr:hover { + background-color: @table-bg-hover; + } +} + + +// Table cell sizing +// +// Reset default table behavior + +table col[class*="col-"] { + position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623) + float: none; + display: table-column; +} +table { + td, + th { + &[class*="col-"] { + position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623) + float: none; + display: table-cell; + } + } +} + + +// Table backgrounds +// +// Exact selectors below required to override `.table-striped` and prevent +// inheritance to nested tables. + +// Generate the contextual variants +.table-row-variant(active; @table-bg-active); +.table-row-variant(success; @state-success-bg); +.table-row-variant(info; @state-info-bg); +.table-row-variant(warning; @state-warning-bg); +.table-row-variant(danger; @state-danger-bg); + + +// Responsive tables +// +// Wrap your tables in `.table-responsive` and we'll make them mobile friendly +// by enabling horizontal scrolling. Only applies <768px. Everything above that +// will display normally. + +.table-responsive { + overflow-x: auto; + min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837) + + @media screen and (max-width: @screen-xs-max) { + width: 100%; + margin-bottom: (@line-height-computed * 0.75); + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid @table-border-color; + + // Tighten up spacing + > .table { + margin-bottom: 0; + + // Ensure the content doesn't wrap + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + white-space: nowrap; + } + } + } + } + + // Special overrides for the bordered tables + > .table-bordered { + border: 0; + + // Nuke the appropriate borders so that the parent can handle them + > thead, + > tbody, + > tfoot { + > tr { + > th:first-child, + > td:first-child { + border-left: 0; + } + > th:last-child, + > td:last-child { + border-right: 0; + } + } + } + + // Only nuke the last row's bottom-border in `tbody` and `tfoot` since + // chances are there will be only one `tr` in a `thead` and that would + // remove the border altogether. + > tbody, + > tfoot { + > tr:last-child { + > th, + > td { + border-bottom: 0; + } + } + } + + } + } +} diff --git a/libs/fmt/doc/bootstrap/theme.less b/libs/fmt/doc/bootstrap/theme.less new file mode 100644 index 0000000000000000000000000000000000000000..6f2eb62126f12b00cde15580964870c7c3e4eb4c --- /dev/null +++ b/libs/fmt/doc/bootstrap/theme.less @@ -0,0 +1,273 @@ + +// +// Load core variables and mixins +// -------------------------------------------------- + +@import "variables.less"; +@import "mixins.less"; + + +// +// Buttons +// -------------------------------------------------- + +// Common styles +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0,0,0,.2); + @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075); + .box-shadow(@shadow); + + // Reset the shadow + &:active, + &.active { + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + .badge { + text-shadow: none; + } +} + +// Mixin for generating new styles +.btn-styles(@btn-color: #555) { + #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%)); + .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620 + background-repeat: repeat-x; + border-color: darken(@btn-color, 14%); + + &:hover, + &:focus { + background-color: darken(@btn-color, 12%); + background-position: 0 -15px; + } + + &:active, + &.active { + background-color: darken(@btn-color, 12%); + border-color: darken(@btn-color, 14%); + } + + &.disabled, + &:disabled, + &[disabled] { + background-color: darken(@btn-color, 12%); + background-image: none; + } +} + +// Common styles +.btn { + // Remove the gradient for the pressed/active state + &:active, + &.active { + background-image: none; + } +} + +// Apply the mixin to the buttons +.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; } +.btn-primary { .btn-styles(@btn-primary-bg); } +.btn-success { .btn-styles(@btn-success-bg); } +.btn-info { .btn-styles(@btn-info-bg); } +.btn-warning { .btn-styles(@btn-warning-bg); } +.btn-danger { .btn-styles(@btn-danger-bg); } + + +// +// Images +// -------------------------------------------------- + +.thumbnail, +.img-thumbnail { + .box-shadow(0 1px 2px rgba(0,0,0,.075)); +} + + +// +// Dropdowns +// -------------------------------------------------- + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%)); + background-color: darken(@dropdown-link-hover-bg, 5%); +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%)); + background-color: darken(@dropdown-link-active-bg, 5%); +} + + +// +// Navbar +// -------------------------------------------------- + +// Default navbar +.navbar-default { + #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg); + .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered + border-radius: @navbar-border-radius; + @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075); + .box-shadow(@shadow); + + .navbar-nav > .open > a, + .navbar-nav > .active > a { + #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%)); + .box-shadow(inset 0 3px 9px rgba(0,0,0,.075)); + } +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255,255,255,.25); +} + +// Inverted navbar +.navbar-inverse { + #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg); + .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257 + + .navbar-nav > .open > a, + .navbar-nav > .active > a { + #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%)); + .box-shadow(inset 0 3px 9px rgba(0,0,0,.25)); + } + + .navbar-brand, + .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + } +} + +// Undo rounded corners in static and fixed navbars +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} + +// Fix active state of dropdown items in collapsed mode +@media (max-width: @grid-float-breakpoint-max) { + .navbar .navbar-nav .open .dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: #fff; + #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%)); + } + } +} + + +// +// Alerts +// -------------------------------------------------- + +// Common styles +.alert { + text-shadow: 0 1px 0 rgba(255,255,255,.2); + @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05); + .box-shadow(@shadow); +} + +// Mixin for generating new styles +.alert-styles(@color) { + #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%)); + border-color: darken(@color, 15%); +} + +// Apply the mixin to the alerts +.alert-success { .alert-styles(@alert-success-bg); } +.alert-info { .alert-styles(@alert-info-bg); } +.alert-warning { .alert-styles(@alert-warning-bg); } +.alert-danger { .alert-styles(@alert-danger-bg); } + + +// +// Progress bars +// -------------------------------------------------- + +// Give the progress background some depth +.progress { + #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg) +} + +// Mixin for generating new styles +.progress-bar-styles(@color) { + #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%)); +} + +// Apply the mixin to the progress bars +.progress-bar { .progress-bar-styles(@progress-bar-bg); } +.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); } +.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); } +.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); } +.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); } + +// Reset the striped class because our mixins don't do multiple gradients and +// the above custom styles override the new `.progress-bar-striped` in v3.2.0. +.progress-bar-striped { + #gradient > .striped(); +} + + +// +// List groups +// -------------------------------------------------- + +.list-group { + border-radius: @border-radius-base; + .box-shadow(0 1px 2px rgba(0,0,0,.075)); +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%); + #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%)); + border-color: darken(@list-group-active-border, 7.5%); + + .badge { + text-shadow: none; + } +} + + +// +// Panels +// -------------------------------------------------- + +// Common styles +.panel { + .box-shadow(0 1px 2px rgba(0,0,0,.05)); +} + +// Mixin for generating new styles +.panel-heading-styles(@color) { + #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%)); +} + +// Apply the mixin to the panel headings only +.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); } +.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); } +.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); } +.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); } +.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); } +.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); } + + +// +// Wells +// -------------------------------------------------- + +.well { + #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg); + border-color: darken(@well-bg, 10%); + @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1); + .box-shadow(@shadow); +} diff --git a/libs/fmt/doc/bootstrap/thumbnails.less b/libs/fmt/doc/bootstrap/thumbnails.less new file mode 100644 index 0000000000000000000000000000000000000000..0713e67d0068e9553c5e21bb6024122029cce520 --- /dev/null +++ b/libs/fmt/doc/bootstrap/thumbnails.less @@ -0,0 +1,36 @@ +// +// Thumbnails +// -------------------------------------------------- + + +// Mixin and adjust the regular image class +.thumbnail { + display: block; + padding: @thumbnail-padding; + margin-bottom: @line-height-computed; + line-height: @line-height-base; + background-color: @thumbnail-bg; + border: 1px solid @thumbnail-border; + border-radius: @thumbnail-border-radius; + .transition(border .2s ease-in-out); + + > img, + a > img { + &:extend(.img-responsive); + margin-left: auto; + margin-right: auto; + } + + // Add a hover state for linked versions only + a&:hover, + a&:focus, + a&.active { + border-color: @link-color; + } + + // Image captions + .caption { + padding: @thumbnail-caption-padding; + color: @thumbnail-caption-color; + } +} diff --git a/libs/fmt/doc/bootstrap/tooltip.less b/libs/fmt/doc/bootstrap/tooltip.less new file mode 100644 index 0000000000000000000000000000000000000000..9d5684743309e74514cebc5e5a2e49d876f38c93 --- /dev/null +++ b/libs/fmt/doc/bootstrap/tooltip.less @@ -0,0 +1,102 @@ +// +// Tooltips +// -------------------------------------------------- + + +// Base class +.tooltip { + position: absolute; + z-index: @zindex-tooltip; + display: block; + // Reset font and text properties given new insertion method + font-family: @font-family-base; + font-size: @font-size-small; + font-weight: normal; + line-height: 1.4; + .opacity(0); + + &.in { .opacity(@tooltip-opacity); } + &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; } + &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; } + &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; } + &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; } +} + +// Wrapper for the tooltip content +.tooltip-inner { + max-width: @tooltip-max-width; + padding: 3px 8px; + color: @tooltip-color; + text-align: center; + text-decoration: none; + background-color: @tooltip-bg; + border-radius: @border-radius-base; +} + +// Arrows +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1 +.tooltip { + &.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width @tooltip-arrow-width 0; + border-top-color: @tooltip-arrow-color; + } + &.top-left .tooltip-arrow { + bottom: 0; + right: @tooltip-arrow-width; + margin-bottom: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width @tooltip-arrow-width 0; + border-top-color: @tooltip-arrow-color; + } + &.top-right .tooltip-arrow { + bottom: 0; + left: @tooltip-arrow-width; + margin-bottom: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width @tooltip-arrow-width 0; + border-top-color: @tooltip-arrow-color; + } + &.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0; + border-right-color: @tooltip-arrow-color; + } + &.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width; + border-left-color: @tooltip-arrow-color; + } + &.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -@tooltip-arrow-width; + border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; + border-bottom-color: @tooltip-arrow-color; + } + &.bottom-left .tooltip-arrow { + top: 0; + right: @tooltip-arrow-width; + margin-top: -@tooltip-arrow-width; + border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; + border-bottom-color: @tooltip-arrow-color; + } + &.bottom-right .tooltip-arrow { + top: 0; + left: @tooltip-arrow-width; + margin-top: -@tooltip-arrow-width; + border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; + border-bottom-color: @tooltip-arrow-color; + } +} diff --git a/libs/fmt/doc/bootstrap/type.less b/libs/fmt/doc/bootstrap/type.less new file mode 100644 index 0000000000000000000000000000000000000000..68ba6017bf184d805a39530f0acd5cbb1495e522 --- /dev/null +++ b/libs/fmt/doc/bootstrap/type.less @@ -0,0 +1,302 @@ +// +// Typography +// -------------------------------------------------- + + +// Headings +// ------------------------- + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + font-family: @headings-font-family; + font-weight: @headings-font-weight; + line-height: @headings-line-height; + color: @headings-color; + + small, + .small { + font-weight: normal; + line-height: 1; + color: @headings-small-color; + } +} + +h1, .h1, +h2, .h2, +h3, .h3 { + margin-top: @line-height-computed; + margin-bottom: (@line-height-computed / 2); + + small, + .small { + font-size: 65%; + } +} +h4, .h4, +h5, .h5, +h6, .h6 { + margin-top: (@line-height-computed / 2); + margin-bottom: (@line-height-computed / 2); + + small, + .small { + font-size: 75%; + } +} + +h1, .h1 { font-size: @font-size-h1; } +h2, .h2 { font-size: @font-size-h2; } +h3, .h3 { font-size: @font-size-h3; } +h4, .h4 { font-size: @font-size-h4; } +h5, .h5 { font-size: @font-size-h5; } +h6, .h6 { font-size: @font-size-h6; } + + +// Body text +// ------------------------- + +p { + margin: 0 0 (@line-height-computed / 2); +} + +.lead { + margin-bottom: @line-height-computed; + font-size: floor((@font-size-base * 1.15)); + font-weight: 300; + line-height: 1.4; + + @media (min-width: @screen-sm-min) { + font-size: (@font-size-base * 1.5); + } +} + + +// Emphasis & misc +// ------------------------- + +// Ex: (12px small font / 14px base font) * 100% = about 85% +small, +.small { + font-size: floor((100% * @font-size-small / @font-size-base)); +} + +mark, +.mark { + background-color: @state-warning-bg; + padding: .2em; +} + +// Alignment +.text-left { text-align: left; } +.text-right { text-align: right; } +.text-center { text-align: center; } +.text-justify { text-align: justify; } +.text-nowrap { white-space: nowrap; } + +// Transformation +.text-lowercase { text-transform: lowercase; } +.text-uppercase { text-transform: uppercase; } +.text-capitalize { text-transform: capitalize; } + +// Contextual colors +.text-muted { + color: @text-muted; +} +.text-primary { + .text-emphasis-variant(@brand-primary); +} +.text-success { + .text-emphasis-variant(@state-success-text); +} +.text-info { + .text-emphasis-variant(@state-info-text); +} +.text-warning { + .text-emphasis-variant(@state-warning-text); +} +.text-danger { + .text-emphasis-variant(@state-danger-text); +} + +// Contextual backgrounds +// For now we'll leave these alongside the text classes until v4 when we can +// safely shift things around (per SemVer rules). +.bg-primary { + // Given the contrast here, this is the only class to have its color inverted + // automatically. + color: #fff; + .bg-variant(@brand-primary); +} +.bg-success { + .bg-variant(@state-success-bg); +} +.bg-info { + .bg-variant(@state-info-bg); +} +.bg-warning { + .bg-variant(@state-warning-bg); +} +.bg-danger { + .bg-variant(@state-danger-bg); +} + + +// Page header +// ------------------------- + +.page-header { + padding-bottom: ((@line-height-computed / 2) - 1); + margin: (@line-height-computed * 2) 0 @line-height-computed; + border-bottom: 1px solid @page-header-border-color; +} + + +// Lists +// ------------------------- + +// Unordered and Ordered lists +ul, +ol { + margin-top: 0; + margin-bottom: (@line-height-computed / 2); + ul, + ol { + margin-bottom: 0; + } +} + +// List options + +// Unstyled keeps list items block level, just removes default browser padding and list-style +.list-unstyled { + padding-left: 0; + list-style: none; +} + +// Inline turns list items into inline-block +.list-inline { + .list-unstyled(); + margin-left: -5px; + + > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; + } +} + +// Description Lists +dl { + margin-top: 0; // Remove browser default + margin-bottom: @line-height-computed; +} +dt, +dd { + line-height: @line-height-base; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; // Undo browser default +} + +// Horizontal description lists +// +// Defaults to being stacked without any of the below styles applied, until the +// grid breakpoint is reached (default of ~768px). + +.dl-horizontal { + dd { + &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present + } + + @media (min-width: @grid-float-breakpoint) { + dt { + float: left; + width: (@dl-horizontal-offset - 20); + clear: left; + text-align: right; + .text-overflow(); + } + dd { + margin-left: @dl-horizontal-offset; + } + } +} + + +// Misc +// ------------------------- + +// Abbreviations and acronyms +abbr[title], +// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted @abbr-border-color; +} +.initialism { + font-size: 90%; + .text-uppercase(); +} + +// Blockquotes +blockquote { + padding: (@line-height-computed / 2) @line-height-computed; + margin: 0 0 @line-height-computed; + font-size: @blockquote-font-size; + border-left: 5px solid @blockquote-border-color; + + p, + ul, + ol { + &:last-child { + margin-bottom: 0; + } + } + + // Note: Deprecated small and .small as of v3.1.0 + // Context: https://github.com/twbs/bootstrap/issues/11660 + footer, + small, + .small { + display: block; + font-size: 80%; // back to default font-size + line-height: @line-height-base; + color: @blockquote-small-color; + + &:before { + content: '\2014 \00A0'; // em dash, nbsp + } + } +} + +// Opposite alignment of blockquote +// +// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0. +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid @blockquote-border-color; + border-left: 0; + text-align: right; + + // Account for citation + footer, + small, + .small { + &:before { content: ''; } + &:after { + content: '\00A0 \2014'; // nbsp, em dash + } + } +} + +// Addresses +address { + margin-bottom: @line-height-computed; + font-style: normal; + line-height: @line-height-base; +} diff --git a/libs/fmt/doc/bootstrap/utilities.less b/libs/fmt/doc/bootstrap/utilities.less new file mode 100644 index 0000000000000000000000000000000000000000..7a8ca27a8ff54db1c439812d4b295d9a1c4f6f76 --- /dev/null +++ b/libs/fmt/doc/bootstrap/utilities.less @@ -0,0 +1,55 @@ +// +// Utility classes +// -------------------------------------------------- + + +// Floats +// ------------------------- + +.clearfix { + .clearfix(); +} +.center-block { + .center-block(); +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} + + +// Toggling content +// ------------------------- + +// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1 +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + .text-hide(); +} + + +// Hide from screenreaders and browsers +// +// Credit: HTML5 Boilerplate + +.hidden { + display: none !important; +} + + +// For Affix plugin +// ------------------------- + +.affix { + position: fixed; +} diff --git a/libs/fmt/doc/bootstrap/variables.less b/libs/fmt/doc/bootstrap/variables.less new file mode 100644 index 0000000000000000000000000000000000000000..b5fb9d02cf5197e50517a60dae2e3447a032cfe5 --- /dev/null +++ b/libs/fmt/doc/bootstrap/variables.less @@ -0,0 +1,861 @@ +// +// Variables +// -------------------------------------------------- + + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +@gray-base: #000; +@gray-darker: lighten(@gray-base, 13.5%); // #222 +@gray-dark: lighten(@gray-base, 20%); // #333 +@gray: lighten(@gray-base, 33.5%); // #555 +@gray-light: lighten(@gray-base, 46.7%); // #777 +@gray-lighter: lighten(@gray-base, 93.5%); // #eee + +@brand-primary: darken(#428bca, 6.5%); // #337ab7 +@brand-success: #5cb85c; +@brand-info: #5bc0de; +@brand-warning: #f0ad4e; +@brand-danger: #d9534f; + + +//== Scaffolding +// +//## Settings for some of the most global styles. + +//** Background color for `<body>`. +@body-bg: #fff; +//** Global text color on `<body>`. +@text-color: @gray-dark; + +//** Global textual link color. +@link-color: @brand-primary; +//** Link hover color set via `darken()` function. +@link-hover-color: darken(@link-color, 15%); +//** Link hover decoration. +@link-hover-decoration: underline; + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif; +@font-family-serif: Georgia, "Times New Roman", Times, serif; +//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`. +@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; +@font-family-base: @font-family-sans-serif; + +@font-size-base: 14px; +@font-size-large: ceil((@font-size-base * 1.25)); // ~18px +@font-size-small: ceil((@font-size-base * 0.85)); // ~12px + +@font-size-h1: floor((@font-size-base * 2.6)); // ~36px +@font-size-h2: floor((@font-size-base * 2.15)); // ~30px +@font-size-h3: ceil((@font-size-base * 1.7)); // ~24px +@font-size-h4: ceil((@font-size-base * 1.25)); // ~18px +@font-size-h5: @font-size-base; +@font-size-h6: ceil((@font-size-base * 0.85)); // ~12px + +//** Unit-less `line-height` for use in components like buttons. +@line-height-base: 1.428571429; // 20/14 +//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc. +@line-height-computed: floor((@font-size-base * @line-height-base)); // ~20px + +//** By default, this inherits from the `<body>`. +@headings-font-family: inherit; +@headings-font-weight: 500; +@headings-line-height: 1.1; +@headings-color: inherit; + + +//== Iconography +// +//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower. + +//** Load fonts from this directory. +@icon-font-path: "../fonts/"; +//** File name for all font files. +@icon-font-name: "glyphicons-halflings-regular"; +//** Element ID within SVG icon file. +@icon-font-svg-id: "glyphicons_halflingsregular"; + + +//== Components +// +//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). + +@padding-base-vertical: 6px; +@padding-base-horizontal: 12px; + +@padding-large-vertical: 10px; +@padding-large-horizontal: 16px; + +@padding-small-vertical: 5px; +@padding-small-horizontal: 10px; + +@padding-xs-vertical: 1px; +@padding-xs-horizontal: 5px; + +@line-height-large: 1.3333333; // extra decimals for Win 8.1 Chrome +@line-height-small: 1.5; + +@border-radius-base: 4px; +@border-radius-large: 6px; +@border-radius-small: 3px; + +//** Global color for active items (e.g., navs or dropdowns). +@component-active-color: #fff; +//** Global background color for active items (e.g., navs or dropdowns). +@component-active-bg: @brand-primary; + +//** Width of the `border` for generating carets that indicator dropdowns. +@caret-width-base: 4px; +//** Carets increase slightly in size for larger components. +@caret-width-large: 5px; + + +//== Tables +// +//## Customizes the `.table` component with basic values, each used across all table variations. + +//** Padding for `<th>`s and `<td>`s. +@table-cell-padding: 8px; +//** Padding for cells in `.table-condensed`. +@table-condensed-cell-padding: 5px; + +//** Default background color used for all tables. +@table-bg: transparent; +//** Background color used for `.table-striped`. +@table-bg-accent: #f9f9f9; +//** Background color used for `.table-hover`. +@table-bg-hover: #f5f5f5; +@table-bg-active: @table-bg-hover; + +//** Border color for table and cell borders. +@table-border-color: #ddd; + + +//== Buttons +// +//## For each of Bootstrap's buttons, define text, background and border color. + +@btn-font-weight: normal; + +@btn-default-color: #333; +@btn-default-bg: #fff; +@btn-default-border: #ccc; + +@btn-primary-color: #fff; +@btn-primary-bg: @brand-primary; +@btn-primary-border: darken(@btn-primary-bg, 5%); + +@btn-success-color: #fff; +@btn-success-bg: @brand-success; +@btn-success-border: darken(@btn-success-bg, 5%); + +@btn-info-color: #fff; +@btn-info-bg: @brand-info; +@btn-info-border: darken(@btn-info-bg, 5%); + +@btn-warning-color: #fff; +@btn-warning-bg: @brand-warning; +@btn-warning-border: darken(@btn-warning-bg, 5%); + +@btn-danger-color: #fff; +@btn-danger-bg: @brand-danger; +@btn-danger-border: darken(@btn-danger-bg, 5%); + +@btn-link-disabled-color: @gray-light; + + +//== Forms +// +//## + +//** `<input>` background color +@input-bg: #fff; +//** `<input disabled>` background color +@input-bg-disabled: @gray-lighter; + +//** Text color for `<input>`s +@input-color: @gray; +//** `<input>` border color +@input-border: #ccc; + +// TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4 +//** Default `.form-control` border radius +// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS. +@input-border-radius: @border-radius-base; +//** Large `.form-control` border radius +@input-border-radius-large: @border-radius-large; +//** Small `.form-control` border radius +@input-border-radius-small: @border-radius-small; + +//** Border color for inputs on focus +@input-border-focus: #66afe9; + +//** Placeholder text color +@input-color-placeholder: #999; + +//** Default `.form-control` height +@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2); +//** Large `.form-control` height +@input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2); +//** Small `.form-control` height +@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2); + +//** `.form-group` margin +@form-group-margin-bottom: 15px; + +@legend-color: @gray-dark; +@legend-border-color: #e5e5e5; + +//** Background color for textual input addons +@input-group-addon-bg: @gray-lighter; +//** Border color for textual input addons +@input-group-addon-border-color: @input-border; + +//** Disabled cursor for form controls and buttons. +@cursor-disabled: not-allowed; + + +//== Dropdowns +// +//## Dropdown menu container and contents. + +//** Background for the dropdown menu. +@dropdown-bg: #fff; +//** Dropdown menu `border-color`. +@dropdown-border: rgba(0,0,0,.15); +//** Dropdown menu `border-color` **for IE8**. +@dropdown-fallback-border: #ccc; +//** Divider color for between dropdown items. +@dropdown-divider-bg: #e5e5e5; + +//** Dropdown link text color. +@dropdown-link-color: @gray-dark; +//** Hover color for dropdown links. +@dropdown-link-hover-color: darken(@gray-dark, 5%); +//** Hover background for dropdown links. +@dropdown-link-hover-bg: #f5f5f5; + +//** Active dropdown menu item text color. +@dropdown-link-active-color: @component-active-color; +//** Active dropdown menu item background color. +@dropdown-link-active-bg: @component-active-bg; + +//** Disabled dropdown menu item background color. +@dropdown-link-disabled-color: @gray-light; + +//** Text color for headers within dropdown menus. +@dropdown-header-color: @gray-light; + +//** Deprecated `@dropdown-caret-color` as of v3.1.0 +@dropdown-caret-color: #000; + + +//-- Z-index master list +// +// Warning: Avoid customizing these values. They're used for a bird's eye view +// of components dependent on the z-axis and are designed to all work together. +// +// Note: These variables are not generated into the Customizer. + +@zindex-navbar: 1000; +@zindex-dropdown: 1000; +@zindex-popover: 1060; +@zindex-tooltip: 1070; +@zindex-navbar-fixed: 1030; +@zindex-modal-background: 1040; +@zindex-modal: 1050; + + +//== Media queries breakpoints +// +//## Define the breakpoints at which your layout will change, adapting to different screen sizes. + +// Extra small screen / phone +//** Deprecated `@screen-xs` as of v3.0.1 +@screen-xs: 480px; +//** Deprecated `@screen-xs-min` as of v3.2.0 +@screen-xs-min: @screen-xs; +//** Deprecated `@screen-phone` as of v3.0.1 +@screen-phone: @screen-xs-min; + +// Small screen / tablet +//** Deprecated `@screen-sm` as of v3.0.1 +@screen-sm: 768px; +@screen-sm-min: @screen-sm; +//** Deprecated `@screen-tablet` as of v3.0.1 +@screen-tablet: @screen-sm-min; + +// Medium screen / desktop +//** Deprecated `@screen-md` as of v3.0.1 +@screen-md: 992px; +@screen-md-min: @screen-md; +//** Deprecated `@screen-desktop` as of v3.0.1 +@screen-desktop: @screen-md-min; + +// Large screen / wide desktop +//** Deprecated `@screen-lg` as of v3.0.1 +@screen-lg: 1200px; +@screen-lg-min: @screen-lg; +//** Deprecated `@screen-lg-desktop` as of v3.0.1 +@screen-lg-desktop: @screen-lg-min; + +// So media queries don't overlap when required, provide a maximum +@screen-xs-max: (@screen-sm-min - 1); +@screen-sm-max: (@screen-md-min - 1); +@screen-md-max: (@screen-lg-min - 1); + + +//== Grid system +// +//## Define your custom responsive grid. + +//** Number of columns in the grid. +@grid-columns: 12; +//** Padding between columns. Gets divided in half for the left and right. +@grid-gutter-width: 30px; +// Navbar collapse +//** Point at which the navbar becomes uncollapsed. +@grid-float-breakpoint: @screen-sm-min; +//** Point at which the navbar begins collapsing. +@grid-float-breakpoint-max: (@grid-float-breakpoint - 1); + + +//== Container sizes +// +//## Define the maximum width of `.container` for different screen sizes. + +// Small screen / tablet +@container-tablet: (720px + @grid-gutter-width); +//** For `@screen-sm-min` and up. +@container-sm: @container-tablet; + +// Medium screen / desktop +@container-desktop: (940px + @grid-gutter-width); +//** For `@screen-md-min` and up. +@container-md: @container-desktop; + +// Large screen / wide desktop +@container-large-desktop: (1140px + @grid-gutter-width); +//** For `@screen-lg-min` and up. +@container-lg: @container-large-desktop; + + +//== Navbar +// +//## + +// Basics of a navbar +@navbar-height: 50px; +@navbar-margin-bottom: @line-height-computed; +@navbar-border-radius: @border-radius-base; +@navbar-padding-horizontal: floor((@grid-gutter-width / 2)); +@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2); +@navbar-collapse-max-height: 340px; + +@navbar-default-color: #777; +@navbar-default-bg: #f8f8f8; +@navbar-default-border: darken(@navbar-default-bg, 6.5%); + +// Navbar links +@navbar-default-link-color: #777; +@navbar-default-link-hover-color: #333; +@navbar-default-link-hover-bg: transparent; +@navbar-default-link-active-color: #555; +@navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%); +@navbar-default-link-disabled-color: #ccc; +@navbar-default-link-disabled-bg: transparent; + +// Navbar brand label +@navbar-default-brand-color: @navbar-default-link-color; +@navbar-default-brand-hover-color: darken(@navbar-default-brand-color, 10%); +@navbar-default-brand-hover-bg: transparent; + +// Navbar toggle +@navbar-default-toggle-hover-bg: #ddd; +@navbar-default-toggle-icon-bar-bg: #888; +@navbar-default-toggle-border-color: #ddd; + + +// Inverted navbar +// Reset inverted navbar basics +@navbar-inverse-color: lighten(@gray-light, 15%); +@navbar-inverse-bg: #222; +@navbar-inverse-border: darken(@navbar-inverse-bg, 10%); + +// Inverted navbar links +@navbar-inverse-link-color: lighten(@gray-light, 15%); +@navbar-inverse-link-hover-color: #fff; +@navbar-inverse-link-hover-bg: transparent; +@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color; +@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%); +@navbar-inverse-link-disabled-color: #444; +@navbar-inverse-link-disabled-bg: transparent; + +// Inverted navbar brand label +@navbar-inverse-brand-color: @navbar-inverse-link-color; +@navbar-inverse-brand-hover-color: #fff; +@navbar-inverse-brand-hover-bg: transparent; + +// Inverted navbar toggle +@navbar-inverse-toggle-hover-bg: #333; +@navbar-inverse-toggle-icon-bar-bg: #fff; +@navbar-inverse-toggle-border-color: #333; + + +//== Navs +// +//## + +//=== Shared nav styles +@nav-link-padding: 10px 15px; +@nav-link-hover-bg: @gray-lighter; + +@nav-disabled-link-color: @gray-light; +@nav-disabled-link-hover-color: @gray-light; + +//== Tabs +@nav-tabs-border-color: #ddd; + +@nav-tabs-link-hover-border-color: @gray-lighter; + +@nav-tabs-active-link-hover-bg: @body-bg; +@nav-tabs-active-link-hover-color: @gray; +@nav-tabs-active-link-hover-border-color: #ddd; + +@nav-tabs-justified-link-border-color: #ddd; +@nav-tabs-justified-active-link-border-color: @body-bg; + +//== Pills +@nav-pills-border-radius: @border-radius-base; +@nav-pills-active-link-hover-bg: @component-active-bg; +@nav-pills-active-link-hover-color: @component-active-color; + + +//== Pagination +// +//## + +@pagination-color: @link-color; +@pagination-bg: #fff; +@pagination-border: #ddd; + +@pagination-hover-color: @link-hover-color; +@pagination-hover-bg: @gray-lighter; +@pagination-hover-border: #ddd; + +@pagination-active-color: #fff; +@pagination-active-bg: @brand-primary; +@pagination-active-border: @brand-primary; + +@pagination-disabled-color: @gray-light; +@pagination-disabled-bg: #fff; +@pagination-disabled-border: #ddd; + + +//== Pager +// +//## + +@pager-bg: @pagination-bg; +@pager-border: @pagination-border; +@pager-border-radius: 15px; + +@pager-hover-bg: @pagination-hover-bg; + +@pager-active-bg: @pagination-active-bg; +@pager-active-color: @pagination-active-color; + +@pager-disabled-color: @pagination-disabled-color; + + +//== Jumbotron +// +//## + +@jumbotron-padding: 30px; +@jumbotron-color: inherit; +@jumbotron-bg: @gray-lighter; +@jumbotron-heading-color: inherit; +@jumbotron-font-size: ceil((@font-size-base * 1.5)); + + +//== Form states and alerts +// +//## Define colors for form feedback states and, by default, alerts. + +@state-success-text: #3c763d; +@state-success-bg: #dff0d8; +@state-success-border: darken(spin(@state-success-bg, -10), 5%); + +@state-info-text: #31708f; +@state-info-bg: #d9edf7; +@state-info-border: darken(spin(@state-info-bg, -10), 7%); + +@state-warning-text: #8a6d3b; +@state-warning-bg: #fcf8e3; +@state-warning-border: darken(spin(@state-warning-bg, -10), 5%); + +@state-danger-text: #a94442; +@state-danger-bg: #f2dede; +@state-danger-border: darken(spin(@state-danger-bg, -10), 5%); + + +//== Tooltips +// +//## + +//** Tooltip max width +@tooltip-max-width: 200px; +//** Tooltip text color +@tooltip-color: #fff; +//** Tooltip background color +@tooltip-bg: #000; +@tooltip-opacity: .9; + +//** Tooltip arrow width +@tooltip-arrow-width: 5px; +//** Tooltip arrow color +@tooltip-arrow-color: @tooltip-bg; + + +//== Popovers +// +//## + +//** Popover body background color +@popover-bg: #fff; +//** Popover maximum width +@popover-max-width: 276px; +//** Popover border color +@popover-border-color: rgba(0,0,0,.2); +//** Popover fallback border color +@popover-fallback-border-color: #ccc; + +//** Popover title background color +@popover-title-bg: darken(@popover-bg, 3%); + +//** Popover arrow width +@popover-arrow-width: 10px; +//** Popover arrow color +@popover-arrow-color: @popover-bg; + +//** Popover outer arrow width +@popover-arrow-outer-width: (@popover-arrow-width + 1); +//** Popover outer arrow color +@popover-arrow-outer-color: fadein(@popover-border-color, 5%); +//** Popover outer arrow fallback color +@popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%); + + +//== Labels +// +//## + +//** Default label background color +@label-default-bg: @gray-light; +//** Primary label background color +@label-primary-bg: @brand-primary; +//** Success label background color +@label-success-bg: @brand-success; +//** Info label background color +@label-info-bg: @brand-info; +//** Warning label background color +@label-warning-bg: @brand-warning; +//** Danger label background color +@label-danger-bg: @brand-danger; + +//** Default label text color +@label-color: #fff; +//** Default text color of a linked label +@label-link-hover-color: #fff; + + +//== Modals +// +//## + +//** Padding applied to the modal body +@modal-inner-padding: 15px; + +//** Padding applied to the modal title +@modal-title-padding: 15px; +//** Modal title line-height +@modal-title-line-height: @line-height-base; + +//** Background color of modal content area +@modal-content-bg: #fff; +//** Modal content border color +@modal-content-border-color: rgba(0,0,0,.2); +//** Modal content border color **for IE8** +@modal-content-fallback-border-color: #999; + +//** Modal backdrop background color +@modal-backdrop-bg: #000; +//** Modal backdrop opacity +@modal-backdrop-opacity: .5; +//** Modal header border color +@modal-header-border-color: #e5e5e5; +//** Modal footer border color +@modal-footer-border-color: @modal-header-border-color; + +@modal-lg: 900px; +@modal-md: 600px; +@modal-sm: 300px; + + +//== Alerts +// +//## Define alert colors, border radius, and padding. + +@alert-padding: 15px; +@alert-border-radius: @border-radius-base; +@alert-link-font-weight: bold; + +@alert-success-bg: @state-success-bg; +@alert-success-text: @state-success-text; +@alert-success-border: @state-success-border; + +@alert-info-bg: @state-info-bg; +@alert-info-text: @state-info-text; +@alert-info-border: @state-info-border; + +@alert-warning-bg: @state-warning-bg; +@alert-warning-text: @state-warning-text; +@alert-warning-border: @state-warning-border; + +@alert-danger-bg: @state-danger-bg; +@alert-danger-text: @state-danger-text; +@alert-danger-border: @state-danger-border; + + +//== Progress bars +// +//## + +//** Background color of the whole progress component +@progress-bg: #f5f5f5; +//** Progress bar text color +@progress-bar-color: #fff; +//** Variable for setting rounded corners on progress bar. +@progress-border-radius: @border-radius-base; + +//** Default progress bar color +@progress-bar-bg: @brand-primary; +//** Success progress bar color +@progress-bar-success-bg: @brand-success; +//** Warning progress bar color +@progress-bar-warning-bg: @brand-warning; +//** Danger progress bar color +@progress-bar-danger-bg: @brand-danger; +//** Info progress bar color +@progress-bar-info-bg: @brand-info; + + +//== List group +// +//## + +//** Background color on `.list-group-item` +@list-group-bg: #fff; +//** `.list-group-item` border color +@list-group-border: #ddd; +//** List group border radius +@list-group-border-radius: @border-radius-base; + +//** Background color of single list items on hover +@list-group-hover-bg: #f5f5f5; +//** Text color of active list items +@list-group-active-color: @component-active-color; +//** Background color of active list items +@list-group-active-bg: @component-active-bg; +//** Border color of active list elements +@list-group-active-border: @list-group-active-bg; +//** Text color for content within active list items +@list-group-active-text-color: lighten(@list-group-active-bg, 40%); + +//** Text color of disabled list items +@list-group-disabled-color: @gray-light; +//** Background color of disabled list items +@list-group-disabled-bg: @gray-lighter; +//** Text color for content within disabled list items +@list-group-disabled-text-color: @list-group-disabled-color; + +@list-group-link-color: #555; +@list-group-link-hover-color: @list-group-link-color; +@list-group-link-heading-color: #333; + + +//== Panels +// +//## + +@panel-bg: #fff; +@panel-body-padding: 15px; +@panel-heading-padding: 10px 15px; +@panel-footer-padding: @panel-heading-padding; +@panel-border-radius: @border-radius-base; + +//** Border color for elements within panels +@panel-inner-border: #ddd; +@panel-footer-bg: #f5f5f5; + +@panel-default-text: @gray-dark; +@panel-default-border: #ddd; +@panel-default-heading-bg: #f5f5f5; + +@panel-primary-text: #fff; +@panel-primary-border: @brand-primary; +@panel-primary-heading-bg: @brand-primary; + +@panel-success-text: @state-success-text; +@panel-success-border: @state-success-border; +@panel-success-heading-bg: @state-success-bg; + +@panel-info-text: @state-info-text; +@panel-info-border: @state-info-border; +@panel-info-heading-bg: @state-info-bg; + +@panel-warning-text: @state-warning-text; +@panel-warning-border: @state-warning-border; +@panel-warning-heading-bg: @state-warning-bg; + +@panel-danger-text: @state-danger-text; +@panel-danger-border: @state-danger-border; +@panel-danger-heading-bg: @state-danger-bg; + + +//== Thumbnails +// +//## + +//** Padding around the thumbnail image +@thumbnail-padding: 4px; +//** Thumbnail background color +@thumbnail-bg: @body-bg; +//** Thumbnail border color +@thumbnail-border: #ddd; +//** Thumbnail border radius +@thumbnail-border-radius: @border-radius-base; + +//** Custom text color for thumbnail captions +@thumbnail-caption-color: @text-color; +//** Padding around the thumbnail caption +@thumbnail-caption-padding: 9px; + + +//== Wells +// +//## + +@well-bg: #f5f5f5; +@well-border: darken(@well-bg, 7%); + + +//== Badges +// +//## + +@badge-color: #fff; +//** Linked badge text color on hover +@badge-link-hover-color: #fff; +@badge-bg: @gray-light; + +//** Badge text color in active nav link +@badge-active-color: @link-color; +//** Badge background color in active nav link +@badge-active-bg: #fff; + +@badge-font-weight: bold; +@badge-line-height: 1; +@badge-border-radius: 10px; + + +//== Breadcrumbs +// +//## + +@breadcrumb-padding-vertical: 8px; +@breadcrumb-padding-horizontal: 15px; +//** Breadcrumb background color +@breadcrumb-bg: #f5f5f5; +//** Breadcrumb text color +@breadcrumb-color: #ccc; +//** Text color of current page in the breadcrumb +@breadcrumb-active-color: @gray-light; +//** Textual separator for between breadcrumb elements +@breadcrumb-separator: "/"; + + +//== Carousel +// +//## + +@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6); + +@carousel-control-color: #fff; +@carousel-control-width: 15%; +@carousel-control-opacity: .5; +@carousel-control-font-size: 20px; + +@carousel-indicator-active-bg: #fff; +@carousel-indicator-border-color: #fff; + +@carousel-caption-color: #fff; + + +//== Close +// +//## + +@close-font-weight: bold; +@close-color: #000; +@close-text-shadow: 0 1px 0 #fff; + + +//== Code +// +//## + +@code-color: #c7254e; +@code-bg: #f9f2f4; + +@kbd-color: #fff; +@kbd-bg: #333; + +@pre-bg: #f5f5f5; +@pre-color: @gray-dark; +@pre-border-color: #ccc; +@pre-scrollable-max-height: 340px; + + +//== Type +// +//## + +//** Horizontal offset for forms and lists. +@component-offset-horizontal: 180px; +//** Text muted color +@text-muted: @gray-light; +//** Abbreviations and acronyms border color +@abbr-border-color: @gray-light; +//** Headings small color +@headings-small-color: @gray-light; +//** Blockquote small color +@blockquote-small-color: @gray-light; +//** Blockquote font size +@blockquote-font-size: (@font-size-base * 1.25); +//** Blockquote border color +@blockquote-border-color: @gray-lighter; +//** Page header border color +@page-header-border-color: @gray-lighter; +//** Width of horizontal description list titles +@dl-horizontal-offset: @component-offset-horizontal; +//** Horizontal line color. +@hr-border: @gray-lighter; diff --git a/libs/fmt/doc/bootstrap/wells.less b/libs/fmt/doc/bootstrap/wells.less new file mode 100644 index 0000000000000000000000000000000000000000..15d072b0cd0e31d6bd40fad5aa1cb8fb1fbdefd9 --- /dev/null +++ b/libs/fmt/doc/bootstrap/wells.less @@ -0,0 +1,29 @@ +// +// Wells +// -------------------------------------------------- + + +// Base class +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: @well-bg; + border: 1px solid @well-border; + border-radius: @border-radius-base; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); + blockquote { + border-color: #ddd; + border-color: rgba(0,0,0,.15); + } +} + +// Sizes +.well-lg { + padding: 24px; + border-radius: @border-radius-large; +} +.well-sm { + padding: 9px; + border-radius: @border-radius-small; +} diff --git a/libs/fmt/doc/build.py b/libs/fmt/doc/build.py new file mode 100644 index 0000000000000000000000000000000000000000..109d8f9dcc182d7f30e0aa12f36eb746e74ee751 --- /dev/null +++ b/libs/fmt/doc/build.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# Build the documentation. + +import errno, os, re, sys +from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT + +versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3'] + +class Pip: + def __init__(self, venv_dir): + self.path = os.path.join(venv_dir, 'bin', 'pip') + + def install(self, package, commit=None): + "Install package using pip." + if commit: + package = 'git+https://github.com/{0}.git@{1}'.format(package, commit) + print('Installing {0}'.format(package)) + check_call([self.path, 'install', package]) + +def create_build_env(venv_dir='virtualenv'): + # Create virtualenv. + if not os.path.exists(venv_dir): + check_call(['python3', '-m', 'venv', venv_dir]) + # Install Sphinx and Breathe. Require the exact version of Sphinx which is + # compatible with Breathe. + pip = Pip(venv_dir) + pip.install('six') + pip.install('sphinx-doc/sphinx', 'v3.3.0') + pip.install('michaeljones/breathe', 'v4.23.0') + +def build_docs(version='dev', **kwargs): + doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__))) + work_dir = kwargs.get('work_dir', '.') + include_dir = kwargs.get( + 'include_dir', os.path.join(os.path.dirname(doc_dir), 'include', 'fmt')) + # Build docs. + cmd = ['doxygen', '-'] + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + doxyxml_dir = os.path.join(work_dir, 'doxyxml') + out, _ = p.communicate(input=r''' + PROJECT_NAME = fmt + GENERATE_LATEX = NO + GENERATE_MAN = NO + GENERATE_RTF = NO + CASE_SENSE_NAMES = NO + INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \ + {0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h + QUIET = YES + JAVADOC_AUTOBRIEF = YES + AUTOLINK_SUPPORT = NO + GENERATE_HTML = NO + GENERATE_XML = YES + XML_OUTPUT = {1} + ALIASES = "rst=\verbatim embed:rst" + ALIASES += "endrst=\endverbatim" + MACRO_EXPANSION = YES + PREDEFINED = _WIN32=1 \ + __linux__=1 \ + FMT_USE_VARIADIC_TEMPLATES=1 \ + FMT_USE_RVALUE_REFERENCES=1 \ + FMT_USE_USER_DEFINED_LITERALS=1 \ + FMT_USE_ALIAS_TEMPLATES=1 \ + FMT_API= \ + "FMT_BEGIN_NAMESPACE=namespace fmt {{" \ + "FMT_END_NAMESPACE=}}" \ + "FMT_STRING_ALIAS=1" \ + "FMT_DOC=1" + EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \ + fmt::basic_format_arg::handle + '''.format(include_dir, doxyxml_dir).encode('UTF-8')) + internal_symbols = [ + 'fmt::detail::.*', + 'basic_data<>', + 'fmt::type_identity', + 'fmt::dynamic_formatter' + ] + noisy_warnings = [ + 'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \ + ') is not documented.', + 'warning: Internal inconsistency: .* does not belong to any container!' + ] + for w in noisy_warnings: + out = re.sub('.*' + w + '\n', '', out) + print(out) + if p.returncode != 0: + raise CalledProcessError(p.returncode, cmd) + + html_dir = os.path.join(work_dir, 'html') + main_versions = reversed(versions[-3:]) + check_call([os.path.join('virtualenv', 'bin', 'sphinx-build'), + '-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir), + '-Dversion=' + version, '-Drelease=' + version, + '-Aversion=' + version, '-Aversions=' + ','.join(main_versions), + '-b', 'html', doc_dir, html_dir]) + try: + check_call(['lessc', '--verbose', '--clean-css', + '--include-path=' + os.path.join(doc_dir, 'bootstrap'), + os.path.join(doc_dir, 'fmt.less'), + os.path.join(html_dir, '_static', 'fmt.css')]) + except OSError as e: + if e.errno != errno.ENOENT: + raise + print('lessc not found; make sure that Less (http://lesscss.org/) ' + + 'is installed') + sys.exit(1) + return html_dir + +if __name__ == '__main__': + create_build_env() + build_docs(sys.argv[1]) diff --git a/libs/fmt/doc/conf.py b/libs/fmt/doc/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..0756192b6065f1b58f37b7aff42395f52278d83a --- /dev/null +++ b/libs/fmt/doc/conf.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +# +# format documentation build configuration file, created by +# sphinx-quickstart on Tue Dec 18 06:46:16 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os, re, subprocess + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.2' + +if os.environ.get('READTHEDOCS', None) == 'True': + subprocess.call('doxygen') + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.ifconfig', 'breathe'] + +breathe_default_project = "format" +breathe_domain_by_extension = {"h" : "cpp"} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +#master_doc = 'contents' + +# General information about the project. +project = u'fmt' +copyright = u'2012-present, Victor Zverovich' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. + +# Version and release are passed from CMake. +#version = None + +# The full version, including alpha/beta/rc tags. +#release = version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['virtualenv'] + +# The reST default role (used for this markup: `text`) to use for all documents. +default_role = 'cpp:any' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +highlight_language = 'c++' + +primary_domain = 'cpp' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'basic-bootstrap' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ['.'] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + '**': ['localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'formatdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'format.tex', u'fmt documentation', + u'Victor Zverovich', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'fmt', u'fmt documentation', [u'Victor Zverovich'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'fmt', u'fmt documentation', + u'Victor Zverovich', 'fmt', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/libs/fmt/doc/contents.rst b/libs/fmt/doc/contents.rst new file mode 100644 index 0000000000000000000000000000000000000000..d38691e41b102cdab52a5c3d09909ed639859bda --- /dev/null +++ b/libs/fmt/doc/contents.rst @@ -0,0 +1,10 @@ +######## +Contents +######## + +.. toctree:: + :maxdepth: 2 + + usage + api + syntax diff --git a/libs/fmt/doc/fmt.less b/libs/fmt/doc/fmt.less new file mode 100644 index 0000000000000000000000000000000000000000..55c716b816b1df863d5e760a327b2592478d1bdb --- /dev/null +++ b/libs/fmt/doc/fmt.less @@ -0,0 +1,66 @@ +@import 'bootstrap.less'; + +@header-bg: #094d75; +@icon-font-path: "fonts/"; + +html { + overflow-y: scroll; +} + +.navbar { + border-radius: 0; + margin-bottom: 0; + background-color: darken(@header-bg, 10%); +} + +.jumbotron { + #gradient > .vertical(@header-bg; darken(@header-bg, 2%); 50%; 50%); + background-size: 100% 4px; + background-color: @header-bg; + background-repeat: repeat-y; + color: white; + text-align: center; +} + +div.sphinxsidebar { + margin-left: 0; +} + +// Keep content not too wide for better readability. +.navbar-content, .content { + .make-md-column-offset(1); + .make-md-column(10); + .make-lg-column-offset(2); + .make-lg-column(8); +} + +.footer { + padding-top: 20px; + padding-bottom: 20px; + border-top: 1px solid @gray-lighter; + text-align: center; +} + +// Indent descriptions of classes, functions and macros. +.class dd, .function dd, .macro dd { + margin-left: 20px; +} + +// Remove Bootstrap padding for Sphinx containers. +.breathe-sectiondef.container { + padding: 0; +} + +// Remove Bootstrap padding for Sphinx code elements in API signatures. +.descclassname, .descname { + padding: 0; +} + +p.rubric { + margin-top: 10px; +} + +.github-btn { + border: 0; + overflow: hidden; +} diff --git a/libs/fmt/doc/index.rst b/libs/fmt/doc/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..a2f4bd95c059875ba239a5900a75eb57f8ac4077 --- /dev/null +++ b/libs/fmt/doc/index.rst @@ -0,0 +1,202 @@ +Overview +======== + +**{fmt}** is an open-source formatting library providing a fast and safe +alternative to C stdio and C++ iostreams. + +.. raw:: html + + <div class="panel panel-default"> + <div class="panel-heading">What users say:</div> + <div class="panel-body"> + Thanks for creating this library. It’s been a hole in C++ for + a long time. I’ve used both <code>boost::format</code> and + <code>loki::SPrintf</code>, and neither felt like the right answer. + This does. + </div> + </div> + +.. _format-api-intro: + +Format API +---------- + +The format API is similar in spirit to the C ``printf`` family of function but +is safer, simpler and several times `faster +<https://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_ +than common standard library implementations. +The `format string syntax <syntax.html>`_ is similar to the one used by +`str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_ in +Python: + +.. code:: c++ + + fmt::format("The answer is {}.", 42); + +The ``fmt::format`` function returns a string "The answer is 42.". You can use +``fmt::memory_buffer`` to avoid constructing ``std::string``: + +.. code:: c++ + + fmt::memory_buffer out; + format_to(out, "For a moment, {} happened.", "nothing"); + out.data(); // returns a pointer to the formatted data + +The ``fmt::print`` function performs formatting and writes the result to a stream: + +.. code:: c++ + + fmt::print(stderr, "System error code = {}\n", errno); + +If you omit the file argument the function will print to ``stdout``: + +.. code:: c++ + + fmt::print("Don't {}\n", "panic"); + +The format API also supports positional arguments useful for localization: + +.. code:: c++ + + fmt::print("I'd rather be {1} than {0}.", "right", "happy"); + +You can pass named arguments with ``fmt::arg``: + +.. code:: c++ + + fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.", + fmt::arg("name", "World"), fmt::arg("number", 42)); + +If your compiler supports C++11 user-defined literals, the suffix ``_a`` offers +an alternative, slightly terser syntax for named arguments: + +.. code:: c++ + + using namespace fmt::literals; + fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.", + "name"_a="World", "number"_a=42); + +.. _safety: + +Safety +------ + +The library is fully type safe, automatic memory management prevents buffer +overflow, errors in format strings are reported using exceptions or at compile +time. For example, the code + +.. code:: c++ + + fmt::format("The answer is {:d}", "forty-two"); + +throws the ``format_error`` exception because the argument ``"forty-two"`` is a +string while the format code ``d`` only applies to integers. + +The code + +.. code:: c++ + + format(FMT_STRING("The answer is {:d}"), "forty-two"); + +reports a compile-time error on compilers that support relaxed ``constexpr``. +See `here <api.html#c.fmt>`_ for details. + +The following code + +.. code:: c++ + + fmt::format("Cyrillic letter {}", L'\x42e'); + +produces a compile-time error because wide character ``L'\x42e'`` cannot be +formatted into a narrow string. You can use a wide format string instead: + +.. code:: c++ + + fmt::format(L"Cyrillic letter {}", L'\x42e'); + +For comparison, writing a wide character to ``std::ostream`` results in +its numeric value being written to the stream (i.e. 1070 instead of letter 'ю' +which is represented by ``L'\x42e'`` if we use Unicode) which is rarely +desirable. + +Compact Binary Code +------------------- + +The library produces compact per-call compiled code. For example +(`godbolt <https://godbolt.org/g/TZU4KF>`_), + +.. code:: c++ + + #include <fmt/core.h> + + int main() { + fmt::print("The answer is {}.", 42); + } + +compiles to just + +.. code:: asm + + main: # @main + sub rsp, 24 + mov qword ptr [rsp], 42 + mov rcx, rsp + mov edi, offset .L.str + mov esi, 17 + mov edx, 1 + call fmt::v7::vprint(fmt::v7::basic_string_view<char>, fmt::v7::format_args) + xor eax, eax + add rsp, 24 + ret + .L.str: + .asciz "The answer is {}." + +.. _portability: + +Portability +----------- + +The library is highly portable and relies only on a small set of C++11 features: + +* variadic templates +* type traits +* rvalue references +* decltype +* trailing return types +* deleted functions +* alias templates + +These are available in GCC 4.8, Clang 3.4, MSVC 19.0 (2015) and more recent +compiler version. For older compilers use {fmt} `version 4.x +<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which is maintained and +only requires C++98. + +The output of all formatting functions is consistent across platforms. +For example, + +.. code:: + + fmt::print("{}", std::numeric_limits<double>::infinity()); + +always prints ``inf`` while the output of ``printf`` is platform-dependent. + +.. _ease-of-use: + +Ease of Use +----------- + +{fmt} has a small self-contained code base with the core library consisting of +just three header files and no external dependencies. +A permissive MIT `license <https://github.com/fmtlib/fmt#license>`_ allows +using the library both in open-source and commercial projects. + +`Learn more... <contents.html>`_ + +.. raw:: html + + <a class="btn btn-success" href="https://github.com/fmtlib/fmt">GitHub Repository</a> + + <div class="section footer"> + <iframe src="https://ghbtns.com/github-btn.html?user=fmtlib&repo=fmt&type=watch&count=true" + class="github-btn" width="100" height="20"></iframe> + </div> diff --git a/libs/fmt/doc/python-license.txt b/libs/fmt/doc/python-license.txt new file mode 100644 index 0000000000000000000000000000000000000000..88eed1f9cb05d3e5b248eee7915fd860885107a3 --- /dev/null +++ b/libs/fmt/doc/python-license.txt @@ -0,0 +1,290 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.2 2.1.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2.1 2.2 2002 PSF yes + 2.2.2 2.2.1 2002 PSF yes + 2.2.3 2.2.2 2003 PSF yes + 2.3 2.2.2 2002-2003 PSF yes + 2.3.1 2.3 2002-2003 PSF yes + 2.3.2 2.3.1 2002-2003 PSF yes + 2.3.3 2.3.2 2002-2003 PSF yes + 2.3.4 2.3.3 2004 PSF yes + 2.3.5 2.3.4 2005 PSF yes + 2.4 2.3 2004 PSF yes + 2.4.1 2.4 2005 PSF yes + 2.4.2 2.4.1 2005 PSF yes + 2.4.3 2.4.2 2006 PSF yes + 2.4.4 2.4.3 2006 PSF yes + 2.5 2.4 2006 PSF yes + 2.5.1 2.5 2007 PSF yes + 2.5.2 2.5.1 2008 PSF yes + 2.5.3 2.5.2 2008 PSF yes + 2.6 2.5 2008 PSF yes + 2.6.1 2.6 2008 PSF yes + 2.6.2 2.6.1 2009 PSF yes + 2.6.3 2.6.2 2009 PSF yes + 2.6.4 2.6.3 2009 PSF yes + 2.6.5 2.6.4 2010 PSF yes + 3.0 2.6 2008 PSF yes + 3.0.1 3.0 2009 PSF yes + 3.1 3.0.1 2009 PSF yes + 3.1.1 3.1 2009 PSF yes + 3.1.2 3.1.1 2010 PSF yes + 3.1.3 3.1.2 2010 PSF yes + 3.1.4 3.1.3 2011 PSF yes + 3.2 3.1 2011 PSF yes + 3.2.1 3.2 2011 PSF yes + 3.2.2 3.2.1 2011 PSF yes + 3.2.3 3.2.2 2012 PSF yes + 3.3.0 3.2 2012 PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012 Python Software Foundation; All Rights Reserved" are retained in Python +alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, 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. diff --git a/libs/fmt/doc/syntax.rst b/libs/fmt/doc/syntax.rst new file mode 100644 index 0000000000000000000000000000000000000000..d68408611e180daadc11b8cbc5ae2c47154e2289 --- /dev/null +++ b/libs/fmt/doc/syntax.rst @@ -0,0 +1,486 @@ +.. _syntax: + +******************** +Format String Syntax +******************** + +Formatting functions such as :ref:`fmt::format() <format>` and +:ref:`fmt::print() <print>` use the same format string syntax described in this +section. + +Format strings contain "replacement fields" surrounded by curly braces ``{}``. +Anything that is not contained in braces is considered literal text, which is +copied unchanged to the output. If you need to include a brace character in the +literal text, it can be escaped by doubling: ``{{`` and ``}}``. + +The grammar for a replacement field is as follows: + +.. productionlist:: sf + replacement_field: "{" [`arg_id`] [":" (`format_spec` | `chrono_format_spec`)] "}" + arg_id: `integer` | `identifier` + integer: `digit`+ + digit: "0"..."9" + identifier: `id_start` `id_continue`* + id_start: "a"..."z" | "A"..."Z" | "_" + id_continue: `id_start` | `digit` + +In less formal terms, the replacement field can start with an *arg_id* +that specifies the argument whose value is to be formatted and inserted into +the output instead of the replacement field. +The *arg_id* is optionally followed by a *format_spec*, which is preceded by a +colon ``':'``. These specify a non-default format for the replacement value. + +See also the :ref:`formatspec` section. + +If the numerical arg_ids in a format string are 0, 1, 2, ... in sequence, +they can all be omitted (not just some) and the numbers 0, 1, 2, ... will be +automatically inserted in that order. + +Named arguments can be referred to by their names or indices. + +Some simple format string examples:: + + "First, thou shalt count to {0}" // References the first argument + "Bring me a {}" // Implicitly references the first argument + "From {} to {}" // Same as "From {0} to {1}" + +The *format_spec* field contains a specification of how the value should be +presented, including such details as field width, alignment, padding, decimal +precision and so on. Each value type can define its own "formatting +mini-language" or interpretation of the *format_spec*. + +Most built-in types support a common formatting mini-language, which is +described in the next section. + +A *format_spec* field can also include nested replacement fields in certain +positions within it. These nested replacement fields can contain only an +argument id; format specifications are not allowed. This allows the formatting +of a value to be dynamically specified. + +See the :ref:`formatexamples` section for some examples. + +.. _formatspec: + +Format Specification Mini-Language +================================== + +"Format specifications" are used within replacement fields contained within a +format string to define how individual values are presented (see +:ref:`syntax`). Each formattable type may define how the format +specification is to be interpreted. + +Most built-in types implement the following options for format specifications, +although some of the formatting options are only supported by the numeric types. + +The general form of a *standard format specifier* is: + +.. productionlist:: sf + format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`]["L"][`type`] + fill: <a character other than '{' or '}'> + align: "<" | ">" | "^" + sign: "+" | "-" | " " + width: `integer` | "{" [`arg_id`] "}" + precision: `integer` | "{" [`arg_id`] "}" + type: "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | + : "o" | "p" | "s" | "x" | "X" + +The *fill* character can be any Unicode code point other than ``'{'`` or +``'}'``. The presence of a fill character is signaled by the character following +it, which must be one of the alignment options. If the second character of +*format_spec* is not a valid alignment option, then it is assumed that both the +fill character and the alignment option are absent. + +The meaning of the various alignment options is as follows: + ++---------+----------------------------------------------------------+ +| Option | Meaning | ++=========+==========================================================+ +| ``'<'`` | Forces the field to be left-aligned within the available | +| | space (this is the default for most objects). | ++---------+----------------------------------------------------------+ +| ``'>'`` | Forces the field to be right-aligned within the | +| | available space (this is the default for numbers). | ++---------+----------------------------------------------------------+ +| ``'^'`` | Forces the field to be centered within the available | +| | space. | ++---------+----------------------------------------------------------+ + +Note that unless a minimum field width is defined, the field width will always +be the same size as the data to fill it, so that the alignment option has no +meaning in this case. + +The *sign* option is only valid for number types, and can be one of the +following: + ++---------+----------------------------------------------------------+ +| Option | Meaning | ++=========+==========================================================+ +| ``'+'`` | indicates that a sign should be used for both | +| | positive as well as negative numbers. | ++---------+----------------------------------------------------------+ +| ``'-'`` | indicates that a sign should be used only for negative | +| | numbers (this is the default behavior). | ++---------+----------------------------------------------------------+ +| space | indicates that a leading space should be used on | +| | positive numbers, and a minus sign on negative numbers. | ++---------+----------------------------------------------------------+ + +The ``'#'`` option causes the "alternate form" to be used for the +conversion. The alternate form is defined differently for different +types. This option is only valid for integer and floating-point types. +For integers, when binary, octal, or hexadecimal output is used, this +option adds the prefix respective ``"0b"`` (``"0B"``), ``"0"``, or +``"0x"`` (``"0X"``) to the output value. Whether the prefix is +lower-case or upper-case is determined by the case of the type +specifier, for example, the prefix ``"0x"`` is used for the type ``'x'`` +and ``"0X"`` is used for ``'X'``. For floating-point numbers the +alternate form causes the result of the conversion to always contain a +decimal-point character, even if no digits follow it. Normally, a +decimal-point character appears in the result of these conversions +only if a digit follows it. In addition, for ``'g'`` and ``'G'`` +conversions, trailing zeros are not removed from the result. + +.. ifconfig:: False + + The ``','`` option signals the use of a comma for a thousands separator. + For a locale aware separator, use the ``'L'`` integer presentation type + instead. + +*width* is a decimal integer defining the minimum field width. If not +specified, then the field width will be determined by the content. + +Preceding the *width* field by a zero (``'0'``) character enables sign-aware +zero-padding for numeric types. It forces the padding to be placed after the +sign or base (if any) but before the digits. This is used for printing fields in +the form '+000000120'. This option is only valid for numeric types and it has no +effect on formatting of infinity and NaN. + +The *precision* is a decimal number indicating how many digits should be +displayed after the decimal point for a floating-point value formatted with +``'f'`` and ``'F'``, or before and after the decimal point for a floating-point +value formatted with ``'g'`` or ``'G'``. For non-number types the field +indicates the maximum field size - in other words, how many characters will be +used from the field content. The *precision* is not allowed for integer, +character, Boolean, and pointer values. + +The ``'L'`` option uses the current locale setting to insert the appropriate +number separator characters. This option is only valid for numeric types. + +Finally, the *type* determines how the data should be presented. + +The available string presentation types are: + ++---------+----------------------------------------------------------+ +| Type | Meaning | ++=========+==========================================================+ +| ``'s'`` | String format. This is the default type for strings and | +| | may be omitted. | ++---------+----------------------------------------------------------+ +| none | The same as ``'s'``. | ++---------+----------------------------------------------------------+ + +The available character presentation types are: + ++---------+----------------------------------------------------------+ +| Type | Meaning | ++=========+==========================================================+ +| ``'c'`` | Character format. This is the default type for | +| | characters and may be omitted. | ++---------+----------------------------------------------------------+ +| none | The same as ``'c'``. | ++---------+----------------------------------------------------------+ + +The available integer presentation types are: + ++---------+----------------------------------------------------------+ +| Type | Meaning | ++=========+==========================================================+ +| ``'b'`` | Binary format. Outputs the number in base 2. Using the | +| | ``'#'`` option with this type adds the prefix ``"0b"`` | +| | to the output value. | ++---------+----------------------------------------------------------+ +| ``'B'`` | Binary format. Outputs the number in base 2. Using the | +| | ``'#'`` option with this type adds the prefix ``"0B"`` | +| | to the output value. | ++---------+----------------------------------------------------------+ +| ``'d'`` | Decimal integer. Outputs the number in base 10. | ++---------+----------------------------------------------------------+ +| ``'o'`` | Octal format. Outputs the number in base 8. | ++---------+----------------------------------------------------------+ +| ``'x'`` | Hex format. Outputs the number in base 16, using | +| | lower-case letters for the digits above 9. Using the | +| | ``'#'`` option with this type adds the prefix ``"0x"`` | +| | to the output value. | ++---------+----------------------------------------------------------+ +| ``'X'`` | Hex format. Outputs the number in base 16, using | +| | upper-case letters for the digits above 9. Using the | +| | ``'#'`` option with this type adds the prefix ``"0X"`` | +| | to the output value. | ++---------+----------------------------------------------------------+ +| none | The same as ``'d'``. | ++---------+----------------------------------------------------------+ + +Integer presentation types can also be used with character and Boolean values. +Boolean values are formatted using textual representation, either ``true`` or +``false``, if the presentation type is not specified. + +The available presentation types for floating-point values are: + ++---------+----------------------------------------------------------+ +| Type | Meaning | ++=========+==========================================================+ +| ``'a'`` | Hexadecimal floating point format. Prints the number in | +| | base 16 with prefix ``"0x"`` and lower-case letters for | +| | digits above 9. Uses ``'p'`` to indicate the exponent. | ++---------+----------------------------------------------------------+ +| ``'A'`` | Same as ``'a'`` except it uses upper-case letters for | +| | the prefix, digits above 9 and to indicate the exponent. | ++---------+----------------------------------------------------------+ +| ``'e'`` | Exponent notation. Prints the number in scientific | +| | notation using the letter 'e' to indicate the exponent. | ++---------+----------------------------------------------------------+ +| ``'E'`` | Exponent notation. Same as ``'e'`` except it uses an | +| | upper-case ``'E'`` as the separator character. | ++---------+----------------------------------------------------------+ +| ``'f'`` | Fixed point. Displays the number as a fixed-point | +| | number. | ++---------+----------------------------------------------------------+ +| ``'F'`` | Fixed point. Same as ``'f'``, but converts ``nan`` to | +| | ``NAN`` and ``inf`` to ``INF``. | ++---------+----------------------------------------------------------+ +| ``'g'`` | General format. For a given precision ``p >= 1``, | +| | this rounds the number to ``p`` significant digits and | +| | then formats the result in either fixed-point format | +| | or in scientific notation, depending on its magnitude. | +| | | +| | A precision of ``0`` is treated as equivalent to a | +| | precision of ``1``. | ++---------+----------------------------------------------------------+ +| ``'G'`` | General format. Same as ``'g'`` except switches to | +| | ``'E'`` if the number gets too large. The | +| | representations of infinity and NaN are uppercased, too. | ++---------+----------------------------------------------------------+ +| none | Similar to ``'g'``, except that fixed-point notation, | +| | when used, has at least one digit past the decimal | +| | point. The default precision is as high as needed to | +| | represent the particular value. | ++---------+----------------------------------------------------------+ + +.. ifconfig:: False + + +---------+----------------------------------------------------------+ + | | The precise rules are as follows: suppose that the | + | | result formatted with presentation type ``'e'`` and | + | | precision ``p-1`` would have exponent ``exp``. Then | + | | if ``-4 <= exp < p``, the number is formatted | + | | with presentation type ``'f'`` and precision | + | | ``p-1-exp``. Otherwise, the number is formatted | + | | with presentation type ``'e'`` and precision ``p-1``. | + | | In both cases insignificant trailing zeros are removed | + | | from the significand, and the decimal point is also | + | | removed if there are no remaining digits following it. | + | | | + | | Positive and negative infinity, positive and negative | + | | zero, and nans, are formatted as ``inf``, ``-inf``, | + | | ``0``, ``-0`` and ``nan`` respectively, regardless of | + | | the precision. | + | | | + +---------+----------------------------------------------------------+ + +The available presentation types for pointers are: + ++---------+----------------------------------------------------------+ +| Type | Meaning | ++=========+==========================================================+ +| ``'p'`` | Pointer format. This is the default type for | +| | pointers and may be omitted. | ++---------+----------------------------------------------------------+ +| none | The same as ``'p'``. | ++---------+----------------------------------------------------------+ + +.. _chrono-specs: + +Chrono Format Specifications +============================ + +Format specifications for chrono types have the following syntax: + +.. productionlist:: sf + chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`] + chrono_specs: [`chrono_specs`] `conversion_spec` | `chrono_specs` `literal_char` + conversion_spec: "%" [`modifier`] `chrono_type` + literal_char: <a character other than '{', '}' or '%'> + modifier: "E" | "O" + chrono_type: "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "F" | + : "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" | "n" | "p" | + : "q" | "Q" | "r" | "R" | "S" | "t" | "T" | "u" | "U" | "V" | + : "w" | "W" | "x" | "X" | "y" | "Y" | "z" | "Z" | "%" + +Literal chars are copied unchanged to the output. Precision is valid only for +``std::chrono::duration`` types with a floating-point representation type. + +The available presentation types (*chrono_type*) for chrono durations and time +points are: + ++---------+--------------------------------------------------------------------+ +| Type | Meaning | ++=========+====================================================================+ +| ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a | +| | single digit, it is prefixed with 0. The modified command ``%OH`` | +| | produces the locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'M'`` | The minute as a decimal number. If the result is a single digit, | +| | it is prefixed with 0. The modified command ``%OM`` produces the | +| | locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'S'`` | Seconds as a decimal number. If the number of seconds is less than | +| | 10, the result is prefixed with 0. If the precision of the input | +| | cannot be exactly represented with seconds, then the format is a | +| | decimal floating-point number with a fixed format and a precision | +| | matching that of the precision of the input (or to a microseconds | +| | precision if the conversion to floating-point decimal seconds | +| | cannot be made within 18 fractional digits). The character for the | +| | decimal point is localized according to the locale. The modified | +| | command ``%OS`` produces the locale's alternative representation. | ++---------+--------------------------------------------------------------------+ + +Specifiers that have a calendaric component such as `'d'` (the day of month) +are valid only for ``std::tm`` and not durations or time points. + +``std::tm`` uses the system's `strftime +<https://en.cppreference.com/w/cpp/chrono/c/strftime>`_ so refer to its +documentation for details on supported conversion specifiers. + +.. _formatexamples: + +Format Examples +=============== + +This section contains examples of the format syntax and comparison with +the printf formatting. + +In most of the cases the syntax is similar to the printf formatting, with the +addition of the ``{}`` and with ``:`` used instead of ``%``. +For example, ``"%03.2f"`` can be translated to ``"{:03.2f}"``. + +The new format syntax also supports new and different options, shown in the +following examples. + +Accessing arguments by position:: + + fmt::format("{0}, {1}, {2}", 'a', 'b', 'c'); + // Result: "a, b, c" + fmt::format("{}, {}, {}", 'a', 'b', 'c'); + // Result: "a, b, c" + fmt::format("{2}, {1}, {0}", 'a', 'b', 'c'); + // Result: "c, b, a" + fmt::format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated + // Result: "abracadabra" + +Aligning the text and specifying a width:: + + fmt::format("{:<30}", "left aligned"); + // Result: "left aligned " + fmt::format("{:>30}", "right aligned"); + // Result: " right aligned" + fmt::format("{:^30}", "centered"); + // Result: " centered " + fmt::format("{:*^30}", "centered"); // use '*' as a fill char + // Result: "***********centered***********" + +Dynamic width:: + + fmt::format("{:<{}}", "left aligned", 30); + // Result: "left aligned " + +Dynamic precision:: + + fmt::format("{:.{}f}", 3.14, 1); + // Result: "3.1" + +Replacing ``%+f``, ``%-f``, and ``% f`` and specifying a sign:: + + fmt::format("{:+f}; {:+f}", 3.14, -3.14); // show it always + // Result: "+3.140000; -3.140000" + fmt::format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers + // Result: " 3.140000; -3.140000" + fmt::format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}' + // Result: "3.140000; -3.140000" + +Replacing ``%x`` and ``%o`` and converting the value to different bases:: + + fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + // Result: "int: 42; hex: 2a; oct: 52; bin: 101010" + // with 0x or 0 or 0b as prefix: + fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42); + // Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010" + +Padded hex byte with prefix and always prints both hex characters:: + + fmt::format("{:#04x}", 0); + // Result: "0x00" + +Box drawing using Unicode fill:: + + fmt::print( + "┌{0:─^{2}}┐\n" + "│{1: ^{2}}│\n" + "└{0:─^{2}}┘\n", "", "Hello, world!", 20); + +prints:: + + ┌────────────────────┐ + │ Hello, world! │ + └────────────────────┘ + +Using type-specific formatting:: + + #include <fmt/chrono.h> + + auto t = tm(); + t.tm_year = 2010 - 1900; + t.tm_mon = 7; + t.tm_mday = 4; + t.tm_hour = 12; + t.tm_min = 15; + t.tm_sec = 58; + fmt::print("{:%Y-%m-%d %H:%M:%S}", t); + // Prints: 2010-08-04 12:15:58 + +Using the comma as a thousands separator:: + + #include <fmt/locale.h> + + auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890); + // s == "1,234,567,890" + +.. ifconfig:: False + + Nesting arguments and more complex examples:: + + >>> for align, text in zip('<^>', ['left', 'center', 'right']): + ... '{0:{fill}{align}16}") << text, fill=align, align=align) + ... + 'left<<<<<<<<<<<<' + '^^^^^center^^^^^' + '>>>>>>>>>>>right' + >>> + >>> octets = [192, 168, 0, 1] + Format("{:02X}{:02X}{:02X}{:02X}") << *octets) + 'C0A80001' + >>> int(_, 16) + 3232235521 + >>> + >>> width = 5 + >>> for num in range(5,12): + ... for base in 'dXob': + ... print('{0:{width}{base}}") << num, base=base, width=width), end=' ') + ... print() + ... + 5 5 5 101 + 6 6 6 110 + 7 7 7 111 + 8 8 10 1000 + 9 9 11 1001 + 10 A 12 1010 + 11 B 13 1011 diff --git a/libs/fmt/doc/usage.rst b/libs/fmt/doc/usage.rst new file mode 100644 index 0000000000000000000000000000000000000000..f9f818584a9a0d7418c81dadcbd3b58164fd6bfd --- /dev/null +++ b/libs/fmt/doc/usage.rst @@ -0,0 +1,212 @@ +***** +Usage +***** + +To use the {fmt} library, add :file:`fmt/core.h`, :file:`fmt/format.h`, +:file:`fmt/format-inl.h`, :file:`src/format.cc` and optionally other headers +from a `release archive <https://github.com/fmtlib/fmt/releases/latest>`_ or +the `Git repository <https://github.com/fmtlib/fmt>`_ to your project. +Alternatively, you can :ref:`build the library with CMake <building>`. + +.. _building: + +Building the Library +==================== + +The included `CMake build script`__ can be used to build the fmt +library on a wide range of platforms. CMake is freely available for +download from https://www.cmake.org/download/. + +__ https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt + +CMake works by generating native makefiles or project files that can +be used in the compiler environment of your choice. The typical +workflow starts with:: + + mkdir build # Create a directory to hold the build output. + cd build + cmake .. # Generate native build scripts. + +where :file:`{<path/to/fmt>}` is a path to the ``fmt`` repository. + +If you are on a \*nix system, you should now see a Makefile in the +current directory. Now you can build the library by running :command:`make`. + +Once the library has been built you can invoke :command:`make test` to run +the tests. + +You can control generation of the make ``test`` target with the ``FMT_TEST`` +CMake option. This can be useful if you include fmt as a subdirectory in +your project but don't want to add fmt's tests to your ``test`` target. + +If you use Windows and have Visual Studio installed, a :file:`FMT.sln` +file and several :file:`.vcproj` files will be created. You can then build them +using Visual Studio or msbuild. + +On Mac OS X with Xcode installed, an :file:`.xcodeproj` file will be generated. + +To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to +``TRUE``:: + + cmake -DBUILD_SHARED_LIBS=TRUE ... + +__ https://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries + + +To build a `static library` with position independent code (required if the main +consumer of the fmt library is a shared library i.e. a Python extension) set the +``CMAKE_POSITION_INDEPENDENT_CODE`` CMake variable to ``TRUE``:: + + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ... + + +Installing the Library +====================== + +After building the library you can install it on a Unix-like system by running +:command:`sudo make install`. + +Usage with CMake +================ + +You can add the ``fmt`` library directory into your project and include it in +your ``CMakeLists.txt`` file:: + + add_subdirectory(fmt) + +or + +:: + + add_subdirectory(fmt EXCLUDE_FROM_ALL) + +to exclude it from ``make``, ``make all``, or ``cmake --build .``. + +You can detect and use an installed version of {fmt} as follows:: + + find_package(fmt) + target_link_libraries(<your-target> fmt::fmt) + +Setting up your target to use a header-only version of ``fmt`` is equally easy:: + + target_link_libraries(<your-target> PRIVATE fmt::fmt-header-only) + +Usage with build2 +================= + +You can use `build2 <https://build2.org>`_, a dependency manager and a +build-system combined, to use ``fmt``. + +Currently this package is available in these package repositories: + +- **https://cppget.org/fmt/** for released and published versions. +- `The git repository with the sources of the build2 package of fmt <https://github.com/build2-packaging/fmt.git>`_ + for unreleased or custom revisions of ``fmt``. + +**Usage:** + +- ``build2`` package name: ``fmt`` +- Library target name : ``lib{fmt}`` + +For example, to make your ``build2`` project depend on ``fmt``: + +- Add one of the repositories to your configurations, or in your + ``repositories.manifest``, if not already there:: + + : + role: prerequisite + location: https://pkg.cppget.org/1/stable + +- Add this package as a dependency to your ``./manifest`` file + (example for ``v7.0.x``):: + + depends: fmt ~7.0.0 + +- Import the target and use it as a prerequisite to your own target + using `fmt` in the appropriate ``buildfile``:: + + import fmt = fmt%lib{fmt} + lib{mylib} : cxx{**} ... $fmt + +Then build your project as usual with `b` or `bdep update`. + +For ``build2`` newcomers or to get more details and use cases, you can read the +``build2`` +`toolchain introduction <https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml>`_. + +Building the Documentation +========================== + +To build the documentation you need the following software installed on your +system: + +* `Python <https://www.python.org/>`_ with pip and virtualenv +* `Doxygen <http://www.stack.nl/~dimitri/doxygen/>`_ +* `Less <http://lesscss.org/>`_ with ``less-plugin-clean-css``. + Ubuntu doesn't package the ``clean-css`` plugin so you should use ``npm`` + instead of ``apt`` to install both ``less`` and the plugin:: + + sudo npm install -g less less-plugin-clean-css. + +First generate makefiles or project files using CMake as described in +the previous section. Then compile the ``doc`` target/project, for example:: + + make doc + +This will generate the HTML documentation in ``doc/html``. + +Conda +===== + +fmt can be installed on Linux, macOS and Windows with +`Conda <https://docs.conda.io/en/latest/>`__, using its +`conda-forge <https://conda-forge.org>`__ +`package <https://github.com/conda-forge/fmt-feedstock>`__, as follows:: + + conda install -c conda-forge fmt + +Vcpkg +===== + +You can download and install fmt using the `vcpkg +<https://github.com/Microsoft/vcpkg>`__ dependency manager:: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install fmt + +The fmt port in vcpkg is kept up to date by Microsoft team members and community +contributors. If the version is out of date, please `create an issue or pull +request <https://github.com/Microsoft/vcpkg>`__ on the vcpkg repository. + +LHelper +======= + +You can download and install fmt using +`lhelper <https://github.com/franko/lhelper>`__ dependency manager:: + + lhelper activate <some-environment> + lhelper install fmt + +All the recipes for lhelper are kept in the +`lhelper's recipe <https://github.com/franko/lhelper-recipes>`__ repository. + +Android NDK +=========== + +fmt provides `Android.mk file`__ that can be used to build the library +with `Android NDK <https://developer.android.com/tools/sdk/ndk/index.html>`_. +For an example of using fmt with Android NDK, see the +`android-ndk-example <https://github.com/fmtlib/android-ndk-example>`_ +repository. + +__ https://github.com/fmtlib/fmt/blob/master/support/Android.mk + +Homebrew +======== + +fmt can be installed on OS X using `Homebrew <https://brew.sh/>`_:: + + brew install fmt diff --git a/libs/fmt/include/fmt/args.h b/libs/fmt/include/fmt/args.h new file mode 100644 index 0000000000000000000000000000000000000000..562e8ab1118514d8670464980a87863c021f330e --- /dev/null +++ b/libs/fmt/include/fmt/args.h @@ -0,0 +1,232 @@ +// Formatting library for C++ - dynamic format arguments +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_ARGS_H_ +#define FMT_ARGS_H_ + +#include <functional> // std::reference_wrapper +#include <memory> // std::unique_ptr +#include <vector> + +#include "core.h" + +FMT_BEGIN_NAMESPACE + +namespace detail { + +template <typename T> struct is_reference_wrapper : std::false_type {}; +template <typename T> +struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {}; + +template <typename T> const T& unwrap(const T& v) { return v; } +template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) { + return static_cast<const T&>(v); +} + +class dynamic_arg_list { + // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for + // templates it doesn't complain about inability to deduce single translation + // unit for placing vtable. So storage_node_base is made a fake template. + template <typename = void> struct node { + virtual ~node() = default; + std::unique_ptr<node<>> next; + }; + + template <typename T> struct typed_node : node<> { + T value; + + template <typename Arg> + FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} + + template <typename Char> + FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg) + : value(arg.data(), arg.size()) {} + }; + + std::unique_ptr<node<>> head_; + + public: + template <typename T, typename Arg> const T& push(const Arg& arg) { + auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg)); + auto& value = new_node->value; + new_node->next = std::move(head_); + head_ = std::move(new_node); + return value; + } +}; +} // namespace detail + +/** + \rst + A dynamic version of `fmt::format_arg_store`. + It's equipped with a storage to potentially temporary objects which lifetimes + could be shorter than the format arguments object. + + It can be implicitly converted into `~fmt::basic_format_args` for passing + into type-erased formatting functions such as `~fmt::vformat`. + \endrst + */ +template <typename Context> +class dynamic_format_arg_store +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args<Context> +#endif +{ + private: + using char_type = typename Context::char_type; + + template <typename T> struct need_copy { + static constexpr detail::type mapped_type = + detail::mapped_type_constant<T, Context>::value; + + enum { + value = !(detail::is_reference_wrapper<T>::value || + std::is_same<T, basic_string_view<char_type>>::value || + std::is_same<T, detail::std_string_view<char_type>>::value || + (mapped_type != detail::type::cstring_type && + mapped_type != detail::type::string_type && + mapped_type != detail::type::custom_type)) + }; + }; + + template <typename T> + using stored_type = conditional_t<detail::is_string<T>::value && + !has_formatter<T, Context>::value && + !detail::is_reference_wrapper<T>::value, + std::basic_string<char_type>, T>; + + // Storage of basic_format_arg must be contiguous. + std::vector<basic_format_arg<Context>> data_; + std::vector<detail::named_arg_info<char_type>> named_info_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + detail::dynamic_arg_list dynamic_args_; + + friend class basic_format_args<Context>; + + unsigned long long get_types() const { + return detail::is_unpacked_bit | data_.size() | + (named_info_.empty() + ? 0ULL + : static_cast<unsigned long long>(detail::has_named_args_bit)); + } + + const basic_format_arg<Context>* data() const { + return named_info_.empty() ? data_.data() : data_.data() + 1; + } + + template <typename T> void emplace_arg(const T& arg) { + data_.emplace_back(detail::make_arg<Context>(arg)); + } + + template <typename T> + void emplace_arg(const detail::named_arg<char_type, T>& arg) { + if (named_info_.empty()) { + constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr}; + data_.insert(data_.begin(), {zero_ptr, 0}); + } + data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value))); + auto pop_one = [](std::vector<basic_format_arg<Context>>* data) { + data->pop_back(); + }; + std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)> + guard{&data_, pop_one}; + named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)}); + data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; + guard.release(); + } + + public: + /** + \rst + Adds an argument into the dynamic store for later passing to a formatting + function. + + Note that custom types and string types (but not string views) are copied + into the store dynamically allocating memory if necessary. + + **Example**:: + + fmt::dynamic_format_arg_store<fmt::format_context> store; + store.push_back(42); + store.push_back("abc"); + store.push_back(1.5f); + std::string result = fmt::vformat("{} and {} and {}", store); + \endrst + */ + template <typename T> void push_back(const T& arg) { + if (detail::const_check(need_copy<T>::value)) + emplace_arg(dynamic_args_.push<stored_type<T>>(arg)); + else + emplace_arg(detail::unwrap(arg)); + } + + /** + \rst + Adds a reference to the argument into the dynamic store for later passing to + a formatting function. + + **Example**:: + + fmt::dynamic_format_arg_store<fmt::format_context> store; + char band[] = "Rolling Stones"; + store.push_back(std::cref(band)); + band[9] = 'c'; // Changing str affects the output. + std::string result = fmt::vformat("{}", store); + // result == "Rolling Scones" + \endrst + */ + template <typename T> void push_back(std::reference_wrapper<T> arg) { + static_assert( + need_copy<T>::value, + "objects of built-in types and string views are always copied"); + emplace_arg(arg.get()); + } + + /** + Adds named argument into the dynamic store for later passing to a formatting + function. ``std::reference_wrapper`` is supported to avoid copying of the + argument. The name is always copied into the store. + */ + template <typename T> + void push_back(const detail::named_arg<char_type, T>& arg) { + const char_type* arg_name = + dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str(); + if (detail::const_check(need_copy<T>::value)) { + emplace_arg( + fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value))); + } else { + emplace_arg(fmt::arg(arg_name, arg.value)); + } + } + + /** Erase all elements from the store */ + void clear() { + data_.clear(); + named_info_.clear(); + dynamic_args_ = detail::dynamic_arg_list(); + } + + /** + \rst + Reserves space to store at least *new_cap* arguments including + *new_cap_named* named arguments. + \endrst + */ + void reserve(size_t new_cap, size_t new_cap_named) { + FMT_ASSERT(new_cap >= new_cap_named, + "Set of arguments includes set of named arguments"); + data_.reserve(new_cap); + named_info_.reserve(new_cap_named); + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_ARGS_H_ diff --git a/libs/fmt/include/fmt/chrono.h b/libs/fmt/include/fmt/chrono.h new file mode 100644 index 0000000000000000000000000000000000000000..c4903c364181d04879b4dbad80c9493907618647 --- /dev/null +++ b/libs/fmt/include/fmt/chrono.h @@ -0,0 +1,1195 @@ +// Formatting library for C++ - chrono support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CHRONO_H_ +#define FMT_CHRONO_H_ + +#include <algorithm> +#include <chrono> +#include <ctime> +#include <locale> +#include <sstream> + +#include "format.h" +#include "locale.h" + +FMT_BEGIN_NAMESPACE + +// Enable safe chrono durations, unless explicitly disabled. +#ifndef FMT_SAFE_DURATION_CAST +# define FMT_SAFE_DURATION_CAST 1 +#endif +#if FMT_SAFE_DURATION_CAST + +// For conversion between std::chrono::durations without undefined +// behaviour or erroneous results. +// This is a stripped down version of duration_cast, for inclusion in fmt. +// See https://github.com/pauldreik/safe_duration_cast +// +// Copyright Paul Dreik 2019 +namespace safe_duration_cast { + +template <typename To, typename From, + FMT_ENABLE_IF(!std::is_same<From, To>::value && + std::numeric_limits<From>::is_signed == + std::numeric_limits<To>::is_signed)> +FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { + ec = 0; + using F = std::numeric_limits<From>; + using T = std::numeric_limits<To>; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + // A and B are both signed, or both unsigned. + if (F::digits <= T::digits) { + // From fits in To without any problem. + } else { + // From does not always fit in To, resort to a dynamic check. + if (from < (T::min)() || from > (T::max)()) { + // outside range. + ec = 1; + return {}; + } + } + return static_cast<To>(from); +} + +/** + * converts From to To, without loss. If the dynamic value of from + * can't be converted to To without loss, ec is set. + */ +template <typename To, typename From, + FMT_ENABLE_IF(!std::is_same<From, To>::value && + std::numeric_limits<From>::is_signed != + std::numeric_limits<To>::is_signed)> +FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { + ec = 0; + using F = std::numeric_limits<From>; + using T = std::numeric_limits<To>; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + if (detail::const_check(F::is_signed && !T::is_signed)) { + // From may be negative, not allowed! + if (fmt::detail::is_negative(from)) { + ec = 1; + return {}; + } + // From is positive. Can it always fit in To? + if (F::digits > T::digits && + from > static_cast<From>(detail::max_value<To>())) { + ec = 1; + return {}; + } + } + + if (!F::is_signed && T::is_signed && F::digits >= T::digits && + from > static_cast<From>(detail::max_value<To>())) { + ec = 1; + return {}; + } + return static_cast<To>(from); // Lossless conversion. +} + +template <typename To, typename From, + FMT_ENABLE_IF(std::is_same<From, To>::value)> +FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { + ec = 0; + return from; +} // function + +// clang-format off +/** + * converts From to To if possible, otherwise ec is set. + * + * input | output + * ---------------------------------|--------------- + * NaN | NaN + * Inf | Inf + * normal, fits in output | converted (possibly lossy) + * normal, does not fit in output | ec is set + * subnormal | best effort + * -Inf | -Inf + */ +// clang-format on +template <typename To, typename From, + FMT_ENABLE_IF(!std::is_same<From, To>::value)> +FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { + ec = 0; + using T = std::numeric_limits<To>; + static_assert(std::is_floating_point<From>::value, "From must be floating"); + static_assert(std::is_floating_point<To>::value, "To must be floating"); + + // catch the only happy case + if (std::isfinite(from)) { + if (from >= T::lowest() && from <= (T::max)()) { + return static_cast<To>(from); + } + // not within range. + ec = 1; + return {}; + } + + // nan and inf will be preserved + return static_cast<To>(from); +} // function + +template <typename To, typename From, + FMT_ENABLE_IF(std::is_same<From, To>::value)> +FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { + ec = 0; + static_assert(std::is_floating_point<From>::value, "From must be floating"); + return from; +} + +/** + * safe duration cast between integral durations + */ +template <typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(std::is_integral<FromRep>::value), + FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)> +To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, + int& ec) { + using From = std::chrono::duration<FromRep, FromPeriod>; + ec = 0; + // the basic idea is that we need to convert from count() in the from type + // to count() in the To type, by multiplying it with this: + struct Factor + : std::ratio_divide<typename From::period, typename To::period> {}; + + static_assert(Factor::num > 0, "num must be positive"); + static_assert(Factor::den > 0, "den must be positive"); + + // the conversion is like this: multiply from.count() with Factor::num + // /Factor::den and convert it to To::rep, all this without + // overflow/underflow. let's start by finding a suitable type that can hold + // both To, From and Factor::num + using IntermediateRep = + typename std::common_type<typename From::rep, typename To::rep, + decltype(Factor::num)>::type; + + // safe conversion to IntermediateRep + IntermediateRep count = + lossless_integral_conversion<IntermediateRep>(from.count(), ec); + if (ec) return {}; + // multiply with Factor::num without overflow or underflow + if (detail::const_check(Factor::num != 1)) { + const auto max1 = detail::max_value<IntermediateRep>() / Factor::num; + if (count > max1) { + ec = 1; + return {}; + } + const auto min1 = + (std::numeric_limits<IntermediateRep>::min)() / Factor::num; + if (count < min1) { + ec = 1; + return {}; + } + count *= Factor::num; + } + + if (detail::const_check(Factor::den != 1)) count /= Factor::den; + auto tocount = lossless_integral_conversion<typename To::rep>(count, ec); + return ec ? To() : To(tocount); +} + +/** + * safe duration_cast between floating point durations + */ +template <typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(std::is_floating_point<FromRep>::value), + FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)> +To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, + int& ec) { + using From = std::chrono::duration<FromRep, FromPeriod>; + ec = 0; + if (std::isnan(from.count())) { + // nan in, gives nan out. easy. + return To{std::numeric_limits<typename To::rep>::quiet_NaN()}; + } + // maybe we should also check if from is denormal, and decide what to do about + // it. + + // +-inf should be preserved. + if (std::isinf(from.count())) { + return To{from.count()}; + } + + // the basic idea is that we need to convert from count() in the from type + // to count() in the To type, by multiplying it with this: + struct Factor + : std::ratio_divide<typename From::period, typename To::period> {}; + + static_assert(Factor::num > 0, "num must be positive"); + static_assert(Factor::den > 0, "den must be positive"); + + // the conversion is like this: multiply from.count() with Factor::num + // /Factor::den and convert it to To::rep, all this without + // overflow/underflow. let's start by finding a suitable type that can hold + // both To, From and Factor::num + using IntermediateRep = + typename std::common_type<typename From::rep, typename To::rep, + decltype(Factor::num)>::type; + + // force conversion of From::rep -> IntermediateRep to be safe, + // even if it will never happen be narrowing in this context. + IntermediateRep count = + safe_float_conversion<IntermediateRep>(from.count(), ec); + if (ec) { + return {}; + } + + // multiply with Factor::num without overflow or underflow + if (Factor::num != 1) { + constexpr auto max1 = detail::max_value<IntermediateRep>() / + static_cast<IntermediateRep>(Factor::num); + if (count > max1) { + ec = 1; + return {}; + } + constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() / + static_cast<IntermediateRep>(Factor::num); + if (count < min1) { + ec = 1; + return {}; + } + count *= static_cast<IntermediateRep>(Factor::num); + } + + // this can't go wrong, right? den>0 is checked earlier. + if (Factor::den != 1) { + using common_t = typename std::common_type<IntermediateRep, intmax_t>::type; + count /= static_cast<common_t>(Factor::den); + } + + // convert to the to type, safely + using ToRep = typename To::rep; + + const ToRep tocount = safe_float_conversion<ToRep>(count, ec); + if (ec) { + return {}; + } + return To{tocount}; +} +} // namespace safe_duration_cast +#endif + +// Prevents expansion of a preceding token as a function-style macro. +// Usage: f FMT_NOMACRO() +#define FMT_NOMACRO + +namespace detail { +inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } +inline null<> localtime_s(...) { return null<>(); } +inline null<> gmtime_r(...) { return null<>(); } +inline null<> gmtime_s(...) { return null<>(); } +} // namespace detail + +FMT_MODULE_EXPORT_BEGIN + +/** + Converts given time since epoch as ``std::time_t`` value into calendar time, + expressed in local time. Unlike ``std::localtime``, this function is + thread-safe on most platforms. + */ +inline std::tm localtime(std::time_t time) { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t) : time_(t) {} + + bool run() { + using namespace fmt::detail; + return handle(localtime_r(&time_, &tm_)); + } + + bool handle(std::tm* tm) { return tm != nullptr; } + + bool handle(detail::null<>) { + using namespace fmt::detail; + return fallback(localtime_s(&tm_, &time_)); + } + + bool fallback(int res) { return res == 0; } + +#if !FMT_MSC_VER + bool fallback(detail::null<>) { + using namespace fmt::detail; + std::tm* tm = std::localtime(&time_); + if (tm) tm_ = *tm; + return tm != nullptr; + } +#endif + }; + dispatcher lt(time); + // Too big time values may be unsupported. + if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); + return lt.tm_; +} + +inline std::tm localtime( + std::chrono::time_point<std::chrono::system_clock> time_point) { + return localtime(std::chrono::system_clock::to_time_t(time_point)); +} + +/** + Converts given time since epoch as ``std::time_t`` value into calendar time, + expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this + function is thread-safe on most platforms. + */ +inline std::tm gmtime(std::time_t time) { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t) : time_(t) {} + + bool run() { + using namespace fmt::detail; + return handle(gmtime_r(&time_, &tm_)); + } + + bool handle(std::tm* tm) { return tm != nullptr; } + + bool handle(detail::null<>) { + using namespace fmt::detail; + return fallback(gmtime_s(&tm_, &time_)); + } + + bool fallback(int res) { return res == 0; } + +#if !FMT_MSC_VER + bool fallback(detail::null<>) { + std::tm* tm = std::gmtime(&time_); + if (tm) tm_ = *tm; + return tm != nullptr; + } +#endif + }; + dispatcher gt(time); + // Too big time values may be unsupported. + if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); + return gt.tm_; +} + +inline std::tm gmtime( + std::chrono::time_point<std::chrono::system_clock> time_point) { + return gmtime(std::chrono::system_clock::to_time_t(time_point)); +} + +FMT_MODULE_EXPORT_END + +namespace detail { +inline size_t strftime(char* str, size_t count, const char* format, + const std::tm* time) { + // Assign to a pointer to suppress GCCs -Wformat-nonliteral + // First assign the nullptr to suppress -Wsuggest-attribute=format + std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) = + nullptr; + strftime = std::strftime; + return strftime(str, count, format, time); +} + +inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, + const std::tm* time) { + // See above + std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*, + const std::tm*) = nullptr; + wcsftime = std::wcsftime; + return wcsftime(str, count, format, time); +} +} // namespace detail + +FMT_MODULE_EXPORT_BEGIN + +template <typename Char, typename Duration> +struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>, + Char> : formatter<std::tm, Char> { + template <typename FormatContext> + auto format(std::chrono::time_point<std::chrono::system_clock> val, + FormatContext& ctx) -> decltype(ctx.out()) { + std::tm time = localtime(val); + return formatter<std::tm, Char>::format(time, ctx); + } +}; + +template <typename Char> struct formatter<std::tm, Char> { + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + auto it = ctx.begin(); + if (it != ctx.end() && *it == ':') ++it; + auto end = it; + while (end != ctx.end() && *end != '}') ++end; + specs = {it, detail::to_unsigned(end - it)}; + return end; + } + + template <typename FormatContext> + auto format(const std::tm& tm, FormatContext& ctx) const + -> decltype(ctx.out()) { + basic_memory_buffer<Char> tm_format; + tm_format.append(specs.begin(), specs.end()); + // By appending an extra space we can distinguish an empty result that + // indicates insufficient buffer size from a guaranteed non-empty result + // https://github.com/fmtlib/fmt/issues/2238 + tm_format.push_back(' '); + tm_format.push_back('\0'); + basic_memory_buffer<Char> buf; + size_t start = buf.size(); + for (;;) { + size_t size = buf.capacity() - start; + size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm); + if (count != 0) { + buf.resize(start + count); + break; + } + const size_t MIN_GROWTH = 10; + buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); + } + // Remove the extra space. + return std::copy(buf.begin(), buf.end() - 1, ctx.out()); + } + + basic_string_view<Char> specs; +}; + +namespace detail { +template <typename Period> FMT_CONSTEXPR const char* get_units() { + return nullptr; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::atto>() { + return "as"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::femto>() { + return "fs"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::pico>() { + return "ps"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::nano>() { + return "ns"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::micro>() { + return "µs"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::milli>() { + return "ms"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::centi>() { + return "cs"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::deci>() { + return "ds"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::ratio<1>>() { + return "s"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::deca>() { + return "das"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::hecto>() { + return "hs"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::kilo>() { + return "ks"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::mega>() { + return "Ms"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::giga>() { + return "Gs"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::tera>() { + return "Ts"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::peta>() { + return "Ps"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::exa>() { + return "Es"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::ratio<60>>() { + return "m"; +} +template <> FMT_CONSTEXPR inline const char* get_units<std::ratio<3600>>() { + return "h"; +} + +enum class numeric_system { + standard, + // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. + alternative +}; + +// Parses a put_time-like format string and invokes handler actions. +template <typename Char, typename Handler> +FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, + const Char* end, + Handler&& handler) { + auto ptr = begin; + while (ptr != end) { + auto c = *ptr; + if (c == '}') break; + if (c != '%') { + ++ptr; + continue; + } + if (begin != ptr) handler.on_text(begin, ptr); + ++ptr; // consume '%' + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case '%': + handler.on_text(ptr - 1, ptr); + break; + case 'n': { + const Char newline[] = {'\n'}; + handler.on_text(newline, newline + 1); + break; + } + case 't': { + const Char tab[] = {'\t'}; + handler.on_text(tab, tab + 1); + break; + } + // Day of the week: + case 'a': + handler.on_abbr_weekday(); + break; + case 'A': + handler.on_full_weekday(); + break; + case 'w': + handler.on_dec0_weekday(numeric_system::standard); + break; + case 'u': + handler.on_dec1_weekday(numeric_system::standard); + break; + // Month: + case 'b': + handler.on_abbr_month(); + break; + case 'B': + handler.on_full_month(); + break; + // Hour, minute, second: + case 'H': + handler.on_24_hour(numeric_system::standard); + break; + case 'I': + handler.on_12_hour(numeric_system::standard); + break; + case 'M': + handler.on_minute(numeric_system::standard); + break; + case 'S': + handler.on_second(numeric_system::standard); + break; + // Other: + case 'c': + handler.on_datetime(numeric_system::standard); + break; + case 'x': + handler.on_loc_date(numeric_system::standard); + break; + case 'X': + handler.on_loc_time(numeric_system::standard); + break; + case 'D': + handler.on_us_date(); + break; + case 'F': + handler.on_iso_date(); + break; + case 'r': + handler.on_12_hour_time(); + break; + case 'R': + handler.on_24_hour_time(); + break; + case 'T': + handler.on_iso_time(); + break; + case 'p': + handler.on_am_pm(); + break; + case 'Q': + handler.on_duration_value(); + break; + case 'q': + handler.on_duration_unit(); + break; + case 'z': + handler.on_utc_offset(); + break; + case 'Z': + handler.on_tz_name(); + break; + // Alternative representation: + case 'E': { + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'c': + handler.on_datetime(numeric_system::alternative); + break; + case 'x': + handler.on_loc_date(numeric_system::alternative); + break; + case 'X': + handler.on_loc_time(numeric_system::alternative); + break; + default: + FMT_THROW(format_error("invalid format")); + } + break; + } + case 'O': + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'w': + handler.on_dec0_weekday(numeric_system::alternative); + break; + case 'u': + handler.on_dec1_weekday(numeric_system::alternative); + break; + case 'H': + handler.on_24_hour(numeric_system::alternative); + break; + case 'I': + handler.on_12_hour(numeric_system::alternative); + break; + case 'M': + handler.on_minute(numeric_system::alternative); + break; + case 'S': + handler.on_second(numeric_system::alternative); + break; + default: + FMT_THROW(format_error("invalid format")); + } + break; + default: + FMT_THROW(format_error("invalid format")); + } + begin = ptr; + } + if (begin != ptr) handler.on_text(begin, ptr); + return ptr; +} + +struct chrono_format_checker { + FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); } + + template <typename Char> + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_NORETURN void on_abbr_weekday() { report_no_date(); } + FMT_NORETURN void on_full_weekday() { report_no_date(); } + FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); } + FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); } + FMT_NORETURN void on_abbr_month() { report_no_date(); } + FMT_NORETURN void on_full_month() { report_no_date(); } + FMT_CONSTEXPR void on_24_hour(numeric_system) {} + FMT_CONSTEXPR void on_12_hour(numeric_system) {} + FMT_CONSTEXPR void on_minute(numeric_system) {} + FMT_CONSTEXPR void on_second(numeric_system) {} + FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); } + FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); } + FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); } + FMT_NORETURN void on_us_date() { report_no_date(); } + FMT_NORETURN void on_iso_date() { report_no_date(); } + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_duration_value() {} + FMT_CONSTEXPR void on_duration_unit() {} + FMT_NORETURN void on_utc_offset() { report_no_date(); } + FMT_NORETURN void on_tz_name() { report_no_date(); } +}; + +template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> +inline bool isnan(T) { + return false; +} +template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> +inline bool isnan(T value) { + return std::isnan(value); +} + +template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> +inline bool isfinite(T) { + return true; +} +template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> +inline bool isfinite(T value) { + return std::isfinite(value); +} + +// Converts value to int and checks that it's in the range [0, upper). +template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> +inline int to_nonnegative_int(T value, int upper) { + FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper), + "invalid value"); + (void)upper; + return static_cast<int>(value); +} +template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> +inline int to_nonnegative_int(T value, int upper) { + FMT_ASSERT( + std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)), + "invalid value"); + (void)upper; + return static_cast<int>(value); +} + +template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> +inline T mod(T x, int y) { + return x % static_cast<T>(y); +} +template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> +inline T mod(T x, int y) { + return std::fmod(x, static_cast<T>(y)); +} + +// If T is an integral type, maps T to its unsigned counterpart, otherwise +// leaves it unchanged (unlike std::make_unsigned). +template <typename T, bool INTEGRAL = std::is_integral<T>::value> +struct make_unsigned_or_unchanged { + using type = T; +}; + +template <typename T> struct make_unsigned_or_unchanged<T, true> { + using type = typename std::make_unsigned<T>::type; +}; + +#if FMT_SAFE_DURATION_CAST +// throwing version of safe_duration_cast +template <typename To, typename FromRep, typename FromPeriod> +To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) { + int ec; + To to = safe_duration_cast::safe_duration_cast<To>(from, ec); + if (ec) FMT_THROW(format_error("cannot format duration")); + return to; +} +#endif + +template <typename Rep, typename Period, + FMT_ENABLE_IF(std::is_integral<Rep>::value)> +inline std::chrono::duration<Rep, std::milli> get_milliseconds( + std::chrono::duration<Rep, Period> d) { + // this may overflow and/or the result may not fit in the + // target type. +#if FMT_SAFE_DURATION_CAST + using CommonSecondsType = + typename std::common_type<decltype(d), std::chrono::seconds>::type; + const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d); + const auto d_as_whole_seconds = + fmt_safe_duration_cast<std::chrono::seconds>(d_as_common); + // this conversion should be nonproblematic + const auto diff = d_as_common - d_as_whole_seconds; + const auto ms = + fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff); + return ms; +#else + auto s = std::chrono::duration_cast<std::chrono::seconds>(d); + return std::chrono::duration_cast<std::chrono::milliseconds>(d - s); +#endif +} + +template <typename Rep, typename Period, + FMT_ENABLE_IF(std::is_floating_point<Rep>::value)> +inline std::chrono::duration<Rep, std::milli> get_milliseconds( + std::chrono::duration<Rep, Period> d) { + using common_type = typename std::common_type<Rep, std::intmax_t>::type; + auto ms = mod(d.count() * static_cast<common_type>(Period::num) / + static_cast<common_type>(Period::den) * 1000, + 1000); + return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms)); +} + +template <typename Char, typename Rep, typename OutputIt, + FMT_ENABLE_IF(std::is_integral<Rep>::value)> +OutputIt format_duration_value(OutputIt out, Rep val, int) { + return write<Char>(out, val); +} + +template <typename Char, typename Rep, typename OutputIt, + FMT_ENABLE_IF(std::is_floating_point<Rep>::value)> +OutputIt format_duration_value(OutputIt out, Rep val, int precision) { + auto specs = basic_format_specs<Char>(); + specs.precision = precision; + specs.type = precision > 0 ? 'f' : 'g'; + return write<Char>(out, val, specs); +} + +template <typename Char, typename OutputIt> +OutputIt copy_unit(string_view unit, OutputIt out, Char) { + return std::copy(unit.begin(), unit.end(), out); +} + +template <typename OutputIt> +OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { + // This works when wchar_t is UTF-32 because units only contain characters + // that have the same representation in UTF-16 and UTF-32. + utf8_to_utf16 u(unit); + return std::copy(u.c_str(), u.c_str() + u.size(), out); +} + +template <typename Char, typename Period, typename OutputIt> +OutputIt format_duration_unit(OutputIt out) { + if (const char* unit = get_units<Period>()) + return copy_unit(string_view(unit), out, Char()); + *out++ = '['; + out = write<Char>(out, Period::num); + if (const_check(Period::den != 1)) { + *out++ = '/'; + out = write<Char>(out, Period::den); + } + *out++ = ']'; + *out++ = 's'; + return out; +} + +template <typename FormatContext, typename OutputIt, typename Rep, + typename Period> +struct chrono_formatter { + FormatContext& context; + OutputIt out; + int precision; + // rep is unsigned to avoid overflow. + using rep = + conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int), + unsigned, typename make_unsigned_or_unchanged<Rep>::type>; + rep val; + using seconds = std::chrono::duration<rep>; + seconds s; + using milliseconds = std::chrono::duration<rep, std::milli>; + bool negative; + + using char_type = typename FormatContext::char_type; + + explicit chrono_formatter(FormatContext& ctx, OutputIt o, + std::chrono::duration<Rep, Period> d) + : context(ctx), + out(o), + val(static_cast<rep>(d.count())), + negative(false) { + if (d.count() < 0) { + val = 0 - val; + negative = true; + } + + // this may overflow and/or the result may not fit in the + // target type. +#if FMT_SAFE_DURATION_CAST + // might need checked conversion (rep!=Rep) + auto tmpval = std::chrono::duration<rep, Period>(val); + s = fmt_safe_duration_cast<seconds>(tmpval); +#else + s = std::chrono::duration_cast<seconds>( + std::chrono::duration<rep, Period>(val)); +#endif + } + + // returns true if nan or inf, writes to out. + bool handle_nan_inf() { + if (isfinite(val)) { + return false; + } + if (isnan(val)) { + write_nan(); + return true; + } + // must be +-inf + if (val > 0) { + write_pinf(); + } else { + write_ninf(); + } + return true; + } + + Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); } + + Rep hour12() const { + Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12)); + return hour <= 0 ? 12 : hour; + } + + Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); } + Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); } + + std::tm time() const { + auto time = std::tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + time.tm_min = to_nonnegative_int(minute(), 60); + time.tm_sec = to_nonnegative_int(second(), 60); + return time; + } + + void write_sign() { + if (negative) { + *out++ = '-'; + negative = false; + } + } + + void write(Rep value, int width) { + write_sign(); + if (isnan(value)) return write_nan(); + uint32_or_64_or_128_t<int> n = + to_unsigned(to_nonnegative_int(value, max_value<int>())); + int num_digits = detail::count_digits(n); + if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); + out = format_decimal<char_type>(out, n, num_digits).end; + } + + void write_nan() { std::copy_n("nan", 3, out); } + void write_pinf() { std::copy_n("inf", 3, out); } + void write_ninf() { std::copy_n("-inf", 4, out); } + + void format_localized(const tm& time, char format, char modifier = 0) { + if (isnan(val)) return write_nan(); + auto locale = context.locale().template get<std::locale>(); + auto& facet = std::use_facet<std::time_put<char_type>>(locale); + std::basic_ostringstream<char_type> os; + os.imbue(locale); + facet.put(os, os, ' ', &time, format, modifier); + auto str = os.str(); + std::copy(str.begin(), str.end(), out); + } + + void on_text(const char_type* begin, const char_type* end) { + std::copy(begin, end, out); + } + + // These are not implemented because durations don't have date information. + void on_abbr_weekday() {} + void on_full_weekday() {} + void on_dec0_weekday(numeric_system) {} + void on_dec1_weekday(numeric_system) {} + void on_abbr_month() {} + void on_full_month() {} + void on_datetime(numeric_system) {} + void on_loc_date(numeric_system) {} + void on_loc_time(numeric_system) {} + void on_us_date() {} + void on_iso_date() {} + void on_utc_offset() {} + void on_tz_name() {} + + void on_24_hour(numeric_system ns) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour(), 2); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + format_localized(time, 'H', 'O'); + } + + void on_12_hour(numeric_system ns) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour12(), 2); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour12(), 12); + format_localized(time, 'I', 'O'); + } + + void on_minute(numeric_system ns) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(minute(), 2); + auto time = tm(); + time.tm_min = to_nonnegative_int(minute(), 60); + format_localized(time, 'M', 'O'); + } + + void on_second(numeric_system ns) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) { + write(second(), 2); +#if FMT_SAFE_DURATION_CAST + // convert rep->Rep + using duration_rep = std::chrono::duration<rep, Period>; + using duration_Rep = std::chrono::duration<Rep, Period>; + auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val}); +#else + auto tmpval = std::chrono::duration<Rep, Period>(val); +#endif + auto ms = get_milliseconds(tmpval); + if (ms != std::chrono::milliseconds(0)) { + *out++ = '.'; + write(ms.count(), 3); + } + return; + } + auto time = tm(); + time.tm_sec = to_nonnegative_int(second(), 60); + format_localized(time, 'S', 'O'); + } + + void on_12_hour_time() { + if (handle_nan_inf()) return; + format_localized(time(), 'r'); + } + + void on_24_hour_time() { + if (handle_nan_inf()) { + *out++ = ':'; + handle_nan_inf(); + return; + } + + write(hour(), 2); + *out++ = ':'; + write(minute(), 2); + } + + void on_iso_time() { + on_24_hour_time(); + *out++ = ':'; + if (handle_nan_inf()) return; + write(second(), 2); + } + + void on_am_pm() { + if (handle_nan_inf()) return; + format_localized(time(), 'p'); + } + + void on_duration_value() { + if (handle_nan_inf()) return; + write_sign(); + out = format_duration_value<char_type>(out, val, precision); + } + + void on_duration_unit() { + out = format_duration_unit<char_type, Period>(out); + } +}; +} // namespace detail + +template <typename Rep, typename Period, typename Char> +struct formatter<std::chrono::duration<Rep, Period>, Char> { + private: + basic_format_specs<Char> specs; + int precision = -1; + using arg_ref_type = detail::arg_ref<Char>; + arg_ref_type width_ref; + arg_ref_type precision_ref; + basic_string_view<Char> format_str; + using duration = std::chrono::duration<Rep, Period>; + + struct spec_handler { + formatter& f; + basic_format_parse_context<Char>& context; + basic_string_view<Char> format_str; + + template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { + context.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) { + context.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { + return arg_ref_type(context.next_arg_id()); + } + + void on_error(const char* msg) { FMT_THROW(format_error(msg)); } + FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) { + f.specs.fill = fill; + } + FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; } + FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; } + FMT_CONSTEXPR void on_precision(int _precision) { + f.precision = _precision; + } + FMT_CONSTEXPR void end_precision() {} + + template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + f.width_ref = make_arg_ref(arg_id); + } + + template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + f.precision_ref = make_arg_ref(arg_id); + } + }; + + using iterator = typename basic_format_parse_context<Char>::iterator; + struct parse_range { + iterator begin; + iterator end; + }; + + FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context<Char>& ctx) { + auto begin = ctx.begin(), end = ctx.end(); + if (begin == end || *begin == '}') return {begin, begin}; + spec_handler handler{*this, ctx, format_str}; + begin = detail::parse_align(begin, end, handler); + if (begin == end) return {begin, begin}; + begin = detail::parse_width(begin, end, handler); + if (begin == end) return {begin, begin}; + if (*begin == '.') { + if (std::is_floating_point<Rep>::value) + begin = detail::parse_precision(begin, end, handler); + else + handler.on_error("precision not allowed for this argument type"); + } + end = parse_chrono_format(begin, end, detail::chrono_format_checker()); + return {begin, end}; + } + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) + -> decltype(ctx.begin()) { + auto range = do_parse(ctx); + format_str = basic_string_view<Char>( + &*range.begin, detail::to_unsigned(range.end - range.begin)); + return range.end; + } + + template <typename FormatContext> + auto format(const duration& d, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs_copy = specs; + auto precision_copy = precision; + auto begin = format_str.begin(), end = format_str.end(); + // As a possible future optimization, we could avoid extra copying if width + // is not specified. + basic_memory_buffer<Char> buf; + auto out = std::back_inserter(buf); + detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width, + width_ref, ctx); + detail::handle_dynamic_spec<detail::precision_checker>(precision_copy, + precision_ref, ctx); + if (begin == end || *begin == '}') { + out = detail::format_duration_value<Char>(out, d.count(), precision_copy); + detail::format_duration_unit<Char, Period>(out); + } else { + detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f( + ctx, out, d); + f.precision = precision_copy; + parse_chrono_format(begin, end, f); + } + return detail::write( + ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy); + } +}; + +FMT_MODULE_EXPORT_END +FMT_END_NAMESPACE + +#endif // FMT_CHRONO_H_ diff --git a/libs/fmt/include/fmt/color.h b/libs/fmt/include/fmt/color.h new file mode 100644 index 0000000000000000000000000000000000000000..f2e1a798f3103733a06df599f4bb770b644832fc --- /dev/null +++ b/libs/fmt/include/fmt/color.h @@ -0,0 +1,629 @@ +// Formatting library for C++ - color support +// +// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COLOR_H_ +#define FMT_COLOR_H_ + +#include "format.h" + +// __declspec(deprecated) is broken in some MSVC versions. +#if FMT_MSC_VER +# define FMT_DEPRECATED_NONMSVC +#else +# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED +#endif + +FMT_BEGIN_NAMESPACE +FMT_MODULE_EXPORT_BEGIN + +enum class color : uint32_t { + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aquamarine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32 // rgb(154,205,50) +}; // enum class color + +enum class terminal_color : uint8_t { + black = 30, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black = 90, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white +}; + +enum class emphasis : uint8_t { + bold = 1, + italic = 1 << 1, + underline = 1 << 2, + strikethrough = 1 << 3 +}; + +// rgb is a struct for red, green and blue colors. +// Using the name "rgb" makes some editors show the color in a tooltip. +struct rgb { + FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} + FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} + FMT_CONSTEXPR rgb(uint32_t hex) + : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} + FMT_CONSTEXPR rgb(color hex) + : r((uint32_t(hex) >> 16) & 0xFF), + g((uint32_t(hex) >> 8) & 0xFF), + b(uint32_t(hex) & 0xFF) {} + uint8_t r; + uint8_t g; + uint8_t b; +}; + +FMT_MODULE_EXPORT_END +namespace detail { + +// color is a struct of either a rgb color or a terminal color. +struct color_type { + FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {} + FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true), + value{} { + value.rgb_color = static_cast<uint32_t>(rgb_color); + } + FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} { + value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) | + (static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b; + } + FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(), + value{} { + value.term_color = static_cast<uint8_t>(term_color); + } + bool is_rgb; + union color_union { + uint8_t term_color; + uint32_t rgb_color; + } value; +}; +} // namespace detail +FMT_MODULE_EXPORT_BEGIN + +/** A text style consisting of foreground and background colors and emphasis. */ +class text_style { + public: + FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT + : set_foreground_color(), + set_background_color(), + ems(em) {} + + FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { + if (!set_foreground_color) { + set_foreground_color = rhs.set_foreground_color; + foreground_color = rhs.foreground_color; + } else if (rhs.set_foreground_color) { + if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) + FMT_THROW(format_error("can't OR a terminal color")); + foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; + } + + if (!set_background_color) { + set_background_color = rhs.set_background_color; + background_color = rhs.background_color; + } else if (rhs.set_background_color) { + if (!background_color.is_rgb || !rhs.background_color.is_rgb) + FMT_THROW(format_error("can't OR a terminal color")); + background_color.value.rgb_color |= rhs.background_color.value.rgb_color; + } + + ems = static_cast<emphasis>(static_cast<uint8_t>(ems) | + static_cast<uint8_t>(rhs.ems)); + return *this; + } + + friend FMT_CONSTEXPR text_style operator|(text_style lhs, + const text_style& rhs) { + return lhs |= rhs; + } + + FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=( + const text_style& rhs) { + return and_assign(rhs); + } + + FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style + operator&(text_style lhs, const text_style& rhs) { + return lhs.and_assign(rhs); + } + + FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT { + return set_foreground_color; + } + FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT { + return set_background_color; + } + FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT { + return static_cast<uint8_t>(ems) != 0; + } + FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT { + FMT_ASSERT(has_foreground(), "no foreground specified for this style"); + return foreground_color; + } + FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT { + FMT_ASSERT(has_background(), "no background specified for this style"); + return background_color; + } + FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT { + FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); + return ems; + } + + private: + FMT_CONSTEXPR text_style(bool is_foreground, + detail::color_type text_color) FMT_NOEXCEPT + : set_foreground_color(), + set_background_color(), + ems() { + if (is_foreground) { + foreground_color = text_color; + set_foreground_color = true; + } else { + background_color = text_color; + set_background_color = true; + } + } + + // DEPRECATED! + FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) { + if (!set_foreground_color) { + set_foreground_color = rhs.set_foreground_color; + foreground_color = rhs.foreground_color; + } else if (rhs.set_foreground_color) { + if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) + FMT_THROW(format_error("can't AND a terminal color")); + foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color; + } + + if (!set_background_color) { + set_background_color = rhs.set_background_color; + background_color = rhs.background_color; + } else if (rhs.set_background_color) { + if (!background_color.is_rgb || !rhs.background_color.is_rgb) + FMT_THROW(format_error("can't AND a terminal color")); + background_color.value.rgb_color &= rhs.background_color.value.rgb_color; + } + + ems = static_cast<emphasis>(static_cast<uint8_t>(ems) & + static_cast<uint8_t>(rhs.ems)); + return *this; + } + + friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground) + FMT_NOEXCEPT; + + friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background) + FMT_NOEXCEPT; + + detail::color_type foreground_color; + detail::color_type background_color; + bool set_foreground_color; + bool set_background_color; + emphasis ems; +}; + +/** Creates a text style from the foreground (text) color. */ +FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT { + return text_style(true, foreground); +} + +/** Creates a text style from the background color. */ +FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT { + return text_style(false, background); +} + +FMT_CONSTEXPR inline text_style operator|(emphasis lhs, + emphasis rhs) FMT_NOEXCEPT { + return text_style(lhs) | rhs; +} + +FMT_MODULE_EXPORT_END +namespace detail { + +template <typename Char> struct ansi_color_escape { + FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, + const char* esc) FMT_NOEXCEPT { + // If we have a terminal color, we need to output another escape code + // sequence. + if (!text_color.is_rgb) { + bool is_background = esc == string_view("\x1b[48;2;"); + uint32_t value = text_color.value.term_color; + // Background ASCII codes are the same as the foreground ones but with + // 10 more. + if (is_background) value += 10u; + + size_t index = 0; + buffer[index++] = static_cast<Char>('\x1b'); + buffer[index++] = static_cast<Char>('['); + + if (value >= 100u) { + buffer[index++] = static_cast<Char>('1'); + value %= 100u; + } + buffer[index++] = static_cast<Char>('0' + value / 10u); + buffer[index++] = static_cast<Char>('0' + value % 10u); + + buffer[index++] = static_cast<Char>('m'); + buffer[index++] = static_cast<Char>('\0'); + return; + } + + for (int i = 0; i < 7; i++) { + buffer[i] = static_cast<Char>(esc[i]); + } + rgb color(text_color.value.rgb_color); + to_esc(color.r, buffer + 7, ';'); + to_esc(color.g, buffer + 11, ';'); + to_esc(color.b, buffer + 15, 'm'); + buffer[19] = static_cast<Char>(0); + } + FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { + uint8_t em_codes[4] = {}; + uint8_t em_bits = static_cast<uint8_t>(em); + if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1; + if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3; + if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4; + if (em_bits & static_cast<uint8_t>(emphasis::strikethrough)) + em_codes[3] = 9; + + size_t index = 0; + for (int i = 0; i < 4; ++i) { + if (!em_codes[i]) continue; + buffer[index++] = static_cast<Char>('\x1b'); + buffer[index++] = static_cast<Char>('['); + buffer[index++] = static_cast<Char>('0' + em_codes[i]); + buffer[index++] = static_cast<Char>('m'); + } + buffer[index++] = static_cast<Char>(0); + } + FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; } + + FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } + FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT { + return buffer + std::char_traits<Char>::length(buffer); + } + + private: + Char buffer[7u + 3u * 4u + 1u]; + + static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, + char delimiter) FMT_NOEXCEPT { + out[0] = static_cast<Char>('0' + c / 100); + out[1] = static_cast<Char>('0' + c / 10 % 10); + out[2] = static_cast<Char>('0' + c % 10); + out[3] = static_cast<Char>(delimiter); + } +}; + +template <typename Char> +FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color( + detail::color_type foreground) FMT_NOEXCEPT { + return ansi_color_escape<Char>(foreground, "\x1b[38;2;"); +} + +template <typename Char> +FMT_CONSTEXPR ansi_color_escape<Char> make_background_color( + detail::color_type background) FMT_NOEXCEPT { + return ansi_color_escape<Char>(background, "\x1b[48;2;"); +} + +template <typename Char> +FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT { + return ansi_color_escape<Char>(em); +} + +template <typename Char> +inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT { + std::fputs(chars, stream); +} + +template <> +inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT { + std::fputws(chars, stream); +} + +template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT { + fputs("\x1b[0m", stream); +} + +template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT { + fputs(L"\x1b[0m", stream); +} + +template <typename Char> +inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT { + auto reset_color = string_view("\x1b[0m"); + buffer.append(reset_color.begin(), reset_color.end()); +} + +template <typename Char> +void vformat_to(buffer<Char>& buf, const text_style& ts, + basic_string_view<Char> format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis()); + buf.append(emphasis.begin(), emphasis.end()); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = detail::make_foreground_color<Char>(ts.get_foreground()); + buf.append(foreground.begin(), foreground.end()); + } + if (ts.has_background()) { + has_style = true; + auto background = detail::make_background_color<Char>(ts.get_background()); + buf.append(background.begin(), background.end()); + } + detail::vformat_to(buf, format_str, args); + if (has_style) detail::reset_color<Char>(buf); +} +} // namespace detail +FMT_MODULE_EXPORT_BEGIN + +template <typename S, typename Char = char_t<S>> +void vprint(std::FILE* f, const text_style& ts, const S& format, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + basic_memory_buffer<Char> buf; + detail::vformat_to(buf, ts, to_string_view(format), args); + buf.push_back(Char(0)); + detail::fputs(buf.data(), f); +} + +/** + \rst + Formats a string and prints it to the specified file stream using ANSI + escape sequences to specify text formatting. + + **Example**:: + + fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + "Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template <typename S, typename... Args, + FMT_ENABLE_IF(detail::is_string<S>::value)> +void print(std::FILE* f, const text_style& ts, const S& format_str, + const Args&... args) { + vprint(f, ts, format_str, + fmt::make_args_checked<Args...>(format_str, args...)); +} + +/** + \rst + Formats a string and prints it to stdout using ANSI escape sequences to + specify text formatting. + + **Example**:: + + fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + "Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template <typename S, typename... Args, + FMT_ENABLE_IF(detail::is_string<S>::value)> +void print(const text_style& ts, const S& format_str, const Args&... args) { + return print(stdout, ts, format_str, args...); +} + +template <typename S, typename Char = char_t<S>> +inline std::basic_string<Char> vformat( + const text_style& ts, const S& format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + basic_memory_buffer<Char> buf; + detail::vformat_to(buf, ts, to_string_view(format_str), args); + return fmt::to_string(buf); +} + +/** + \rst + Formats arguments and returns the result as a string using ANSI + escape sequences to specify text formatting. + + **Example**:: + + #include <fmt/color.h> + std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + "The answer is {}", 42); + \endrst +*/ +template <typename S, typename... Args, typename Char = char_t<S>> +inline std::basic_string<Char> format(const text_style& ts, const S& format_str, + const Args&... args) { + return vformat(ts, to_string_view(format_str), + fmt::make_args_checked<Args...>(format_str, args...)); +} + +/** + Formats a string with the given text_style and writes the output to ``out``. + */ +template <typename OutputIt, typename Char, + FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)> +OutputIt vformat_to( + OutputIt out, const text_style& ts, basic_string_view<Char> format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out)); + detail::vformat_to(buf, ts, format_str, args); + return detail::get_iterator(buf); +} + +/** + \rst + Formats arguments with the given text_style, writes the result to the output + iterator ``out`` and returns the iterator past the end of the output range. + + **Example**:: + + std::vector<char> out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + \endrst +*/ +template <typename OutputIt, typename S, typename... Args, + bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&& + detail::is_string<S>::value> +inline auto format_to(OutputIt out, const text_style& ts, const S& format_str, + Args&&... args) -> + typename std::enable_if<enable, OutputIt>::type { + return vformat_to(out, ts, to_string_view(format_str), + fmt::make_args_checked<Args...>(format_str, args...)); +} + +FMT_MODULE_EXPORT_END +FMT_END_NAMESPACE + +#endif // FMT_COLOR_H_ diff --git a/libs/fmt/include/fmt/compile.h b/libs/fmt/include/fmt/compile.h new file mode 100644 index 0000000000000000000000000000000000000000..7ff2fe866077edd77c0b335a77f91b6f8c0226a7 --- /dev/null +++ b/libs/fmt/include/fmt/compile.h @@ -0,0 +1,642 @@ +// Formatting library for C++ - experimental format string compilation +// +// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COMPILE_H_ +#define FMT_COMPILE_H_ + +#include <algorithm> +#include <vector> + +#include "format.h" + +FMT_BEGIN_NAMESPACE +namespace detail { + +// An output iterator that counts the number of objects written to it and +// discards them. +class counting_iterator { + private: + size_t count_; + + public: + using iterator_category = std::output_iterator_tag; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = void; + using _Unchecked_type = counting_iterator; // Mark iterator as checked. + + struct value_type { + template <typename T> void operator=(const T&) {} + }; + + counting_iterator() : count_(0) {} + + size_t count() const { return count_; } + + counting_iterator& operator++() { + ++count_; + return *this; + } + counting_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + friend counting_iterator operator+(counting_iterator it, difference_type n) { + it.count_ += static_cast<size_t>(n); + return it; + } + + value_type operator*() const { return {}; } +}; + +template <typename Char, typename InputIt> +inline counting_iterator copy_str(InputIt begin, InputIt end, + counting_iterator it) { + return it + (end - begin); +} + +template <typename OutputIt> class truncating_iterator_base { + protected: + OutputIt out_; + size_t limit_; + size_t count_ = 0; + + truncating_iterator_base() : out_(), limit_(0) {} + + truncating_iterator_base(OutputIt out, size_t limit) + : out_(out), limit_(limit) {} + + public: + using iterator_category = std::output_iterator_tag; + using value_type = typename std::iterator_traits<OutputIt>::value_type; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = void; + using _Unchecked_type = + truncating_iterator_base; // Mark iterator as checked. + + OutputIt base() const { return out_; } + size_t count() const { return count_; } +}; + +// An output iterator that truncates the output and counts the number of objects +// written to it. +template <typename OutputIt, + typename Enable = typename std::is_void< + typename std::iterator_traits<OutputIt>::value_type>::type> +class truncating_iterator; + +template <typename OutputIt> +class truncating_iterator<OutputIt, std::false_type> + : public truncating_iterator_base<OutputIt> { + mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_; + + public: + using value_type = typename truncating_iterator_base<OutputIt>::value_type; + + truncating_iterator() = default; + + truncating_iterator(OutputIt out, size_t limit) + : truncating_iterator_base<OutputIt>(out, limit) {} + + truncating_iterator& operator++() { + if (this->count_++ < this->limit_) ++this->out_; + return *this; + } + + truncating_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + value_type& operator*() const { + return this->count_ < this->limit_ ? *this->out_ : blackhole_; + } +}; + +template <typename OutputIt> +class truncating_iterator<OutputIt, std::true_type> + : public truncating_iterator_base<OutputIt> { + public: + truncating_iterator() = default; + + truncating_iterator(OutputIt out, size_t limit) + : truncating_iterator_base<OutputIt>(out, limit) {} + + template <typename T> truncating_iterator& operator=(T val) { + if (this->count_++ < this->limit_) *this->out_++ = val; + return *this; + } + + truncating_iterator& operator++() { return *this; } + truncating_iterator& operator++(int) { return *this; } + truncating_iterator& operator*() { return *this; } +}; + +// A compile-time string which is compiled into fast formatting code. +class compiled_string {}; + +template <typename S> +struct is_compiled_string : std::is_base_of<compiled_string, S> {}; + +/** + \rst + Converts a string literal *s* into a format string that will be parsed at + compile time and converted into efficient formatting code. Requires C++17 + ``constexpr if`` compiler support. + + **Example**:: + + // Converts 42 into std::string using the most efficient method and no + // runtime format string processing. + std::string s = fmt::format(FMT_COMPILE("{}"), 42); + \endrst + */ +#ifdef __cpp_if_constexpr +# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string) +#else +# define FMT_COMPILE(s) FMT_STRING(s) +#endif + +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +template <typename Char, size_t N, fixed_string<Char, N> Str> +struct udl_compiled_string : compiled_string { + using char_type = Char; + constexpr operator basic_string_view<char_type>() const { + return {Str.data, N - 1}; + } +}; +#endif + +template <typename T, typename... Tail> +const T& first(const T& value, const Tail&...) { + return value; +} + +#ifdef __cpp_if_constexpr +template <typename... Args> struct type_list {}; + +// Returns a reference to the argument at index N from [first, rest...]. +template <int N, typename T, typename... Args> +constexpr const auto& get([[maybe_unused]] const T& first, + [[maybe_unused]] const Args&... rest) { + static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); + if constexpr (N == 0) + return first; + else + return get<N - 1>(rest...); +} + +constexpr int invalid_arg_index = -1; + +template <int N, typename T, typename... Args, typename Char> +constexpr int get_arg_index_by_name(basic_string_view<Char> name) { + if constexpr (detail::is_statically_named_arg<T>()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) == 0) { + return invalid_arg_index; + } else { + return get_arg_index_by_name<N + 1, Args...>(name); + } +} + +template <typename Char, typename... Args> +constexpr int get_arg_index_by_name(basic_string_view<Char> name, + type_list<Args...>) { + return get_arg_index_by_name<0, Args...>(name); +} + +template <int N, typename> struct get_type_impl; + +template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> { + using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>; +}; + +template <int N, typename T> +using get_type = typename get_type_impl<N, T>::type; + +template <typename T> struct is_compiled_format : std::false_type {}; + +template <typename Char> struct text { + basic_string_view<Char> data; + using char_type = Char; + + template <typename OutputIt, typename... Args> + constexpr OutputIt format(OutputIt out, const Args&...) const { + return write<Char>(out, data); + } +}; + +template <typename Char> +struct is_compiled_format<text<Char>> : std::true_type {}; + +template <typename Char> +constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos, + size_t size) { + return {{&s[pos], size}}; +} + +template <typename Char> struct code_unit { + Char value; + using char_type = Char; + + template <typename OutputIt, typename... Args> + constexpr OutputIt format(OutputIt out, const Args&...) const { + return write<Char>(out, value); + } +}; + +// This ensures that the argument type is convertible to `const T&`. +template <typename T, int N, typename... Args> +constexpr const T& get_arg_checked(const Args&... args) { + const auto& arg = get<N>(args...); + if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) { + return arg.value; + } else { + return arg; + } +} + +template <typename Char> +struct is_compiled_format<code_unit<Char>> : std::true_type {}; + +// A replacement field that refers to argument N. +template <typename Char, typename T, int N> struct field { + using char_type = Char; + + template <typename OutputIt, typename... Args> + constexpr OutputIt format(OutputIt out, const Args&... args) const { + return write<Char>(out, get_arg_checked<T, N>(args...)); + } +}; + +template <typename Char, typename T, int N> +struct is_compiled_format<field<Char, T, N>> : std::true_type {}; + +// A replacement field that refers to argument with name. +template <typename Char> struct runtime_named_field { + using char_type = Char; + basic_string_view<Char> name; + + template <typename OutputIt, typename T> + constexpr static bool try_format_argument( + OutputIt& out, + // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 + [[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) { + if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) { + if (arg_name == arg.name) { + out = write<Char>(out, arg.value); + return true; + } + } + return false; + } + + template <typename OutputIt, typename... Args> + constexpr OutputIt format(OutputIt out, const Args&... args) const { + bool found = (try_format_argument(out, name, args) || ...); + if (!found) { + throw format_error("argument with specified name is not found"); + } + return out; + } +}; + +template <typename Char> +struct is_compiled_format<runtime_named_field<Char>> : std::true_type {}; + +// A replacement field that refers to argument N and has format specifiers. +template <typename Char, typename T, int N> struct spec_field { + using char_type = Char; + formatter<T, Char> fmt; + + template <typename OutputIt, typename... Args> + constexpr OutputIt format(OutputIt out, const Args&... args) const { + const auto& vargs = + make_format_args<basic_format_context<OutputIt, Char>>(args...); + basic_format_context<OutputIt, Char> ctx(out, vargs); + return fmt.format(get_arg_checked<T, N>(args...), ctx); + } +}; + +template <typename Char, typename T, int N> +struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {}; + +template <typename L, typename R> struct concat { + L lhs; + R rhs; + using char_type = typename L::char_type; + + template <typename OutputIt, typename... Args> + constexpr OutputIt format(OutputIt out, const Args&... args) const { + out = lhs.format(out, args...); + return rhs.format(out, args...); + } +}; + +template <typename L, typename R> +struct is_compiled_format<concat<L, R>> : std::true_type {}; + +template <typename L, typename R> +constexpr concat<L, R> make_concat(L lhs, R rhs) { + return {lhs, rhs}; +} + +struct unknown_format {}; + +template <typename Char> +constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) { + for (size_t size = str.size(); pos != size; ++pos) { + if (str[pos] == '{' || str[pos] == '}') break; + } + return pos; +} + +template <typename Args, size_t POS, int ID, typename S> +constexpr auto compile_format_string(S format_str); + +template <typename Args, size_t POS, int ID, typename T, typename S> +constexpr auto parse_tail(T head, S format_str) { + if constexpr (POS != + basic_string_view<typename S::char_type>(format_str).size()) { + constexpr auto tail = compile_format_string<Args, POS, ID>(format_str); + if constexpr (std::is_same<remove_cvref_t<decltype(tail)>, + unknown_format>()) + return tail; + else + return make_concat(head, tail); + } else { + return head; + } +} + +template <typename T, typename Char> struct parse_specs_result { + formatter<T, Char> fmt; + size_t end; + int next_arg_id; +}; + +constexpr int manual_indexing_id = -1; + +template <typename T, typename Char> +constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, + size_t pos, int next_arg_id) { + str.remove_prefix(pos); + auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id); + auto f = formatter<T, Char>(); + auto end = f.parse(ctx); + return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1, + next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; +} + +template <typename Char> struct arg_id_handler { + constexpr void on_error(const char* message) { throw format_error(message); } + + constexpr int on_arg_id() { + FMT_ASSERT(false, "handler cannot be used with automatic indexing"); + return 0; + } + + constexpr int on_arg_id(int id) { + arg_id = arg_ref<Char>(id); + return 0; + } + + constexpr int on_arg_id(basic_string_view<Char> id) { + arg_id = arg_ref<Char>(id); + return 0; + } + + arg_ref<Char> arg_id; +}; + +template <typename Char> struct parse_arg_id_result { + arg_ref<Char> arg_id; + const Char* arg_id_end; +}; + +template <int ID, typename Char> +constexpr auto parse_arg_id(const Char* begin, const Char* end) { + auto handler = arg_id_handler<Char>{arg_ref<Char>{}}; + auto adapter = id_adapter<arg_id_handler<Char>, Char>{handler, 0}; + auto arg_id_end = parse_arg_id(begin, end, adapter); + return parse_arg_id_result<Char>{handler.arg_id, arg_id_end}; +} + +template <typename T, typename Enable = void> struct field_type { + using type = remove_cvref_t<T>; +}; + +template <typename T> +struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> { + using type = remove_cvref_t<decltype(T::value)>; +}; + +template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID, + typename S> +constexpr auto parse_replacement_field_then_tail(S format_str) { + using char_type = typename S::char_type; + constexpr basic_string_view<char_type> str = format_str; + constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); + if constexpr (c == '}') { + return parse_tail<Args, END_POS + 1, NEXT_ID>( + field<char_type, typename field_type<T>::type, ARG_INDEX>(), + format_str); + } else if constexpr (c == ':') { + constexpr auto result = parse_specs<typename field_type<T>::type>( + str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); + return parse_tail<Args, result.end, result.next_arg_id>( + spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{ + result.fmt}, + format_str); + } +} + +// Compiles a non-empty format string and returns the compiled representation +// or unknown_format() on unrecognized input. +template <typename Args, size_t POS, int ID, typename S> +constexpr auto compile_format_string(S format_str) { + using char_type = typename S::char_type; + constexpr basic_string_view<char_type> str = format_str; + if constexpr (str[POS] == '{') { + if constexpr (POS + 1 == str.size()) + throw format_error("unmatched '{' in format string"); + if constexpr (str[POS + 1] == '{') { + return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); + } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { + static_assert(ID != manual_indexing_id, + "cannot switch from manual to automatic argument indexing"); + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail<get_type<ID, Args>, Args, + POS + 1, ID, next_id>( + format_str); + } else { + constexpr auto arg_id_result = + parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size()); + constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); + constexpr char_type c = + arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); + static_assert(c == '}' || c == ':', "missing '}' in format string"); + if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { + static_assert( + ID == manual_indexing_id || ID == 0, + "cannot switch from automatic to manual argument indexing"); + constexpr auto arg_index = arg_id_result.arg_id.val.index; + return parse_replacement_field_then_tail<get_type<arg_index, Args>, + Args, arg_id_end_pos, + arg_index, manual_indexing_id>( + format_str); + } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { + constexpr auto arg_index = + get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); + if constexpr (arg_index != invalid_arg_index) { + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail< + decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos, + arg_index, next_id>(format_str); + } else { + if constexpr (c == '}') { + return parse_tail<Args, arg_id_end_pos + 1, ID>( + runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, + format_str); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing + } + } + } + } + } else if constexpr (str[POS] == '}') { + if constexpr (POS + 1 == str.size()) + throw format_error("unmatched '}' in format string"); + return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); + } else { + constexpr auto end = parse_text(str, POS + 1); + if constexpr (end - POS > 1) { + return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), + format_str); + } else { + return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, + format_str); + } + } +} + +template <typename... Args, typename S, + FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> +constexpr auto compile(S format_str) { + constexpr basic_string_view<typename S::char_type> str = format_str; + if constexpr (str.size() == 0) { + return detail::make_text(str, 0, 0); + } else { + constexpr auto result = + detail::compile_format_string<detail::type_list<Args...>, 0, 0>( + format_str); + return result; + } +} +#endif // __cpp_if_constexpr +} // namespace detail + +FMT_MODULE_EXPORT_BEGIN + +#ifdef __cpp_if_constexpr + +template <typename CompiledFormat, typename... Args, + typename Char = typename CompiledFormat::char_type, + FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> +FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf, + const Args&... args) { + basic_memory_buffer<Char> buffer; + cf.format(detail::buffer_appender<Char>(buffer), args...); + return to_string(buffer); +} + +template <typename OutputIt, typename CompiledFormat, typename... Args, + FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> +constexpr OutputIt format_to(OutputIt out, const CompiledFormat& cf, + const Args&... args) { + return cf.format(out, args...); +} + +template <typename S, typename... Args, + FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> +FMT_INLINE std::basic_string<typename S::char_type> format(const S&, + Args&&... args) { + if constexpr (std::is_same<typename S::char_type, char>::value) { + constexpr basic_string_view<typename S::char_type> str = S(); + if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { + const auto& first = detail::first(args...); + if constexpr (detail::is_named_arg< + remove_cvref_t<decltype(first)>>::value) { + return fmt::to_string(first.value); + } else { + return fmt::to_string(first); + } + } + } + constexpr auto compiled = detail::compile<Args...>(S()); + if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, + detail::unknown_format>()) { + return format(static_cast<basic_string_view<typename S::char_type>>(S()), + std::forward<Args>(args)...); + } else { + return format(compiled, std::forward<Args>(args)...); + } +} + +template <typename OutputIt, typename S, typename... Args, + FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> +FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { + constexpr auto compiled = detail::compile<Args...>(S()); + if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, + detail::unknown_format>()) { + return format_to(out, + static_cast<basic_string_view<typename S::char_type>>(S()), + std::forward<Args>(args)...); + } else { + return format_to(out, compiled, std::forward<Args>(args)...); + } +} +#endif + +template <typename OutputIt, typename S, typename... Args, + FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> +format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, + const S& format_str, Args&&... args) { + auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str, + std::forward<Args>(args)...); + return {it.base(), it.count()}; +} + +template <typename S, typename... Args, + FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> +size_t formatted_size(const S& format_str, const Args&... args) { + return format_to(detail::counting_iterator(), format_str, args...).count(); +} + +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +inline namespace literals { +template <detail::fixed_string Str> +constexpr detail::udl_compiled_string< + remove_cvref_t<decltype(Str.data[0])>, + sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str> +operator""_cf() { + return {}; +} +} // namespace literals +#endif + +FMT_MODULE_EXPORT_END +FMT_END_NAMESPACE + +#endif // FMT_COMPILE_H_ diff --git a/libs/fmt/include/fmt/core.h b/libs/fmt/include/fmt/core.h new file mode 100644 index 0000000000000000000000000000000000000000..10ac28438e0e36d6d1e7904605d280498ad25f35 --- /dev/null +++ b/libs/fmt/include/fmt/core.h @@ -0,0 +1,2531 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CORE_H_ +#define FMT_CORE_H_ + +#include <climits> // INT_MAX +#include <cstdio> // std::FILE +#include <cstring> +#include <iterator> +#include <string> +#include <type_traits> + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 70104 + +#ifdef __clang__ +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# define FMT_GCC_PRAGMA(arg) _Pragma(arg) +#else +# define FMT_GCC_VERSION 0 +# define FMT_GCC_PRAGMA(arg) +#endif + +#if defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION +#else +# define FMT_HAS_GXX_CXX11 0 +#endif + +// Check if constexpr std::char_traits<>::compare,length is supported. +// +// libstdc++: GCC 7 and newer and __cplusplus >= 201703L +// MSVC : VS 2017 15.7 and newer and /std:c++17 +// https://docs.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance +// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros +// libc++ : 4.0 and newer and if __cplusplus >= 201703L +// https://libcxx.llvm.org/docs/Cxx1zStatus.html +// +// NOTE: FMT_GCC_VERSION - is not libstdc++ version. +// _GLIBCXX_RELEASE - is present in GCC 7 libstdc++ and newer. +#if defined(_MSC_VER) +# if _MSVC_LANG >= 201703L && _MSC_VER >= 1914 +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +# endif +#elif defined(_LIBCPP_VERSION) +# if __cplusplus >= 201703L && _LIBCPP_VERSION >= 4000 +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +# endif +#elif defined(__GLIBCXX__) +# if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \ + _GLIBCXX_RELEASE >= 7 +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +# endif +#endif +#ifndef FMT_CONSTEXPR_CHAR_TRAITS +# define FMT_CONSTEXPR_CHAR_TRAITS +#endif + +#ifdef __NVCC__ +# define FMT_NVCC __NVCC__ +#else +# define FMT_NVCC 0 +#endif + +#ifdef _MSC_VER +# define FMT_MSC_VER _MSC_VER +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) +#else +# define FMT_MSC_VER 0 +# define FMT_MSC_WARNING(...) +#endif + +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif + +#if defined(__has_include) && !defined(__INTELLISENSE__) && \ + (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600) +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif + +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +// Check if relaxed C++14 constexpr is supported. +// GCC doesn't allow throw in constexpr until version 6 (bug 67371). +#ifndef FMT_USE_CONSTEXPR +# define FMT_USE_CONSTEXPR \ + (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ + (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \ + !FMT_NVCC && !FMT_ICC_VERSION +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +# define FMT_CONSTEXPR_DECL constexpr +#else +# define FMT_CONSTEXPR +# define FMT_CONSTEXPR_DECL +#endif + +#ifndef FMT_OVERRIDE +# if FMT_HAS_FEATURE(cxx_override_control) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +# define FMT_OVERRIDE override +# else +# define FMT_OVERRIDE +# endif +#endif + +// Check if exceptions are disabled. +#ifndef FMT_EXCEPTIONS +# if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ + FMT_MSC_VER && !_HAS_EXCEPTIONS +# define FMT_EXCEPTIONS 0 +# else +# define FMT_EXCEPTIONS 1 +# endif +#endif + +// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). +#ifndef FMT_USE_NOEXCEPT +# define FMT_USE_NOEXCEPT 0 +#endif + +#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +# define FMT_DETECTED_NOEXCEPT noexcept +# define FMT_HAS_CXX11_NOEXCEPT 1 +#else +# define FMT_DETECTED_NOEXCEPT throw() +# define FMT_HAS_CXX11_NOEXCEPT 0 +#endif + +#ifndef FMT_NOEXCEPT +# if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT +# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +# else +# define FMT_NOEXCEPT +# endif +#endif + +// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code +// warnings. +#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VER && \ + !FMT_NVCC +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +#ifndef FMT_MAYBE_UNUSED +# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) +# define FMT_MAYBE_UNUSED [[maybe_unused]] +# else +# define FMT_MAYBE_UNUSED +# endif +#endif + +#ifndef FMT_DEPRECATED +# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 +# define FMT_DEPRECATED [[deprecated]] +# else +# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) +# define FMT_DEPRECATED __attribute__((deprecated)) +# elif FMT_MSC_VER +# define FMT_DEPRECATED __declspec(deprecated) +# else +# define FMT_DEPRECATED /* deprecated */ +# endif +# endif +#endif + +// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. +#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC +# define FMT_DEPRECATED_ALIAS +#else +# define FMT_DEPRECATED_ALIAS FMT_DEPRECATED +#endif + +#ifndef FMT_INLINE +# if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_INLINE inline __attribute__((always_inline)) +# else +# define FMT_INLINE inline +# endif +#endif + +#ifndef FMT_USE_INLINE_NAMESPACES +# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ + (FMT_MSC_VER >= 1900 && (!defined(_MANAGED) || !_MANAGED)) +# define FMT_USE_INLINE_NAMESPACES 1 +# else +# define FMT_USE_INLINE_NAMESPACES 0 +# endif +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# if FMT_USE_INLINE_NAMESPACES +# define FMT_INLINE_NAMESPACE inline namespace +# define FMT_END_NAMESPACE \ + } \ + } +# else +# define FMT_INLINE_NAMESPACE namespace +# define FMT_END_NAMESPACE \ + } \ + using namespace v7; \ + } +# endif +# define FMT_BEGIN_NAMESPACE \ + namespace fmt { \ + FMT_INLINE_NAMESPACE v7 { +#endif + +#ifndef FMT_MODULE_EXPORT +# define FMT_MODULE_EXPORT +#endif +#ifndef FMT_MODULE_EXPORT_BEGIN +# define FMT_MODULE_EXPORT_BEGIN +#endif +#ifndef FMT_MODULE_EXPORT_END +# define FMT_MODULE_EXPORT_END +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) +# ifdef FMT_EXPORT +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#else +# define FMT_CLASS_API +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#if FMT_GCC_VERSION +# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) +#else +# define FMT_GCC_VISIBILITY_HIDDEN +#endif + +// libc++ supports string_view in pre-c++17. +#if (FMT_HAS_INCLUDE(<string_view>) && \ + (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ + (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) +# include <string_view> +# define FMT_USE_STRING_VIEW +#elif FMT_HAS_INCLUDE("experimental/string_view") && __cplusplus >= 201402L +# include <experimental/string_view> +# define FMT_USE_EXPERIMENTAL_STRING_VIEW +#endif + +#ifndef FMT_UNICODE +# define FMT_UNICODE !FMT_MSC_VER +#endif + +#ifndef FMT_COMPILE_TIME_CHECKS +# define FMT_COMPILE_TIME_CHECKS 0 +#endif + +// Enable minimal optimizations for more compact code in debug mode. +FMT_GCC_PRAGMA("GCC push_options") +#ifndef __OPTIMIZE__ +FMT_GCC_PRAGMA("GCC optimize(\"Og\")") +#endif + +FMT_BEGIN_NAMESPACE +FMT_MODULE_EXPORT_BEGIN + +// Implementations of enable_if_t and other metafunctions for older systems. +template <bool B, class T = void> +using enable_if_t = typename std::enable_if<B, T>::type; +template <bool B, class T, class F> +using conditional_t = typename std::conditional<B, T, F>::type; +template <bool B> using bool_constant = std::integral_constant<bool, B>; +template <typename T> +using remove_reference_t = typename std::remove_reference<T>::type; +template <typename T> +using remove_const_t = typename std::remove_const<T>::type; +template <typename T> +using remove_cvref_t = typename std::remove_cv<remove_reference_t<T>>::type; +template <typename T> struct type_identity { using type = T; }; +template <typename T> using type_identity_t = typename type_identity<T>::type; + +struct monostate {}; + +FMT_MODULE_EXPORT_END + +// An enable_if helper to be used in template parameters which results in much +// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed +// to workaround a bug in MSVC 2019 (see #1140 and #1186). +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +#endif + +namespace detail { + +constexpr FMT_INLINE bool is_constant_evaluated() FMT_NOEXCEPT { +#ifdef __cpp_lib_is_constant_evaluated + return std::is_constant_evaluated(); +#else + return false; +#endif +} + +// A helper function to suppress "conditional expression is constant" warnings. +template <typename T> constexpr T const_check(T value) { return value; } + +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); + +#ifndef FMT_ASSERT +# ifdef NDEBUG +// FMT_ASSERT is not empty to avoid -Werror=empty-body. +# define FMT_ASSERT(condition, message) ((void)0) +# else +# define FMT_ASSERT(condition, message) \ + ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ + ? (void)0 \ + : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) +# endif +#endif + +#if defined(FMT_USE_STRING_VIEW) +template <typename Char> using std_string_view = std::basic_string_view<Char>; +#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) +template <typename Char> +using std_string_view = std::experimental::basic_string_view<Char>; +#else +template <typename T> struct std_string_view {}; +#endif + +#ifdef FMT_USE_INT128 +// Do nothing. +#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \ + !(FMT_CLANG_VERSION && FMT_MSC_VER) +# define FMT_USE_INT128 1 +using int128_t = __int128_t; +using uint128_t = __uint128_t; +#else +# define FMT_USE_INT128 0 +#endif +#if !FMT_USE_INT128 +struct int128_t {}; +struct uint128_t {}; +#endif + +// Casts a nonnegative integer to unsigned. +template <typename Int> +FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) { + FMT_ASSERT(value >= 0, "negative value"); + return static_cast<typename std::make_unsigned<Int>::type>(value); +} + +FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; + +template <typename Char> constexpr bool is_unicode() { + return FMT_UNICODE || sizeof(Char) != 1 || + (sizeof(micro) == 3 && micro[0] == 0xC2 && micro[1] == 0xB5); +} +} // namespace detail + +/** + An implementation of ``std::basic_string_view`` for pre-C++17. It provides a + subset of the API. ``fmt::basic_string_view`` is used for format strings even + if ``std::string_view`` is available to prevent issues when a library is + compiled with a different ``-std`` option than the client code (which is not + recommended). + */ +FMT_MODULE_EXPORT_BEGIN + +template <typename Char> class basic_string_view { + private: + const Char* data_; + size_t size_; + + public: + using value_type = Char; + using iterator = const Char*; + + constexpr basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {} + + /** Constructs a string reference object from a C string and a size. */ + constexpr basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT + : data_(s), + size_(count) {} + + /** + \rst + Constructs a string reference object from a C string computing + the size with ``std::char_traits<Char>::length``. + \endrst + */ + FMT_CONSTEXPR_CHAR_TRAITS + FMT_INLINE + basic_string_view(const Char* s) : data_(s) { + if (detail::const_check(std::is_same<Char, char>::value && + !detail::is_constant_evaluated())) + size_ = std::strlen(reinterpret_cast<const char*>(s)); + else + size_ = std::char_traits<Char>::length(s); + } + + /** Constructs a string reference from a ``std::basic_string`` object. */ + template <typename Traits, typename Alloc> + FMT_CONSTEXPR basic_string_view( + const std::basic_string<Char, Traits, Alloc>& s) FMT_NOEXCEPT + : data_(s.data()), + size_(s.size()) {} + + template <typename S, FMT_ENABLE_IF(std::is_same< + S, detail::std_string_view<Char>>::value)> + FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()), + size_(s.size()) {} + + /** Returns a pointer to the string data. */ + constexpr const Char* data() const { return data_; } + + /** Returns the string size. */ + constexpr size_t size() const { return size_; } + + constexpr iterator begin() const { return data_; } + constexpr iterator end() const { return data_ + size_; } + + constexpr const Char& operator[](size_t pos) const { return data_[pos]; } + + FMT_CONSTEXPR void remove_prefix(size_t n) { + data_ += n; + size_ -= n; + } + + // Lexicographically compare this string reference to other. + FMT_CONSTEXPR_CHAR_TRAITS int compare(basic_string_view other) const { + size_t str_size = size_ < other.size_ ? size_ : other.size_; + int result = std::char_traits<Char>::compare(data_, other.data_, str_size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + FMT_CONSTEXPR_CHAR_TRAITS friend bool operator==(basic_string_view lhs, + basic_string_view rhs) { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) != 0; + } + friend bool operator<(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) < 0; + } + friend bool operator<=(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) <= 0; + } + friend bool operator>(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) >= 0; + } +}; + +using string_view = basic_string_view<char>; +using wstring_view = basic_string_view<wchar_t>; + +/** Specifies if ``T`` is a character type. Can be specialized by users. */ +template <typename T> struct is_char : std::false_type {}; +template <> struct is_char<char> : std::true_type {}; +template <> struct is_char<wchar_t> : std::true_type {}; + +/** + \rst + Returns a string view of `s`. In order to add custom string type support to + {fmt} provide an overload of `to_string_view` for it in the same namespace as + the type for the argument-dependent lookup to work. + + **Example**:: + + namespace my_ns { + inline string_view to_string_view(const my_string& s) { + return {s.data(), s.length()}; + } + } + std::string message = fmt::format(my_string("The answer is {}"), 42); + \endrst + */ +template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)> +FMT_INLINE basic_string_view<Char> to_string_view(const Char* s) { + return s; +} + +template <typename Char, typename Traits, typename Alloc> +inline basic_string_view<Char> to_string_view( + const std::basic_string<Char, Traits, Alloc>& s) { + return s; +} + +template <typename Char> +constexpr basic_string_view<Char> to_string_view(basic_string_view<Char> s) { + return s; +} + +template <typename Char, + FMT_ENABLE_IF(!std::is_empty<detail::std_string_view<Char>>::value)> +inline basic_string_view<Char> to_string_view(detail::std_string_view<Char> s) { + return s; +} + +// A base class for compile-time strings. It is defined in the fmt namespace to +// make formatting functions visible via ADL, e.g. format(FMT_STRING("{}"), 42). +struct compile_string {}; + +template <typename S> +struct is_compile_string : std::is_base_of<compile_string, S> {}; + +template <typename S, FMT_ENABLE_IF(is_compile_string<S>::value)> +constexpr basic_string_view<typename S::char_type> to_string_view(const S& s) { + return s; +} + +FMT_MODULE_EXPORT_END +namespace detail { +void to_string_view(...); +using fmt::v7::to_string_view; + +// Specifies whether S is a string type convertible to fmt::basic_string_view. +// It should be a constexpr function but MSVC 2017 fails to compile it in +// enable_if and MSVC 2015 fails to compile it as an alias template. +template <typename S> +struct is_string : std::is_class<decltype(to_string_view(std::declval<S>()))> { +}; + +template <typename S, typename = void> struct char_t_impl {}; +template <typename S> struct char_t_impl<S, enable_if_t<is_string<S>::value>> { + using result = decltype(to_string_view(std::declval<S>())); + using type = typename result::value_type; +}; + +// Reports a compile-time error if S is not a valid format string. +template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)> +FMT_INLINE void check_format_string(const S&) { +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert(is_compile_string<S>::value, + "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " + "FMT_STRING."); +#endif +} +template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)> +void check_format_string(S); + +struct error_handler { + constexpr error_handler() = default; + constexpr error_handler(const error_handler&) = default; + + // This function is intentionally not constexpr to give a compile-time error. + FMT_NORETURN FMT_API void on_error(const char* message); +}; +} // namespace detail +FMT_MODULE_EXPORT_BEGIN + +/** String's character type. */ +template <typename S> using char_t = typename detail::char_t_impl<S>::type; + +/** + \rst + Parsing context consisting of a format string range being parsed and an + argument counter for automatic indexing. + + You can use one of the following type aliases for common character types: + + +-----------------------+-------------------------------------+ + | Type | Definition | + +=======================+=====================================+ + | format_parse_context | basic_format_parse_context<char> | + +-----------------------+-------------------------------------+ + | wformat_parse_context | basic_format_parse_context<wchar_t> | + +-----------------------+-------------------------------------+ + \endrst + */ +template <typename Char, typename ErrorHandler = detail::error_handler> +class basic_format_parse_context : private ErrorHandler { + private: + basic_string_view<Char> format_str_; + int next_arg_id_; + + public: + using char_type = Char; + using iterator = typename basic_string_view<Char>::iterator; + + explicit constexpr basic_format_parse_context( + basic_string_view<Char> format_str, ErrorHandler eh = {}, + int next_arg_id = 0) + : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {} + + /** + Returns an iterator to the beginning of the format string range being + parsed. + */ + constexpr iterator begin() const FMT_NOEXCEPT { return format_str_.begin(); } + + /** + Returns an iterator past the end of the format string range being parsed. + */ + constexpr iterator end() const FMT_NOEXCEPT { return format_str_.end(); } + + /** Advances the begin iterator to ``it``. */ + FMT_CONSTEXPR void advance_to(iterator it) { + format_str_.remove_prefix(detail::to_unsigned(it - begin())); + } + + /** + Reports an error if using the manual argument indexing; otherwise returns + the next argument index and switches to the automatic indexing. + */ + FMT_CONSTEXPR int next_arg_id() { + // Don't check if the argument id is valid to avoid overhead and because it + // will be checked during formatting anyway. + if (next_arg_id_ >= 0) return next_arg_id_++; + on_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + + /** + Reports an error if using the automatic argument indexing; otherwise + switches to the manual indexing. + */ + FMT_CONSTEXPR void check_arg_id(int) { + if (next_arg_id_ > 0) + on_error("cannot switch from automatic to manual argument indexing"); + else + next_arg_id_ = -1; + } + + FMT_CONSTEXPR void check_arg_id(basic_string_view<Char>) {} + + FMT_CONSTEXPR void on_error(const char* message) { + ErrorHandler::on_error(message); + } + + constexpr ErrorHandler error_handler() const { return *this; } +}; + +using format_parse_context = basic_format_parse_context<char>; +using wformat_parse_context = basic_format_parse_context<wchar_t>; + +template <typename Context> class basic_format_arg; +template <typename Context> class basic_format_args; +template <typename Context> class dynamic_format_arg_store; + +// A formatter for objects of type T. +template <typename T, typename Char = char, typename Enable = void> +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +// DEPRECATED! +// Specifies if T has an enabled formatter specialization. A type can be +// formattable even if it doesn't have a formatter e.g. via a conversion. +template <typename T, typename Context> +using has_formatter = + std::is_constructible<typename Context::template formatter_type<T>>; + +// Checks whether T is a container with contiguous storage. +template <typename T> struct is_contiguous : std::false_type {}; +template <typename Char> +struct is_contiguous<std::basic_string<Char>> : std::true_type {}; + +FMT_MODULE_EXPORT_END +namespace detail { + +// Extracts a reference to the container from back_insert_iterator. +template <typename Container> +inline Container& get_container(std::back_insert_iterator<Container> it) { + using bi_iterator = std::back_insert_iterator<Container>; + struct accessor : bi_iterator { + accessor(bi_iterator iter) : bi_iterator(iter) {} + using bi_iterator::container; + }; + return *accessor(it).container; +} + +/** + \rst + A contiguous memory buffer with an optional growing ability. It is an internal + class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`. + \endrst + */ +template <typename T> class buffer { + private: + T* ptr_; + size_t size_; + size_t capacity_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + FMT_MSC_WARNING(suppress : 26495) + buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} + + buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT + : ptr_(p), + size_(sz), + capacity_(cap) {} + + ~buffer() = default; + + /** Sets the buffer data and capacity. */ + void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + /** Increases the buffer capacity to hold at least *capacity* elements. */ + virtual void grow(size_t capacity) = 0; + + public: + using value_type = T; + using const_reference = const T&; + + buffer(const buffer&) = delete; + void operator=(const buffer&) = delete; + + T* begin() FMT_NOEXCEPT { return ptr_; } + T* end() FMT_NOEXCEPT { return ptr_ + size_; } + + const T* begin() const FMT_NOEXCEPT { return ptr_; } + const T* end() const FMT_NOEXCEPT { return ptr_ + size_; } + + /** Returns the size of this buffer. */ + size_t size() const FMT_NOEXCEPT { return size_; } + + /** Returns the capacity of this buffer. */ + size_t capacity() const FMT_NOEXCEPT { return capacity_; } + + /** Returns a pointer to the buffer data. */ + T* data() FMT_NOEXCEPT { return ptr_; } + + /** Returns a pointer to the buffer data. */ + const T* data() const FMT_NOEXCEPT { return ptr_; } + + /** Clears this buffer. */ + void clear() { size_ = 0; } + + // Tries resizing the buffer to contain *count* elements. If T is a POD type + // the new elements may not be initialized. + void try_resize(size_t count) { + try_reserve(count); + size_ = count <= capacity_ ? count : capacity_; + } + + // Tries increasing the buffer capacity to *new_capacity*. It can increase the + // capacity by a smaller amount than requested but guarantees there is space + // for at least one additional element either by increasing the capacity or by + // flushing the buffer if it is full. + void try_reserve(size_t new_capacity) { + if (new_capacity > capacity_) grow(new_capacity); + } + + void push_back(const T& value) { + try_reserve(size_ + 1); + ptr_[size_++] = value; + } + + /** Appends data to the end of the buffer. */ + template <typename U> void append(const U* begin, const U* end); + + template <typename I> T& operator[](I index) { return ptr_[index]; } + template <typename I> const T& operator[](I index) const { + return ptr_[index]; + } +}; + +struct buffer_traits { + explicit buffer_traits(size_t) {} + size_t count() const { return 0; } + size_t limit(size_t size) { return size; } +}; + +class fixed_buffer_traits { + private: + size_t count_ = 0; + size_t limit_; + + public: + explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + size_t count() const { return count_; } + size_t limit(size_t size) { + size_t n = limit_ > count_ ? limit_ - count_ : 0; + count_ += size; + return size < n ? size : n; + } +}; + +// A buffer that writes to an output iterator when flushed. +template <typename OutputIt, typename T, typename Traits = buffer_traits> +class iterator_buffer final : public Traits, public buffer<T> { + private: + OutputIt out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + protected: + void grow(size_t) final FMT_OVERRIDE { + if (this->size() == buffer_size) flush(); + } + void flush(); + + public: + explicit iterator_buffer(OutputIt out, size_t n = buffer_size) + : Traits(n), buffer<T>(data_, 0, buffer_size), out_(out) {} + ~iterator_buffer() { flush(); } + + OutputIt out() { + flush(); + return out_; + } + size_t count() const { return Traits::count() + this->size(); } +}; + +template <typename T> class iterator_buffer<T*, T> final : public buffer<T> { + protected: + void grow(size_t) final FMT_OVERRIDE {} + + public: + explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, ~size_t()) {} + + T* out() { return &*this->end(); } +}; + +// A buffer that writes to a container with the contiguous storage. +template <typename Container> +class iterator_buffer<std::back_insert_iterator<Container>, + enable_if_t<is_contiguous<Container>::value, + typename Container::value_type>> + final : public buffer<typename Container::value_type> { + private: + Container& container_; + + protected: + void grow(size_t capacity) final FMT_OVERRIDE { + container_.resize(capacity); + this->set(&container_[0], capacity); + } + + public: + explicit iterator_buffer(Container& c) + : buffer<typename Container::value_type>(c.size()), container_(c) {} + explicit iterator_buffer(std::back_insert_iterator<Container> out, size_t = 0) + : iterator_buffer(get_container(out)) {} + std::back_insert_iterator<Container> out() { + return std::back_inserter(container_); + } +}; + +// A buffer that counts the number of code units written discarding the output. +template <typename T = char> class counting_buffer final : public buffer<T> { + private: + enum { buffer_size = 256 }; + T data_[buffer_size]; + size_t count_ = 0; + + protected: + void grow(size_t) final FMT_OVERRIDE { + if (this->size() != buffer_size) return; + count_ += this->size(); + this->clear(); + } + + public: + counting_buffer() : buffer<T>(data_, 0, buffer_size) {} + + size_t count() { return count_ + this->size(); } +}; + +// An output iterator that appends to the buffer. +// It is used to reduce symbol sizes for the common case. +template <typename T> +class buffer_appender : public std::back_insert_iterator<buffer<T>> { + using base = std::back_insert_iterator<buffer<T>>; + + public: + using std::back_insert_iterator<buffer<T>>::back_insert_iterator; + buffer_appender(base it) : base(it) {} + using _Unchecked_type = buffer_appender; // Mark iterator as checked. + + buffer_appender& operator++() { + base::operator++(); + return *this; + } + + buffer_appender operator++(int) { + buffer_appender tmp = *this; + ++*this; + return tmp; + } +}; + +// Maps an output iterator into a buffer. +template <typename T, typename OutputIt> +iterator_buffer<OutputIt, T> get_buffer(OutputIt); +template <typename T> buffer<T>& get_buffer(buffer_appender<T>); + +template <typename OutputIt> OutputIt get_buffer_init(OutputIt out) { + return out; +} +template <typename T> buffer<T>& get_buffer_init(buffer_appender<T> out) { + return get_container(out); +} + +template <typename Buffer> +auto get_iterator(Buffer& buf) -> decltype(buf.out()) { + return buf.out(); +} +template <typename T> buffer_appender<T> get_iterator(buffer<T>& buf) { + return buffer_appender<T>(buf); +} + +template <typename T, typename Char = char, typename Enable = void> +struct fallback_formatter { + fallback_formatter() = delete; +}; + +// Specifies if T has an enabled fallback_formatter specialization. +template <typename T, typename Char> +using has_fallback_formatter = + std::is_constructible<fallback_formatter<T, Char>>; + +struct view {}; + +template <typename Char, typename T> struct named_arg : view { + const Char* name; + const T& value; + named_arg(const Char* n, const T& v) : name(n), value(v) {} +}; + +template <typename Char> struct named_arg_info { + const Char* name; + int id; +}; + +template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS> +struct arg_data { + // args_[0].named_args points to named_args_ to avoid bloating format_args. + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)]; + named_arg_info<Char> named_args_[NUM_NAMED_ARGS]; + + template <typename... U> + arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} + arg_data(const arg_data& other) = delete; + const T* args() const { return args_ + 1; } + named_arg_info<Char>* named_args() { return named_args_; } +}; + +template <typename T, typename Char, size_t NUM_ARGS> +struct arg_data<T, Char, NUM_ARGS, 0> { + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + T args_[NUM_ARGS != 0 ? NUM_ARGS : +1]; + + template <typename... U> + FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {} + FMT_CONSTEXPR FMT_INLINE const T* args() const { return args_; } + FMT_CONSTEXPR FMT_INLINE std::nullptr_t named_args() { return nullptr; } +}; + +template <typename Char> +inline void init_named_args(named_arg_info<Char>*, int, int) {} + +template <typename T> struct is_named_arg : std::false_type {}; + +template <typename T, typename Char> +struct is_named_arg<named_arg<Char, T>> : std::true_type {}; + +template <typename Char, typename T, typename... Tail, + FMT_ENABLE_IF(!is_named_arg<T>::value)> +void init_named_args(named_arg_info<Char>* named_args, int arg_count, + int named_arg_count, const T&, const Tail&... args) { + init_named_args(named_args, arg_count + 1, named_arg_count, args...); +} + +template <typename Char, typename T, typename... Tail, + FMT_ENABLE_IF(is_named_arg<T>::value)> +void init_named_args(named_arg_info<Char>* named_args, int arg_count, + int named_arg_count, const T& arg, const Tail&... args) { + named_args[named_arg_count++] = {arg.name, arg_count}; + init_named_args(named_args, arg_count + 1, named_arg_count, args...); +} + +template <typename... Args> +FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int, + const Args&...) {} + +template <bool B = false> constexpr size_t count() { return B ? 1 : 0; } +template <bool B1, bool B2, bool... Tail> constexpr size_t count() { + return (B1 ? 1 : 0) + count<B2, Tail...>(); +} + +template <typename... Args> constexpr size_t count_named_args() { + return count<is_named_arg<Args>::value...>(); +} + +enum class type { + none_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + int128_type, + uint128_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + float_type, + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +// Maps core type T to the corresponding type enum constant. +template <typename T, typename Char> +struct type_constant : std::integral_constant<type, type::custom_type> {}; + +#define FMT_TYPE_CONSTANT(Type, constant) \ + template <typename Char> \ + struct type_constant<Type, Char> \ + : std::integral_constant<type, type::constant> {} + +FMT_TYPE_CONSTANT(int, int_type); +FMT_TYPE_CONSTANT(unsigned, uint_type); +FMT_TYPE_CONSTANT(long long, long_long_type); +FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); +FMT_TYPE_CONSTANT(int128_t, int128_type); +FMT_TYPE_CONSTANT(uint128_t, uint128_type); +FMT_TYPE_CONSTANT(bool, bool_type); +FMT_TYPE_CONSTANT(Char, char_type); +FMT_TYPE_CONSTANT(float, float_type); +FMT_TYPE_CONSTANT(double, double_type); +FMT_TYPE_CONSTANT(long double, long_double_type); +FMT_TYPE_CONSTANT(const Char*, cstring_type); +FMT_TYPE_CONSTANT(basic_string_view<Char>, string_type); +FMT_TYPE_CONSTANT(const void*, pointer_type); + +constexpr bool is_integral_type(type t) { + return t > type::none_type && t <= type::last_integer_type; +} + +constexpr bool is_arithmetic_type(type t) { + return t > type::none_type && t <= type::last_numeric_type; +} + +template <typename Char> struct string_value { + const Char* data; + size_t size; +}; + +template <typename Char> struct named_arg_value { + const named_arg_info<Char>* data; + size_t size; +}; + +template <typename Context> struct custom_value { + using parse_context = typename Context::parse_context_type; + const void* value; + void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); +}; + +// A formatting argument value. +template <typename Context> class value { + public: + using char_type = typename Context::char_type; + + union { + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + int128_t int128_value; + uint128_t uint128_value; + bool bool_value; + char_type char_value; + float float_value; + double double_value; + long double long_double_value; + const void* pointer; + string_value<char_type> string; + custom_value<Context> custom; + named_arg_value<char_type> named_args; + }; + + constexpr FMT_INLINE value(int val = 0) : int_value(val) {} + constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} + constexpr FMT_INLINE value(long long val) : long_long_value(val) {} + constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} + FMT_INLINE value(int128_t val) : int128_value(val) {} + FMT_INLINE value(uint128_t val) : uint128_value(val) {} + FMT_INLINE value(float val) : float_value(val) {} + FMT_INLINE value(double val) : double_value(val) {} + FMT_INLINE value(long double val) : long_double_value(val) {} + constexpr FMT_INLINE value(bool val) : bool_value(val) {} + constexpr FMT_INLINE value(char_type val) : char_value(val) {} + FMT_CONSTEXPR FMT_INLINE value(const char_type* val) { + string.data = val; + if (is_constant_evaluated()) string.size = {}; + } + FMT_CONSTEXPR FMT_INLINE value(basic_string_view<char_type> val) { + string.data = val.data(); + string.size = val.size(); + } + FMT_INLINE value(const void* val) : pointer(val) {} + FMT_INLINE value(const named_arg_info<char_type>* args, size_t size) + : named_args{args, size} {} + + template <typename T> FMT_INLINE value(const T& val) { + custom.value = &val; + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter<T>` for `format` and + // `printf_formatter<T>` for `printf`. + custom.format = format_custom_arg< + T, conditional_t<has_formatter<T, Context>::value, + typename Context::template formatter_type<T>, + fallback_formatter<T, char_type>>>; + } + + private: + // Formats an argument of a custom type, such as a user-defined class. + template <typename T, typename Formatter> + static void format_custom_arg(const void* arg, + typename Context::parse_context_type& parse_ctx, + Context& ctx) { + Formatter f; + parse_ctx.advance_to(f.parse(parse_ctx)); + ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx)); + } +}; + +template <typename Context, typename T> +FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value); + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +enum { long_short = sizeof(long) == sizeof(int) }; +using long_type = conditional_t<long_short, int, long long>; +using ulong_type = conditional_t<long_short, unsigned, unsigned long long>; + +struct unformattable {}; + +// Maps formatting arguments to core types. +template <typename Context> struct arg_mapper { + using char_type = typename Context::char_type; + + FMT_CONSTEXPR FMT_INLINE int map(signed char val) { return val; } + FMT_CONSTEXPR FMT_INLINE unsigned map(unsigned char val) { return val; } + FMT_CONSTEXPR FMT_INLINE int map(short val) { return val; } + FMT_CONSTEXPR FMT_INLINE unsigned map(unsigned short val) { return val; } + FMT_CONSTEXPR FMT_INLINE int map(int val) { return val; } + FMT_CONSTEXPR FMT_INLINE unsigned map(unsigned val) { return val; } + FMT_CONSTEXPR FMT_INLINE long_type map(long val) { return val; } + FMT_CONSTEXPR FMT_INLINE ulong_type map(unsigned long val) { return val; } + FMT_CONSTEXPR FMT_INLINE long long map(long long val) { return val; } + FMT_CONSTEXPR FMT_INLINE unsigned long long map(unsigned long long val) { + return val; + } + FMT_CONSTEXPR FMT_INLINE int128_t map(int128_t val) { return val; } + FMT_CONSTEXPR FMT_INLINE uint128_t map(uint128_t val) { return val; } + FMT_CONSTEXPR FMT_INLINE bool map(bool val) { return val; } + + template <typename T, FMT_ENABLE_IF(is_char<T>::value)> + FMT_CONSTEXPR FMT_INLINE char_type map(T val) { + static_assert( + std::is_same<T, char>::value || std::is_same<T, char_type>::value, + "mixing character types is disallowed"); + return val; + } + + FMT_CONSTEXPR FMT_INLINE float map(float val) { return val; } + FMT_CONSTEXPR FMT_INLINE double map(double val) { return val; } + FMT_CONSTEXPR FMT_INLINE long double map(long double val) { return val; } + + FMT_CONSTEXPR FMT_INLINE const char_type* map(char_type* val) { return val; } + FMT_CONSTEXPR FMT_INLINE const char_type* map(const char_type* val) { + return val; + } + template <typename T, FMT_ENABLE_IF(is_string<T>::value)> + FMT_CONSTEXPR FMT_INLINE basic_string_view<char_type> map(const T& val) { + static_assert(std::is_same<char_type, char_t<T>>::value, + "mixing character types is disallowed"); + return to_string_view(val); + } + template <typename T, + FMT_ENABLE_IF( + std::is_constructible<basic_string_view<char_type>, T>::value && + !is_string<T>::value && !has_formatter<T, Context>::value && + !has_fallback_formatter<T, char_type>::value)> + FMT_CONSTEXPR FMT_INLINE basic_string_view<char_type> map(const T& val) { + return basic_string_view<char_type>(val); + } + template < + typename T, + FMT_ENABLE_IF( + std::is_constructible<std_string_view<char_type>, T>::value && + !std::is_constructible<basic_string_view<char_type>, T>::value && + !is_string<T>::value && !has_formatter<T, Context>::value && + !has_fallback_formatter<T, char_type>::value)> + FMT_CONSTEXPR FMT_INLINE basic_string_view<char_type> map(const T& val) { + return std_string_view<char_type>(val); + } + FMT_CONSTEXPR FMT_INLINE const char* map(const signed char* val) { + static_assert(std::is_same<char_type, char>::value, "invalid string type"); + return reinterpret_cast<const char*>(val); + } + FMT_CONSTEXPR FMT_INLINE const char* map(const unsigned char* val) { + static_assert(std::is_same<char_type, char>::value, "invalid string type"); + return reinterpret_cast<const char*>(val); + } + FMT_CONSTEXPR FMT_INLINE const char* map(signed char* val) { + const auto* const_val = val; + return map(const_val); + } + FMT_CONSTEXPR FMT_INLINE const char* map(unsigned char* val) { + const auto* const_val = val; + return map(const_val); + } + + FMT_CONSTEXPR FMT_INLINE const void* map(void* val) { return val; } + FMT_CONSTEXPR FMT_INLINE const void* map(const void* val) { return val; } + FMT_CONSTEXPR FMT_INLINE const void* map(std::nullptr_t val) { return val; } + + // We use SFINAE instead of a const T* parameter to avoid conflicting with + // the C array overload. + template <typename T> + FMT_CONSTEXPR auto map(T) -> enable_if_t<std::is_pointer<T>::value, int> { + // Formatting of arbitrary pointers is disallowed. If you want to output + // a pointer cast it to "void *" or "const void *". In particular, this + // forbids formatting of "[const] volatile char *" which is printed as bool + // by iostreams. + static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); + return 0; + } + + template <typename T, std::size_t N> + FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] { + return values; + } + + template <typename T, + FMT_ENABLE_IF(std::is_enum<T>::value && + !has_formatter<T, Context>::value && + !has_fallback_formatter<T, char_type>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> decltype(std::declval<arg_mapper>().map( + static_cast<typename std::underlying_type<T>::type>(val))) { + return map(static_cast<typename std::underlying_type<T>::type>(val)); + } + template <typename T, + FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value && + (has_formatter<T, Context>::value || + has_fallback_formatter<T, char_type>::value))> + FMT_CONSTEXPR FMT_INLINE const T& map(const T& val) { + return val; + } + + template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) + -> decltype(std::declval<arg_mapper>().map(named_arg.value)) { + return map(named_arg.value); + } + + unformattable map(...) { return {}; } +}; + +// A type constant after applying arg_mapper<Context>. +template <typename T, typename Context> +using mapped_type_constant = + type_constant<decltype(arg_mapper<Context>().map(std::declval<const T&>())), + typename Context::char_type>; + +enum { packed_arg_bits = 4 }; +// Maximum number of arguments with packed types. +enum { max_packed_args = 62 / packed_arg_bits }; +enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; +} // namespace detail +FMT_MODULE_EXPORT_BEGIN + +// A formatting argument. It is a trivially copyable/constructible type to +// allow storage in basic_memory_buffer. +template <typename Context> class basic_format_arg { + private: + detail::value<Context> value_; + detail::type type_; + + template <typename ContextType, typename T> + friend FMT_CONSTEXPR basic_format_arg<ContextType> detail::make_arg( + const T& value); + + template <typename Visitor, typename Ctx> + friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, + const basic_format_arg<Ctx>& arg) + -> decltype(vis(0)); + + friend class basic_format_args<Context>; + friend class dynamic_format_arg_store<Context>; + + using char_type = typename Context::char_type; + + template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS> + friend struct detail::arg_data; + + basic_format_arg(const detail::named_arg_info<char_type>* args, size_t size) + : value_(args, size) {} + + public: + class handle { + public: + explicit handle(detail::custom_value<Context> custom) : custom_(custom) {} + + void format(typename Context::parse_context_type& parse_ctx, + Context& ctx) const { + custom_.format(custom_.value, parse_ctx, ctx); + } + + private: + detail::custom_value<Context> custom_; + }; + + constexpr basic_format_arg() : type_(detail::type::none_type) {} + + constexpr explicit operator bool() const FMT_NOEXCEPT { + return type_ != detail::type::none_type; + } + + detail::type type() const { return type_; } + + bool is_integral() const { return detail::is_integral_type(type_); } + bool is_arithmetic() const { return detail::is_arithmetic_type(type_); } +}; + +/** + \rst + Visits an argument dispatching to the appropriate visit method based on + the argument type. For example, if the argument type is ``double`` then + ``vis(value)`` will be called with the value of type ``double``. + \endrst + */ +template <typename Visitor, typename Context> +FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( + Visitor&& vis, const basic_format_arg<Context>& arg) -> decltype(vis(0)) { + using char_type = typename Context::char_type; + switch (arg.type_) { + case detail::type::none_type: + break; + case detail::type::int_type: + return vis(arg.value_.int_value); + case detail::type::uint_type: + return vis(arg.value_.uint_value); + case detail::type::long_long_type: + return vis(arg.value_.long_long_value); + case detail::type::ulong_long_type: + return vis(arg.value_.ulong_long_value); +#if FMT_USE_INT128 + case detail::type::int128_type: + return vis(arg.value_.int128_value); + case detail::type::uint128_type: + return vis(arg.value_.uint128_value); +#else + case detail::type::int128_type: + case detail::type::uint128_type: + break; +#endif + case detail::type::bool_type: + return vis(arg.value_.bool_value); + case detail::type::char_type: + return vis(arg.value_.char_value); + case detail::type::float_type: + return vis(arg.value_.float_value); + case detail::type::double_type: + return vis(arg.value_.double_value); + case detail::type::long_double_type: + return vis(arg.value_.long_double_value); + case detail::type::cstring_type: + return vis(arg.value_.string.data); + case detail::type::string_type: + return vis(basic_string_view<char_type>(arg.value_.string.data, + arg.value_.string.size)); + case detail::type::pointer_type: + return vis(arg.value_.pointer); + case detail::type::custom_type: + return vis(typename basic_format_arg<Context>::handle(arg.value_.custom)); + } + return vis(monostate()); +} + +FMT_MODULE_EXPORT_END +namespace detail { + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template <typename... Ts> struct void_t_impl { using type = void; }; +template <typename... Ts> +using void_t = typename detail::void_t_impl<Ts...>::type; +#else +template <typename...> using void_t = void; +#endif + +template <typename It, typename T, typename Enable = void> +struct is_output_iterator : std::false_type {}; + +template <typename It, typename T> +struct is_output_iterator< + It, T, + void_t<typename std::iterator_traits<It>::iterator_category, + decltype(*std::declval<It>() = std::declval<T>())>> + : std::true_type {}; + +template <typename OutputIt> +struct is_back_insert_iterator : std::false_type {}; +template <typename Container> +struct is_back_insert_iterator<std::back_insert_iterator<Container>> + : std::true_type {}; + +template <typename OutputIt> +struct is_contiguous_back_insert_iterator : std::false_type {}; +template <typename Container> +struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>> + : is_contiguous<Container> {}; +template <typename Char> +struct is_contiguous_back_insert_iterator<buffer_appender<Char>> + : std::true_type {}; + +// A type-erased reference to an std::locale to avoid heavy <locale> include. +class locale_ref { + private: + const void* locale_; // A type-erased pointer to std::locale. + + public: + constexpr locale_ref() : locale_(nullptr) {} + template <typename Locale> explicit locale_ref(const Locale& loc); + + explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; } + + template <typename Locale> Locale get() const; +}; + +template <typename> constexpr unsigned long long encode_types() { return 0; } + +template <typename Context, typename Arg, typename... Args> +constexpr unsigned long long encode_types() { + return static_cast<unsigned>(mapped_type_constant<Arg, Context>::value) | + (encode_types<Context, Args...>() << packed_arg_bits); +} + +template <typename Context, typename T> +FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value) { + basic_format_arg<Context> arg; + arg.type_ = mapped_type_constant<T, Context>::value; + arg.value_ = arg_mapper<Context>().map(value); + return arg; +} + +// The type template parameter is there to avoid an ODR violation when using +// a fallback formatter in one translation unit and an implicit conversion in +// another (not recommended). +template <bool IS_PACKED, typename Context, type, typename T, + FMT_ENABLE_IF(IS_PACKED)> +FMT_CONSTEXPR FMT_INLINE value<Context> make_arg(const T& val) { + const auto& arg = arg_mapper<Context>().map(val); + static_assert( + !std::is_same<decltype(arg), const unformattable&>::value, + "Cannot format an argument. To make type T formattable provide a " + "formatter<T> specialization: https://fmt.dev/latest/api.html#udt"); + return arg; +} + +template <bool IS_PACKED, typename Context, type, typename T, + FMT_ENABLE_IF(!IS_PACKED)> +inline basic_format_arg<Context> make_arg(const T& value) { + return make_arg<Context>(value); +} +} // namespace detail +FMT_MODULE_EXPORT_BEGIN + +// Formatting context. +template <typename OutputIt, typename Char> class basic_format_context { + public: + /** The character type for the output. */ + using char_type = Char; + + private: + OutputIt out_; + basic_format_args<basic_format_context> args_; + detail::locale_ref loc_; + + public: + using iterator = OutputIt; + using format_arg = basic_format_arg<basic_format_context>; + using parse_context_type = basic_format_parse_context<Char>; + template <typename T> using formatter_type = formatter<T, char_type>; + + basic_format_context(basic_format_context&&) = default; + basic_format_context(const basic_format_context&) = delete; + void operator=(const basic_format_context&) = delete; + /** + Constructs a ``basic_format_context`` object. References to the arguments are + stored in the object so make sure they have appropriate lifetimes. + */ + constexpr basic_format_context( + OutputIt out, basic_format_args<basic_format_context> ctx_args, + detail::locale_ref loc = detail::locale_ref()) + : out_(out), args_(ctx_args), loc_(loc) {} + + constexpr format_arg arg(int id) const { return args_.get(id); } + FMT_CONSTEXPR format_arg arg(basic_string_view<char_type> name) { + return args_.get(name); + } + int arg_id(basic_string_view<char_type> name) { return args_.get_id(name); } + const basic_format_args<basic_format_context>& args() const { return args_; } + + FMT_CONSTEXPR detail::error_handler error_handler() { return {}; } + void on_error(const char* message) { error_handler().on_error(message); } + + // Returns an iterator to the beginning of the output range. + FMT_CONSTEXPR iterator out() { return out_; } + + // Advances the begin iterator to ``it``. + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator<iterator>()) out_ = it; + } + + FMT_CONSTEXPR detail::locale_ref locale() { return loc_; } +}; + +template <typename Char> +using buffer_context = + basic_format_context<detail::buffer_appender<Char>, Char>; +using format_context = buffer_context<char>; +using wformat_context = buffer_context<wchar_t>; + +FMT_MODULE_EXPORT_END + +// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. +#define FMT_BUFFER_CONTEXT(Char) \ + basic_format_context<detail::buffer_appender<Char>, Char> + +FMT_MODULE_EXPORT_BEGIN + +template <typename T, typename Char = char> +using is_formattable = bool_constant< + !std::is_same<decltype(detail::arg_mapper<buffer_context<Char>>().map( + std::declval<T>())), + detail::unformattable>::value && + !detail::has_fallback_formatter<T, Char>::value>; + +/** + \rst + An array of references to arguments. It can be implicitly converted into + `~fmt::basic_format_args` for passing into type-erased formatting functions + such as `~fmt::vformat`. + \endrst + */ +template <typename Context, typename... Args> +class format_arg_store +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args<Context> +#endif +{ + private: + static const size_t num_args = sizeof...(Args); + static const size_t num_named_args = detail::count_named_args<Args...>(); + static const bool is_packed = num_args <= detail::max_packed_args; + + using value_type = conditional_t<is_packed, detail::value<Context>, + basic_format_arg<Context>>; + + detail::arg_data<value_type, typename Context::char_type, num_args, + num_named_args> + data_; + + friend class basic_format_args<Context>; + + static constexpr unsigned long long desc = + (is_packed ? detail::encode_types<Context, Args...>() + : detail::is_unpacked_bit | num_args) | + (num_named_args != 0 + ? static_cast<unsigned long long>(detail::has_named_args_bit) + : 0); + + public: + FMT_CONSTEXPR FMT_INLINE format_arg_store(const Args&... args) + : +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 + basic_format_args<Context>(*this), +#endif + data_{detail::make_arg< + is_packed, Context, + detail::mapped_type_constant<Args, Context>::value>(args)...} { + detail::init_named_args(data_.named_args(), 0, 0, args...); + } +}; + +/** + \rst + Constructs a `~fmt::format_arg_store` object that contains references to + arguments and can be implicitly converted to `~fmt::format_args`. `Context` + can be omitted in which case it defaults to `~fmt::context`. + See `~fmt::arg` for lifetime considerations. + \endrst + */ +template <typename Context = format_context, typename... Args> +constexpr format_arg_store<Context, Args...> make_format_args( + const Args&... args) { + return {args...}; +} + +/** + \rst + Constructs a `~fmt::format_arg_store` object that contains references + to arguments and can be implicitly converted to `~fmt::format_args`. + If ``format_str`` is a compile-time string then `make_args_checked` checks + its validity at compile time. + \endrst + */ +template <typename... Args, typename S, typename Char = char_t<S>> +FMT_INLINE auto make_args_checked(const S& format_str, + const remove_reference_t<Args>&... args) + -> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> { + static_assert( + detail::count<( + std::is_base_of<detail::view, remove_reference_t<Args>>::value && + std::is_reference<Args>::value)...>() == 0, + "passing views as lvalues is disallowed"); + detail::check_format_string<Args...>(format_str); + return {args...}; +} + +/** + \rst + Returns a named argument to be used in a formatting function. + It should only be used in a call to a formatting function or + `dynamic_format_arg_store::push_back`. + + **Example**:: + + fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + \endrst + */ +template <typename Char, typename T> +inline detail::named_arg<Char, T> arg(const Char* name, const T& arg) { + static_assert(!detail::is_named_arg<T>(), "nested named arguments"); + return {name, arg}; +} + +/** + \rst + A view of a collection of formatting arguments. To avoid lifetime issues it + should only be used as a parameter type in type-erased functions such as + ``vformat``:: + + void vlog(string_view format_str, format_args args); // OK + format_args args = make_format_args(42); // Error: dangling reference + \endrst + */ +template <typename Context> class basic_format_args { + public: + using size_type = int; + using format_arg = basic_format_arg<Context>; + + private: + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; + union { + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const detail::value<Context>* values_; + const format_arg* args_; + }; + + constexpr bool is_packed() const { + return (desc_ & detail::is_unpacked_bit) == 0; + } + bool has_named_args() const { + return (desc_ & detail::has_named_args_bit) != 0; + } + + FMT_CONSTEXPR detail::type type(int index) const { + int shift = index * detail::packed_arg_bits; + unsigned int mask = (1 << detail::packed_arg_bits) - 1; + return static_cast<detail::type>((desc_ >> shift) & mask); + } + + constexpr FMT_INLINE basic_format_args(unsigned long long desc, + const detail::value<Context>* values) + : desc_(desc), values_(values) {} + constexpr basic_format_args(unsigned long long desc, const format_arg* args) + : desc_(desc), args_(args) {} + + public: + constexpr basic_format_args() : desc_(0), args_(nullptr) {} + + /** + \rst + Constructs a `basic_format_args` object from `~fmt::format_arg_store`. + \endrst + */ + template <typename... Args> + constexpr FMT_INLINE basic_format_args( + const format_arg_store<Context, Args...>& store) + : basic_format_args(format_arg_store<Context, Args...>::desc, + store.data_.args()) {} + + /** + \rst + Constructs a `basic_format_args` object from + `~fmt::dynamic_format_arg_store`. + \endrst + */ + constexpr FMT_INLINE basic_format_args( + const dynamic_format_arg_store<Context>& store) + : basic_format_args(store.get_types(), store.data()) {} + + /** + \rst + Constructs a `basic_format_args` object from a dynamic set of arguments. + \endrst + */ + constexpr basic_format_args(const format_arg* args, int count) + : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), + args) {} + + /** Returns the argument with the specified id. */ + FMT_CONSTEXPR format_arg get(int id) const { + format_arg arg; + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (id >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ == detail::type::none_type) return arg; + arg.value_ = values_[id]; + return arg; + } + + template <typename Char> format_arg get(basic_string_view<Char> name) const { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template <typename Char> int get_id(basic_string_view<Char> name) const { + if (!has_named_args()) return -1; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + + int max_size() const { + unsigned long long max_packed = detail::max_packed_args; + return static_cast<int>(is_packed() ? max_packed + : desc_ & ~detail::is_unpacked_bit); + } +}; + +/** An alias to ``basic_format_args<format_context>``. */ +// Separate types would result in shorter symbols but break ABI compatibility +// between clang and gcc on ARM (#1919). +using format_args = basic_format_args<format_context>; +using wformat_args = basic_format_args<wformat_context>; + +// We cannot use enum classes as bit fields because of a gcc bug +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414. +namespace align { +enum type { none, left, right, center, numeric }; +} +using align_t = align::type; + +FMT_MODULE_EXPORT_END +namespace detail { + +template <typename Char> constexpr bool is_ascii_letter(Char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +// Converts a character to ASCII. Returns a number > 127 on conversion failure. +template <typename Char, FMT_ENABLE_IF(std::is_integral<Char>::value)> +constexpr Char to_ascii(Char value) { + return value; +} +template <typename Char, FMT_ENABLE_IF(std::is_enum<Char>::value)> +constexpr typename std::underlying_type<Char>::type to_ascii(Char value) { + return value; +} + +template <typename Char> +FMT_CONSTEXPR int code_point_length(const Char* begin) { + if (const_check(sizeof(Char) != 1)) return 1; + constexpr char lengths[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0}; + int len = lengths[static_cast<unsigned char>(*begin) >> 3]; + + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + return len + !len; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end, + ErrorHandler&& eh) { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0; + // Convert to unsigned to prevent a warning. + const unsigned max_int = to_unsigned(INT_MAX); + unsigned big = max_int / 10; + do { + // Check for overflow. + if (value > big) { + value = max_int + 1; + break; + } + value = value * 10 + unsigned(*begin - '0'); + ++begin; + } while (begin != end && '0' <= *begin && *begin <= '9'); + if (value > max_int) eh.on_error("number is too big"); + return static_cast<int>(value); +} + +// Parses fill and alignment. +template <typename Char, typename Handler> +FMT_CONSTEXPR const Char* parse_align(const Char* begin, const Char* end, + Handler&& handler) { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (p >= end) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; + break; + default: + break; + } + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '{') + return handler.on_error("invalid fill character '{'"), begin; + handler.on_fill(basic_string_view<Char>(begin, to_unsigned(p - begin))); + begin = p + 1; + } else + ++begin; + handler.on_align(align); + break; + } else if (p == begin) { + break; + } + p = begin; + } + return begin; +} + +struct auto_id {}; + +template <typename Char> FMT_CONSTEXPR bool is_name_start(Char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +template <typename Char, typename IDHandler> +FMT_CONSTEXPR const Char* do_parse_arg_id(const Char* begin, const Char* end, + IDHandler&& handler) { + FMT_ASSERT(begin != end, ""); + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = parse_nonnegative_int(begin, end, handler); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + handler.on_error("invalid format string"); + else + handler(index); + return begin; + } + if (!is_name_start(c)) { + handler.on_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); + handler(basic_string_view<Char>(begin, to_unsigned(it - begin))); + return it; +} + +template <typename Char, typename IDHandler> +FMT_CONSTEXPR_DECL FMT_INLINE const Char* parse_arg_id(const Char* begin, + const Char* end, + IDHandler&& handler) { + Char c = *begin; + if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); + handler(); + return begin; +} + +// Adapts SpecHandler to IDHandler API for dynamic width. +template <typename SpecHandler, typename Char> struct width_adapter { + explicit FMT_CONSTEXPR width_adapter(SpecHandler& h) : handler(h) {} + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_dynamic_width(id); + } + + FMT_CONSTEXPR void on_error(const char* message) { + handler.on_error(message); + } + + SpecHandler& handler; +}; + +template <typename Char, typename Handler> +FMT_CONSTEXPR const Char* parse_width(const Char* begin, const Char* end, + Handler&& handler) { + FMT_ASSERT(begin != end, ""); + if ('0' <= *begin && *begin <= '9') { + handler.on_width(parse_nonnegative_int(begin, end, handler)); + } else if (*begin == '{') { + ++begin; + if (begin != end) + begin = parse_arg_id(begin, end, width_adapter<Handler, Char>(handler)); + if (begin == end || *begin != '}') + return handler.on_error("invalid format string"), begin; + ++begin; + } + return begin; +} + +// Adapts SpecHandler to IDHandler API for dynamic precision. +template <typename SpecHandler, typename Char> struct precision_adapter { + explicit FMT_CONSTEXPR precision_adapter(SpecHandler& h) : handler(h) {} + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_dynamic_precision(id); + } + + FMT_CONSTEXPR void on_error(const char* message) { + handler.on_error(message); + } + + SpecHandler& handler; +}; + +template <typename Char, typename Handler> +FMT_CONSTEXPR const Char* parse_precision(const Char* begin, const Char* end, + Handler&& handler) { + ++begin; + auto c = begin != end ? *begin : Char(); + if ('0' <= c && c <= '9') { + handler.on_precision(parse_nonnegative_int(begin, end, handler)); + } else if (c == '{') { + ++begin; + if (begin != end) { + begin = + parse_arg_id(begin, end, precision_adapter<Handler, Char>(handler)); + } + if (begin == end || *begin++ != '}') + return handler.on_error("invalid format string"), begin; + } else { + return handler.on_error("missing precision specifier"), begin; + } + handler.end_precision(); + return begin; +} + +// Parses standard format specifiers and sends notifications about parsed +// components to handler. +template <typename Char, typename SpecHandler> +FMT_CONSTEXPR_DECL FMT_INLINE const Char* parse_format_specs( + const Char* begin, const Char* end, SpecHandler&& handler) { + if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && + *begin != 'L') { + handler.on_type(*begin++); + return begin; + } + + if (begin == end) return begin; + + begin = parse_align(begin, end, handler); + if (begin == end) return begin; + + // Parse sign. + switch (to_ascii(*begin)) { + case '+': + handler.on_plus(); + ++begin; + break; + case '-': + handler.on_minus(); + ++begin; + break; + case ' ': + handler.on_space(); + ++begin; + break; + default: + break; + } + if (begin == end) return begin; + + if (*begin == '#') { + handler.on_hash(); + if (++begin == end) return begin; + } + + // Parse zero flag. + if (*begin == '0') { + handler.on_zero(); + if (++begin == end) return begin; + } + + begin = parse_width(begin, end, handler); + if (begin == end) return begin; + + // Parse precision. + if (*begin == '.') { + begin = parse_precision(begin, end, handler); + if (begin == end) return begin; + } + + if (*begin == 'L') { + handler.on_localized(); + ++begin; + } + + // Parse type. + if (begin != end && *begin != '}') handler.on_type(*begin++); + return begin; +} + +// Return the result via the out param to workaround gcc bug 77539. +template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*> +FMT_CONSTEXPR bool find(Ptr first, Ptr last, T value, Ptr& out) { + for (out = first; out != last; ++out) { + if (*out == value) return true; + } + return false; +} + +template <> +inline bool find<false, char>(const char* first, const char* last, char value, + const char*& out) { + out = static_cast<const char*>( + std::memchr(first, value, to_unsigned(last - first))); + return out != nullptr; +} + +template <typename Handler, typename Char> struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } + FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + arg_id = handler.on_arg_id(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + handler.on_error(message); + } +}; + +template <typename Char, typename Handler> +FMT_CONSTEXPR const Char* parse_replacement_field(const Char* begin, + const Char* end, + Handler&& handler) { + ++begin; + if (begin == end) return handler.on_error("invalid format string"), end; + if (*begin == '}') { + handler.on_replacement_field(handler.on_arg_id(), begin); + } else if (*begin == '{') { + handler.on_text(begin, begin + 1); + } else { + auto adapter = id_adapter<Handler, Char>{handler, 0}; + begin = parse_arg_id(begin, end, adapter); + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(adapter.arg_id, begin); + } else if (c == ':') { + begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + } else { + return handler.on_error("missing '}' in format string"), end; + } + } + return begin + 1; +} + +template <bool IS_CONSTEXPR, typename Char, typename Handler> +FMT_CONSTEXPR_DECL FMT_INLINE void parse_format_string( + basic_string_view<Char> format_str, Handler&& handler) { + // this is most likely a name-lookup defect in msvc's modules implementation + using detail::find; + + auto begin = format_str.data(); + auto end = begin + format_str.size(); + if (end - begin < 32) { + // Use a simple loop instead of memchr for small strings. + const Char* p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); + return; + } + struct writer { + FMT_CONSTEXPR void operator()(const Char* pbegin, const Char* pend) { + if (pbegin == pend) return; + for (;;) { + const Char* p = nullptr; + if (!find<IS_CONSTEXPR>(pbegin, pend, '}', p)) + return handler_.on_text(pbegin, pend); + ++p; + if (p == pend || *p != '}') + return handler_.on_error("unmatched '}' in format string"); + handler_.on_text(pbegin, p); + pbegin = p + 1; + } + } + Handler& handler_; + } write{handler}; + while (begin != end) { + // Doing two passes with memchr (one for '{' and another for '}') is up to + // 2.5x faster than the naive one-pass implementation on big format strings. + const Char* p = begin; + if (*begin != '{' && !find<IS_CONSTEXPR>(begin + 1, end, '{', p)) + return write(begin, end); + write(begin, p); + begin = parse_replacement_field(p, end, handler); + } +} + +template <typename T, typename ParseContext> +FMT_CONSTEXPR const typename ParseContext::char_type* parse_format_specs( + ParseContext& ctx) { + using char_type = typename ParseContext::char_type; + using context = buffer_context<char_type>; + using mapped_type = conditional_t< + mapped_type_constant<T, context>::value != type::custom_type, + decltype(arg_mapper<context>().map(std::declval<const T&>())), T>; + auto f = conditional_t<has_formatter<mapped_type, context>::value, + formatter<mapped_type, char_type>, + fallback_formatter<T, char_type>>(); + return f.parse(ctx); +} + +// A parse context with extra argument id checks. It is only used at compile +// time because adding checks at runtime would introduce substantial overhead +// and would be redundant since argument ids are checked when arguments are +// retrieved anyway. +template <typename Char, typename ErrorHandler = error_handler> +class compile_parse_context + : public basic_format_parse_context<Char, ErrorHandler> { + private: + int num_args_; + using base = basic_format_parse_context<Char, ErrorHandler>; + + public: + explicit FMT_CONSTEXPR compile_parse_context( + basic_string_view<Char> format_str, int num_args = INT_MAX, + ErrorHandler eh = {}) + : base(format_str, eh), num_args_(num_args) {} + + FMT_CONSTEXPR int next_arg_id() { + int id = base::next_arg_id(); + if (id >= num_args_) this->on_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) this->on_error("argument not found"); + } + using base::check_arg_id; +}; + +template <typename Char, typename ErrorHandler, typename... Args> +class format_string_checker { + public: + explicit FMT_CONSTEXPR format_string_checker( + basic_string_view<Char> format_str, ErrorHandler eh) + : context_(format_str, num_args, eh), + parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {} + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR int on_arg_id() { return context_.next_arg_id(); } + FMT_CONSTEXPR int on_arg_id(int id) { return context_.check_arg_id(id), id; } + FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) { + on_error("compile-time checks don't support named arguments"); + return 0; + } + + FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} + + FMT_CONSTEXPR const Char* on_format_specs(int id, const Char* begin, + const Char*) { + advance_to(context_, begin); + // id >= 0 check is a workaround for gcc 10 bug (#2065). + return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; + } + + FMT_CONSTEXPR void on_error(const char* message) { + context_.on_error(message); + } + + private: + using parse_context_type = compile_parse_context<Char, ErrorHandler>; + enum { num_args = sizeof...(Args) }; + + // Format specifier parsing function. + using parse_func = const Char* (*)(parse_context_type&); + + parse_context_type context_; + parse_func parse_funcs_[num_args > 0 ? num_args : 1]; +}; + +template <typename... Args, typename S, + enable_if_t<(is_compile_string<S>::value), int>> +void check_format_string(S format_str) { + FMT_CONSTEXPR_DECL auto s = to_string_view(format_str); + using checker = format_string_checker<typename S::char_type, error_handler, + remove_cvref_t<Args>...>; + FMT_CONSTEXPR_DECL bool invalid_format = + (parse_format_string<true>(s, checker(s, {})), true); + (void)invalid_format; +} + +// Converts string literals to basic_string_view. +template <typename Char, size_t N> +FMT_CONSTEXPR basic_string_view<Char> compile_string_to_view( + const Char (&s)[N]) { + // Remove trailing null character if needed. Won't be present if this is used + // with raw character array (i.e. not defined as a string). + return {s, + N - ((std::char_traits<Char>::to_int_type(s[N - 1]) == 0) ? 1 : 0)}; +} + +// Converts string_view to basic_string_view. +template <typename Char> +FMT_CONSTEXPR basic_string_view<Char> compile_string_to_view( + const std_string_view<Char>& s) { + return {s.data(), s.size()}; +} + +#define FMT_STRING_IMPL(s, base) \ + [] { \ + /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \ + using char_type = fmt::remove_cvref_t<decltype(s[0])>; \ + FMT_MAYBE_UNUSED FMT_CONSTEXPR \ + operator fmt::basic_string_view<char_type>() const { \ + return fmt::detail::compile_string_to_view<char_type>(s); \ + } \ + }; \ + return FMT_COMPILE_STRING(); \ + }() + +/** + \rst + Constructs a compile-time format string from a string literal *s*. + + **Example**:: + + // A compile-time error because 'd' is an invalid specifier for strings. + std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); + \endrst + */ +#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string) + +template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)> +std::basic_string<Char> vformat( + basic_string_view<Char> format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args); + +FMT_API std::string vformat(string_view format_str, format_args args); + +template <typename Char> +void vformat_to( + buffer<Char>& buf, basic_string_view<Char> format_str, + basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args, + detail::locale_ref loc = {}); + +template <typename Char, typename Args, + FMT_ENABLE_IF(!std::is_same<Char, char>::value)> +inline void vprint_mojibake(std::FILE*, basic_string_view<Char>, const Args&) {} + +FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); +#ifndef _WIN32 +inline void vprint_mojibake(std::FILE*, string_view, format_args) {} +#endif +} // namespace detail +FMT_MODULE_EXPORT_BEGIN + +/** Formats a string and writes the output to ``out``. */ +// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with +// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead. +template <typename OutputIt, typename S, typename Char = char_t<S>, + bool enable = detail::is_output_iterator<OutputIt, Char>::value> +auto vformat_to(OutputIt out, const S& format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) + -> typename std::enable_if<enable, OutputIt>::type { + decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out)); + detail::vformat_to(buf, to_string_view(format_str), args); + return detail::get_iterator(buf); +} + +/** + \rst + Formats arguments, writes the result to the output iterator ``out`` and returns + the iterator past the end of the output range. + + **Example**:: + + std::vector<char> out; + fmt::format_to(std::back_inserter(out), "{}", 42); + \endrst + */ +// We cannot use FMT_ENABLE_IF because of a bug in gcc 8.3. +template <typename OutputIt, typename S, typename... Args, + bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value> +inline auto format_to(OutputIt out, const S& format_str, Args&&... args) -> + typename std::enable_if<enable, OutputIt>::type { + const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); + return vformat_to(out, to_string_view(format_str), vargs); +} + +template <typename OutputIt> struct format_to_n_result { + /** Iterator past the end of the output range. */ + OutputIt out; + /** Total (not truncated) output size. */ + size_t size; +}; + +template <typename OutputIt, typename Char, typename... Args, + FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)> +inline format_to_n_result<OutputIt> vformat_to_n( + OutputIt out, size_t n, basic_string_view<Char> format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out, + n); + detail::vformat_to(buf, format_str, args); + return {buf.out(), buf.count()}; +} + +/** + \rst + Formats arguments, writes up to ``n`` characters of the result to the output + iterator ``out`` and returns the total output size and the iterator past the + end of the output range. + \endrst + */ +template <typename OutputIt, typename S, typename... Args, + bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value> +inline auto format_to_n(OutputIt out, size_t n, const S& format_str, + const Args&... args) -> + typename std::enable_if<enable, format_to_n_result<OutputIt>>::type { + const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); + return vformat_to_n(out, n, to_string_view(format_str), vargs); +} + +/** + Returns the number of characters in the output of + ``format(format_str, args...)``. + */ +template <typename S, typename... Args, typename Char = char_t<S>> +inline size_t formatted_size(const S& format_str, Args&&... args) { + const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); + detail::counting_buffer<> buf; + detail::vformat_to(buf, to_string_view(format_str), vargs); + return buf.count(); +} + +template <typename S, typename Char = char_t<S>> +FMT_INLINE std::basic_string<Char> vformat( + const S& format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + return detail::vformat(to_string_view(format_str), args); +} + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + #include <fmt/core.h> + std::string message = fmt::format("The answer is {}", 42); + \endrst +*/ +// Pass char_t as a default template parameter instead of using +// std::basic_string<char_t<S>> to reduce the symbol size. +template <typename S, typename... Args, typename Char = char_t<S>, + FMT_ENABLE_IF(!FMT_COMPILE_TIME_CHECKS || + !std::is_same<Char, char>::value)> +FMT_INLINE std::basic_string<Char> format(const S& format_str, Args&&... args) { + const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); + return detail::vformat(to_string_view(format_str), vargs); +} + +FMT_API void vprint(string_view, format_args); +FMT_API void vprint(std::FILE*, string_view, format_args); + +/** + \rst + Formats ``args`` according to specifications in ``format_str`` and writes the + output to the file ``f``. Strings are assumed to be Unicode-encoded unless the + ``FMT_UNICODE`` macro is set to 0. + + **Example**:: + + fmt::print(stderr, "Don't {}!", "panic"); + \endrst + */ +template <typename S, typename... Args, typename Char = char_t<S>> +inline void print(std::FILE* f, const S& format_str, Args&&... args) { + const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); + return detail::is_unicode<Char>() + ? vprint(f, to_string_view(format_str), vargs) + : detail::vprint_mojibake(f, to_string_view(format_str), vargs); +} + +/** + \rst + Formats ``args`` according to specifications in ``format_str`` and writes + the output to ``stdout``. Strings are assumed to be Unicode-encoded unless + the ``FMT_UNICODE`` macro is set to 0. + + **Example**:: + + fmt::print("Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template <typename S, typename... Args, typename Char = char_t<S>> +inline void print(const S& format_str, Args&&... args) { + const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); + return detail::is_unicode<Char>() + ? vprint(to_string_view(format_str), vargs) + : detail::vprint_mojibake(stdout, to_string_view(format_str), + vargs); +} + +FMT_MODULE_EXPORT_END +FMT_GCC_PRAGMA("GCC pop_options") +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif +#endif // FMT_CORE_H_ diff --git a/libs/fmt/include/fmt/format-inl.h b/libs/fmt/include/fmt/format-inl.h new file mode 100644 index 0000000000000000000000000000000000000000..d23af721c4c0838de3a9a1cda866bc4f428ce731 --- /dev/null +++ b/libs/fmt/include/fmt/format-inl.h @@ -0,0 +1,2723 @@ +// Formatting library for C++ - implementation +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_FORMAT_INL_H_ +#define FMT_FORMAT_INL_H_ + +#include <algorithm> +#include <cctype> +#include <climits> +#include <cmath> +#include <cstdarg> +#include <cstring> // std::memmove +#include <cwchar> +#include <exception> + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +# include <locale> +#endif + +#ifdef _WIN32 +# include <io.h> // _isatty +#endif + +#include "format.h" + +// Dummy implementations of strerror_r and strerror_s called if corresponding +// system functions are not available. +inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; } +inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; } + +FMT_BEGIN_NAMESPACE +namespace detail { + +FMT_FUNC void assert_fail(const char* file, int line, const char* message) { + // Use unchecked std::fprintf to avoid triggering another assertion when + // writing to stderr fails + std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); + // Chosen instead of std::abort to satisfy Clang in CUDA mode during device + // code pass. + std::terminate(); +} + +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) { + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +// A portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +inline int safe_strerror(int error_code, char*& buffer, size_t buffer_size) { + FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer"); + + class dispatcher { + private: + int error_code_; + char*& buffer_; + size_t buffer_size_; + + // A noop assignment operator to avoid bogus warnings. + void operator=(const dispatcher&) {} + + // Handle the result of XSI-compliant version of strerror_r. + int handle(int result) { + // glibc versions before 2.13 return result in errno. + return result == -1 ? errno : result; + } + + // Handle the result of GNU-specific version of strerror_r. + FMT_MAYBE_UNUSED + int handle(char* message) { + // If the buffer is full then the message is probably truncated. + if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) + return ERANGE; + buffer_ = message; + return 0; + } + + // Handle the case when strerror_r is not available. + FMT_MAYBE_UNUSED + int handle(detail::null<>) { + return fallback(strerror_s(buffer_, buffer_size_, error_code_)); + } + + // Fallback to strerror_s when strerror_r is not available. + FMT_MAYBE_UNUSED + int fallback(int result) { + // If the buffer is full then the message is probably truncated. + return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? ERANGE + : result; + } + +#if !FMT_MSC_VER + // Fallback to strerror if strerror_r and strerror_s are not available. + int fallback(detail::null<>) { + errno = 0; + buffer_ = strerror(error_code_); + return errno; + } +#endif + + public: + dispatcher(int err_code, char*& buf, size_t buf_size) + : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} + + int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); } + }; + return dispatcher(error_code, buffer, buffer_size).run(); +} + +FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code, + string_view message) FMT_NOEXCEPT { + // Report error code making sure that the output fits into + // inline_buffer_size to avoid dynamic memory allocation and potential + // bad_alloc. + out.try_resize(0); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + auto abs_value = static_cast<uint32_or_64_or_128_t<int>>(error_code); + if (detail::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); + auto it = buffer_appender<char>(out); + if (message.size() <= inline_buffer_size - error_code_size) + format_to(it, FMT_STRING("{}{}"), message, SEP); + format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + FMT_ASSERT(out.size() <= inline_buffer_size, ""); +} + +FMT_FUNC void report_error(format_func func, int error_code, + string_view message) FMT_NOEXCEPT { + memory_buffer full_message; + func(full_message, error_code, message); + // Don't use fwrite_fully because the latter may throw. + if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) + std::fputc('\n', stderr); +} + +// A wrapper around fwrite that throws on error. +inline void fwrite_fully(const void* ptr, size_t size, size_t count, + FILE* stream) { + size_t written = std::fwrite(ptr, size, count, stream); + if (written < count) FMT_THROW(system_error(errno, "cannot write to file")); +} + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template <typename Locale> +locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { + static_assert(std::is_same<Locale, std::locale>::value, ""); +} + +template <typename Locale> Locale locale_ref::get() const { + static_assert(std::is_same<Locale, std::locale>::value, ""); + return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale(); +} + +template <typename Char> FMT_FUNC std::string grouping_impl(locale_ref loc) { + return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>()).grouping(); +} +template <typename Char> FMT_FUNC Char thousands_sep_impl(locale_ref loc) { + return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>()) + .thousands_sep(); +} +template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) { + return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>()) + .decimal_point(); +} +#else +template <typename Char> FMT_FUNC std::string grouping_impl(locale_ref) { + return "\03"; +} +template <typename Char> FMT_FUNC Char thousands_sep_impl(locale_ref) { + return FMT_STATIC_THOUSANDS_SEPARATOR; +} +template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) { + return '.'; +} +#endif +} // namespace detail + +#if !FMT_MSC_VER +FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default; +FMT_API FMT_FUNC system_error::~system_error() FMT_NOEXCEPT = default; +#endif + +FMT_FUNC void system_error::init(int err_code, string_view format_str, + format_args args) { + error_code_ = err_code; + memory_buffer buffer; + format_system_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error& base = *this; + base = std::runtime_error(to_string(buffer)); +} + +namespace detail { + +template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) { + // fallback_uintptr is always stored in little endian. + int i = static_cast<int>(sizeof(void*)) - 1; + while (i > 0 && n.value[i] == 0) --i; + auto char_digits = std::numeric_limits<unsigned char>::digits / 4; + return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1; +} + +#if __cplusplus < 201703L +template <typename T> constexpr const char basic_data<T>::digits[][2]; +template <typename T> constexpr const char basic_data<T>::hex_digits[]; +template <typename T> constexpr const char basic_data<T>::signs[]; +template <typename T> constexpr const unsigned basic_data<T>::prefixes[]; +template <typename T> constexpr const char basic_data<T>::left_padding_shifts[]; +template <typename T> +constexpr const char basic_data<T>::right_padding_shifts[]; +#endif + +template <typename T> struct bits { + static FMT_CONSTEXPR_DECL const int value = + static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits); +}; + +class fp; +template <int SHIFT = 0> fp normalize(fp value); + +// Lower (upper) boundary is a value half way between a floating-point value +// and its predecessor (successor). Boundaries have the same exponent as the +// value so only significands are stored. +struct boundaries { + uint64_t lower; + uint64_t upper; +}; + +// A handmade floating-point number f * pow(2, e). +class fp { + private: + using significand_type = uint64_t; + + template <typename Float> + using is_supported_float = bool_constant<sizeof(Float) == sizeof(uint64_t) || + sizeof(Float) == sizeof(uint32_t)>; + + public: + significand_type f; + int e; + + // All sizes are in bits. + // Subtract 1 to account for an implicit most significant bit in the + // normalized form. + static FMT_CONSTEXPR_DECL const int double_significand_size = + std::numeric_limits<double>::digits - 1; + static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = + 1ULL << double_significand_size; + static FMT_CONSTEXPR_DECL const int significand_size = + bits<significand_type>::value; + + fp() : f(0), e(0) {} + fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} + + // Constructs fp from an IEEE754 double. It is a template to prevent compile + // errors on platforms where double is not IEEE754. + template <typename Double> explicit fp(Double d) { assign(d); } + + // Assigns d to this and return true iff predecessor is closer than successor. + template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)> + bool assign(Float d) { + // Assume float is in the format [sign][exponent][significand]. + using limits = std::numeric_limits<Float>; + const int float_significand_size = limits::digits - 1; + const int exponent_size = + bits<Float>::value - float_significand_size - 1; // -1 for sign + const uint64_t float_implicit_bit = 1ULL << float_significand_size; + const uint64_t significand_mask = float_implicit_bit - 1; + const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask; + const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; + constexpr bool is_double = sizeof(Float) == sizeof(uint64_t); + auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(d); + f = u & significand_mask; + int biased_e = + static_cast<int>((u & exponent_mask) >> float_significand_size); + // Predecessor is closer if d is a normalized power of 2 (f == 0) other than + // the smallest normalized number (biased_e > 1). + bool is_predecessor_closer = f == 0 && biased_e > 1; + if (biased_e != 0) + f += float_implicit_bit; + else + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + e = biased_e - exponent_bias - float_significand_size; + return is_predecessor_closer; + } + + template <typename Float, FMT_ENABLE_IF(!is_supported_float<Float>::value)> + bool assign(Float) { + *this = fp(); + return false; + } +}; + +// Normalizes the value converted from double and multiplied by (1 << SHIFT). +template <int SHIFT> fp normalize(fp value) { + // Handle subnormals. + const auto shifted_implicit_bit = fp::implicit_bit << SHIFT; + while ((value.f & shifted_implicit_bit) == 0) { + value.f <<= 1; + --value.e; + } + // Subtract 1 to account for hidden bit. + const auto offset = + fp::significand_size - fp::double_significand_size - SHIFT - 1; + value.f <<= offset; + value.e -= offset; + return value; +} + +inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; } + +// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. +inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { +#if FMT_USE_INT128 + auto product = static_cast<__uint128_t>(lhs) * rhs; + auto f = static_cast<uint64_t>(product >> 64); + return (static_cast<uint64_t>(product) & (1ULL << 63)) != 0 ? f + 1 : f; +#else + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = lhs >> 32, b = lhs & mask; + uint64_t c = rhs >> 32, d = rhs & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); +#endif +} + +inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; } + +// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its +// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. +inline fp get_cached_power(int min_exponent, int& pow10_exponent) { + // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. + // These are generated by support/compute-powers.py. + static constexpr const uint64_t pow10_significands[] = { + 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, + 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, + 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, + 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, + 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, + 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, + 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, + 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, + 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, + 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, + 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, + 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, + 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, + 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, + 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, + 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, + 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, + 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, + 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, + 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, + 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, + 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, + 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, + 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, + 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, + 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, + 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, + 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, + 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, + }; + + // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding + // to significands above. + static constexpr const int16_t pow10_exponents[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, + -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, + -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, + -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, + -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, + 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, + 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, + 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; + + const int shift = 32; + const auto significand = static_cast<int64_t>(data::log10_2_significand); + int index = static_cast<int>( + ((min_exponent + fp::significand_size - 1) * (significand >> shift) + + ((int64_t(1) << shift) - 1)) // ceil + >> 32 // arithmetic shift + ); + // Decimal exponent of the first (smallest) cached power of 10. + const int first_dec_exp = -348; + // Difference between 2 consecutive decimal exponents in cached powers of 10. + const int dec_exp_step = 8; + index = (index - first_dec_exp - 1) / dec_exp_step + 1; + pow10_exponent = first_dec_exp + index * dec_exp_step; + return {pow10_significands[index], pow10_exponents[index]}; +} + +// A simple accumulator to hold the sums of terms in bigint::square if uint128_t +// is not available. +struct accumulator { + uint64_t lower; + uint64_t upper; + + accumulator() : lower(0), upper(0) {} + explicit operator uint32_t() const { return static_cast<uint32_t>(lower); } + + void operator+=(uint64_t n) { + lower += n; + if (lower < n) ++upper; + } + void operator>>=(int shift) { + FMT_ASSERT(shift == 32, ""); + (void)shift; + lower = (upper << 32) | (lower >> 32); + upper >>= 32; + } +}; + +class bigint { + private: + // A bigint is stored as an array of bigits (big digits), with bigit at index + // 0 being the least significant one. + using bigit = uint32_t; + using double_bigit = uint64_t; + enum { bigits_capacity = 32 }; + basic_memory_buffer<bigit, bigits_capacity> bigits_; + int exp_; + + bigit operator[](int index) const { return bigits_[to_unsigned(index)]; } + bigit& operator[](int index) { return bigits_[to_unsigned(index)]; } + + static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value; + + friend struct formatter<bigint>; + + void subtract_bigits(int index, bigit other, bigit& borrow) { + auto result = static_cast<double_bigit>((*this)[index]) - other - borrow; + (*this)[index] = static_cast<bigit>(result); + borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1)); + } + + void remove_leading_zeros() { + int num_bigits = static_cast<int>(bigits_.size()) - 1; + while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; + bigits_.resize(to_unsigned(num_bigits + 1)); + } + + // Computes *this -= other assuming aligned bigints and *this >= other. + void subtract_aligned(const bigint& other) { + FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); + FMT_ASSERT(compare(*this, other) >= 0, ""); + bigit borrow = 0; + int i = other.exp_ - exp_; + for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) + subtract_bigits(i, other.bigits_[j], borrow); + while (borrow > 0) subtract_bigits(i, 0, borrow); + remove_leading_zeros(); + } + + void multiply(uint32_t value) { + const double_bigit wide_value = value; + bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + double_bigit result = bigits_[i] * wide_value + carry; + bigits_[i] = static_cast<bigit>(result); + carry = static_cast<bigit>(result >> bigit_bits); + } + if (carry != 0) bigits_.push_back(carry); + } + + void multiply(uint64_t value) { + const bigit mask = ~bigit(0); + const double_bigit lower = value & mask; + const double_bigit upper = value >> bigit_bits; + double_bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + double_bigit result = bigits_[i] * lower + (carry & mask); + carry = + bigits_[i] * upper + (result >> bigit_bits) + (carry >> bigit_bits); + bigits_[i] = static_cast<bigit>(result); + } + while (carry != 0) { + bigits_.push_back(carry & mask); + carry >>= bigit_bits; + } + } + + public: + bigint() : exp_(0) {} + explicit bigint(uint64_t n) { assign(n); } + ~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); } + + bigint(const bigint&) = delete; + void operator=(const bigint&) = delete; + + void assign(const bigint& other) { + auto size = other.bigits_.size(); + bigits_.resize(size); + auto data = other.bigits_.data(); + std::copy(data, data + size, make_checked(bigits_.data(), size)); + exp_ = other.exp_; + } + + void assign(uint64_t n) { + size_t num_bigits = 0; + do { + bigits_[num_bigits++] = n & ~bigit(0); + n >>= bigit_bits; + } while (n != 0); + bigits_.resize(num_bigits); + exp_ = 0; + } + + int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; } + + FMT_NOINLINE bigint& operator<<=(int shift) { + FMT_ASSERT(shift >= 0, ""); + exp_ += shift / bigit_bits; + shift %= bigit_bits; + if (shift == 0) return *this; + bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + bigit c = bigits_[i] >> (bigit_bits - shift); + bigits_[i] = (bigits_[i] << shift) + carry; + carry = c; + } + if (carry != 0) bigits_.push_back(carry); + return *this; + } + + template <typename Int> bigint& operator*=(Int value) { + FMT_ASSERT(value > 0, ""); + multiply(uint32_or_64_or_128_t<Int>(value)); + return *this; + } + + friend int compare(const bigint& lhs, const bigint& rhs) { + int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); + if (num_lhs_bigits != num_rhs_bigits) + return num_lhs_bigits > num_rhs_bigits ? 1 : -1; + int i = static_cast<int>(lhs.bigits_.size()) - 1; + int j = static_cast<int>(rhs.bigits_.size()) - 1; + int end = i - j; + if (end < 0) end = 0; + for (; i >= end; --i, --j) { + bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; + if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; + } + if (i != j) return i > j ? 1 : -1; + return 0; + } + + // Returns compare(lhs1 + lhs2, rhs). + friend int add_compare(const bigint& lhs1, const bigint& lhs2, + const bigint& rhs) { + int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits()); + int num_rhs_bigits = rhs.num_bigits(); + if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; + if (max_lhs_bigits > num_rhs_bigits) return 1; + auto get_bigit = [](const bigint& n, int i) -> bigit { + return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; + }; + double_bigit borrow = 0; + int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_); + for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { + double_bigit sum = + static_cast<double_bigit>(get_bigit(lhs1, i)) + get_bigit(lhs2, i); + bigit rhs_bigit = get_bigit(rhs, i); + if (sum > rhs_bigit + borrow) return 1; + borrow = rhs_bigit + borrow - sum; + if (borrow > 1) return -1; + borrow <<= bigit_bits; + } + return borrow != 0 ? -1 : 0; + } + + // Assigns pow(10, exp) to this bigint. + void assign_pow10(int exp) { + FMT_ASSERT(exp >= 0, ""); + if (exp == 0) return assign(1); + // Find the top bit. + int bitmask = 1; + while (exp >= bitmask) bitmask <<= 1; + bitmask >>= 1; + // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by + // repeated squaring and multiplication. + assign(5); + bitmask >>= 1; + while (bitmask != 0) { + square(); + if ((exp & bitmask) != 0) *this *= 5; + bitmask >>= 1; + } + *this <<= exp; // Multiply by pow(2, exp) by shifting. + } + + void square() { + basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_)); + int num_bigits = static_cast<int>(bigits_.size()); + int num_result_bigits = 2 * num_bigits; + bigits_.resize(to_unsigned(num_result_bigits)); + using accumulator_t = conditional_t<FMT_USE_INT128, uint128_t, accumulator>; + auto sum = accumulator_t(); + for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { + // Compute bigit at position bigit_index of the result by adding + // cross-product terms n[i] * n[j] such that i + j == bigit_index. + for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { + // Most terms are multiplied twice which can be optimized in the future. + sum += static_cast<double_bigit>(n[i]) * n[j]; + } + (*this)[bigit_index] = static_cast<bigit>(sum); + sum >>= bits<bigit>::value; // Compute the carry. + } + // Do the same for the top half. + for (int bigit_index = num_bigits; bigit_index < num_result_bigits; + ++bigit_index) { + for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) + sum += static_cast<double_bigit>(n[i++]) * n[j--]; + (*this)[bigit_index] = static_cast<bigit>(sum); + sum >>= bits<bigit>::value; + } + --num_result_bigits; + remove_leading_zeros(); + exp_ *= 2; + } + + // If this bigint has a bigger exponent than other, adds trailing zero to make + // exponents equal. This simplifies some operations such as subtraction. + void align(const bigint& other) { + int exp_difference = exp_ - other.exp_; + if (exp_difference <= 0) return; + int num_bigits = static_cast<int>(bigits_.size()); + bigits_.resize(to_unsigned(num_bigits + exp_difference)); + for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) + bigits_[j] = bigits_[i]; + std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); + exp_ -= exp_difference; + } + + // Divides this bignum by divisor, assigning the remainder to this and + // returning the quotient. + int divmod_assign(const bigint& divisor) { + FMT_ASSERT(this != &divisor, ""); + if (compare(*this, divisor) < 0) return 0; + FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); + align(divisor); + int quotient = 0; + do { + subtract_aligned(divisor); + ++quotient; + } while (compare(*this, divisor) >= 0); + return quotient; + } +}; + +enum class round_direction { unknown, up, down }; + +// Given the divisor (normally a power of 10), the remainder = v % divisor for +// some number v and the error, returns whether v should be rounded up, down, or +// whether the rounding direction can't be determined due to error. +// error should be less than divisor / 2. +inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder, + uint64_t error) { + FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. + FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. + FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow. + // Round down if (remainder + error) * 2 <= divisor. + if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2) + return round_direction::down; + // Round up if (remainder - error) * 2 >= divisor. + if (remainder >= error && + remainder - error >= divisor - (remainder - error)) { + return round_direction::up; + } + return round_direction::unknown; +} + +namespace digits { +enum result { + more, // Generate more digits. + done, // Done generating digits. + error // Digit generation cancelled due to an error. +}; +} + +inline uint64_t power_of_10_64(int exp) { + static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1), + FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; + return data[exp]; +} + +// Generates output using the Grisu digit-gen algorithm. +// error: the size of the region (lower, upper) outside of which numbers +// definitely do not round to value (Delta in Grisu3). +template <typename Handler> +FMT_ALWAYS_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, + int& exp, Handler& handler) { + const fp one(1ULL << -value.e, value.e); + // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be + // zero because it contains a product of two 64-bit numbers with MSB set (due + // to normalization) - 1, shifted right by at most 60 bits. + auto integral = static_cast<uint32_t>(value.f >> -one.e); + FMT_ASSERT(integral != 0, ""); + FMT_ASSERT(integral == value.f >> -one.e, ""); + // The fractional part of scaled value (p2 in Grisu) c = value % one. + uint64_t fractional = value.f & (one.f - 1); + exp = count_digits(integral); // kappa in Grisu. + // Divide by 10 to prevent overflow. + auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e, + value.f / 10, error * 10, exp); + if (result != digits::more) return result; + // Generate digits for the integral part. This can produce up to 10 digits. + do { + uint32_t digit = 0; + auto divmod_integral = [&](uint32_t divisor) { + digit = integral / divisor; + integral %= divisor; + }; + // This optimization by Milo Yip reduces the number of integer divisions by + // one per iteration. + switch (exp) { + case 10: + divmod_integral(1000000000); + break; + case 9: + divmod_integral(100000000); + break; + case 8: + divmod_integral(10000000); + break; + case 7: + divmod_integral(1000000); + break; + case 6: + divmod_integral(100000); + break; + case 5: + divmod_integral(10000); + break; + case 4: + divmod_integral(1000); + break; + case 3: + divmod_integral(100); + break; + case 2: + divmod_integral(10); + break; + case 1: + digit = integral; + integral = 0; + break; + default: + FMT_ASSERT(false, "invalid number of digits"); + } + --exp; + auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional; + result = handler.on_digit(static_cast<char>('0' + digit), + power_of_10_64(exp) << -one.e, remainder, error, + exp, true); + if (result != digits::more) return result; + } while (exp > 0); + // Generate digits for the fractional part. + for (;;) { + fractional *= 10; + error *= 10; + char digit = static_cast<char>('0' + (fractional >> -one.e)); + fractional &= one.f - 1; + --exp; + result = handler.on_digit(digit, one.f, fractional, error, exp, false); + if (result != digits::more) return result; + } +} + +// The fixed precision digit handler. +struct fixed_handler { + char* buf; + int size; + int precision; + int exp10; + bool fixed; + + digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error, + int& exp) { + // Non-fixed formats require at least one digit and no precision adjustment. + if (!fixed) return digits::more; + // Adjust fixed precision by exponent because it is relative to decimal + // point. + precision += exp + exp10; + // Check if precision is satisfied just by leading zeros, e.g. + // format("{:.2f}", 0.001) gives "0.00" without generating any digits. + if (precision > 0) return digits::more; + if (precision < 0) return digits::done; + auto dir = get_round_direction(divisor, remainder, error); + if (dir == round_direction::unknown) return digits::error; + buf[size++] = dir == round_direction::up ? '1' : '0'; + return digits::done; + } + + digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder, + uint64_t error, int, bool integral) { + FMT_ASSERT(remainder < divisor, ""); + buf[size++] = digit; + if (!integral && error >= remainder) return digits::error; + if (size < precision) return digits::more; + if (!integral) { + // Check if error * 2 < divisor with overflow prevention. + // The check is not needed for the integral part because error = 1 + // and divisor > (1 << 32) there. + if (error >= divisor || error >= divisor - error) return digits::error; + } else { + FMT_ASSERT(error == 1 && divisor > 2, ""); + } + auto dir = get_round_direction(divisor, remainder, error); + if (dir != round_direction::up) + return dir == round_direction::down ? digits::done : digits::error; + ++buf[size - 1]; + for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] > '9') { + buf[0] = '1'; + if (fixed) + buf[size++] = '0'; + else + ++exp10; + } + return digits::done; + } +}; + +// A 128-bit integer type used internally, +struct uint128_wrapper { + uint128_wrapper() = default; + +#if FMT_USE_INT128 + uint128_t internal_; + + constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT + : internal_{static_cast<uint128_t>(low) | + (static_cast<uint128_t>(high) << 64)} {} + + constexpr uint128_wrapper(uint128_t u) : internal_{u} {} + + constexpr uint64_t high() const FMT_NOEXCEPT { + return uint64_t(internal_ >> 64); + } + constexpr uint64_t low() const FMT_NOEXCEPT { return uint64_t(internal_); } + + uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT { + internal_ += n; + return *this; + } +#else + uint64_t high_; + uint64_t low_; + + constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT + : high_{high}, + low_{low} {} + + constexpr uint64_t high() const FMT_NOEXCEPT { return high_; } + constexpr uint64_t low() const FMT_NOEXCEPT { return low_; } + + uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT { +# if defined(_MSC_VER) && defined(_M_X64) + unsigned char carry = _addcarry_u64(0, low_, n, &low_); + _addcarry_u64(carry, high_, 0, &high_); + return *this; +# else + uint64_t sum = low_ + n; + high_ += (sum < low_ ? 1 : 0); + low_ = sum; + return *this; +# endif + } +#endif +}; + +// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. +namespace dragonbox { +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +inline uint128_wrapper umul128(uint64_t x, uint64_t y) FMT_NOEXCEPT { +#if FMT_USE_INT128 + return static_cast<uint128_t>(x) * static_cast<uint128_t>(y); +#elif defined(_MSC_VER) && defined(_M_X64) + uint128_wrapper result; + result.low_ = _umul128(x, y, &result.high_); + return result; +#else + const uint64_t mask = (uint64_t(1) << 32) - uint64_t(1); + + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} + +// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. +inline uint64_t umul128_upper64(uint64_t x, uint64_t y) FMT_NOEXCEPT { +#if FMT_USE_INT128 + auto p = static_cast<uint128_t>(x) * static_cast<uint128_t>(y); + return static_cast<uint64_t>(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// Computes upper 64 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline uint64_t umul192_upper64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT { + uint128_wrapper g0 = umul128(x, y.high()); + g0 += umul128_upper64(x, y.low()); + return g0.high(); +} + +// Computes upper 32 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline uint32_t umul96_upper32(uint32_t x, uint64_t y) FMT_NOEXCEPT { + return static_cast<uint32_t>(umul128_upper64(x, y)); +} + +// Computes middle 64 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline uint64_t umul192_middle64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT { + uint64_t g01 = x * y.high(); + uint64_t g10 = umul128_upper64(x, y.low()); + return g01 + g10; +} + +// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT { + return x * y; +} + +// Computes floor(log10(pow(2, e))) for e in [-1700, 1700] using the method from +// https://fmt.dev/papers/Grisu-Exact.pdf#page=5, section 3.4. +inline int floor_log10_pow2(int e) FMT_NOEXCEPT { + FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); + const int shift = 22; + return (e * static_cast<int>(data::log10_2_significand >> (64 - shift))) >> + shift; +} + +// Various fast log computations. +inline int floor_log2_pow10(int e) FMT_NOEXCEPT { + FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); + const uint64_t log2_10_integer_part = 3; + const uint64_t log2_10_fractional_digits = 0x5269e12f346e2bf9; + const int shift_amount = 19; + return (e * static_cast<int>( + (log2_10_integer_part << shift_amount) | + (log2_10_fractional_digits >> (64 - shift_amount)))) >> + shift_amount; +} +inline int floor_log10_pow2_minus_log10_4_over_3(int e) FMT_NOEXCEPT { + FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); + const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375; + const int shift_amount = 22; + return (e * static_cast<int>(data::log10_2_significand >> + (64 - shift_amount)) - + static_cast<int>(log10_4_over_3_fractional_digits >> + (64 - shift_amount))) >> + shift_amount; +} + +// Returns true iff x is divisible by pow(2, exp). +inline bool divisible_by_power_of_2(uint32_t x, int exp) FMT_NOEXCEPT { + FMT_ASSERT(exp >= 1, ""); + FMT_ASSERT(x != 0, ""); +#ifdef FMT_BUILTIN_CTZ + return FMT_BUILTIN_CTZ(x) >= exp; +#else + return exp < num_bits<uint32_t>() && x == ((x >> exp) << exp); +#endif +} +inline bool divisible_by_power_of_2(uint64_t x, int exp) FMT_NOEXCEPT { + FMT_ASSERT(exp >= 1, ""); + FMT_ASSERT(x != 0, ""); +#ifdef FMT_BUILTIN_CTZLL + return FMT_BUILTIN_CTZLL(x) >= exp; +#else + return exp < num_bits<uint64_t>() && x == ((x >> exp) << exp); +#endif +} + +// Table entry type for divisibility test. +template <typename T> struct divtest_table_entry { + T mod_inv; + T max_quotient; +}; + +// Returns true iff x is divisible by pow(5, exp). +inline bool divisible_by_power_of_5(uint32_t x, int exp) FMT_NOEXCEPT { + FMT_ASSERT(exp <= 10, "too large exponent"); + static constexpr const divtest_table_entry<uint32_t> divtest_table[] = { + {0x00000001, 0xffffffff}, {0xcccccccd, 0x33333333}, + {0xc28f5c29, 0x0a3d70a3}, {0x26e978d5, 0x020c49ba}, + {0x3afb7e91, 0x0068db8b}, {0x0bcbe61d, 0x0014f8b5}, + {0x68c26139, 0x000431bd}, {0xae8d46a5, 0x0000d6bf}, + {0x22e90e21, 0x00002af3}, {0x3a2e9c6d, 0x00000897}, + {0x3ed61f49, 0x000001b7}}; + return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient; +} +inline bool divisible_by_power_of_5(uint64_t x, int exp) FMT_NOEXCEPT { + FMT_ASSERT(exp <= 23, "too large exponent"); + static constexpr const divtest_table_entry<uint64_t> divtest_table[] = { + {0x0000000000000001, 0xffffffffffffffff}, + {0xcccccccccccccccd, 0x3333333333333333}, + {0x8f5c28f5c28f5c29, 0x0a3d70a3d70a3d70}, + {0x1cac083126e978d5, 0x020c49ba5e353f7c}, + {0xd288ce703afb7e91, 0x0068db8bac710cb2}, + {0x5d4e8fb00bcbe61d, 0x0014f8b588e368f0}, + {0x790fb65668c26139, 0x000431bde82d7b63}, + {0xe5032477ae8d46a5, 0x0000d6bf94d5e57a}, + {0xc767074b22e90e21, 0x00002af31dc46118}, + {0x8e47ce423a2e9c6d, 0x0000089705f4136b}, + {0x4fa7f60d3ed61f49, 0x000001b7cdfd9d7b}, + {0x0fee64690c913975, 0x00000057f5ff85e5}, + {0x3662e0e1cf503eb1, 0x000000119799812d}, + {0xa47a2cf9f6433fbd, 0x0000000384b84d09}, + {0x54186f653140a659, 0x00000000b424dc35}, + {0x7738164770402145, 0x0000000024075f3d}, + {0xe4a4d1417cd9a041, 0x000000000734aca5}, + {0xc75429d9e5c5200d, 0x000000000170ef54}, + {0xc1773b91fac10669, 0x000000000049c977}, + {0x26b172506559ce15, 0x00000000000ec1e4}, + {0xd489e3a9addec2d1, 0x000000000002f394}, + {0x90e860bb892c8d5d, 0x000000000000971d}, + {0x502e79bf1b6f4f79, 0x0000000000001e39}, + {0xdcd618596be30fe5, 0x000000000000060b}}; + return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient; +} + +// Replaces n by floor(n / pow(5, N)) returning true if and only if n is +// divisible by pow(5, N). +// Precondition: n <= 2 * pow(5, N + 1). +template <int N> +bool check_divisibility_and_divide_by_pow5(uint32_t& n) FMT_NOEXCEPT { + static constexpr struct { + uint32_t magic_number; + int bits_for_comparison; + uint32_t threshold; + int shift_amount; + } infos[] = {{0xcccd, 16, 0x3333, 18}, {0xa429, 8, 0x0a, 20}}; + constexpr auto info = infos[N - 1]; + n *= info.magic_number; + const uint32_t comparison_mask = (1u << info.bits_for_comparison) - 1; + bool result = (n & comparison_mask) <= info.threshold; + n >>= info.shift_amount; + return result; +} + +// Computes floor(n / pow(10, N)) for small n and N. +// Precondition: n <= pow(10, N + 1). +template <int N> uint32_t small_division_by_pow10(uint32_t n) FMT_NOEXCEPT { + static constexpr struct { + uint32_t magic_number; + int shift_amount; + uint32_t divisor_times_10; + } infos[] = {{0xcccd, 19, 100}, {0xa3d8, 22, 1000}}; + constexpr auto info = infos[N - 1]; + FMT_ASSERT(n <= info.divisor_times_10, "n is too large"); + return n * info.magic_number >> info.shift_amount; +} + +// Computes floor(n / 10^(kappa + 1)) (float) +inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) FMT_NOEXCEPT { + return n / float_info<float>::big_divisor; +} +// Computes floor(n / 10^(kappa + 1)) (double) +inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) FMT_NOEXCEPT { + return umul128_upper64(n, 0x83126e978d4fdf3c) >> 9; +} + +// Various subroutines using pow10 cache +template <class T> struct cache_accessor; + +template <> struct cache_accessor<float> { + using carrier_uint = float_info<float>::carrier_uint; + using cache_entry_type = uint64_t; + + static uint64_t get_cached_power(int k) FMT_NOEXCEPT { + FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k, + "k is out of range"); + constexpr const uint64_t pow10_significands[] = { + 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, + 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, + 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, + 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb, + 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a, + 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810, + 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, + 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd, + 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424, + 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b, + 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, + 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, + 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, + 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, + 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, + 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, + 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, + 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, + 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, + 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984, + 0xa18f07d736b90be5, 0xc9f2c9cd04674ede, 0xfc6f7c4045812296, + 0x9dc5ada82b70b59d, 0xc5371912364ce305, 0xf684df56c3e01bc6, + 0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20, + 0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd, + 0x92efd1b8d0cf37be, 0xb7abc627050305ad, 0xe596b7b0c643c719, + 0x8f7e32ce7bea5c6f, 0xb35dbf821ae4f38b, 0xe0352f62a19e306e}; + return pow10_significands[k - float_info<float>::min_k]; + } + + static carrier_uint compute_mul(carrier_uint u, + const cache_entry_type& cache) FMT_NOEXCEPT { + return umul96_upper32(u, cache); + } + + static uint32_t compute_delta(const cache_entry_type& cache, + int beta_minus_1) FMT_NOEXCEPT { + return static_cast<uint32_t>(cache >> (64 - 1 - beta_minus_1)); + } + + static bool compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta_minus_1) FMT_NOEXCEPT { + FMT_ASSERT(beta_minus_1 >= 1, ""); + FMT_ASSERT(beta_minus_1 < 64, ""); + + return ((umul96_lower64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0; + } + + static carrier_uint compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return static_cast<carrier_uint>( + (cache - (cache >> (float_info<float>::significand_bits + 2))) >> + (64 - float_info<float>::significand_bits - 1 - beta_minus_1)); + } + + static carrier_uint compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return static_cast<carrier_uint>( + (cache + (cache >> (float_info<float>::significand_bits + 1))) >> + (64 - float_info<float>::significand_bits - 1 - beta_minus_1)); + } + + static carrier_uint compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return (static_cast<carrier_uint>( + cache >> + (64 - float_info<float>::significand_bits - 2 - beta_minus_1)) + + 1) / + 2; + } +}; + +template <> struct cache_accessor<double> { + using carrier_uint = float_info<double>::carrier_uint; + using cache_entry_type = uint128_wrapper; + + static uint128_wrapper get_cached_power(int k) FMT_NOEXCEPT { + FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k, + "k is out of range"); + + static constexpr const uint128_wrapper pow10_significands[] = { +#if FMT_USE_FULL_CACHE_DRAGONBOX + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0x9faacf3df73609b1, 0x77b191618c54e9ad}, + {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, + {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, + {0x9becce62836ac577, 0x4ee367f9430aec33}, + {0xc2e801fb244576d5, 0x229c41f793cda740}, + {0xf3a20279ed56d48a, 0x6b43527578c11110}, + {0x9845418c345644d6, 0x830a13896b78aaaa}, + {0xbe5691ef416bd60c, 0x23cc986bc656d554}, + {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, + {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, + {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, + {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, + {0x91376c36d99995be, 0x23100809b9c21fa2}, + {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, + {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, + {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, + {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, + {0xdd95317f31c7fa1d, 0x40405643d711d584}, + {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, + {0xad1c8eab5ee43b66, 0xda3243650005eed0}, + {0xd863b256369d4a40, 0x90bed43e40076a83}, + {0x873e4f75e2224e68, 0x5a7744a6e804a292}, + {0xa90de3535aaae202, 0x711515d0a205cb37}, + {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, + {0x8412d9991ed58091, 0xe858790afe9486c3}, + {0xa5178fff668ae0b6, 0x626e974dbe39a873}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, + {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, + {0xc987434744ac874e, 0xa327ffb266b56221}, + {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, + {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, + {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, + {0xf6019da07f549b2b, 0x7e2a53a146606a49}, + {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, + {0xc0314325637a1939, 0xfa911155fefb5309}, + {0xf03d93eebc589f88, 0x793555ab7eba27cb}, + {0x96267c7535b763b5, 0x4bc1558b2f3458df}, + {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, + {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, + {0x92a1958a7675175f, 0x0bfacd89ec191eca}, + {0xb749faed14125d36, 0xcef980ec671f667c}, + {0xe51c79a85916f484, 0x82b7e12780e7401b}, + {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, + {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, + {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, + {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, + {0xaecc49914078536d, 0x58fae9f773886e19}, + {0xda7f5bf590966848, 0xaf39a475506a899f}, + {0x888f99797a5e012d, 0x6d8406c952429604}, + {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, + {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, + {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0xd0601d8efc57b08b, 0xf13b94daf124da27}, + {0x823c12795db6ce57, 0x76c53d08d6b70859}, + {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, + {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, + {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, + {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, + {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, + {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, + {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, + {0xc21094364dfb5636, 0x985915fc12f542e5}, + {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, + {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, + {0xbd8430bd08277231, 0x50c6ff782a838354}, + {0xece53cec4a314ebd, 0xa4f8bf5635246429}, + {0x940f4613ae5ed136, 0x871b7795e136be9a}, + {0xb913179899f68584, 0x28e2557b59846e40}, + {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, + {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, + {0xb4bca50b065abe63, 0x0fed077a756b53aa}, + {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, + {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, + {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, + {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, + {0x89e42caaf9491b60, 0xf41686c49db57245}, + {0xac5d37d5b79b6239, 0x311c2875c522ced6}, + {0xd77485cb25823ac7, 0x7d633293366b828c}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, + {0xd267caa862a12d66, 0xd072df63c324fd7c}, + {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, + {0xa46116538d0deb78, 0x52d9be85f074e609}, + {0xcd795be870516656, 0x67902e276c921f8c}, + {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, + {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, + {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, + {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, + {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, + {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, + {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, + {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, + {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, + {0xef340a98172aace4, 0x86fb897116c87c35}, + {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, + {0xbae0a846d2195712, 0x8974836059cca10a}, + {0xe998d258869facd7, 0x2bd1a438703fc94c}, + {0x91ff83775423cc06, 0x7b6306a34627ddd0}, + {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, + {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, + {0x8e938662882af53e, 0x547eb47b7282ee9d}, + {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, + {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, + {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, + {0xae0b158b4738705e, 0x9624ab50b148d446}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, + {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, + {0xd47487cc8470652b, 0x7647c32000696720}, + {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, + {0xa5fb0a17c777cf09, 0xf468107100525891}, + {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, + {0x81ac1fe293d599bf, 0xc6f14cd848405531}, + {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, + {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, + {0xfd442e4688bd304a, 0x908f4a166d1da664}, + {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, + {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, + {0xf7549530e188c128, 0xd12bee59e68ef47d}, + {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, + {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, + {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, + {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, + {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, + {0xebdf661791d60f56, 0x111b495b3464ad22}, + {0x936b9fcebb25c995, 0xcab10dd900beec35}, + {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, + {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, + {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, + {0xb3f4e093db73a093, 0x59ed216765690f57}, + {0xe0f218b8d25088b8, 0x306869c13ec3532d}, + {0x8c974f7383725573, 0x1e414218c73a13fc}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, + {0x894bc396ce5da772, 0x6b8bba8c328eb784}, + {0xab9eb47c81f5114f, 0x066ea92f3f326565}, + {0xd686619ba27255a2, 0xc80a537b0efefebe}, + {0x8613fd0145877585, 0xbd06742ce95f5f37}, + {0xa798fc4196e952e7, 0x2c48113823b73705}, + {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, + {0x82ef85133de648c4, 0x9a984d73dbe722fc}, + {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, + {0xcc963fee10b7d1b3, 0x318df905079926a9}, + {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, + {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, + {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, + {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, + {0x9c1661a651213e2d, 0x06bea10ca65c084f}, + {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, + {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, + {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, + {0xbe89523386091465, 0xf6bbb397f1135824}, + {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, + {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, + {0xba121a4650e4ddeb, 0x92f34d62616ce414}, + {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, + {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, + {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, + {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, + {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, + {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, + {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, + {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, + {0x87625f056c7c4a8b, 0x11471cd764ad4973}, + {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, + {0xd389b47879823479, 0x4aff1d108d4ec2c4}, + {0x843610cb4bf160cb, 0xcedf722a585139bb}, + {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, + {0xce947a3da6a9273e, 0x733d226229feea33}, + {0x811ccc668829b887, 0x0806357d5a3f5260}, + {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, + {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, + {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, + {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, + {0xc5029163f384a931, 0x0a9e795e65d4df12}, + {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, + {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, + {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, + {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, + {0x964e858c91ba2655, 0x3a6a07f8d510f870}, + {0xbbe226efb628afea, 0x890489f70a55368c}, + {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, + {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, + {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, + {0xb32df8e9f3546564, 0x47939822dc96abfa}, + {0xdff9772470297ebd, 0x59787e2b93bc56f8}, + {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, + {0xaefae51477a06b03, 0xede622920b6b23f2}, + {0xdab99e59958885c4, 0xe95fab368e45ecee}, + {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, + {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, + {0xd59944a37c0752a2, 0x4be76d3346f04960}, + {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, + {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, + {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, + {0x825ecc24c873782f, 0x8ed400668c0c28c9}, + {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, + {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, + {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, + {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, + {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, + {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, + {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, + {0xc24452da229b021b, 0xfbe85badce996169}, + {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, + {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, + {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, + {0xed246723473e3813, 0x290123e9aab23b69}, + {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, + {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, + {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, + {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, + {0x8d590723948a535f, 0x579c487e5a38ad0f}, + {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, + {0xdcdb1b2798182244, 0xf8e431456cf88e66}, + {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, + {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, + {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, + {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, + {0xa87fea27a539e9a5, 0x3f2398d747b36225}, + {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, + {0x83a3eeeef9153e89, 0x1953cf68300424ad}, + {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, + {0xcdb02555653131b6, 0x3792f412cb06794e}, + {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, + {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, + {0xc8de047564d20a8b, 0xf245825a5a445276}, + {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, + {0x9ced737bb6c4183d, 0x55464dd69685606c}, + {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, + {0xf53304714d9265df, 0xd53dd99f4b3066a9}, + {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, + {0xbf8fdb78849a5f96, 0xde98520472bdd034}, + {0xef73d256a5c0f77c, 0x963e66858f6d4441}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xbb127c53b17ec159, 0x5560c018580d5d53}, + {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, + {0x9226712162ab070d, 0xcab3961304ca70e9}, + {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, + {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, + {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, + {0xb267ed1940f1c61c, 0x55f038b237591ed4}, + {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, + {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, + {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, + {0xd9c7dced53c72255, 0x96e7bd358c904a22}, + {0x881cea14545c7575, 0x7e50d64177da2e55}, + {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, + {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, + {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, + {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, + {0xcfb11ead453994ba, 0x67de18eda5814af3}, + {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, + {0xa2425ff75e14fc31, 0xa1258379a94d028e}, + {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, + {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, + {0x9e74d1b791e07e48, 0x775ea264cf55347e}, + {0xc612062576589dda, 0x95364afe032a819e}, + {0xf79687aed3eec551, 0x3a83ddbd83f52205}, + {0x9abe14cd44753b52, 0xc4926a9672793543}, + {0xc16d9a0095928a27, 0x75b7053c0f178294}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, + {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, + {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, + {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, + {0xb877aa3236a4b449, 0x09befeb9fad487c3}, + {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, + {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, + {0xb424dc35095cd80f, 0x538484c19ef38c95}, + {0xe12e13424bb40e13, 0x2865a5f206b06fba}, + {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, + {0xafebff0bcb24aafe, 0xf78f69a51539d749}, + {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, + {0x89705f4136b4a597, 0x31680a88f8953031}, + {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, + {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, + {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, + {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, + {0xd1b71758e219652b, 0xd3c36113404ea4a9}, + {0x83126e978d4fdf3b, 0x645a1cac083126ea}, + {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, + {0xcccccccccccccccc, 0xcccccccccccccccd}, + {0x8000000000000000, 0x0000000000000000}, + {0xa000000000000000, 0x0000000000000000}, + {0xc800000000000000, 0x0000000000000000}, + {0xfa00000000000000, 0x0000000000000000}, + {0x9c40000000000000, 0x0000000000000000}, + {0xc350000000000000, 0x0000000000000000}, + {0xf424000000000000, 0x0000000000000000}, + {0x9896800000000000, 0x0000000000000000}, + {0xbebc200000000000, 0x0000000000000000}, + {0xee6b280000000000, 0x0000000000000000}, + {0x9502f90000000000, 0x0000000000000000}, + {0xba43b74000000000, 0x0000000000000000}, + {0xe8d4a51000000000, 0x0000000000000000}, + {0x9184e72a00000000, 0x0000000000000000}, + {0xb5e620f480000000, 0x0000000000000000}, + {0xe35fa931a0000000, 0x0000000000000000}, + {0x8e1bc9bf04000000, 0x0000000000000000}, + {0xb1a2bc2ec5000000, 0x0000000000000000}, + {0xde0b6b3a76400000, 0x0000000000000000}, + {0x8ac7230489e80000, 0x0000000000000000}, + {0xad78ebc5ac620000, 0x0000000000000000}, + {0xd8d726b7177a8000, 0x0000000000000000}, + {0x878678326eac9000, 0x0000000000000000}, + {0xa968163f0a57b400, 0x0000000000000000}, + {0xd3c21bcecceda100, 0x0000000000000000}, + {0x84595161401484a0, 0x0000000000000000}, + {0xa56fa5b99019a5c8, 0x0000000000000000}, + {0xcecb8f27f4200f3a, 0x0000000000000000}, + {0x813f3978f8940984, 0x4000000000000000}, + {0xa18f07d736b90be5, 0x5000000000000000}, + {0xc9f2c9cd04674ede, 0xa400000000000000}, + {0xfc6f7c4045812296, 0x4d00000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xc5371912364ce305, 0x6c28000000000000}, + {0xf684df56c3e01bc6, 0xc732000000000000}, + {0x9a130b963a6c115c, 0x3c7f400000000000}, + {0xc097ce7bc90715b3, 0x4b9f100000000000}, + {0xf0bdc21abb48db20, 0x1e86d40000000000}, + {0x96769950b50d88f4, 0x1314448000000000}, + {0xbc143fa4e250eb31, 0x17d955a000000000}, + {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, + {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, + {0xb7abc627050305ad, 0xf14a3d9e40000000}, + {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, + {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, + {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, + {0xe0352f62a19e306e, 0xd50b2037ad200000}, + {0x8c213d9da502de45, 0x4526f422cc340000}, + {0xaf298d050e4395d6, 0x9670b12b7f410000}, + {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, + {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, + {0xab0e93b6efee0053, 0x8eea0d047a457a00}, + {0xd5d238a4abe98068, 0x72a4904598d6d880}, + {0x85a36366eb71f041, 0x47a6da2b7f864750}, + {0xa70c3c40a64e6c51, 0x999090b65f67d924}, + {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, + {0x82818f1281ed449f, 0xbff8f10e7a8921a4}, + {0xa321f2d7226895c7, 0xaff72d52192b6a0d}, + {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490}, + {0xfee50b7025c36a08, 0x02f236d04753d5b4}, + {0x9f4f2726179a2245, 0x01d762422c946590}, + {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5}, + {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2}, + {0x9b934c3b330c8577, 0x63cc55f49f88eb2f}, + {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb}, + {0xf316271c7fc3908a, 0x8bef464e3945ef7a}, + {0x97edd871cfda3a56, 0x97758bf0e3cbb5ac}, + {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317}, + {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd}, + {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a}, + {0xb975d6b6ee39e436, 0xb3e2fd538e122b44}, + {0xe7d34c64a9c85d44, 0x60dbbca87196b616}, + {0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd}, + {0xb51d13aea4a488dd, 0x6babab6398bdbe41}, + {0xe264589a4dcdab14, 0xc696963c7eed2dd1}, + {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2}, + {0xb0de65388cc8ada8, 0x3b25a55f43294bcb}, + {0xdd15fe86affad912, 0x49ef0eb713f39ebe}, + {0x8a2dbf142dfcc7ab, 0x6e3569326c784337}, + {0xacb92ed9397bf996, 0x49c2c37f07965404}, + {0xd7e77a8f87daf7fb, 0xdc33745ec97be906}, + {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3}, + {0xa8acd7c0222311bc, 0xc40832ea0d68ce0c}, + {0xd2d80db02aabd62b, 0xf50a3fa490c30190}, + {0x83c7088e1aab65db, 0x792667c6da79e0fa}, + {0xa4b8cab1a1563f52, 0x577001b891185938}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86}, + {0x80b05e5ac60b6178, 0x544f8158315b05b4}, + {0xa0dc75f1778e39d6, 0x696361ae3db1c721}, + {0xc913936dd571c84c, 0x03bc3a19cd1e38e9}, + {0xfb5878494ace3a5f, 0x04ab48a04065c723}, + {0x9d174b2dcec0e47b, 0x62eb0d64283f9c76}, + {0xc45d1df942711d9a, 0x3ba5d0bd324f8394}, + {0xf5746577930d6500, 0xca8f44ec7ee36479}, + {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb}, + {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e}, + {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e}, + {0x95d04aee3b80ece5, 0xbba1f1d158724a12}, + {0xbb445da9ca61281f, 0x2a8a6e45ae8edc97}, + {0xea1575143cf97226, 0xf52d09d71a3293bd}, + {0x924d692ca61be758, 0x593c2626705f9c56}, + {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c}, + {0xe498f455c38b997a, 0x0b6dfb9c0f956447}, + {0x8edf98b59a373fec, 0x4724bd4189bd5eac}, + {0xb2977ee300c50fe7, 0x58edec91ec2cb657}, + {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed}, + {0x8b865b215899f46c, 0xbd79e0d20082ee74}, + {0xae67f1e9aec07187, 0xecd8590680a3aa11}, + {0xda01ee641a708de9, 0xe80e6f4820cc9495}, + {0x884134fe908658b2, 0x3109058d147fdcdd}, + {0xaa51823e34a7eede, 0xbd4b46f0599fd415}, + {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a}, + {0x850fadc09923329e, 0x03e2cf6bc604ddb0}, + {0xa6539930bf6bff45, 0x84db8346b786151c}, + {0xcfe87f7cef46ff16, 0xe612641865679a63}, + {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e}, + {0xa26da3999aef7749, 0xe3be5e330f38f09d}, + {0xcb090c8001ab551c, 0x5cadf5bfd3072cc5}, + {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6}, + {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa}, + {0xc646d63501a1511d, 0xb281e1fd541501b8}, + {0xf7d88bc24209a565, 0x1f225a7ca91a4226}, + {0x9ae757596946075f, 0x3375788de9b06958}, + {0xc1a12d2fc3978937, 0x0052d6b1641c83ae}, + {0xf209787bb47d6b84, 0xc0678c5dbd23a49a}, + {0x9745eb4d50ce6332, 0xf840b7ba963646e0}, + {0xbd176620a501fbff, 0xb650e5a93bc3d898}, + {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe}, + {0x93ba47c980e98cdf, 0xc66f336c36b10137}, + {0xb8a8d9bbe123f017, 0xb80b0047445d4184}, + {0xe6d3102ad96cec1d, 0xa60dc059157491e5}, + {0x9043ea1ac7e41392, 0x87c89837ad68db2f}, + {0xb454e4a179dd1877, 0x29babe4598c311fb}, + {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a}, + {0x8ce2529e2734bb1d, 0x1899e4a65f58660c}, + {0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f}, + {0xdc21a1171d42645d, 0x76707543f4fa1f73}, + {0x899504ae72497eba, 0x6a06494a791c53a8}, + {0xabfa45da0edbde69, 0x0487db9d17636892}, + {0xd6f8d7509292d603, 0x45a9d2845d3c42b6}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2}, + {0xa7f26836f282b732, 0x8e6cac7768d7141e}, + {0xd1ef0244af2364ff, 0x3207d795430cd926}, + {0x8335616aed761f1f, 0x7f44e6bd49e807b8}, + {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6}, + {0xcd036837130890a1, 0x36dba887c37a8c0f}, + {0x802221226be55a64, 0xc2494954da2c9789}, + {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c}, + {0xc83553c5c8965d3d, 0x6f92829494e5acc7}, + {0xfa42a8b73abbf48c, 0xcb772339ba1f17f9}, + {0x9c69a97284b578d7, 0xff2a760414536efb}, + {0xc38413cf25e2d70d, 0xfef5138519684aba}, + {0xf46518c2ef5b8cd1, 0x7eb258665fc25d69}, + {0x98bf2f79d5993802, 0xef2f773ffbd97a61}, + {0xbeeefb584aff8603, 0xaafb550ffacfd8fa}, + {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38}, + {0x952ab45cfa97a0b2, 0xdd945a747bf26183}, + {0xba756174393d88df, 0x94f971119aeef9e4}, + {0xe912b9d1478ceb17, 0x7a37cd5601aab85d}, + {0x91abb422ccb812ee, 0xac62e055c10ab33a}, + {0xb616a12b7fe617aa, 0x577b986b314d6009}, + {0xe39c49765fdf9d94, 0xed5a7e85fda0b80b}, + {0x8e41ade9fbebc27d, 0x14588f13be847307}, + {0xb1d219647ae6b31c, 0x596eb2d8ae258fc8}, + {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb}, + {0x8aec23d680043bee, 0x25de7bb9480d5854}, + {0xada72ccc20054ae9, 0xaf561aa79a10ae6a}, + {0xd910f7ff28069da4, 0x1b2ba1518094da04}, + {0x87aa9aff79042286, 0x90fb44d2f05d0842}, + {0xa99541bf57452b28, 0x353a1607ac744a53}, + {0xd3fa922f2d1675f2, 0x42889b8997915ce8}, + {0x847c9b5d7c2e09b7, 0x69956135febada11}, + {0xa59bc234db398c25, 0x43fab9837e699095}, + {0xcf02b2c21207ef2e, 0x94f967e45e03f4bb}, + {0x8161afb94b44f57d, 0x1d1be0eebac278f5}, + {0xa1ba1ba79e1632dc, 0x6462d92a69731732}, + {0xca28a291859bbf93, 0x7d7b8f7503cfdcfe}, + {0xfcb2cb35e702af78, 0x5cda735244c3d43e}, + {0x9defbf01b061adab, 0x3a0888136afa64a7}, + {0xc56baec21c7a1916, 0x088aaa1845b8fdd0}, + {0xf6c69a72a3989f5b, 0x8aad549e57273d45}, + {0x9a3c2087a63f6399, 0x36ac54e2f678864b}, + {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd}, + {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5}, + {0x969eb7c47859e743, 0x9f644ae5a4b1b325}, + {0xbc4665b596706114, 0x873d5d9f0dde1fee}, + {0xeb57ff22fc0c7959, 0xa90cb506d155a7ea}, + {0x9316ff75dd87cbd8, 0x09a7f12442d588f2}, + {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb2f}, + {0xe5d3ef282a242e81, 0x8f1668c8a86da5fa}, + {0x8fa475791a569d10, 0xf96e017d694487bc}, + {0xb38d92d760ec4455, 0x37c981dcc395a9ac}, + {0xe070f78d3927556a, 0x85bbe253f47b1417}, + {0x8c469ab843b89562, 0x93956d7478ccec8e}, + {0xaf58416654a6babb, 0x387ac8d1970027b2}, + {0xdb2e51bfe9d0696a, 0x06997b05fcc0319e}, + {0x88fcf317f22241e2, 0x441fece3bdf81f03}, + {0xab3c2fddeeaad25a, 0xd527e81cad7626c3}, + {0xd60b3bd56a5586f1, 0x8a71e223d8d3b074}, + {0x85c7056562757456, 0xf6872d5667844e49}, + {0xa738c6bebb12d16c, 0xb428f8ac016561db}, + {0xd106f86e69d785c7, 0xe13336d701beba52}, + {0x82a45b450226b39c, 0xecc0024661173473}, + {0xa34d721642b06084, 0x27f002d7f95d0190}, + {0xcc20ce9bd35c78a5, 0x31ec038df7b441f4}, + {0xff290242c83396ce, 0x7e67047175a15271}, + {0x9f79a169bd203e41, 0x0f0062c6e984d386}, + {0xc75809c42c684dd1, 0x52c07b78a3e60868}, + {0xf92e0c3537826145, 0xa7709a56ccdf8a82}, + {0x9bbcc7a142b17ccb, 0x88a66076400bb691}, + {0xc2abf989935ddbfe, 0x6acff893d00ea435}, + {0xf356f7ebf83552fe, 0x0583f6b8c4124d43}, + {0x98165af37b2153de, 0xc3727a337a8b704a}, + {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c}, + {0xeda2ee1c7064130c, 0x1162def06f79df73}, + {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8}, + {0xb9a74a0637ce2ee1, 0x6d953e2bd7173692}, + {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437}, + {0x910ab1d4db9914a0, 0x1d9c9892400a22a2}, + {0xb54d5e4a127f59c8, 0x2503beb6d00cab4b}, + {0xe2a0b5dc971f303a, 0x2e44ae64840fd61d}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d2}, + {0xb10d8e1456105dad, 0x7425a83e872c5f47}, + {0xdd50f1996b947518, 0xd12f124e28f77719}, + {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f}, + {0xace73cbfdc0bfb7b, 0x636cc64d1001550b}, + {0xd8210befd30efa5a, 0x3c47f7e05401aa4e}, + {0x8714a775e3e95c78, 0x65acfaec34810a71}, + {0xa8d9d1535ce3b396, 0x7f1839a741a14d0d}, + {0xd31045a8341ca07c, 0x1ede48111209a050}, + {0x83ea2b892091e44d, 0x934aed0aab460432}, + {0xa4e4b66b68b65d60, 0xf81da84d5617853f}, + {0xce1de40642e3f4b9, 0x36251260ab9d668e}, + {0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019}, + {0xa1075a24e4421730, 0xb24cf65b8612f81f}, + {0xc94930ae1d529cfc, 0xdee033f26797b627}, + {0xfb9b7cd9a4a7443c, 0x169840ef017da3b1}, + {0x9d412e0806e88aa5, 0x8e1f289560ee864e}, + {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2}, + {0xf5b5d7ec8acb58a2, 0xae10af696774b1db}, + {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29}, + {0xbff610b0cc6edd3f, 0x17fd090a58d32af3}, + {0xeff394dcff8a948e, 0xddfc4b4cef07f5b0}, + {0x95f83d0a1fb69cd9, 0x4abdaf101564f98e}, + {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1}, + {0xea53df5fd18d5513, 0x84c86189216dc5ed}, + {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4}, + {0xb7118682dbb66a77, 0x3fbc8c33221dc2a1}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334a}, + {0x8f05b1163ba6832d, 0x29cb4d87f2a7400e}, + {0xb2c71d5bca9023f8, 0x743e20e9ef511012}, + {0xdf78e4b2bd342cf6, 0x914da9246b255416}, + {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e}, + {0xae9672aba3d0c320, 0xa184ac2473b529b1}, + {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e}, + {0x8865899617fb1871, 0x7e2fa67c7a658892}, + {0xaa7eebfb9df9de8d, 0xddbb901b98feeab7}, + {0xd51ea6fa85785631, 0x552a74227f3ea565}, + {0x8533285c936b35de, 0xd53a88958f87275f}, + {0xa67ff273b8460356, 0x8a892abaf368f137}, + {0xd01fef10a657842c, 0x2d2b7569b0432d85}, + {0x8213f56a67f6b29b, 0x9c3b29620e29fc73}, + {0xa298f2c501f45f42, 0x8349f3ba91b47b8f}, + {0xcb3f2f7642717713, 0x241c70a936219a73}, + {0xfe0efb53d30dd4d7, 0xed238cd383aa0110}, + {0x9ec95d1463e8a506, 0xf4363804324a40aa}, + {0xc67bb4597ce2ce48, 0xb143c6053edcd0d5}, + {0xf81aa16fdc1b81da, 0xdd94b7868e94050a}, + {0x9b10a4e5e9913128, 0xca7cf2b4191c8326}, + {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0}, + {0xf24a01a73cf2dccf, 0xbc633b39673c8cec}, + {0x976e41088617ca01, 0xd5be0503e085d813}, + {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18}, + {0xec9c459d51852ba2, 0xddf8e7d60ed1219e}, + {0x93e1ab8252f33b45, 0xcabb90e5c942b503}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936243}, + {0xe7109bfba19c0c9d, 0x0cc512670a783ad4}, + {0x906a617d450187e2, 0x27fb2b80668b24c5}, + {0xb484f9dc9641e9da, 0xb1f9f660802dedf6}, + {0xe1a63853bbd26451, 0x5e7873f8a0396973}, + {0x8d07e33455637eb2, 0xdb0b487b6423e1e8}, + {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62}, + {0xdc5c5301c56b75f7, 0x7641a140cc7810fb}, + {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d}, + {0xac2820d9623bf429, 0x546345fa9fbdcd44}, + {0xd732290fbacaf133, 0xa97c177947ad4095}, + {0x867f59a9d4bed6c0, 0x49ed8eabcccc485d}, + {0xa81f301449ee8c70, 0x5c68f256bfff5a74}, + {0xd226fc195c6a2f8c, 0x73832eec6fff3111}, + {0x83585d8fd9c25db7, 0xc831fd53c5ff7eab}, + {0xa42e74f3d032f525, 0xba3e7ca8b77f5e55}, + {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb}, + {0x80444b5e7aa7cf85, 0x7980d163cf5b81b3}, + {0xa0555e361951c366, 0xd7e105bcc332621f}, + {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7}, + {0xfa856334878fc150, 0xb14f98f6f0feb951}, + {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3}, + {0xc3b8358109e84f07, 0x0a862f80ec4700c8}, + {0xf4a642e14c6262c8, 0xcd27bb612758c0fa}, + {0x98e7e9cccfbd7dbd, 0x8038d51cb897789c}, + {0xbf21e44003acdd2c, 0xe0470a63e6bd56c3}, + {0xeeea5d5004981478, 0x1858ccfce06cac74}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc8}, + {0xbaa718e68396cffd, 0xd30560258f54e6ba}, + {0xe950df20247c83fd, 0x47c6b82ef32a2069}, + {0x91d28b7416cdd27e, 0x4cdc331d57fa5441}, + {0xb6472e511c81471d, 0xe0133fe4adf8e952}, + {0xe3d8f9e563a198e5, 0x58180fddd97723a6}, + {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648}, + {0xb201833b35d63f73, 0x2cd2cc6551e513da}, + {0xde81e40a034bcf4f, 0xf8077f7ea65e58d1}, + {0x8b112e86420f6191, 0xfb04afaf27faf782}, + {0xadd57a27d29339f6, 0x79c5db9af1f9b563}, + {0xd94ad8b1c7380874, 0x18375281ae7822bc}, + {0x87cec76f1c830548, 0x8f2293910d0b15b5}, + {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb22}, + {0xd433179d9c8cb841, 0x5fa60692a46151eb}, + {0x849feec281d7f328, 0xdbc7c41ba6bcd333}, + {0xa5c7ea73224deff3, 0x12b9b522906c0800}, + {0xcf39e50feae16bef, 0xd768226b34870a00}, + {0x81842f29f2cce375, 0xe6a1158300d46640}, + {0xa1e53af46f801c53, 0x60495ae3c1097fd0}, + {0xca5e89b18b602368, 0x385bb19cb14bdfc4}, + {0xfcf62c1dee382c42, 0x46729e03dd9ed7b5}, + {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d1}, + {0xc5a05277621be293, 0xc7098b7305241885}, + { 0xf70867153aa2db38, + 0xb8cbee4fc66d1ea7 } +#else + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0xc350000000000000, 0x0000000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xfee50b7025c36a08, 0x02f236d04753d5b4}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86}, + {0xa6539930bf6bff45, 0x84db8346b786151c}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2}, + {0xd910f7ff28069da4, 0x1b2ba1518094da04}, + {0xaf58416654a6babb, 0x387ac8d1970027b2}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d2}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334a}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936243}, + { 0x95527a5202df0ccb, + 0x0f37801e0c43ebc8 } +#endif + }; + +#if FMT_USE_FULL_CACHE_DRAGONBOX + return pow10_significands[k - float_info<double>::min_k]; +#else + static constexpr const uint64_t powers_of_5_64[] = { + 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, + 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, + 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, + 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd, + 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9, + 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5, + 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631, + 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, + 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; + + static constexpr const uint32_t pow10_recovery_errors[] = { + 0x50001400, 0x54044100, 0x54014555, 0x55954415, 0x54115555, 0x00000001, + 0x50000000, 0x00104000, 0x54010004, 0x05004001, 0x55555544, 0x41545555, + 0x54040551, 0x15445545, 0x51555514, 0x10000015, 0x00101100, 0x01100015, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04450514, 0x45414110, + 0x55555145, 0x50544050, 0x15040155, 0x11054140, 0x50111514, 0x11451454, + 0x00400541, 0x00000000, 0x55555450, 0x10056551, 0x10054011, 0x55551014, + 0x69514555, 0x05151109, 0x00155555}; + + static const int compression_ratio = 27; + + // Compute base index. + int cache_index = (k - float_info<double>::min_k) / compression_ratio; + int kb = cache_index * compression_ratio + float_info<double>::min_k; + int offset = k - kb; + + // Get base cache. + uint128_wrapper base_cache = pow10_significands[cache_index]; + if (offset == 0) return base_cache; + + // Compute the required amount of bit-shift. + int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset; + FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected"); + + // Try to recover the real cache. + uint64_t pow5 = powers_of_5_64[offset]; + uint128_wrapper recovered_cache = umul128(base_cache.high(), pow5); + uint128_wrapper middle_low = + umul128(base_cache.low() - (kb < 0 ? 1u : 0u), pow5); + + recovered_cache += middle_low.high(); + + uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); + uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); + + recovered_cache = + uint128_wrapper{(recovered_cache.low() >> alpha) | high_to_middle, + ((middle_low.low() >> alpha) | middle_to_low)}; + + if (kb < 0) recovered_cache += 1; + + // Get error. + int error_idx = (k - float_info<double>::min_k) / 16; + uint32_t error = (pow10_recovery_errors[error_idx] >> + ((k - float_info<double>::min_k) % 16) * 2) & + 0x3; + + // Add the error back. + FMT_ASSERT(recovered_cache.low() + error >= recovered_cache.low(), ""); + return {recovered_cache.high(), recovered_cache.low() + error}; +#endif + } + + static carrier_uint compute_mul(carrier_uint u, + const cache_entry_type& cache) FMT_NOEXCEPT { + return umul192_upper64(u, cache); + } + + static uint32_t compute_delta(cache_entry_type const& cache, + int beta_minus_1) FMT_NOEXCEPT { + return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta_minus_1)); + } + + static bool compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta_minus_1) FMT_NOEXCEPT { + FMT_ASSERT(beta_minus_1 >= 1, ""); + FMT_ASSERT(beta_minus_1 < 64, ""); + + return ((umul192_middle64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0; + } + + static carrier_uint compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return (cache.high() - + (cache.high() >> (float_info<double>::significand_bits + 2))) >> + (64 - float_info<double>::significand_bits - 1 - beta_minus_1); + } + + static carrier_uint compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return (cache.high() + + (cache.high() >> (float_info<double>::significand_bits + 1))) >> + (64 - float_info<double>::significand_bits - 1 - beta_minus_1); + } + + static carrier_uint compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return ((cache.high() >> + (64 - float_info<double>::significand_bits - 2 - beta_minus_1)) + + 1) / + 2; + } +}; + +// Various integer checks +template <class T> +bool is_left_endpoint_integer_shorter_interval(int exponent) FMT_NOEXCEPT { + return exponent >= + float_info< + T>::case_shorter_interval_left_endpoint_lower_threshold && + exponent <= + float_info<T>::case_shorter_interval_left_endpoint_upper_threshold; +} +template <class T> +bool is_endpoint_integer(typename float_info<T>::carrier_uint two_f, + int exponent, int minus_k) FMT_NOEXCEPT { + if (exponent < float_info<T>::case_fc_pm_half_lower_threshold) return false; + // For k >= 0. + if (exponent <= float_info<T>::case_fc_pm_half_upper_threshold) return true; + // For k < 0. + if (exponent > float_info<T>::divisibility_check_by_5_threshold) return false; + return divisible_by_power_of_5(two_f, minus_k); +} + +template <class T> +bool is_center_integer(typename float_info<T>::carrier_uint two_f, int exponent, + int minus_k) FMT_NOEXCEPT { + // Exponent for 5 is negative. + if (exponent > float_info<T>::divisibility_check_by_5_threshold) return false; + if (exponent > float_info<T>::case_fc_upper_threshold) + return divisible_by_power_of_5(two_f, minus_k); + // Both exponents are nonnegative. + if (exponent >= float_info<T>::case_fc_lower_threshold) return true; + // Exponent for 2 is negative. + return divisible_by_power_of_2(two_f, minus_k - exponent + 1); +} + +// Remove trailing zeros from n and return the number of zeros removed (float) +FMT_ALWAYS_INLINE int remove_trailing_zeros(uint32_t& n) FMT_NOEXCEPT { +#ifdef FMT_BUILTIN_CTZ + int t = FMT_BUILTIN_CTZ(n); +#else + int t = ctz(n); +#endif + if (t > float_info<float>::max_trailing_zeros) + t = float_info<float>::max_trailing_zeros; + + const uint32_t mod_inv1 = 0xcccccccd; + const uint32_t max_quotient1 = 0x33333333; + const uint32_t mod_inv2 = 0xc28f5c29; + const uint32_t max_quotient2 = 0x0a3d70a3; + + int s = 0; + for (; s < t - 1; s += 2) { + if (n * mod_inv2 > max_quotient2) break; + n *= mod_inv2; + } + if (s < t && n * mod_inv1 <= max_quotient1) { + n *= mod_inv1; + ++s; + } + n >>= s; + return s; +} + +// Removes trailing zeros and returns the number of zeros removed (double) +FMT_ALWAYS_INLINE int remove_trailing_zeros(uint64_t& n) FMT_NOEXCEPT { +#ifdef FMT_BUILTIN_CTZLL + int t = FMT_BUILTIN_CTZLL(n); +#else + int t = ctzll(n); +#endif + if (t > float_info<double>::max_trailing_zeros) + t = float_info<double>::max_trailing_zeros; + // Divide by 10^8 and reduce to 32-bits + // Since ret_value.significand <= (2^64 - 1) / 1000 < 10^17, + // both of the quotient and the r should fit in 32-bits + + const uint32_t mod_inv1 = 0xcccccccd; + const uint32_t max_quotient1 = 0x33333333; + const uint64_t mod_inv8 = 0xc767074b22e90e21; + const uint64_t max_quotient8 = 0x00002af31dc46118; + + // If the number is divisible by 1'0000'0000, work with the quotient + if (t >= 8) { + auto quotient_candidate = n * mod_inv8; + + if (quotient_candidate <= max_quotient8) { + auto quotient = static_cast<uint32_t>(quotient_candidate >> 8); + + int s = 8; + for (; s < t; ++s) { + if (quotient * mod_inv1 > max_quotient1) break; + quotient *= mod_inv1; + } + quotient >>= (s - 8); + n = quotient; + return s; + } + } + + // Otherwise, work with the remainder + auto quotient = static_cast<uint32_t>(n / 100000000); + auto remainder = static_cast<uint32_t>(n - 100000000 * quotient); + + if (t == 0 || remainder * mod_inv1 > max_quotient1) { + return 0; + } + remainder *= mod_inv1; + + if (t == 1 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 1) + quotient * 10000000ull; + return 1; + } + remainder *= mod_inv1; + + if (t == 2 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 2) + quotient * 1000000ull; + return 2; + } + remainder *= mod_inv1; + + if (t == 3 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 3) + quotient * 100000ull; + return 3; + } + remainder *= mod_inv1; + + if (t == 4 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 4) + quotient * 10000ull; + return 4; + } + remainder *= mod_inv1; + + if (t == 5 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 5) + quotient * 1000ull; + return 5; + } + remainder *= mod_inv1; + + if (t == 6 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 6) + quotient * 100ull; + return 6; + } + remainder *= mod_inv1; + + n = (remainder >> 7) + quotient * 10ull; + return 7; +} + +// The main algorithm for shorter interval case +template <class T> +FMT_ALWAYS_INLINE decimal_fp<T> shorter_interval_case(int exponent) + FMT_NOEXCEPT { + decimal_fp<T> ret_value; + // Compute k and beta + const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); + const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k); + + // Compute xi and zi + using cache_entry_type = typename cache_accessor<T>::cache_entry_type; + const cache_entry_type cache = cache_accessor<T>::get_cached_power(-minus_k); + + auto xi = cache_accessor<T>::compute_left_endpoint_for_shorter_interval_case( + cache, beta_minus_1); + auto zi = cache_accessor<T>::compute_right_endpoint_for_shorter_interval_case( + cache, beta_minus_1); + + // If the left endpoint is not an integer, increase it + if (!is_left_endpoint_integer_shorter_interval<T>(exponent)) ++xi; + + // Try bigger divisor + ret_value.significand = zi / 10; + + // If succeed, remove trailing zeros if necessary and return + if (ret_value.significand * 10 >= xi) { + ret_value.exponent = minus_k + 1; + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + } + + // Otherwise, compute the round-up of y + ret_value.significand = + cache_accessor<T>::compute_round_up_for_shorter_interval_case( + cache, beta_minus_1); + ret_value.exponent = minus_k; + + // When tie occurs, choose one of them according to the rule + if (exponent >= float_info<T>::shorter_interval_tie_lower_threshold && + exponent <= float_info<T>::shorter_interval_tie_upper_threshold) { + ret_value.significand = ret_value.significand % 2 == 0 + ? ret_value.significand + : ret_value.significand - 1; + } else if (ret_value.significand < xi) { + ++ret_value.significand; + } + return ret_value; +} + +template <typename T> decimal_fp<T> to_decimal(T x) FMT_NOEXCEPT { + // Step 1: integer promotion & Schubfach multiplier calculation. + + using carrier_uint = typename float_info<T>::carrier_uint; + using cache_entry_type = typename cache_accessor<T>::cache_entry_type; + auto br = bit_cast<carrier_uint>(x); + + // Extract significand bits and exponent bits. + const carrier_uint significand_mask = + (static_cast<carrier_uint>(1) << float_info<T>::significand_bits) - 1; + carrier_uint significand = (br & significand_mask); + int exponent = static_cast<int>((br & exponent_mask<T>()) >> + float_info<T>::significand_bits); + + if (exponent != 0) { // Check if normal. + exponent += float_info<T>::exponent_bias - float_info<T>::significand_bits; + + // Shorter interval case; proceed like Schubfach. + if (significand == 0) return shorter_interval_case<T>(exponent); + + significand |= + (static_cast<carrier_uint>(1) << float_info<T>::significand_bits); + } else { + // Subnormal case; the interval is always regular. + if (significand == 0) return {0, 0}; + exponent = float_info<T>::min_exponent - float_info<T>::significand_bits; + } + + const bool include_left_endpoint = (significand % 2 == 0); + const bool include_right_endpoint = include_left_endpoint; + + // Compute k and beta. + const int minus_k = floor_log10_pow2(exponent) - float_info<T>::kappa; + const cache_entry_type cache = cache_accessor<T>::get_cached_power(-minus_k); + const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k); + + // Compute zi and deltai + // 10^kappa <= deltai < 10^(kappa + 1) + const uint32_t deltai = cache_accessor<T>::compute_delta(cache, beta_minus_1); + const carrier_uint two_fc = significand << 1; + const carrier_uint two_fr = two_fc | 1; + const carrier_uint zi = + cache_accessor<T>::compute_mul(two_fr << beta_minus_1, cache); + + // Step 2: Try larger divisor; remove trailing zeros if necessary + + // Using an upper bound on zi, we might be able to optimize the division + // better than the compiler; we are computing zi / big_divisor here + decimal_fp<T> ret_value; + ret_value.significand = divide_by_10_to_kappa_plus_1(zi); + uint32_t r = static_cast<uint32_t>(zi - float_info<T>::big_divisor * + ret_value.significand); + + if (r > deltai) { + goto small_divisor_case_label; + } else if (r < deltai) { + // Exclude the right endpoint if necessary + if (r == 0 && !include_right_endpoint && + is_endpoint_integer<T>(two_fr, exponent, minus_k)) { + --ret_value.significand; + r = float_info<T>::big_divisor; + goto small_divisor_case_label; + } + } else { + // r == deltai; compare fractional parts + // Check conditions in the order different from the paper + // to take advantage of short-circuiting + const carrier_uint two_fl = two_fc - 1; + if ((!include_left_endpoint || + !is_endpoint_integer<T>(two_fl, exponent, minus_k)) && + !cache_accessor<T>::compute_mul_parity(two_fl, cache, beta_minus_1)) { + goto small_divisor_case_label; + } + } + ret_value.exponent = minus_k + float_info<T>::kappa + 1; + + // We may need to remove trailing zeros + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + + // Step 3: Find the significand with the smaller divisor + +small_divisor_case_label: + ret_value.significand *= 10; + ret_value.exponent = minus_k + float_info<T>::kappa; + + const uint32_t mask = (1u << float_info<T>::kappa) - 1; + auto dist = r - (deltai / 2) + (float_info<T>::small_divisor / 2); + + // Is dist divisible by 2^kappa? + if ((dist & mask) == 0) { + const bool approx_y_parity = + ((dist ^ (float_info<T>::small_divisor / 2)) & 1) != 0; + dist >>= float_info<T>::kappa; + + // Is dist divisible by 5^kappa? + if (check_divisibility_and_divide_by_pow5<float_info<T>::kappa>(dist)) { + ret_value.significand += dist; + + // Check z^(f) >= epsilon^(f) + // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, + // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f) + // Since there are only 2 possibilities, we only need to care about the + // parity. Also, zi and r should have the same parity since the divisor + // is an even number + if (cache_accessor<T>::compute_mul_parity(two_fc, cache, beta_minus_1) != + approx_y_parity) { + --ret_value.significand; + } else { + // If z^(f) >= epsilon^(f), we might have a tie + // when z^(f) == epsilon^(f), or equivalently, when y is an integer + if (is_center_integer<T>(two_fc, exponent, minus_k)) { + ret_value.significand = ret_value.significand % 2 == 0 + ? ret_value.significand + : ret_value.significand - 1; + } + } + } + // Is dist not divisible by 5^kappa? + else { + ret_value.significand += dist; + } + } + // Is dist not divisible by 2^kappa? + else { + // Since we know dist is small, we might be able to optimize the division + // better than the compiler; we are computing dist / small_divisor here + ret_value.significand += + small_division_by_pow10<float_info<T>::kappa>(dist); + } + return ret_value; +} +} // namespace dragonbox + +// Formats value using a variation of the Fixed-Precision Positive +// Floating-Point Printout ((FPP)^2) algorithm by Steele & White: +// https://fmt.dev/papers/p372-steele.pdf. +template <typename Double> +void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf, + int& exp10) { + bigint numerator; // 2 * R in (FPP)^2. + bigint denominator; // 2 * S in (FPP)^2. + // lower and upper are differences between value and corresponding boundaries. + bigint lower; // (M^- in (FPP)^2). + bigint upper_store; // upper's value if different from lower. + bigint* upper = nullptr; // (M^+ in (FPP)^2). + fp value; + // Shift numerator and denominator by an extra bit or two (if lower boundary + // is closer) to make lower and upper integers. This eliminates multiplication + // by 2 during later computations. + const bool is_predecessor_closer = + binary32 ? value.assign(static_cast<float>(d)) : value.assign(d); + int shift = is_predecessor_closer ? 2 : 1; + uint64_t significand = value.f << shift; + if (value.e >= 0) { + numerator.assign(significand); + numerator <<= value.e; + lower.assign(1); + lower <<= value.e; + if (shift != 1) { + upper_store.assign(1); + upper_store <<= value.e + 1; + upper = &upper_store; + } + denominator.assign_pow10(exp10); + denominator <<= shift; + } else if (exp10 < 0) { + numerator.assign_pow10(-exp10); + lower.assign(numerator); + if (shift != 1) { + upper_store.assign(numerator); + upper_store <<= 1; + upper = &upper_store; + } + numerator *= significand; + denominator.assign(1); + denominator <<= shift - value.e; + } else { + numerator.assign(significand); + denominator.assign_pow10(exp10); + denominator <<= shift - value.e; + lower.assign(1); + if (shift != 1) { + upper_store.assign(1ULL << 1); + upper = &upper_store; + } + } + // Invariant: value == (numerator / denominator) * pow(10, exp10). + if (num_digits < 0) { + // Generate the shortest representation. + if (!upper) upper = &lower; + bool even = (value.f & 1) == 0; + num_digits = 0; + char* data = buf.data(); + for (;;) { + int digit = numerator.divmod_assign(denominator); + bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. + // numerator + upper >[=] pow10: + bool high = add_compare(numerator, *upper, denominator) + even > 0; + data[num_digits++] = static_cast<char>('0' + digit); + if (low || high) { + if (!low) { + ++data[num_digits - 1]; + } else if (high) { + int result = add_compare(numerator, numerator, denominator); + // Round half to even. + if (result > 0 || (result == 0 && (digit % 2) != 0)) + ++data[num_digits - 1]; + } + buf.try_resize(to_unsigned(num_digits)); + exp10 -= num_digits - 1; + return; + } + numerator *= 10; + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + // Generate the given number of digits. + exp10 -= num_digits - 1; + if (num_digits == 0) { + buf.try_resize(1); + denominator *= 10; + buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + return; + } + buf.try_resize(to_unsigned(num_digits)); + for (int i = 0; i < num_digits - 1; ++i) { + int digit = numerator.divmod_assign(denominator); + buf[i] = static_cast<char>('0' + digit); + numerator *= 10; + } + int digit = numerator.divmod_assign(denominator); + auto result = add_compare(numerator, numerator, denominator); + if (result > 0 || (result == 0 && (digit % 2) != 0)) { + if (digit == 9) { + const auto overflow = '0' + 10; + buf[num_digits - 1] = overflow; + // Propagate the carry. + for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] == overflow) { + buf[0] = '1'; + ++exp10; + } + return; + } + ++digit; + } + buf[num_digits - 1] = static_cast<char>('0' + digit); +} + +template <typename T> +int format_float(T value, int precision, float_specs specs, buffer<char>& buf) { + static_assert(!std::is_same<T, float>::value, ""); + FMT_ASSERT(value >= 0, "value is negative"); + + const bool fixed = specs.format == float_format::fixed; + if (value <= 0) { // <= instead of == to silence a warning. + if (precision <= 0 || !fixed) { + buf.push_back('0'); + return 0; + } + buf.try_resize(to_unsigned(precision)); + std::uninitialized_fill_n(buf.data(), precision, '0'); + return -precision; + } + + if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf); + + if (precision < 0) { + // Use Dragonbox for the shortest format. + if (specs.binary32) { + auto dec = dragonbox::to_decimal(static_cast<float>(value)); + write<char>(buffer_appender<char>(buf), dec.significand); + return dec.exponent; + } + auto dec = dragonbox::to_decimal(static_cast<double>(value)); + write<char>(buffer_appender<char>(buf), dec.significand); + return dec.exponent; + } + + // Use Grisu + Dragon4 for the given precision: + // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. + int exp = 0; + const int min_exp = -60; // alpha in Grisu. + int cached_exp10 = 0; // K in Grisu. + fp normalized = normalize(fp(value)); + const auto cached_pow = get_cached_power( + min_exp - (normalized.e + fp::significand_size), cached_exp10); + normalized = normalized * cached_pow; + // Limit precision to the maximum possible number of significant digits in an + // IEEE754 double because we don't need to generate zeros. + const int max_double_digits = 767; + if (precision > max_double_digits) precision = max_double_digits; + fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; + if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) { + exp += handler.size - cached_exp10 - 1; + fallback_format(value, handler.precision, specs.binary32, buf, exp); + } else { + exp += handler.exp10; + buf.try_resize(to_unsigned(handler.size)); + } + if (!fixed && !specs.showpoint) { + // Remove trailing zeros. + auto num_digits = buf.size(); + while (num_digits > 0 && buf[num_digits - 1] == '0') { + --num_digits; + ++exp; + } + buf.try_resize(num_digits); + } + return exp; +} // namespace detail + +template <typename T> +int snprintf_float(T value, int precision, float_specs specs, + buffer<char>& buf) { + // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. + FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); + static_assert(!std::is_same<T, float>::value, ""); + + // Subtract 1 to account for the difference in precision since we use %e for + // both general and exponent format. + if (specs.format == float_format::general || + specs.format == float_format::exp) + precision = (precision >= 0 ? precision : 6) - 1; + + // Build the format string. + enum { max_format_size = 7 }; // The longest format is "%#.*Le". + char format[max_format_size]; + char* format_ptr = format; + *format_ptr++ = '%'; + if (specs.showpoint && specs.format == float_format::hex) *format_ptr++ = '#'; + if (precision >= 0) { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + if (std::is_same<T, long double>()) *format_ptr++ = 'L'; + *format_ptr++ = specs.format != float_format::hex + ? (specs.format == float_format::fixed ? 'f' : 'e') + : (specs.upper ? 'A' : 'a'); + *format_ptr = '\0'; + + // Format using snprintf. + auto offset = buf.size(); + for (;;) { + auto begin = buf.data() + offset; + auto capacity = buf.capacity() - offset; +#ifdef FMT_FUZZ + if (precision > 100000) + throw std::runtime_error( + "fuzz mode - avoid large allocation inside snprintf"); +#endif + // Suppress the warning about a nonliteral format string. + // Cannot use auto because of a bug in MinGW (#1532). + int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; + int result = precision >= 0 + ? snprintf_ptr(begin, capacity, format, precision, value) + : snprintf_ptr(begin, capacity, format, value); + if (result < 0) { + // The buffer will grow exponentially. + buf.try_reserve(buf.capacity() + 1); + continue; + } + auto size = to_unsigned(result); + // Size equal to capacity means that the last character was truncated. + if (size >= capacity) { + buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. + continue; + } + auto is_digit = [](char c) { return c >= '0' && c <= '9'; }; + if (specs.format == float_format::fixed) { + if (precision == 0) { + buf.try_resize(size); + return 0; + } + // Find and remove the decimal point. + auto end = begin + size, p = end; + do { + --p; + } while (is_digit(*p)); + int fraction_size = static_cast<int>(end - p - 1); + std::memmove(p, p + 1, to_unsigned(fraction_size)); + buf.try_resize(size - 1); + return -fraction_size; + } + if (specs.format == float_format::hex) { + buf.try_resize(size + offset); + return 0; + } + // Find and parse the exponent. + auto end = begin + size, exp_pos = end; + do { + --exp_pos; + } while (*exp_pos != 'e'); + char sign = exp_pos[1]; + FMT_ASSERT(sign == '+' || sign == '-', ""); + int exp = 0; + auto p = exp_pos + 2; // Skip 'e' and sign. + do { + FMT_ASSERT(is_digit(*p), ""); + exp = exp * 10 + (*p++ - '0'); + } while (p != end); + if (sign == '-') exp = -exp; + int fraction_size = 0; + if (exp_pos != begin + 1) { + // Remove trailing zeros. + auto fraction_end = exp_pos - 1; + while (*fraction_end == '0') --fraction_end; + // Move the fractional part left to get rid of the decimal point. + fraction_size = static_cast<int>(fraction_end - begin - 1); + std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size)); + } + buf.try_resize(to_unsigned(fraction_size) + offset + 1); + return exp - fraction_size; + } +} + +struct stringifier { + template <typename T> FMT_INLINE std::string operator()(T value) const { + return to_string(value); + } + std::string operator()(basic_format_arg<format_context>::handle h) const { + memory_buffer buf; + format_parse_context parse_ctx({}); + format_context format_ctx(buffer_appender<char>(buf), {}, {}); + h.format(parse_ctx, format_ctx); + return to_string(buf); + } +}; +} // namespace detail + +template <> struct formatter<detail::bigint> { + FMT_CONSTEXPR format_parse_context::iterator parse( + format_parse_context& ctx) { + return ctx.begin(); + } + + format_context::iterator format(const detail::bigint& n, + format_context& ctx) { + auto out = ctx.out(); + bool first = true; + for (auto i = n.bigits_.size(); i > 0; --i) { + auto value = n.bigits_[i - 1u]; + if (first) { + out = format_to(out, FMT_STRING("{:x}"), value); + first = false; + continue; + } + out = format_to(out, FMT_STRING("{:08x}"), value); + } + if (n.exp_ > 0) + out = format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); + return out; + } +}; + +FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { + for_each_codepoint(s, [this](uint32_t cp, int error) { + if (error != 0) FMT_THROW(std::runtime_error("invalid utf8")); + if (cp <= 0xFFFF) { + buffer_.push_back(static_cast<wchar_t>(cp)); + } else { + cp -= 0x10000; + buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10))); + buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF))); + } + }); + buffer_.push_back(0); +} + +FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code, + string_view message) FMT_NOEXCEPT { + FMT_TRY { + memory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) { + char* system_message = &buf[0]; + int result = + detail::safe_strerror(error_code, system_message, buf.size()); + if (result == 0) { + format_to(detail::buffer_appender<char>(out), FMT_STRING("{}: {}"), + message, system_message); + return; + } + if (result != ERANGE) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +FMT_FUNC void detail::error_handler::on_error(const char* message) { + FMT_THROW(format_error(message)); +} + +FMT_FUNC void report_system_error(int error_code, + fmt::string_view message) FMT_NOEXCEPT { + report_error(format_system_error, error_code, message); +} + +FMT_FUNC std::string detail::vformat(string_view format_str, format_args args) { + if (format_str.size() == 2 && equal2(format_str.data(), "{}")) { + auto arg = args.get(0); + if (!arg) error_handler().on_error("argument not found"); + return visit_format_arg(stringifier(), arg); + } + memory_buffer buffer; + detail::vformat_to(buffer, format_str, args); + return to_string(buffer); +} + +#ifdef _WIN32 +namespace detail { +using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>; +extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // + void*, const void*, dword, dword*, void*); +} // namespace detail +#endif + +FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { + memory_buffer buffer; + detail::vformat_to(buffer, format_str, args); +#ifdef _WIN32 + auto fd = _fileno(f); + if (_isatty(fd)) { + detail::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size())); + auto written = detail::dword(); + if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), + u16.c_str(), static_cast<uint32_t>(u16.size()), + &written, nullptr)) { + return; + } + // Fallback to fwrite on failure. It can happen if the output has been + // redirected to NUL. + } +#endif + detail::fwrite_fully(buffer.data(), 1, buffer.size(), f); +} + +#ifdef _WIN32 +// Print assuming legacy (non-Unicode) encoding. +FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, + format_args args) { + memory_buffer buffer; + detail::vformat_to(buffer, format_str, + basic_format_args<buffer_context<char>>(args)); + fwrite_fully(buffer.data(), 1, buffer.size(), f); +} +#endif + +FMT_FUNC void vprint(string_view format_str, format_args args) { + vprint(stdout, format_str, args); +} + +FMT_END_NAMESPACE + +#endif // FMT_FORMAT_INL_H_ diff --git a/libs/fmt/include/fmt/format.h b/libs/fmt/include/fmt/format.h new file mode 100644 index 0000000000000000000000000000000000000000..83dee9cb84ccb67bf793991ed1cf7e9fb1818007 --- /dev/null +++ b/libs/fmt/include/fmt/format.h @@ -0,0 +1,3481 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - present, Victor Zverovich + + 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. + + --- Optional exception to the license --- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. + */ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#include <cerrno> // errno +#include <cmath> // std::signbit +#include <cstdint> +#include <cwchar> +#include <limits> // std::numeric_limits +#include <memory> // std::uninitialized_copy +#include <stdexcept> // std::runtime_error +#include <utility> // std::swap + +#include "core.h" + +#ifdef __INTEL_COMPILER +# define FMT_ICC_VERSION __INTEL_COMPILER +#elif defined(__ICL) +# define FMT_ICC_VERSION __ICL +#else +# define FMT_ICC_VERSION 0 +#endif + +#ifdef __NVCC__ +# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) +#else +# define FMT_CUDA_VERSION 0 +#endif + +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_NOINLINE __attribute__((noinline)) +#else +# define FMT_NOINLINE +#endif + +#if FMT_MSC_VER +# define FMT_MSC_DEFAULT = default +#else +# define FMT_MSC_DEFAULT +#endif + +#if __cplusplus == 201103L || __cplusplus == 201402L +# if defined(__INTEL_COMPILER) || defined(__PGI) +# define FMT_FALLTHROUGH +# elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +# elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +# else +# define FMT_FALLTHROUGH +# endif +#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \ + (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define FMT_FALLTHROUGH [[fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +#ifndef FMT_THROW +# if FMT_EXCEPTIONS +# if FMT_MSC_VER || FMT_NVCC +FMT_BEGIN_NAMESPACE +namespace detail { +template <typename Exception> inline void do_throw(const Exception& x) { + // Silence unreachable code warnings in MSVC and NVCC because these + // are nearly impossible to fix in a generic code. + volatile bool b = true; + if (b) throw x; +} +} // namespace detail +FMT_END_NAMESPACE +# define FMT_THROW(x) detail::do_throw(x) +# else +# define FMT_THROW(x) throw x +# endif +# else +# define FMT_THROW(x) \ + do { \ + FMT_ASSERT(false, (x).what()); \ + } while (false) +# endif +#endif + +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifndef FMT_USE_USER_DEFINED_LITERALS +// EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. +# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ + FMT_MSC_VER >= 1900) && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) +# define FMT_USE_USER_DEFINED_LITERALS 1 +# else +# define FMT_USE_USER_DEFINED_LITERALS 0 +# endif +#endif + +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif + +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif + +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 +#endif + +// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of +// integer formatter template instantiations to just one by only using the +// largest integer type. This results in a reduction in binary size but will +// cause a decrease in integer formatting performance. +#if !defined(FMT_REDUCE_INT_INSTANTIATIONS) +# define FMT_REDUCE_INT_INSTANTIATIONS 0 +#endif + +// __builtin_clz is broken in clang with Microsoft CodeGen: +// https://github.com/fmtlib/fmt/issues/519 +#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +#endif +#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll)) && !FMT_MSC_VER +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +#endif +#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz)) +# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) +#endif +#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctzll)) +# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) +#endif + +#if FMT_MSC_VER +# include <intrin.h> // _BitScanReverse[64], _BitScanForward[64], _umul128 +#endif + +// Some compilers masquerade as both MSVC and GCC-likes or otherwise support +// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the +// MSVC intrinsics if the clz and clzll builtins are not available. +#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(FMT_BUILTIN_CTZLL) +FMT_BEGIN_NAMESPACE +namespace detail { +// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. +# if !defined(__clang__) +# pragma managed(push, off) +# pragma intrinsic(_BitScanForward) +# pragma intrinsic(_BitScanReverse) +# if defined(_WIN64) +# pragma intrinsic(_BitScanForward64) +# pragma intrinsic(_BitScanReverse64) +# endif +# endif + +inline int clz(uint32_t x) { + unsigned long r = 0; + _BitScanReverse(&r, x); + FMT_ASSERT(x != 0, ""); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. + FMT_MSC_WARNING(suppress : 6102) + return 31 ^ static_cast<int>(r); +} +# define FMT_BUILTIN_CLZ(n) detail::clz(n) + +inline int clzll(uint64_t x) { + unsigned long r = 0; +# ifdef _WIN64 + _BitScanReverse64(&r, x); +# else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) return 63 ^ (r + 32); + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast<uint32_t>(x)); +# endif + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + return 63 ^ static_cast<int>(r); +} +# define FMT_BUILTIN_CLZLL(n) detail::clzll(n) + +inline int ctz(uint32_t x) { + unsigned long r = 0; + _BitScanForward(&r, x); + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + return static_cast<int>(r); +} +# define FMT_BUILTIN_CTZ(n) detail::ctz(n) + +inline int ctzll(uint64_t x) { + unsigned long r = 0; + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. +# ifdef _WIN64 + _BitScanForward64(&r, x); +# else + // Scan the low 32 bits. + if (_BitScanForward(&r, static_cast<uint32_t>(x))) return static_cast<int>(r); + // Scan the high 32 bits. + _BitScanForward(&r, static_cast<uint32_t>(x >> 32)); + r += 32; +# endif + return static_cast<int>(r); +} +# define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) +# if !defined(__clang__) +# pragma managed(pop) +# endif +} // namespace detail +FMT_END_NAMESPACE +#endif + +#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +# if defined(__cpp_nontype_template_args) && \ + ((FMT_GCC_VERSION >= 903 && __cplusplus >= 201709L) || \ + __cpp_nontype_template_args >= 201911L) +# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 1 +# else +# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0 +# endif +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +#if __cplusplus >= 202002L || \ + (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002) +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEXPR20 +#endif + +// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have +// undefined behavior (e.g. due to type aliasing). +// Example: uint64_t d = bit_cast<uint64_t>(2.718); +template <typename Dest, typename Source> +inline Dest bit_cast(const Source& source) { + static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); + Dest dest; + std::memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +inline bool is_big_endian() { + const auto u = 1u; + struct bytes { + char data[sizeof(u)]; + }; + return bit_cast<bytes>(u).data[0] == 0; +} + +// A fallback implementation of uintptr_t for systems that lack it. +struct fallback_uintptr { + unsigned char value[sizeof(void*)]; + + fallback_uintptr() = default; + explicit fallback_uintptr(const void* p) { + *this = bit_cast<fallback_uintptr>(p); + if (is_big_endian()) { + for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j) + std::swap(value[i], value[j]); + } + } +}; +#ifdef UINTPTR_MAX +using uintptr_t = ::uintptr_t; +inline uintptr_t to_uintptr(const void* p) { return bit_cast<uintptr_t>(p); } +#else +using uintptr_t = fallback_uintptr; +inline fallback_uintptr to_uintptr(const void* p) { + return fallback_uintptr(p); +} +#endif + +// Returns the largest possible value for type T. Same as +// std::numeric_limits<T>::max() but shorter and not affected by the max macro. +template <typename T> constexpr T max_value() { + return (std::numeric_limits<T>::max)(); +} +template <typename T> constexpr int num_bits() { + return std::numeric_limits<T>::digits; +} +// std::numeric_limits<T>::digits may return 0 for 128-bit ints. +template <> constexpr int num_bits<int128_t>() { return 128; } +template <> constexpr int num_bits<uint128_t>() { return 128; } +template <> constexpr int num_bits<fallback_uintptr>() { + return static_cast<int>(sizeof(void*) * + std::numeric_limits<unsigned char>::digits); +} + +FMT_INLINE void assume(bool condition) { + (void)condition; +#if FMT_HAS_BUILTIN(__builtin_assume) + __builtin_assume(condition); +#endif +} + +// An approximation of iterator_t for pre-C++20 systems. +template <typename T> +using iterator_t = decltype(std::begin(std::declval<T&>())); +template <typename T> using sentinel_t = decltype(std::end(std::declval<T&>())); + +// A workaround for std::string not having mutable data() until C++17. +template <typename Char> inline Char* get_data(std::basic_string<Char>& s) { + return &s[0]; +} +template <typename Container> +inline typename Container::value_type* get_data(Container& c) { + return c.data(); +} + +#if defined(_SECURE_SCL) && _SECURE_SCL +// Make a checked iterator to avoid MSVC warnings. +template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>; +template <typename T> checked_ptr<T> make_checked(T* p, size_t size) { + return {p, size}; +} +#else +template <typename T> using checked_ptr = T*; +template <typename T> inline T* make_checked(T* p, size_t) { return p; } +#endif + +template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)> +#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION +__attribute__((no_sanitize("undefined"))) +#endif +inline checked_ptr<typename Container::value_type> +reserve(std::back_insert_iterator<Container> it, size_t n) { + Container& c = get_container(it); + size_t size = c.size(); + c.resize(size + n); + return make_checked(get_data(c) + size, n); +} + +template <typename T> +inline buffer_appender<T> reserve(buffer_appender<T> it, size_t n) { + buffer<T>& buf = get_container(it); + buf.try_reserve(buf.size() + n); + return it; +} + +template <typename Iterator> constexpr Iterator& reserve(Iterator& it, size_t) { + return it; +} + +template <typename OutputIt> +using reserve_iterator = + remove_reference_t<decltype(reserve(std::declval<OutputIt&>(), 0))>; + +template <typename T, typename OutputIt> +constexpr T* to_pointer(OutputIt, size_t) { + return nullptr; +} +template <typename T> T* to_pointer(buffer_appender<T> it, size_t n) { + buffer<T>& buf = get_container(it); + auto size = buf.size(); + if (buf.capacity() < size + n) return nullptr; + buf.try_resize(size + n); + return buf.data() + size; +} + +template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)> +inline std::back_insert_iterator<Container> base_iterator( + std::back_insert_iterator<Container>& it, + checked_ptr<typename Container::value_type>) { + return it; +} + +template <typename Iterator> +constexpr Iterator base_iterator(Iterator, Iterator it) { + return it; +} + +// <algorithm> is spectacularly slow to compile in C++20 so use a simple fill_n +// instead (#1998). +template <typename OutputIt, typename Size, typename T> +FMT_CONSTEXPR OutputIt fill_n(OutputIt out, Size count, const T& value) { + for (Size i = 0; i < count; ++i) *out++ = value; + return out; +} +template <typename T, typename Size> +FMT_CONSTEXPR20 T* fill_n(T* out, Size count, char value) { + if (is_constant_evaluated()) { + return fill_n<T*, Size, T>(out, count, value); + } + std::memset(out, value, to_unsigned(count)); + return out + count; +} + +#ifdef __cpp_char8_t +using char8_type = char8_t; +#else +enum char8_type : unsigned char {}; +#endif + +template <typename InputIt, typename OutChar> +using needs_conversion = bool_constant< + std::is_same<typename std::iterator_traits<InputIt>::value_type, + char>::value && + std::is_same<OutChar, char8_type>::value>; + +template <typename OutChar, typename InputIt, typename OutputIt, + FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)> +FMT_CONSTEXPR OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { + while (begin != end) *it++ = *begin++; + return it; +} + +template <typename OutChar, typename InputIt, + FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)> +FMT_CONSTEXPR20 OutChar* copy_str(InputIt begin, InputIt end, OutChar* out) { + if (is_constant_evaluated()) { + return copy_str<OutChar, InputIt, OutChar*>(begin, end, out); + } + auto size = to_unsigned(end - begin); + std::uninitialized_copy(begin, end, make_checked(out, size)); + return out + size; +} + +template <typename OutChar, typename InputIt, typename OutputIt, + FMT_ENABLE_IF(needs_conversion<InputIt, OutChar>::value)> +OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { + while (begin != end) *it++ = static_cast<char8_type>(*begin++); + return it; +} + +template <typename OutChar, typename InputIt, + FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)> +buffer_appender<OutChar> copy_str(InputIt begin, InputIt end, + buffer_appender<OutChar> out) { + get_container(out).append(begin, end); + return out; +} + +// A public domain branchless UTF-8 decoder by Christopher Wellons: +// https://github.com/skeeto/branchless-utf8 +/* Decode the next character, c, from s, reporting errors in e. + * + * Since this is a branchless decoder, four bytes will be read from the + * buffer regardless of the actual length of the next character. This + * means the buffer _must_ have at least three bytes of zero padding + * following the end of the data stream. + * + * Errors are reported in e, which will be non-zero if the parsed + * character was somehow invalid: invalid byte sequence, non-canonical + * encoding, or a surrogate half. + * + * The function returns a pointer to the next character. When an error + * occurs, this pointer will be a guess that depends on the particular + * error, but it will always advance at least one byte. + */ +FMT_CONSTEXPR inline const char* utf8_decode(const char* s, uint32_t* c, + int* e) { + constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + constexpr const int shiftc[] = {0, 18, 12, 6, 0}; + constexpr const int shifte[] = {0, 6, 4, 2, 0}; + + int len = code_point_length(s); + const char* next = s + len; + + // Assume a four-byte character and load four bytes. Unused bits are + // shifted out. + *c = uint32_t(s[0] & masks[len]) << 18; + *c |= uint32_t(s[1] & 0x3f) << 12; + *c |= uint32_t(s[2] & 0x3f) << 6; + *c |= uint32_t(s[3] & 0x3f) << 0; + *c >>= shiftc[len]; + + // Accumulate the various error conditions. + using uchar = unsigned char; + *e = (*c < mins[len]) << 6; // non-canonical encoding + *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? + *e |= (*c > 0x10FFFF) << 8; // out of range? + *e |= (uchar(s[1]) & 0xc0) >> 2; + *e |= (uchar(s[2]) & 0xc0) >> 4; + *e |= uchar(s[3]) >> 6; + *e ^= 0x2a; // top two bits of each tail byte correct? + *e >>= shifte[len]; + + return next; +} + +template <typename F> +FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { + auto decode = [f](const char* p) { + auto cp = uint32_t(); + auto error = 0; + p = utf8_decode(p, &cp, &error); + f(cp, error); + return p; + }; + auto p = s.data(); + const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. + if (s.size() >= block_size) { + for (auto end = p + s.size() - block_size + 1; p < end;) p = decode(p); + } + if (auto num_chars_left = s.data() + s.size() - p) { + char buf[2 * block_size - 1] = {}; + copy_str<char>(p, p + num_chars_left, buf); + p = buf; + do { + p = decode(p); + } while (p - buf < num_chars_left); + } +} + +template <typename Char> +inline size_t compute_width(basic_string_view<Char> s) { + return s.size(); +} + +// Computes approximate display width of a UTF-8 string. +FMT_CONSTEXPR inline size_t compute_width(string_view s) { + size_t num_code_points = 0; + // It is not a lambda for compatibility with C++14. + struct count_code_points { + size_t* count; + FMT_CONSTEXPR void operator()(uint32_t cp, int error) const { + *count += + 1 + + (error == 0 && cp >= 0x1100 && + (cp <= 0x115f || // Hangul Jamo init. consonants + cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET〈 + cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET 〉 + // CJK ... Yi except Unicode Character “〿”: + (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || + (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables + (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs + (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms + (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms + (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms + (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms + (cp >= 0x20000 && cp <= 0x2fffd) || // CJK + (cp >= 0x30000 && cp <= 0x3fffd) || + // Miscellaneous Symbols and Pictographs + Emoticons: + (cp >= 0x1f300 && cp <= 0x1f64f) || + // Supplemental Symbols and Pictographs: + (cp >= 0x1f900 && cp <= 0x1f9ff))); + } + }; + for_each_codepoint(s, count_code_points{&num_code_points}); + return num_code_points; +} + +inline size_t compute_width(basic_string_view<char8_type> s) { + return compute_width(basic_string_view<char>( + reinterpret_cast<const char*>(s.data()), s.size())); +} + +template <typename Char> +inline size_t code_point_index(basic_string_view<Char> s, size_t n) { + size_t size = s.size(); + return n < size ? n : size; +} + +// Calculates the index of the nth code point in a UTF-8 string. +inline size_t code_point_index(basic_string_view<char8_type> s, size_t n) { + const char8_type* data = s.data(); + size_t num_code_points = 0; + for (size_t i = 0, size = s.size(); i != size; ++i) { + if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i; + } + return s.size(); +} + +template <typename T> +using is_fast_float = bool_constant<std::numeric_limits<T>::is_iec559 && + sizeof(T) <= sizeof(double)>; + +#ifndef FMT_USE_FULL_CACHE_DRAGONBOX +# define FMT_USE_FULL_CACHE_DRAGONBOX 0 +#endif + +template <typename T> +template <typename U> +void buffer<T>::append(const U* begin, const U* end) { + while (begin != end) { + auto count = to_unsigned(end - begin); + try_reserve(size_ + count); + auto free_cap = capacity_ - size_; + if (free_cap < count) count = free_cap; + std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count)); + size_ += count; + begin += count; + } +} + +template <typename OutputIt, typename T, typename Traits> +void iterator_buffer<OutputIt, T, Traits>::flush() { + auto size = this->size(); + this->clear(); + out_ = copy_str<T>(data_, data_ + this->limit(size), out_); +} +} // namespace detail + +FMT_MODULE_EXPORT_BEGIN + +template <> struct is_char<detail::char8_type> : std::true_type {}; +template <> struct is_char<char16_t> : std::true_type {}; +template <> struct is_char<char32_t> : std::true_type {}; + +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum { inline_buffer_size = 500 }; + +/** + \rst + A dynamically growing memory buffer for trivially copyable/constructible types + with the first ``SIZE`` elements stored in the object itself. + + You can use one of the following type aliases for common character types: + + +----------------+------------------------------+ + | Type | Definition | + +================+==============================+ + | memory_buffer | basic_memory_buffer<char> | + +----------------+------------------------------+ + | wmemory_buffer | basic_memory_buffer<wchar_t> | + +----------------+------------------------------+ + + **Example**:: + + fmt::memory_buffer out; + format_to(out, "The answer is {}.", 42); + + This will append the following output to the ``out`` object: + + .. code-block:: none + + The answer is 42. + + The output can be converted to an ``std::string`` with ``to_string(out)``. + \endrst + */ +template <typename T, size_t SIZE = inline_buffer_size, + typename Allocator = std::allocator<T>> +class basic_memory_buffer final : public detail::buffer<T> { + private: + T store_[SIZE]; + + // Don't inherit from Allocator avoid generating type_info for it. + Allocator alloc_; + + // Deallocate memory allocated by the buffer. + void deallocate() { + T* data = this->data(); + if (data != store_) alloc_.deallocate(data, this->capacity()); + } + + protected: + void grow(size_t size) final FMT_OVERRIDE; + + public: + using value_type = T; + using const_reference = const T&; + + explicit basic_memory_buffer(const Allocator& alloc = Allocator()) + : alloc_(alloc) { + this->set(store_, SIZE); + } + ~basic_memory_buffer() { deallocate(); } + + private: + // Move data from other to this buffer. + void move(basic_memory_buffer& other) { + alloc_ = std::move(other.alloc_); + T* data = other.data(); + size_t size = other.size(), capacity = other.capacity(); + if (data == other.store_) { + this->set(store_, capacity); + std::uninitialized_copy(other.store_, other.store_ + size, + detail::make_checked(store_, capacity)); + } else { + this->set(data, capacity); + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.set(other.store_, 0); + } + this->resize(size); + } + + public: + /** + \rst + Constructs a :class:`fmt::basic_memory_buffer` object moving the content + of the other object to it. + \endrst + */ + basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); } + + /** + \rst + Moves the content of the other ``basic_memory_buffer`` object to this one. + \endrst + */ + basic_memory_buffer& operator=(basic_memory_buffer&& other) FMT_NOEXCEPT { + FMT_ASSERT(this != &other, ""); + deallocate(); + move(other); + return *this; + } + + // Returns a copy of the allocator associated with this buffer. + Allocator get_allocator() const { return alloc_; } + + /** + Resizes the buffer to contain *count* elements. If T is a POD type new + elements may not be initialized. + */ + void resize(size_t count) { this->try_resize(count); } + + /** Increases the buffer capacity to *new_capacity*. */ + void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } + + // Directly append data into the buffer + using detail::buffer<T>::append; + template <typename ContiguousRange> + void append(const ContiguousRange& range) { + append(range.data(), range.data() + range.size()); + } +}; + +template <typename T, size_t SIZE, typename Allocator> +void basic_memory_buffer<T, SIZE, Allocator>::grow(size_t size) { +#ifdef FMT_FUZZ + if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much"); +#endif + const size_t max_size = std::allocator_traits<Allocator>::max_size(alloc_); + size_t old_capacity = this->capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = size > max_size ? size : max_size; + T* old_data = this->data(); + T* new_data = + std::allocator_traits<Allocator>::allocate(alloc_, new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(old_data, old_data + this->size(), + detail::make_checked(new_data, new_capacity)); + this->set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != store_) alloc_.deallocate(old_data, old_capacity); +} + +using memory_buffer = basic_memory_buffer<char>; +using wmemory_buffer = basic_memory_buffer<wchar_t>; + +template <typename T, size_t SIZE, typename Allocator> +struct is_contiguous<basic_memory_buffer<T, SIZE, Allocator>> : std::true_type { +}; + +/** A formatting error such as invalid format string. */ +FMT_CLASS_API +class FMT_API format_error : public std::runtime_error { + public: + explicit format_error(const char* message) : std::runtime_error(message) {} + explicit format_error(const std::string& message) + : std::runtime_error(message) {} + format_error(const format_error&) = default; + format_error& operator=(const format_error&) = default; + format_error(format_error&&) = default; + format_error& operator=(format_error&&) = default; + ~format_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT; +}; + +FMT_MODULE_EXPORT_END +namespace detail { + +template <typename T> +using is_signed = + std::integral_constant<bool, std::numeric_limits<T>::is_signed || + std::is_same<T, int128_t>::value>; + +// Returns true if value is negative, false otherwise. +// Same as `value < 0` but doesn't produce warnings if T is an unsigned type. +template <typename T, FMT_ENABLE_IF(is_signed<T>::value)> +FMT_CONSTEXPR bool is_negative(T value) { + return value < 0; +} +template <typename T, FMT_ENABLE_IF(!is_signed<T>::value)> +FMT_CONSTEXPR bool is_negative(T) { + return false; +} + +template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> +FMT_CONSTEXPR bool is_supported_floating_point(T) { + return (std::is_same<T, float>::value && FMT_USE_FLOAT) || + (std::is_same<T, double>::value && FMT_USE_DOUBLE) || + (std::is_same<T, long double>::value && FMT_USE_LONG_DOUBLE); +} + +// Smallest of uint32_t, uint64_t, uint128_t that is large enough to +// represent all values of an integral type T. +template <typename T> +using uint32_or_64_or_128_t = + conditional_t<num_bits<T>() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, + uint32_t, + conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>>; +template <typename T> +using uint64_or_128_t = conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ + (factor)*1000000, (factor)*10000000, (factor)*100000000, \ + (factor)*1000000000 + +// Static data is placed in this class template for the header-only config. +template <typename T = void> struct basic_data { + // log10(2) = 0x0.4d104d427de7fbcc... + static const uint64_t log10_2_significand = 0x4d104d427de7fbcc; + + // GCC generates slightly better code for pairs than chars. + // using digit_pair = char; + static constexpr const char digits[][2] = { + {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, + {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, + {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, + {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, + {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, + {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, + {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, + {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, + {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, + {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, + {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, + {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, + {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, + {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, + {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, + {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, + {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; + + static constexpr const char hex_digits[] = "0123456789abcdef"; + static constexpr const char signs[] = {0, '-', '+', ' '}; + static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', + 0x1000000u | ' '}; + static constexpr const char left_padding_shifts[] = {31, 31, 0, 1, 0}; + static constexpr const char right_padding_shifts[] = {0, 31, 0, 1, 0}; +}; + +// This is a struct rather than an alias to avoid shadowing warnings in gcc. +struct data : basic_data<> {}; + +// Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). +// This is a function instead of an array to workaround a bug in GCC10 (#1810). +FMT_INLINE uint16_t bsr2log10(int bsr) { + static constexpr uint16_t data[] = { + 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, + 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; + return data[bsr]; +} + +template <typename T> FMT_CONSTEXPR int count_digits_fallback(T n) { + int count = 1; + for (;;) { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#if FMT_USE_INT128 +FMT_CONSTEXPR inline int count_digits(uint128_t n) { + return count_digits_fallback(n); +} +#endif + +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +FMT_CONSTEXPR20 inline int count_digits(uint64_t n) { + if (is_constant_evaluated()) { + return count_digits_fallback(n); + } +#ifdef FMT_BUILTIN_CLZLL + // https://github.com/fmtlib/format-benchmark/blob/master/digits10 + auto t = bsr2log10(FMT_BUILTIN_CLZLL(n | 1) ^ 63); + constexpr const uint64_t zero_or_powers_of_10[] = { + 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; + return t - (n < zero_or_powers_of_10[t]); +#else + return count_digits_fallback(n); +#endif +} + +// Counts the number of digits in n. BITS = log2(radix). +template <int BITS, typename UInt> FMT_CONSTEXPR int count_digits(UInt n) { +#ifdef FMT_BUILTIN_CLZ + if (num_bits<UInt>() == 32) + return (FMT_BUILTIN_CLZ(static_cast<uint32_t>(n) | 1) ^ 31) / BITS + 1; +#endif + int num_digits = 0; + do { + ++num_digits; + } while ((n >>= BITS) != 0); + return num_digits; +} + +template <> int count_digits<4>(detail::fallback_uintptr n); + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) +#elif FMT_MSC_VER +# define FMT_ALWAYS_INLINE __forceinline +#else +# define FMT_ALWAYS_INLINE inline +#endif + +#ifdef FMT_BUILTIN_CLZ +// Optional version of count_digits for better performance on 32-bit platforms. +FMT_CONSTEXPR20 inline int count_digits(uint32_t n) { + if (is_constant_evaluated()) { + return count_digits_fallback(n); + } + auto t = bsr2log10(FMT_BUILTIN_CLZ(n | 1) ^ 31); + constexpr const uint32_t zero_or_powers_of_10[] = {0, 0, + FMT_POWERS_OF_10(1U)}; + return t - (n < zero_or_powers_of_10[t]); +} +#endif + +template <typename Int> constexpr int digits10() FMT_NOEXCEPT { + return std::numeric_limits<Int>::digits10; +} +template <> constexpr int digits10<int128_t>() FMT_NOEXCEPT { return 38; } +template <> constexpr int digits10<uint128_t>() FMT_NOEXCEPT { return 38; } + +// DEPRECATED! grouping will be merged into thousands_sep. +template <typename Char> FMT_API std::string grouping_impl(locale_ref loc); +template <typename Char> inline std::string grouping(locale_ref loc) { + return grouping_impl<char>(loc); +} +template <> inline std::string grouping<wchar_t>(locale_ref loc) { + return grouping_impl<wchar_t>(loc); +} + +template <typename Char> FMT_API Char thousands_sep_impl(locale_ref loc); +template <typename Char> inline Char thousands_sep(locale_ref loc) { + return Char(thousands_sep_impl<char>(loc)); +} +template <> inline wchar_t thousands_sep(locale_ref loc) { + return thousands_sep_impl<wchar_t>(loc); +} + +template <typename Char> FMT_API Char decimal_point_impl(locale_ref loc); +template <typename Char> inline Char decimal_point(locale_ref loc) { + return Char(decimal_point_impl<char>(loc)); +} +template <> inline wchar_t decimal_point(locale_ref loc) { + return decimal_point_impl<wchar_t>(loc); +} + +// Compares two characters for equality. +template <typename Char> bool equal2(const Char* lhs, const char* rhs) { + return lhs[0] == rhs[0] && lhs[1] == rhs[1]; +} +inline bool equal2(const char* lhs, const char* rhs) { + return memcmp(lhs, rhs, 2) == 0; +} + +// Copies two characters from src to dst. +template <typename Char> void copy2(Char* dst, const char* src) { + *dst++ = static_cast<Char>(*src++); + *dst = static_cast<Char>(*src); +} +FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } + +template <typename Iterator> struct format_decimal_result { + Iterator begin; + Iterator end; +}; + +// Formats a decimal unsigned integer value writing into out pointing to a +// buffer of specified size. The caller must ensure that the buffer is large +// enough. +template <typename Char, typename UInt> +FMT_CONSTEXPR20 format_decimal_result<Char*> format_decimal(Char* out, + UInt value, + int size) { + FMT_ASSERT(size >= count_digits(value), "invalid digit count"); + out += size; + Char* end = out; + if (is_constant_evaluated()) { + while (value >= 10) { + *--out = static_cast<Char>('0' + value % 10); + value /= 10; + } + *--out = static_cast<Char>('0' + value); + return {out, end}; + } + while (value >= 100) { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + out -= 2; + copy2(out, data::digits[value % 100]); + value /= 100; + } + if (value < 10) { + *--out = static_cast<Char>('0' + value); + return {out, end}; + } + out -= 2; + copy2(out, data::digits[value]); + return {out, end}; +} + +template <typename Char, typename UInt, typename Iterator, + FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<Iterator>>::value)> +inline format_decimal_result<Iterator> format_decimal(Iterator out, UInt value, + int size) { + // Buffer is large enough to hold all digits (digits10 + 1). + Char buffer[digits10<UInt>() + 1]; + auto end = format_decimal(buffer, value, size).end; + return {out, detail::copy_str<Char>(buffer, end, out)}; +} + +template <unsigned BASE_BITS, typename Char, typename UInt> +FMT_CONSTEXPR Char* format_uint(Char* buffer, UInt value, int num_digits, + bool upper = false) { + buffer += num_digits; + Char* end = buffer; + do { + const char* digits = upper ? "0123456789ABCDEF" : data::hex_digits; + unsigned digit = (value & ((1 << BASE_BITS) - 1)); + *--buffer = static_cast<Char>(BASE_BITS < 4 ? static_cast<char>('0' + digit) + : digits[digit]); + } while ((value >>= BASE_BITS) != 0); + return end; +} + +template <unsigned BASE_BITS, typename Char> +Char* format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, + bool = false) { + auto char_digits = std::numeric_limits<unsigned char>::digits / 4; + int start = (num_digits + char_digits - 1) / char_digits - 1; + if (int start_digits = num_digits % char_digits) { + unsigned value = n.value[start--]; + buffer = format_uint<BASE_BITS>(buffer, value, start_digits); + } + for (; start >= 0; --start) { + unsigned value = n.value[start]; + buffer += char_digits; + auto p = buffer; + for (int i = 0; i < char_digits; ++i) { + unsigned digit = (value & ((1 << BASE_BITS) - 1)); + *--p = static_cast<Char>(data::hex_digits[digit]); + value >>= BASE_BITS; + } + } + return buffer; +} + +template <unsigned BASE_BITS, typename Char, typename It, typename UInt> +inline It format_uint(It out, UInt value, int num_digits, bool upper = false) { + if (auto ptr = to_pointer<Char>(out, to_unsigned(num_digits))) { + format_uint<BASE_BITS>(ptr, value, num_digits, upper); + return out; + } + // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). + char buffer[num_bits<UInt>() / BASE_BITS + 1]; + format_uint<BASE_BITS>(buffer, value, num_digits, upper); + return detail::copy_str<Char>(buffer, buffer + num_digits, out); +} + +// A converter from UTF-8 to UTF-16. +class utf8_to_utf16 { + private: + wmemory_buffer buffer_; + + public: + FMT_API explicit utf8_to_utf16(string_view s); + operator wstring_view() const { return {&buffer_[0], size()}; } + size_t size() const { return buffer_.size() - 1; } + const wchar_t* c_str() const { return &buffer_[0]; } + std::wstring str() const { return {&buffer_[0], size()}; } +}; + +template <typename T = void> struct null {}; + +// Workaround an array initialization issue in gcc 4.8. +template <typename Char> struct fill_t { + private: + enum { max_size = 4 }; + Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)}; + unsigned char size_ = 1; + + public: + FMT_CONSTEXPR void operator=(basic_string_view<Char> s) { + auto size = s.size(); + if (size > max_size) { + FMT_THROW(format_error("invalid fill")); + return; + } + for (size_t i = 0; i < size; ++i) data_[i] = s[i]; + size_ = static_cast<unsigned char>(size); + } + + constexpr size_t size() const { return size_; } + constexpr const Char* data() const { return data_; } + + FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; } + FMT_CONSTEXPR const Char& operator[](size_t index) const { + return data_[index]; + } +}; +} // namespace detail +FMT_MODULE_EXPORT_BEGIN + +namespace sign { +enum type { none, minus, plus, space }; +} +using sign_t = sign::type; + +// Format specifiers for built-in and string types. +template <typename Char> struct basic_format_specs { + int width; + int precision; + char type; + align_t align : 4; + sign_t sign : 3; + bool alt : 1; // Alternate form ('#'). + bool localized : 1; + detail::fill_t<Char> fill; + + constexpr basic_format_specs() + : width(0), + precision(-1), + type(0), + align(align::none), + sign(sign::none), + alt(false), + localized(false) {} +}; + +using format_specs = basic_format_specs<char>; + +FMT_MODULE_EXPORT_END +namespace detail { +namespace dragonbox { + +// Type-specific information that Dragonbox uses. +template <class T> struct float_info; + +template <> struct float_info<float> { + using carrier_uint = uint32_t; + static const int significand_bits = 23; + static const int exponent_bits = 8; + static const int min_exponent = -126; + static const int max_exponent = 127; + static const int exponent_bias = -127; + static const int decimal_digits = 9; + static const int kappa = 1; + static const int big_divisor = 100; + static const int small_divisor = 10; + static const int min_k = -31; + static const int max_k = 46; + static const int cache_bits = 64; + static const int divisibility_check_by_5_threshold = 39; + static const int case_fc_pm_half_lower_threshold = -1; + static const int case_fc_pm_half_upper_threshold = 6; + static const int case_fc_lower_threshold = -2; + static const int case_fc_upper_threshold = 6; + static const int case_shorter_interval_left_endpoint_lower_threshold = 2; + static const int case_shorter_interval_left_endpoint_upper_threshold = 3; + static const int shorter_interval_tie_lower_threshold = -35; + static const int shorter_interval_tie_upper_threshold = -35; + static const int max_trailing_zeros = 7; +}; + +template <> struct float_info<double> { + using carrier_uint = uint64_t; + static const int significand_bits = 52; + static const int exponent_bits = 11; + static const int min_exponent = -1022; + static const int max_exponent = 1023; + static const int exponent_bias = -1023; + static const int decimal_digits = 17; + static const int kappa = 2; + static const int big_divisor = 1000; + static const int small_divisor = 100; + static const int min_k = -292; + static const int max_k = 326; + static const int cache_bits = 128; + static const int divisibility_check_by_5_threshold = 86; + static const int case_fc_pm_half_lower_threshold = -2; + static const int case_fc_pm_half_upper_threshold = 9; + static const int case_fc_lower_threshold = -4; + static const int case_fc_upper_threshold = 9; + static const int case_shorter_interval_left_endpoint_lower_threshold = 2; + static const int case_shorter_interval_left_endpoint_upper_threshold = 3; + static const int shorter_interval_tie_lower_threshold = -77; + static const int shorter_interval_tie_upper_threshold = -77; + static const int max_trailing_zeros = 16; +}; + +template <typename T> struct decimal_fp { + using significand_type = typename float_info<T>::carrier_uint; + significand_type significand; + int exponent; +}; + +template <typename T> FMT_API decimal_fp<T> to_decimal(T x) FMT_NOEXCEPT; +} // namespace dragonbox + +template <typename T> +constexpr typename dragonbox::float_info<T>::carrier_uint exponent_mask() { + using uint = typename dragonbox::float_info<T>::carrier_uint; + return ((uint(1) << dragonbox::float_info<T>::exponent_bits) - 1) + << dragonbox::float_info<T>::significand_bits; +} + +// A floating-point presentation format. +enum class float_format : unsigned char { + general, // General: exponent notation or fixed point based on magnitude. + exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. + fixed, // Fixed point with the default precision of 6, e.g. 0.0012. + hex +}; + +struct float_specs { + int precision; + float_format format : 8; + sign_t sign : 8; + bool upper : 1; + bool locale : 1; + bool binary32 : 1; + bool use_grisu : 1; + bool showpoint : 1; +}; + +// Writes the exponent exp in the form "[+-]d{2,3}" to buffer. +template <typename Char, typename It> It write_exponent(int exp, It it) { + FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); + if (exp < 0) { + *it++ = static_cast<Char>('-'); + exp = -exp; + } else { + *it++ = static_cast<Char>('+'); + } + if (exp >= 100) { + const char* top = data::digits[exp / 100]; + if (exp >= 1000) *it++ = static_cast<Char>(top[0]); + *it++ = static_cast<Char>(top[1]); + exp %= 100; + } + const char* d = data::digits[exp]; + *it++ = static_cast<Char>(d[0]); + *it++ = static_cast<Char>(d[1]); + return it; +} + +template <typename T> +int format_float(T value, int precision, float_specs specs, buffer<char>& buf); + +// Formats a floating-point number with snprintf. +template <typename T> +int snprintf_float(T value, int precision, float_specs specs, + buffer<char>& buf); + +template <typename T> T promote_float(T value) { return value; } +inline double promote_float(float value) { return static_cast<double>(value); } + +template <typename ErrorHandler = error_handler, typename Char> +FMT_CONSTEXPR float_specs parse_float_type_spec( + const basic_format_specs<Char>& specs, ErrorHandler&& eh = {}) { + auto result = float_specs(); + result.showpoint = specs.alt; + result.locale = specs.localized; + switch (specs.type) { + case 0: + result.format = float_format::general; + break; + case 'G': + result.upper = true; + FMT_FALLTHROUGH; + case 'g': + result.format = float_format::general; + break; + case 'E': + result.upper = true; + FMT_FALLTHROUGH; + case 'e': + result.format = float_format::exp; + result.showpoint |= specs.precision != 0; + break; + case 'F': + result.upper = true; + FMT_FALLTHROUGH; + case 'f': + result.format = float_format::fixed; + result.showpoint |= specs.precision != 0; + break; + case 'A': + result.upper = true; + FMT_FALLTHROUGH; + case 'a': + result.format = float_format::hex; + break; + default: + eh.on_error("invalid type specifier"); + break; + } + return result; +} + +template <typename ErrorHandler> +FMT_CONSTEXPR void check_int_type_spec(char spec, ErrorHandler&& eh) { + switch (spec) { + case 0: + case 'd': + case 'x': + case 'X': + case 'b': + case 'B': + case 'o': + case 'c': + break; + default: + eh.on_error("invalid type specifier"); + break; + } +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_char_specs(const basic_format_specs<Char>& specs, + Handler&& handler) { + if (specs.type && specs.type != 'c') return handler.on_int(); + if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) + handler.on_error("invalid format specifier for char"); + handler.on_char(); +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler&& handler) { + if (spec == 0 || spec == 's') + handler.on_string(); + else if (spec == 'p') + handler.on_pointer(); + else + handler.on_error("invalid type specifier"); +} + +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler&& eh) { + if (spec != 0 && spec != 's') eh.on_error("invalid type specifier"); +} + +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler&& eh) { + if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier"); +} + +template <typename ErrorHandler> +class char_specs_checker : public ErrorHandler { + private: + char type_; + + public: + FMT_CONSTEXPR char_specs_checker(char type, ErrorHandler eh) + : ErrorHandler(eh), type_(type) {} + + FMT_CONSTEXPR void on_int() { check_int_type_spec(type_, *this); } + FMT_CONSTEXPR void on_char() {} +}; + +template <typename ErrorHandler> +class cstring_type_checker : public ErrorHandler { + public: + FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh) + : ErrorHandler(eh) {} + + FMT_CONSTEXPR void on_string() {} + FMT_CONSTEXPR void on_pointer() {} +}; + +template <typename OutputIt, typename Char> +FMT_NOINLINE FMT_CONSTEXPR OutputIt fill(OutputIt it, size_t n, + const fill_t<Char>& fill) { + auto fill_size = fill.size(); + if (fill_size == 1) return detail::fill_n(it, n, fill[0]); + auto data = fill.data(); + for (size_t i = 0; i < n; ++i) + it = copy_str<Char>(data, data + fill_size, it); + return it; +} + +// Writes the output of f, padded according to format specifications in specs. +// size: output size in code units. +// width: output display width in (terminal) column positions. +template <align::type align = align::left, typename OutputIt, typename Char, + typename F> +FMT_CONSTEXPR OutputIt write_padded(OutputIt out, + const basic_format_specs<Char>& specs, + size_t size, size_t width, F&& f) { + static_assert(align == align::left || align == align::right, ""); + unsigned spec_width = to_unsigned(specs.width); + size_t padding = spec_width > width ? spec_width - width : 0; + auto* shifts = align == align::left ? data::left_padding_shifts + : data::right_padding_shifts; + size_t left_padding = padding >> shifts[specs.align]; + size_t right_padding = padding - left_padding; + auto it = reserve(out, size + padding * specs.fill.size()); + if (left_padding != 0) it = fill(it, left_padding, specs.fill); + it = f(it); + if (right_padding != 0) it = fill(it, right_padding, specs.fill); + return base_iterator(out, it); +} + +template <align::type align = align::left, typename OutputIt, typename Char, + typename F> +constexpr OutputIt write_padded(OutputIt out, + const basic_format_specs<Char>& specs, + size_t size, F&& f) { + return write_padded<align>(out, specs, size, size, f); +} + +template <typename Char, typename OutputIt> +OutputIt write_bytes(OutputIt out, string_view bytes, + const basic_format_specs<Char>& specs) { + return write_padded(out, specs, bytes.size(), + [bytes](reserve_iterator<OutputIt> it) { + const char* data = bytes.data(); + return copy_str<Char>(data, data + bytes.size(), it); + }); +} + +template <typename Char, typename OutputIt> +constexpr OutputIt write_char(OutputIt out, Char value, + const basic_format_specs<Char>& specs) { + return write_padded(out, specs, 1, [=](reserve_iterator<OutputIt> it) { + *it++ = value; + return it; + }); +} + +// Data for write_int that doesn't depend on output iterator type. It is used to +// avoid template code bloat. +template <typename Char> struct write_int_data { + size_t size; + size_t padding; + + FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, + const basic_format_specs<Char>& specs) + : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { + if (specs.align == align::numeric) { + auto width = to_unsigned(specs.width); + if (width > size) { + padding = width - size; + size = width; + } + } else if (specs.precision > num_digits) { + size = (prefix >> 24) + to_unsigned(specs.precision); + padding = to_unsigned(specs.precision - num_digits); + } + } +}; + +// Writes an integer in the format +// <left-padding><prefix><numeric-padding><digits><right-padding> +// where <digits> are written by write_digits(it). +// prefix contains chars in three lower bytes and the size in the fourth byte. +template <typename OutputIt, typename Char, typename W> +FMT_CONSTEXPR FMT_INLINE OutputIt +write_int(OutputIt out, int num_digits, unsigned prefix, + const basic_format_specs<Char>& specs, W write_digits) { + // Slightly faster check for specs.width == 0 && specs.precision == -1. + if ((specs.width | (specs.precision + 1)) == 0) { + auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); + if (prefix != 0) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast<Char>(p & 0xff); + } + return base_iterator(out, write_digits(it)); + } + auto data = write_int_data<Char>(num_digits, prefix, specs); + return write_padded<align::right>( + out, specs, data.size, [=](reserve_iterator<OutputIt> it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast<Char>(p & 0xff); + it = detail::fill_n(it, data.padding, static_cast<Char>('0')); + return write_digits(it); + }); +} + +template <typename OutputIt, typename UInt, typename Char> +bool write_int_localized(OutputIt& out, UInt value, unsigned prefix, + const basic_format_specs<Char>& specs, + locale_ref loc) { + static_assert(std::is_same<uint64_or_128_t<UInt>, UInt>::value, ""); + const auto sep_size = 1; + std::string groups = grouping<Char>(loc); + if (groups.empty()) return false; + auto sep = thousands_sep<Char>(loc); + if (!sep) return false; + int num_digits = count_digits(value); + int size = num_digits, n = num_digits; + std::string::const_iterator group = groups.cbegin(); + while (group != groups.cend() && n > *group && *group > 0 && + *group != max_value<char>()) { + size += sep_size; + n -= *group; + ++group; + } + if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); + char digits[40]; + format_decimal(digits, value, num_digits); + basic_memory_buffer<Char> buffer; + if (prefix != 0) ++size; + const auto usize = to_unsigned(size); + buffer.resize(usize); + basic_string_view<Char> s(&sep, sep_size); + // Index of a decimal digit with the least significant digit having index 0. + int digit_index = 0; + group = groups.cbegin(); + auto p = buffer.data() + size - 1; + for (int i = num_digits - 1; i > 0; --i) { + *p-- = static_cast<Char>(digits[i]); + if (*group <= 0 || ++digit_index % *group != 0 || + *group == max_value<char>()) + continue; + if (group + 1 != groups.cend()) { + digit_index = 0; + ++group; + } + std::uninitialized_copy(s.data(), s.data() + s.size(), + make_checked(p, s.size())); + p -= s.size(); + } + *p-- = static_cast<Char>(*digits); + if (prefix != 0) *p = static_cast<Char>(prefix); + auto data = buffer.data(); + out = write_padded<align::right>( + out, specs, usize, usize, [=](reserve_iterator<OutputIt> it) { + return copy_str<Char>(data, data + size, it); + }); + return true; +} + +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + +template <typename OutputIt, typename T, typename Char> +FMT_CONSTEXPR OutputIt write_int(OutputIt out, T value, + const basic_format_specs<Char>& specs, + locale_ref loc) { + auto prefix = 0u; + auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value); + if (is_negative(value)) { + prefix = 0x01000000 | '-'; + abs_value = 0 - abs_value; + } else { + prefix = data::prefixes[specs.sign]; + } + auto utype = static_cast<unsigned>(specs.type); + switch (specs.type) { + case 0: + case 'd': { + if (specs.localized && + write_int_localized(out, static_cast<uint64_or_128_t<T>>(abs_value), + prefix, specs, loc)) { + return out; + } + auto num_digits = count_digits(abs_value); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) { + return format_decimal<Char>(it, abs_value, num_digits).end; + }); + } + case 'x': + case 'X': { + if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); + bool upper = specs.type != 'x'; + int num_digits = count_digits<4>(abs_value); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) { + return format_uint<4, Char>(it, abs_value, num_digits, upper); + }); + } + case 'b': + case 'B': { + if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); + int num_digits = count_digits<1>(abs_value); + return write_int(out, num_digits, prefix, specs, + [=](reserve_iterator<OutputIt> it) { + return format_uint<1, Char>(it, abs_value, num_digits); + }); + } + case 'o': { + int num_digits = count_digits<3>(abs_value); + if (specs.alt && specs.precision <= num_digits && abs_value != 0) { + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + prefix_append(prefix, '0'); + } + return write_int(out, num_digits, prefix, specs, + [=](reserve_iterator<OutputIt> it) { + return format_uint<3, Char>(it, abs_value, num_digits); + }); + } + case 'c': + return write_char(out, static_cast<Char>(abs_value), specs); + default: + FMT_THROW(format_error("invalid type specifier")); + } + return out; +} + +template <typename OutputIt, typename StrChar, typename Char> +FMT_CONSTEXPR OutputIt write(OutputIt out, basic_string_view<StrChar> s, + const basic_format_specs<Char>& specs) { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = code_point_index(s, to_unsigned(specs.precision)); + auto width = specs.width != 0 + ? compute_width(basic_string_view<StrChar>(data, size)) + : 0; + return write_padded(out, specs, size, width, + [=](reserve_iterator<OutputIt> it) { + return copy_str<Char>(data, data + size, it); + }); +} + +template <typename Char, typename OutputIt> +OutputIt write_nonfinite(OutputIt out, bool isinf, + const basic_format_specs<Char>& specs, + const float_specs& fspecs) { + auto str = + isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); + constexpr size_t str_size = 3; + auto sign = fspecs.sign; + auto size = str_size + (sign ? 1 : 0); + return write_padded(out, specs, size, [=](reserve_iterator<OutputIt> it) { + if (sign) *it++ = static_cast<Char>(data::signs[sign]); + return copy_str<Char>(str, str + str_size, it); + }); +} + +// A decimal floating-point number significand * pow(10, exp). +struct big_decimal_fp { + const char* significand; + int significand_size; + int exponent; +}; + +inline int get_significand_size(const big_decimal_fp& fp) { + return fp.significand_size; +} +template <typename T> +inline int get_significand_size(const dragonbox::decimal_fp<T>& fp) { + return count_digits(fp.significand); +} + +template <typename Char, typename OutputIt> +inline OutputIt write_significand(OutputIt out, const char* significand, + int& significand_size) { + return copy_str<Char>(significand, significand + significand_size, out); +} +template <typename Char, typename OutputIt, typename UInt> +inline OutputIt write_significand(OutputIt out, UInt significand, + int significand_size) { + return format_decimal<Char>(out, significand, significand_size).end; +} + +template <typename Char, typename UInt, + FMT_ENABLE_IF(std::is_integral<UInt>::value)> +inline Char* write_significand(Char* out, UInt significand, + int significand_size, int integral_size, + Char decimal_point) { + if (!decimal_point) + return format_decimal(out, significand, significand_size).end; + auto end = format_decimal(out + 1, significand, significand_size).end; + if (integral_size == 1) { + out[0] = out[1]; + } else { + std::uninitialized_copy_n(out + 1, integral_size, + make_checked(out, to_unsigned(integral_size))); + } + out[integral_size] = decimal_point; + return end; +} + +template <typename OutputIt, typename UInt, typename Char, + FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<OutputIt>>::value)> +inline OutputIt write_significand(OutputIt out, UInt significand, + int significand_size, int integral_size, + Char decimal_point) { + // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. + Char buffer[digits10<UInt>() + 2]; + auto end = write_significand(buffer, significand, significand_size, + integral_size, decimal_point); + return detail::copy_str<Char>(buffer, end, out); +} + +template <typename OutputIt, typename Char> +inline OutputIt write_significand(OutputIt out, const char* significand, + int significand_size, int integral_size, + Char decimal_point) { + out = detail::copy_str<Char>(significand, significand + integral_size, out); + if (!decimal_point) return out; + *out++ = decimal_point; + return detail::copy_str<Char>(significand + integral_size, + significand + significand_size, out); +} + +template <typename OutputIt, typename DecimalFP, typename Char> +OutputIt write_float(OutputIt out, const DecimalFP& fp, + const basic_format_specs<Char>& specs, float_specs fspecs, + Char decimal_point) { + auto significand = fp.significand; + int significand_size = get_significand_size(fp); + static const Char zero = static_cast<Char>('0'); + auto sign = fspecs.sign; + size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); + using iterator = reserve_iterator<OutputIt>; + + int output_exp = fp.exponent + significand_size - 1; + auto use_exp_format = [=]() { + if (fspecs.format == float_format::exp) return true; + if (fspecs.format != float_format::general) return false; + // Use the fixed notation if the exponent is in [exp_lower, exp_upper), + // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. + const int exp_lower = -4, exp_upper = 16; + return output_exp < exp_lower || + output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper); + }; + if (use_exp_format()) { + int num_zeros = 0; + if (fspecs.showpoint) { + num_zeros = fspecs.precision - significand_size; + if (num_zeros < 0) num_zeros = 0; + size += to_unsigned(num_zeros); + } else if (significand_size == 1) { + decimal_point = Char(); + } + auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; + int exp_digits = 2; + if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; + + size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); + char exp_char = fspecs.upper ? 'E' : 'e'; + auto write = [=](iterator it) { + if (sign) *it++ = static_cast<Char>(data::signs[sign]); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, significand, significand_size, 1, + decimal_point); + if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); + *it++ = static_cast<Char>(exp_char); + return write_exponent<Char>(output_exp, it); + }; + return specs.width > 0 ? write_padded<align::right>(out, specs, size, write) + : base_iterator(out, write(reserve(out, size))); + } + + int exp = fp.exponent + significand_size; + if (fp.exponent >= 0) { + // 1234e5 -> 123400000[.0+] + size += to_unsigned(fp.exponent); + int num_zeros = fspecs.precision - exp; +#ifdef FMT_FUZZ + if (num_zeros > 5000) + throw std::runtime_error("fuzz mode - avoiding excessive cpu use"); +#endif + if (fspecs.showpoint) { + if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; + if (num_zeros > 0) size += to_unsigned(num_zeros) + 1; + } + return write_padded<align::right>(out, specs, size, [&](iterator it) { + if (sign) *it++ = static_cast<Char>(data::signs[sign]); + it = write_significand<Char>(it, significand, significand_size); + it = detail::fill_n(it, fp.exponent, zero); + if (!fspecs.showpoint) return it; + *it++ = decimal_point; + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } else if (exp > 0) { + // 1234e-2 -> 12.34[0+] + int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; + size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); + return write_padded<align::right>(out, specs, size, [&](iterator it) { + if (sign) *it++ = static_cast<Char>(data::signs[sign]); + it = write_significand(it, significand, significand_size, exp, + decimal_point); + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } + // 1234e-6 -> 0.001234 + int num_zeros = -exp; + if (significand_size == 0 && fspecs.precision >= 0 && + fspecs.precision < num_zeros) { + num_zeros = fspecs.precision; + } + bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint; + size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); + return write_padded<align::right>(out, specs, size, [&](iterator it) { + if (sign) *it++ = static_cast<Char>(data::signs[sign]); + *it++ = zero; + if (!pointy) return it; + *it++ = decimal_point; + it = detail::fill_n(it, num_zeros, zero); + return write_significand<Char>(it, significand, significand_size); + }); +} + +template <typename Char, typename OutputIt, typename T, + FMT_ENABLE_IF(std::is_floating_point<T>::value)> +OutputIt write(OutputIt out, T value, basic_format_specs<Char> specs, + locale_ref loc = {}) { + if (const_check(!is_supported_floating_point(value))) return out; + float_specs fspecs = parse_float_type_spec(specs); + fspecs.sign = specs.sign; + if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. + fspecs.sign = sign::minus; + value = -value; + } else if (fspecs.sign == sign::minus) { + fspecs.sign = sign::none; + } + + if (!std::isfinite(value)) + return write_nonfinite(out, std::isinf(value), specs, fspecs); + + if (specs.align == align::numeric && fspecs.sign) { + auto it = reserve(out, 1); + *it++ = static_cast<Char>(data::signs[fspecs.sign]); + out = base_iterator(out, it); + fspecs.sign = sign::none; + if (specs.width != 0) --specs.width; + } + + memory_buffer buffer; + if (fspecs.format == float_format::hex) { + if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); + snprintf_float(promote_float(value), specs.precision, fspecs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, specs); + } + int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; + if (fspecs.format == float_format::exp) { + if (precision == max_value<int>()) + FMT_THROW(format_error("number is too big")); + else + ++precision; + } + if (const_check(std::is_same<T, float>())) fspecs.binary32 = true; + fspecs.use_grisu = is_fast_float<T>(); + int exp = format_float(promote_float(value), precision, fspecs, buffer); + fspecs.precision = precision; + Char point = + fspecs.locale ? decimal_point<Char>(loc) : static_cast<Char>('.'); + auto fp = big_decimal_fp{buffer.data(), static_cast<int>(buffer.size()), exp}; + return write_float(out, fp, specs, fspecs, point); +} + +template <typename Char, typename OutputIt, typename T, + FMT_ENABLE_IF(is_fast_float<T>::value)> +OutputIt write(OutputIt out, T value) { + if (const_check(!is_supported_floating_point(value))) return out; + + using floaty = conditional_t<std::is_same<T, long double>::value, double, T>; + using uint = typename dragonbox::float_info<floaty>::carrier_uint; + auto bits = bit_cast<uint>(value); + + auto fspecs = float_specs(); + auto sign_bit = bits & (uint(1) << (num_bits<uint>() - 1)); + if (sign_bit != 0) { + fspecs.sign = sign::minus; + value = -value; + } + + static const auto specs = basic_format_specs<Char>(); + uint mask = exponent_mask<floaty>(); + if ((bits & mask) == mask) + return write_nonfinite(out, std::isinf(value), specs, fspecs); + + auto dec = dragonbox::to_decimal(static_cast<floaty>(value)); + return write_float(out, dec, specs, fspecs, static_cast<Char>('.')); +} + +template <typename Char, typename OutputIt, typename T, + FMT_ENABLE_IF(std::is_floating_point<T>::value && + !is_fast_float<T>::value)> +inline OutputIt write(OutputIt out, T value) { + return write(out, value, basic_format_specs<Char>()); +} + +template <typename Char, typename OutputIt, typename UIntPtr> +OutputIt write_ptr(OutputIt out, UIntPtr value, + const basic_format_specs<Char>* specs) { + int num_digits = count_digits<4>(value); + auto size = to_unsigned(num_digits) + size_t(2); + auto write = [=](reserve_iterator<OutputIt> it) { + *it++ = static_cast<Char>('0'); + *it++ = static_cast<Char>('x'); + return format_uint<4, Char>(it, value, num_digits); + }; + return specs ? write_padded<align::right>(out, *specs, size, write) + : base_iterator(out, write(reserve(out, size))); +} + +template <typename T> struct is_integral : std::is_integral<T> {}; +template <> struct is_integral<int128_t> : std::true_type {}; +template <> struct is_integral<uint128_t> : std::true_type {}; + +template <typename Char, typename OutputIt> +OutputIt write(OutputIt out, monostate) { + FMT_ASSERT(false, ""); + return out; +} + +template <typename Char, typename OutputIt, + FMT_ENABLE_IF(!std::is_same<Char, char>::value)> +OutputIt write(OutputIt out, string_view value) { + auto it = reserve(out, value.size()); + it = copy_str<Char>(value.begin(), value.end(), it); + return base_iterator(out, it); +} + +template <typename Char, typename OutputIt> +FMT_CONSTEXPR OutputIt write(OutputIt out, basic_string_view<Char> value) { + auto it = reserve(out, value.size()); + it = copy_str<Char>(value.begin(), value.end(), it); + return base_iterator(out, it); +} + +template <typename Char, typename OutputIt, typename T, + FMT_ENABLE_IF(is_string<T>::value)> +constexpr OutputIt write(OutputIt out, const T& value) { + return write<Char>(out, to_string_view(value)); +} + +template <typename Char, typename OutputIt, typename T, + FMT_ENABLE_IF(is_integral<T>::value && + !std::is_same<T, bool>::value && + !std::is_same<T, Char>::value)> +FMT_CONSTEXPR OutputIt write(OutputIt out, T value) { + auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value); + bool negative = is_negative(value); + // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. + if (negative) abs_value = ~abs_value + 1; + int num_digits = count_digits(abs_value); + auto size = (negative ? 1 : 0) + static_cast<size_t>(num_digits); + auto it = reserve(out, size); + if (auto ptr = to_pointer<Char>(it, size)) { + if (negative) *ptr++ = static_cast<Char>('-'); + format_decimal<Char>(ptr, abs_value, num_digits); + return out; + } + if (negative) *it++ = static_cast<Char>('-'); + it = format_decimal<Char>(it, abs_value, num_digits).end; + return base_iterator(out, it); +} + +// FMT_ENABLE_IF() condition separated to workaround MSVC bug +template < + typename Char, typename OutputIt, typename T, + bool check = + std::is_enum<T>::value && !std::is_same<T, Char>::value && + mapped_type_constant<T, basic_format_context<OutputIt, Char>>::value != + type::custom_type, + FMT_ENABLE_IF(check)> +FMT_CONSTEXPR OutputIt write(OutputIt out, T value) { + return write<Char>( + out, static_cast<typename std::underlying_type<T>::type>(value)); +} + +template <typename Char, typename OutputIt> +constexpr OutputIt write(OutputIt out, bool value) { + return write<Char>(out, string_view(value ? "true" : "false")); +} + +template <typename Char, typename OutputIt> +FMT_CONSTEXPR OutputIt write(OutputIt out, Char value) { + auto it = reserve(out, 1); + *it++ = value; + return base_iterator(out, it); +} + +template <typename Char, typename OutputIt> +FMT_CONSTEXPR_CHAR_TRAITS OutputIt write(OutputIt out, const Char* value) { + if (!value) { + FMT_THROW(format_error("string pointer is null")); + } else { + auto length = std::char_traits<Char>::length(value); + out = write(out, basic_string_view<Char>(value, length)); + } + return out; +} + +template <typename Char, typename OutputIt> +OutputIt write(OutputIt out, const void* value) { + return write_ptr<Char>(out, to_uintptr(value), nullptr); +} + +template <typename Char, typename OutputIt, typename T> +auto write(OutputIt out, const T& value) -> typename std::enable_if< + mapped_type_constant<T, basic_format_context<OutputIt, Char>>::value == + type::custom_type, + OutputIt>::type { + using context_type = basic_format_context<OutputIt, Char>; + using formatter_type = + conditional_t<has_formatter<T, context_type>::value, + typename context_type::template formatter_type<T>, + fallback_formatter<T, Char>>; + context_type ctx(out, {}, {}); + return formatter_type().format(value, ctx); +} + +// An argument visitor that formats the argument and writes it via the output +// iterator. It's a class and not a generic lambda for compatibility with C++11. +template <typename OutputIt, typename Char> struct default_arg_formatter { + using context = basic_format_context<OutputIt, Char>; + + OutputIt out; + basic_format_args<context> args; + locale_ref loc; + + template <typename T> OutputIt operator()(T value) { + return write<Char>(out, value); + } + + OutputIt operator()(typename basic_format_arg<context>::handle handle) { + basic_format_parse_context<Char> parse_ctx({}); + basic_format_context<OutputIt, Char> format_ctx(out, args, loc); + handle.format(parse_ctx, format_ctx); + return format_ctx.out(); + } +}; + +template <typename OutputIt, typename Char, + typename ErrorHandler = error_handler> +class arg_formatter_base { + public: + using iterator = OutputIt; + using char_type = Char; + using format_specs = basic_format_specs<Char>; + + private: + iterator out_; + const format_specs& specs_; + locale_ref locale_; + + // Attempts to reserve space for n extra characters in the output range. + // Returns a pointer to the reserved range or a reference to out_. + auto reserve(size_t n) -> decltype(detail::reserve(out_, n)) { + return detail::reserve(out_, n); + } + + void write(char value) { + auto&& it = reserve(1); + *it++ = value; + } + + template <typename Ch, FMT_ENABLE_IF(std::is_same<Ch, Char>::value)> + void write(Ch value) { + out_ = detail::write<Char>(out_, value); + } + + void write(string_view value) { + auto&& it = reserve(value.size()); + it = copy_str<Char>(value.begin(), value.end(), it); + } + void write(wstring_view value) { + static_assert(std::is_same<Char, wchar_t>::value, ""); + auto&& it = reserve(value.size()); + it = copy_str<Char>(value.begin(), value.end(), it); + } + + template <typename Ch> + void write(const Ch* s, size_t size, const format_specs& specs) { + auto width = + specs.width != 0 ? compute_width(basic_string_view<Ch>(s, size)) : 0; + out_ = write_padded(out_, specs, size, width, + [=](reserve_iterator<OutputIt> it) { + return copy_str<Char>(s, s + size, it); + }); + } + + template <typename Ch> + FMT_CONSTEXPR void write(basic_string_view<Ch> s, + const format_specs& specs = {}) { + out_ = detail::write(out_, s, specs); + } + + void write_pointer(const void* p) { + out_ = write_ptr<char_type>(out_, to_uintptr(p), &specs_); + } + + struct char_spec_handler : ErrorHandler { + arg_formatter_base& formatter; + Char value; + + constexpr char_spec_handler(arg_formatter_base& f, Char val) + : formatter(f), value(val) {} + + FMT_CONSTEXPR void on_int() { + // char is only formatted as int if there are specs. + formatter.out_ = + detail::write_int(formatter.out_, static_cast<int>(value), + formatter.specs_, formatter.locale_); + } + FMT_CONSTEXPR void on_char() { + formatter.out_ = write_char(formatter.out_, value, formatter.specs_); + } + }; + + struct cstring_spec_handler : error_handler { + arg_formatter_base& formatter; + const Char* value; + + cstring_spec_handler(arg_formatter_base& f, const Char* val) + : formatter(f), value(val) {} + + void on_string() { formatter.write(value); } + void on_pointer() { formatter.write_pointer(value); } + }; + + protected: + iterator out() { return out_; } + const format_specs& specs() { return specs_; } + + FMT_CONSTEXPR void write(bool value) { + write(string_view(value ? "true" : "false"), specs_); + } + + void write(const Char* value) { + if (value) + write(basic_string_view<char_type>(value), specs_); + else + FMT_THROW(format_error("string pointer is null")); + } + + public: + constexpr arg_formatter_base(OutputIt out, const format_specs& s, + locale_ref loc) + : out_(out), specs_(s), locale_(loc) {} + + iterator operator()(monostate) { + FMT_ASSERT(false, "invalid argument type"); + return out_; + } + + template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> + FMT_CONSTEXPR FMT_INLINE iterator operator()(T value) { + return out_ = detail::write_int(out_, value, specs_, locale_); + } + + FMT_CONSTEXPR iterator operator()(Char value) { + handle_char_specs(specs_, + char_spec_handler(*this, static_cast<Char>(value))); + return out_; + } + + FMT_CONSTEXPR iterator operator()(bool value) { + if (specs_.type && specs_.type != 's') return (*this)(value ? 1 : 0); + write(value != 0); + return out_; + } + + template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> + iterator operator()(T value) { + if (const_check(is_supported_floating_point(value))) + out_ = detail::write(out_, value, specs_, locale_); + else + FMT_ASSERT(false, "unsupported float argument type"); + return out_; + } + + iterator operator()(const Char* value) { + handle_cstring_type_spec(specs_.type, cstring_spec_handler(*this, value)); + return out_; + } + + FMT_CONSTEXPR iterator operator()(basic_string_view<Char> value) { + check_string_type_spec(specs_.type, error_handler()); + write(value, specs_); + return out_; + } + + iterator operator()(const void* value) { + check_pointer_type_spec(specs_.type, error_handler()); + write_pointer(value); + return out_; + } +}; + +/** The default argument formatter. */ +template <typename OutputIt, typename Char> +class arg_formatter : public arg_formatter_base<OutputIt, Char> { + private: + using char_type = Char; + using base = arg_formatter_base<OutputIt, Char>; + using context_type = basic_format_context<OutputIt, Char>; + + context_type& ctx_; + + public: + using iterator = typename base::iterator; + using format_specs = typename base::format_specs; + + /** + \rst + Constructs an argument formatter object. + *ctx* is a reference to the formatting context, + *specs* contains format specifier information for standard argument types. + \endrst + */ + constexpr explicit arg_formatter(context_type& ctx, const format_specs& specs) + : base(ctx.out(), specs, ctx.locale()), ctx_(ctx) {} + + using base::operator(); + + iterator operator()(typename basic_format_arg<context_type>::handle) { + // User-defined types are handled separately because they require access to + // the parse context. + return ctx_.out(); + } +}; + +template <typename Context> class custom_formatter { + private: + using char_type = typename Context::char_type; + + basic_format_parse_context<char_type>& parse_ctx_; + Context& ctx_; + + public: + explicit custom_formatter(basic_format_parse_context<char_type>& parse_ctx, + Context& ctx) + : parse_ctx_(parse_ctx), ctx_(ctx) {} + + void operator()(typename basic_format_arg<Context>::handle h) const { + h.format(parse_ctx_, ctx_); + } + + template <typename T> void operator()(T) const {} +}; + +template <typename T> +using is_integer = + bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value && + !std::is_same<T, char>::value && + !std::is_same<T, wchar_t>::value>; + +template <typename ErrorHandler> class width_checker { + public: + explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} + + template <typename T, FMT_ENABLE_IF(is_integer<T>::value)> + FMT_CONSTEXPR unsigned long long operator()(T value) { + if (is_negative(value)) handler_.on_error("negative width"); + return static_cast<unsigned long long>(value); + } + + template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)> + FMT_CONSTEXPR unsigned long long operator()(T) { + handler_.on_error("width is not integer"); + return 0; + } + + private: + ErrorHandler& handler_; +}; + +template <typename ErrorHandler> class precision_checker { + public: + explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} + + template <typename T, FMT_ENABLE_IF(is_integer<T>::value)> + FMT_CONSTEXPR unsigned long long operator()(T value) { + if (is_negative(value)) handler_.on_error("negative precision"); + return static_cast<unsigned long long>(value); + } + + template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)> + FMT_CONSTEXPR unsigned long long operator()(T) { + handler_.on_error("precision is not integer"); + return 0; + } + + private: + ErrorHandler& handler_; +}; + +// A format specifier handler that sets fields in basic_format_specs. +template <typename Char> class specs_setter { + public: + explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char>& specs) + : specs_(specs) {} + + FMT_CONSTEXPR specs_setter(const specs_setter& other) + : specs_(other.specs_) {} + + FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } + FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) { + specs_.fill = fill; + } + FMT_CONSTEXPR void on_plus() { specs_.sign = sign::plus; } + FMT_CONSTEXPR void on_minus() { specs_.sign = sign::minus; } + FMT_CONSTEXPR void on_space() { specs_.sign = sign::space; } + FMT_CONSTEXPR void on_hash() { specs_.alt = true; } + FMT_CONSTEXPR void on_localized() { specs_.localized = true; } + + FMT_CONSTEXPR void on_zero() { + specs_.align = align::numeric; + specs_.fill[0] = Char('0'); + } + + FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } + FMT_CONSTEXPR void on_precision(int precision) { + specs_.precision = precision; + } + FMT_CONSTEXPR void end_precision() {} + + FMT_CONSTEXPR void on_type(Char type) { + specs_.type = static_cast<char>(type); + } + + protected: + basic_format_specs<Char>& specs_; +}; + +template <typename ErrorHandler> class numeric_specs_checker { + public: + FMT_CONSTEXPR numeric_specs_checker(ErrorHandler& eh, detail::type arg_type) + : error_handler_(eh), arg_type_(arg_type) {} + + FMT_CONSTEXPR void require_numeric_argument() { + if (!is_arithmetic_type(arg_type_)) + error_handler_.on_error("format specifier requires numeric argument"); + } + + FMT_CONSTEXPR void check_sign() { + require_numeric_argument(); + if (is_integral_type(arg_type_) && arg_type_ != type::int_type && + arg_type_ != type::long_long_type && arg_type_ != type::char_type) { + error_handler_.on_error("format specifier requires signed argument"); + } + } + + FMT_CONSTEXPR void check_precision() { + if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) + error_handler_.on_error("precision not allowed for this argument type"); + } + + private: + ErrorHandler& error_handler_; + detail::type arg_type_; +}; + +// A format specifier handler that checks if specifiers are consistent with the +// argument type. +template <typename Handler> class specs_checker : public Handler { + private: + numeric_specs_checker<Handler> checker_; + + // Suppress an MSVC warning about using this in initializer list. + FMT_CONSTEXPR Handler& error_handler() { return *this; } + + public: + FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) + : Handler(handler), checker_(error_handler(), arg_type) {} + + FMT_CONSTEXPR specs_checker(const specs_checker& other) + : Handler(other), checker_(error_handler(), other.arg_type_) {} + + FMT_CONSTEXPR void on_align(align_t align) { + if (align == align::numeric) checker_.require_numeric_argument(); + Handler::on_align(align); + } + + FMT_CONSTEXPR void on_plus() { + checker_.check_sign(); + Handler::on_plus(); + } + + FMT_CONSTEXPR void on_minus() { + checker_.check_sign(); + Handler::on_minus(); + } + + FMT_CONSTEXPR void on_space() { + checker_.check_sign(); + Handler::on_space(); + } + + FMT_CONSTEXPR void on_hash() { + checker_.require_numeric_argument(); + Handler::on_hash(); + } + + FMT_CONSTEXPR void on_localized() { + checker_.require_numeric_argument(); + Handler::on_localized(); + } + + FMT_CONSTEXPR void on_zero() { + checker_.require_numeric_argument(); + Handler::on_zero(); + } + + FMT_CONSTEXPR void end_precision() { checker_.check_precision(); } +}; + +template <template <typename> class Handler, typename FormatArg, + typename ErrorHandler> +FMT_CONSTEXPR int get_dynamic_spec(FormatArg arg, ErrorHandler eh) { + unsigned long long value = visit_format_arg(Handler<ErrorHandler>(eh), arg); + if (value > to_unsigned(max_value<int>())) eh.on_error("number is too big"); + return static_cast<int>(value); +} + +template <typename Context, typename ID> +FMT_CONSTEXPR typename Context::format_arg get_arg(Context& ctx, ID id) { + auto arg = ctx.arg(id); + if (!arg) ctx.on_error("argument not found"); + return arg; +} + +// The standard format specifier handler with checking. +template <typename ParseContext, typename Context> +class specs_handler : public specs_setter<typename Context::char_type> { + public: + using char_type = typename Context::char_type; + + FMT_CONSTEXPR specs_handler(basic_format_specs<char_type>& specs, + ParseContext& parse_ctx, Context& ctx) + : specs_setter<char_type>(specs), + parse_context_(parse_ctx), + context_(ctx) {} + + template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + this->specs_.width = get_dynamic_spec<width_checker>( + get_arg(arg_id), context_.error_handler()); + } + + template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + this->specs_.precision = get_dynamic_spec<precision_checker>( + get_arg(arg_id), context_.error_handler()); + } + + void on_error(const char* message) { context_.on_error(message); } + + private: + // This is only needed for compatibility with gcc 4.4. + using format_arg = typename Context::format_arg; + + FMT_CONSTEXPR format_arg get_arg(auto_id) { + return detail::get_arg(context_, parse_context_.next_arg_id()); + } + + FMT_CONSTEXPR format_arg get_arg(int arg_id) { + parse_context_.check_arg_id(arg_id); + return detail::get_arg(context_, arg_id); + } + + FMT_CONSTEXPR format_arg get_arg(basic_string_view<char_type> arg_id) { + parse_context_.check_arg_id(arg_id); + return detail::get_arg(context_, arg_id); + } + + ParseContext& parse_context_; + Context& context_; +}; + +enum class arg_id_kind { none, index, name }; + +// An argument reference. +template <typename Char> struct arg_ref { + FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} + + FMT_CONSTEXPR explicit arg_ref(int index) + : kind(arg_id_kind::index), val(index) {} + FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name) + : kind(arg_id_kind::name), val(name) {} + + FMT_CONSTEXPR arg_ref& operator=(int idx) { + kind = arg_id_kind::index; + val.index = idx; + return *this; + } + + arg_id_kind kind; + union value { + FMT_CONSTEXPR value(int id = 0) : index{id} {} + FMT_CONSTEXPR value(basic_string_view<Char> n) : name(n) {} + + int index; + basic_string_view<Char> name; + } val; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow re-using the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template <typename Char> +struct dynamic_format_specs : basic_format_specs<Char> { + arg_ref<Char> width_ref; + arg_ref<Char> precision_ref; +}; + +// Format spec handler that saves references to arguments representing dynamic +// width and precision to be resolved at formatting time. +template <typename ParseContext> +class dynamic_specs_handler + : public specs_setter<typename ParseContext::char_type> { + public: + using char_type = typename ParseContext::char_type; + + FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs<char_type>& specs, + ParseContext& ctx) + : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {} + + FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) + : specs_setter<char_type>(other), + specs_(other.specs_), + context_(other.context_) {} + + template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + specs_.width_ref = make_arg_ref(arg_id); + } + + template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + specs_.precision_ref = make_arg_ref(arg_id); + } + + FMT_CONSTEXPR void on_error(const char* message) { + context_.on_error(message); + } + + private: + using arg_ref_type = arg_ref<char_type>; + + FMT_CONSTEXPR arg_ref_type make_arg_ref(int arg_id) { + context_.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) { + return arg_ref_type(context_.next_arg_id()); + } + + FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<char_type> arg_id) { + context_.check_arg_id(arg_id); + basic_string_view<char_type> format_str( + context_.begin(), to_unsigned(context_.end() - context_.begin())); + return arg_ref_type(arg_id); + } + + dynamic_format_specs<char_type>& specs_; + ParseContext& context_; +}; + +template <typename OutputIt, typename Char, typename Context> +struct format_handler : detail::error_handler { + basic_format_parse_context<Char> parse_context; + Context context; + + format_handler(OutputIt out, basic_string_view<Char> str, + basic_format_args<Context> format_args, detail::locale_ref loc) + : parse_context(str), context(out, format_args, loc) {} + + void on_text(const Char* begin, const Char* end) { + auto text = basic_string_view<Char>(begin, to_unsigned(end - begin)); + context.advance_to(write<Char>(context.out(), text)); + } + + int on_arg_id() { return parse_context.next_arg_id(); } + int on_arg_id(int id) { return parse_context.check_arg_id(id), id; } + int on_arg_id(basic_string_view<Char> id) { + int arg_id = context.arg_id(id); + if (arg_id < 0) on_error("argument not found"); + return arg_id; + } + + FMT_INLINE void on_replacement_field(int id, const Char*) { + auto arg = get_arg(context, id); + context.advance_to(visit_format_arg( + default_arg_formatter<OutputIt, Char>{context.out(), context.args(), + context.locale()}, + arg)); + } + + const Char* on_format_specs(int id, const Char* begin, const Char* end) { + auto arg = get_arg(context, id); + if (arg.type() == type::custom_type) { + advance_to(parse_context, begin); + visit_format_arg(custom_formatter<Context>(parse_context, context), arg); + return parse_context.begin(); + } + auto specs = basic_format_specs<Char>(); + using parse_context_t = basic_format_parse_context<Char>; + specs_checker<specs_handler<parse_context_t, Context>> handler( + specs_handler<parse_context_t, Context>(specs, parse_context, context), + arg.type()); + begin = parse_format_specs(begin, end, handler); + if (begin == end || *begin != '}') on_error("missing '}' in format string"); + context.advance_to( + visit_format_arg(arg_formatter<OutputIt, Char>(context, specs), arg)); + return begin; + } +}; + +template <template <typename> class Handler, typename Context> +FMT_CONSTEXPR void handle_dynamic_spec(int& value, + arg_ref<typename Context::char_type> ref, + Context& ctx) { + switch (ref.kind) { + case arg_id_kind::none: + break; + case arg_id_kind::index: + value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.index), + ctx.error_handler()); + break; + case arg_id_kind::name: + value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.name), + ctx.error_handler()); + break; + } +} + +using format_func = void (*)(detail::buffer<char>&, int, string_view); + +FMT_API void format_error_code(buffer<char>& out, int error_code, + string_view message) FMT_NOEXCEPT; + +FMT_API void report_error(format_func func, int error_code, + string_view message) FMT_NOEXCEPT; +} // namespace detail +FMT_MODULE_EXPORT_BEGIN + +template <typename OutputIt, typename Char> +using arg_formatter FMT_DEPRECATED_ALIAS = + detail::arg_formatter<OutputIt, Char>; + +/** + An error returned by an operating system or a language runtime, + for example a file opening error. +*/ +FMT_CLASS_API +class FMT_API system_error : public std::runtime_error { + private: + void init(int err_code, string_view format_str, format_args args); + + protected: + int error_code_; + + system_error() : std::runtime_error(""), error_code_(0) {} + + public: + /** + \rst + Constructs a :class:`fmt::system_error` object with a description + formatted with `fmt::format_system_error`. *message* and additional + arguments passed into the constructor are formatted similarly to + `fmt::format`. + + **Example**:: + + // This throws a system_error with the description + // cannot open file 'madeup': No such file or directory + // or similar (system message may vary). + const char *filename = "madeup"; + std::FILE *file = std::fopen(filename, "r"); + if (!file) + throw fmt::system_error(errno, "cannot open file '{}'", filename); + \endrst + */ + template <typename... Args> + system_error(int error_code, string_view message, const Args&... args) + : std::runtime_error("") { + init(error_code, message, make_format_args(args...)); + } + system_error(const system_error&) = default; + system_error& operator=(const system_error&) = default; + system_error(system_error&&) = default; + system_error& operator=(system_error&&) = default; + ~system_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT; + + int error_code() const { return error_code_; } +}; + +/** + \rst + Formats an error returned by an operating system or a language runtime, + for example a file opening error, and writes it to *out* in the following + form: + + .. parsed-literal:: + *<message>*: *<system-message>* + + where *<message>* is the passed message and *<system-message>* is + the system message corresponding to the error code. + *error_code* is a system error code as given by ``errno``. + If *error_code* is not a valid error code such as -1, the system message + may look like "Unknown error -1" and is platform-dependent. + \endrst + */ +FMT_API void format_system_error(detail::buffer<char>& out, int error_code, + string_view message) FMT_NOEXCEPT; + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_system_error(int error_code, + string_view message) FMT_NOEXCEPT; + +/** Fast integer formatter. */ +class format_int { + private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum { buffer_size = std::numeric_limits<unsigned long long>::digits10 + 3 }; + mutable char buffer_[buffer_size]; + char* str_; + + template <typename UInt> char* format_unsigned(UInt value) { + auto n = static_cast<detail::uint32_or_64_or_128_t<UInt>>(value); + return detail::format_decimal(buffer_, n, buffer_size - 1).begin; + } + + template <typename Int> char* format_signed(Int value) { + auto abs_value = static_cast<detail::uint32_or_64_or_128_t<Int>>(value); + bool negative = value < 0; + if (negative) abs_value = 0 - abs_value; + auto begin = format_unsigned(abs_value); + if (negative) *--begin = '-'; + return begin; + } + + public: + explicit format_int(int value) : str_(format_signed(value)) {} + explicit format_int(long value) : str_(format_signed(value)) {} + explicit format_int(long long value) : str_(format_signed(value)) {} + explicit format_int(unsigned value) : str_(format_unsigned(value)) {} + explicit format_int(unsigned long value) : str_(format_unsigned(value)) {} + explicit format_int(unsigned long long value) + : str_(format_unsigned(value)) {} + + /** Returns the number of characters written to the output buffer. */ + size_t size() const { + return detail::to_unsigned(buffer_ - str_ + buffer_size - 1); + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const char* data() const { return str_; } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const char* c_str() const { + buffer_[buffer_size - 1] = '\0'; + return str_; + } + + /** + \rst + Returns the content of the output buffer as an ``std::string``. + \endrst + */ + std::string str() const { return std::string(str_, size()); } +}; + +// A formatter specialization for the core types corresponding to detail::type +// constants. +template <typename T, typename Char> +struct formatter<T, Char, + enable_if_t<detail::type_constant<T, Char>::value != + detail::type::custom_type>> { + FMT_CONSTEXPR formatter() = default; + + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + if (begin == end) return begin; + using handler_type = detail::dynamic_specs_handler<ParseContext>; + auto type = detail::type_constant<T, Char>::value; + detail::specs_checker<handler_type> handler(handler_type(specs_, ctx), + type); + auto it = detail::parse_format_specs(begin, end, handler); + auto eh = ctx.error_handler(); + switch (type) { + case detail::type::none_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case detail::type::bool_type: + if (!specs_.type || specs_.type == 's') break; + FMT_FALLTHROUGH; + case detail::type::int_type: + case detail::type::uint_type: + case detail::type::long_long_type: + case detail::type::ulong_long_type: + case detail::type::int128_type: + case detail::type::uint128_type: + detail::check_int_type_spec(specs_.type, eh); + break; + case detail::type::char_type: + detail::handle_char_specs( + specs_, detail::char_specs_checker<decltype(eh)>(specs_.type, eh)); + break; + case detail::type::float_type: + if (detail::const_check(FMT_USE_FLOAT)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "float support disabled"); + break; + case detail::type::double_type: + if (detail::const_check(FMT_USE_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "double support disabled"); + break; + case detail::type::long_double_type: + if (detail::const_check(FMT_USE_LONG_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "long double support disabled"); + break; + case detail::type::cstring_type: + detail::handle_cstring_type_spec( + specs_.type, detail::cstring_type_checker<decltype(eh)>(eh)); + break; + case detail::type::string_type: + detail::check_string_type_spec(specs_.type, eh); + break; + case detail::type::pointer_type: + detail::check_pointer_type_spec(specs_.type, eh); + break; + case detail::type::custom_type: + // Custom format specifiers should be checked in parse functions of + // formatter specializations. + break; + } + return it; + } + + template <typename FormatContext> + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec<detail::width_checker>(specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec<detail::precision_checker>( + specs.precision, specs.precision_ref, ctx); + using af = detail::arg_formatter<typename FormatContext::iterator, + typename FormatContext::char_type>; + return visit_format_arg(af(ctx, specs), + detail::make_arg<FormatContext>(val)); + } + + private: + detail::dynamic_format_specs<Char> specs_; +}; + +#define FMT_FORMAT_AS(Type, Base) \ + template <typename Char> \ + struct formatter<Type, Char> : formatter<Base, Char> { \ + template <typename FormatContext> \ + auto format(Type const& val, FormatContext& ctx) const \ + -> decltype(ctx.out()) { \ + return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \ + } \ + } + +FMT_FORMAT_AS(signed char, int); +FMT_FORMAT_AS(unsigned char, unsigned); +FMT_FORMAT_AS(short, int); +FMT_FORMAT_AS(unsigned short, unsigned); +FMT_FORMAT_AS(long, long long); +FMT_FORMAT_AS(unsigned long, unsigned long long); +FMT_FORMAT_AS(Char*, const Char*); +FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>); +FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>); + +template <typename Char> +struct formatter<void*, Char> : formatter<const void*, Char> { + template <typename FormatContext> + auto format(void* val, FormatContext& ctx) const -> decltype(ctx.out()) { + return formatter<const void*, Char>::format(val, ctx); + } +}; + +template <typename Char, size_t N> +struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> { + template <typename FormatContext> + FMT_CONSTEXPR auto format(const Char* val, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter<basic_string_view<Char>, Char>::format(val, ctx); + } +}; + +// A formatter for types known only at run time such as variant alternatives. +// +// Usage: +// using variant = std::variant<int, std::string>; +// template <> +// struct formatter<variant>: dynamic_formatter<> { +// auto format(const variant& v, format_context& ctx) { +// return visit([&](const auto& val) { +// return dynamic_formatter<>::format(val, ctx); +// }, v); +// } +// }; +template <typename Char = char> class dynamic_formatter { + private: + struct null_handler : detail::error_handler { + void on_align(align_t) {} + void on_plus() {} + void on_minus() {} + void on_space() {} + void on_hash() {} + }; + + public: + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + format_str_ = ctx.begin(); + // Checks are deferred to formatting time when the argument type is known. + detail::dynamic_specs_handler<ParseContext> handler(specs_, ctx); + return detail::parse_format_specs(ctx.begin(), ctx.end(), handler); + } + + template <typename T, typename FormatContext> + auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) { + handle_specs(ctx); + detail::specs_checker<null_handler> checker( + null_handler(), detail::mapped_type_constant<T, FormatContext>::value); + checker.on_align(specs_.align); + switch (specs_.sign) { + case sign::none: + break; + case sign::plus: + checker.on_plus(); + break; + case sign::minus: + checker.on_minus(); + break; + case sign::space: + checker.on_space(); + break; + } + if (specs_.alt) checker.on_hash(); + if (specs_.precision >= 0) checker.end_precision(); + using af = detail::arg_formatter<typename FormatContext::iterator, + typename FormatContext::char_type>; + visit_format_arg(af(ctx, specs_), detail::make_arg<FormatContext>(val)); + return ctx.out(); + } + + private: + template <typename Context> void handle_specs(Context& ctx) { + detail::handle_dynamic_spec<detail::width_checker>(specs_.width, + specs_.width_ref, ctx); + detail::handle_dynamic_spec<detail::precision_checker>( + specs_.precision, specs_.precision_ref, ctx); + } + + detail::dynamic_format_specs<Char> specs_; + const Char* format_str_; +}; + +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR void advance_to( + basic_format_parse_context<Char, ErrorHandler>& ctx, const Char* p) { + ctx.advance_to(ctx.begin() + (p - &*ctx.begin())); +} + +/** + \rst + Converts ``p`` to ``const void*`` for pointer formatting. + + **Example**:: + + auto s = fmt::format("{}", fmt::ptr(p)); + \endrst + */ +template <typename T> const void* ptr(T p) { + static_assert(std::is_pointer<T>::value, ""); + return detail::bit_cast<const void*>(p); +} +template <typename T> const void* ptr(const std::unique_ptr<T>& p) { + return p.get(); +} +template <typename T> const void* ptr(const std::shared_ptr<T>& p) { + return p.get(); +} + +class bytes { + private: + string_view data_; + friend struct formatter<bytes>; + + public: + explicit bytes(string_view data) : data_(data) {} +}; + +template <> struct formatter<bytes> { + private: + detail::dynamic_format_specs<char> specs_; + + public: + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + using handler_type = detail::dynamic_specs_handler<ParseContext>; + detail::specs_checker<handler_type> handler(handler_type(specs_, ctx), + detail::type::string_type); + auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); + detail::check_string_type_spec(specs_.type, ctx.error_handler()); + return it; + } + + template <typename FormatContext> + auto format(bytes b, FormatContext& ctx) -> decltype(ctx.out()) { + detail::handle_dynamic_spec<detail::width_checker>(specs_.width, + specs_.width_ref, ctx); + detail::handle_dynamic_spec<detail::precision_checker>( + specs_.precision, specs_.precision_ref, ctx); + return detail::write_bytes(ctx.out(), b.data_, specs_); + } +}; + +template <typename It, typename Sentinel, typename Char> +struct arg_join : detail::view { + It begin; + Sentinel end; + basic_string_view<Char> sep; + + arg_join(It b, Sentinel e, basic_string_view<Char> s) + : begin(b), end(e), sep(s) {} +}; + +template <typename It, typename Sentinel, typename Char> +struct formatter<arg_join<It, Sentinel, Char>, Char> { + private: + using value_type = typename std::iterator_traits<It>::value_type; + using context = buffer_context<Char>; + using mapper = detail::arg_mapper<context>; + + template <typename T, FMT_ENABLE_IF(has_formatter<T, context>::value)> + static auto map(const T& value) -> const T& { + return value; + } + template <typename T, FMT_ENABLE_IF(!has_formatter<T, context>::value)> + static auto map(const T& value) -> decltype(mapper().map(value)) { + return mapper().map(value); + } + + using formatter_type = + conditional_t<is_formattable<value_type>::value, + formatter<remove_cvref_t<decltype( + map(std::declval<const value_type&>()))>, + Char>, + detail::fallback_formatter<value_type, Char>>; + + formatter_type value_formatter_; + + public: + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return value_formatter_.parse(ctx); + } + + template <typename FormatContext> + auto format(const arg_join<It, Sentinel, Char>& value, FormatContext& ctx) + -> decltype(ctx.out()) { + auto it = value.begin; + auto out = ctx.out(); + if (it != value.end) { + out = value_formatter_.format(map(*it++), ctx); + while (it != value.end) { + out = detail::copy_str<Char>(value.sep.begin(), value.sep.end(), out); + ctx.advance_to(out); + out = value_formatter_.format(map(*it++), ctx); + } + } + return out; + } +}; + +/** + Returns an object that formats the iterator range `[begin, end)` with elements + separated by `sep`. + */ +template <typename It, typename Sentinel> +arg_join<It, Sentinel, char> join(It begin, Sentinel end, string_view sep) { + return {begin, end, sep}; +} + +template <typename It, typename Sentinel> +arg_join<It, Sentinel, wchar_t> join(It begin, Sentinel end, wstring_view sep) { + return {begin, end, sep}; +} + +/** + \rst + Returns an object that formats `range` with elements separated by `sep`. + + **Example**:: + + std::vector<int> v = {1, 2, 3}; + fmt::print("{}", fmt::join(v, ", ")); + // Output: "1, 2, 3" + + ``fmt::join`` applies passed format specifiers to the range elements:: + + fmt::print("{:02}", fmt::join(v, ", ")); + // Output: "01, 02, 03" + \endrst + */ +template <typename Range> +arg_join<detail::iterator_t<Range>, detail::sentinel_t<Range>, char> join( + Range&& range, string_view sep) { + return join(std::begin(range), std::end(range), sep); +} + +template <typename Range> +arg_join<detail::iterator_t<Range>, detail::sentinel_t<Range>, wchar_t> join( + Range&& range, wstring_view sep) { + return join(std::begin(range), std::end(range), sep); +} + +/** + \rst + Converts *value* to ``std::string`` using the default format for type *T*. + + **Example**:: + + #include <fmt/format.h> + + std::string answer = fmt::to_string(42); + \endrst + */ +template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> +inline std::string to_string(const T& value) { + std::string result; + detail::write<char>(std::back_inserter(result), value); + return result; +} + +template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> +inline std::string to_string(T value) { + // The buffer should be large enough to store the number including the sign or + // "false" for bool. + constexpr int max_size = detail::digits10<T>() + 2; + char buffer[max_size > 5 ? static_cast<unsigned>(max_size) : 5]; + char* begin = buffer; + return std::string(begin, detail::write<char>(begin, value)); +} + +/** + Converts *value* to ``std::wstring`` using the default format for type *T*. + */ +template <typename T> inline std::wstring to_wstring(const T& value) { + return format(FMT_STRING(L"{}"), value); +} + +template <typename Char, size_t SIZE> +std::basic_string<Char> to_string(const basic_memory_buffer<Char, SIZE>& buf) { + auto size = buf.size(); + detail::assume(size < std::basic_string<Char>().max_size()); + return std::basic_string<Char>(buf.data(), size); +} + +FMT_MODULE_EXPORT_END + +template <typename Char> +void detail::vformat_to( + detail::buffer<Char>& buf, basic_string_view<Char> format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args, + detail::locale_ref loc) { + using iterator = typename buffer_context<Char>::iterator; + auto out = buffer_appender<Char>(buf); + if (format_str.size() == 2 && equal2(format_str.data(), "{}")) { + auto arg = args.get(0); + if (!arg) error_handler().on_error("argument not found"); + visit_format_arg(default_arg_formatter<iterator, Char>{out, args, loc}, + arg); + return; + } + format_handler<iterator, Char, buffer_context<Char>> h(out, format_str, args, + loc); + parse_format_string<false>(format_str, h); +} + +#ifndef FMT_HEADER_ONLY +extern template void detail::vformat_to(detail::buffer<char>&, string_view, + basic_format_args<format_context>, + detail::locale_ref); +namespace detail { + +extern template FMT_API std::string grouping_impl<char>(locale_ref loc); +extern template FMT_API std::string grouping_impl<wchar_t>(locale_ref loc); +extern template FMT_API char thousands_sep_impl<char>(locale_ref loc); +extern template FMT_API wchar_t thousands_sep_impl<wchar_t>(locale_ref loc); +extern template FMT_API char decimal_point_impl(locale_ref loc); +extern template FMT_API wchar_t decimal_point_impl(locale_ref loc); +extern template int format_float<double>(double value, int precision, + float_specs specs, buffer<char>& buf); +extern template int format_float<long double>(long double value, int precision, + float_specs specs, + buffer<char>& buf); +int snprintf_float(float value, int precision, float_specs specs, + buffer<char>& buf) = delete; +extern template int snprintf_float<double>(double value, int precision, + float_specs specs, + buffer<char>& buf); +extern template int snprintf_float<long double>(long double value, + int precision, + float_specs specs, + buffer<char>& buf); +} // namespace detail +#endif + +FMT_MODULE_EXPORT_BEGIN + +template <typename S, typename Char = char_t<S>, + FMT_ENABLE_IF(detail::is_string<S>::value)> +inline void vformat_to( + detail::buffer<Char>& buf, const S& format_str, + basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args) { + return detail::vformat_to(buf, to_string_view(format_str), args); +} + +template <typename S, typename... Args, size_t SIZE = inline_buffer_size, + typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> +inline typename buffer_context<Char>::iterator format_to( + basic_memory_buffer<Char, SIZE>& buf, const S& format_str, Args&&... args) { + const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); + detail::vformat_to(buf, to_string_view(format_str), vargs); + return detail::buffer_appender<Char>(buf); +} + +template <typename OutputIt, typename Char = char> +using format_context_t = basic_format_context<OutputIt, Char>; + +template <typename OutputIt, typename Char = char> +using format_args_t = basic_format_args<format_context_t<OutputIt, Char>>; + +template <typename OutputIt, typename Char = typename OutputIt::value_type> +using format_to_n_context FMT_DEPRECATED_ALIAS = buffer_context<Char>; + +template <typename OutputIt, typename Char = typename OutputIt::value_type> +using format_to_n_args FMT_DEPRECATED_ALIAS = + basic_format_args<buffer_context<Char>>; + +template <typename OutputIt, typename Char, typename... Args> +FMT_DEPRECATED format_arg_store<buffer_context<Char>, Args...> +make_format_to_n_args(const Args&... args) { + return format_arg_store<buffer_context<Char>, Args...>(args...); +} + +#if FMT_COMPILE_TIME_CHECKS +template <typename... Args> struct format_string { + string_view str; + + template <size_t N> consteval format_string(const char (&s)[N]) : str(s) { + if constexpr (detail::count_named_args<Args...>() == 0) { + using checker = detail::format_string_checker<char, detail::error_handler, + remove_cvref_t<Args>...>; + detail::parse_format_string<true>(string_view(s, N), checker(s, {})); + } + } + + template <typename T, + FMT_ENABLE_IF(std::is_constructible_v<string_view, const T&>)> + format_string(const T& s) : str(s) {} +}; + +template <typename... Args> +FMT_INLINE std::string format( + format_string<std::type_identity_t<Args>...> format_str, Args&&... args) { + return detail::vformat(format_str.str, make_format_args(args...)); +} +#endif + +template <typename Char, enable_if_t<(!std::is_same<Char, char>::value), int>> +std::basic_string<Char> detail::vformat( + basic_string_view<Char> format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + basic_memory_buffer<Char> buffer; + detail::vformat_to(buffer, format_str, args); + return to_string(buffer); +} + +template <typename Char, FMT_ENABLE_IF(std::is_same<Char, wchar_t>::value)> +void vprint(std::FILE* f, basic_string_view<Char> format_str, + wformat_args args) { + wmemory_buffer buffer; + detail::vformat_to(buffer, format_str, args); + buffer.push_back(L'\0'); + if (std::fputws(buffer.data(), f) == -1) + FMT_THROW(system_error(errno, "cannot write to file")); +} + +template <typename Char, FMT_ENABLE_IF(std::is_same<Char, wchar_t>::value)> +void vprint(basic_string_view<Char> format_str, wformat_args args) { + vprint(stdout, format_str, args); +} + +FMT_MODULE_EXPORT_END + +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +namespace detail { +template <typename Char, size_t N> struct fixed_string { + constexpr fixed_string(const Char (&str)[N]) { + copy_str<Char, const Char*, Char*>(static_cast<const Char*>(str), str + N, + data); + } + Char data[N]{}; +}; +} // namespace detail +#endif + +#if FMT_USE_USER_DEFINED_LITERALS +namespace detail { +template <typename Char> struct udl_formatter { + basic_string_view<Char> str; + + template <typename... Args> + std::basic_string<Char> operator()(Args&&... args) const { + return format(str, std::forward<Args>(args)...); + } +}; + +template <typename T, typename = void> +struct is_statically_named_arg : std::false_type {}; + +# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +template <typename T, typename Char, size_t N, fixed_string<Char, N> Str> +struct statically_named_arg : view { + static constexpr auto name = Str.data; + + const T& value; + statically_named_arg(const T& v) : value(v) {} +}; + +template <typename T, typename Char, size_t N, fixed_string<Char, N> Str> +struct is_named_arg<statically_named_arg<T, Char, N, Str>> : std::true_type {}; + +template <typename T, typename Char, size_t N, fixed_string<Char, N> Str> +struct is_statically_named_arg<statically_named_arg<T, Char, N, Str>> + : std::true_type {}; + +template <typename Char, size_t N, fixed_string<Char, N> Str> struct udl_arg { + template <typename T> auto operator=(T&& value) const { + return statically_named_arg<T, Char, N, Str>(std::forward<T>(value)); + } +}; +# else +template <typename Char> struct udl_arg { + const Char* str; + + template <typename T> named_arg<Char, T> operator=(T&& value) const { + return {str, std::forward<T>(value)}; + } +}; +# endif +} // namespace detail +FMT_MODULE_EXPORT_BEGIN + +inline namespace literals { +/** + \rst + User-defined literal equivalent of :func:`fmt::format`. + + **Example**:: + + using namespace fmt::literals; + std::string message = "The answer is {}"_format(42); + \endrst + */ +constexpr detail::udl_formatter<char> operator"" _format(const char* s, + size_t n) { + return {{s, n}}; +} +constexpr detail::udl_formatter<wchar_t> operator"" _format(const wchar_t* s, + size_t n) { + return {{s, n}}; +} + +/** + \rst + User-defined literal equivalent of :func:`fmt::arg`. + + **Example**:: + + using namespace fmt::literals; + fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); + \endrst + */ +# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +template <detail::fixed_string Str> +constexpr detail::udl_arg<remove_cvref_t<decltype(Str.data[0])>, + sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str> +operator""_a() { + return {}; +} +# else +constexpr detail::udl_arg<char> operator"" _a(const char* s, size_t) { + return {s}; +} +constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) { + return {s}; +} +# endif +} // namespace literals + +FMT_MODULE_EXPORT_END +#endif // FMT_USE_USER_DEFINED_LITERALS +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline +# include "format-inl.h" +#else +# define FMT_FUNC +#endif + +#endif // FMT_FORMAT_H_ diff --git a/libs/fmt/include/fmt/locale.h b/libs/fmt/include/fmt/locale.h new file mode 100644 index 0000000000000000000000000000000000000000..e79140a1a19a1ad7095aa1046d4397a3482c1c89 --- /dev/null +++ b/libs/fmt/include/fmt/locale.h @@ -0,0 +1,67 @@ +// Formatting library for C++ - std::locale support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_LOCALE_H_ +#define FMT_LOCALE_H_ + +#include <locale> + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +namespace detail { +template <typename Char> +std::basic_string<Char> vformat( + const std::locale& loc, basic_string_view<Char> format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + basic_memory_buffer<Char> buffer; + detail::vformat_to(buffer, format_str, args, detail::locale_ref(loc)); + return fmt::to_string(buffer); +} +} // namespace detail + +FMT_MODULE_EXPORT_BEGIN + +template <typename S, typename Char = char_t<S>> +inline std::basic_string<Char> vformat( + const std::locale& loc, const S& format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + return detail::vformat(loc, to_string_view(format_str), args); +} + +template <typename S, typename... Args, typename Char = char_t<S>> +inline std::basic_string<Char> format(const std::locale& loc, + const S& format_str, Args&&... args) { + return detail::vformat(loc, to_string_view(format_str), + fmt::make_args_checked<Args...>(format_str, args...)); +} + +template <typename S, typename OutputIt, typename... Args, + typename Char = char_t<S>, + FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)> +inline OutputIt vformat_to( + OutputIt out, const std::locale& loc, const S& format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out)); + vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc)); + return detail::get_iterator(buf); +} + +template <typename OutputIt, typename S, typename... Args, + bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value> +inline auto format_to(OutputIt out, const std::locale& loc, const S& format_str, + Args&&... args) -> + typename std::enable_if<enable, OutputIt>::type { + const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); + return vformat_to(out, loc, to_string_view(format_str), vargs); +} + +FMT_MODULE_EXPORT_END +FMT_END_NAMESPACE + +#endif // FMT_LOCALE_H_ diff --git a/libs/fmt/include/fmt/os.h b/libs/fmt/include/fmt/os.h new file mode 100644 index 0000000000000000000000000000000000000000..14c8c8b6fb8e0d3032e7800ec90647c80784d190 --- /dev/null +++ b/libs/fmt/include/fmt/os.h @@ -0,0 +1,502 @@ +// Formatting library for C++ - optional OS-specific functionality +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_OS_H_ +#define FMT_OS_H_ + +#include <cerrno> +#include <clocale> // for locale_t +#include <cstddef> +#include <cstdio> +#include <cstdlib> // for strtod_l + +#if defined __APPLE__ || defined(__FreeBSD__) +# include <xlocale.h> // for LC_NUMERIC_MASK on OS X +#endif + +#include "format.h" + +// UWP doesn't provide _pipe. +#if FMT_HAS_INCLUDE("winapifamily.h") +# include <winapifamily.h> +#endif +#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \ + defined(__linux__)) && \ + (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# include <fcntl.h> // for O_RDONLY +# define FMT_USE_FCNTL 1 +#else +# define FMT_USE_FCNTL 0 +#endif + +#ifndef FMT_POSIX +# if defined(_WIN32) && !defined(__MINGW32__) +// Fix warnings about deprecated symbols. +# define FMT_POSIX(call) _##call +# else +# define FMT_POSIX(call) call +# endif +#endif + +// Calls to system functions are wrapped in FMT_SYSTEM for testability. +#ifdef FMT_SYSTEM +# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) +#else +# define FMT_SYSTEM(call) ::call +# ifdef _WIN32 +// Fix warnings about deprecated symbols. +# define FMT_POSIX_CALL(call) ::_##call +# else +# define FMT_POSIX_CALL(call) ::call +# endif +#endif + +// Retries the expression while it evaluates to error_result and errno +// equals to EINTR. +#ifndef _WIN32 +# define FMT_RETRY_VAL(result, expression, error_result) \ + do { \ + (result) = (expression); \ + } while ((result) == (error_result) && errno == EINTR) +#else +# define FMT_RETRY_VAL(result, expression, error_result) result = (expression) +#endif + +#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) + +FMT_BEGIN_NAMESPACE + +/** + \rst + A reference to a null-terminated string. It can be constructed from a C + string or ``std::string``. + + You can use one of the following type aliases for common character types: + + +---------------+-----------------------------+ + | Type | Definition | + +===============+=============================+ + | cstring_view | basic_cstring_view<char> | + +---------------+-----------------------------+ + | wcstring_view | basic_cstring_view<wchar_t> | + +---------------+-----------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template <typename... Args> + std::string format(cstring_view format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template <typename Char> class basic_cstring_view { + private: + const Char* data_; + + public: + /** Constructs a string reference object from a C string. */ + basic_cstring_view(const Char* s) : data_(s) {} + + /** + \rst + Constructs a string reference from an ``std::string`` object. + \endrst + */ + basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {} + + /** Returns the pointer to a C string. */ + const Char* c_str() const { return data_; } +}; + +using cstring_view = basic_cstring_view<char>; +using wcstring_view = basic_cstring_view<wchar_t>; + +// An error code. +class error_code { + private: + int value_; + + public: + explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} + + int get() const FMT_NOEXCEPT { return value_; } +}; + +#ifdef _WIN32 +namespace detail { +// A converter from UTF-16 to UTF-8. +// It is only provided for Windows since other systems support UTF-8 natively. +class utf16_to_utf8 { + private: + memory_buffer buffer_; + + public: + utf16_to_utf8() {} + FMT_API explicit utf16_to_utf8(wstring_view s); + operator string_view() const { return string_view(&buffer_[0], size()); } + size_t size() const { return buffer_.size() - 1; } + const char* c_str() const { return &buffer_[0]; } + std::string str() const { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a system error code instead of + // throwing exception on conversion error. This method may still throw + // in case of memory allocation error. + FMT_API int convert(wstring_view s); +}; + +FMT_API void format_windows_error(buffer<char>& out, int error_code, + string_view message) FMT_NOEXCEPT; +} // namespace detail + +/** A Windows error. */ +class windows_error : public system_error { + private: + FMT_API void init(int error_code, string_view format_str, format_args args); + + public: + /** + \rst + Constructs a :class:`fmt::windows_error` object with the description + of the form + + .. parsed-literal:: + *<message>*: *<system-message>* + + where *<message>* is the formatted message and *<system-message>* is the + system message corresponding to the error code. + *error_code* is a Windows error code as given by ``GetLastError``. + If *error_code* is not a valid error code such as -1, the system message + will look like "error -1". + + **Example**:: + + // This throws a windows_error with the description + // cannot open file 'madeup': The system cannot find the file specified. + // or similar (system message may vary). + const char *filename = "madeup"; + LPOFSTRUCT of = LPOFSTRUCT(); + HFILE file = OpenFile(filename, &of, OF_READ); + if (file == HFILE_ERROR) { + throw fmt::windows_error(GetLastError(), + "cannot open file '{}'", filename); + } + \endrst + */ + template <typename... Args> + windows_error(int error_code, string_view message, const Args&... args) { + init(error_code, message, make_format_args(args...)); + } +}; + +// Reports a Windows error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_windows_error(int error_code, + string_view message) FMT_NOEXCEPT; +#endif // _WIN32 + +// std::system is not available on some platforms such as iOS (#2248). +#ifdef __OSX__ +template <typename S, typename... Args, typename Char = char_t<S>> +void say(const S& format_str, Args&&... args) { + std::system(format("say \"{}\"", format(format_str, args...)).c_str()); +} +#endif + +// A buffered file. +class buffered_file { + private: + FILE* file_; + + friend class file; + + explicit buffered_file(FILE* f) : file_(f) {} + + public: + buffered_file(const buffered_file&) = delete; + void operator=(const buffered_file&) = delete; + + // Constructs a buffered_file object which doesn't represent any file. + buffered_file() FMT_NOEXCEPT : file_(nullptr) {} + + // Destroys the object closing the file it represents if any. + FMT_API ~buffered_file() FMT_NOEXCEPT; + + public: + buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) { + other.file_ = nullptr; + } + + buffered_file& operator=(buffered_file&& other) { + close(); + file_ = other.file_; + other.file_ = nullptr; + return *this; + } + + // Opens a file. + FMT_API buffered_file(cstring_view filename, cstring_view mode); + + // Closes the file. + FMT_API void close(); + + // Returns the pointer to a FILE object representing this file. + FILE* get() const FMT_NOEXCEPT { return file_; } + + // We place parentheses around fileno to workaround a bug in some versions + // of MinGW that define fileno as a macro. + FMT_API int(fileno)() const; + + void vprint(string_view format_str, format_args args) { + fmt::vprint(file_, format_str, args); + } + + template <typename... Args> + inline void print(string_view format_str, const Args&... args) { + vprint(format_str, make_format_args(args...)); + } +}; + +#if FMT_USE_FCNTL +// A file. Closed file is represented by a file object with descriptor -1. +// Methods that are not declared with FMT_NOEXCEPT may throw +// fmt::system_error in case of failure. Note that some errors such as +// closing the file multiple times will cause a crash on Windows rather +// than an exception. You can get standard behavior by overriding the +// invalid parameter handler with _set_invalid_parameter_handler. +class file { + private: + int fd_; // File descriptor. + + // Constructs a file object with a given descriptor. + explicit file(int fd) : fd_(fd) {} + + public: + // Possible values for the oflag argument to the constructor. + enum { + RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. + WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. + RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing. + CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist. + APPEND = FMT_POSIX(O_APPEND), // Open in append mode. + TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file. + }; + + // Constructs a file object which doesn't represent any file. + file() FMT_NOEXCEPT : fd_(-1) {} + + // Opens a file and constructs a file object representing this file. + FMT_API file(cstring_view path, int oflag); + + public: + file(const file&) = delete; + void operator=(const file&) = delete; + + file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; } + + // Move assignment is not noexcept because close may throw. + file& operator=(file&& other) { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } + + // Destroys the object closing the file it represents if any. + FMT_API ~file() FMT_NOEXCEPT; + + // Returns the file descriptor. + int descriptor() const FMT_NOEXCEPT { return fd_; } + + // Closes the file. + FMT_API void close(); + + // Returns the file size. The size has signed type for consistency with + // stat::st_size. + FMT_API long long size() const; + + // Attempts to read count bytes from the file into the specified buffer. + FMT_API size_t read(void* buffer, size_t count); + + // Attempts to write count bytes from the specified buffer to the file. + FMT_API size_t write(const void* buffer, size_t count); + + // Duplicates a file descriptor with the dup function and returns + // the duplicate as a file object. + FMT_API static file dup(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT; + + // Creates a pipe setting up read_end and write_end file objects for reading + // and writing respectively. + FMT_API static void pipe(file& read_end, file& write_end); + + // Creates a buffered_file object associated with this file and detaches + // this file object from the file. + FMT_API buffered_file fdopen(const char* mode); +}; + +// Returns the memory page size. +long getpagesize(); + +namespace detail { + +struct buffer_size { + buffer_size() = default; + size_t value = 0; + buffer_size operator=(size_t val) const { + auto bs = buffer_size(); + bs.value = val; + return bs; + } +}; + +struct ostream_params { + int oflag = file::WRONLY | file::CREATE | file::TRUNC; + size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768; + + ostream_params() {} + + template <typename... T> + ostream_params(T... params, int new_oflag) : ostream_params(params...) { + oflag = new_oflag; + } + + template <typename... T> + ostream_params(T... params, detail::buffer_size bs) + : ostream_params(params...) { + this->buffer_size = bs.value; + } +}; +} // namespace detail + +static constexpr detail::buffer_size buffer_size; + +/** A fast output stream which is not thread-safe. */ +class ostream final : private detail::buffer<char> { + private: + file file_; + + void flush() { + if (size() == 0) return; + file_.write(data(), size()); + clear(); + } + + FMT_API void grow(size_t) override final; + + ostream(cstring_view path, const detail::ostream_params& params) + : file_(path, params.oflag) { + set(new char[params.buffer_size], params.buffer_size); + } + + public: + ostream(ostream&& other) + : detail::buffer<char>(other.data(), other.size(), other.capacity()), + file_(std::move(other.file_)) { + other.clear(); + other.set(nullptr, 0); + } + ~ostream() { + flush(); + delete[] data(); + } + + template <typename... T> + friend ostream output_file(cstring_view path, T... params); + + void close() { + flush(); + file_.close(); + } + + /** + Formats ``args`` according to specifications in ``format_str`` and writes + the output to the file. + */ + template <typename S, typename... Args> + void print(const S& format_str, Args&&... args) { + format_to(detail::buffer_appender<char>(*this), format_str, + std::forward<Args>(args)...); + } +}; + +/** + \rst + Opens a file for writing. Supported parameters passed in *params*: + + * ``<integer>``: Flags passed to `open + <https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_ + (``file::WRONLY | file::CREATE`` by default) + * ``buffer_size=<integer>``: Output buffer size + + **Example**:: + + auto out = fmt::output_file("guide.txt"); + out.print("Don't {}", "Panic"); + \endrst + */ +template <typename... T> +inline ostream output_file(cstring_view path, T... params) { + return {path, detail::ostream_params(params...)}; +} +#endif // FMT_USE_FCNTL + +#ifdef FMT_LOCALE +// A "C" numeric locale. +class locale { + private: +# ifdef _WIN32 + using locale_t = _locale_t; + + static void freelocale(locale_t loc) { _free_locale(loc); } + + static double strtod_l(const char* nptr, char** endptr, _locale_t loc) { + return _strtod_l(nptr, endptr, loc); + } +# endif + + locale_t locale_; + + public: + using type = locale_t; + locale(const locale&) = delete; + void operator=(const locale&) = delete; + + locale() { +# ifndef _WIN32 + locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr)); +# else + locale_ = _create_locale(LC_NUMERIC, "C"); +# endif + if (!locale_) FMT_THROW(system_error(errno, "cannot create locale")); + } + ~locale() { freelocale(locale_); } + + type get() const { return locale_; } + + // Converts string to floating-point number and advances str past the end + // of the parsed input. + double strtod(const char*& str) const { + char* end = nullptr; + double result = strtod_l(str, &end, locale_); + str = end; + return result; + } +}; +using Locale FMT_DEPRECATED_ALIAS = locale; +#endif // FMT_LOCALE +FMT_END_NAMESPACE + +#endif // FMT_OS_H_ diff --git a/libs/fmt/include/fmt/ostream.h b/libs/fmt/include/fmt/ostream.h new file mode 100644 index 0000000000000000000000000000000000000000..d66248a6012e87d06cd607fcd5366db65a22e14e --- /dev/null +++ b/libs/fmt/include/fmt/ostream.h @@ -0,0 +1,181 @@ +// Formatting library for C++ - std::ostream support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_OSTREAM_H_ +#define FMT_OSTREAM_H_ + +#include <ostream> + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +template <typename Char> class basic_printf_parse_context; +template <typename OutputIt, typename Char> class basic_printf_context; + +namespace detail { + +template <class Char> class formatbuf : public std::basic_streambuf<Char> { + private: + using int_type = typename std::basic_streambuf<Char>::int_type; + using traits_type = typename std::basic_streambuf<Char>::traits_type; + + buffer<Char>& buffer_; + + public: + formatbuf(buffer<Char>& buf) : buffer_(buf) {} + + protected: + // The put-area is actually always empty. This makes the implementation + // simpler and has the advantage that the streambuf and the buffer are always + // in sync and sputc never writes into uninitialized memory. The obvious + // disadvantage is that each call to sputc always results in a (virtual) call + // to overflow. There is no disadvantage here for sputn since this always + // results in a call to xsputn. + + int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast<Char>(ch)); + return ch; + } + + std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE { + buffer_.append(s, s + count); + return count; + } +}; + +struct converter { + template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T); +}; + +template <typename Char> struct test_stream : std::basic_ostream<Char> { + private: + void_t<> operator<<(converter); +}; + +// Hide insertion operators for built-in types. +template <typename Char, typename Traits> +void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char); +template <typename Char, typename Traits> +void_t<> operator<<(std::basic_ostream<Char, Traits>&, char); +template <typename Traits> +void_t<> operator<<(std::basic_ostream<char, Traits>&, char); +template <typename Traits> +void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char); +template <typename Traits> +void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char); + +// Checks if T has a user-defined operator<< (e.g. not a member of +// std::ostream). +template <typename T, typename Char> class is_streamable { + private: + template <typename U> + static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>() + << std::declval<U>()), + void_t<>>::value> + test(int); + + template <typename> static std::false_type test(...); + + using result = decltype(test<T>(0)); + + public: + is_streamable() = default; + + static const bool value = result::value; +}; + +// Write the content of buf to os. +template <typename Char> +void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) { + const Char* buf_data = buf.data(); + using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; + unsigned_streamsize size = buf.size(); + unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>()); + do { + unsigned_streamsize n = size <= max_size ? size : max_size; + os.write(buf_data, static_cast<std::streamsize>(n)); + buf_data += n; + size -= n; + } while (size != 0); +} + +template <typename Char, typename T> +void format_value(buffer<Char>& buf, const T& value, + locale_ref loc = locale_ref()) { + formatbuf<Char> format_buf(buf); + std::basic_ostream<Char> output(&format_buf); +#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) + if (loc) output.imbue(loc.get<std::locale>()); +#endif + output << value; + output.exceptions(std::ios_base::failbit | std::ios_base::badbit); + buf.try_resize(buf.size()); +} + +// Formats an object of type T that has an overloaded ostream operator<<. +template <typename T, typename Char> +struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> + : private formatter<basic_string_view<Char>, Char> { + FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) + -> decltype(ctx.begin()) { + return formatter<basic_string_view<Char>, Char>::parse(ctx); + } + template <typename ParseCtx, + FMT_ENABLE_IF(std::is_same< + ParseCtx, basic_printf_parse_context<Char>>::value)> + auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template <typename OutputIt> + auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) + -> OutputIt { + basic_memory_buffer<Char> buffer; + format_value(buffer, value, ctx.locale()); + basic_string_view<Char> str(buffer.data(), buffer.size()); + return formatter<basic_string_view<Char>, Char>::format(str, ctx); + } + template <typename OutputIt> + auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx) + -> OutputIt { + basic_memory_buffer<Char> buffer; + format_value(buffer, value, ctx.locale()); + return std::copy(buffer.begin(), buffer.end(), ctx.out()); + } +}; +} // namespace detail + +FMT_MODULE_EXPORT +template <typename Char> +void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) { + basic_memory_buffer<Char> buffer; + detail::vformat_to(buffer, format_str, args); + detail::write_buffer(os, buffer); +} + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fmt::print(cerr, "Don't {}!", "panic"); + \endrst + */ +FMT_MODULE_EXPORT +template <typename S, typename... Args, + typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> +void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) { + vprint(os, to_string_view(format_str), + fmt::make_args_checked<Args...>(format_str, args...)); +} +FMT_END_NAMESPACE + +#endif // FMT_OSTREAM_H_ diff --git a/libs/fmt/include/fmt/printf.h b/libs/fmt/include/fmt/printf.h new file mode 100644 index 0000000000000000000000000000000000000000..ee6a20c8c12f8195fcbd5090c9c6ed2bcc8c4c2b --- /dev/null +++ b/libs/fmt/include/fmt/printf.h @@ -0,0 +1,730 @@ +// Formatting library for C++ - legacy printf implementation +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_PRINTF_H_ +#define FMT_PRINTF_H_ + +#include <algorithm> // std::max +#include <limits> // std::numeric_limits +#include <ostream> + +#include "format.h" + +FMT_BEGIN_NAMESPACE +namespace detail { + +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template <bool IsSigned> struct int_checker { + template <typename T> static bool fits_in_int(T value) { + unsigned max = max_value<int>(); + return value <= max; + } + static bool fits_in_int(bool) { return true; } +}; + +template <> struct int_checker<true> { + template <typename T> static bool fits_in_int(T value) { + return value >= (std::numeric_limits<int>::min)() && + value <= max_value<int>(); + } + static bool fits_in_int(int) { return true; } +}; + +class printf_precision_handler { + public: + template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> + int operator()(T value) { + if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) + FMT_THROW(format_error("number is too big")); + return (std::max)(static_cast<int>(value), 0); + } + + template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> + int operator()(T) { + FMT_THROW(format_error("precision is not integer")); + return 0; + } +}; + +// An argument visitor that returns true iff arg is a zero integer. +class is_zero_int { + public: + template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> + bool operator()(T value) { + return value == 0; + } + + template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> + bool operator()(T) { + return false; + } +}; + +template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {}; + +template <> struct make_unsigned_or_bool<bool> { using type = bool; }; + +template <typename T, typename Context> class arg_converter { + private: + using char_type = typename Context::char_type; + + basic_format_arg<Context>& arg_; + char_type type_; + + public: + arg_converter(basic_format_arg<Context>& arg, char_type type) + : arg_(arg), type_(type) {} + + void operator()(bool value) { + if (type_ != 's') operator()<bool>(value); + } + + template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)> + void operator()(U value) { + bool is_signed = type_ == 'd' || type_ == 'i'; + using target_type = conditional_t<std::is_same<T, void>::value, U, T>; + if (const_check(sizeof(target_type) <= sizeof(int))) { + // Extra casts are used to silence warnings. + if (is_signed) { + arg_ = detail::make_arg<Context>( + static_cast<int>(static_cast<target_type>(value))); + } else { + using unsigned_type = typename make_unsigned_or_bool<target_type>::type; + arg_ = detail::make_arg<Context>( + static_cast<unsigned>(static_cast<unsigned_type>(value))); + } + } else { + if (is_signed) { + // glibc's printf doesn't sign extend arguments of smaller types: + // std::printf("%lld", -42); // prints "4294967254" + // but we don't have to do the same because it's a UB. + arg_ = detail::make_arg<Context>(static_cast<long long>(value)); + } else { + arg_ = detail::make_arg<Context>( + static_cast<typename make_unsigned_or_bool<U>::type>(value)); + } + } + } + + template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)> + void operator()(U) {} // No conversion needed for non-integral types. +}; + +// Converts an integer argument to T for printf, if T is an integral type. +// If T is void, the argument is converted to corresponding signed or unsigned +// type depending on the type specifier: 'd' and 'i' - signed, other - +// unsigned). +template <typename T, typename Context, typename Char> +void convert_arg(basic_format_arg<Context>& arg, Char type) { + visit_format_arg(arg_converter<T, Context>(arg, type), arg); +} + +// Converts an integer argument to char for printf. +template <typename Context> class char_converter { + private: + basic_format_arg<Context>& arg_; + + public: + explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {} + + template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> + void operator()(T value) { + arg_ = detail::make_arg<Context>( + static_cast<typename Context::char_type>(value)); + } + + template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> + void operator()(T) {} // No conversion needed for non-integral types. +}; + +// An argument visitor that return a pointer to a C string if argument is a +// string or null otherwise. +template <typename Char> struct get_cstring { + template <typename T> const Char* operator()(T) { return nullptr; } + const Char* operator()(const Char* s) { return s; } +}; + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +template <typename Char> class printf_width_handler { + private: + using format_specs = basic_format_specs<Char>; + + format_specs& specs_; + + public: + explicit printf_width_handler(format_specs& specs) : specs_(specs) {} + + template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> + unsigned operator()(T value) { + auto width = static_cast<uint32_or_64_or_128_t<T>>(value); + if (detail::is_negative(value)) { + specs_.align = align::left; + width = 0 - width; + } + unsigned int_max = max_value<int>(); + if (width > int_max) FMT_THROW(format_error("number is too big")); + return static_cast<unsigned>(width); + } + + template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> + unsigned operator()(T) { + FMT_THROW(format_error("width is not integer")); + return 0; + } +}; + +template <typename Char, typename Context> +void vprintf(buffer<Char>& buf, basic_string_view<Char> format, + basic_format_args<Context> args) { + Context(buffer_appender<Char>(buf), format, args).format(); +} +} // namespace detail + +// For printing into memory_buffer. +template <typename Char, typename Context> +FMT_DEPRECATED void printf(detail::buffer<Char>& buf, + basic_string_view<Char> format, + basic_format_args<Context> args) { + return detail::vprintf(buf, format, args); +} +using detail::vprintf; + +FMT_MODULE_EXPORT_BEGIN + +template <typename Char> +class basic_printf_parse_context : public basic_format_parse_context<Char> { + using basic_format_parse_context<Char>::basic_format_parse_context; +}; +template <typename OutputIt, typename Char> class basic_printf_context; + +FMT_MODULE_EXPORT_END + +/** + \rst + The ``printf`` argument formatter. + \endrst + */ +template <typename OutputIt, typename Char> +class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> { + private: + using base = detail::arg_formatter_base<OutputIt, Char>; + using context_type = basic_printf_context<OutputIt, Char>; + using format_specs = typename base::format_specs; + + context_type& context_; + + OutputIt write_null_pointer(bool is_string = false) { + auto s = this->specs(); + s.type = 0; + return detail::write(this->out(), + string_view(is_string ? "(null)" : "(nil)"), s); + } + + public: + printf_arg_formatter(OutputIt iter, format_specs& specs, context_type& ctx) + : base(iter, specs, detail::locale_ref()), context_(ctx) {} + + OutputIt operator()(monostate value) { return base::operator()(value); } + + template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)> + OutputIt operator()(T value) { + // MSVC2013 fails to compile separate overloads for bool and Char so use + // std::is_same instead. + if (std::is_same<T, Char>::value) { + format_specs fmt_specs = this->specs(); + if (fmt_specs.type && fmt_specs.type != 'c') + return (*this)(static_cast<int>(value)); + fmt_specs.sign = sign::none; + fmt_specs.alt = false; + fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types. + // align::numeric needs to be overwritten here since the '0' flag is + // ignored for non-numeric types + if (fmt_specs.align == align::none || fmt_specs.align == align::numeric) + fmt_specs.align = align::right; + return write_char(this->out(), static_cast<Char>(value), fmt_specs); + } + return base::operator()(value); + } + + template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> + OutputIt operator()(T value) { + return base::operator()(value); + } + + /** Formats a null-terminated C string. */ + OutputIt operator()(const char* value) { + if (value) return base::operator()(value); + return write_null_pointer(this->specs().type != 'p'); + } + + /** Formats a null-terminated wide C string. */ + OutputIt operator()(const wchar_t* value) { + if (value) return base::operator()(value); + return write_null_pointer(this->specs().type != 'p'); + } + + OutputIt operator()(basic_string_view<Char> value) { + return base::operator()(value); + } + + /** Formats a pointer. */ + OutputIt operator()(const void* value) { + return value ? base::operator()(value) : write_null_pointer(); + } + + /** Formats an argument of a custom (user-defined) type. */ + OutputIt operator()(typename basic_format_arg<context_type>::handle handle) { + handle.format(context_.parse_context(), context_); + return this->out(); + } +}; + +template <typename T> struct printf_formatter { + printf_formatter() = delete; + + template <typename ParseContext> + auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template <typename FormatContext> + auto format(const T&, FormatContext& ctx) -> decltype(ctx.out()) { + return ctx.out(); + } +}; + +/** + This template formats data and writes the output through an output iterator. + */ +template <typename OutputIt, typename Char> class basic_printf_context { + public: + /** The character type for the output. */ + using char_type = Char; + using iterator = OutputIt; + using format_arg = basic_format_arg<basic_printf_context>; + using parse_context_type = basic_printf_parse_context<Char>; + template <typename T> using formatter_type = printf_formatter<T>; + + private: + using format_specs = basic_format_specs<char_type>; + + OutputIt out_; + basic_format_args<basic_printf_context> args_; + parse_context_type parse_ctx_; + + static void parse_flags(format_specs& specs, const Char*& it, + const Char* end); + + // Returns the argument with specified index or, if arg_index is -1, the next + // argument. + format_arg get_arg(int arg_index = -1); + + // Parses argument index, flags and width and returns the argument index. + int parse_header(const Char*& it, const Char* end, format_specs& specs); + + public: + /** + \rst + Constructs a ``printf_context`` object. References to the arguments are + stored in the context object so make sure they have appropriate lifetimes. + \endrst + */ + basic_printf_context(OutputIt out, basic_string_view<char_type> format_str, + basic_format_args<basic_printf_context> args) + : out_(out), args_(args), parse_ctx_(format_str) {} + + OutputIt out() { return out_; } + void advance_to(OutputIt it) { out_ = it; } + + detail::locale_ref locale() { return {}; } + + format_arg arg(int id) const { return args_.get(id); } + + parse_context_type& parse_context() { return parse_ctx_; } + + FMT_CONSTEXPR void on_error(const char* message) { + parse_ctx_.on_error(message); + } + + /** Formats stored arguments and writes the output to the range. */ + template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>> + OutputIt format(); +}; + +template <typename OutputIt, typename Char> +void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs, + const Char*& it, + const Char* end) { + for (; it != end; ++it) { + switch (*it) { + case '-': + specs.align = align::left; + break; + case '+': + specs.sign = sign::plus; + break; + case '0': + specs.fill[0] = '0'; + break; + case ' ': + if (specs.sign != sign::plus) { + specs.sign = sign::space; + } + break; + case '#': + specs.alt = true; + break; + default: + return; + } + } +} + +template <typename OutputIt, typename Char> +typename basic_printf_context<OutputIt, Char>::format_arg +basic_printf_context<OutputIt, Char>::get_arg(int arg_index) { + if (arg_index < 0) + arg_index = parse_ctx_.next_arg_id(); + else + parse_ctx_.check_arg_id(--arg_index); + return detail::get_arg(*this, arg_index); +} + +template <typename OutputIt, typename Char> +int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it, + const Char* end, + format_specs& specs) { + int arg_index = -1; + char_type c = *it; + if (c >= '0' && c <= '9') { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + detail::error_handler eh; + int value = parse_nonnegative_int(it, end, eh); + if (it != end && *it == '$') { // value is an argument index + ++it; + arg_index = value; + } else { + if (c == '0') specs.fill[0] = '0'; + if (value != 0) { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + specs.width = value; + return arg_index; + } + } + } + parse_flags(specs, it, end); + // Parse width. + if (it != end) { + if (*it >= '0' && *it <= '9') { + detail::error_handler eh; + specs.width = parse_nonnegative_int(it, end, eh); + } else if (*it == '*') { + ++it; + specs.width = static_cast<int>(visit_format_arg( + detail::printf_width_handler<char_type>(specs), get_arg())); + } + } + return arg_index; +} + +template <typename OutputIt, typename Char> +template <typename ArgFormatter> +OutputIt basic_printf_context<OutputIt, Char>::format() { + auto out = this->out(); + const Char* start = parse_ctx_.begin(); + const Char* end = parse_ctx_.end(); + auto it = start; + while (it != end) { + if (!detail::find<false, Char>(it, end, '%', it)) { + it = end; // detail::find leaves it == nullptr if it doesn't find '%' + break; + } + char_type c = *it++; + if (it != end && *it == c) { + out = detail::write( + out, basic_string_view<Char>(start, detail::to_unsigned(it - start))); + start = ++it; + continue; + } + out = detail::write(out, basic_string_view<Char>( + start, detail::to_unsigned(it - 1 - start))); + + format_specs specs; + specs.align = align::right; + + // Parse argument index, flags and width. + int arg_index = parse_header(it, end, specs); + if (arg_index == 0) on_error("argument not found"); + + // Parse precision. + if (it != end && *it == '.') { + ++it; + c = it != end ? *it : 0; + if ('0' <= c && c <= '9') { + detail::error_handler eh; + specs.precision = parse_nonnegative_int(it, end, eh); + } else if (c == '*') { + ++it; + specs.precision = static_cast<int>( + visit_format_arg(detail::printf_precision_handler(), get_arg())); + } else { + specs.precision = 0; + } + } + + format_arg arg = get_arg(arg_index); + // For d, i, o, u, x, and X conversion specifiers, if a precision is + // specified, the '0' flag is ignored + if (specs.precision >= 0 && arg.is_integral()) + specs.fill[0] = + ' '; // Ignore '0' flag for non-numeric types or if '-' present. + if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) { + auto str = visit_format_arg(detail::get_cstring<Char>(), arg); + auto str_end = str + specs.precision; + auto nul = std::find(str, str_end, Char()); + arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>( + str, + detail::to_unsigned(nul != str_end ? nul - str : specs.precision))); + } + if (specs.alt && visit_format_arg(detail::is_zero_int(), arg)) + specs.alt = false; + if (specs.fill[0] == '0') { + if (arg.is_arithmetic() && specs.align != align::left) + specs.align = align::numeric; + else + specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-' + // flag is also present. + } + + // Parse length and convert the argument to the required type. + c = it != end ? *it++ : 0; + char_type t = it != end ? *it : 0; + using detail::convert_arg; + switch (c) { + case 'h': + if (t == 'h') { + ++it; + t = it != end ? *it : 0; + convert_arg<signed char>(arg, t); + } else { + convert_arg<short>(arg, t); + } + break; + case 'l': + if (t == 'l') { + ++it; + t = it != end ? *it : 0; + convert_arg<long long>(arg, t); + } else { + convert_arg<long>(arg, t); + } + break; + case 'j': + convert_arg<intmax_t>(arg, t); + break; + case 'z': + convert_arg<size_t>(arg, t); + break; + case 't': + convert_arg<std::ptrdiff_t>(arg, t); + break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: + --it; + convert_arg<void>(arg, c); + } + + // Parse type. + if (it == end) FMT_THROW(format_error("invalid format string")); + specs.type = static_cast<char>(*it++); + if (arg.is_integral()) { + // Normalize type. + switch (specs.type) { + case 'i': + case 'u': + specs.type = 'd'; + break; + case 'c': + visit_format_arg(detail::char_converter<basic_printf_context>(arg), + arg); + break; + } + } + + start = it; + + // Format argument. + out = visit_format_arg(ArgFormatter(out, specs, *this), arg); + } + return detail::write( + out, basic_string_view<Char>(start, detail::to_unsigned(it - start))); +} + +FMT_MODULE_EXPORT_BEGIN + +template <typename Char> +using basic_printf_context_t = + basic_printf_context<detail::buffer_appender<Char>, Char>; + +using printf_context = basic_printf_context_t<char>; +using wprintf_context = basic_printf_context_t<wchar_t>; + +using printf_args = basic_format_args<printf_context>; +using wprintf_args = basic_format_args<wprintf_context>; + +/** + \rst + Constructs an `~fmt::format_arg_store` object that contains references to + arguments and can be implicitly converted to `~fmt::printf_args`. + \endrst + */ +template <typename... Args> +inline format_arg_store<printf_context, Args...> make_printf_args( + const Args&... args) { + return {args...}; +} + +/** + \rst + Constructs an `~fmt::format_arg_store` object that contains references to + arguments and can be implicitly converted to `~fmt::wprintf_args`. + \endrst + */ +template <typename... Args> +inline format_arg_store<wprintf_context, Args...> make_wprintf_args( + const Args&... args) { + return {args...}; +} + +template <typename S, typename Char = char_t<S>> +inline std::basic_string<Char> vsprintf( + const S& format, + basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { + basic_memory_buffer<Char> buffer; + vprintf(buffer, to_string_view(format), args); + return to_string(buffer); +} + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + std::string message = fmt::sprintf("The answer is %d", 42); + \endrst +*/ +template <typename S, typename... Args, + typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> +inline std::basic_string<Char> sprintf(const S& format, const Args&... args) { + using context = basic_printf_context_t<Char>; + return vsprintf(to_string_view(format), make_format_args<context>(args...)); +} + +template <typename S, typename Char = char_t<S>> +inline int vfprintf( + std::FILE* f, const S& format, + basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { + basic_memory_buffer<Char> buffer; + vprintf(buffer, to_string_view(format), args); + size_t size = buffer.size(); + return std::fwrite(buffer.data(), sizeof(Char), size, f) < size + ? -1 + : static_cast<int>(size); +} + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::fprintf(stderr, "Don't %s!", "panic"); + \endrst + */ +template <typename S, typename... Args, + typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> +inline int fprintf(std::FILE* f, const S& format, const Args&... args) { + using context = basic_printf_context_t<Char>; + return vfprintf(f, to_string_view(format), + make_format_args<context>(args...)); +} + +template <typename S, typename Char = char_t<S>> +inline int vprintf( + const S& format, + basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { + return vfprintf(stdout, to_string_view(format), args); +} + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::printf("Elapsed time: %.2f seconds", 1.23); + \endrst + */ +template <typename S, typename... Args, + FMT_ENABLE_IF(detail::is_string<S>::value)> +inline int printf(const S& format_str, const Args&... args) { + using context = basic_printf_context_t<char_t<S>>; + return vprintf(to_string_view(format_str), + make_format_args<context>(args...)); +} + +template <typename S, typename Char = char_t<S>> +inline int vfprintf( + std::basic_ostream<Char>& os, const S& format, + basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { + basic_memory_buffer<Char> buffer; + vprintf(buffer, to_string_view(format), args); + os.write(buffer.data(), static_cast<std::streamsize>(buffer.size())); + return static_cast<int>(buffer.size()); +} + +/** Formats arguments and writes the output to the range. */ +template <typename ArgFormatter, typename Char, + typename Context = + basic_printf_context<typename ArgFormatter::iterator, Char>> +typename ArgFormatter::iterator vprintf( + detail::buffer<Char>& out, basic_string_view<Char> format_str, + basic_format_args<type_identity_t<Context>> args) { + typename ArgFormatter::iterator iter(out); + Context(iter, format_str, args).template format<ArgFormatter>(); + return iter; +} + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fmt::fprintf(cerr, "Don't %s!", "panic"); + \endrst + */ +template <typename S, typename... Args, typename Char = char_t<S>> +inline int fprintf(std::basic_ostream<Char>& os, const S& format_str, + const Args&... args) { + using context = basic_printf_context_t<Char>; + return vfprintf(os, to_string_view(format_str), + make_format_args<context>(args...)); +} + +FMT_MODULE_EXPORT_END +FMT_END_NAMESPACE + +#endif // FMT_PRINTF_H_ diff --git a/libs/fmt/include/fmt/ranges.h b/libs/fmt/include/fmt/ranges.h new file mode 100644 index 0000000000000000000000000000000000000000..367d7a6ba340889542ad8c4ff01b6f479226f34a --- /dev/null +++ b/libs/fmt/include/fmt/ranges.h @@ -0,0 +1,469 @@ +// Formatting library for C++ - experimental range support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for ranges, containers and types tuple interface. + +#ifndef FMT_RANGES_H_ +#define FMT_RANGES_H_ + +#include <initializer_list> +#include <type_traits> + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +template <typename Char> struct formatting_base { + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } +}; + +template <typename Char, typename Enable = void> +struct formatting_range : formatting_base<Char> { +#ifdef FMT_DEPRECATED_BRACED_RANGES + Char prefix = '{'; + Char postfix = '}'; +#else + Char prefix = '['; + Char postfix = ']'; +#endif +}; + +template <typename Char, typename Enable = void> +struct formatting_tuple : formatting_base<Char> { + Char prefix = '('; + Char postfix = ')'; +}; + +namespace detail { + +template <typename RangeT, typename OutputIterator> +OutputIterator copy(const RangeT& range, OutputIterator out) { + for (auto it = range.begin(), end = range.end(); it != end; ++it) + *out++ = *it; + return out; +} + +template <typename OutputIterator> +OutputIterator copy(const char* str, OutputIterator out) { + while (*str) *out++ = *str++; + return out; +} + +template <typename OutputIterator> +OutputIterator copy(char ch, OutputIterator out) { + *out++ = ch; + return out; +} + +template <typename OutputIterator> +OutputIterator copy(wchar_t ch, OutputIterator out) { + *out++ = ch; + return out; +} + +/// Return true value if T has std::string interface, like std::string_view. +template <typename T> class is_std_string_like { + template <typename U> + static auto check(U* p) + -> decltype((void)p->find('a'), p->length(), (void)p->data(), int()); + template <typename> static void check(...); + + public: + static FMT_CONSTEXPR_DECL const bool value = + is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value; +}; + +template <typename Char> +struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {}; + +template <typename... Ts> struct conditional_helper {}; + +template <typename T, typename _ = void> struct is_range_ : std::false_type {}; + +#if !FMT_MSC_VER || FMT_MSC_VER > 1800 + +# define FMT_DECLTYPE_RETURN(val) \ + ->decltype(val) { return val; } \ + static_assert( \ + true, "") // This makes it so that a semicolon is required after the + // macro, which helps clang-format handle the formatting. + +// C array overload +template <typename T, std::size_t N> +auto range_begin(const T (&arr)[N]) -> const T* { + return arr; +} +template <typename T, std::size_t N> +auto range_end(const T (&arr)[N]) -> const T* { + return arr + N; +} + +template <typename T, typename Enable = void> +struct has_member_fn_begin_end_t : std::false_type {}; + +template <typename T> +struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()), + decltype(std::declval<T>().end())>> + : std::true_type {}; + +// Member function overload +template <typename T> +auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin()); +template <typename T> +auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end()); + +// ADL overload. Only participates in overload resolution if member functions +// are not found. +template <typename T> +auto range_begin(T&& rng) + -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value, + decltype(begin(static_cast<T&&>(rng)))> { + return begin(static_cast<T&&>(rng)); +} +template <typename T> +auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value, + decltype(end(static_cast<T&&>(rng)))> { + return end(static_cast<T&&>(rng)); +} + +template <typename T, typename Enable = void> +struct has_const_begin_end : std::false_type {}; +template <typename T, typename Enable = void> +struct has_mutable_begin_end : std::false_type {}; + +template <typename T> +struct has_const_begin_end< + T, void_t<decltype(detail::range_begin( + std::declval<const remove_cvref_t<T>&>())), + decltype(detail::range_begin( + std::declval<const remove_cvref_t<T>&>()))>> + : std::true_type {}; + +template <typename T> +struct has_mutable_begin_end< + T, void_t<decltype(detail::range_begin(std::declval<T>())), + decltype(detail::range_begin(std::declval<T>())), + enable_if_t<std::is_copy_constructible<T>::value>>> + : std::true_type {}; + +template <typename T> +struct is_range_<T, void> + : std::integral_constant<bool, (has_const_begin_end<T>::value || + has_mutable_begin_end<T>::value)> {}; + +template <typename T, typename Enable = void> struct range_to_view; +template <typename T> +struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> { + struct view_t { + const T* m_range_ptr; + + auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr)); + auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr)); + }; + static auto view(const T& range) -> view_t { return {&range}; } +}; + +template <typename T> +struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value && + has_mutable_begin_end<T>::value>> { + struct view_t { + T m_range_copy; + + auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy)); + auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy)); + }; + static auto view(const T& range) -> view_t { return {range}; } +}; +# undef FMT_DECLTYPE_RETURN +#endif + +/// tuple_size and tuple_element check. +template <typename T> class is_tuple_like_ { + template <typename U> + static auto check(U* p) -> decltype(std::tuple_size<U>::value, int()); + template <typename> static void check(...); + + public: + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void<decltype(check<T>(nullptr))>::value; +}; + +// Check for integer_sequence +#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900 +template <typename T, T... N> +using integer_sequence = std::integer_sequence<T, N...>; +template <size_t... N> using index_sequence = std::index_sequence<N...>; +template <size_t N> using make_index_sequence = std::make_index_sequence<N>; +#else +template <typename T, T... N> struct integer_sequence { + using value_type = T; + + static FMT_CONSTEXPR size_t size() { return sizeof...(N); } +}; + +template <size_t... N> using index_sequence = integer_sequence<size_t, N...>; + +template <typename T, size_t N, T... Ns> +struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {}; +template <typename T, T... Ns> +struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {}; + +template <size_t N> +using make_index_sequence = make_integer_sequence<size_t, N>; +#endif + +template <class Tuple, class F, size_t... Is> +void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT { + using std::get; + // using free function get<I>(T) now. + const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; + (void)_; // blocks warnings +} + +template <class T> +FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes( + T const&) { + return {}; +} + +template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) { + const auto indexes = get_indexes(tup); + for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); +} + +template <typename Range> +using value_type = + remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>; + +template <typename OutputIt> OutputIt write_delimiter(OutputIt out) { + *out++ = ','; + *out++ = ' '; + return out; +} + +template < + typename Char, typename OutputIt, typename Arg, + FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)> +OutputIt write_range_entry(OutputIt out, const Arg& v) { + *out++ = '"'; + out = write<Char>(out, v); + *out++ = '"'; + return out; +} + +template <typename Char, typename OutputIt, typename Arg, + FMT_ENABLE_IF(std::is_same<Arg, Char>::value)> +OutputIt write_range_entry(OutputIt out, const Arg v) { + *out++ = '\''; + *out++ = v; + *out++ = '\''; + return out; +} + +template < + typename Char, typename OutputIt, typename Arg, + FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value && + !std::is_same<Arg, Char>::value)> +OutputIt write_range_entry(OutputIt out, const Arg& v) { + return write<Char>(out, v); +} + +} // namespace detail + +template <typename T> struct is_tuple_like { + static FMT_CONSTEXPR_DECL const bool value = + detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value; +}; + +template <typename TupleT, typename Char> +struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> { + private: + // C++11 generic lambda for format() + template <typename FormatContext> struct format_each { + template <typename T> void operator()(const T& v) { + if (i > 0) out = detail::write_delimiter(out); + out = detail::write_range_entry<Char>(out, v); + ++i; + } + formatting_tuple<Char>& formatting; + size_t& i; + typename std::add_lvalue_reference<decltype( + std::declval<FormatContext>().out())>::type out; + }; + + public: + formatting_tuple<Char> formatting; + + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return formatting.parse(ctx); + } + + template <typename FormatContext = format_context> + auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + size_t i = 0; + + detail::copy(formatting.prefix, out); + detail::for_each(values, format_each<FormatContext>{formatting, i, out}); + detail::copy(formatting.postfix, out); + + return ctx.out(); + } +}; + +template <typename T, typename Char> struct is_range { + static FMT_CONSTEXPR_DECL const bool value = + detail::is_range_<T>::value && !detail::is_std_string_like<T>::value && + !std::is_convertible<T, std::basic_string<Char>>::value && + !std::is_constructible<detail::std_string_view<Char>, T>::value; +}; + +template <typename T, typename Char> +struct formatter< + T, Char, + enable_if_t< + fmt::is_range<T, Char>::value +// Workaround a bug in MSVC 2017 and earlier. +#if !FMT_MSC_VER || FMT_MSC_VER >= 1927 + && (has_formatter<detail::value_type<T>, format_context>::value || + detail::has_fallback_formatter<detail::value_type<T>, Char>::value) +#endif + >> { + formatting_range<Char> formatting; + + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return formatting.parse(ctx); + } + + template <typename FormatContext> + typename FormatContext::iterator format(const T& values, FormatContext& ctx) { + auto out = detail::copy(formatting.prefix, ctx.out()); + size_t i = 0; + auto view = detail::range_to_view<T>::view(values); + auto it = view.begin(); + auto end = view.end(); + for (; it != end; ++it) { + if (i > 0) out = detail::write_delimiter(out); + out = detail::write_range_entry<Char>(out, *it); + ++i; + } + return detail::copy(formatting.postfix, out); + } +}; + +template <typename Char, typename... T> struct tuple_arg_join : detail::view { + const std::tuple<T...>& tuple; + basic_string_view<Char> sep; + + tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s) + : tuple(t), sep{s} {} +}; + +template <typename Char, typename... T> +struct formatter<tuple_arg_join<Char, T...>, Char> { + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template <typename FormatContext> + typename FormatContext::iterator format( + const tuple_arg_join<Char, T...>& value, FormatContext& ctx) { + return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{}); + } + + private: + template <typename FormatContext, size_t... N> + typename FormatContext::iterator format( + const tuple_arg_join<Char, T...>& value, FormatContext& ctx, + detail::index_sequence<N...>) { + return format_args(value, ctx, std::get<N>(value.tuple)...); + } + + template <typename FormatContext> + typename FormatContext::iterator format_args( + const tuple_arg_join<Char, T...>&, FormatContext& ctx) { + // NOTE: for compilers that support C++17, this empty function instantiation + // can be replaced with a constexpr branch in the variadic overload. + return ctx.out(); + } + + template <typename FormatContext, typename Arg, typename... Args> + typename FormatContext::iterator format_args( + const tuple_arg_join<Char, T...>& value, FormatContext& ctx, + const Arg& arg, const Args&... args) { + using base = formatter<typename std::decay<Arg>::type, Char>; + auto out = ctx.out(); + out = base{}.format(arg, ctx); + if (sizeof...(Args) > 0) { + out = std::copy(value.sep.begin(), value.sep.end(), out); + ctx.advance_to(out); + return format_args(value, ctx, args...); + } + return out; + } +}; + +FMT_MODULE_EXPORT_BEGIN + +/** + \rst + Returns an object that formats `tuple` with elements separated by `sep`. + + **Example**:: + + std::tuple<int, char> t = {1, 'a'}; + fmt::print("{}", fmt::join(t, ", ")); + // Output: "1, a" + \endrst + */ +template <typename... T> +FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple, + string_view sep) { + return {tuple, sep}; +} + +template <typename... T> +FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple, + wstring_view sep) { + return {tuple, sep}; +} + +/** + \rst + Returns an object that formats `initializer_list` with elements separated by + `sep`. + + **Example**:: + + fmt::print("{}", fmt::join({1, 2, 3}, ", ")); + // Output: "1, 2, 3" + \endrst + */ +template <typename T> +arg_join<const T*, const T*, char> join(std::initializer_list<T> list, + string_view sep) { + return join(std::begin(list), std::end(list), sep); +} + +template <typename T> +arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list, + wstring_view sep) { + return join(std::begin(list), std::end(list), sep); +} + +FMT_MODULE_EXPORT_END +FMT_END_NAMESPACE + +#endif // FMT_RANGES_H_ diff --git a/libs/fmt/src/fmt.cc b/libs/fmt/src/fmt.cc new file mode 100644 index 0000000000000000000000000000000000000000..f0dc572193cc769b2d32642d5dfa574fe689407a --- /dev/null +++ b/libs/fmt/src/fmt.cc @@ -0,0 +1,87 @@ +module; +// put all implementation-provided headers into the global module fragment +// to prevent attachment to this module +#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) +# define _CRT_SECURE_NO_WARNINGS +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +#endif + +#include <cctype> +#include <cerrno> +#include <climits> +#include <clocale> +#include <cmath> +#include <cstdarg> +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <cwchar> + +#include <algorithm> +#include <chrono> +#include <exception> +#include <functional> +#include <iterator> +#include <limits> +#include <locale> +#include <memory> +#include <ostream> +#include <sstream> +#include <stdexcept> +#include <string> +#include <string_view> +#include <type_traits> +#include <utility> +#include <vector> + +#if _MSC_VER +# include <intrin.h> +#endif +#if defined __APPLE__ || defined(__FreeBSD__) +# include <xlocale.h> +#endif +#if __has_include(<winapifamily.h>) +# include <winapifamily.h> +#endif +#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \ + defined(__linux__)) && \ + (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# include <fcntl.h> +# include <sys/stat.h> +# include <sys/types.h> +# ifndef _WIN32 +# include <unistd.h> +# else +# include <io.h> +# endif +#endif +#ifdef _WIN32 +# include <windows.h> +#endif + +export module fmt; + +#define FMT_MODULE_EXPORT export +#define FMT_MODULE_EXPORT_BEGIN export { +#define FMT_MODULE_EXPORT_END } + +// all library-provided declarations and definitions +// must be in the module purview to be exported +#include "fmt/format.h" +#include "fmt/args.h" +#include "fmt/color.h" +#include "fmt/compile.h" +#include "fmt/locale.h" +#include "fmt/chrono.h" +#include "fmt/printf.h" +#include "fmt/os.h" + +module : private; + +#include "format.cc" +#include "os.cc" diff --git a/libs/fmt/src/format.cc b/libs/fmt/src/format.cc new file mode 100644 index 0000000000000000000000000000000000000000..618aa07d0bb6ca2a9f7fcdc5b210b417e4b8953e --- /dev/null +++ b/libs/fmt/src/format.cc @@ -0,0 +1,97 @@ +// Formatting library for C++ +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/format-inl.h" + +FMT_BEGIN_NAMESPACE +namespace detail { + +template <typename T> +int format_float(char* buf, std::size_t size, const char* format, int precision, + T value) { +#ifdef FMT_FUZZ + if (precision > 100000) + throw std::runtime_error( + "fuzz mode - avoid large allocation inside snprintf"); +#endif + // Suppress the warning about nonliteral format string. + int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; + return precision < 0 ? snprintf_ptr(buf, size, format, value) + : snprintf_ptr(buf, size, format, precision, value); +} + +template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x) + FMT_NOEXCEPT; +template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x) + FMT_NOEXCEPT; + +// DEPRECATED! This function exists for ABI compatibility. +template <typename Char> +typename basic_format_context<std::back_insert_iterator<buffer<Char>>, + Char>::iterator +vformat_to(buffer<Char>& buf, basic_string_view<Char> format_str, + basic_format_args<basic_format_context< + std::back_insert_iterator<buffer<type_identity_t<Char>>>, + type_identity_t<Char>>> + args) { + using iterator = std::back_insert_iterator<buffer<char>>; + using context = basic_format_context< + std::back_insert_iterator<buffer<type_identity_t<Char>>>, + type_identity_t<Char>>; + auto out = iterator(buf); + format_handler<iterator, Char, context> h(out, format_str, args, {}); + parse_format_string<false>(format_str, h); + return out; +} +template basic_format_context<std::back_insert_iterator<buffer<char>>, + char>::iterator +vformat_to(buffer<char>&, string_view, + basic_format_args<basic_format_context< + std::back_insert_iterator<buffer<type_identity_t<char>>>, + type_identity_t<char>>>); +} // namespace detail + +// Workaround a bug in MSVC2013 that prevents instantiation of format_float. +int (*instantiate_format_float)(double, int, detail::float_specs, + detail::buffer<char>&) = detail::format_float; + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template FMT_API detail::locale_ref::locale_ref(const std::locale& loc); +template FMT_API std::locale detail::locale_ref::get<std::locale>() const; +#endif + +// Explicit instantiations for char. + +template FMT_API std::string detail::grouping_impl<char>(locale_ref); +template FMT_API char detail::thousands_sep_impl(locale_ref); +template FMT_API char detail::decimal_point_impl(locale_ref); + +template FMT_API void detail::buffer<char>::append(const char*, const char*); + +template FMT_API void detail::vformat_to( + detail::buffer<char>&, string_view, + basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref); + +template FMT_API int detail::snprintf_float(double, int, detail::float_specs, + detail::buffer<char>&); +template FMT_API int detail::snprintf_float(long double, int, + detail::float_specs, + detail::buffer<char>&); +template FMT_API int detail::format_float(double, int, detail::float_specs, + detail::buffer<char>&); +template FMT_API int detail::format_float(long double, int, detail::float_specs, + detail::buffer<char>&); + +// Explicit instantiations for wchar_t. + +template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref); +template FMT_API wchar_t detail::thousands_sep_impl(locale_ref); +template FMT_API wchar_t detail::decimal_point_impl(locale_ref); + +template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*, + const wchar_t*); +FMT_END_NAMESPACE diff --git a/libs/fmt/src/os.cc b/libs/fmt/src/os.cc new file mode 100644 index 0000000000000000000000000000000000000000..42a88b32291f0dc4911705d952b097cc3a467190 --- /dev/null +++ b/libs/fmt/src/os.cc @@ -0,0 +1,321 @@ +// Formatting library for C++ - optional OS-specific functionality +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +// Disable bogus MSVC warnings. +#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "fmt/os.h" + +#include <climits> + +#if FMT_USE_FCNTL +# include <sys/stat.h> +# include <sys/types.h> + +# ifndef _WIN32 +# include <unistd.h> +# else +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include <io.h> + +# define O_CREAT _O_CREAT +# define O_TRUNC _O_TRUNC + +# ifndef S_IRUSR +# define S_IRUSR _S_IREAD +# endif + +# ifndef S_IWUSR +# define S_IWUSR _S_IWRITE +# endif + +# ifdef __MINGW32__ +# define _SH_DENYNO 0x40 +# endif +# endif // _WIN32 +#endif // FMT_USE_FCNTL + +#ifdef _WIN32 +# include <windows.h> +#endif + +#ifdef fileno +# undef fileno +#endif + +namespace { +#ifdef _WIN32 +// Return type of read and write functions. +using rwresult = int; + +// On Windows the count argument to read and write is unsigned, so convert +// it from size_t preventing integer overflow. +inline unsigned convert_rwcount(std::size_t count) { + return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX; +} +#elif FMT_USE_FCNTL +// Return type of read and write functions. +using rwresult = ssize_t; + +inline std::size_t convert_rwcount(std::size_t count) { return count; } +#endif +} // namespace + +FMT_BEGIN_NAMESPACE + +#ifdef _WIN32 +detail::utf16_to_utf8::utf16_to_utf8(wstring_view s) { + if (int error_code = convert(s)) { + FMT_THROW(windows_error(error_code, + "cannot convert string from UTF-16 to UTF-8")); + } +} + +int detail::utf16_to_utf8::convert(wstring_view s) { + if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER; + int s_size = static_cast<int>(s.size()); + if (s_size == 0) { + // WideCharToMultiByte does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return 0; + } + + int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0, + nullptr, nullptr); + if (length == 0) return GetLastError(); + buffer_.resize(length + 1); + length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0], + length, nullptr, nullptr); + if (length == 0) return GetLastError(); + buffer_[length] = 0; + return 0; +} + +void windows_error::init(int err_code, string_view format_str, + format_args args) { + error_code_ = err_code; + memory_buffer buffer; + detail::format_windows_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error& base = *this; + base = std::runtime_error(to_string(buffer)); +} + +void detail::format_windows_error(detail::buffer<char>& out, int error_code, + string_view message) FMT_NOEXCEPT { + FMT_TRY { + wmemory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) { + wchar_t* system_message = &buf[0]; + int result = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, + error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message, + static_cast<uint32_t>(buf.size()), nullptr); + if (result != 0) { + utf16_to_utf8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) { + format_to(buffer_appender<char>(out), "{}: {}", message, + utf8_message); + return; + } + break; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +void report_windows_error(int error_code, + fmt::string_view message) FMT_NOEXCEPT { + report_error(detail::format_windows_error, error_code, message); +} +#endif // _WIN32 + +buffered_file::~buffered_file() FMT_NOEXCEPT { + if (file_ && FMT_SYSTEM(fclose(file_)) != 0) + report_system_error(errno, "cannot close file"); +} + +buffered_file::buffered_file(cstring_view filename, cstring_view mode) { + FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), + nullptr); + if (!file_) + FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str())); +} + +void buffered_file::close() { + if (!file_) return; + int result = FMT_SYSTEM(fclose(file_)); + file_ = nullptr; + if (result != 0) FMT_THROW(system_error(errno, "cannot close file")); +} + +// A macro used to prevent expansion of fileno on broken versions of MinGW. +#define FMT_ARGS + +int buffered_file::fileno() const { + int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_)); + if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor")); + return fd; +} + +#if FMT_USE_FCNTL +file::file(cstring_view path, int oflag) { + int mode = S_IRUSR | S_IWUSR; +# if defined(_WIN32) && !defined(__MINGW32__) + fd_ = -1; + FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); +# else + FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode))); +# endif + if (fd_ == -1) + FMT_THROW(system_error(errno, "cannot open file {}", path.c_str())); +} + +file::~file() FMT_NOEXCEPT { + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0) + report_system_error(errno, "cannot close file"); +} + +void file::close() { + if (fd_ == -1) return; + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + int result = FMT_POSIX_CALL(close(fd_)); + fd_ = -1; + if (result != 0) FMT_THROW(system_error(errno, "cannot close file")); +} + +long long file::size() const { +# ifdef _WIN32 + // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT + // is less than 0x0500 as is the case with some default MinGW builds. + // Both functions support large file sizes. + DWORD size_upper = 0; + HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_)); + DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper)); + if (size_lower == INVALID_FILE_SIZE) { + DWORD error = GetLastError(); + if (error != NO_ERROR) + FMT_THROW(windows_error(GetLastError(), "cannot get file size")); + } + unsigned long long long_size = size_upper; + return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower; +# else + using Stat = struct stat; + Stat file_stat = Stat(); + if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) + FMT_THROW(system_error(errno, "cannot get file attributes")); + static_assert(sizeof(long long) >= sizeof(file_stat.st_size), + "return type of file::size is not large enough"); + return file_stat.st_size; +# endif +} + +std::size_t file::read(void* buffer, std::size_t count) { + rwresult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); + if (result < 0) FMT_THROW(system_error(errno, "cannot read from file")); + return detail::to_unsigned(result); +} + +std::size_t file::write(const void* buffer, std::size_t count) { + rwresult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); + if (result < 0) FMT_THROW(system_error(errno, "cannot write to file")); + return detail::to_unsigned(result); +} + +file file::dup(int fd) { + // Don't retry as dup doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html + int new_fd = FMT_POSIX_CALL(dup(fd)); + if (new_fd == -1) + FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd)); + return file(new_fd); +} + +void file::dup2(int fd) { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) { + FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}", + fd_, fd)); + } +} + +void file::dup2(int fd, error_code& ec) FMT_NOEXCEPT { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) ec = error_code(errno); +} + +void file::pipe(file& read_end, file& write_end) { + // Close the descriptors first to make sure that assignments don't throw + // and there are no leaks. + read_end.close(); + write_end.close(); + int fds[2] = {}; +# ifdef _WIN32 + // Make the default pipe capacity same as on Linux 2.6.11+. + enum { DEFAULT_CAPACITY = 65536 }; + int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY)); +# else + // Don't retry as the pipe function doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html + int result = FMT_POSIX_CALL(pipe(fds)); +# endif + if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe")); + // The following assignments don't throw because read_fd and write_fd + // are closed. + read_end = file(fds[0]); + write_end = file(fds[1]); +} + +buffered_file file::fdopen(const char* mode) { +// Don't retry as fdopen doesn't return EINTR. +# if defined(__MINGW32__) && defined(_POSIX_) + FILE* f = ::fdopen(fd_, mode); +# else + FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode)); +# endif + if (!f) + FMT_THROW( + system_error(errno, "cannot associate stream with file descriptor")); + buffered_file bf(f); + fd_ = -1; + return bf; +} + +long getpagesize() { +# ifdef _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +# else + long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); + if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size")); + return size; +# endif +} + +FMT_API void ostream::grow(size_t) { + if (this->size() == this->capacity()) flush(); +} +#endif // FMT_USE_FCNTL +FMT_END_NAMESPACE diff --git a/libs/fmt/support/Android.mk b/libs/fmt/support/Android.mk new file mode 100644 index 0000000000000000000000000000000000000000..84a3e32f07181cfc2f4d66875cd919c2f844ee71 --- /dev/null +++ b/libs/fmt/support/Android.mk @@ -0,0 +1,15 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := fmt_static +LOCAL_MODULE_FILENAME := libfmt + +LOCAL_SRC_FILES := ../src/format.cc + +LOCAL_C_INCLUDES := $(LOCAL_PATH) +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) + +LOCAL_CFLAGS += -std=c++11 -fexceptions + +include $(BUILD_STATIC_LIBRARY) + diff --git a/libs/fmt/support/AndroidManifest.xml b/libs/fmt/support/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..b5281fee181e19fcf6a3b19dd177de8cb08b36d7 --- /dev/null +++ b/libs/fmt/support/AndroidManifest.xml @@ -0,0 +1 @@ +<manifest package="net.fmtlib" /> diff --git a/libs/fmt/support/C++.sublime-syntax b/libs/fmt/support/C++.sublime-syntax new file mode 100644 index 0000000000000000000000000000000000000000..9dfb5cbe4324c81808fd02d0c7e371265a5141cf --- /dev/null +++ b/libs/fmt/support/C++.sublime-syntax @@ -0,0 +1,2061 @@ +%YAML 1.2 +--- +# http://www.sublimetext.com/docs/3/syntax.html +name: C++ (fmt) +comment: I don't think anyone uses .hp. .cp tends to be paired with .h. (I could be wrong. :) -- chris +file_extensions: + - cpp + - cc + - cp + - cxx + - c++ + - C + - h + - hh + - hpp + - hxx + - h++ + - inl + - ipp +first_line_match: '-\*- C\+\+ -\*-' +scope: source.c++ +variables: + identifier: \b[[:alpha:]_][[:alnum:]_]*\b # upper and lowercase + macro_identifier: \b[[:upper:]_][[:upper:][:digit:]_]{2,}\b # only uppercase, at least 3 chars + path_lookahead: '(?:::\s*)?(?:{{identifier}}\s*::\s*)*(?:template\s+)?{{identifier}}' + operator_method_name: '\boperator\s*(?:[-+*/%^&|~!=<>]|[-+*/%^&|=!<>]=|<<=?|>>=?|&&|\|\||\+\+|--|,|->\*?|\(\)|\[\]|""\s*{{identifier}})' + casts: 'const_cast|dynamic_cast|reinterpret_cast|static_cast' + operator_keywords: 'and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq|noexcept' + control_keywords: 'break|case|catch|continue|default|do|else|for|goto|if|_Pragma|return|switch|throw|try|while' + memory_operators: 'new|delete' + basic_types: 'asm|__asm__|auto|bool|_Bool|char|_Complex|double|float|_Imaginary|int|long|short|signed|unsigned|void' + before_tag: 'struct|union|enum\s+class|enum\s+struct|enum|class' + declspec: '__declspec\(\s*\w+(?:\([^)]+\))?\s*\)' + storage_classes: 'static|export|extern|friend|explicit|virtual|register|thread_local' + type_qualifier: 'const|constexpr|mutable|typename|volatile' + compiler_directive: 'inline|restrict|__restrict__|__restrict' + visibility_modifiers: 'private|protected|public' + other_keywords: 'typedef|nullptr|{{visibility_modifiers}}|static_assert|sizeof|using|typeid|alignof|alignas|namespace|template' + modifiers: '{{storage_classes}}|{{type_qualifier}}|{{compiler_directive}}' + non_angle_brackets: '(?=<<|<=)' + + regular: '[^(){}&;*^%=<>-]*' + paren_open: (?:\( + paren_close: '\))?' + generic_open: (?:< + generic_close: '>)?' + balance_parentheses: '{{regular}}{{paren_open}}{{regular}}{{paren_close}}{{regular}}' + generic_lookahead: <{{regular}}{{generic_open}}{{regular}}{{generic_open}}{{regular}}{{generic_close}}\s*{{generic_close}}{{balance_parentheses}}> + + data_structures_forward_decl_lookahead: '(\s+{{macro_identifier}})*\s*(:\s*({{path_lookahead}}|{{visibility_modifiers}}|,|\s|<[^;]*>)+)?;' + non_func_keywords: 'if|for|switch|while|decltype|sizeof|__declspec|__attribute__|typeid|alignof|alignas|static_assert' + + format_spec: |- + (?x: + (?:.? [<>=^])? # fill align + [ +-]? # sign + \#? # alternate form + # technically, octal and hexadecimal integers are also supported as 'width', but rarely used + \d* # width + ,? # thousands separator + (?:\.\d+)? # precision + [bcdeEfFgGnosxX%]? # type + ) + +contexts: + main: + - include: preprocessor-global + - include: global + + ############################################################################# + # Reusable contexts + # + # The follow contexts are currently constructed to be reused in the + # Objetive-C++ syntax. They are specifically constructed to not push into + # sub-contexts, which ensures that Objective-C++ code isn't accidentally + # lexed as plain C++. + # + # The "unique-*" contexts are additions that C++ makes over C, and thus can + # be directly reused in Objective-C++ along with contexts from Objective-C + # and C. + ############################################################################# + + unique-late-expressions: + # This is highlighted after all of the other control keywords + # to allow operator overloading to be lexed properly + - match: \boperator\b + scope: keyword.control.c++ + + unique-modifiers: + - match: \b({{modifiers}})\b + scope: storage.modifier.c++ + + unique-variables: + - match: \bthis\b + scope: variable.language.c++ + # common C++ instance var naming idiom -- fMemberName + - match: '\b(f|m)[[:upper:]]\w*\b' + scope: variable.other.readwrite.member.c++ + # common C++ instance var naming idiom -- m_member_name + - match: '\bm_[[:alnum:]_]+\b' + scope: variable.other.readwrite.member.c++ + + unique-constants: + - match: \bnullptr\b + scope: constant.language.c++ + + unique-keywords: + - match: \busing\b + scope: keyword.control.c++ + - match: \bbreak\b + scope: keyword.control.flow.break.c++ + - match: \bcontinue\b + scope: keyword.control.flow.continue.c++ + - match: \bgoto\b + scope: keyword.control.flow.goto.c++ + - match: \breturn\b + scope: keyword.control.flow.return.c++ + - match: \bthrow\b + scope: keyword.control.flow.throw.c++ + - match: \b({{control_keywords}})\b + scope: keyword.control.c++ + - match: '\bdelete\b(\s*\[\])?|\bnew\b(?!])' + scope: keyword.control.c++ + - match: \b({{operator_keywords}})\b + scope: keyword.operator.word.c++ + + unique-types: + - match: \b(char16_t|char32_t|wchar_t|nullptr_t)\b + scope: storage.type.c++ + - match: \bclass\b + scope: storage.type.c++ + + unique-strings: + - match: '((?:L|u8|u|U)?R)("([^\(\)\\ ]{0,16})\()' + captures: + 1: storage.type.string.c++ + 2: punctuation.definition.string.begin.c++ + push: + - meta_scope: string.quoted.double.c++ + - match: '\)\3"' + scope: punctuation.definition.string.end.c++ + pop: true + - match: '\{\{|\}\}' + scope: constant.character.escape.c++ + - include: formatting-syntax + + unique-numbers: + - match: |- + (?x) + (?: + # floats + (?: + (?:\b\d(?:[\d']*\d)?\.\d(?:[\d']*\d)?|\B\.\d(?:[\d']*\d)?)(?:[Ee][+-]?\d(?:[\d']*\d)?)?(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b + | + (?:\b\d(?:[\d']*\d)?\.)(?:\B|(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))\b|(?:[Ee][+-]?\d(?:[\d']*\d)?)(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b) + | + \b\d(?:[\d']*\d)?(?:[Ee][+-]?\d(?:[\d']*\d)?)(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b + ) + | + # ints + \b(?: + (?: + # dec + [1-9](?:[\d']*\d)? + | + # oct + 0(?:[0-7']*[0-7])? + | + # hex + 0[Xx][\da-fA-F](?:[\da-fA-F']*[\da-fA-F])? + | + # bin + 0[Bb][01](?:[01']*[01])? + ) + # int suffixes + (?:(?:l{1,2}|L{1,2})[uU]?|[uU](?:l{0,2}|L{0,2})|(?:i[fl]?|h|min|[mun]?s|_\w*))?)\b + ) + (?!\.) # Number must not be followed by a decimal point + scope: constant.numeric.c++ + + identifiers: + - match: '{{identifier}}\s*(::)\s*' + captures: + 1: punctuation.accessor.c++ + - match: '(?:(::)\s*)?{{identifier}}' + captures: + 1: punctuation.accessor.c++ + + function-specifiers: + - match: \b(const|final|noexcept|override)\b + scope: storage.modifier.c++ + + ############################################################################# + # The following are C++-specific contexts that should not be reused. This is + # because they push into subcontexts and use variables that are C++-specific. + ############################################################################# + + ## Common context layout + + global: + - match: '(?=\btemplate\b)' + push: + - include: template + - match: (?=\S) + set: global-modifier + - include: namespace + - include: keywords-angle-brackets + - match: '(?={{path_lookahead}}\s*<)' + push: global-modifier + # Take care of comments just before a function definition. + - match: /\* + scope: punctuation.definition.comment.c + push: + - - match: \s*(?=\w) + set: global-modifier + - match: "" + pop: true + - - meta_scope: comment.block.c + - match: \*/ + scope: punctuation.definition.comment.c + pop: true + - include: early-expressions + - match: ^\s*\b(extern)(?=\s+"C(\+\+)?") + scope: storage.modifier.c++ + push: + - include: comments + - include: strings + - match: '\{' + scope: punctuation.section.block.begin.c++ + set: + - meta_scope: meta.extern-c.c++ + - match: '^\s*(#\s*ifdef)\s*__cplusplus\s*' + scope: meta.preprocessor.c++ + captures: + 1: keyword.control.import.c++ + set: + - match: '\}' + scope: punctuation.section.block.end.c++ + pop: true + - include: preprocessor-global + - include: global + - match: '\}' + scope: punctuation.section.block.end.c++ + pop: true + - include: preprocessor-global + - include: global + - match: (?=\S) + set: global-modifier + - match: ^\s*(?=\w) + push: global-modifier + - include: late-expressions + + statements: + - include: preprocessor-statements + - include: scope:source.c#label + - include: expressions + + expressions: + - include: early-expressions + - include: late-expressions + + early-expressions: + - include: early-expressions-before-generic-type + - include: generic-type + - include: early-expressions-after-generic-type + + early-expressions-before-generic-type: + - include: preprocessor-expressions + - include: comments + - include: case-default + - include: typedef + - include: keywords-angle-brackets + - include: keywords-parens + - include: keywords + - include: numbers + # Prevent a '<' from getting scoped as the start of another template + # parameter list, if in reality a less-than-or-equals sign is meant. + - match: <= + scope: keyword.operator.comparison.c + + early-expressions-after-generic-type: + - include: members-arrow + - include: operators + - include: members-dot + - include: strings + - include: parens + - include: brackets + - include: block + - include: variables + - include: constants + - match: ',' + scope: punctuation.separator.c++ + - match: '\)|\}' + scope: invalid.illegal.stray-bracket-end.c++ + + expressions-minus-generic-type: + - include: early-expressions-before-generic-type + - include: angle-brackets + - include: early-expressions-after-generic-type + - include: late-expressions + + expressions-minus-generic-type-function-call: + - include: early-expressions-before-generic-type + - include: angle-brackets + - include: early-expressions-after-generic-type + - include: late-expressions-before-function-call + - include: identifiers + - match: ';' + scope: punctuation.terminator.c++ + + late-expressions: + - include: late-expressions-before-function-call + - include: function-call + - include: identifiers + - match: ';' + scope: punctuation.terminator.c++ + + late-expressions-before-function-call: + - include: unique-late-expressions + - include: modifiers-parens + - include: modifiers + - include: types + + expressions-minus-function-call: + - include: early-expressions + - include: late-expressions-before-function-call + - include: identifiers + - match: ';' + scope: punctuation.terminator.c++ + + comments: + - include: scope:source.c#comments + + operators: + - include: scope:source.c#operators + + modifiers: + - include: unique-modifiers + - include: scope:source.c#modifiers + + variables: + - include: unique-variables + - include: scope:source.c#variables + + constants: + - include: unique-constants + - include: scope:source.c#constants + + keywords: + - include: unique-keywords + - include: scope:source.c#keywords + + types: + - include: unique-types + - include: types-parens + - include: scope:source.c#types + + strings: + - include: unique-strings + - match: '(L|u8|u|U)?(")' + captures: + 1: storage.type.string.c++ + 2: punctuation.definition.string.begin.c++ + push: + - meta_scope: string.quoted.double.c++ + - match: '"' + scope: punctuation.definition.string.end.c++ + pop: true + - include: scope:source.c#string_escaped_char + - match: |- + (?x)% + (\d+\$)? # field (argument #) + [#0\- +']* # flags + [,;:_]? # separator character (AltiVec) + ((-?\d+)|\*(-?\d+\$)?)? # minimum field width + (\.((-?\d+)|\*(-?\d+\$)?)?)? # precision + (hh|h|ll|l|j|t|z|q|L|vh|vl|v|hv|hl)? # length modifier + (\[[^\]]+\]|[am]s|[diouxXDOUeEfFgGaACcSspn%]) # conversion type + scope: constant.other.placeholder.c++ + - match: '\{\{|\}\}' + scope: constant.character.escape.c++ + - include: formatting-syntax + - include: scope:source.c#strings + + formatting-syntax: + # https://docs.python.org/3.6/library/string.html#formatstrings + - match: |- # simple form + (?x) + (\{) + (?: [\w.\[\]]+)? # field_name + ( ! [ars])? # conversion + ( : (?:{{format_spec}}| # format_spec OR + [^}%]*%.[^}]*) # any format-like string + )? + (\}) + scope: constant.other.placeholder.c++ + captures: + 1: punctuation.definition.placeholder.begin.c++ + 2: storage.modifier.c++onversion.c++ + 3: constant.other.format-spec.c++ + 4: punctuation.definition.placeholder.end.c++ + - match: \{(?=[^\}"']+\{[^"']*\}) # complex (nested) form + scope: punctuation.definition.placeholder.begin.c++ + push: + - meta_scope: constant.other.placeholder.c++ + - match: \} + scope: punctuation.definition.placeholder.end.c++ + pop: true + - match: '[\w.\[\]]+' + - match: '![ars]' + scope: storage.modifier.conversion.c++ + - match: ':' + push: + - meta_scope: meta.format-spec.c++ constant.other.format-spec.c++ + - match: (?=\}) + pop: true + - include: formatting-syntax + + numbers: + - include: unique-numbers + - include: scope:source.c#numbers + + ## C++-specific contexts + + case-default: + - match: '\b(default|case)\b' + scope: keyword.control.c++ + push: + - match: (?=[);,]) + pop: true + - match: ':' + scope: punctuation.separator.c++ + pop: true + - include: expressions + + modifiers-parens: + - match: '\b(alignas)\b\s*(\()' + captures: + 1: storage.modifier.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - match: \b(__attribute__)\s*(\(\() + captures: + 1: storage.modifier.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push : + - meta_scope: meta.attribute.c++ + - meta_content_scope: meta.group.c++ + - include: parens + - include: strings + - match: \)\) + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - match: \b(__declspec)(\() + captures: + 1: storage.modifier.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - match: '\b(align|allocate|code_seg|deprecated|property|uuid)\b\s*(\()' + captures: + 1: storage.modifier.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: numbers + - include: strings + - match: \b(get|put)\b + scope: variable.parameter.c++ + - match: ',' + scope: punctuation.separator.c++ + - match: '=' + scope: keyword.operator.assignment.c++ + - match: '\b(appdomain|deprecated|dllimport|dllexport|jintrinsic|naked|noalias|noinline|noreturn|nothrow|novtable|process|restrict|safebuffers|selectany|thread)\b' + scope: constant.other.c++ + + types-parens: + - match: '\b(decltype)\b\s*(\()' + captures: + 1: storage.type.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + keywords-angle-brackets: + - match: \b({{casts}})\b\s* + scope: keyword.operator.word.cast.c++ + push: + - match: '>' + scope: punctuation.section.generic.end.c++ + pop: true + - match: '<' + scope: punctuation.section.generic.begin.c++ + push: + - match: '(?=>)' + pop: true + - include: expressions-minus-generic-type-function-call + + keywords-parens: + - match: '\b(alignof|typeid|static_assert|sizeof)\b\s*(\()' + captures: + 1: keyword.operator.word.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + namespace: + - match: '\b(using)\s+(namespace)\s+(?={{path_lookahead}})' + captures: + 1: keyword.control.c++ + 2: keyword.control.c++ + push: + - include: identifiers + - match: '' + pop: true + - match: '\b(namespace)\s+(?=({{path_lookahead}})?(?!\s*[;,]))' + scope: meta.namespace.c++ + captures: + 1: keyword.control.c++ + push: + - meta_content_scope: meta.namespace.c++ entity.name.namespace.c++ + - include: identifiers + - match: '' + set: + - meta_scope: meta.namespace.c++ + - include: comments + - match: '=' + scope: keyword.operator.alias.c++ + - match: '(?=;)' + pop: true + - match: '\}' + scope: meta.block.c++ punctuation.section.block.end.c++ + pop: true + - match: '\{' + scope: punctuation.section.block.begin.c++ + push: + - meta_scope: meta.block.c++ + - match: '(?=\})' + pop: true + - include: preprocessor-global + - include: global + - include: expressions + + template-common: + # Exit the template scope if we hit some basic invalid characters. This + # helps when a user is in the middle of typing their template types and + # prevents re-highlighting the whole file until the next > is found. + - match: (?=[{};]) + pop: true + - include: expressions + + template: + - match: \btemplate\b + scope: storage.type.template.c++ + push: + - meta_scope: meta.template.c++ + # Explicitly include comments here at the top, in order to NOT match the + # \S lookahead in the case of comments. + - include: comments + - match: < + scope: punctuation.section.generic.begin.c++ + set: + - meta_content_scope: meta.template.c++ + - match: '>' + scope: meta.template.c++ punctuation.section.generic.end.c++ + pop: true + - match: \.{3} + scope: keyword.operator.variadic.c++ + - match: \b(typename|{{before_tag}})\b + scope: storage.type.c++ + - include: template # include template here for nested templates + - include: template-common + - match: (?=\S) + set: + - meta_content_scope: meta.template.c++ + - match: \b({{before_tag}})\b + scope: storage.type.c++ + - include: template-common + + generic-type: + - match: '(?=(?!template){{path_lookahead}}\s*{{generic_lookahead}}\s*\()' + push: + - meta_scope: meta.function-call.c++ + - match: \btemplate\b + scope: storage.type.template.c++ + - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*' + captures: + 1: punctuation.accessor.double-colon.c++ + 2: punctuation.accessor.double-colon.c++ + - match: (?:(::)\s*)?({{identifier}})\s*(<) + captures: + 1: punctuation.accessor.double-colon.c++ + 2: variable.function.c++ + 3: punctuation.section.generic.begin.c++ + push: + - match: '>' + scope: punctuation.section.generic.end.c++ + pop: true + - include: expressions-minus-generic-type-function-call + - match: (?:(::)\s*)?({{identifier}})\s*(\() + captures: + 1: punctuation.accessor.double-colon.c++ + 2: variable.function.c++ + 3: punctuation.section.group.begin.c++ + set: + - meta_scope: meta.function-call.c++ + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - include: angle-brackets + - match: '\(' + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_scope: meta.function-call.c++ + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - match: '(?=(?!template){{path_lookahead}}\s*{{generic_lookahead}})' + push: + - include: identifiers + - match: '<' + scope: punctuation.section.generic.begin.c++ + set: + - match: '>' + scope: punctuation.section.generic.end.c++ + pop: true + - include: expressions-minus-generic-type-function-call + + angle-brackets: + - match: '<(?!<)' + scope: punctuation.section.generic.begin.c++ + push: + - match: '>' + scope: punctuation.section.generic.end.c++ + pop: true + - include: expressions-minus-generic-type-function-call + + block: + - match: '\{' + scope: punctuation.section.block.begin.c++ + push: + - meta_scope: meta.block.c++ + - match: (?=^\s*#\s*(elif|else|endif)\b) + pop: true + - match: '\}' + scope: punctuation.section.block.end.c++ + pop: true + - include: statements + + function-call: + - match: (?={{path_lookahead}}\s*\() + push: + - meta_scope: meta.function-call.c++ + - include: scope:source.c#c99 + - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*' + scope: variable.function.c++ + captures: + 1: punctuation.accessor.c++ + 2: punctuation.accessor.c++ + - match: '(?:(::)\s*)?{{identifier}}' + scope: variable.function.c++ + captures: + 1: punctuation.accessor.c++ + - match: '\(' + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.function-call.c++ meta.group.c++ + - match: '\)' + scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + members-inside-function-call: + - meta_content_scope: meta.method-call.c++ meta.group.c++ + - match: \) + scope: meta.method-call.c++ meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + members-after-accessor-junction: + # After we've seen an accessor (dot or arrow), this context decides what + # kind of entity we're accessing. + - include: comments + - match: \btemplate\b + scope: meta.method-call.c++ storage.type.template.c++ + # Guaranteed to be a template member function call after we match this + set: + - meta_content_scope: meta.method-call.c++ + - include: comments + - match: '{{identifier}}' + scope: variable.function.member.c++ + set: + - meta_content_scope: meta.method-call.c++ + - match: \( + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: members-inside-function-call + - include: comments + - include: angle-brackets + - match: (?=\S) # safety pop + pop: true + - match: (?=\S) # safety pop + pop: true + # Operator overloading + - match: '({{operator_method_name}})\s*(\()' + captures: + 0: meta.method-call.c++ + 1: variable.function.member.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + set: members-inside-function-call + # Non-templated member function call + - match: (~?{{identifier}})\s*(\() + captures: + 0: meta.method-call.c++ + 1: variable.function.member.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + set: members-inside-function-call + # Templated member function call + - match: (~?{{identifier}})\s*(?={{generic_lookahead}}) + captures: + 1: variable.function.member.c++ + set: + - meta_scope: meta.method-call.c++ + - match: < + scope: punctuation.section.generic.begin.c++ + set: + - meta_content_scope: meta.method-call.c++ + - match: '>' + scope: punctuation.section.generic.end.c++ + set: + - meta_content_scope: meta.method-call.c++ + - include: comments + - match: \( + scope: punctuation.section.group.begin.c++ + set: members-inside-function-call + - match: (?=\S) # safety pop + pop: true + - include: expressions + # Explicit base-class access + - match: ({{identifier}})\s*(::) + captures: + 1: variable.other.base-class.c++ + 2: punctuation.accessor.double-colon.c++ + set: members-after-accessor-junction # reset + # Just a regular member variable + - match: '{{identifier}}' + scope: variable.other.readwrite.member.c++ + pop: true + + members-dot: + - include: scope:source.c#access-illegal + # No lookahead required because members-dot goes after operators in the + # early-expressions-after-generic-type context. This means triple dots + # (i.e. "..." or "variadic") is attempted first. + - match: \. + scope: punctuation.accessor.dot.c++ + push: members-after-accessor-junction + + members-arrow: + # This needs to be before operators in the + # early-expressions-after-generic-type context because otherwise the "->" + # from the C language will match. + - match: -> + scope: punctuation.accessor.arrow.c++ + push: members-after-accessor-junction + + typedef: + - match: \btypedef\b + scope: storage.type.c++ + push: + - match: ({{identifier}})?\s*(?=;) + captures: + 1: entity.name.type.typedef.c++ + pop: true + - match: \b(struct)\s+({{identifier}})\b + captures: + 1: storage.type.c++ + - include: expressions-minus-generic-type + + parens: + - match: \( + scope: punctuation.section.group.begin.c++ + push: + - meta_scope: meta.group.c++ + - match: \) + scope: punctuation.section.group.end.c++ + pop: true + - include: expressions + + brackets: + - match: \[ + scope: punctuation.section.brackets.begin.c++ + push: + - meta_scope: meta.brackets.c++ + - match: \] + scope: punctuation.section.brackets.end.c++ + pop: true + - include: expressions + + function-trailing-return-type: + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + - include: modifiers-parens + - include: modifiers + - include: identifiers + - match: \*|& + scope: keyword.operator.c++ + - include: function-trailing-return-type-parens + - match: '(?=\S)' + pop: true + + function-trailing-return-type-parens: + - match: \( + scope: punctuation.section.group.begin.c++ + push: + - meta_scope: meta.group.c++ + - match: \) + scope: punctuation.section.group.end.c++ + pop: true + - include: function-trailing-return-type + + ## Detection of function and data structure definitions at the global level + + global-modifier: + - include: comments + - include: modifiers-parens + - include: modifiers + # Constructors and destructors don't have a type + - match: '(?={{path_lookahead}}\s*::\s*{{identifier}}\s*(\(|$))' + set: + - meta_content_scope: meta.function.c++ entity.name.function.constructor.c++ + - include: identifiers + - match: '(?=[^\w\s])' + set: function-definition-params + - match: '(?={{path_lookahead}}\s*::\s*~{{identifier}}\s*(\(|$))' + set: + - meta_content_scope: meta.function.c++ entity.name.function.destructor.c++ + - include: identifiers + - match: '~{{identifier}}' + - match: '(?=[^\w\s])' + set: function-definition-params + # If we see a path ending in :: before a newline, we don't know if it is + # a constructor or destructor, or a long return type, so we are just going + # to treat it like a regular function. Most likely it is a constructor, + # since it doesn't seem most developers would create such a long typename. + - match: '(?={{path_lookahead}}\s*::\s*$)' + set: + - meta_content_scope: meta.function.c++ entity.name.function.c++ + - include: identifiers + - match: '~{{identifier}}' + - match: '(?=[^\w\s])' + set: function-definition-params + - include: unique-strings + - match: '(?=\S)' + set: global-type + + global-type: + - include: comments + - match: \*|& + scope: keyword.operator.c++ + - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}}|operator)\b)' + pop: true + - match: '(?=\s)' + set: global-maybe-function + # If a class/struct/enum followed by a name that is not a macro or declspec + # then this is likely a return type of a function. This is uncommon. + - match: |- + (?x: + ({{before_tag}}) + \s+ + (?= + (?![[:upper:][:digit:]_]+\b|__declspec|{{before_tag}}) + {{path_lookahead}} + (\s+{{identifier}}\s*\(|\s*[*&]) + ) + ) + captures: + 1: storage.type.c++ + set: + - include: identifiers + - match: '' + set: global-maybe-function + # The previous match handles return types of struct/enum/etc from a func, + # there this one exits the context to allow matching an actual struct/class + - match: '(?=\b({{before_tag}})\b)' + set: data-structures + - match: '(?=\b({{casts}})\b\s*<)' + pop: true + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + # Allow a macro call + - match: '({{identifier}})\s*(\()(?=[^\)]+\))' + captures: + 1: variable.function.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_scope: meta.function-call.c++ + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - match: '(?={{path_lookahead}}\s*\()' + set: + - include: function-call + - match: '' + pop: true + - include: variables + - include: constants + - include: identifiers + - match: (?=\W) + pop: true + + global-maybe-function: + - include: comments + # Consume pointer info, macros and any type info that was offset by macros + - match: \*|& + scope: keyword.operator.c++ + - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}})\b)' + pop: true + - match: '\b({{type_qualifier}})\b' + scope: storage.modifier.c++ + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + - include: modifiers-parens + - include: modifiers + # All uppercase identifier just before a newline is most likely a macro + - match: '[[:upper:][:digit:]_]+\s*$' + # Operator overloading + - match: '(?=({{path_lookahead}}\s*(?:{{generic_lookahead}})?::\s*)?{{operator_method_name}}\s*(\(|$))' + set: + - meta_content_scope: meta.function.c++ entity.name.function.c++ + - include: identifiers + - match: '(?=\s*(\(|$))' + set: function-definition-params + # Identifier that is not the function name - likely a macro or type + - match: '(?={{path_lookahead}}([ \t]+|[*&])(?!\s*(<|::|\(|$)))' + push: + - include: identifiers + - match: '' + pop: true + # Real function definition + - match: '(?={{path_lookahead}}({{generic_lookahead}}({{path_lookahead}})?)\s*(\(|$))' + set: [function-definition-params, global-function-identifier-generic] + - match: '(?={{path_lookahead}}\s*(\(|$))' + set: [function-definition-params, global-function-identifier] + - match: '(?={{path_lookahead}}\s*::\s*$)' + set: [function-definition-params, global-function-identifier] + - match: '(?=\S)' + pop: true + + global-function-identifier-generic: + - include: angle-brackets + - match: '::' + scope: punctuation.accessor.c++ + - match: '(?={{identifier}}<.*>\s*\()' + push: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '(?=<)' + pop: true + - match: '(?={{identifier}}\s*\()' + push: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '' + pop: true + - match: '(?=\()' + pop: true + + global-function-identifier: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '(?=\S)' + pop: true + + function-definition-params: + - meta_content_scope: meta.function.c++ + - include: comments + - match: '(?=\()' + set: + - match: \( + scope: meta.function.parameters.c++ meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.function.parameters.c++ meta.group.c++ + - match : \) + scope: punctuation.section.group.end.c++ + set: function-definition-continue + - match: '\bvoid\b' + scope: storage.type.c++ + - match: '{{identifier}}(?=\s*(\[|,|\)|=))' + scope: variable.parameter.c++ + - match: '=' + scope: keyword.operator.assignment.c++ + push: + - match: '(?=,|\))' + pop: true + - include: expressions-minus-generic-type + - include: scope:source.c#preprocessor-line-continuation + - include: expressions-minus-generic-type + - include: scope:source.c#preprocessor-line-continuation + - match: (?=\S) + pop: true + + function-definition-continue: + - meta_content_scope: meta.function.c++ + - include: comments + - match: '(?=;)' + pop: true + - match: '->' + scope: punctuation.separator.c++ + set: function-definition-trailing-return + - include: function-specifiers + - match: '=' + scope: keyword.operator.assignment.c++ + - match: '&' + scope: keyword.operator.c++ + - match: \b0\b + scope: constant.numeric.c++ + - match: \b(default|delete)\b + scope: storage.modifier.c++ + - match: '(?=\{)' + set: function-definition-body + - match: '(?=\S)' + pop: true + + function-definition-trailing-return: + - include: comments + - match: '(?=;)' + pop: true + - match: '(?=\{)' + set: function-definition-body + - include: function-specifiers + - include: function-trailing-return-type + + function-definition-body: + - meta_content_scope: meta.function.c++ meta.block.c++ + - match: '\{' + scope: punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.function.c++ meta.block.c++ + - match: '\}' + scope: meta.function.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - match: (?=^\s*#\s*(elif|else|endif)\b) + pop: true + - match: '(?=({{before_tag}})([^(;]+$|.*\{))' + push: data-structures + - include: statements + + ## Data structures including classes, structs, unions and enums + + data-structures: + - match: '\bclass\b' + scope: storage.type.c++ + set: data-structures-class-definition + # Detect variable type definitions using struct/enum/union followed by a tag + - match: '\b({{before_tag}})(?=\s+{{path_lookahead}}\s+{{path_lookahead}}\s*[=;\[])' + scope: storage.type.c++ + - match: '\bstruct\b' + scope: storage.type.c++ + set: data-structures-struct-definition + - match: '\benum(\s+(class|struct))?\b' + scope: storage.type.c++ + set: data-structures-enum-definition + - match: '\bunion\b' + scope: storage.type.c++ + set: data-structures-union-definition + - match: '(?=\S)' + pop: true + + preprocessor-workaround-eat-macro-before-identifier: + # Handle macros so they aren't matched as the class name + - match: ({{macro_identifier}})(?=\s+~?{{identifier}}) + captures: + 1: meta.assumed-macro.c + + data-structures-class-definition: + - meta_scope: meta.class.c++ + - include: data-structures-definition-common-begin + - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' + scope: entity.name.class.forward-decl.c++ + set: data-structures-class-definition-after-identifier + - match: '{{identifier}}' + scope: entity.name.class.c++ + set: data-structures-class-definition-after-identifier + - match: '(?=[:{])' + set: data-structures-class-definition-after-identifier + - match: '(?=;)' + pop: true + + data-structures-class-definition-after-identifier: + - meta_content_scope: meta.class.c++ + - include: data-structures-definition-common-begin + # No matching of identifiers since they should all be macros at this point + - include: data-structures-definition-common-end + - match: '\{' + scope: meta.block.c++ punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.class.c++ meta.block.c++ + - match: '\}' + scope: meta.class.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - include: data-structures-body + + data-structures-struct-definition: + - meta_scope: meta.struct.c++ + - include: data-structures-definition-common-begin + - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' + scope: entity.name.struct.forward-decl.c++ + set: data-structures-struct-definition-after-identifier + - match: '{{identifier}}' + scope: entity.name.struct.c++ + set: data-structures-struct-definition-after-identifier + - match: '(?=[:{])' + set: data-structures-struct-definition-after-identifier + - match: '(?=;)' + pop: true + + data-structures-struct-definition-after-identifier: + - meta_content_scope: meta.struct.c++ + - include: data-structures-definition-common-begin + # No matching of identifiers since they should all be macros at this point + - include: data-structures-definition-common-end + - match: '\{' + scope: meta.block.c++ punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.struct.c++ meta.block.c++ + - match: '\}' + scope: meta.struct.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - include: data-structures-body + + data-structures-enum-definition: + - meta_scope: meta.enum.c++ + - include: data-structures-definition-common-begin + - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' + scope: entity.name.enum.forward-decl.c++ + set: data-structures-enum-definition-after-identifier + - match: '{{identifier}}' + scope: entity.name.enum.c++ + set: data-structures-enum-definition-after-identifier + - match: '(?=[:{])' + set: data-structures-enum-definition-after-identifier + - match: '(?=;)' + pop: true + + data-structures-enum-definition-after-identifier: + - meta_content_scope: meta.enum.c++ + - include: data-structures-definition-common-begin + # No matching of identifiers since they should all be macros at this point + - include: data-structures-definition-common-end + - match: '\{' + scope: meta.block.c++ punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.enum.c++ meta.block.c++ + # Enums don't support methods so we have a simplified body + - match: '\}' + scope: meta.enum.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - include: statements + + data-structures-union-definition: + - meta_scope: meta.union.c++ + - include: data-structures-definition-common-begin + - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' + scope: entity.name.union.forward-decl.c++ + set: data-structures-union-definition-after-identifier + - match: '{{identifier}}' + scope: entity.name.union.c++ + set: data-structures-union-definition-after-identifier + - match: '(?=[{])' + set: data-structures-union-definition-after-identifier + - match: '(?=;)' + pop: true + + data-structures-union-definition-after-identifier: + - meta_content_scope: meta.union.c++ + - include: data-structures-definition-common-begin + # No matching of identifiers since they should all be macros at this point + # Unions don't support base classes + - include: angle-brackets + - match: '\{' + scope: meta.block.c++ punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.union.c++ meta.block.c++ + - match: '\}' + scope: meta.union.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - include: data-structures-body + - match: '(?=;)' + pop: true + + data-structures-definition-common-begin: + - include: comments + - match: '(?=\b(?:{{before_tag}}|{{control_keywords}})\b)' + pop: true + - include: preprocessor-other + - include: modifiers-parens + - include: modifiers + - include: preprocessor-workaround-eat-macro-before-identifier + + data-structures-definition-common-end: + - include: angle-brackets + - match: \bfinal\b + scope: storage.modifier.c++ + - match: ':' + scope: punctuation.separator.c++ + push: + - include: comments + - include: preprocessor-other + - include: modifiers-parens + - include: modifiers + - match: '\b(virtual|{{visibility_modifiers}})\b' + scope: storage.modifier.c++ + - match: (?={{path_lookahead}}) + push: + - meta_scope: entity.other.inherited-class.c++ + - include: identifiers + - match: '' + pop: true + - include: angle-brackets + - match: ',' + scope: punctuation.separator.c++ + - match: (?=\{|;) + pop: true + - match: '(?=;)' + pop: true + + data-structures-body: + - include: preprocessor-data-structures + - match: '(?=\btemplate\b)' + push: + - include: template + - match: (?=\S) + set: data-structures-modifier + - include: typedef + - match: \b({{visibility_modifiers}})\s*(:)(?!:) + captures: + 1: storage.modifier.c++ + 2: punctuation.section.class.c++ + - match: '^\s*(?=(?:~?\w+|::))' + push: data-structures-modifier + - include: expressions-minus-generic-type + + data-structures-modifier: + - match: '\bfriend\b' + scope: storage.modifier.c++ + push: + - match: (?=;) + pop: true + - match: '\{' + scope: punctuation.section.block.begin.c++ + set: + - meta_scope: meta.block.c++ + - match: '\}' + scope: punctuation.section.block.end.c++ + pop: true + - include: statements + - match: '\b({{before_tag}})\b' + scope: storage.type.c++ + - include: expressions-minus-function-call + - include: comments + - include: modifiers-parens + - include: modifiers + - match: '\bstatic_assert(?=\s*\()' + scope: meta.static-assert.c++ keyword.operator.word.c++ + push: + - match: '\(' + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.function-call.c++ meta.group.c++ + - match: '\)' + scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + # Destructor + - match: '(?:{{identifier}}\s*(::)\s*)?~{{identifier}}(?=\s*(\(|$))' + scope: meta.method.destructor.c++ entity.name.function.destructor.c++ + captures: + 1: punctuation.accessor.c++ + set: method-definition-params + # It's a macro, not a constructor if there is no type in the first param + - match: '({{identifier}})\s*(\()(?=\s*(?!void){{identifier}}\s*[),])' + captures: + 1: variable.function.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_scope: meta.function-call.c++ + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + # Constructor + - include: preprocessor-workaround-eat-macro-before-identifier + - match: '((?!{{before_tag}}|template){{identifier}})(?=\s*\()' + scope: meta.method.constructor.c++ entity.name.function.constructor.c++ + set: method-definition-params + # Long form constructor + - match: '({{identifier}}\s*(::)\s*{{identifier}})(?=\s*\()' + captures: + 1: meta.method.constructor.c++ entity.name.function.constructor.c++ + 2: punctuation.accessor.c++ + push: method-definition-params + - match: '(?=\S)' + set: data-structures-type + + data-structures-type: + - include: comments + - match: \*|& + scope: keyword.operator.c++ + # Cast methods + - match: '(operator)\s+({{identifier}})(?=\s*(\(|$))' + captures: + 1: keyword.control.c++ + 2: meta.method.c++ entity.name.function.c++ + set: method-definition-params + - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}}|operator)\b)' + pop: true + - match: '(?=\s)' + set: data-structures-maybe-method + # If a class/struct/enum followed by a name that is not a macro or declspec + # then this is likely a return type of a function. This is uncommon. + - match: |- + (?x: + ({{before_tag}}) + \s+ + (?= + (?![[:upper:][:digit:]_]+\b|__declspec|{{before_tag}}) + {{path_lookahead}} + (\s+{{identifier}}\s*\(|\s*[*&]) + ) + ) + captures: + 1: storage.type.c++ + set: + - include: identifiers + - match: '' + set: data-structures-maybe-method + # The previous match handles return types of struct/enum/etc from a func, + # there this one exits the context to allow matching an actual struct/class + - match: '(?=\b({{before_tag}})\b)' + set: data-structures + - match: '(?=\b({{casts}})\b\s*<)' + pop: true + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + - include: variables + - include: constants + - include: identifiers + - match: (?=[&*]) + set: data-structures-maybe-method + - match: (?=\W) + pop: true + + data-structures-maybe-method: + - include: comments + # Consume pointer info, macros and any type info that was offset by macros + - match: \*|& + scope: keyword.operator.c++ + - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}})\b)' + pop: true + - match: '\b({{type_qualifier}})\b' + scope: storage.modifier.c++ + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + - include: modifiers-parens + - include: modifiers + # Operator overloading + - match: '{{operator_method_name}}(?=\s*(\(|$))' + scope: meta.method.c++ entity.name.function.c++ + set: method-definition-params + # Identifier that is not the function name - likely a macro or type + - match: '(?={{path_lookahead}}([ \t]+|[*&])(?!\s*(<|::|\()))' + push: + - include: identifiers + - match: '' + pop: true + # Real function definition + - match: '(?={{path_lookahead}}({{generic_lookahead}})\s*(\())' + set: [method-definition-params, data-structures-function-identifier-generic] + - match: '(?={{path_lookahead}}\s*(\())' + set: [method-definition-params, data-structures-function-identifier] + - match: '(?={{path_lookahead}}\s*::\s*$)' + set: [method-definition-params, data-structures-function-identifier] + - match: '(?=\S)' + pop: true + + data-structures-function-identifier-generic: + - include: angle-brackets + - match: '(?={{identifier}})' + push: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '(?=<)' + pop: true + - match: '(?=\()' + pop: true + + data-structures-function-identifier: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '(?=\S)' + pop: true + + method-definition-params: + - meta_content_scope: meta.method.c++ + - include: comments + - match: '(?=\()' + set: + - match: \( + scope: meta.method.parameters.c++ meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.method.parameters.c++ meta.group.c++ + - match : \) + scope: punctuation.section.group.end.c++ + set: method-definition-continue + - match: '\bvoid\b' + scope: storage.type.c++ + - match: '{{identifier}}(?=\s*(\[|,|\)|=))' + scope: variable.parameter.c++ + - match: '=' + scope: keyword.operator.assignment.c++ + push: + - match: '(?=,|\))' + pop: true + - include: expressions-minus-generic-type + - include: expressions-minus-generic-type + - match: '(?=\S)' + pop: true + + method-definition-continue: + - meta_content_scope: meta.method.c++ + - include: comments + - match: '(?=;)' + pop: true + - match: '->' + scope: punctuation.separator.c++ + set: method-definition-trailing-return + - include: function-specifiers + - match: '=' + scope: keyword.operator.assignment.c++ + - match: '&' + scope: keyword.operator.c++ + - match: \b0\b + scope: constant.numeric.c++ + - match: \b(default|delete)\b + scope: storage.modifier.c++ + - match: '(?=:)' + set: + - match: ':' + scope: punctuation.separator.initializer-list.c++ + set: + - meta_scope: meta.method.constructor.initializer-list.c++ + - match: '{{identifier}}' + scope: variable.other.readwrite.member.c++ + push: + - match: \( + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.group.c++ + - match: \) + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - match: \{ + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.group.c++ + - match: \} + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - include: comments + - match: (?=\{|;) + set: method-definition-continue + - include: expressions + - match: '(?=\{)' + set: method-definition-body + - match: '(?=\S)' + pop: true + + method-definition-trailing-return: + - include: comments + - match: '(?=;)' + pop: true + - match: '(?=\{)' + set: method-definition-body + - include: function-specifiers + - include: function-trailing-return-type + + method-definition-body: + - meta_content_scope: meta.method.c++ meta.block.c++ + - match: '\{' + scope: punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.method.c++ meta.block.c++ + - match: '\}' + scope: meta.method.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - match: (?=^\s*#\s*(elif|else|endif)\b) + pop: true + - match: '(?=({{before_tag}})([^(;]+$|.*\{))' + push: data-structures + - include: statements + + ## Preprocessor for data-structures + + preprocessor-data-structures: + - include: preprocessor-rule-enabled-data-structures + - include: preprocessor-rule-disabled-data-structures + - include: preprocessor-practical-workarounds + + preprocessor-rule-disabled-data-structures: + - match: ^\s*((#if)\s+(0))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - match: (?=^\s*#\s*endif\b) + pop: true + - include: negated-block + - include: data-structures-body + - match: "" + push: + - meta_scope: comment.block.preprocessor.if-branch.c++ + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: scope:source.c#preprocessor-disabled + + preprocessor-rule-enabled-data-structures: + - match: ^\s*((#if)\s+(0*1))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - meta_content_scope: comment.block.preprocessor.else-branch.c++ + - match: (?=^\s*#\s*endif\b) + pop: true + - include: scope:source.c#preprocessor-disabled + - match: "" + push: + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: negated-block + - include: data-structures-body + + ## Preprocessor for global + + preprocessor-global: + - include: preprocessor-rule-enabled-global + - include: preprocessor-rule-disabled-global + - include: preprocessor-rule-other-global + + preprocessor-statements: + - include: preprocessor-rule-enabled-statements + - include: preprocessor-rule-disabled-statements + - include: preprocessor-rule-other-statements + + preprocessor-expressions: + - include: scope:source.c#incomplete-inc + - include: preprocessor-macro-define + - include: scope:source.c#pragma-mark + - include: preprocessor-other + + preprocessor-rule-disabled-global: + - match: ^\s*((#if)\s+(0))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - match: (?=^\s*#\s*endif\b) + pop: true + - include: preprocessor-global + - include: negated-block + - include: global + - match: "" + push: + - meta_scope: comment.block.preprocessor.if-branch.c++ + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: scope:source.c#preprocessor-disabled + + preprocessor-rule-enabled-global: + - match: ^\s*((#if)\s+(0*1))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - meta_content_scope: comment.block.preprocessor.else-branch.c++ + - match: (?=^\s*#\s*endif\b) + pop: true + - include: scope:source.c#preprocessor-disabled + - match: "" + push: + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: preprocessor-global + - include: negated-block + - include: global + + preprocessor-rule-other-global: + - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b + captures: + 1: keyword.control.import.c++ + push: + - meta_scope: meta.preprocessor.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-comments + - match: \bdefined\b + scope: keyword.control.c++ + # Enter a new scope where all elif/else branches have their + # contexts popped by a subsequent elif/else/endif. This ensures that + # preprocessor branches don't push multiple meta.block scopes on + # the stack, thus messing up the "global" context's detection of + # functions. + - match: $\n + set: preprocessor-if-branch-global + + # These gymnastics here ensure that we are properly handling scope even + # when the preprocessor is used to create different scope beginnings, such + # as a different if/while condition + preprocessor-if-branch-global: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: (?=^\s*#\s*(elif|else)\b) + push: preprocessor-elif-else-branch-global + - match: \{ + scope: punctuation.section.block.begin.c++ + set: preprocessor-block-if-branch-global + - include: preprocessor-global + - include: negated-block + - include: global + + preprocessor-block-if-branch-global: + - meta_scope: meta.block.c++ + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-block-finish-global + - match: (?=^\s*#\s*(elif|else)\b) + push: preprocessor-elif-else-branch-global + - match: \} + scope: punctuation.section.block.end.c++ + set: preprocessor-if-branch-global + - include: statements + + preprocessor-block-finish-global: + - meta_scope: meta.block.c++ + - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-block-finish-if-branch-global + - match: \} + scope: punctuation.section.block.end.c++ + pop: true + - include: statements + + preprocessor-block-finish-if-branch-global: + - match: ^\s*(#\s*endif)\b + captures: + 1: keyword.control.import.c++ + pop: true + - match: \} + scope: punctuation.section.block.end.c++ + set: preprocessor-if-branch-global + - include: statements + + preprocessor-elif-else-branch-global: + - match: (?=^\s*#\s*(endif)\b) + pop: true + - include: preprocessor-global + - include: negated-block + - include: global + + ## Preprocessor for statements + + preprocessor-rule-disabled-statements: + - match: ^\s*((#if)\s+(0))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - match: (?=^\s*#\s*endif\b) + pop: true + - include: negated-block + - include: statements + - match: "" + push: + - meta_scope: comment.block.preprocessor.if-branch.c++ + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: scope:source.c#preprocessor-disabled + + preprocessor-rule-enabled-statements: + - match: ^\s*((#if)\s+(0*1))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - meta_content_scope: comment.block.preprocessor.else-branch.c++ + - match: (?=^\s*#\s*endif\b) + pop: true + - include: scope:source.c#preprocessor-disabled + - match: "" + push: + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: negated-block + - include: statements + + preprocessor-rule-other-statements: + - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b + captures: + 1: keyword.control.import.c++ + push: + - meta_scope: meta.preprocessor.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-comments + - match: \bdefined\b + scope: keyword.control.c++ + # Enter a new scope where all elif/else branches have their + # contexts popped by a subsequent elif/else/endif. This ensures that + # preprocessor branches don't push multiple meta.block scopes on + # the stack, thus messing up the "global" context's detection of + # functions. + - match: $\n + set: preprocessor-if-branch-statements + + # These gymnastics here ensure that we are properly handling scope even + # when the preprocessor is used to create different scope beginnings, such + # as a different if/while condition + preprocessor-if-branch-statements: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: (?=^\s*#\s*(elif|else)\b) + push: preprocessor-elif-else-branch-statements + - match: \{ + scope: punctuation.section.block.begin.c++ + set: preprocessor-block-if-branch-statements + - match: (?=(?!{{non_func_keywords}}){{path_lookahead}}\s*\() + set: preprocessor-if-branch-function-call + - include: negated-block + - include: statements + + preprocessor-if-branch-function-call: + - meta_content_scope: meta.function-call.c++ + - include: scope:source.c#c99 + - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*' + scope: variable.function.c++ + captures: + 1: punctuation.accessor.c++ + 2: punctuation.accessor.c++ + - match: '(?:(::)\s*)?{{identifier}}' + scope: variable.function.c++ + captures: + 1: punctuation.accessor.c++ + - match: '\(' + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: preprocessor-if-branch-function-call-arguments + + preprocessor-if-branch-function-call-arguments: + - meta_content_scope: meta.function-call.c++ meta.group.c++ + - match : \) + scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ + set: preprocessor-if-branch-statements + - match: ^\s*(#\s*(?:elif|else))\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-if-branch-statements + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-if-branch-function-call-arguments-finish + - include: expressions + + preprocessor-if-branch-function-call-arguments-finish: + - meta_content_scope: meta.function-call.c++ meta.group.c++ + - match: \) + scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + preprocessor-block-if-branch-statements: + - meta_scope: meta.block.c++ + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-block-finish-statements + - match: (?=^\s*#\s*(elif|else)\b) + push: preprocessor-elif-else-branch-statements + - match: \} + scope: punctuation.section.block.end.c++ + set: preprocessor-if-branch-statements + - include: statements + + preprocessor-block-finish-statements: + - meta_scope: meta.block.c++ + - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-block-finish-if-branch-statements + - match: \} + scope: punctuation.section.block.end.c++ + pop: true + - include: statements + + preprocessor-block-finish-if-branch-statements: + - match: ^\s*(#\s*endif)\b + captures: + 1: keyword.control.import.c++ + pop: true + - match: \} + scope: meta.block.c++ punctuation.section.block.end.c++ + set: preprocessor-if-branch-statements + - include: statements + + preprocessor-elif-else-branch-statements: + - match: (?=^\s*#\s*endif\b) + pop: true + - include: negated-block + - include: statements + + ## Preprocessor other + + negated-block: + - match: '\}' + scope: punctuation.section.block.end.c++ + push: + - match: '\{' + scope: punctuation.section.block.begin.c++ + pop: true + - match: (?=^\s*#\s*(elif|else|endif)\b) + pop: true + - include: statements + + preprocessor-macro-define: + - match: ^\s*(\#\s*define)\b + captures: + 1: meta.preprocessor.macro.c++ keyword.control.import.define.c++ + push: + - meta_content_scope: meta.preprocessor.macro.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + - match: '({{identifier}})(?=\()' + scope: entity.name.function.preprocessor.c++ + set: + - match: '\(' + scope: punctuation.section.group.begin.c++ + set: preprocessor-macro-params + - match: '{{identifier}}' + scope: entity.name.constant.preprocessor.c++ + set: preprocessor-macro-definition + + preprocessor-macro-params: + - meta_scope: meta.preprocessor.macro.parameters.c++ meta.group.c++ + - match: '{{identifier}}' + scope: variable.parameter.c++ + - match: \) + scope: punctuation.section.group.end.c++ + set: preprocessor-macro-definition + - match: ',' + scope: punctuation.separator.c++ + push: + - match: '{{identifier}}' + scope: variable.parameter.c++ + pop: true + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-comments + - match: '\.\.\.' + scope: keyword.operator.variadic.c++ + - match: '(?=\))' + pop: true + - match: (/\*).*(\*/) + scope: comment.block.c++ + captures: + 1: punctuation.definition.comment.c++ + 2: punctuation.definition.comment.c++ + - match: '\S+' + scope: invalid.illegal.unexpected-character.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-comments + - match: '\.\.\.' + scope: keyword.operator.variadic.c++ + - match: (/\*).*(\*/) + scope: comment.block.c++ + captures: + 1: punctuation.definition.comment.c++ + 2: punctuation.definition.comment.c++ + - match: $\n + scope: invalid.illegal.unexpected-end-of-line.c++ + + preprocessor-macro-definition: + - meta_content_scope: meta.preprocessor.macro.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + # Don't define blocks in define statements + - match: '\{' + scope: punctuation.section.block.begin.c++ + - match: '\}' + scope: punctuation.section.block.end.c++ + - include: expressions + + preprocessor-practical-workarounds: + - include: preprocessor-convention-ignore-uppercase-ident-lines + - include: scope:source.c#preprocessor-convention-ignore-uppercase-calls-without-semicolon + + preprocessor-convention-ignore-uppercase-ident-lines: + - match: ^(\s*{{macro_identifier}})+\s*$ + scope: meta.assumed-macro.c++ + push: + # It's possible that we are dealing with a function return type on its own line, and the + # name of the function is on the subsequent line. + - match: '(?={{path_lookahead}}({{generic_lookahead}}({{path_lookahead}})?)\s*\()' + set: [function-definition-params, global-function-identifier-generic] + - match: '(?={{path_lookahead}}\s*\()' + set: [function-definition-params, global-function-identifier] + - match: ^ + pop: true + + preprocessor-other: + - match: ^\s*(#\s*(?:if|ifdef|ifndef|elif|else|line|pragma|undef))\b + captures: + 1: keyword.control.import.c++ + push: + - meta_scope: meta.preprocessor.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + - match: \bdefined\b + scope: keyword.control.c++ + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + - match: ^\s*(#\s*(?:error|warning))\b + captures: + 1: keyword.control.import.error.c++ + push: + - meta_scope: meta.preprocessor.diagnostic.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + - include: strings + - match: '\S+' + scope: string.unquoted.c++ + - match: ^\s*(#\s*(?:include|include_next|import))\b + captures: + 1: keyword.control.import.include.c++ + push: + - meta_scope: meta.preprocessor.include.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + - match: '"' + scope: punctuation.definition.string.begin.c++ + push: + - meta_scope: string.quoted.double.include.c++ + - match: '"' + scope: punctuation.definition.string.end.c++ + pop: true + - match: < + scope: punctuation.definition.string.begin.c++ + push: + - meta_scope: string.quoted.other.lt-gt.include.c++ + - match: '>' + scope: punctuation.definition.string.end.c++ + pop: true + - include: preprocessor-practical-workarounds diff --git a/libs/fmt/support/README b/libs/fmt/support/README new file mode 100644 index 0000000000000000000000000000000000000000..468f5485d9e476c547b65ea92b31fcbc1fef9b31 --- /dev/null +++ b/libs/fmt/support/README @@ -0,0 +1,4 @@ +This directory contains build support files such as + +* CMake modules +* Build scripts diff --git a/libs/fmt/support/Vagrantfile b/libs/fmt/support/Vagrantfile new file mode 100644 index 0000000000000000000000000000000000000000..f6b5f9366259be1d882089eb96e4dca731873e9b --- /dev/null +++ b/libs/fmt/support/Vagrantfile @@ -0,0 +1,20 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# A vagrant config for testing against gcc-4.8. +Vagrant.configure("2") do |config| + config.vm.box = "ubuntu/xenial64" + config.disksize.size = '15GB' + + config.vm.provider "virtualbox" do |vb| + vb.memory = "4096" + end + + config.vm.provision "shell", inline: <<-SHELL + apt-get update + apt-get install -y g++ make wget git + wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz + tar xzf cmake-3.14.4-Linux-x86_64.tar.gz + ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin + SHELL +end diff --git a/libs/fmt/support/appveyor-build.py b/libs/fmt/support/appveyor-build.py new file mode 100644 index 0000000000000000000000000000000000000000..6544610326739f279947fc2b2a4309a742f23743 --- /dev/null +++ b/libs/fmt/support/appveyor-build.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# Build the project on AppVeyor. + +import os +from subprocess import check_call + +build = os.environ['BUILD'] +config = os.environ['CONFIGURATION'] +platform = os.environ['PLATFORM'] +path = os.environ['PATH'] +image = os.environ['APPVEYOR_BUILD_WORKER_IMAGE'] +jobid = os.environ['APPVEYOR_JOB_ID'] +cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config, '..'] +if build == 'mingw': + cmake_command.append('-GMinGW Makefiles') + build_command = ['mingw32-make', '-j4'] + test_command = ['mingw32-make', 'test'] + # Remove the path to Git bin directory from $PATH because it breaks + # MinGW config. + path = path.replace(r'C:\Program Files (x86)\Git\bin', '') + os.environ['PATH'] = r'C:\MinGW\bin;' + path +else: + # Add MSBuild 14.0 to PATH as described in + # http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc. + os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\15.0\Bin;' + path + if image == 'Visual Studio 2019': + generator = 'Visual Studio 16 2019' + if platform == 'x64': + cmake_command.extend(['-A', 'x64']) + else: + if image == 'Visual Studio 2015': + generator = 'Visual Studio 14 2015' + elif image == 'Visual Studio 2017': + generator = 'Visual Studio 15 2017' + if platform == 'x64': + generator += ' Win64' + cmake_command.append('-G' + generator) + build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4'] + test_command = ['ctest', '-C', config] + +check_call(cmake_command) +check_call(build_command) +check_call(test_command) diff --git a/libs/fmt/support/appveyor.yml b/libs/fmt/support/appveyor.yml new file mode 100644 index 0000000000000000000000000000000000000000..64ca58a949c243e5b6f87cc953446ea6e0d1d146 --- /dev/null +++ b/libs/fmt/support/appveyor.yml @@ -0,0 +1,31 @@ +configuration: + - Debug + - Release + +clone_depth: 1 + +image: + - Visual Studio 2015 + +platform: + - x64 + +environment: + CTEST_OUTPUT_ON_FAILURE: 1 + MSVC_DEFAULT_OPTIONS: ON + BUILD: msvc + +before_build: + - mkdir build + - cd build + +build_script: + - python ../support/appveyor-build.py + +on_failure: + - appveyor PushArtifact Testing/Temporary/LastTest.log + - appveyor AddTest test + +# Uncomment this to debug AppVeyor failures. +#on_finish: +# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/libs/fmt/support/build-docs.py b/libs/fmt/support/build-docs.py new file mode 100644 index 0000000000000000000000000000000000000000..70100ed4dfe8f86c210c13bac6f6c6c954575440 --- /dev/null +++ b/libs/fmt/support/build-docs.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# Build the documentation in CI. + +from __future__ import print_function +import errno, os, shutil, subprocess, sys, urllib +from subprocess import call, check_call, Popen, PIPE, STDOUT + +def rmtree_if_exists(dir): + try: + shutil.rmtree(dir) + except OSError as e: + if e.errno == errno.ENOENT: + pass + +# Build the docs. +fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +sys.path.insert(0, os.path.join(fmt_dir, 'doc')) +import build +build.create_build_env() +html_dir = build.build_docs() + +repo = 'fmtlib.github.io' +branch = os.environ['GITHUB_REF'] +is_ci = 'CI' in os.environ +if is_ci and branch != 'refs/heads/master': + print('Branch: ' + branch) + exit(0) # Ignore non-master branches +if is_ci and 'KEY' not in os.environ: + # Don't update the repo if building in CI from an account that doesn't have + # push access. + print('Skipping update of ' + repo) + exit(0) + +# Clone the fmtlib.github.io repo. +rmtree_if_exists(repo) +git_url = 'https://github.com/' if is_ci else 'git@github.com:' +check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)]) + +# Copy docs to the repo. +target_dir = os.path.join(repo, 'dev') +rmtree_if_exists(target_dir) +shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*')) +if is_ci: + check_call(['git', 'config', '--global', 'user.name', 'fmtbot']) + check_call(['git', 'config', '--global', 'user.email', 'viz@fmt.dev']) + +# Push docs to GitHub pages. +check_call(['git', 'add', '--all'], cwd=repo) +if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo): + check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo) + cmd = 'git push' + if is_ci: + cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master' + p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo) + # Print the output without the key. + print(p.communicate()[0].replace(os.environ['KEY'], '$KEY')) + if p.returncode != 0: + raise subprocess.CalledProcessError(p.returncode, cmd) diff --git a/libs/fmt/support/build.gradle b/libs/fmt/support/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..8d18da6bc691f5e904c0767296624be24252963e --- /dev/null +++ b/libs/fmt/support/build.gradle @@ -0,0 +1,132 @@ +import java.nio.file.Paths + +// General gradle arguments for root project +buildscript { + repositories { + google() + jcenter() + } + dependencies { + // + // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle + // + // Notice that 4.0.0 here is the version of [Android Gradle Plugin] + // Accroding to URL above you will need Gradle 6.1 or higher + // + classpath "com.android.tools.build:gradle:4.1.1" + } +} +repositories { + google() + jcenter() +} + +// Project's root where CMakeLists.txt exists: rootDir/support/.cxx -> rootDir +def rootDir = Paths.get(project.buildDir.getParent()).getParent() +println("rootDir: ${rootDir}") + +// Output: Shared library (.so) for Android +apply plugin: "com.android.library" +android { + compileSdkVersion 25 // Android 7.0 + + // Target ABI + // - This option controls target platform of module + // - The platform might be limited by compiler's support + // some can work with Clang(default), but some can work only with GCC... + // if bad, both toolchains might not support it + splits { + abi { + enable true + // Specify platforms for Application + reset() + include "arm64-v8a", "armeabi-v7a", "x86_64" + } + } + ndkVersion "21.3.6528147" // ANDROID_NDK_HOME is deprecated. Be explicit + + defaultConfig { + minSdkVersion 21 // Android 5.0+ + targetSdkVersion 25 // Follow Compile SDK + versionCode 34 // Follow release count + versionName "7.1.2" // Follow Official version + + externalNativeBuild { + cmake { + arguments "-DANDROID_STL=c++_shared" // Specify Android STL + arguments "-DBUILD_SHARED_LIBS=true" // Build shared object + arguments "-DFMT_TEST=false" // Skip test + arguments "-DFMT_DOC=false" // Skip document + cppFlags "-std=c++17" + targets "fmt" + } + } + println(externalNativeBuild.cmake.cppFlags) + println(externalNativeBuild.cmake.arguments) + } + + // External Native build + // - Use existing CMakeList.txt + // - Give path to CMake. This gradle file should be + // neighbor of the top level cmake + externalNativeBuild { + cmake { + version "3.10.0+" + path "${rootDir}/CMakeLists.txt" + // buildStagingDirectory "./build" // Custom path for cmake output + } + } + + sourceSets{ + // Android Manifest for Gradle + main { + manifest.srcFile "AndroidManifest.xml" + } + } + + // https://developer.android.com/studio/build/native-dependencies#build_system_configuration + buildFeatures { + prefab true + prefabPublishing true + } + prefab { + fmt { + headers "${rootDir}/include" + } + } +} + +assemble.doLast +{ + // Instead of `ninja install`, Gradle will deploy the files. + // We are doing this since FMT is dependent to the ANDROID_STL after build + copy { + from "build/intermediates/cmake" + into "${rootDir}/libs" + } + // Copy debug binaries + copy { + from "${rootDir}/libs/debug/obj" + into "${rootDir}/libs/debug" + } + // Copy Release binaries + copy { + from "${rootDir}/libs/release/obj" + into "${rootDir}/libs/release" + } + // Remove empty directory + delete "${rootDir}/libs/debug/obj" + delete "${rootDir}/libs/release/obj" + + // Copy AAR files. Notice that the aar is named after the folder of this script. + copy { + from "build/outputs/aar/support-release.aar" + into "${rootDir}/libs" + rename "support-release.aar", "fmt-release.aar" + } + copy { + from "build/outputs/aar/support-debug.aar" + into "${rootDir}/libs" + rename "support-debug.aar", "fmt-debug.aar" + } +} diff --git a/libs/fmt/support/cmake/FindSetEnv.cmake b/libs/fmt/support/cmake/FindSetEnv.cmake new file mode 100644 index 0000000000000000000000000000000000000000..4e2da5408ac3c54975ff9acc7091ffdbb6c83b0d --- /dev/null +++ b/libs/fmt/support/cmake/FindSetEnv.cmake @@ -0,0 +1,7 @@ +# A CMake script to find SetEnv.cmd. + +find_program(WINSDK_SETENV NAMES SetEnv.cmd + PATHS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]/bin") +if (WINSDK_SETENV AND PRINT_PATH) + execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${WINSDK_SETENV}") +endif () diff --git a/libs/fmt/support/cmake/JoinPaths.cmake b/libs/fmt/support/cmake/JoinPaths.cmake new file mode 100644 index 0000000000000000000000000000000000000000..32d6d6685c06e84a18ff23c8eec8cf0f9eec9bff --- /dev/null +++ b/libs/fmt/support/cmake/JoinPaths.cmake @@ -0,0 +1,26 @@ +# This module provides function for joining paths +# known from from most languages +# +# Original license: +# SPDX-License-Identifier: (MIT OR CC0-1.0) +# Explicit permission given to distribute this module under +# the terms of the project as described in /LICENSE.rst. +# Copyright 2020 Jan Tojnar +# https://github.com/jtojnar/cmake-snips +# +# Modelled after Python’s os.path.join +# https://docs.python.org/3.7/library/os.path.html#os.path.join +# Windows not supported +function(join_paths joined_path first_path_segment) + set(temp_path "${first_path_segment}") + foreach(current_segment IN LISTS ARGN) + if(NOT ("${current_segment}" STREQUAL "")) + if(IS_ABSOLUTE "${current_segment}") + set(temp_path "${current_segment}") + else() + set(temp_path "${temp_path}/${current_segment}") + endif() + endif() + endforeach() + set(${joined_path} "${temp_path}" PARENT_SCOPE) +endfunction() diff --git a/libs/fmt/support/cmake/cxx14.cmake b/libs/fmt/support/cmake/cxx14.cmake new file mode 100644 index 0000000000000000000000000000000000000000..16ff57541b18c005ade84d0c9fe87e0676c3f35c --- /dev/null +++ b/libs/fmt/support/cmake/cxx14.cmake @@ -0,0 +1,70 @@ +# C++14 feature support detection + +include(CheckCXXSourceCompiles) +include(CheckCXXCompilerFlag) + +if (NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 11) +endif() +message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}") + +if (CMAKE_CXX_STANDARD EQUAL 20) + check_cxx_compiler_flag(-std=c++20 has_std_20_flag) + check_cxx_compiler_flag(-std=c++2a has_std_2a_flag) + + if (has_std_20_flag) + set(CXX_STANDARD_FLAG -std=c++20) + elseif (has_std_2a_flag) + set(CXX_STANDARD_FLAG -std=c++2a) + endif () +elseif (CMAKE_CXX_STANDARD EQUAL 17) + check_cxx_compiler_flag(-std=c++17 has_std_17_flag) + check_cxx_compiler_flag(-std=c++1z has_std_1z_flag) + + if (has_std_17_flag) + set(CXX_STANDARD_FLAG -std=c++17) + elseif (has_std_1z_flag) + set(CXX_STANDARD_FLAG -std=c++1z) + endif () +elseif (CMAKE_CXX_STANDARD EQUAL 14) + check_cxx_compiler_flag(-std=c++14 has_std_14_flag) + check_cxx_compiler_flag(-std=c++1y has_std_1y_flag) + + if (has_std_14_flag) + set(CXX_STANDARD_FLAG -std=c++14) + elseif (has_std_1y_flag) + set(CXX_STANDARD_FLAG -std=c++1y) + endif () +elseif (CMAKE_CXX_STANDARD EQUAL 11) + check_cxx_compiler_flag(-std=c++11 has_std_11_flag) + check_cxx_compiler_flag(-std=c++0x has_std_0x_flag) + + if (has_std_11_flag) + set(CXX_STANDARD_FLAG -std=c++11) + elseif (has_std_0x_flag) + set(CXX_STANDARD_FLAG -std=c++0x) + endif () +endif () + +set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG}) + +# Check if user-defined literals are available +check_cxx_source_compiles(" + void operator\"\" _udl(long double); + int main() {}" + SUPPORTS_USER_DEFINED_LITERALS) +if (NOT SUPPORTS_USER_DEFINED_LITERALS) + set (SUPPORTS_USER_DEFINED_LITERALS OFF) +endif () + +# Check if <variant> is available +set(CMAKE_REQUIRED_FLAGS -std=c++1z) +check_cxx_source_compiles(" + #include <variant> + int main() {}" + FMT_HAS_VARIANT) +if (NOT FMT_HAS_VARIANT) + set (FMT_HAS_VARIANT OFF) +endif () + +set(CMAKE_REQUIRED_FLAGS ) diff --git a/libs/fmt/support/cmake/fmt-config.cmake.in b/libs/fmt/support/cmake/fmt-config.cmake.in new file mode 100644 index 0000000000000000000000000000000000000000..71e302860bc9c1dbc2db2b02cf341279ff82bfd7 --- /dev/null +++ b/libs/fmt/support/cmake/fmt-config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) +check_required_components(fmt) diff --git a/libs/fmt/support/cmake/fmt.pc.in b/libs/fmt/support/cmake/fmt.pc.in new file mode 100644 index 0000000000000000000000000000000000000000..29976a8af0b241881fd6ba30c27582f619821c95 --- /dev/null +++ b/libs/fmt/support/cmake/fmt.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@libdir_for_pc_file@ +includedir=@includedir_for_pc_file@ + +Name: fmt +Description: A modern formatting library +Version: @FMT_VERSION@ +Libs: -L${libdir} -l@FMT_LIB_NAME@ +Cflags: -I${includedir} + diff --git a/libs/fmt/support/compute-powers.py b/libs/fmt/support/compute-powers.py new file mode 100644 index 0000000000000000000000000000000000000000..601063d4248f47858f51d854de627d70c307bbe0 --- /dev/null +++ b/libs/fmt/support/compute-powers.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# Compute 10 ** exp with exp in the range [min_exponent, max_exponent] and print +# normalized (with most-significant bit equal to 1) significands in hexadecimal. + +from __future__ import print_function + +min_exponent = -348 +max_exponent = 340 +step = 8 +significand_size = 64 +exp_offset = 2000 + +class fp: + pass + +powers = [] +for i, exp in enumerate(range(min_exponent, max_exponent + 1, step)): + result = fp() + n = 10 ** exp if exp >= 0 else 2 ** exp_offset / 10 ** -exp + k = significand_size + 1 + # Convert to binary and round. + binary = '{:b}'.format(n) + result.f = (int('{:0<{}}'.format(binary[:k], k), 2) + 1) / 2 + result.e = len(binary) - (exp_offset if exp < 0 else 0) - significand_size + powers.append(result) + # Sanity check. + exp_offset10 = 400 + actual = result.f * 10 ** exp_offset10 + if result.e > 0: + actual *= 2 ** result.e + else: + for j in range(-result.e): + actual /= 2 + expected = 10 ** (exp_offset10 + exp) + precision = len('{}'.format(expected)) - len('{}'.format(actual - expected)) + if precision < 19: + print('low precision:', precision) + exit(1) + +print('Significands:', end='') +for i, fp in enumerate(powers): + if i % 3 == 0: + print(end='\n ') + print(' {:0<#16x}'.format(fp.f, ), end=',') + +print('\n\nExponents:', end='') +for i, fp in enumerate(powers): + if i % 11 == 0: + print(end='\n ') + print(' {:5}'.format(fp.e), end=',') + +print('\n\nMax exponent difference:', + max([x.e - powers[i - 1].e for i, x in enumerate(powers)][1:])) diff --git a/libs/fmt/support/docopt.py b/libs/fmt/support/docopt.py new file mode 100644 index 0000000000000000000000000000000000000000..2e43f7cef8193b2273e687e27204c98115078d57 --- /dev/null +++ b/libs/fmt/support/docopt.py @@ -0,0 +1,581 @@ +"""Pythonic command-line interface parser that will make you smile. + + * http://docopt.org + * Repository and issue-tracker: https://github.com/docopt/docopt + * Licensed under terms of MIT license (see LICENSE-MIT) + * Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com + +""" +import sys +import re + + +__all__ = ['docopt'] +__version__ = '0.6.1' + + +class DocoptLanguageError(Exception): + + """Error in construction of usage-message by developer.""" + + +class DocoptExit(SystemExit): + + """Exit in case user invoked program with incorrect arguments.""" + + usage = '' + + def __init__(self, message=''): + SystemExit.__init__(self, (message + '\n' + self.usage).strip()) + + +class Pattern(object): + + def __eq__(self, other): + return repr(self) == repr(other) + + def __hash__(self): + return hash(repr(self)) + + def fix(self): + self.fix_identities() + self.fix_repeating_arguments() + return self + + def fix_identities(self, uniq=None): + """Make pattern-tree tips point to same object if they are equal.""" + if not hasattr(self, 'children'): + return self + uniq = list(set(self.flat())) if uniq is None else uniq + for i, child in enumerate(self.children): + if not hasattr(child, 'children'): + assert child in uniq + self.children[i] = uniq[uniq.index(child)] + else: + child.fix_identities(uniq) + + def fix_repeating_arguments(self): + """Fix elements that should accumulate/increment values.""" + either = [list(child.children) for child in transform(self).children] + for case in either: + for e in [child for child in case if case.count(child) > 1]: + if type(e) is Argument or type(e) is Option and e.argcount: + if e.value is None: + e.value = [] + elif type(e.value) is not list: + e.value = e.value.split() + if type(e) is Command or type(e) is Option and e.argcount == 0: + e.value = 0 + return self + + +def transform(pattern): + """Expand pattern into an (almost) equivalent one, but with single Either. + + Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) + Quirks: [-a] => (-a), (-a...) => (-a -a) + + """ + result = [] + groups = [[pattern]] + while groups: + children = groups.pop(0) + parents = [Required, Optional, OptionsShortcut, Either, OneOrMore] + if any(t in map(type, children) for t in parents): + child = [c for c in children if type(c) in parents][0] + children.remove(child) + if type(child) is Either: + for c in child.children: + groups.append([c] + children) + elif type(child) is OneOrMore: + groups.append(child.children * 2 + children) + else: + groups.append(child.children + children) + else: + result.append(children) + return Either(*[Required(*e) for e in result]) + + +class LeafPattern(Pattern): + + """Leaf/terminal node of a pattern tree.""" + + def __init__(self, name, value=None): + self.name, self.value = name, value + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value) + + def flat(self, *types): + return [self] if not types or type(self) in types else [] + + def match(self, left, collected=None): + collected = [] if collected is None else collected + pos, match = self.single_match(left) + if match is None: + return False, left, collected + left_ = left[:pos] + left[pos + 1:] + same_name = [a for a in collected if a.name == self.name] + if type(self.value) in (int, list): + if type(self.value) is int: + increment = 1 + else: + increment = ([match.value] if type(match.value) is str + else match.value) + if not same_name: + match.value = increment + return True, left_, collected + [match] + same_name[0].value += increment + return True, left_, collected + return True, left_, collected + [match] + + +class BranchPattern(Pattern): + + """Branch/inner node of a pattern tree.""" + + def __init__(self, *children): + self.children = list(children) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, + ', '.join(repr(a) for a in self.children)) + + def flat(self, *types): + if type(self) in types: + return [self] + return sum([child.flat(*types) for child in self.children], []) + + +class Argument(LeafPattern): + + def single_match(self, left): + for n, pattern in enumerate(left): + if type(pattern) is Argument: + return n, Argument(self.name, pattern.value) + return None, None + + @classmethod + def parse(class_, source): + name = re.findall('(<\S*?>)', source)[0] + value = re.findall('\[default: (.*)\]', source, flags=re.I) + return class_(name, value[0] if value else None) + + +class Command(Argument): + + def __init__(self, name, value=False): + self.name, self.value = name, value + + def single_match(self, left): + for n, pattern in enumerate(left): + if type(pattern) is Argument: + if pattern.value == self.name: + return n, Command(self.name, True) + else: + break + return None, None + + +class Option(LeafPattern): + + def __init__(self, short=None, long=None, argcount=0, value=False): + assert argcount in (0, 1) + self.short, self.long, self.argcount = short, long, argcount + self.value = None if value is False and argcount else value + + @classmethod + def parse(class_, option_description): + short, long, argcount, value = None, None, 0, False + options, _, description = option_description.strip().partition(' ') + options = options.replace(',', ' ').replace('=', ' ') + for s in options.split(): + if s.startswith('--'): + long = s + elif s.startswith('-'): + short = s + else: + argcount = 1 + if argcount: + matched = re.findall('\[default: (.*)\]', description, flags=re.I) + value = matched[0] if matched else None + return class_(short, long, argcount, value) + + def single_match(self, left): + for n, pattern in enumerate(left): + if self.name == pattern.name: + return n, pattern + return None, None + + @property + def name(self): + return self.long or self.short + + def __repr__(self): + return 'Option(%r, %r, %r, %r)' % (self.short, self.long, + self.argcount, self.value) + + +class Required(BranchPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + l = left + c = collected + for pattern in self.children: + matched, l, c = pattern.match(l, c) + if not matched: + return False, left, collected + return True, l, c + + +class Optional(BranchPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + for pattern in self.children: + m, left, collected = pattern.match(left, collected) + return True, left, collected + + +class OptionsShortcut(Optional): + + """Marker/placeholder for [options] shortcut.""" + + +class OneOrMore(BranchPattern): + + def match(self, left, collected=None): + assert len(self.children) == 1 + collected = [] if collected is None else collected + l = left + c = collected + l_ = None + matched = True + times = 0 + while matched: + # could it be that something didn't match but changed l or c? + matched, l, c = self.children[0].match(l, c) + times += 1 if matched else 0 + if l_ == l: + break + l_ = l + if times >= 1: + return True, l, c + return False, left, collected + + +class Either(BranchPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + outcomes = [] + for pattern in self.children: + matched, _, _ = outcome = pattern.match(left, collected) + if matched: + outcomes.append(outcome) + if outcomes: + return min(outcomes, key=lambda outcome: len(outcome[1])) + return False, left, collected + + +class Tokens(list): + + def __init__(self, source, error=DocoptExit): + self += source.split() if hasattr(source, 'split') else source + self.error = error + + @staticmethod + def from_pattern(source): + source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source) + source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s] + return Tokens(source, error=DocoptLanguageError) + + def move(self): + return self.pop(0) if len(self) else None + + def current(self): + return self[0] if len(self) else None + + +def parse_long(tokens, options): + """long ::= '--' chars [ ( ' ' | '=' ) chars ] ;""" + long, eq, value = tokens.move().partition('=') + assert long.startswith('--') + value = None if eq == value == '' else value + similar = [o for o in options if o.long == long] + if tokens.error is DocoptExit and similar == []: # if no exact match + similar = [o for o in options if o.long and o.long.startswith(long)] + if len(similar) > 1: # might be simply specified ambiguously 2+ times? + raise tokens.error('%s is not a unique prefix: %s?' % + (long, ', '.join(o.long for o in similar))) + elif len(similar) < 1: + argcount = 1 if eq == '=' else 0 + o = Option(None, long, argcount) + options.append(o) + if tokens.error is DocoptExit: + o = Option(None, long, argcount, value if argcount else True) + else: + o = Option(similar[0].short, similar[0].long, + similar[0].argcount, similar[0].value) + if o.argcount == 0: + if value is not None: + raise tokens.error('%s must not have an argument' % o.long) + else: + if value is None: + if tokens.current() in [None, '--']: + raise tokens.error('%s requires argument' % o.long) + value = tokens.move() + if tokens.error is DocoptExit: + o.value = value if value is not None else True + return [o] + + +def parse_shorts(tokens, options): + """shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;""" + token = tokens.move() + assert token.startswith('-') and not token.startswith('--') + left = token.lstrip('-') + parsed = [] + while left != '': + short, left = '-' + left[0], left[1:] + similar = [o for o in options if o.short == short] + if len(similar) > 1: + raise tokens.error('%s is specified ambiguously %d times' % + (short, len(similar))) + elif len(similar) < 1: + o = Option(short, None, 0) + options.append(o) + if tokens.error is DocoptExit: + o = Option(short, None, 0, True) + else: # why copying is necessary here? + o = Option(short, similar[0].long, + similar[0].argcount, similar[0].value) + value = None + if o.argcount != 0: + if left == '': + if tokens.current() in [None, '--']: + raise tokens.error('%s requires argument' % short) + value = tokens.move() + else: + value = left + left = '' + if tokens.error is DocoptExit: + o.value = value if value is not None else True + parsed.append(o) + return parsed + + +def parse_pattern(source, options): + tokens = Tokens.from_pattern(source) + result = parse_expr(tokens, options) + if tokens.current() is not None: + raise tokens.error('unexpected ending: %r' % ' '.join(tokens)) + return Required(*result) + + +def parse_expr(tokens, options): + """expr ::= seq ( '|' seq )* ;""" + seq = parse_seq(tokens, options) + if tokens.current() != '|': + return seq + result = [Required(*seq)] if len(seq) > 1 else seq + while tokens.current() == '|': + tokens.move() + seq = parse_seq(tokens, options) + result += [Required(*seq)] if len(seq) > 1 else seq + return [Either(*result)] if len(result) > 1 else result + + +def parse_seq(tokens, options): + """seq ::= ( atom [ '...' ] )* ;""" + result = [] + while tokens.current() not in [None, ']', ')', '|']: + atom = parse_atom(tokens, options) + if tokens.current() == '...': + atom = [OneOrMore(*atom)] + tokens.move() + result += atom + return result + + +def parse_atom(tokens, options): + """atom ::= '(' expr ')' | '[' expr ']' | 'options' + | long | shorts | argument | command ; + """ + token = tokens.current() + result = [] + if token in '([': + tokens.move() + matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token] + result = pattern(*parse_expr(tokens, options)) + if tokens.move() != matching: + raise tokens.error("unmatched '%s'" % token) + return [result] + elif token == 'options': + tokens.move() + return [OptionsShortcut()] + elif token.startswith('--') and token != '--': + return parse_long(tokens, options) + elif token.startswith('-') and token not in ('-', '--'): + return parse_shorts(tokens, options) + elif token.startswith('<') and token.endswith('>') or token.isupper(): + return [Argument(tokens.move())] + else: + return [Command(tokens.move())] + + +def parse_argv(tokens, options, options_first=False): + """Parse command-line argument vector. + + If options_first: + argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; + else: + argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; + + """ + parsed = [] + while tokens.current() is not None: + if tokens.current() == '--': + return parsed + [Argument(None, v) for v in tokens] + elif tokens.current().startswith('--'): + parsed += parse_long(tokens, options) + elif tokens.current().startswith('-') and tokens.current() != '-': + parsed += parse_shorts(tokens, options) + elif options_first: + return parsed + [Argument(None, v) for v in tokens] + else: + parsed.append(Argument(None, tokens.move())) + return parsed + + +def parse_defaults(doc): + defaults = [] + for s in parse_section('options:', doc): + # FIXME corner case "bla: options: --foo" + _, _, s = s.partition(':') # get rid of "options:" + split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:] + split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] + options = [Option.parse(s) for s in split if s.startswith('-')] + defaults += options + return defaults + + +def parse_section(name, source): + pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)', + re.IGNORECASE | re.MULTILINE) + return [s.strip() for s in pattern.findall(source)] + + +def formal_usage(section): + _, _, section = section.partition(':') # drop "usage:" + pu = section.split() + return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )' + + +def extras(help, version, options, doc): + if help and any((o.name in ('-h', '--help')) and o.value for o in options): + print(doc.strip("\n")) + sys.exit() + if version and any(o.name == '--version' and o.value for o in options): + print(version) + sys.exit() + + +class Dict(dict): + def __repr__(self): + return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items())) + + +def docopt(doc, argv=None, help=True, version=None, options_first=False): + """Parse `argv` based on command-line interface described in `doc`. + + `docopt` creates your command-line interface based on its + description that you pass as `doc`. Such description can contain + --options, <positional-argument>, commands, which could be + [optional], (required), (mutually | exclusive) or repeated... + + Parameters + ---------- + doc : str + Description of your command-line interface. + argv : list of str, optional + Argument vector to be parsed. sys.argv[1:] is used if not + provided. + help : bool (default: True) + Set to False to disable automatic help on -h or --help + options. + version : any object + If passed, the object will be printed if --version is in + `argv`. + options_first : bool (default: False) + Set to True to require options precede positional arguments, + i.e. to forbid options and positional arguments intermix. + + Returns + ------- + args : dict + A dictionary, where keys are names of command-line elements + such as e.g. "--verbose" and "<path>", and values are the + parsed values of those elements. + + Example + ------- + >>> from docopt import docopt + >>> doc = ''' + ... Usage: + ... my_program tcp <host> <port> [--timeout=<seconds>] + ... my_program serial <port> [--baud=<n>] [--timeout=<seconds>] + ... my_program (-h | --help | --version) + ... + ... Options: + ... -h, --help Show this screen and exit. + ... --baud=<n> Baudrate [default: 9600] + ... ''' + >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30'] + >>> docopt(doc, argv) + {'--baud': '9600', + '--help': False, + '--timeout': '30', + '--version': False, + '<host>': '127.0.0.1', + '<port>': '80', + 'serial': False, + 'tcp': True} + + See also + -------- + * For video introduction see http://docopt.org + * Full documentation is available in README.rst as well as online + at https://github.com/docopt/docopt#readme + + """ + argv = sys.argv[1:] if argv is None else argv + + usage_sections = parse_section('usage:', doc) + if len(usage_sections) == 0: + raise DocoptLanguageError('"usage:" (case-insensitive) not found.') + if len(usage_sections) > 1: + raise DocoptLanguageError('More than one "usage:" (case-insensitive).') + DocoptExit.usage = usage_sections[0] + + options = parse_defaults(doc) + pattern = parse_pattern(formal_usage(DocoptExit.usage), options) + # [default] syntax for argument is disabled + #for a in pattern.flat(Argument): + # same_name = [d for d in arguments if d.name == a.name] + # if same_name: + # a.value = same_name[0].value + argv = parse_argv(Tokens(argv), list(options), options_first) + pattern_options = set(pattern.flat(Option)) + for options_shortcut in pattern.flat(OptionsShortcut): + doc_options = parse_defaults(doc) + options_shortcut.children = list(set(doc_options) - pattern_options) + #if any_options: + # options_shortcut.children += [Option(o.short, o.long, o.argcount) + # for o in argv if type(o) is Option] + extras(help, version, argv, doc) + matched, left, collected = pattern.fix().match(argv) + if matched and left == []: # better error message if left? + return Dict((a.name, a.value) for a in (pattern.flat() + collected)) + raise DocoptExit() diff --git a/libs/fmt/support/manage.py b/libs/fmt/support/manage.py new file mode 100644 index 0000000000000000000000000000000000000000..9f2706694002d719f072ce1a1ab2f1e0d8d6bbf8 --- /dev/null +++ b/libs/fmt/support/manage.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python + +"""Manage site and releases. + +Usage: + manage.py release [<branch>] + manage.py site + +For the release command $FMT_TOKEN should contain a GitHub personal access token +obtained from https://github.com/settings/tokens. +""" + +from __future__ import print_function +import datetime, docopt, errno, fileinput, json, os +import re, requests, shutil, sys, tempfile +from contextlib import contextmanager +from distutils.version import LooseVersion +from subprocess import check_call + + +class Git: + def __init__(self, dir): + self.dir = dir + + def call(self, method, args, **kwargs): + return check_call(['git', method] + list(args), **kwargs) + + def add(self, *args): + return self.call('add', args, cwd=self.dir) + + def checkout(self, *args): + return self.call('checkout', args, cwd=self.dir) + + def clean(self, *args): + return self.call('clean', args, cwd=self.dir) + + def clone(self, *args): + return self.call('clone', list(args) + [self.dir]) + + def commit(self, *args): + return self.call('commit', args, cwd=self.dir) + + def pull(self, *args): + return self.call('pull', args, cwd=self.dir) + + def push(self, *args): + return self.call('push', args, cwd=self.dir) + + def reset(self, *args): + return self.call('reset', args, cwd=self.dir) + + def update(self, *args): + clone = not os.path.exists(self.dir) + if clone: + self.clone(*args) + return clone + + +def clean_checkout(repo, branch): + repo.clean('-f', '-d') + repo.reset('--hard') + repo.checkout(branch) + + +class Runner: + def __init__(self, cwd): + self.cwd = cwd + + def __call__(self, *args, **kwargs): + kwargs['cwd'] = kwargs.get('cwd', self.cwd) + check_call(args, **kwargs) + + +def create_build_env(): + """Create a build environment.""" + class Env: + pass + env = Env() + + # Import the documentation build module. + env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + sys.path.insert(0, os.path.join(env.fmt_dir, 'doc')) + import build + + env.build_dir = 'build' + env.versions = build.versions + + # Virtualenv and repos are cached to speed up builds. + build.create_build_env(os.path.join(env.build_dir, 'virtualenv')) + + env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt')) + return env + + +@contextmanager +def rewrite(filename): + class Buffer: + pass + buffer = Buffer() + if not os.path.exists(filename): + buffer.data = '' + yield buffer + return + with open(filename) as f: + buffer.data = f.read() + yield buffer + with open(filename, 'w') as f: + f.write(buffer.data) + + +fmt_repo_url = 'git@github.com:fmtlib/fmt' + + +def update_site(env): + env.fmt_repo.update(fmt_repo_url) + + doc_repo = Git(os.path.join(env.build_dir, 'fmtlib.github.io')) + doc_repo.update('git@github.com:fmtlib/fmtlib.github.io') + + for version in env.versions: + clean_checkout(env.fmt_repo, version) + target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc') + # Remove the old theme. + for entry in os.listdir(target_doc_dir): + path = os.path.join(target_doc_dir, entry) + if os.path.isdir(path): + shutil.rmtree(path) + # Copy the new theme. + for entry in ['_static', '_templates', 'basic-bootstrap', 'bootstrap', + 'conf.py', 'fmt.less']: + src = os.path.join(env.fmt_dir, 'doc', entry) + dst = os.path.join(target_doc_dir, entry) + copy = shutil.copytree if os.path.isdir(src) else shutil.copyfile + copy(src, dst) + # Rename index to contents. + contents = os.path.join(target_doc_dir, 'contents.rst') + if not os.path.exists(contents): + os.rename(os.path.join(target_doc_dir, 'index.rst'), contents) + # Fix issues in reference.rst/api.rst. + for filename in ['reference.rst', 'api.rst', 'index.rst']: + pattern = re.compile('doxygenfunction.. (bin|oct|hexu|hex)$', re.M) + with rewrite(os.path.join(target_doc_dir, filename)) as b: + b.data = b.data.replace('std::ostream &', 'std::ostream&') + b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data) + b.data = b.data.replace('std::FILE*', 'std::FILE *') + b.data = b.data.replace('unsigned int', 'unsigned') + #b.data = b.data.replace('operator""_', 'operator"" _') + b.data = b.data.replace( + 'format_to_n(OutputIt, size_t, string_view, Args&&', + 'format_to_n(OutputIt, size_t, const S&, const Args&') + b.data = b.data.replace( + 'format_to_n(OutputIt, std::size_t, string_view, Args&&', + 'format_to_n(OutputIt, std::size_t, const S&, const Args&') + if version == ('3.0.2'): + b.data = b.data.replace( + 'fprintf(std::ostream&', 'fprintf(std::ostream &') + if version == ('5.3.0'): + b.data = b.data.replace( + 'format_to(OutputIt, const S&, const Args&...)', + 'format_to(OutputIt, const S &, const Args &...)') + if version.startswith('5.') or version.startswith('6.'): + b.data = b.data.replace(', size_t', ', std::size_t') + if version.startswith('7.'): + b.data = b.data.replace(', std::size_t', ', size_t') + b.data = b.data.replace('join(It, It', 'join(It, Sentinel') + b.data = b.data.replace('aa long', 'a long') + b.data = b.data.replace('serveral', 'several') + if version.startswith('6.2.'): + b.data = b.data.replace( + 'vformat(const S&, basic_format_args<' + + 'buffer_context<Char>>)', + 'vformat(const S&, basic_format_args<' + + 'buffer_context<type_identity_t<Char>>>)') + # Fix a broken link in index.rst. + index = os.path.join(target_doc_dir, 'index.rst') + with rewrite(index) as b: + b.data = b.data.replace( + 'doc/latest/index.html#format-string-syntax', 'syntax.html') + # Build the docs. + html_dir = os.path.join(env.build_dir, 'html') + if os.path.exists(html_dir): + shutil.rmtree(html_dir) + include_dir = env.fmt_repo.dir + if LooseVersion(version) >= LooseVersion('5.0.0'): + include_dir = os.path.join(include_dir, 'include', 'fmt') + elif LooseVersion(version) >= LooseVersion('3.0.0'): + include_dir = os.path.join(include_dir, 'fmt') + import build + build.build_docs(version, doc_dir=target_doc_dir, + include_dir=include_dir, work_dir=env.build_dir) + shutil.rmtree(os.path.join(html_dir, '.doctrees')) + # Create symlinks for older versions. + for link, target in {'index': 'contents', 'api': 'reference'}.items(): + link = os.path.join(html_dir, link) + '.html' + target += '.html' + if os.path.exists(os.path.join(html_dir, target)) and \ + not os.path.exists(link): + os.symlink(target, link) + # Copy docs to the website. + version_doc_dir = os.path.join(doc_repo.dir, version) + try: + shutil.rmtree(version_doc_dir) + except OSError as e: + if e.errno != errno.ENOENT: + raise + shutil.move(html_dir, version_doc_dir) + + +def release(args): + env = create_build_env() + fmt_repo = env.fmt_repo + + branch = args.get('<branch>') + if branch is None: + branch = 'master' + if not fmt_repo.update('-b', branch, fmt_repo_url): + clean_checkout(fmt_repo, branch) + + # Convert changelog from RST to GitHub-flavored Markdown and get the + # version. + changelog = 'ChangeLog.rst' + changelog_path = os.path.join(fmt_repo.dir, changelog) + import rst2md + changes, version = rst2md.convert(changelog_path) + cmakelists = 'CMakeLists.txt' + for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists), + inplace=True): + prefix = 'set(FMT_VERSION ' + if line.startswith(prefix): + line = prefix + version + ')\n' + sys.stdout.write(line) + + # Update the version in the changelog. + title_len = 0 + for line in fileinput.input(changelog_path, inplace=True): + if line.decode('utf-8').startswith(version + ' - TBD'): + line = version + ' - ' + datetime.date.today().isoformat() + title_len = len(line) + line += '\n' + elif title_len: + line = '-' * title_len + '\n' + title_len = 0 + sys.stdout.write(line) + + # Add the version to the build script. + script = os.path.join('doc', 'build.py') + script_path = os.path.join(fmt_repo.dir, script) + for line in fileinput.input(script_path, inplace=True): + m = re.match(r'( *versions = )\[(.+)\]', line) + if m: + line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version) + sys.stdout.write(line) + + fmt_repo.checkout('-B', 'release') + fmt_repo.add(changelog, cmakelists, script) + fmt_repo.commit('-m', 'Update version') + + # Build the docs and package. + run = Runner(fmt_repo.dir) + run('cmake', '.') + run('make', 'doc', 'package_source') + update_site(env) + + # Create a release on GitHub. + fmt_repo.push('origin', 'release') + params = {'access_token': os.getenv('FMT_TOKEN')} + r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases', + params=params, + data=json.dumps({'tag_name': version, + 'target_commitish': 'release', + 'body': changes, 'draft': True})) + if r.status_code != 201: + raise Exception('Failed to create a release ' + str(r)) + id = r.json()['id'] + uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' + package = 'fmt-{}.zip'.format(version) + r = requests.post( + '{}/{}/assets?name={}'.format(uploads_url, id, package), + headers={'Content-Type': 'application/zip'}, + params=params, data=open('build/fmt/' + package, 'rb')) + if r.status_code != 201: + raise Exception('Failed to upload an asset ' + str(r)) + + +if __name__ == '__main__': + args = docopt.docopt(__doc__) + if args.get('release'): + release(args) + elif args.get('site'): + update_site(create_build_env()) diff --git a/libs/fmt/support/rst2md.py b/libs/fmt/support/rst2md.py new file mode 100644 index 0000000000000000000000000000000000000000..cf170fe2bd4c2479e194c091aeea0773414efd48 --- /dev/null +++ b/libs/fmt/support/rst2md.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# reStructuredText (RST) to GitHub-flavored Markdown converter + +import re, sys +from docutils import core, nodes, writers + + +def is_github_ref(node): + return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri']) + + +class Translator(nodes.NodeVisitor): + def __init__(self, document): + nodes.NodeVisitor.__init__(self, document) + self.output = '' + self.indent = 0 + self.preserve_newlines = False + + def write(self, text): + self.output += text.replace('\n', '\n' + ' ' * self.indent) + + def visit_document(self, node): + pass + + def depart_document(self, node): + pass + + def visit_section(self, node): + pass + + def depart_section(self, node): + # Skip all sections except the first one. + raise nodes.StopTraversal + + def visit_title(self, node): + self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1) + raise nodes.SkipChildren + + def visit_title_reference(self, node): + raise Exception(node) + + def depart_title(self, node): + pass + + def visit_Text(self, node): + if not self.preserve_newlines: + node = node.replace('\n', ' ') + self.write(node) + + def depart_Text(self, node): + pass + + def visit_bullet_list(self, node): + pass + + def depart_bullet_list(self, node): + pass + + def visit_list_item(self, node): + self.write('* ') + self.indent += 2 + + def depart_list_item(self, node): + self.indent -= 2 + self.write('\n\n') + + def visit_paragraph(self, node): + pass + + def depart_paragraph(self, node): + pass + + def visit_reference(self, node): + if not is_github_ref(node): + self.write('[') + + def depart_reference(self, node): + if not is_github_ref(node): + self.write('](' + node['refuri'] + ')') + + def visit_target(self, node): + pass + + def depart_target(self, node): + pass + + def visit_literal(self, node): + self.write('`') + + def depart_literal(self, node): + self.write('`') + + def visit_literal_block(self, node): + self.write('\n\n```') + if 'c++' in node['classes']: + self.write('c++') + self.write('\n') + self.preserve_newlines = True + + def depart_literal_block(self, node): + self.write('\n```\n') + self.preserve_newlines = False + + def visit_inline(self, node): + pass + + def depart_inline(self, node): + pass + + def visit_image(self, node): + self.write('') + + def depart_image(self, node): + pass + + def write_row(self, row, widths): + for i, entry in enumerate(row): + text = entry[0][0] if len(entry) > 0 else '' + if i != 0: + self.write('|') + self.write('{:{}}'.format(text, widths[i])) + self.write('\n') + + def visit_table(self, node): + table = node.children[0] + colspecs = table[:-2] + thead = table[-2] + tbody = table[-1] + widths = [int(cs['colwidth']) for cs in colspecs] + sep = '|'.join(['-' * w for w in widths]) + '\n' + self.write('\n\n') + self.write_row(thead[0], widths) + self.write(sep) + for row in tbody: + self.write_row(row, widths) + raise nodes.SkipChildren + + def depart_table(self, node): + pass + +class MDWriter(writers.Writer): + """GitHub-flavored markdown writer""" + + supported = ('md',) + """Formats this writer supports.""" + + def translate(self): + translator = Translator(self.document) + self.document.walkabout(translator) + self.output = (translator.output, translator.version) + + +def convert(rst_path): + """Converts RST file to Markdown.""" + return core.publish_file(source_path=rst_path, writer=MDWriter()) + + +if __name__ == '__main__': + convert(sys.argv[1]) diff --git a/libs/fmt/support/rtd/conf.py b/libs/fmt/support/rtd/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..124fb9d4c84c3a15623552056a2adafdfffe46b7 --- /dev/null +++ b/libs/fmt/support/rtd/conf.py @@ -0,0 +1,7 @@ +# Sphinx configuration for readthedocs. + +import os, sys + +master_doc = 'index' +html_theme = 'theme' +html_theme_path = ["."] diff --git a/libs/fmt/support/rtd/index.rst b/libs/fmt/support/rtd/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..7c88322f6246a9f66b9ade5f62d70e76bbf0d347 --- /dev/null +++ b/libs/fmt/support/rtd/index.rst @@ -0,0 +1,2 @@ +If you are not redirected automatically, follow the +`link to the fmt documentation <https://fmt.dev/latest/>`_. diff --git a/libs/fmt/support/rtd/theme/layout.html b/libs/fmt/support/rtd/theme/layout.html new file mode 100644 index 0000000000000000000000000000000000000000..29ebc55bb42e51977b1aba567d629efafcfa2eae --- /dev/null +++ b/libs/fmt/support/rtd/theme/layout.html @@ -0,0 +1,17 @@ +{% extends "basic/layout.html" %} + +{% block extrahead %} +<meta charset="UTF-8"> +<meta http-equiv="refresh" content="1;url=https://fmt.dev/latest/"> +<script type="text/javascript"> + window.location.href = "https://fmt.dev/latest/" +</script> +<title>Page Redirection</title> +{% endblock %} + +{% block document %} +If you are not redirected automatically, follow the <a href='https://fmt.dev/latest/'>link to the fmt documentation</a>. +{% endblock %} + +{% block footer %} +{% endblock %} diff --git a/libs/fmt/support/rtd/theme/theme.conf b/libs/fmt/support/rtd/theme/theme.conf new file mode 100644 index 0000000000000000000000000000000000000000..89e03bbdac5e2ff5c08eb4bc4918067516932c5c --- /dev/null +++ b/libs/fmt/support/rtd/theme/theme.conf @@ -0,0 +1,2 @@ +[theme] +inherit = basic diff --git a/libs/fmt/test/CMakeLists.txt b/libs/fmt/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f1dcf16a3b92d3f9fe9a4808aad01e1d6b111470 --- /dev/null +++ b/libs/fmt/test/CMakeLists.txt @@ -0,0 +1,206 @@ +add_subdirectory(gtest) + +set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc) +add_library(test-main STATIC ${TEST_MAIN_SRC}) +target_link_libraries(test-main gtest fmt) + +include(CheckCXXCompilerFlag) + +# Workaround GTest bug https://github.com/google/googletest/issues/705. +check_cxx_compiler_flag( + -fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS) +if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS) + target_compile_options(test-main PUBLIC -fno-delete-null-pointer-checks) +endif () + +# Use less strict pedantic flags for the tests because GMock doesn't compile +# cleanly with -pedantic and -std=c++98. +if (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) + #set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wno-long-long -Wno-variadic-macros) +endif () + +function(add_fmt_executable name) + add_executable(${name} ${ARGN}) + if (MINGW) + target_link_libraries(${name} -static-libgcc -static-libstdc++) + endif () +endfunction() + +# Adds a test. +# Usage: add_fmt_test(name srcs...) +function(add_fmt_test name) + cmake_parse_arguments(ADD_FMT_TEST "HEADER_ONLY" "" "" ${ARGN}) + + set(sources ${name}.cc ${ADD_FMT_TEST_UNPARSED_ARGUMENTS}) + set(libs test-main) + if (ADD_FMT_TEST_HEADER_ONLY) + set(sources ${sources} ${TEST_MAIN_SRC} ../src/os.cc) + set(libs gtest fmt-header-only) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables) + endif () + endif () + add_fmt_executable(${name} ${sources}) + target_link_libraries(${name} ${libs}) + + # Define if certain C++ features can be used. + if (FMT_PEDANTIC) + target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS}) + endif () + if (FMT_WERROR) + target_compile_options(${name} PRIVATE ${WERROR_FLAG}) + endif () + add_test(NAME ${name} COMMAND ${name}) +endfunction() + +add_fmt_test(args-test) +add_fmt_test(assert-test) +add_fmt_test(chrono-test) +add_fmt_test(color-test) +add_fmt_test(core-test) +add_fmt_test(gtest-extra-test) +add_fmt_test(format-test mock-allocator.h) +if (MSVC) + target_compile_options(format-test PRIVATE /bigobj) +endif () +if (NOT (MSVC AND BUILD_SHARED_LIBS)) + add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc) +endif () +add_fmt_test(locale-test) +add_fmt_test(ostream-test) +add_fmt_test(compile-test) +add_fmt_test(printf-test) +add_fmt_test(ranges-test) +add_fmt_test(scan-test) + +if (NOT MSVC) + # FMT_ENFORCE_COMPILE_STRING is not supported under MSVC due to compiler bugs. + add_fmt_test(enforce-checks-test) + target_compile_definitions(enforce-checks-test PRIVATE + -DFMT_ENFORCE_COMPILE_STRING) +endif () + +if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC) + foreach (flag_var + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if (${flag_var} MATCHES "^(/|-)(MT|MTd)") + set(MSVC_STATIC_RUNTIME ON) + break() + endif() + endforeach() +endif() + +if (NOT MSVC_STATIC_RUNTIME) + add_fmt_executable(posix-mock-test + posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC}) + target_include_directories( + posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include) + target_link_libraries(posix-mock-test gtest) + if (FMT_PEDANTIC) + target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) + endif () + if (HAVE_STRTOD_L) + target_compile_definitions(posix-mock-test PRIVATE FMT_LOCALE) + endif () + add_test(NAME posix-mock-test COMMAND posix-mock-test) + add_fmt_test(os-test) +endif () + +message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}") + +if (FMT_PEDANTIC AND CXX_STANDARD LESS 20) + # MSVC fails to compile GMock when C++17 is enabled. + if (FMT_HAS_VARIANT AND NOT MSVC) + add_fmt_test(std-format-test) + set_property(TARGET std-format-test PROPERTY CXX_STANDARD 17) + endif () + + # Test that the library can be compiled with exceptions disabled. + # -fno-exception is broken in icc: https://github.com/fmtlib/fmt/issues/822. + if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel") + check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG) + endif () + if (HAVE_FNO_EXCEPTIONS_FLAG) + add_library(noexception-test ../src/format.cc) + target_include_directories( + noexception-test PRIVATE ${PROJECT_SOURCE_DIR}/include) + target_compile_options(noexception-test PRIVATE -fno-exceptions) + if (FMT_PEDANTIC) + target_compile_options(noexception-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) + endif () + endif () + + # Test that the library compiles without locale. + add_library(nolocale-test ../src/format.cc) + target_include_directories( + nolocale-test PRIVATE ${PROJECT_SOURCE_DIR}/include) + target_compile_definitions( + nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1) + + add_test(compile-error-test ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test" + "${CMAKE_CURRENT_BINARY_DIR}/compile-error-test" + --build-generator ${CMAKE_GENERATOR} + --build-makeprogram ${CMAKE_MAKE_PROGRAM} + --build-options + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCXX_STANDARD_FLAG=${CXX_STANDARD_FLAG}" + "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}" + "-DSUPPORTS_USER_DEFINED_LITERALS=${SUPPORTS_USER_DEFINED_LITERALS}") +endif () + +# These tests are disabled on Windows because they take too long. +if (FMT_PEDANTIC AND NOT WIN32) + # Test if the targets are found from the build directory. + add_test(find-package-test ${CMAKE_CTEST_COMMAND} + -C ${CMAKE_BUILD_TYPE} + --build-and-test + "${CMAKE_CURRENT_SOURCE_DIR}/find-package-test" + "${CMAKE_CURRENT_BINARY_DIR}/find-package-test" + --build-generator ${CMAKE_GENERATOR} + --build-makeprogram ${CMAKE_MAKE_PROGRAM} + --build-options + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DFMT_DIR=${PROJECT_BINARY_DIR}" + "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}" + "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") + + # Test if the targets are found when add_subdirectory is used. + add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND} + -C ${CMAKE_BUILD_TYPE} + --build-and-test + "${CMAKE_CURRENT_SOURCE_DIR}/add-subdirectory-test" + "${CMAKE_CURRENT_BINARY_DIR}/add-subdirectory-test" + --build-generator ${CMAKE_GENERATOR} + --build-makeprogram ${CMAKE_MAKE_PROGRAM} + --build-options + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}" + "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") +endif () + +# Activate optional CUDA tests if CUDA is found. For version selection see +# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features +if (FMT_CUDA_TEST) + if (${CMAKE_VERSION} VERSION_LESS 3.15) + find_package(CUDA 9.0) + else () + include(CheckLanguage) + check_language(CUDA) + if (CMAKE_CUDA_COMPILER) + enable_language(CUDA OPTIONAL) + set(CUDA_FOUND TRUE) + endif () + endif () + + if (CUDA_FOUND) + add_subdirectory(cuda-test) + add_test(NAME cuda-test COMMAND fmt-in-cuda-test) + endif () +endif () diff --git a/libs/fmt/test/add-subdirectory-test/CMakeLists.txt b/libs/fmt/test/add-subdirectory-test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..9cc4b0e97fe8d967be4cf4826d48d20706c90a1a --- /dev/null +++ b/libs/fmt/test/add-subdirectory-test/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.1...3.18) + +project(fmt-test) + +add_subdirectory(../.. fmt) + +add_executable(library-test "main.cc") +target_link_libraries(library-test fmt::fmt) +target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) +target_include_directories(library-test PUBLIC SYSTEM .) + +if (TARGET fmt::fmt-header-only) + add_executable(header-only-test "main.cc") + target_link_libraries(header-only-test fmt::fmt-header-only) + target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) + target_include_directories(header-only-test PUBLIC SYSTEM .) +endif () diff --git a/libs/fmt/test/add-subdirectory-test/main.cc b/libs/fmt/test/add-subdirectory-test/main.cc new file mode 100644 index 0000000000000000000000000000000000000000..f39f377cc7c6a173ae9b1e9aa29e99452f71b7b6 --- /dev/null +++ b/libs/fmt/test/add-subdirectory-test/main.cc @@ -0,0 +1,6 @@ +#include "fmt/format.h" + +int main(int argc, char** argv) { + for(int i = 0; i < argc; ++i) + fmt::print("{}: {}\n", i, argv[i]); +} diff --git a/libs/fmt/test/args-test.cc b/libs/fmt/test/args-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..a7421bc5299d092c2c49d5f4794aa4eb067dacd6 --- /dev/null +++ b/libs/fmt/test/args-test.cc @@ -0,0 +1,173 @@ +// Formatting library for C++ - dynamic argument store tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/args.h" + +#include "gtest/gtest.h" + +TEST(args_test, basic) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + store.push_back(42); + store.push_back("abc1"); + store.push_back(1.5f); + EXPECT_EQ("42 and abc1 and 1.5", fmt::vformat("{} and {} and {}", store)); +} + +TEST(args_test, strings_and_refs) { + // Unfortunately the tests are compiled with old ABI so strings use COW. + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + char str[] = "1234567890"; + store.push_back(str); + store.push_back(std::cref(str)); + store.push_back(fmt::string_view{str}); + str[0] = 'X'; + + auto result = fmt::vformat("{} and {} and {}", store); + EXPECT_EQ("1234567890 and X234567890 and X234567890", result); +} + +struct custom_type { + int i = 0; +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<custom_type> { + auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template <typename FormatContext> + auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) { + return format_to(ctx.out(), "cust={}", p.i); + } +}; +FMT_END_NAMESPACE + +TEST(args_test, custom_format) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + auto c = custom_type(); + store.push_back(c); + ++c.i; + store.push_back(c); + ++c.i; + store.push_back(std::cref(c)); + ++c.i; + auto result = fmt::vformat("{} and {} and {}", store); + EXPECT_EQ("cust=0 and cust=1 and cust=3", result); +} + +struct to_stringable { + friend fmt::string_view to_string_view(to_stringable) { return {}; } +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<to_stringable> { + auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { + return ctx.begin(); + } + + auto format(to_stringable, format_context& ctx) -> decltype(ctx.out()) { + return ctx.out(); + } +}; +FMT_END_NAMESPACE + +TEST(args_test, to_string_and_formatter) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + auto s = to_stringable(); + store.push_back(s); + store.push_back(std::cref(s)); + fmt::vformat("", store); +} + +TEST(args_test, named_int) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + store.push_back(fmt::arg("a1", 42)); + EXPECT_EQ("42", fmt::vformat("{a1}", store)); +} + +TEST(args_test, named_strings) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + char str[] = "1234567890"; + store.push_back(fmt::arg("a1", str)); + store.push_back(fmt::arg("a2", std::cref(str))); + str[0] = 'X'; + EXPECT_EQ("1234567890 and X234567890", fmt::vformat("{a1} and {a2}", store)); +} + +TEST(args_test, named_arg_by_ref) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + char band[] = "Rolling Stones"; + store.push_back(fmt::arg("band", std::cref(band))); + band[9] = 'c'; // Changing band affects the output. + EXPECT_EQ(fmt::vformat("{band}", store), "Rolling Scones"); +} + +TEST(args_test, named_custom_format) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + auto c = custom_type(); + store.push_back(fmt::arg("c1", c)); + ++c.i; + store.push_back(fmt::arg("c2", c)); + ++c.i; + store.push_back(fmt::arg("c_ref", std::cref(c))); + ++c.i; + auto result = fmt::vformat("{c1} and {c2} and {c_ref}", store); + EXPECT_EQ("cust=0 and cust=1 and cust=3", result); +} + +TEST(args_test, clear) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + store.push_back(42); + + auto result = fmt::vformat("{}", store); + EXPECT_EQ("42", result); + + store.push_back(43); + result = fmt::vformat("{} and {}", store); + EXPECT_EQ("42 and 43", result); + + store.clear(); + store.push_back(44); + result = fmt::vformat("{}", store); + EXPECT_EQ("44", result); +} + +TEST(args_test, reserve) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + store.reserve(2, 1); + store.push_back(1.5f); + store.push_back(fmt::arg("a1", 42)); + auto result = fmt::vformat("{a1} and {}", store); + EXPECT_EQ("42 and 1.5", result); +} + +struct copy_throwable { + copy_throwable() {} + copy_throwable(const copy_throwable&) { throw "deal with it"; } +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<copy_throwable> { + auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { + return ctx.begin(); + } + auto format(copy_throwable, format_context& ctx) -> decltype(ctx.out()) { + return ctx.out(); + } +}; +FMT_END_NAMESPACE + +TEST(args_test, throw_on_copy) { + auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); + store.push_back(std::string("foo")); + try { + store.push_back(copy_throwable()); + } catch (...) { + } + EXPECT_EQ(fmt::vformat("{}", store), "foo"); +} diff --git a/libs/fmt/test/assert-test.cc b/libs/fmt/test/assert-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..c74e617e68f35024919149826a011916a827cd46 --- /dev/null +++ b/libs/fmt/test/assert-test.cc @@ -0,0 +1,31 @@ +// Formatting library for C++ - FMT_ASSERT test +// +// It is a separate test to minimize the number of EXPECT_DEBUG_DEATH checks +// which are slow on some platforms. In other tests FMT_ASSERT is made to throw +// an exception which is much faster and easier to check. +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/core.h" +#include "gtest/gtest.h" + +TEST(assert_test, fail) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!"); +#else + fmt::print("warning: death tests are not supported\n"); +#endif +} + +TEST(assert_test, dangling_else) { + bool test_condition = false; + bool executed_else = false; + if (test_condition) + FMT_ASSERT(true, ""); + else + executed_else = true; + EXPECT_TRUE(executed_else); +} diff --git a/libs/fmt/test/chrono-test.cc b/libs/fmt/test/chrono-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..d03e92d3f58f0808b0097a58d85b87805c6cd8df --- /dev/null +++ b/libs/fmt/test/chrono-test.cc @@ -0,0 +1,369 @@ +// Formatting library for C++ - time formatting tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/chrono.h" + +#include "gtest-extra.h" // EXPECT_THROW_MSG + +auto make_tm() -> std::tm { + auto time = std::tm(); + time.tm_mday = 1; + return time; +} + +auto make_hour(int h) -> std::tm { + auto time = make_tm(); + time.tm_hour = h; + return time; +} + +auto make_minute(int m) -> std::tm { + auto time = make_tm(); + time.tm_min = m; + return time; +} + +auto make_second(int s) -> std::tm { + auto time = make_tm(); + time.tm_sec = s; + return time; +} + +TEST(chrono_test, format_tm) { + auto tm = std::tm(); + tm.tm_year = 116; + tm.tm_mon = 3; + tm.tm_mday = 25; + tm.tm_hour = 11; + tm.tm_min = 22; + tm.tm_sec = 33; + EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), + "The date is 2016-04-25 11:22:33."); + EXPECT_EQ(fmt::format(L"The date is {:%Y-%m-%d %H:%M:%S}.", tm), + L"The date is 2016-04-25 11:22:33."); +} + +TEST(chrono_test, grow_buffer) { + auto s = std::string("{:"); + for (int i = 0; i < 30; ++i) s += "%c"; + s += "}\n"; + auto t = std::time(nullptr); + fmt::format(s, *std::localtime(&t)); +} + +TEST(chrono_test, format_to_empty_container) { + auto time = std::tm(); + time.tm_sec = 42; + auto s = std::string(); + fmt::format_to(std::back_inserter(s), "{:%S}", time); + EXPECT_EQ(s, "42"); +} + +TEST(chrono_test, empty_result) { EXPECT_EQ(fmt::format("{}", std::tm()), ""); } + +auto equal(const std::tm& lhs, const std::tm& rhs) -> bool { + return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min && + lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday && + lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year && + lhs.tm_wday == rhs.tm_wday && lhs.tm_yday == rhs.tm_yday && + lhs.tm_isdst == rhs.tm_isdst; +} + +TEST(chrono_test, localtime) { + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + EXPECT_TRUE(equal(tm, fmt::localtime(t))); +} + +TEST(chrono_test, gmtime) { + auto t = std::time(nullptr); + auto tm = *std::gmtime(&t); + EXPECT_TRUE(equal(tm, fmt::gmtime(t))); +} + +template <typename TimePoint> auto strftime(TimePoint tp) -> std::string { + auto t = std::chrono::system_clock::to_time_t(tp); + auto tm = *std::localtime(&t); + char output[256] = {}; + std::strftime(output, sizeof(output), "%Y-%m-%d %H:%M:%S", &tm); + return output; +} + +TEST(chrono_test, time_point) { + auto t1 = std::chrono::system_clock::now(); + EXPECT_EQ(strftime(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); + using time_point = + std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>; + auto t2 = time_point(std::chrono::seconds(42)); + EXPECT_EQ(strftime(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); +} + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + +TEST(chrono_test, format_default) { + EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42))); + EXPECT_EQ("42as", + fmt::format("{}", std::chrono::duration<int, std::atto>(42))); + EXPECT_EQ("42fs", + fmt::format("{}", std::chrono::duration<int, std::femto>(42))); + EXPECT_EQ("42ps", + fmt::format("{}", std::chrono::duration<int, std::pico>(42))); + EXPECT_EQ("42ns", fmt::format("{}", std::chrono::nanoseconds(42))); + EXPECT_EQ("42µs", fmt::format("{}", std::chrono::microseconds(42))); + EXPECT_EQ("42ms", fmt::format("{}", std::chrono::milliseconds(42))); + EXPECT_EQ("42cs", + fmt::format("{}", std::chrono::duration<int, std::centi>(42))); + EXPECT_EQ("42ds", + fmt::format("{}", std::chrono::duration<int, std::deci>(42))); + EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42))); + EXPECT_EQ("42das", + fmt::format("{}", std::chrono::duration<int, std::deca>(42))); + EXPECT_EQ("42hs", + fmt::format("{}", std::chrono::duration<int, std::hecto>(42))); + EXPECT_EQ("42ks", + fmt::format("{}", std::chrono::duration<int, std::kilo>(42))); + EXPECT_EQ("42Ms", + fmt::format("{}", std::chrono::duration<int, std::mega>(42))); + EXPECT_EQ("42Gs", + fmt::format("{}", std::chrono::duration<int, std::giga>(42))); + EXPECT_EQ("42Ts", + fmt::format("{}", std::chrono::duration<int, std::tera>(42))); + EXPECT_EQ("42Ps", + fmt::format("{}", std::chrono::duration<int, std::peta>(42))); + EXPECT_EQ("42Es", + fmt::format("{}", std::chrono::duration<int, std::exa>(42))); + EXPECT_EQ("42m", fmt::format("{}", std::chrono::minutes(42))); + EXPECT_EQ("42h", fmt::format("{}", std::chrono::hours(42))); + EXPECT_EQ( + "42[15]s", + fmt::format("{}", std::chrono::duration<int, std::ratio<15, 1>>(42))); + EXPECT_EQ( + "42[15/4]s", + fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42))); +} + +TEST(chrono_test, format_wide) { + EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42))); +} + +TEST(chrono_test, align) { + auto s = std::chrono::seconds(42); + EXPECT_EQ("42s ", fmt::format("{:5}", s)); + EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5)); + EXPECT_EQ(" 42s", fmt::format("{:>5}", s)); + EXPECT_EQ("**42s**", fmt::format("{:*^7}", s)); + EXPECT_EQ("03:25:45 ", + fmt::format("{:12%H:%M:%S}", std::chrono::seconds(12345))); + EXPECT_EQ(" 03:25:45", + fmt::format("{:>12%H:%M:%S}", std::chrono::seconds(12345))); + EXPECT_EQ("~~03:25:45~~", + fmt::format("{:~^12%H:%M:%S}", std::chrono::seconds(12345))); + EXPECT_EQ("03:25:45 ", + fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12)); +} + +TEST(chrono_test, format_specs) { + EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0))); + EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0))); + EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0))); + EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(0))); + EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(60))); + EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42))); + EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234))); + EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(0))); + EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(60))); + EXPECT_EQ("42", fmt::format("{:%M}", std::chrono::minutes(42))); + EXPECT_EQ("01", fmt::format("{:%M}", std::chrono::seconds(61))); + EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(0))); + EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(24))); + EXPECT_EQ("14", fmt::format("{:%H}", std::chrono::hours(14))); + EXPECT_EQ("01", fmt::format("{:%H}", std::chrono::minutes(61))); + EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(0))); + EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(12))); + EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(24))); + EXPECT_EQ("04", fmt::format("{:%I}", std::chrono::hours(4))); + EXPECT_EQ("02", fmt::format("{:%I}", std::chrono::hours(14))); + EXPECT_EQ("03:25:45", + fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345))); + EXPECT_EQ("03:25", fmt::format("{:%R}", std::chrono::seconds(12345))); + EXPECT_EQ("03:25:45", fmt::format("{:%T}", std::chrono::seconds(12345))); + EXPECT_EQ("12345", fmt::format("{:%Q}", std::chrono::seconds(12345))); + EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345))); +} + +TEST(chrono_test, invalid_specs) { + auto sec = std::chrono::seconds(0); + EXPECT_THROW_MSG(fmt::format(+"{:%a}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%A}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%c}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%x}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%Ex}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%X}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%EX}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%D}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%F}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%Ec}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%w}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%u}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%b}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%B}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%z}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%Z}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format(+"{:%Eq}", sec), fmt::format_error, + "invalid format"); + EXPECT_THROW_MSG(fmt::format(+"{:%Oq}", sec), fmt::format_error, + "invalid format"); +} + +auto format_tm(const std::tm& time, fmt::string_view spec, + const std::locale& loc) -> std::string { + auto& facet = std::use_facet<std::time_put<char>>(loc); + std::ostringstream os; + os.imbue(loc); + facet.put(os, os, ' ', &time, spec.begin(), spec.end()); + return os.str(); +} + +# define EXPECT_TIME(spec, time, duration) \ + { \ + auto jp_loc = std::locale("ja_JP.utf8"); \ + EXPECT_EQ(format_tm(time, spec, jp_loc), \ + fmt::format(loc, "{:" spec "}", duration)); \ + } + +TEST(chrono_test, locale) { + auto loc_name = "ja_JP.utf8"; + bool has_locale = false; + auto loc = std::locale(); + try { + loc = std::locale(loc_name); + has_locale = true; + } catch (const std::runtime_error&) { + } + if (!has_locale) { + fmt::print("{} locale is missing.\n", loc_name); + return; + } + EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14)); + EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14)); + EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42)); + EXPECT_TIME("%OS", make_second(42), std::chrono::seconds(42)); + auto time = make_tm(); + time.tm_hour = 3; + time.tm_min = 25; + time.tm_sec = 45; + auto sec = std::chrono::seconds(12345); + EXPECT_TIME("%r", time, sec); + EXPECT_TIME("%p", time, sec); +} + +using dms = std::chrono::duration<double, std::milli>; + +TEST(chrono_test, format_default_fp) { + typedef std::chrono::duration<float> fs; + EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234))); + typedef std::chrono::duration<float, std::milli> fms; + EXPECT_EQ("1.234ms", fmt::format("{}", fms(1.234))); + typedef std::chrono::duration<double> ds; + EXPECT_EQ("1.234s", fmt::format("{}", ds(1.234))); + EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234))); +} + +TEST(chrono_test, format_precision) { + EXPECT_THROW_MSG(fmt::format(+"{:.2}", std::chrono::seconds(42)), + fmt::format_error, + "precision not allowed for this argument type"); + EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); + EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2)); +} + +TEST(chrono_test, format_full_specs) { + EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234))); + EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2)); + EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1)); + EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8)); + EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3)); + EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234))); +} + +TEST(chrono_test, format_simple_q) { + typedef std::chrono::duration<float> fs; + EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234))); + typedef std::chrono::duration<float, std::milli> fms; + EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", fms(1.234))); + typedef std::chrono::duration<double> ds; + EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", ds(1.234))); + EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234))); +} + +TEST(chrono_test, format_precision_q) { + EXPECT_THROW_MSG(fmt::format(+"{:.2%Q %q}", std::chrono::seconds(42)), + fmt::format_error, + "precision not allowed for this argument type"); + EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234))); + EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2)); +} + +TEST(chrono_test, format_full_specs_q) { + EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234))); + EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2)); + EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1)); + EXPECT_EQ(" 1.23 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9)); + EXPECT_EQ("=1.234 ms=", fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3)); + EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234))); +} + +TEST(chrono_test, invalid_width_id) { + EXPECT_THROW(fmt::format(+"{:{o}", std::chrono::seconds(0)), + fmt::format_error); +} + +TEST(chrono_test, invalid_colons) { + EXPECT_THROW(fmt::format(+"{0}=:{0::", std::chrono::seconds(0)), + fmt::format_error); +} + +TEST(chrono_test, negative_durations) { + EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345))); + EXPECT_EQ("-03:25:45", + fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345))); + EXPECT_EQ("-00:01", + fmt::format("{:%M:%S}", std::chrono::duration<double>(-1))); + EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(-12345))); + EXPECT_EQ("-00.127", + fmt::format("{:%S}", + std::chrono::duration<signed char, std::milli>{-127})); + auto min = std::numeric_limits<int>::min(); + EXPECT_EQ(fmt::format("{}", min), + fmt::format("{:%Q}", std::chrono::duration<int>(min))); +} + +TEST(chrono_test, special_durations) { + EXPECT_EQ( + "40.", + fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3)); + auto nan = std::numeric_limits<double>::quiet_NaN(); + EXPECT_EQ( + "nan nan nan nan nan:nan nan", + fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan))); + fmt::format("{:%S}", + std::chrono::duration<float, std::atto>(1.79400457e+31f)); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::exa>(1)), + "1Es"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::atto>(1)), + "1as"); + EXPECT_EQ(fmt::format("{:%R}", std::chrono::duration<char, std::mega>{2}), + "03:33"); + EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}), + "03:33:20"); +} + +TEST(chrono_test, unsigned_duration) { + EXPECT_EQ("42s", fmt::format("{}", std::chrono::duration<unsigned>(42))); +} + +#endif // FMT_STATIC_THOUSANDS_SEPARATOR diff --git a/libs/fmt/test/color-test.cc b/libs/fmt/test/color-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..9a38765afa781fa38519c8f5d01e0fd1cb86be07 --- /dev/null +++ b/libs/fmt/test/color-test.cc @@ -0,0 +1,62 @@ +// Formatting library for C++ - color tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/color.h" + +#include <iterator> // std::back_inserter + +#include "gtest-extra.h" // EXPECT_WRITE + +TEST(color_test, format) { + EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), + "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); + EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"), + L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m"); + EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"), + "\x1b[38;2;000;000;255mblue\x1b[0m"); + EXPECT_EQ( + fmt::format(fg(fmt::color::blue) | bg(fmt::color::red), "two color"), + "\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold"), "\x1b[1mbold\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::emphasis::italic, "italic"), + "\x1b[3mitalic\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::emphasis::underline, "underline"), + "\x1b[4munderline\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::emphasis::strikethrough, "strikethrough"), + "\x1b[9mstrikethrough\x1b[0m"); + EXPECT_EQ( + fmt::format(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"), + "\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"), + "\x1b[1mbold error\x1b[0m"); + EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue log"), + "\x1b[38;2;000;000;255mblue log\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::text_style(), "hi"), "hi"); + EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "tred"), + "\x1b[31mtred\x1b[0m"); + EXPECT_EQ(fmt::format(bg(fmt::terminal_color::cyan), "tcyan"), + "\x1b[46mtcyan\x1b[0m"); + EXPECT_EQ(fmt::format(fg(fmt::terminal_color::bright_green), "tbgreen"), + "\x1b[92mtbgreen\x1b[0m"); + EXPECT_EQ(fmt::format(bg(fmt::terminal_color::bright_magenta), "tbmagenta"), + "\x1b[105mtbmagenta\x1b[0m"); + EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"), + "\x1b[31mfoo\x1b[0m"); +} + +TEST(color_test, format_to) { + auto out = std::string(); + fmt::format_to(std::back_inserter(out), fg(fmt::rgb(255, 20, 30)), + "rgb(255,20,30){}{}{}", 1, 2, 3); + EXPECT_EQ(fmt::to_string(out), + "\x1b[38;2;255;020;030mrgb(255,20,30)123\x1b[0m"); +} + +TEST(color_test, print) { + EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), + "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); +} diff --git a/libs/fmt/test/compile-error-test/CMakeLists.txt b/libs/fmt/test/compile-error-test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..8202f279434e8d48ad9c3d3e9a6d0c6bf7a665c0 --- /dev/null +++ b/libs/fmt/test/compile-error-test/CMakeLists.txt @@ -0,0 +1,79 @@ +# Test if compile errors are produced where necessary. + +cmake_minimum_required(VERSION 3.1...3.18) + +include(CheckCXXSourceCompiles) +include(CheckCXXCompilerFlag) + +set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/../../include) +set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG} ${PEDANTIC_COMPILE_FLAGS}) + +function (generate_source result fragment) + set(${result} " + #define FMT_HEADER_ONLY 1 + #include \"fmt/format.h\" + int main() { + ${fragment} + } + " PARENT_SCOPE) +endfunction () + +function (expect_compile code) + generate_source(source "${code}") + check_cxx_source_compiles("${source}" compiles) + if (NOT compiles) + set(error_msg "Compile error for: ${code}") + endif () + # Unset the CMake cache variable compiles. Otherwise the compile test will + # just use cached information next time it runs. + unset(compiles CACHE) + if (error_msg) + message(FATAL_ERROR ${error_msg}) + endif () +endfunction () + +function (expect_compile_error code) + generate_source(source "${code}") + check_cxx_source_compiles("${source}" compiles) + if (compiles) + set(error_msg "No compile error for: ${code}") + endif () + # Unset the CMake cache variable compiles. Otherwise the compile test will + # just use cached information next time it runs. + unset(compiles CACHE) + if (error_msg) + message(FATAL_ERROR ${error_msg}) + endif () +endfunction () + +# check if the source file skeleton compiles +expect_compile("") + +# Formatting a wide character with a narrow format string is forbidden. +expect_compile_error("fmt::format(\"{}\", L'a');") + +# Formatting a wide string with a narrow format string is forbidden. +expect_compile_error("fmt::format(\"{}\", L\"foo\");") + +# Formatting a narrow string with a wide format string is forbidden because +# mixing UTF-8 with UTF-16/32 can result in an invalid output. +expect_compile_error("fmt::format(L\"{}\", \"foo\");") + +# Formatting a wide string with a narrow format string is forbidden. +expect_compile_error(" + struct S { + operator std::string() const { return std::string(); } + }; + fmt::format(\"{}\", S()); +") + +# Make sure that compiler features detected in the header +# match the features detected in CMake. +if (SUPPORTS_USER_DEFINED_LITERALS) + set(supports_udl 1) +else () + set(supports_udl 0) +endif () +expect_compile("#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl} + # error + #endif") diff --git a/libs/fmt/test/compile-test.cc b/libs/fmt/test/compile-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..71eddc920e4f5a23591a8a16d448ba3e1847b1ff --- /dev/null +++ b/libs/fmt/test/compile-test.cc @@ -0,0 +1,334 @@ +// Formatting library for C++ - formatting library tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/compile.h" + +#include <type_traits> + +#include "fmt/chrono.h" +#include "gmock/gmock.h" + +TEST(iterator_test, counting_iterator) { + auto it = fmt::detail::counting_iterator(); + auto prev = it++; + EXPECT_EQ(prev.count(), 0); + EXPECT_EQ(it.count(), 1); + EXPECT_EQ((it + 41).count(), 42); +} + +TEST(iterator_test, truncating_iterator) { + char* p = nullptr; + auto it = fmt::detail::truncating_iterator<char*>(p, 3); + auto prev = it++; + EXPECT_EQ(prev.base(), p); + EXPECT_EQ(it.base(), p + 1); +} + +TEST(iterator_test, truncating_iterator_default_construct) { + auto it = fmt::detail::truncating_iterator<char*>(); + EXPECT_EQ(nullptr, it.base()); + EXPECT_EQ(std::size_t{0}, it.count()); +} + +#ifdef __cpp_lib_ranges +TEST(iterator_test, truncating_iterator_is_output_iterator) { + static_assert( + std::output_iterator<fmt::detail::truncating_iterator<char*>, char>); +} +#endif + +TEST(iterator_test, truncating_back_inserter) { + auto buffer = std::string(); + auto bi = std::back_inserter(buffer); + auto it = fmt::detail::truncating_iterator<decltype(bi)>(bi, 2); + *it++ = '4'; + *it++ = '2'; + *it++ = '1'; + EXPECT_EQ(buffer.size(), 2); + EXPECT_EQ(buffer, "42"); +} + +struct test_formattable {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<test_formattable> : formatter<const char*> { + template <typename FormatContext> + auto format(test_formattable, FormatContext& ctx) -> decltype(ctx.out()) { + return formatter<const char*>::format("foo", ctx); + } +}; +FMT_END_NAMESPACE + +TEST(compile_test, compile_fallback) { + // FMT_COMPILE should fallback on runtime formatting when `if constexpr` is + // not available. + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42)); +} + +#ifdef __cpp_if_constexpr +TEST(compile_test, format_default) { + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42)); + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u)); + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll)); + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ull)); + EXPECT_EQ("true", fmt::format(FMT_COMPILE("{}"), true)); + EXPECT_EQ("x", fmt::format(FMT_COMPILE("{}"), 'x')); + EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2)); + EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo")); + EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo"))); + EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), test_formattable())); +# ifdef __cpp_lib_byte + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), std::byte{42})); +# endif +} + +TEST(compile_test, format_wide_string) { + EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42)); +} + +TEST(compile_test, format_specs) { + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); + EXPECT_EQ("1.2 ms ", + fmt::format(FMT_COMPILE("{:7.1%Q %q}"), + std::chrono::duration<double, std::milli>(1.234))); +} + +TEST(compile_test, dynamic_format_specs) { + EXPECT_EQ("foo ", fmt::format(FMT_COMPILE("{:{}}"), "foo", 5)); + EXPECT_EQ(" 3.14", fmt::format(FMT_COMPILE("{:{}.{}f}"), 3.141592, 6, 2)); + EXPECT_EQ( + "=1.234ms=", + fmt::format(FMT_COMPILE("{:=^{}.{}}"), + std::chrono::duration<double, std::milli>(1.234), 9, 3)); +} + +TEST(compile_test, manual_ordering) { + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{0}"), 42)); + EXPECT_EQ(" -42", fmt::format(FMT_COMPILE("{0:4}"), -42)); + EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {1}"), 41, 43)); + EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{1} {0}"), 43, 41)); + EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {2}"), 41, 42, 43)); + EXPECT_EQ(" 41 43", fmt::format(FMT_COMPILE("{1:{2}} {0:4}"), 43, 41, 4)); + EXPECT_EQ("42 1.2 ms ", + fmt::format(FMT_COMPILE("{0} {1:7.1%Q %q}"), 42, + std::chrono::duration<double, std::milli>(1.234))); + EXPECT_EQ( + "true 42 42 foo 0x1234 foo", + fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f, + "foo", reinterpret_cast<void*>(0x1234), test_formattable())); + EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42)); +} + +TEST(compile_test, named) { + auto runtime_named_field_compiled = + fmt::detail::compile<decltype(fmt::arg("arg", 42))>(FMT_COMPILE("{arg}")); + static_assert(std::is_same_v<decltype(runtime_named_field_compiled), + fmt::detail::runtime_named_field<char>>); + + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42))); + EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41), + fmt::arg("arg", 43))); + + EXPECT_EQ("foobar", + fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"), + fmt::arg("a1", "bar"))); + EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{}{a1}"), fmt::arg("a0", "foo"), + fmt::arg("a1", "bar"))); + EXPECT_EQ("foofoo", fmt::format(FMT_COMPILE("{a0}{}"), fmt::arg("a0", "foo"), + fmt::arg("a1", "bar"))); + EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{0}{a1}"), fmt::arg("a0", "foo"), + fmt::arg("a1", "bar"))); + EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{a0}{1}"), fmt::arg("a0", "foo"), + fmt::arg("a1", "bar"))); + + EXPECT_EQ("foobar", + fmt::format(FMT_COMPILE("{}{a1}"), "foo", fmt::arg("a1", "bar"))); + EXPECT_EQ("foobar", + fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a1", "bar"), + fmt::arg("a2", "baz"), fmt::arg("a0", "foo"))); + EXPECT_EQ(" bar foo ", + fmt::format(FMT_COMPILE(" {foo} {bar} "), fmt::arg("foo", "bar"), + fmt::arg("bar", "foo"))); + + EXPECT_THROW(fmt::format(FMT_COMPILE("{invalid}"), fmt::arg("valid", 42)), + fmt::format_error); + +# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS + using namespace fmt::literals; + auto statically_named_field_compiled = + fmt::detail::compile<decltype("arg"_a = 42)>(FMT_COMPILE("{arg}")); + static_assert(std::is_same_v<decltype(statically_named_field_compiled), + fmt::detail::field<char, int, 0>>); + + EXPECT_EQ("41 43", + fmt::format(FMT_COMPILE("{a0} {a1}"), "a0"_a = 41, "a1"_a = 43)); + EXPECT_EQ("41 43", + fmt::format(FMT_COMPILE("{a1} {a0}"), "a0"_a = 43, "a1"_a = 41)); +# endif +} + +TEST(compile_test, format_to) { + char buf[8]; + auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42); + *end = '\0'; + EXPECT_STREQ("42", buf); + end = fmt::format_to(buf, FMT_COMPILE("{:x}"), 42); + *end = '\0'; + EXPECT_STREQ("2a", buf); +} + +TEST(compile_test, format_to_n) { + constexpr auto buffer_size = 8; + char buffer[buffer_size]; + auto res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{}"), 42); + *res.out = '\0'; + EXPECT_STREQ("42", buffer); + res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{:x}"), 42); + *res.out = '\0'; + EXPECT_STREQ("2a", buffer); +} + +TEST(compile_test, formatted_size) { + EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42)); + EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0)); +} + +TEST(compile_test, text_and_arg) { + EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42)); + EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42)); +} + +TEST(compile_test, unknown_format_fallback) { + EXPECT_EQ(" 42 ", + fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42))); + + std::vector<char> v; + fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"), + fmt::arg("name", 42)); + EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size())); + + char buffer[4]; + auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"), + fmt::arg("name", 42)); + EXPECT_EQ(5u, result.size); + EXPECT_EQ(buffer + 4, result.out); + EXPECT_EQ(" 42 ", fmt::string_view(buffer, 4)); +} + +TEST(compile_test, empty) { EXPECT_EQ("", fmt::format(FMT_COMPILE(""))); } + +struct to_stringable { + friend fmt::string_view to_string_view(to_stringable) { return {}; } +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<to_stringable> { + auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template <typename FormatContext> + auto format(const to_stringable&, FormatContext& ctx) -> decltype(ctx.out()) { + return ctx.out(); + } +}; +FMT_END_NAMESPACE + +TEST(compile_test, to_string_and_formatter) { + fmt::format(FMT_COMPILE("{}"), to_stringable()); +} +#endif + +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +TEST(compile_test, compile_format_string_literal) { + using namespace fmt::literals; + EXPECT_EQ("", fmt::format(""_cf)); + EXPECT_EQ("42", fmt::format("{}"_cf, 42)); + EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42)); +} +#endif + +#if __cplusplus >= 202002L || \ + (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002) +template <size_t max_string_length, typename Char = char> struct test_string { + template <typename T> constexpr bool operator==(const T& rhs) const noexcept { + return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0; + } + Char buffer[max_string_length]{}; +}; + +template <size_t max_string_length, typename Char = char, typename... Args> +consteval auto test_format(auto format, const Args&... args) { + test_string<max_string_length, Char> string{}; + fmt::format_to(string.buffer, format, args...); + return string; +} + +TEST(compile_time_formatting_test, bool) { + EXPECT_EQ("true", test_format<5>(FMT_COMPILE("{}"), true)); + EXPECT_EQ("false", test_format<6>(FMT_COMPILE("{}"), false)); + EXPECT_EQ("true ", test_format<6>(FMT_COMPILE("{:5}"), true)); + EXPECT_EQ("1", test_format<2>(FMT_COMPILE("{:d}"), true)); +} + +TEST(compile_time_formatting_test, integer) { + EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), 42)); + EXPECT_EQ("420", test_format<4>(FMT_COMPILE("{}"), 420)); + EXPECT_EQ("42 42", test_format<6>(FMT_COMPILE("{} {}"), 42, 42)); + EXPECT_EQ("42 42", + test_format<6>(FMT_COMPILE("{} {}"), uint32_t{42}, uint64_t{42})); + + EXPECT_EQ("+42", test_format<4>(FMT_COMPILE("{:+}"), 42)); + EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{:-}"), 42)); + EXPECT_EQ(" 42", test_format<4>(FMT_COMPILE("{: }"), 42)); + + EXPECT_EQ("-0042", test_format<6>(FMT_COMPILE("{:05}"), -42)); + + EXPECT_EQ("101010", test_format<7>(FMT_COMPILE("{:b}"), 42)); + EXPECT_EQ("0b101010", test_format<9>(FMT_COMPILE("{:#b}"), 42)); + EXPECT_EQ("0B101010", test_format<9>(FMT_COMPILE("{:#B}"), 42)); + EXPECT_EQ("042", test_format<4>(FMT_COMPILE("{:#o}"), 042)); + EXPECT_EQ("0x4a", test_format<5>(FMT_COMPILE("{:#x}"), 0x4a)); + EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a)); + + EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42)); + EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ll)); + EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull)); + + EXPECT_EQ("42 ", test_format<5>(FMT_COMPILE("{:<4}"), 42)); + EXPECT_EQ(" 42", test_format<5>(FMT_COMPILE("{:>4}"), 42)); + EXPECT_EQ(" 42 ", test_format<5>(FMT_COMPILE("{:^4}"), 42)); + EXPECT_EQ("**-42", test_format<6>(FMT_COMPILE("{:*>5}"), -42)); +} + +TEST(compile_time_formatting_test, char) { + EXPECT_EQ("c", test_format<2>(FMT_COMPILE("{}"), 'c')); + + EXPECT_EQ("c ", test_format<4>(FMT_COMPILE("{:3}"), 'c')); + EXPECT_EQ("99", test_format<3>(FMT_COMPILE("{:d}"), 'c')); +} + +TEST(compile_time_formatting_test, string) { + EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), "42")); + EXPECT_EQ("The answer is 42", + test_format<17>(FMT_COMPILE("{} is {}"), "The answer", "42")); + + EXPECT_EQ("abc**", test_format<6>(FMT_COMPILE("{:*<5}"), "abc")); + EXPECT_EQ("**🤡**", test_format<9>(FMT_COMPILE("{:*^6}"), "🤡")); +} + +TEST(compile_time_formatting_test, combination) { + EXPECT_EQ("420, true, answer", + test_format<18>(FMT_COMPILE("{}, {}, {}"), 420, true, "answer")); + + EXPECT_EQ(" -42", test_format<5>(FMT_COMPILE("{:{}}"), -42, 4)); +} + +TEST(compile_time_formatting_test, multibyte_fill) { + EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42)); +} +#endif diff --git a/libs/fmt/test/core-test.cc b/libs/fmt/test/core-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..76b20a3e242cf3f1e7efab9108fa10c27dff6b67 --- /dev/null +++ b/libs/fmt/test/core-test.cc @@ -0,0 +1,649 @@ +// Formatting library for C++ - core tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +// clang-format off +#include "test-assert.h" +// clang-format on + +#include "fmt/core.h" + +#include <algorithm> // std::copy_n +#include <climits> // INT_MAX +#include <cstring> // std::strlen +#include <functional> // std::equal_to +#include <iterator> // std::back_insert_iterator +#include <limits> // std::numeric_limits +#include <string> // std::string +#include <type_traits> // std::is_same + +#include "gmock/gmock.h" + +#if defined(FMT_COMPILE_TIME_CHECKS) && FMT_COMPILE_TIME_CHECKS +# include "fmt/format.h" +#endif + +using fmt::string_view; +using fmt::detail::buffer; + +using testing::_; +using testing::Invoke; +using testing::Return; + +TEST(string_view_test, value_type) { + static_assert(std::is_same<string_view::value_type, char>::value, ""); +} + +TEST(string_view_test, ctor) { + EXPECT_STREQ("abc", fmt::string_view("abc").data()); + EXPECT_EQ(3u, fmt::string_view("abc").size()); + + EXPECT_STREQ("defg", fmt::string_view(std::string("defg")).data()); + EXPECT_EQ(4u, fmt::string_view(std::string("defg")).size()); +} + +TEST(string_view_test, length) { + // Test that string_view::size() returns string length, not buffer size. + char str[100] = "some string"; + EXPECT_EQ(std::strlen(str), string_view(str).size()); + EXPECT_LT(std::strlen(str), sizeof(str)); +} + +// Check string_view's comparison operator. +template <template <typename> class Op> void check_op() { + const char* inputs[] = {"foo", "fop", "fo"}; + size_t num_inputs = sizeof(inputs) / sizeof(*inputs); + for (size_t i = 0; i < num_inputs; ++i) { + for (size_t j = 0; j < num_inputs; ++j) { + string_view lhs(inputs[i]), rhs(inputs[j]); + EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0), Op<string_view>()(lhs, rhs)); + } + } +} + +TEST(string_view_test, compare) { + EXPECT_EQ(string_view("foo").compare(string_view("foo")), 0); + EXPECT_GT(string_view("fop").compare(string_view("foo")), 0); + EXPECT_LT(string_view("foo").compare(string_view("fop")), 0); + EXPECT_GT(string_view("foo").compare(string_view("fo")), 0); + EXPECT_LT(string_view("fo").compare(string_view("foo")), 0); + check_op<std::equal_to>(); + check_op<std::not_equal_to>(); + check_op<std::less>(); + check_op<std::less_equal>(); + check_op<std::greater>(); + check_op<std::greater_equal>(); +} + +namespace test_ns { +template <typename Char> class test_string { + private: + std::basic_string<Char> s_; + + public: + test_string(const Char* s) : s_(s) {} + const Char* data() const { return s_.data(); } + size_t length() const { return s_.size(); } + operator const Char*() const { return s_.c_str(); } +}; + +template <typename Char> +fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) { + return {s.data(), s.length()}; +} + +struct non_string {}; +} // namespace test_ns + +template <typename T> class is_string_test : public testing::Test {}; + +using string_char_types = testing::Types<char, wchar_t, char16_t, char32_t>; +TYPED_TEST_SUITE(is_string_test, string_char_types); + +template <typename Char> +struct derived_from_string_view : fmt::basic_string_view<Char> {}; + +TYPED_TEST(is_string_test, is_string) { + EXPECT_TRUE(fmt::detail::is_string<TypeParam*>::value); + EXPECT_TRUE(fmt::detail::is_string<const TypeParam*>::value); + EXPECT_TRUE(fmt::detail::is_string<TypeParam[2]>::value); + EXPECT_TRUE(fmt::detail::is_string<const TypeParam[2]>::value); + EXPECT_TRUE(fmt::detail::is_string<std::basic_string<TypeParam>>::value); + EXPECT_TRUE(fmt::detail::is_string<fmt::basic_string_view<TypeParam>>::value); + EXPECT_TRUE( + fmt::detail::is_string<derived_from_string_view<TypeParam>>::value); + using fmt_string_view = fmt::detail::std_string_view<TypeParam>; + EXPECT_TRUE(std::is_empty<fmt_string_view>::value != + fmt::detail::is_string<fmt_string_view>::value); + EXPECT_TRUE(fmt::detail::is_string<test_ns::test_string<TypeParam>>::value); + EXPECT_FALSE(fmt::detail::is_string<test_ns::non_string>::value); +} + +TEST(core_test, is_output_iterator) { + EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value)); + EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value)); + EXPECT_FALSE((fmt::detail::is_output_iterator<std::string, char>::value)); + EXPECT_TRUE( + (fmt::detail::is_output_iterator<std::back_insert_iterator<std::string>, + char>::value)); + EXPECT_TRUE( + (fmt::detail::is_output_iterator<std::string::iterator, char>::value)); + EXPECT_FALSE((fmt::detail::is_output_iterator<std::string::const_iterator, + char>::value)); +} + +TEST(core_test, buffer_appender) { + // back_insert_iterator is not default-constructible before C++20, so + // buffer_appender can only be default-constructible when back_insert_iterator + // is. + static_assert( + std::is_default_constructible< + std::back_insert_iterator<fmt::detail::buffer<char>>>::value == + std::is_default_constructible< + fmt::detail::buffer_appender<char>>::value, + ""); + +#ifdef __cpp_lib_ranges + static_assert(std::output_iterator<fmt::detail::buffer_appender<char>, char>); +#endif +} + +#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 470 +TEST(buffer_test, noncopyable) { + EXPECT_FALSE(std::is_copy_constructible<buffer<char>>::value); +# if !FMT_MSC_VER + // std::is_copy_assignable is broken in MSVC2013. + EXPECT_FALSE(std::is_copy_assignable<buffer<char>>::value); +# endif +} + +TEST(buffer_test, nonmoveable) { + EXPECT_FALSE(std::is_move_constructible<buffer<char>>::value); +# if !FMT_MSC_VER + // std::is_move_assignable is broken in MSVC2013. + EXPECT_FALSE(std::is_move_assignable<buffer<char>>::value); +# endif +} +#endif + +TEST(buffer_test, indestructible) { + static_assert(!std::is_destructible<fmt::detail::buffer<int>>(), + "buffer's destructor is protected"); +} + +template <typename T> struct mock_buffer final : buffer<T> { + MOCK_METHOD1(do_grow, size_t(size_t capacity)); + + void grow(size_t capacity) { this->set(this->data(), do_grow(capacity)); } + + mock_buffer(T* data = nullptr, size_t buf_capacity = 0) { + this->set(data, buf_capacity); + ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) { + return capacity; + })); + } +}; + +TEST(buffer_test, ctor) { + { + mock_buffer<int> buffer; + EXPECT_EQ(nullptr, buffer.data()); + EXPECT_EQ(static_cast<size_t>(0), buffer.size()); + EXPECT_EQ(static_cast<size_t>(0), buffer.capacity()); + } + { + int dummy; + mock_buffer<int> buffer(&dummy); + EXPECT_EQ(&dummy, &buffer[0]); + EXPECT_EQ(static_cast<size_t>(0), buffer.size()); + EXPECT_EQ(static_cast<size_t>(0), buffer.capacity()); + } + { + int dummy; + size_t capacity = std::numeric_limits<size_t>::max(); + mock_buffer<int> buffer(&dummy, capacity); + EXPECT_EQ(&dummy, &buffer[0]); + EXPECT_EQ(static_cast<size_t>(0), buffer.size()); + EXPECT_EQ(capacity, buffer.capacity()); + } +} + +TEST(buffer_test, access) { + char data[10]; + mock_buffer<char> buffer(data, sizeof(data)); + buffer[0] = 11; + EXPECT_EQ(11, buffer[0]); + buffer[3] = 42; + EXPECT_EQ(42, *(&buffer[0] + 3)); + const fmt::detail::buffer<char>& const_buffer = buffer; + EXPECT_EQ(42, const_buffer[3]); +} + +TEST(buffer_test, try_resize) { + char data[123]; + mock_buffer<char> buffer(data, sizeof(data)); + buffer[10] = 42; + EXPECT_EQ(42, buffer[10]); + buffer.try_resize(20); + EXPECT_EQ(20u, buffer.size()); + EXPECT_EQ(123u, buffer.capacity()); + EXPECT_EQ(42, buffer[10]); + buffer.try_resize(5); + EXPECT_EQ(5u, buffer.size()); + EXPECT_EQ(123u, buffer.capacity()); + EXPECT_EQ(42, buffer[10]); + // Check if try_resize calls grow. + EXPECT_CALL(buffer, do_grow(124)); + buffer.try_resize(124); + EXPECT_CALL(buffer, do_grow(200)); + buffer.try_resize(200); +} + +TEST(buffer_test, try_resize_partial) { + char data[10]; + mock_buffer<char> buffer(data, sizeof(data)); + EXPECT_CALL(buffer, do_grow(20)).WillOnce(Return(15)); + buffer.try_resize(20); + EXPECT_EQ(buffer.capacity(), 15); + EXPECT_EQ(buffer.size(), 15); +} + +TEST(buffer_test, clear) { + mock_buffer<char> buffer; + EXPECT_CALL(buffer, do_grow(20)); + buffer.try_resize(20); + buffer.try_resize(0); + EXPECT_EQ(static_cast<size_t>(0), buffer.size()); + EXPECT_EQ(20u, buffer.capacity()); +} + +TEST(buffer_test, append) { + char data[15]; + mock_buffer<char> buffer(data, 10); + auto test = "test"; + buffer.append(test, test + 5); + EXPECT_STREQ(test, &buffer[0]); + EXPECT_EQ(5u, buffer.size()); + buffer.try_resize(10); + EXPECT_CALL(buffer, do_grow(12)); + buffer.append(test, test + 2); + EXPECT_EQ('t', buffer[10]); + EXPECT_EQ('e', buffer[11]); + EXPECT_EQ(12u, buffer.size()); +} + +TEST(buffer_test, append_partial) { + char data[10]; + mock_buffer<char> buffer(data, sizeof(data)); + testing::InSequence seq; + EXPECT_CALL(buffer, do_grow(15)).WillOnce(Return(10)); + EXPECT_CALL(buffer, do_grow(15)).WillOnce(Invoke([&buffer](size_t) { + EXPECT_EQ(fmt::string_view(buffer.data(), buffer.size()), "0123456789"); + buffer.clear(); + return 10; + })); + auto test = "0123456789abcde"; + buffer.append(test, test + 15); +} + +TEST(buffer_test, append_allocates_enough_storage) { + char data[19]; + mock_buffer<char> buffer(data, 10); + auto test = "abcdefgh"; + buffer.try_resize(10); + EXPECT_CALL(buffer, do_grow(19)); + buffer.append(test, test + 9); +} + +struct custom_context { + using char_type = char; + using parse_context_type = fmt::format_parse_context; + + bool called = false; + + template <typename T> struct formatter_type { + auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + const char* format(const T&, custom_context& ctx) { + ctx.called = true; + return nullptr; + } + }; + + void advance_to(const char*) {} +}; + +struct test_struct {}; + +FMT_BEGIN_NAMESPACE +template <typename Char> struct formatter<test_struct, Char> { + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + auto format(test_struct, format_context& ctx) -> decltype(ctx.out()) { + auto test = string_view("test"); + return std::copy_n(test.data(), test.size(), ctx.out()); + } +}; +FMT_END_NAMESPACE + +TEST(arg_test, format_args) { + auto args = fmt::format_args(); + EXPECT_FALSE(args.get(1)); +} + +TEST(arg_test, make_value_with_custom_context) { + auto t = test_struct(); + fmt::detail::value<custom_context> arg( + fmt::detail::arg_mapper<custom_context>().map(t)); + auto ctx = custom_context(); + auto parse_ctx = fmt::format_parse_context(""); + arg.custom.format(&t, parse_ctx, ctx); + EXPECT_TRUE(ctx.called); +} + +// Use a unique result type to make sure that there are no undesirable +// conversions. +struct test_result {}; + +template <typename T> struct mock_visitor { + template <typename U> struct result { using type = test_result; }; + + mock_visitor() { + ON_CALL(*this, visit(_)).WillByDefault(Return(test_result())); + } + + MOCK_METHOD1_T(visit, test_result(T value)); + MOCK_METHOD0_T(unexpected, void()); + + test_result operator()(T value) { return visit(value); } + + template <typename U> test_result operator()(U) { + unexpected(); + return test_result(); + } +}; + +template <typename T> struct visit_type { using type = T; }; + +#define VISIT_TYPE(type_, visit_type_) \ + template <> struct visit_type<type_> { using type = visit_type_; } + +VISIT_TYPE(signed char, int); +VISIT_TYPE(unsigned char, unsigned); +VISIT_TYPE(short, int); +VISIT_TYPE(unsigned short, unsigned); + +#if LONG_MAX == INT_MAX +VISIT_TYPE(long, int); +VISIT_TYPE(unsigned long, unsigned); +#else +VISIT_TYPE(long, long long); +VISIT_TYPE(unsigned long, unsigned long long); +#endif + +#define CHECK_ARG(Char, expected, value) \ + { \ + testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \ + EXPECT_CALL(visitor, visit(expected)); \ + using iterator = std::back_insert_iterator<buffer<Char>>; \ + fmt::visit_format_arg( \ + visitor, \ + fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \ + value)); \ + } + +#define CHECK_ARG_SIMPLE(value) \ + { \ + using value_type = decltype(value); \ + typename visit_type<value_type>::type expected = value; \ + CHECK_ARG(char, expected, value) \ + CHECK_ARG(wchar_t, expected, value) \ + } + +template <typename T> class numeric_arg_test : public testing::Test {}; + +using types = + testing::Types<bool, signed char, unsigned char, short, unsigned short, int, + unsigned, long, unsigned long, long long, unsigned long long, + float, double, long double>; +TYPED_TEST_SUITE(numeric_arg_test, types); + +template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0> +T test_value() { + return static_cast<T>(42); +} + +template <typename T, + fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0> +T test_value() { + return static_cast<T>(4.2); +} + +TYPED_TEST(numeric_arg_test, make_and_visit) { + CHECK_ARG_SIMPLE(test_value<TypeParam>()); + CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::min()); + CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max()); +} + +TEST(arg_test, char_arg) { + CHECK_ARG(char, 'a', 'a'); + CHECK_ARG(wchar_t, L'a', 'a'); + CHECK_ARG(wchar_t, L'a', L'a'); +} + +TEST(arg_test, string_arg) { + char str_data[] = "test"; + char* str = str_data; + const char* cstr = str; + CHECK_ARG(char, cstr, str); + + auto sv = fmt::string_view(str); + CHECK_ARG(char, sv, std::string(str)); +} + +TEST(arg_test, wstring_arg) { + wchar_t str_data[] = L"test"; + wchar_t* str = str_data; + const wchar_t* cstr = str; + + auto sv = fmt::wstring_view(str); + CHECK_ARG(wchar_t, cstr, str); + CHECK_ARG(wchar_t, cstr, cstr); + CHECK_ARG(wchar_t, sv, std::wstring(str)); + CHECK_ARG(wchar_t, sv, fmt::wstring_view(str)); +} + +TEST(arg_test, pointer_arg) { + void* p = nullptr; + const void* cp = nullptr; + CHECK_ARG(char, cp, p); + CHECK_ARG(wchar_t, cp, p); + CHECK_ARG_SIMPLE(cp); +} + +struct check_custom { + test_result operator()( + fmt::basic_format_arg<fmt::format_context>::handle h) const { + struct test_buffer final : fmt::detail::buffer<char> { + char data[10]; + test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {} + void grow(size_t) {} + } buffer; + auto parse_ctx = fmt::format_parse_context(""); + auto ctx = fmt::format_context(fmt::detail::buffer_appender<char>(buffer), + fmt::format_args()); + h.format(parse_ctx, ctx); + EXPECT_EQ("test", std::string(buffer.data, buffer.size())); + return test_result(); + } +}; + +TEST(arg_test, custom_arg) { + auto test = test_struct(); + using visitor = + mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>; + testing::StrictMock<visitor> v; + EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom())); + fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test)); +} + +TEST(arg_test, visit_invalid_arg) { + testing::StrictMock<mock_visitor<fmt::monostate>> visitor; + EXPECT_CALL(visitor, visit(_)); + auto arg = fmt::basic_format_arg<fmt::format_context>(); + fmt::visit_format_arg(visitor, arg); +} + +struct enabled_formatter {}; +struct disabled_formatter {}; +struct disabled_formatter_convertible { + operator int() const { return 42; } +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<enabled_formatter> { + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + auto format(enabled_formatter, format_context& ctx) -> decltype(ctx.out()) { + return ctx.out(); + } +}; +FMT_END_NAMESPACE + +TEST(core_test, has_formatter) { + using fmt::has_formatter; + using context = fmt::format_context; + static_assert(has_formatter<enabled_formatter, context>::value, ""); + static_assert(!has_formatter<disabled_formatter, context>::value, ""); + static_assert(!has_formatter<disabled_formatter_convertible, context>::value, + ""); +} + +TEST(core_test, is_formattable) { + static_assert(fmt::is_formattable<enabled_formatter>::value, ""); + static_assert(!fmt::is_formattable<disabled_formatter>::value, ""); + static_assert(fmt::is_formattable<disabled_formatter_convertible>::value, ""); +} + +TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); } + +TEST(core_test, format_to) { + std::string s; + fmt::format_to(std::back_inserter(s), "{}", 42); + EXPECT_EQ(s, "42"); +} + +struct convertible_to_int { + operator int() const { return 42; } +}; + +struct convertible_to_c_string { + operator const char*() const { return "foo"; } +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<convertible_to_int> { + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + auto format(convertible_to_int, format_context& ctx) -> decltype(ctx.out()) { + return std::copy_n("foo", 3, ctx.out()); + } +}; + +template <> struct formatter<convertible_to_c_string> { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + auto format(convertible_to_c_string, format_context& ctx) + -> decltype(ctx.out()) { + return std::copy_n("bar", 3, ctx.out()); + } +}; +FMT_END_NAMESPACE + +TEST(core_test, formatter_overrides_implicit_conversion) { + EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo"); + EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar"); +} + +// Test that check is not found by ADL. +template <typename T> void check(T); +TEST(core_test, adl_check) { + EXPECT_EQ(fmt::format("{}", test_struct()), "test"); +} + +TEST(core_test, to_string_view_foreign_strings) { + using namespace test_ns; + EXPECT_EQ(to_string_view(test_string<char>("42")), "42"); + fmt::detail::type type = + fmt::detail::mapped_type_constant<test_string<char>, + fmt::format_context>::value; + EXPECT_EQ(type, fmt::detail::type::string_type); +} + +TEST(core_test, format_foreign_strings) { + using namespace test_ns; + EXPECT_EQ(fmt::format(test_string<char>("{}"), 42), "42"); +} + +struct implicitly_convertible_to_string { + operator std::string() const { return "foo"; } +}; + +struct implicitly_convertible_to_string_view { + operator fmt::string_view() const { return "foo"; } +}; + +TEST(core_test, format_implicitly_convertible_to_string_view) { + EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view())); +} + +// std::is_constructible is broken in MSVC until version 2015. +#if !FMT_MSC_VER || FMT_MSC_VER >= 1900 +struct explicitly_convertible_to_string_view { + explicit operator fmt::string_view() const { return "foo"; } +}; + +TEST(core_test, format_explicitly_convertible_to_string_view) { + EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_view())); +} + +# ifdef FMT_USE_STRING_VIEW +struct explicitly_convertible_to_std_string_view { + explicit operator std::string_view() const { return "foo"; } +}; + +TEST(core_test, format_explicitly_convertible_to_std_string_view) { + EXPECT_EQ("foo", + fmt::format("{}", explicitly_convertible_to_std_string_view())); +} +# endif +#endif + +struct convertible_to_long_long { + operator long long() const { return 1LL << 32; } +}; + +TEST(format_test, format_convertible_to_long_long) { + EXPECT_EQ("100000000", fmt::format("{:x}", convertible_to_long_long())); +} + +struct disabled_rvalue_conversion { + operator const char*() const& { return "foo"; } + operator const char*() & { return "foo"; } + operator const char*() const&& = delete; + operator const char*() && = delete; +}; + +TEST(core_test, disabled_rvalue_conversion) { + EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion())); +} diff --git a/libs/fmt/test/cuda-test/CMakeLists.txt b/libs/fmt/test/cuda-test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..be8685b897ec3d3618878b4f28ab10a53831e283 --- /dev/null +++ b/libs/fmt/test/cuda-test/CMakeLists.txt @@ -0,0 +1,73 @@ +# We can find some usecases which follow the guide of CMake which uses +# `enable_language(CUDA)` instead of `find_package(CUDA)` and let the CMake +# built-in functions use NVCC. + +# See: https://cmake.org/cmake/help/latest/module/FindCUDA.html#replacement +# +# However, this requires CMake version 3.10 or higher and we can't be sure most +# of the CUDA projects are using those. +# +# This test relies on `find_package(CUDA)` in the parent CMake config. + +# These can be updated when NVCC becomes ready for C++ 17 features +# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features +set(CMAKE_CUDA_STANDARD 14) +set(CMAKE_CUDA_STANDARD_REQUIRED 14) + +# In this test, we assume that the user is going to compile CUDA source code +# with some libraries (fmt in this case). +# +# In addition to that, this test invokes both the C++ host compiler and NVCC +# by providing another (non-CUDA) C++ source code. +if (${CMAKE_VERSION} VERSION_LESS 3.15) + # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html + list(APPEND CUDA_NVCC_FLAGS "-std=c++14") + if (MSVC) + # This is the solution of pytorch: + # https://github.com/pytorch/pytorch/pull/7118 + list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/std:c++14") + list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/Zc:__cplusplus") + # for the reason of this -Xcompiler options, see below. + endif () + cuda_add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc) + target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14) + if (MSVC) + # This part is for (non-CUDA) C++ code. MSVC can define incorrect + # `__cplusplus` macro. Fix for the issue is to use additional compiler flag. + # + # See Also: + # https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ + # https://github.com/Microsoft/vscode-cpptools/issues/2595 + target_compile_options(fmt-in-cuda-test PRIVATE /Zc:__cplusplus /permissive-) + endif () +else() + # now using a "new" way of handling CUDA + add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc) + set_target_properties(fmt-in-cuda-test PROPERTIES CUDA_SEPARABLE_COMPILATION ON) + target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14) + if (MSVC) + # with MSVC, 'cxx_std_14' will only propagate to the host code (MSVC), but will + # not set __cplusplus correctly anyway, while nvcc will ignore it. + # If specified for nvcc on the command line as '-std=c++14' nvcc will emit this + # message instead: + # nvcc warning : The -std=c++14 flag is not supported with the configured host + # compiler. Flag will be ignored. + set_property(SOURCE cuda-cpp14.cu APPEND PROPERTY + COMPILE_OPTIONS -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus) + set_property(SOURCE cpp14.cc APPEND PROPERTY + COMPILE_OPTIONS /std:c++14 /Zc:__cplusplus) + endif() +endif() + +get_target_property(IN_USE_CUDA_STANDARD fmt-in-cuda-test CUDA_STANDARD) +message(STATUS "cuda_standard: ${IN_USE_CUDA_STANDARD}") + +get_target_property(IN_USE_CUDA_STANDARD_REQUIRED + fmt-in-cuda-test CUDA_STANDARD_REQUIRED) +message(STATUS "cuda_standard_required: ${IN_USE_CUDA_STANDARD_REQUIRED}") + +# We don't use PUBLIC or other keyword for reasons explained in the +# CUDA_LINK_LIBRARIES_KEYWORD section in +# https://cmake.org/cmake/help/latest/module/FindCUDA.html +target_link_libraries(fmt-in-cuda-test fmt::fmt) + diff --git a/libs/fmt/test/cuda-test/cpp14.cc b/libs/fmt/test/cuda-test/cpp14.cc new file mode 100644 index 0000000000000000000000000000000000000000..ad8463962f58200df35eacbc897d4b9d1c3aeed0 --- /dev/null +++ b/libs/fmt/test/cuda-test/cpp14.cc @@ -0,0 +1,11 @@ +#include <fmt/core.h> + +// The purpose of this part is to ensure NVCC's host compiler also supports +// the standard version. See 'cuda-cpp14.cu'. +// +// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros +static_assert(__cplusplus >= 201402L, "expect C++ 2014 for host compiler"); + +auto make_message_cpp() -> std::string { + return fmt::format("host compiler \t: __cplusplus == {}", __cplusplus); +} diff --git a/libs/fmt/test/cuda-test/cuda-cpp14.cu b/libs/fmt/test/cuda-test/cuda-cpp14.cu new file mode 100644 index 0000000000000000000000000000000000000000..76a9d08836f6c6a3fa1c64e28bd43cae0392fc93 --- /dev/null +++ b/libs/fmt/test/cuda-test/cuda-cpp14.cu @@ -0,0 +1,28 @@ +// Direct NVCC command line example: +// +// nvcc ./cuda-cpp14.cu -x cu -I"../include" -l"fmtd" -L"../build/Debug" \ +// -std=c++14 -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus + +// Ensure that we are using the latest C++ standard for NVCC +// The version is C++14 +// +// https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-cplusplus-language-support +// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros +static_assert(__cplusplus >= 201402L, "expect C++ 2014 for nvcc"); + +#include <fmt/core.h> + +#include <cuda.h> +#include <iostream> + +extern auto make_message_cpp() -> std::string; +extern auto make_message_cuda() -> std::string; + +int main() { + std::cout << make_message_cuda() << std::endl; + std::cout << make_message_cpp() << std::endl; +} + +auto make_message_cuda() -> std::string { + return fmt::format("nvcc compiler \t: __cplusplus == {}", __cplusplus); +} diff --git a/libs/fmt/test/enforce-checks-test.cc b/libs/fmt/test/enforce-checks-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..103f827e327959657b1e7656ab0d05243195a91b --- /dev/null +++ b/libs/fmt/test/enforce-checks-test.cc @@ -0,0 +1,62 @@ +// Formatting library for C++ - formatting library tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include <iterator> +#include <vector> + +#include "fmt/chrono.h" +#include "fmt/color.h" +#include "fmt/format.h" +#include "fmt/locale.h" +#include "fmt/ostream.h" +#include "fmt/ranges.h" + +// Exercise the API to verify that everything we expect to can compile. +void test_format_api() { + fmt::format(FMT_STRING("{}"), 42); + fmt::format(FMT_STRING(L"{}"), 42); + fmt::format(FMT_STRING("noop")); + + fmt::to_string(42); + fmt::to_wstring(42); + + std::vector<char> out; + fmt::format_to(std::back_inserter(out), FMT_STRING("{}"), 42); + + char buffer[4]; + fmt::format_to_n(buffer, 3, FMT_STRING("{}"), 12345); + + wchar_t wbuffer[4]; + fmt::format_to_n(wbuffer, 3, FMT_STRING(L"{}"), 12345); +} + +void test_chrono() { + fmt::format(FMT_STRING("{}"), std::chrono::seconds(42)); + fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42)); +} + +void test_text_style() { + fmt::print(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)"); + fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)"); + + fmt::text_style ts = fg(fmt::rgb(255, 20, 30)); + std::string out; + fmt::format_to(std::back_inserter(out), ts, + FMT_STRING("rgb(255,20,30){}{}{}"), 1, 2, 3); +} + +void test_range() { + std::vector<char> hello = {'h', 'e', 'l', 'l', 'o'}; + fmt::format(FMT_STRING("{}"), hello); +} + +int main() { + test_format_api(); + test_chrono(); + test_text_style(); + test_range(); +} diff --git a/libs/fmt/test/find-package-test/CMakeLists.txt b/libs/fmt/test/find-package-test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..93d686e64335d8f44c267fe00ddfdc808256e5be --- /dev/null +++ b/libs/fmt/test/find-package-test/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.1...3.18) + +project(fmt-test) + +find_package(FMT REQUIRED) + +add_executable(library-test main.cc) +target_link_libraries(library-test fmt::fmt) +target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) +target_include_directories(library-test PUBLIC SYSTEM .) + +if (TARGET fmt::fmt-header-only) + add_executable(header-only-test main.cc) + target_link_libraries(header-only-test fmt::fmt-header-only) + target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) + target_include_directories(header-only-test PUBLIC SYSTEM .) +endif () diff --git a/libs/fmt/test/find-package-test/main.cc b/libs/fmt/test/find-package-test/main.cc new file mode 100644 index 0000000000000000000000000000000000000000..f39f377cc7c6a173ae9b1e9aa29e99452f71b7b6 --- /dev/null +++ b/libs/fmt/test/find-package-test/main.cc @@ -0,0 +1,6 @@ +#include "fmt/format.h" + +int main(int argc, char** argv) { + for(int i = 0; i < argc; ++i) + fmt::print("{}: {}\n", i, argv[i]); +} diff --git a/libs/fmt/test/format b/libs/fmt/test/format new file mode 100644 index 0000000000000000000000000000000000000000..a6c9e7e990b159e7cab0d97962db4feb15468387 --- /dev/null +++ b/libs/fmt/test/format @@ -0,0 +1,856 @@ +// Formatting library for C++ - the standard API implementation +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_FORMAT_ +#define FMT_FORMAT_ + +#include <algorithm> +#include <cassert> +#include <variant> +#include "fmt/format.h" + +// This implementation verifies the correctness of the standard API proposed in +// P0645 Text Formatting and is optimized for copy-pasting from the paper, not +// for efficiency or readability. An efficient implementation should not use +// std::variant and should store packed argument type tags separately from +// values in basic_format_args for small number of arguments. + +namespace std { +template<class T> +constexpr bool Integral = is_integral_v<T>; + +template <class O> + using iter_difference_t = ptrdiff_t; +} + +// https://fmt.dev/Text%20Formatting.html#format.syn +namespace std { + // [format.error], class format_error + class format_error; + + // [format.formatter], formatter + template<class charT> class basic_format_parse_context; + using format_parse_context = basic_format_parse_context<char>; + using wformat_parse_context = basic_format_parse_context<wchar_t>; + + template<class Out, class charT> class basic_format_context; + using format_context = basic_format_context< + /* unspecified */ fmt::detail::buffer_appender<char>, char>; + using wformat_context = basic_format_context< + /* unspecified */ fmt::detail::buffer_appender<wchar_t>, wchar_t>; + + template<class T, class charT = char> struct formatter { + formatter() = delete; + }; + + // [format.arguments], arguments + template<class Context> class basic_format_arg; + + template<class Visitor, class Context> + /* see below */ auto visit_format_arg(Visitor&& vis, basic_format_arg<Context> arg); + + template<class Context, class... Args> struct format_arg_store; // exposition only + + template<class Context> class basic_format_args; + using format_args = basic_format_args<format_context>; + using wformat_args = basic_format_args<wformat_context>; + + template<class Out, class charT> + using format_args_t = basic_format_args<basic_format_context<Out, charT>>; + + template<class Context = format_context, class... Args> + format_arg_store<Context, Args...> + make_format_args(const Args&... args); + template<class... Args> + format_arg_store<wformat_context, Args...> + make_wformat_args(const Args&... args); + + // [format.functions], formatting functions + template<class... Args> + string format(string_view fmt, const Args&... args); + template<class... Args> + wstring format(wstring_view fmt, const Args&... args); + + string vformat(string_view fmt, format_args args); + wstring vformat(wstring_view fmt, wformat_args args); + + template<class Out, class... Args> + Out format_to(Out out, string_view fmt, const Args&... args); + template<class Out, class... Args> + Out format_to(Out out, wstring_view fmt, const Args&... args); + + template<class Out> + Out vformat_to(Out out, string_view fmt, format_args_t<fmt::type_identity_t<Out>, char> args); + template<class Out> + Out vformat_to(Out out, wstring_view fmt, format_args_t<fmt::type_identity_t<Out>, wchar_t> args); + + template<class Out> + struct format_to_n_result { + Out out; + iter_difference_t<Out> size; + }; + + template<class Out, class... Args> + format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n, + string_view fmt, const Args&... args); + template<class Out, class... Args> + format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n, + wstring_view fmt, const Args&... args); + + template<class... Args> + size_t formatted_size(string_view fmt, const Args&... args); + template<class... Args> + size_t formatted_size(wstring_view fmt, const Args&... args); +} + +// https://fmt.dev/Text%20Formatting.html#format.error +namespace std { + class format_error : public runtime_error { + public: + explicit format_error(const string& what_arg) : runtime_error(what_arg) {} + explicit format_error(const char* what_arg) : runtime_error(what_arg) {} + }; +} + +namespace std { +namespace detail { +struct error_handler { + // This function is intentionally not constexpr to give a compile-time error. + void on_error(const char* message) { + throw std::format_error(message); + } +}; +} +} + +// https://fmt.dev/Text%20Formatting.html#format.parse_context +namespace std { + template<class charT> + class basic_format_parse_context { + public: + using char_type = charT; + using const_iterator = typename basic_string_view<charT>::const_iterator; + using iterator = const_iterator; + + private: + iterator begin_; // exposition only + iterator end_; // exposition only + enum indexing { unknown, manual, automatic }; // exposition only + indexing indexing_; // exposition only + size_t next_arg_id_; // exposition only + size_t num_args_; // exposition only + + public: + explicit constexpr basic_format_parse_context(basic_string_view<charT> fmt, + size_t num_args = 0) noexcept; + basic_format_parse_context(const basic_format_parse_context&) = delete; + basic_format_parse_context& operator=(const basic_format_parse_context&) = delete; + + constexpr const_iterator begin() const noexcept; + constexpr const_iterator end() const noexcept; + constexpr void advance_to(const_iterator it); + + constexpr size_t next_arg_id(); + constexpr void check_arg_id(size_t id); + + // Implementation detail: + constexpr void check_arg_id(fmt::string_view) {} + detail::error_handler error_handler() const { return {}; } + void on_error(const char* msg) { error_handler().on_error(msg); } + }; +} + +namespace std { +template<class charT> +/* explicit */ constexpr basic_format_parse_context<charT>:: + basic_format_parse_context(basic_string_view<charT> fmt, + size_t num_args) noexcept +: begin_(fmt.begin()), end_(fmt.end()), indexing_(unknown), next_arg_id_(0), num_args_(num_args) {} + +template<class charT> +constexpr typename basic_format_parse_context<charT>::const_iterator basic_format_parse_context<charT>::begin() const noexcept { return begin_; } + +template<class charT> +constexpr typename basic_format_parse_context<charT>::const_iterator basic_format_parse_context<charT>::end() const noexcept { return end_; } + +template<class charT> +constexpr void basic_format_parse_context<charT>::advance_to(typename basic_format_parse_context<charT>::iterator it) { begin_ = it; } + +template<class charT> +constexpr size_t basic_format_parse_context<charT>::next_arg_id() { + if (indexing_ == manual) + throw format_error("manual to automatic indexing"); + if (indexing_ == unknown) + indexing_ = automatic; + return next_arg_id_++; +} + +template<class charT> +constexpr void basic_format_parse_context<charT>::check_arg_id(size_t id) { + // clang doesn't support __builtin_is_constant_evaluated yet + //if (!(!__builtin_is_constant_evaluated() || id < num_args_)) + // throw format_error(invalid index is out of range"); + if (indexing_ == automatic) + throw format_error("automatic to manual indexing"); + if (indexing_ == unknown) + indexing_ = manual; +} +} + +// https://fmt.dev/Text%20Formatting.html#format.context +namespace std { + template<class Out, class charT> + class basic_format_context { + basic_format_args<basic_format_context> args_; // exposition only + Out out_; // exposition only + + public: + using iterator = Out; + using char_type = charT; + template<class T> using formatter_type = formatter<T, charT>; + + basic_format_arg<basic_format_context> arg(size_t id) const; + + iterator out(); + void advance_to(iterator it); + + // Implementation details: + using format_arg = basic_format_arg<basic_format_context>; + basic_format_context(Out out, basic_format_args<basic_format_context> args, fmt::detail::locale_ref) + : args_(args), out_(out) {} + detail::error_handler error_handler() const { return {}; } + basic_format_arg<basic_format_context> arg(fmt::basic_string_view<charT>) const { + return {}; // unused: named arguments are not supported yet + } + void on_error(const char* msg) { error_handler().on_error(msg); } + }; +} + +namespace std { +template<class O, class charT> +basic_format_arg<basic_format_context<O, charT>> basic_format_context<O, charT>::arg(size_t id) const { return args_.get(id); } + +template<class O, class charT> +typename basic_format_context<O, charT>::iterator basic_format_context<O, charT>::out() { return out_; } + +template<class O, class charT> +void basic_format_context<O, charT>::advance_to(typename basic_format_context<O, charT>::iterator it) { out_ = it; } +} + +namespace std { +namespace detail { +template <typename T> +constexpr bool is_standard_integer_v = + std::is_same_v<T, signed char> || + std::is_same_v<T, short int> || + std::is_same_v<T, int> || + std::is_same_v<T, long int> || + std::is_same_v<T, long long int>; + +template <typename T> +constexpr bool is_standard_unsigned_integer_v = + std::is_same_v<T, unsigned char> || + std::is_same_v<T, unsigned short int> || + std::is_same_v<T, unsigned int> || + std::is_same_v<T, unsigned long int> || + std::is_same_v<T, unsigned long long int>; + +template <typename T, typename Char> struct formatter; +} +} + +// https://fmt.dev/Text%20Formatting.html#format.arg +namespace std { + template<class Context> + class basic_format_arg { + public: + class handle; + + private: + using char_type = typename Context::char_type; // exposition only + + variant<monostate, bool, char_type, + int, unsigned int, long long int, unsigned long long int, + double, long double, + const char_type*, basic_string_view<char_type>, + const void*, handle> value; // exposition only + + template<typename T, + typename = enable_if_t< + std::is_same_v<T, bool> || + std::is_same_v<T, char_type> || + (std::is_same_v<T, char> && std::is_same_v<char_type, wchar_t>) || + detail::is_standard_integer_v<T> || + detail::is_standard_unsigned_integer_v<T> || + sizeof(typename Context::template formatter_type<T>().format(declval<const T&>(), declval<Context&>())) != 0 + >> explicit basic_format_arg(const T& v) noexcept; // exposition only + explicit basic_format_arg(float n) noexcept; // exposition only + explicit basic_format_arg(double n) noexcept; // exposition only + explicit basic_format_arg(long double n) noexcept; // exposition only + explicit basic_format_arg(const char_type* s); // exposition only + + template<class traits> + explicit basic_format_arg( + basic_string_view<char_type, traits> s) noexcept; // exposition only + + template<class traits, class Allocator> + explicit basic_format_arg( + const basic_string<char_type, traits, Allocator>& s) noexcept; // exposition only + + explicit basic_format_arg(nullptr_t) noexcept; // exposition only + + template<class T, typename = enable_if_t<is_void_v<T>>> + explicit basic_format_arg(const T* p) noexcept; // exposition only + + // Fails due to a bug in clang + //template<class Visitor, class Ctx> + // friend auto visit_format_arg(Visitor&& vis, + // basic_format_arg<Ctx> arg); // exposition only + + friend auto get_value(basic_format_arg arg) { + return arg.value; + } + + template <typename T, typename Char> friend struct detail::formatter; + + template<class Ctx, class... Args> + friend format_arg_store<Ctx, Args...> + make_format_args(const Args&... args); // exposition only + + public: + basic_format_arg() noexcept; + + explicit operator bool() const noexcept; + }; +} + +namespace std { +template<class Context> +basic_format_arg<Context>::basic_format_arg() noexcept {} + +template<class Context> +template<class T, typename> /* explicit */ basic_format_arg<Context>::basic_format_arg(const T& v) noexcept { + if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, char_type>) + value = v; + else if constexpr (std::is_same_v<T, char> && std::is_same_v<char_type, wchar_t>) + value = static_cast<wchar_t>(v); + else if constexpr (detail::is_standard_integer_v<T> && sizeof(T) <= sizeof(int)) + value = static_cast<int>(v); + else if constexpr (detail::is_standard_unsigned_integer_v<T> && sizeof(T) <= sizeof(unsigned)) + value = static_cast<unsigned>(v); + else if constexpr (detail::is_standard_integer_v<T>) + value = static_cast<long long int>(v); + else if constexpr (detail::is_standard_unsigned_integer_v<T>) + value = static_cast<unsigned long long int>(v); + else if constexpr (sizeof(typename Context::template formatter_type<T>().format(declval<const T&>(), declval<Context&>())) != 0) + value = handle(v); +} + +template<class Context> +/* explicit */ basic_format_arg<Context>::basic_format_arg(float n) noexcept + : value(static_cast<double>(n)) {} + +template<class Context> +/* explicit */ basic_format_arg<Context>::basic_format_arg(double n) noexcept + : value(n) {} + +template<class Context> +/* explicit */ basic_format_arg<Context>::basic_format_arg(long double n) noexcept + : value(n) {} + +template<class Context> +/* explicit */ basic_format_arg<Context>::basic_format_arg(const typename basic_format_arg<Context>::char_type* s) + : value(s) { + assert(s != nullptr); +} + +template<class Context> +template<class traits> +/* explicit */ basic_format_arg<Context>::basic_format_arg(basic_string_view<char_type, traits> s) noexcept + : value(s) {} + +template<class Context> +template<class traits, class Allocator> +/* explicit */ basic_format_arg<Context>::basic_format_arg( + const basic_string<char_type, traits, Allocator>& s) noexcept + : value(basic_string_view<char_type>(s.data(), s.size())) {} + + +template<class Context> +/* explicit */ basic_format_arg<Context>::basic_format_arg(nullptr_t) noexcept + : value(static_cast<const void*>(nullptr)) {} + +template<class Context> +template<class T, typename> /* explicit */ basic_format_arg<Context>::basic_format_arg(const T* p) noexcept + : value(p) {} + +template<class Context> +/* explicit */ basic_format_arg<Context>::operator bool() const noexcept { + return !holds_alternative<monostate>(value); +} +} + +namespace std { + template<class Context> + class basic_format_arg<Context>::handle { + const void* ptr_; // exposition only + void (*format_)(basic_format_parse_context<char_type>&, + Context&, const void*); // exposition only + + template<class T> explicit handle(const T& val) noexcept; // exposition only + + friend class basic_format_arg<Context>; // exposition only + + public: + void format(basic_format_parse_context<char_type>&, Context& ctx) const; + }; +} + +namespace std { +template<class Context> +template<class T> /* explicit */ basic_format_arg<Context>::handle::handle(const T& val) noexcept + : ptr_(&val), format_([](basic_format_parse_context<char_type>& parse_ctx, Context& format_ctx, const void* ptr) { + typename Context::template formatter_type<T> f; + parse_ctx.advance_to(f.parse(parse_ctx)); + format_ctx.advance_to(f.format(*static_cast<const T*>(ptr), format_ctx)); + }) {} + +template<class Context> +void basic_format_arg<Context>::handle::format(basic_format_parse_context<char_type>& parse_ctx, Context& format_ctx) const { + format_(parse_ctx, format_ctx, ptr_); +} + +// https://fmt.dev/Text%20Formatting.html#format.visit +template<class Visitor, class Context> + auto visit_format_arg(Visitor&& vis, basic_format_arg<Context> arg) { + return visit(vis, get_value(arg)); + } +} + +// https://fmt.dev/Text%20Formatting.html#format.store +namespace std { + template<class Context, class... Args> + struct format_arg_store { // exposition only + array<basic_format_arg<Context>, sizeof...(Args)> args; + }; +} + +// https://fmt.dev/Text%20Formatting.html#format.basic_args +namespace std { + template<class Context> + class basic_format_args { + size_t size_; // exposition only + const basic_format_arg<Context>* data_; // exposition only + + public: + basic_format_args() noexcept; + + template<class... Args> + basic_format_args(const format_arg_store<Context, Args...>& store) noexcept; + + basic_format_arg<Context> get(size_t i) const noexcept; + }; +} + +namespace std { + +template<class Context> +basic_format_args<Context>::basic_format_args() noexcept : size_(0) {} + +template<class Context> +template<class... Args> + basic_format_args<Context>::basic_format_args(const format_arg_store<Context, Args...>& store) noexcept + : size_(sizeof...(Args)), data_(store.args.data()) {} + +template<class Context> +basic_format_arg<Context> basic_format_args<Context>::get(size_t i) const noexcept { + return i < size_ ? data_[i] : basic_format_arg<Context>(); +} +} + +namespace std { +// https://fmt.dev/Text%20Formatting.html#format.make_args +template<class Context /*= format_context*/, class... Args> + format_arg_store<Context, Args...> make_format_args(const Args&... args) { + return {basic_format_arg<Context>(args)...}; + } + +// https://fmt.dev/Text%20Formatting.html#format.make_wargs +template<class... Args> + format_arg_store<wformat_context, Args...> make_wformat_args(const Args&... args) { + return make_format_args<wformat_context>(args...); + } +} + +namespace std { +namespace detail { + +template <typename OutputIt, typename Char> +class arg_formatter + : public fmt::detail::arg_formatter_base<OutputIt, Char, error_handler> { + private: + using char_type = Char; + using base = fmt::detail::arg_formatter_base<OutputIt, Char, error_handler>; + using format_context = std::basic_format_context<OutputIt, Char>; + using parse_context = basic_format_parse_context<Char>; + + parse_context* parse_ctx_; + format_context& ctx_; + + public: + using iterator = OutputIt; + using format_specs = typename base::format_specs; + + /** + \rst + Constructs an argument formatter object. + *ctx* is a reference to the formatting context, + *spec* contains format specifier information for standard argument types. + \endrst + */ + arg_formatter(format_context& ctx, parse_context* parse_ctx = nullptr, fmt::format_specs* spec = nullptr) + : base(ctx.out(), spec, {}), parse_ctx_(parse_ctx), ctx_(ctx) {} + + using base::operator(); + + /** Formats an argument of a user-defined type. */ + iterator operator()(typename std::basic_format_arg<format_context>::handle handle) { + handle.format(*parse_ctx_, ctx_); + return this->out(); + } + + iterator operator()(monostate) { + throw format_error(""); + } +}; + +template <typename Context> +inline fmt::detail::type get_type(basic_format_arg<Context> arg) { + return visit_format_arg([&] (auto val) { + using char_type = typename Context::char_type; + using T = decltype(val); + if (std::is_same_v<T, monostate>) + return fmt::detail::type::none_type; + if (std::is_same_v<T, bool>) + return fmt::detail::type::bool_type; + if (std::is_same_v<T, char_type>) + return fmt::detail::type::char_type; + if (std::is_same_v<T, int>) + return fmt::detail::type::int_type; + if (std::is_same_v<T, unsigned int>) + return fmt::detail::type::uint_type; + if (std::is_same_v<T, long long int>) + return fmt::detail::type::long_long_type; + if (std::is_same_v<T, unsigned long long int>) + return fmt::detail::type::ulong_long_type; + if (std::is_same_v<T, double>) + return fmt::detail::type::double_type; + if (std::is_same_v<T, long double>) + return fmt::detail::type::long_double_type; + if (std::is_same_v<T, const char_type*>) + return fmt::detail::type::cstring_type; + if (std::is_same_v<T, basic_string_view<char_type>>) + return fmt::detail::type::string_type; + if (std::is_same_v<T, const void*>) + return fmt::detail::type::pointer_type; + assert(get_value(arg).index() == 12); + return fmt::detail::type::custom_type; + }, arg); +} + +template <typename Context> +class custom_formatter { + private: + using parse_context = basic_format_parse_context<typename Context::char_type>; + parse_context& parse_ctx_; + Context& format_ctx_; + + public: + custom_formatter(parse_context& parse_ctx, Context& ctx) : parse_ctx_(parse_ctx), format_ctx_(ctx) {} + + bool operator()(typename basic_format_arg<Context>::handle h) const { + h.format(parse_ctx_, format_ctx_); + return true; + } + + template <typename T> bool operator()(T) const { return false; } +}; + +template <typename ArgFormatter, typename Char, typename Context> +struct format_handler : detail::error_handler { + using iterator = typename ArgFormatter::iterator; + + format_handler(iterator out, basic_string_view<Char> str, + basic_format_args<Context> format_args, + fmt::detail::locale_ref loc) + : parse_ctx(str), context(out, format_args, loc) {} + + void on_text(const Char* begin, const Char* end) { + auto size = fmt::detail::to_unsigned(end - begin); + auto out = context.out(); + auto&& it = fmt::detail::reserve(out, size); + it = std::copy_n(begin, size, it); + context.advance_to(out); + } + + int on_arg_id() { return parse_ctx.next_arg_id(); } + int on_arg_id(unsigned id) { return parse_ctx.check_arg_id(id), id; } + int on_arg_id(fmt::basic_string_view<Char>) { return 0; } + + void on_replacement_field(int id, const Char* p) { + auto arg = context.arg(id); + parse_ctx.advance_to(parse_ctx.begin() + (p - &*parse_ctx.begin())); + custom_formatter<Context> f(parse_ctx, context); + if (!visit_format_arg(f, arg)) + context.advance_to(visit_format_arg(ArgFormatter(context, &parse_ctx), arg)); + } + + const Char* on_format_specs(int id, const Char* begin, const Char* end) { + auto arg = context.arg(id); + parse_ctx.advance_to(parse_ctx.begin() + (begin - &*parse_ctx.begin())); + custom_formatter<Context> f(parse_ctx, context); + if (visit_format_arg(f, arg)) return &*parse_ctx.begin(); + fmt::basic_format_specs<Char> specs; + using fmt::detail::specs_handler; + using parse_context = basic_format_parse_context<Char>; + fmt::detail::specs_checker<specs_handler<parse_context, Context>> handler( + specs_handler<parse_context, Context>(specs, parse_ctx, context), get_type(arg)); + begin = parse_format_specs(begin, end, handler); + if (begin == end || *begin != '}') on_error("missing '}' in format string"); + parse_ctx.advance_to(parse_ctx.begin() + (begin - &*parse_ctx.begin())); + context.advance_to(visit_format_arg(ArgFormatter(context, &parse_ctx, &specs), arg)); + return begin; + } + + basic_format_parse_context<Char> parse_ctx; + Context context; +}; + +template <typename T, typename Char> +struct formatter { + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + template <typename ParseContext> + FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext& ctx) { + namespace detail = fmt::detail; + typedef detail::dynamic_specs_handler<ParseContext> handler_type; + auto type = detail::mapped_type_constant<T, fmt::buffer_context<Char>>::value; + detail::specs_checker<handler_type> handler(handler_type(specs_, ctx), + type); + auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); + auto type_spec = specs_.type; + auto eh = ctx.error_handler(); + switch (type) { + case detail::type::none_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case detail::type::int_type: + case detail::type::uint_type: + case detail::type::long_long_type: + case detail::type::ulong_long_type: + case detail::type::bool_type: + handle_int_type_spec(type_spec, + detail::int_type_checker<decltype(eh)>(eh)); + break; + case detail::type::char_type: + handle_char_specs( + &specs_, detail::char_specs_checker<decltype(eh)>(type_spec, eh)); + break; + case detail::type::double_type: + case detail::type::long_double_type: + detail::parse_float_type_spec(specs_, eh); + break; + case detail::type::cstring_type: + detail::handle_cstring_type_spec( + type_spec, detail::cstring_type_checker<decltype(eh)>(eh)); + break; + case detail::type::string_type: + detail::check_string_type_spec(type_spec, eh); + break; + case detail::type::pointer_type: + detail::check_pointer_type_spec(type_spec, eh); + break; + case detail::type::custom_type: + // Custom format specifiers should be checked in parse functions of + // formatter specializations. + break; + } + return it; + } + + template <typename FormatContext> + auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) { + fmt::detail::handle_dynamic_spec<fmt::detail::width_checker>( + specs_.width, specs_.width_ref, ctx); + fmt::detail::handle_dynamic_spec<fmt::detail::precision_checker>( + specs_.precision, specs_.precision_ref, ctx); + using af = arg_formatter<typename FormatContext::iterator, + typename FormatContext::char_type>; + return visit_format_arg(af(ctx, nullptr, &specs_), + basic_format_arg<FormatContext>(val)); + } + + private: + fmt::detail::dynamic_format_specs<Char> specs_; +}; +} // namespace detail + +// https://fmt.dev/Text%20Formatting.html#format.functions +template<class... Args> + string format(string_view fmt, const Args&... args) { + return vformat(fmt, make_format_args(args...)); + } + +template<class... Args> + wstring format(wstring_view fmt, const Args&... args) { + return vformat(fmt, make_wformat_args(args...)); + } + +string vformat(string_view fmt, format_args args) { + fmt::memory_buffer mbuf; + fmt::detail::buffer<char>& buf = mbuf; + using af = detail::arg_formatter<fmt::format_context::iterator, char>; + detail::format_handler<af, char, format_context> + h(fmt::detail::buffer_appender<char>(buf), fmt, args, {}); + fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h); + return to_string(mbuf); +} + +wstring vformat(wstring_view fmt, wformat_args args); + +template<class Out, class... Args> + Out format_to(Out out, string_view fmt, const Args&... args) { + using context = basic_format_context<Out, decltype(fmt)::value_type>; + return vformat_to(out, fmt, make_format_args<context>(args...)); + } + +template<class Out, class... Args> + Out format_to(Out out, wstring_view fmt, const Args&... args) { + using context = basic_format_context<Out, decltype(fmt)::value_type>; + return vformat_to(out, fmt, make_format_args<context>(args...)); + } + +template<class Out> + Out vformat_to(Out out, string_view fmt, format_args_t<fmt::type_identity_t<Out>, char> args) { + using af = detail::arg_formatter<Out, char>; + detail::format_handler<af, char, basic_format_context<Out, char>> + h(out, fmt, args, {}); + fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h); + return h.context.out(); + } + +template<class Out> + Out vformat_to(Out out, wstring_view fmt, format_args_t<fmt::type_identity_t<Out>, wchar_t> args); + +template<class Out, class... Args> + format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n, + string_view fmt, const Args&... args); +template<class Out, class... Args> + format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n, + wstring_view fmt, const Args&... args); + +template<class... Args> + size_t formatted_size(string_view fmt, const Args&... args); +template<class... Args> + size_t formatted_size(wstring_view fmt, const Args&... args); + +#define charT char + +template<> struct formatter<charT, charT> : detail::formatter<charT, charT> {}; + +template<> struct formatter<char, wchar_t>; + +template<> struct formatter<charT*, charT> : detail::formatter<const charT*, charT> {}; + +template<> struct formatter<const charT*, charT> : detail::formatter<const charT*, charT> {}; + +template<size_t N> struct formatter<const charT[N], charT> + : detail::formatter<std::basic_string_view<charT>, charT> {}; + +template<class traits, class Allocator> + struct formatter<basic_string<charT, traits, Allocator>, charT> + : detail::formatter<std::basic_string_view<charT>, charT> {}; + +template<class traits> + struct formatter<basic_string_view<charT, traits>, charT> + : detail::formatter<std::basic_string_view<charT>, charT> {}; + +template <> struct formatter<nullptr_t, charT> : detail::formatter<const void*, charT> {}; +template <> struct formatter<void*, charT> : detail::formatter<const void*, charT> {}; +template <> struct formatter<const void*, charT> : detail::formatter<const void*, charT> {}; +template <> struct formatter<bool, charT> : detail::formatter<bool, charT> {}; + +template <> struct formatter<signed char, charT> : detail::formatter<int, charT> {}; +template <> struct formatter<short, charT> : detail::formatter<int, charT> {}; +template <> struct formatter<int, charT> : detail::formatter<int, charT> {}; +template <> struct formatter<long, charT> + : detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), int, long long>, charT> {}; +template <> struct formatter<long long, charT> : detail::formatter<long long, charT> {}; +template <> struct formatter<unsigned char, charT> : detail::formatter<unsigned int, charT> {}; +template <> struct formatter<unsigned short, charT> : detail::formatter<unsigned int, charT> {}; +template <> struct formatter<unsigned int, charT> : detail::formatter<unsigned int, charT> {}; +template <> struct formatter<unsigned long, charT> + : detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), unsigned, unsigned long long>, charT> {}; +template <> struct formatter<unsigned long long, charT> : detail::formatter<unsigned long long, charT> {}; + +template <> struct formatter<float, charT> : detail::formatter<double, charT> {}; +template <> struct formatter<double, charT> : detail::formatter<double, charT> {}; +template <> struct formatter<long double, charT> : detail::formatter<long double, charT> {}; + +#undef charT + +#define charT wchar_t + +template<> struct formatter<charT, charT> : detail::formatter<charT, charT> {}; + +template<> struct formatter<char, wchar_t> : detail::formatter<charT, charT> {}; + +template<> struct formatter<charT*, charT> : detail::formatter<const charT*, charT> {}; + +template<> struct formatter<const charT*, charT> : detail::formatter<const charT*, charT> {}; + +template<size_t N> struct formatter<const charT[N], charT> + : detail::formatter<std::basic_string_view<charT>, charT> {}; + +template<class traits, class Allocator> + struct formatter<std::basic_string<charT, traits, Allocator>, charT> + : detail::formatter<std::basic_string_view<charT>, charT> {}; + +template<class traits> + struct formatter<std::basic_string_view<charT, traits>, charT> + : detail::formatter<std::basic_string_view<charT>, charT> {}; + +template <> struct formatter<nullptr_t, charT> : detail::formatter<const void*, charT> {}; +template <> struct formatter<void*, charT> : detail::formatter<const void*, charT> {}; +template <> struct formatter<const void*, charT> : detail::formatter<const void*, charT> {}; +template <> struct formatter<bool, charT> : detail::formatter<bool, charT> {}; + +template <> struct formatter<signed char, charT> : detail::formatter<int, charT> {}; +template <> struct formatter<short, charT> : detail::formatter<int, charT> {}; +template <> struct formatter<int, charT> : detail::formatter<int, charT> {}; +template <> struct formatter<long, charT> + : detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), int, long long>, charT> {}; +template <> struct formatter<long long, charT> : detail::formatter<long long, charT> {}; +template <> struct formatter<unsigned char, charT> : detail::formatter<unsigned int, charT> {}; +template <> struct formatter<unsigned short, charT> : detail::formatter<unsigned int, charT> {}; +template <> struct formatter<unsigned int, charT> : detail::formatter<unsigned int, charT> {}; +template <> struct formatter<unsigned long, charT> + : detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), unsigned, unsigned long long>, charT> {}; +template <> struct formatter<unsigned long long, charT> : detail::formatter<unsigned long long, charT> {}; + +template <> struct formatter<float, charT> : detail::formatter<double, charT> {}; +template <> struct formatter<double, charT> : detail::formatter<double, charT> {}; +template <> struct formatter<long double, charT> : detail::formatter<long double, charT> {}; + +#undef charT + + template<> struct formatter<const wchar_t, char> { + formatter() = delete; + }; +} + +#endif // FMT_FORMAT_ diff --git a/libs/fmt/test/format-impl-test.cc b/libs/fmt/test/format-impl-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..8094daf8e8022b96d6b8053a641f8427fe2374c7 --- /dev/null +++ b/libs/fmt/test/format-impl-test.cc @@ -0,0 +1,414 @@ +// Formatting library for C++ - formatting library implementation tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include <algorithm> +#include <cstring> + +// clang-format off +#include "test-assert.h" +// clang-format on + +#include "fmt/format.h" +#include "gmock/gmock.h" +#include "util.h" + +using fmt::detail::bigint; +using fmt::detail::fp; +using fmt::detail::max_value; + +static_assert(!std::is_copy_constructible<bigint>::value, ""); +static_assert(!std::is_copy_assignable<bigint>::value, ""); + +TEST(bigint_test, construct) { + EXPECT_EQ("", fmt::format("{}", bigint())); + EXPECT_EQ("42", fmt::format("{}", bigint(0x42))); + EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0))); +} + +TEST(bigint_test, compare) { + bigint n1(42); + bigint n2(42); + EXPECT_EQ(compare(n1, n2), 0); + n2 <<= 32; + EXPECT_LT(compare(n1, n2), 0); + bigint n3(43); + EXPECT_LT(compare(n1, n3), 0); + EXPECT_GT(compare(n3, n1), 0); + bigint n4(42 * 0x100000001); + EXPECT_LT(compare(n2, n4), 0); + EXPECT_GT(compare(n4, n2), 0); +} + +TEST(bigint_test, add_compare) { + EXPECT_LT( + add_compare(bigint(0xffffffff), bigint(0xffffffff), bigint(1) <<= 64), 0); + EXPECT_LT(add_compare(bigint(1) <<= 32, bigint(1), bigint(1) <<= 96), 0); + EXPECT_GT(add_compare(bigint(1) <<= 32, bigint(0), bigint(0xffffffff)), 0); + EXPECT_GT(add_compare(bigint(0), bigint(1) <<= 32, bigint(0xffffffff)), 0); + EXPECT_GT(add_compare(bigint(42), bigint(1), bigint(42)), 0); + EXPECT_GT(add_compare(bigint(0xffffffff), bigint(1), bigint(0xffffffff)), 0); + EXPECT_LT(add_compare(bigint(10), bigint(10), bigint(22)), 0); + EXPECT_LT(add_compare(bigint(0x100000010), bigint(0x100000010), + bigint(0x300000010)), + 0); + EXPECT_GT(add_compare(bigint(0x1ffffffff), bigint(0x100000002), + bigint(0x300000000)), + 0); + EXPECT_EQ(add_compare(bigint(0x1ffffffff), bigint(0x100000002), + bigint(0x300000001)), + 0); + EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002), + bigint(0x300000002)), + 0); + EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002), + bigint(0x300000003)), + 0); +} + +TEST(bigint_test, shift_left) { + bigint n(0x42); + n <<= 0; + EXPECT_EQ("42", fmt::format("{}", n)); + n <<= 1; + EXPECT_EQ("84", fmt::format("{}", n)); + n <<= 25; + EXPECT_EQ("108000000", fmt::format("{}", n)); +} + +TEST(bigint_test, multiply) { + bigint n(0x42); + EXPECT_THROW(n *= 0, assertion_failure); + n *= 1; + EXPECT_EQ("42", fmt::format("{}", n)); + n *= 2; + EXPECT_EQ("84", fmt::format("{}", n)); + n *= 0x12345678; + EXPECT_EQ("962fc95e0", fmt::format("{}", n)); + bigint bigmax(max_value<uint32_t>()); + bigmax *= max_value<uint32_t>(); + EXPECT_EQ("fffffffe00000001", fmt::format("{}", bigmax)); + bigmax.assign(max_value<uint64_t>()); + bigmax *= max_value<uint64_t>(); + EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax)); +} + +TEST(bigint_test, accumulator) { + fmt::detail::accumulator acc; + EXPECT_EQ(acc.lower, 0); + EXPECT_EQ(acc.upper, 0); + acc.upper = 12; + acc.lower = 34; + EXPECT_EQ(static_cast<uint32_t>(acc), 34); + acc += 56; + EXPECT_EQ(acc.lower, 90); + acc += max_value<uint64_t>(); + EXPECT_EQ(acc.upper, 13); + EXPECT_EQ(acc.lower, 89); + acc >>= 32; + EXPECT_EQ(acc.upper, 0); + EXPECT_EQ(acc.lower, 13 * 0x100000000); +} + +TEST(bigint_test, square) { + bigint n0(0); + n0.square(); + EXPECT_EQ("0", fmt::format("{}", n0)); + bigint n1(0x100); + n1.square(); + EXPECT_EQ("10000", fmt::format("{}", n1)); + bigint n2(0xfffffffff); + n2.square(); + EXPECT_EQ("ffffffffe000000001", fmt::format("{}", n2)); + bigint n3(max_value<uint64_t>()); + n3.square(); + EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", n3)); + bigint n4; + n4.assign_pow10(10); + EXPECT_EQ("2540be400", fmt::format("{}", n4)); +} + +TEST(bigint_test, divmod_assign_zero_divisor) { + bigint zero(0); + EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure); + EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure); +} + +TEST(bigint_test, divmod_assign_self) { + bigint n(100); + EXPECT_THROW(n.divmod_assign(n), assertion_failure); +} + +TEST(bigint_test, divmod_assign_unaligned) { + // (42 << 340) / pow(10, 100): + bigint n1(42); + n1 <<= 340; + bigint n2; + n2.assign_pow10(100); + int result = n1.divmod_assign(n2); + EXPECT_EQ(result, 9406); + EXPECT_EQ("10f8353019583bfc29ffc8f564e1b9f9d819dbb4cf783e4507eca1539220p96", + fmt::format("{}", n1)); +} + +TEST(bigint_test, divmod_assign) { + // 100 / 10: + bigint n1(100); + int result = n1.divmod_assign(bigint(10)); + EXPECT_EQ(result, 10); + EXPECT_EQ("0", fmt::format("{}", n1)); + // pow(10, 100) / (42 << 320): + n1.assign_pow10(100); + result = n1.divmod_assign(bigint(42) <<= 320); + EXPECT_EQ(result, 111); + EXPECT_EQ("13ad2594c37ceb0b2784c4ce0bf38ace408e211a7caab24308a82e8f10p96", + fmt::format("{}", n1)); + // 42 / 100: + bigint n2(42); + n1.assign_pow10(2); + result = n2.divmod_assign(n1); + EXPECT_EQ(result, 0); + EXPECT_EQ("2a", fmt::format("{}", n2)); +} + +template <bool is_iec559> void run_double_tests() { + fmt::print("warning: double is not IEC559, skipping FP tests\n"); +} + +template <> void run_double_tests<true>() { + // Construct from double. + EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52)); +} + +TEST(fp_test, double_tests) { + run_double_tests<std::numeric_limits<double>::is_iec559>(); +} + +TEST(fp_test, normalize) { + const auto v = fp(0xbeef, 42); + auto normalized = normalize(v); + EXPECT_EQ(0xbeef000000000000, normalized.f); + EXPECT_EQ(-6, normalized.e); +} + +TEST(fp_test, multiply) { + auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7); + EXPECT_EQ(v.f, 123u * 56u); + EXPECT_EQ(v.e, 4 + 7 + 64); + v = fp(123ULL << 32, 4) * fp(567ULL << 31, 8); + EXPECT_EQ(v.f, (123 * 567 + 1u) / 2); + EXPECT_EQ(v.e, 4 + 8 + 64); +} + +TEST(fp_test, get_cached_power) { + using limits = std::numeric_limits<double>; + for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) { + int dec_exp = 0; + auto fp = fmt::detail::get_cached_power(exp, dec_exp); + bigint exact, cache(fp.f); + if (dec_exp >= 0) { + exact.assign_pow10(dec_exp); + if (fp.e <= 0) + exact <<= -fp.e; + else + cache <<= fp.e; + exact.align(cache); + cache.align(exact); + auto exact_str = fmt::format("{}", exact); + auto cache_str = fmt::format("{}", cache); + EXPECT_EQ(exact_str.size(), cache_str.size()); + EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15)); + int diff = cache_str[15] - exact_str[15]; + if (diff == 1) + EXPECT_GT(exact_str[16], '8'); + else + EXPECT_EQ(diff, 0); + } else { + cache.assign_pow10(-dec_exp); + cache *= fp.f + 1; // Inexact check. + exact.assign(1); + exact <<= -fp.e; + exact.align(cache); + auto exact_str = fmt::format("{}", exact); + auto cache_str = fmt::format("{}", cache); + EXPECT_EQ(exact_str.size(), cache_str.size()); + EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16)); + } + } +} + +TEST(fp_test, dragonbox_max_k) { + using fmt::detail::dragonbox::floor_log10_pow2; + using float_info = fmt::detail::dragonbox::float_info<float>; + EXPECT_EQ(fmt::detail::const_check(float_info::max_k), + float_info::kappa - floor_log10_pow2(float_info::min_exponent - + float_info::significand_bits)); + using double_info = fmt::detail::dragonbox::float_info<double>; + EXPECT_EQ( + fmt::detail::const_check(double_info::max_k), + double_info::kappa - floor_log10_pow2(double_info::min_exponent - + double_info::significand_bits)); +} + +TEST(fp_test, get_round_direction) { + using fmt::detail::get_round_direction; + using fmt::detail::round_direction; + EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0)); + EXPECT_EQ(round_direction::up, get_round_direction(100, 51, 0)); + EXPECT_EQ(round_direction::down, get_round_direction(100, 40, 10)); + EXPECT_EQ(round_direction::up, get_round_direction(100, 60, 10)); + for (size_t i = 41; i < 60; ++i) + EXPECT_EQ(round_direction::unknown, get_round_direction(100, i, 10)); + uint64_t max = max_value<uint64_t>(); + EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure); + EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure); + EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure); + // Check that remainder + error doesn't overflow. + EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 2)); + // Check that 2 * (remainder + error) doesn't overflow. + EXPECT_EQ(round_direction::unknown, + get_round_direction(max, max / 2 + 1, max / 2)); + // Check that remainder - error doesn't overflow. + EXPECT_EQ(round_direction::unknown, get_round_direction(100, 40, 41)); + // Check that 2 * (remainder - error) doesn't overflow. + EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1)); +} + +TEST(fp_test, fixed_handler) { + struct handler : fmt::detail::fixed_handler { + char buffer[10]; + handler(int prec = 0) : fmt::detail::fixed_handler() { + buf = buffer; + precision = prec; + } + }; + int exp = 0; + handler().on_digit('0', 100, 99, 0, exp, false); + EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false), + assertion_failure); + namespace digits = fmt::detail::digits; + EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::error); + // Check that divisor - error doesn't overflow. + EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error); + // Check that 2 * error doesn't overflow. + uint64_t max = max_value<uint64_t>(); + EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, exp, false), + digits::error); +} + +TEST(fp_test, grisu_format_compiles_with_on_ieee_double) { + fmt::memory_buffer buf; + format_float(0.42, -1, fmt::detail::float_specs(), buf); +} + +TEST(format_impl_test, strerror) { + char* message = nullptr; + char buffer[256]; + EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = nullptr, 0), + "invalid buffer"); + EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = buffer, 0), + "invalid buffer"); + buffer[0] = 'x'; +#if defined(_GNU_SOURCE) && !defined(__COVERITY__) + // Use invalid error code to make sure that safe_strerror returns an error + // message in the buffer rather than a pointer to a static string. + int error_code = -1; +#else + int error_code = EDOM; +#endif + + int result = fmt::detail::safe_strerror(error_code, message = buffer, 256); + EXPECT_EQ(result, 0); + size_t message_size = std::strlen(message); + EXPECT_GE(255u, message_size); + EXPECT_EQ(get_system_error(error_code), message); + + // safe_strerror never uses buffer on MinGW. +#if !defined(__MINGW32__) && !defined(__sun) + result = + fmt::detail::safe_strerror(error_code, message = buffer, message_size); + EXPECT_EQ(ERANGE, result); + result = fmt::detail::safe_strerror(error_code, message = buffer, 1); + EXPECT_EQ(buffer, message); // Message should point to buffer. + EXPECT_EQ(ERANGE, result); + EXPECT_STREQ("", message); +#endif +} + +TEST(format_impl_test, format_error_code) { + std::string msg = "error 42", sep = ": "; + { + fmt::memory_buffer buffer; + format_to(buffer, "garbage"); + fmt::detail::format_error_code(buffer, 42, "test"); + EXPECT_EQ("test: " + msg, to_string(buffer)); + } + { + fmt::memory_buffer buffer; + auto prefix = + std::string(fmt::inline_buffer_size - msg.size() - sep.size() + 1, 'x'); + fmt::detail::format_error_code(buffer, 42, prefix); + EXPECT_EQ(msg, to_string(buffer)); + } + int codes[] = {42, -1}; + for (size_t i = 0, n = sizeof(codes) / sizeof(*codes); i < n; ++i) { + // Test maximum buffer size. + msg = fmt::format("error {}", codes[i]); + fmt::memory_buffer buffer; + auto prefix = + std::string(fmt::inline_buffer_size - msg.size() - sep.size(), 'x'); + fmt::detail::format_error_code(buffer, codes[i], prefix); + EXPECT_EQ(prefix + sep + msg, to_string(buffer)); + size_t size = fmt::inline_buffer_size; + EXPECT_EQ(size, buffer.size()); + buffer.resize(0); + // Test with a message that doesn't fit into the buffer. + prefix += 'x'; + fmt::detail::format_error_code(buffer, codes[i], prefix); + EXPECT_EQ(msg, to_string(buffer)); + } +} + +TEST(format_impl_test, compute_width) { + EXPECT_EQ(4, + fmt::detail::compute_width( + fmt::basic_string_view<fmt::detail::char8_type>( + reinterpret_cast<const fmt::detail::char8_type*>("ёжик")))); +} + +// Tests fmt::detail::count_digits for integer type Int. +template <typename Int> void test_count_digits() { + for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::detail::count_digits(i)); + for (Int i = 1, n = 1, end = max_value<Int>() / 10; n <= end; ++i) { + n *= 10; + EXPECT_EQ(i, fmt::detail::count_digits(n - 1)); + EXPECT_EQ(i + 1, fmt::detail::count_digits(n)); + } +} + +TEST(format_impl_test, count_digits) { + test_count_digits<uint32_t>(); + test_count_digits<uint64_t>(); +} + +TEST(format_impl_test, write_fallback_uintptr) { + std::string s; + fmt::detail::write_ptr<char>( + std::back_inserter(s), + fmt::detail::fallback_uintptr(reinterpret_cast<void*>(0xface)), nullptr); + EXPECT_EQ(s, "0xface"); +} + +#ifdef _WIN32 +# include <windows.h> +#endif + +#ifdef _WIN32 +TEST(format_impl_test, write_console_signature) { + decltype(WriteConsoleW)* p = fmt::detail::WriteConsoleW; + (void)p; +} +#endif diff --git a/libs/fmt/test/format-test.cc b/libs/fmt/test/format-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..992b1269def8bbc1e226825852d472bf452cbc3a --- /dev/null +++ b/libs/fmt/test/format-test.cc @@ -0,0 +1,2515 @@ +// Formatting library for C++ - formatting library tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +// Check if fmt/format.h compiles with windows.h included before it. +#ifdef _WIN32 +# include <windows.h> +#endif +// clang-format off +#include "fmt/format.h" +// clang-format on + +#include <stdint.h> // uint32_t +#include <climits> // INT_MAX +#include <cmath> // std::signbit +#include <cstring> // std::strlen +#include <iterator> // std::back_inserter +#include <list> // std::list +#include <memory> // std::unique_ptr +#include <type_traits> // std::is_default_constructible + +#include "gtest-extra.h" +#include "mock-allocator.h" +#include "util.h" + +using fmt::basic_memory_buffer; +using fmt::format_error; +using fmt::memory_buffer; +using fmt::string_view; +using fmt::detail::max_value; + +using testing::Return; +using testing::StrictMock; + +enum { buffer_size = 256 }; + +struct uint32_pair { + uint32_t u[2]; +}; + +TEST(util_test, bit_cast) { + auto s = fmt::detail::bit_cast<uint32_pair>(uint64_t{42}); + EXPECT_EQ(fmt::detail::bit_cast<uint64_t>(s), 42ull); + s = fmt::detail::bit_cast<uint32_pair>(~uint64_t{0}); + EXPECT_EQ(fmt::detail::bit_cast<uint64_t>(s), ~0ull); +} + +// Increment a number in a string. +void increment(char* s) { + for (int i = static_cast<int>(std::strlen(s)) - 1; i >= 0; --i) { + if (s[i] != '9') { + ++s[i]; + break; + } + s[i] = '0'; + } +} + +TEST(util_test, increment) { + char s[10] = "123"; + increment(s); + EXPECT_STREQ("124", s); + s[2] = '8'; + increment(s); + EXPECT_STREQ("129", s); + increment(s); + EXPECT_STREQ("130", s); + s[1] = s[2] = '9'; + increment(s); + EXPECT_STREQ("200", s); +} + +TEST(util_test, parse_nonnegative_int) { + auto s = fmt::string_view("10000000000"); + auto begin = s.begin(), end = s.end(); + EXPECT_THROW_MSG( + parse_nonnegative_int(begin, end, fmt::detail::error_handler()), + fmt::format_error, "number is too big"); + s = "2147483649"; + begin = s.begin(); + end = s.end(); + EXPECT_THROW_MSG( + parse_nonnegative_int(begin, end, fmt::detail::error_handler()), + fmt::format_error, "number is too big"); +} + +TEST(util_test, utf8_to_utf16) { + auto u = fmt::detail::utf8_to_utf16("лошадка"); + EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str()); + EXPECT_EQ(7, u.size()); + // U+10437 { DESERET SMALL LETTER YEE } + EXPECT_EQ(L"\xD801\xDC37", fmt::detail::utf8_to_utf16("𐐷").str()); + EXPECT_THROW_MSG(fmt::detail::utf8_to_utf16("\xc3\x28"), std::runtime_error, + "invalid utf8"); + EXPECT_THROW_MSG(fmt::detail::utf8_to_utf16(fmt::string_view("л", 1)), + std::runtime_error, "invalid utf8"); + EXPECT_EQ(L"123456", fmt::detail::utf8_to_utf16("123456").str()); +} + +TEST(util_test, utf8_to_utf16_empty_string) { + auto s = std::string(); + auto u = fmt::detail::utf8_to_utf16(s.c_str()); + EXPECT_EQ(L"", u.str()); + EXPECT_EQ(s.size(), u.size()); +} + +TEST(util_test, allocator_ref) { + using test_allocator_ref = allocator_ref<mock_allocator<int>>; + auto check_forwarding = [](mock_allocator<int>& alloc, + test_allocator_ref& ref) { + int mem; + // Check if value_type is properly defined. + allocator_ref<mock_allocator<int>>::value_type* ptr = &mem; + // Check forwarding. + EXPECT_CALL(alloc, allocate(42)).WillOnce(Return(ptr)); + ref.allocate(42); + EXPECT_CALL(alloc, deallocate(ptr, 42)); + ref.deallocate(ptr, 42); + }; + + StrictMock<mock_allocator<int>> alloc; + auto ref = test_allocator_ref(&alloc); + // Check if allocator_ref forwards to the underlying allocator. + check_forwarding(alloc, ref); + test_allocator_ref ref2(ref); + check_forwarding(alloc, ref2); + test_allocator_ref ref3; + EXPECT_EQ(nullptr, ref3.get()); + ref3 = ref; + check_forwarding(alloc, ref3); +} + +TEST(util_test, format_system_error) { + fmt::memory_buffer message; + fmt::format_system_error(message, EDOM, "test"); + EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), + to_string(message)); + message = fmt::memory_buffer(); + + // Check if std::allocator throws on allocating max size_t / 2 chars. + size_t max_size = max_value<size_t>() / 2; + bool throws_on_alloc = false; + try { + auto alloc = std::allocator<char>(); + alloc.deallocate(alloc.allocate(max_size), max_size); + } catch (const std::bad_alloc&) { + throws_on_alloc = true; + } + if (!throws_on_alloc) { + fmt::print("warning: std::allocator allocates {} chars", max_size); + return; + } + fmt::format_system_error(message, EDOM, fmt::string_view(nullptr, max_size)); + EXPECT_EQ(fmt::format("error {}", EDOM), to_string(message)); +} + +TEST(util_test, system_error) { + auto test_error = fmt::system_error(EDOM, "test"); + EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), test_error.what()); + EXPECT_EQ(EDOM, test_error.error_code()); + + auto error = fmt::system_error(0, ""); + try { + throw fmt::system_error(EDOM, "test {}", "error"); + } catch (const fmt::system_error& e) { + error = e; + } + fmt::memory_buffer message; + fmt::format_system_error(message, EDOM, "test error"); + EXPECT_EQ(to_string(message), error.what()); + EXPECT_EQ(EDOM, error.error_code()); +} + +TEST(util_test, report_system_error) { + fmt::memory_buffer out; + fmt::format_system_error(out, EDOM, "test error"); + out.push_back('\n'); + EXPECT_WRITE(stderr, fmt::report_system_error(EDOM, "test error"), + to_string(out)); +} + +TEST(memory_buffer_test, ctor) { + basic_memory_buffer<char, 123> buffer; + EXPECT_EQ(static_cast<size_t>(0), buffer.size()); + EXPECT_EQ(123u, buffer.capacity()); +} + +using std_allocator = allocator_ref<std::allocator<char>>; + +TEST(memory_buffer_test, move_ctor_inline_buffer) { + auto check_move_buffer = + [](const char* str, basic_memory_buffer<char, 5, std_allocator>& buffer) { + std::allocator<char>* alloc = buffer.get_allocator().get(); + basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer)); + // Move shouldn't destroy the inline content of the first buffer. + EXPECT_EQ(str, std::string(&buffer[0], buffer.size())); + EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size())); + EXPECT_EQ(5u, buffer2.capacity()); + // Move should transfer allocator. + EXPECT_EQ(nullptr, buffer.get_allocator().get()); + EXPECT_EQ(alloc, buffer2.get_allocator().get()); + }; + + auto alloc = std::allocator<char>(); + basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc))); + const char test[] = "test"; + buffer.append(string_view(test, 4)); + check_move_buffer("test", buffer); + // Adding one more character fills the inline buffer, but doesn't cause + // dynamic allocation. + buffer.push_back('a'); + check_move_buffer("testa", buffer); +} + +TEST(memory_buffer_test, move_ctor_dynamic_buffer) { + auto alloc = std::allocator<char>(); + basic_memory_buffer<char, 4, std_allocator> buffer((std_allocator(&alloc))); + const char test[] = "test"; + buffer.append(test, test + 4); + const char* inline_buffer_ptr = &buffer[0]; + // Adding one more character causes the content to move from the inline to + // a dynamically allocated buffer. + buffer.push_back('a'); + basic_memory_buffer<char, 4, std_allocator> buffer2(std::move(buffer)); + // Move should rip the guts of the first buffer. + EXPECT_EQ(inline_buffer_ptr, &buffer[0]); + EXPECT_EQ("testa", std::string(&buffer2[0], buffer2.size())); + EXPECT_GT(buffer2.capacity(), 4u); +} + +void check_move_assign_buffer(const char* str, + basic_memory_buffer<char, 5>& buffer) { + basic_memory_buffer<char, 5> buffer2; + buffer2 = std::move(buffer); + // Move shouldn't destroy the inline content of the first buffer. + EXPECT_EQ(str, std::string(&buffer[0], buffer.size())); + EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size())); + EXPECT_EQ(5u, buffer2.capacity()); +} + +TEST(memory_buffer_test, move_assignment) { + basic_memory_buffer<char, 5> buffer; + const char test[] = "test"; + buffer.append(test, test + 4); + check_move_assign_buffer("test", buffer); + // Adding one more character fills the inline buffer, but doesn't cause + // dynamic allocation. + buffer.push_back('a'); + check_move_assign_buffer("testa", buffer); + const char* inline_buffer_ptr = &buffer[0]; + // Adding one more character causes the content to move from the inline to + // a dynamically allocated buffer. + buffer.push_back('b'); + basic_memory_buffer<char, 5> buffer2; + buffer2 = std::move(buffer); + // Move should rip the guts of the first buffer. + EXPECT_EQ(inline_buffer_ptr, &buffer[0]); + EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size())); + EXPECT_GT(buffer2.capacity(), 5u); +} + +TEST(memory_buffer_test, grow) { + typedef allocator_ref<mock_allocator<int>> Allocator; + mock_allocator<int> alloc; + basic_memory_buffer<int, 10, Allocator> buffer((Allocator(&alloc))); + buffer.resize(7); + using fmt::detail::to_unsigned; + for (int i = 0; i < 7; ++i) buffer[to_unsigned(i)] = i * i; + EXPECT_EQ(10u, buffer.capacity()); + int mem[20]; + mem[7] = 0xdead; + EXPECT_CALL(alloc, allocate(20)).WillOnce(Return(mem)); + buffer.try_reserve(20); + EXPECT_EQ(20u, buffer.capacity()); + // Check if size elements have been copied + for (int i = 0; i < 7; ++i) EXPECT_EQ(i * i, buffer[to_unsigned(i)]); + // and no more than that. + EXPECT_EQ(0xdead, buffer[7]); + EXPECT_CALL(alloc, deallocate(mem, 20)); +} + +TEST(memory_buffer_test, allocator) { + using test_allocator = allocator_ref<mock_allocator<char>>; + basic_memory_buffer<char, 10, test_allocator> buffer; + EXPECT_EQ(nullptr, buffer.get_allocator().get()); + StrictMock<mock_allocator<char>> alloc; + char mem; + { + basic_memory_buffer<char, 10, test_allocator> buffer2( + (test_allocator(&alloc))); + EXPECT_EQ(&alloc, buffer2.get_allocator().get()); + size_t size = 2 * fmt::inline_buffer_size; + EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem)); + buffer2.reserve(size); + EXPECT_CALL(alloc, deallocate(&mem, size)); + } +} + +TEST(memory_buffer_test, exception_in_deallocate) { + using test_allocator = allocator_ref<mock_allocator<char>>; + StrictMock<mock_allocator<char>> alloc; + basic_memory_buffer<char, 10, test_allocator> buffer( + (test_allocator(&alloc))); + size_t size = 2 * fmt::inline_buffer_size; + auto mem = std::vector<char>(size); + { + EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem[0])); + buffer.resize(size); + std::fill(&buffer[0], &buffer[0] + size, 'x'); + } + auto mem2 = std::vector<char>(2 * size); + { + EXPECT_CALL(alloc, allocate(2 * size)).WillOnce(Return(&mem2[0])); + auto e = std::exception(); + EXPECT_CALL(alloc, deallocate(&mem[0], size)).WillOnce(testing::Throw(e)); + EXPECT_THROW(buffer.reserve(2 * size), std::exception); + EXPECT_EQ(&mem2[0], &buffer[0]); + // Check that the data has been copied. + for (size_t i = 0; i < size; ++i) EXPECT_EQ('x', buffer[i]); + } + EXPECT_CALL(alloc, deallocate(&mem2[0], 2 * size)); +} + +template <typename Allocator, size_t MaxSize> +class max_size_allocator : public Allocator { + public: + using typename Allocator::value_type; + size_t max_size() const FMT_NOEXCEPT { return MaxSize; } + value_type* allocate(size_t n) { + if (n > max_size()) { + throw std::length_error("size > max_size"); + } + return std::allocator_traits<Allocator>::allocate( + *static_cast<Allocator*>(this), n); + } + void deallocate(value_type* p, size_t n) { + std::allocator_traits<Allocator>::deallocate(*static_cast<Allocator*>(this), + p, n); + } +}; + +TEST(memory_buffer_test, max_size_allocator) { + // 160 = 128 + 32 + using test_allocator = max_size_allocator<std::allocator<char>, 160>; + basic_memory_buffer<char, 10, test_allocator> buffer; + buffer.resize(128); + // new_capacity = 128 + 128/2 = 192 > 160 + buffer.resize(160); // Shouldn't throw. +} + +TEST(memory_buffer_test, max_size_allocator_overflow) { + using test_allocator = max_size_allocator<std::allocator<char>, 160>; + basic_memory_buffer<char, 10, test_allocator> buffer; + EXPECT_THROW(buffer.resize(161), std::exception); +} + +TEST(format_test, escape) { + EXPECT_EQ("{", fmt::format("{{")); + EXPECT_EQ("before {", fmt::format("before {{")); + EXPECT_EQ("{ after", fmt::format("{{ after")); + EXPECT_EQ("before { after", fmt::format("before {{ after")); + + EXPECT_EQ("}", fmt::format("}}")); + EXPECT_EQ("before }", fmt::format("before }}")); + EXPECT_EQ("} after", fmt::format("}} after")); + EXPECT_EQ("before } after", fmt::format("before }} after")); + + EXPECT_EQ("{}", fmt::format("{{}}")); + EXPECT_EQ("{42}", fmt::format("{{{0}}}", 42)); +} + +TEST(format_test, unmatched_braces) { + EXPECT_THROW_MSG(fmt::format(+"{"), format_error, "invalid format string"); + EXPECT_THROW_MSG(fmt::format(+"}"), format_error, + "unmatched '}' in format string"); + EXPECT_THROW_MSG(fmt::format(+"{0{}"), format_error, "invalid format string"); +} + +TEST(format_test, no_args) { EXPECT_EQ("test", fmt::format("test")); } + +TEST(format_test, args_in_different_positions) { + EXPECT_EQ("42", fmt::format("{0}", 42)); + EXPECT_EQ("before 42", fmt::format("before {0}", 42)); + EXPECT_EQ("42 after", fmt::format("{0} after", 42)); + EXPECT_EQ("before 42 after", fmt::format("before {0} after", 42)); + EXPECT_EQ("answer = 42", fmt::format("{0} = {1}", "answer", 42)); + EXPECT_EQ("42 is the answer", fmt::format("{1} is the {0}", "answer", 42)); + EXPECT_EQ("abracadabra", fmt::format("{0}{1}{0}", "abra", "cad")); +} + +TEST(format_test, arg_errors) { + EXPECT_THROW_MSG(fmt::format(+"{"), format_error, "invalid format string"); + EXPECT_THROW_MSG(fmt::format(+"{?}"), format_error, "invalid format string"); + EXPECT_THROW_MSG(fmt::format(+"{0"), format_error, "invalid format string"); + EXPECT_THROW_MSG(fmt::format(+"{0}"), format_error, "argument not found"); + EXPECT_THROW_MSG(fmt::format(+"{00}", 42), format_error, + "invalid format string"); + + char format_str[buffer_size]; + safe_sprintf(format_str, "{%u", INT_MAX); + EXPECT_THROW_MSG(fmt::format(+format_str), format_error, + "invalid format string"); + safe_sprintf(format_str, "{%u}", INT_MAX); + EXPECT_THROW_MSG(fmt::format(+format_str), format_error, + "argument not found"); + + safe_sprintf(format_str, "{%u", INT_MAX + 1u); + EXPECT_THROW_MSG(fmt::format(+format_str), format_error, "number is too big"); + safe_sprintf(format_str, "{%u}", INT_MAX + 1u); + EXPECT_THROW_MSG(fmt::format(+format_str), format_error, "number is too big"); +} + +template <int N> struct test_format { + template <typename... Args> + static std::string format(fmt::string_view format_str, const Args&... args) { + return test_format<N - 1>::format(format_str, N - 1, args...); + } +}; + +template <> struct test_format<0> { + template <typename... Args> + static std::string format(fmt::string_view format_str, const Args&... args) { + return fmt::format(format_str, args...); + } +}; + +TEST(format_test, many_args) { + EXPECT_EQ("19", test_format<20>::format("{19}")); + EXPECT_THROW_MSG(test_format<20>::format("{20}"), format_error, + "argument not found"); + EXPECT_THROW_MSG(test_format<21>::format("{21}"), format_error, + "argument not found"); + using fmt::detail::max_packed_args; + std::string format_str = fmt::format("{{{}}}", max_packed_args + 1); + EXPECT_THROW_MSG(test_format<max_packed_args>::format(format_str), + format_error, "argument not found"); +} + +TEST(format_test, named_arg) { + EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'), + fmt::arg("A_", "A"), fmt::arg("_1", 1))); + EXPECT_EQ(" -42", fmt::format("{0:{width}}", -42, fmt::arg("width", 4))); + EXPECT_EQ("st", + fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2))); + EXPECT_EQ("1 2", fmt::format("{} {two}", 1, fmt::arg("two", 2))); + EXPECT_EQ("42", + fmt::format("{c}", fmt::arg("a", 0), fmt::arg("b", 0), + fmt::arg("c", 42), fmt::arg("d", 0), fmt::arg("e", 0), + fmt::arg("f", 0), fmt::arg("g", 0), fmt::arg("h", 0), + fmt::arg("i", 0), fmt::arg("j", 0), fmt::arg("k", 0), + fmt::arg("l", 0), fmt::arg("m", 0), fmt::arg("n", 0), + fmt::arg("o", 0), fmt::arg("p", 0))); + EXPECT_THROW_MSG(fmt::format(+"{a}"), format_error, "argument not found"); + EXPECT_THROW_MSG(fmt::format(+"{a}", 42), format_error, "argument not found"); +} + +TEST(format_test, auto_arg_index) { + EXPECT_EQ("abc", fmt::format("{}{}{}", 'a', 'b', 'c')); + EXPECT_THROW_MSG(fmt::format(+"{0}{}", 'a', 'b'), format_error, + "cannot switch from manual to automatic argument indexing"); + EXPECT_THROW_MSG(fmt::format(+"{}{0}", 'a', 'b'), format_error, + "cannot switch from automatic to manual argument indexing"); + EXPECT_EQ("1.2", fmt::format("{:.{}}", 1.2345, 2)); + EXPECT_THROW_MSG(fmt::format(+"{0}:.{}", 1.2345, 2), format_error, + "cannot switch from manual to automatic argument indexing"); + EXPECT_THROW_MSG(fmt::format(+"{:.{0}}", 1.2345, 2), format_error, + "cannot switch from automatic to manual argument indexing"); + EXPECT_THROW_MSG(fmt::format(+"{}"), format_error, "argument not found"); +} + +TEST(format_test, empty_specs) { EXPECT_EQ("42", fmt::format("{0:}", 42)); } + +TEST(format_test, left_align) { + EXPECT_EQ("42 ", fmt::format("{0:<4}", 42)); + EXPECT_EQ("42 ", fmt::format("{0:<4o}", 042)); + EXPECT_EQ("42 ", fmt::format("{0:<4x}", 0x42)); + EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42)); + EXPECT_EQ("42 ", fmt::format("{0:<5}", 42u)); + EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42l)); + EXPECT_EQ("42 ", fmt::format("{0:<5}", 42ul)); + EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42ll)); + EXPECT_EQ("42 ", fmt::format("{0:<5}", 42ull)); + EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42.0)); + EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42.0l)); + EXPECT_EQ("c ", fmt::format("{0:<5}", 'c')); + EXPECT_EQ("abc ", fmt::format("{0:<5}", "abc")); + EXPECT_EQ("0xface ", fmt::format("{0:<8}", reinterpret_cast<void*>(0xface))); +} + +TEST(format_test, right_align) { + EXPECT_EQ(" 42", fmt::format("{0:>4}", 42)); + EXPECT_EQ(" 42", fmt::format("{0:>4o}", 042)); + EXPECT_EQ(" 42", fmt::format("{0:>4x}", 0x42)); + EXPECT_EQ(" -42", fmt::format("{0:>5}", -42)); + EXPECT_EQ(" 42", fmt::format("{0:>5}", 42u)); + EXPECT_EQ(" -42", fmt::format("{0:>5}", -42l)); + EXPECT_EQ(" 42", fmt::format("{0:>5}", 42ul)); + EXPECT_EQ(" -42", fmt::format("{0:>5}", -42ll)); + EXPECT_EQ(" 42", fmt::format("{0:>5}", 42ull)); + EXPECT_EQ(" -42", fmt::format("{0:>5}", -42.0)); + EXPECT_EQ(" -42", fmt::format("{0:>5}", -42.0l)); + EXPECT_EQ(" c", fmt::format("{0:>5}", 'c')); + EXPECT_EQ(" abc", fmt::format("{0:>5}", "abc")); + EXPECT_EQ(" 0xface", fmt::format("{0:>8}", reinterpret_cast<void*>(0xface))); +} + +TEST(format_test, center_align) { + EXPECT_EQ(" 42 ", fmt::format("{0:^5}", 42)); + EXPECT_EQ(" 42 ", fmt::format("{0:^5o}", 042)); + EXPECT_EQ(" 42 ", fmt::format("{0:^5x}", 0x42)); + EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42)); + EXPECT_EQ(" 42 ", fmt::format("{0:^5}", 42u)); + EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42l)); + EXPECT_EQ(" 42 ", fmt::format("{0:^5}", 42ul)); + EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42ll)); + EXPECT_EQ(" 42 ", fmt::format("{0:^5}", 42ull)); + EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42.0)); + EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42.0l)); + EXPECT_EQ(" c ", fmt::format("{0:^5}", 'c')); + EXPECT_EQ(" abc ", fmt::format("{0:^6}", "abc")); + EXPECT_EQ(" 0xface ", fmt::format("{0:^8}", reinterpret_cast<void*>(0xface))); +} + +TEST(format_test, fill) { + EXPECT_THROW_MSG(fmt::format(+"{0:{<5}", 'c'), format_error, + "invalid fill character '{'"); + EXPECT_THROW_MSG(fmt::format(+"{0:{<5}}", 'c'), format_error, + "invalid fill character '{'"); + EXPECT_EQ("**42", fmt::format("{0:*>4}", 42)); + EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42)); + EXPECT_EQ("***42", fmt::format("{0:*>5}", 42u)); + EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42l)); + EXPECT_EQ("***42", fmt::format("{0:*>5}", 42ul)); + EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42ll)); + EXPECT_EQ("***42", fmt::format("{0:*>5}", 42ull)); + EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42.0)); + EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42.0l)); + EXPECT_EQ("c****", fmt::format("{0:*<5}", 'c')); + EXPECT_EQ("abc**", fmt::format("{0:*<5}", "abc")); + EXPECT_EQ("**0xface", + fmt::format("{0:*>8}", reinterpret_cast<void*>(0xface))); + EXPECT_EQ("foo=", fmt::format("{:}=", "foo")); + EXPECT_EQ(std::string("\0\0\0*", 4), + fmt::format(string_view("{:\0>4}", 6), '*')); + EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42)); + EXPECT_THROW_MSG(fmt::format(+"{:\x80\x80\x80\x80\x80>}", 0), format_error, + "missing '}' in format string"); +} + +TEST(format_test, plus_sign) { + EXPECT_EQ("+42", fmt::format("{0:+}", 42)); + EXPECT_EQ("-42", fmt::format("{0:+}", -42)); + EXPECT_EQ("+42", fmt::format("{0:+}", 42)); + EXPECT_THROW_MSG(fmt::format(+"{0:+}", 42u), format_error, + "format specifier requires signed argument"); + EXPECT_EQ("+42", fmt::format("{0:+}", 42l)); + EXPECT_THROW_MSG(fmt::format(+"{0:+}", 42ul), format_error, + "format specifier requires signed argument"); + EXPECT_EQ("+42", fmt::format("{0:+}", 42ll)); + EXPECT_THROW_MSG(fmt::format(+"{0:+}", 42ull), format_error, + "format specifier requires signed argument"); + EXPECT_EQ("+42", fmt::format("{0:+}", 42.0)); + EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l)); + EXPECT_THROW_MSG(fmt::format(+"{0:+", 'c'), format_error, + "missing '}' in format string"); + EXPECT_THROW_MSG(fmt::format(+"{0:+}", 'c'), format_error, + "invalid format specifier for char"); + EXPECT_THROW_MSG(fmt::format(+"{0:+}", "abc"), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{0:+}", reinterpret_cast<void*>(0x42)), + format_error, "format specifier requires numeric argument"); +} + +TEST(format_test, minus_sign) { + EXPECT_EQ("42", fmt::format("{0:-}", 42)); + EXPECT_EQ("-42", fmt::format("{0:-}", -42)); + EXPECT_EQ("42", fmt::format("{0:-}", 42)); + EXPECT_THROW_MSG(fmt::format(+"{0:-}", 42u), format_error, + "format specifier requires signed argument"); + EXPECT_EQ("42", fmt::format("{0:-}", 42l)); + EXPECT_THROW_MSG(fmt::format(+"{0:-}", 42ul), format_error, + "format specifier requires signed argument"); + EXPECT_EQ("42", fmt::format("{0:-}", 42ll)); + EXPECT_THROW_MSG(fmt::format(+"{0:-}", 42ull), format_error, + "format specifier requires signed argument"); + EXPECT_EQ("42", fmt::format("{0:-}", 42.0)); + EXPECT_EQ("42", fmt::format("{0:-}", 42.0l)); + EXPECT_THROW_MSG(fmt::format(+"{0:-", 'c'), format_error, + "missing '}' in format string"); + EXPECT_THROW_MSG(fmt::format(+"{0:-}", 'c'), format_error, + "invalid format specifier for char"); + EXPECT_THROW_MSG(fmt::format(+"{0:-}", "abc"), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{0:-}", reinterpret_cast<void*>(0x42)), + format_error, "format specifier requires numeric argument"); +} + +TEST(format_test, space_sign) { + EXPECT_EQ(" 42", fmt::format("{0: }", 42)); + EXPECT_EQ("-42", fmt::format("{0: }", -42)); + EXPECT_EQ(" 42", fmt::format("{0: }", 42)); + EXPECT_THROW_MSG(fmt::format(+"{0: }", 42u), format_error, + "format specifier requires signed argument"); + EXPECT_EQ(" 42", fmt::format("{0: }", 42l)); + EXPECT_THROW_MSG(fmt::format(+"{0: }", 42ul), format_error, + "format specifier requires signed argument"); + EXPECT_EQ(" 42", fmt::format("{0: }", 42ll)); + EXPECT_THROW_MSG(fmt::format(+"{0: }", 42ull), format_error, + "format specifier requires signed argument"); + EXPECT_EQ(" 42", fmt::format("{0: }", 42.0)); + EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l)); + EXPECT_THROW_MSG(fmt::format(+"{0: ", 'c'), format_error, + "missing '}' in format string"); + EXPECT_THROW_MSG(fmt::format(+"{0: }", 'c'), format_error, + "invalid format specifier for char"); + EXPECT_THROW_MSG(fmt::format(+"{0: }", "abc"), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{0: }", reinterpret_cast<void*>(0x42)), + format_error, "format specifier requires numeric argument"); +} + +TEST(format_test, sign_not_truncated) { + wchar_t format_str[] = { + L'{', L':', + '+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0}; + EXPECT_THROW(fmt::format(format_str, 42), format_error); +} + +TEST(format_test, hash_flag) { + EXPECT_EQ("42", fmt::format("{0:#}", 42)); + EXPECT_EQ("-42", fmt::format("{0:#}", -42)); + EXPECT_EQ("0b101010", fmt::format("{0:#b}", 42)); + EXPECT_EQ("0B101010", fmt::format("{0:#B}", 42)); + EXPECT_EQ("-0b101010", fmt::format("{0:#b}", -42)); + EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42)); + EXPECT_EQ("0X42", fmt::format("{0:#X}", 0x42)); + EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42)); + EXPECT_EQ("0", fmt::format("{0:#o}", 0)); + EXPECT_EQ("042", fmt::format("{0:#o}", 042)); + EXPECT_EQ("-042", fmt::format("{0:#o}", -042)); + EXPECT_EQ("42", fmt::format("{0:#}", 42u)); + EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42u)); + EXPECT_EQ("042", fmt::format("{0:#o}", 042u)); + + EXPECT_EQ("-42", fmt::format("{0:#}", -42l)); + EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42l)); + EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42l)); + EXPECT_EQ("042", fmt::format("{0:#o}", 042l)); + EXPECT_EQ("-042", fmt::format("{0:#o}", -042l)); + EXPECT_EQ("42", fmt::format("{0:#}", 42ul)); + EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ul)); + EXPECT_EQ("042", fmt::format("{0:#o}", 042ul)); + + EXPECT_EQ("-42", fmt::format("{0:#}", -42ll)); + EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ll)); + EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42ll)); + EXPECT_EQ("042", fmt::format("{0:#o}", 042ll)); + EXPECT_EQ("-042", fmt::format("{0:#o}", -042ll)); + EXPECT_EQ("42", fmt::format("{0:#}", 42ull)); + EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ull)); + EXPECT_EQ("042", fmt::format("{0:#o}", 042ull)); + + EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0)); + EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0l)); + EXPECT_EQ("4.e+01", fmt::format("{:#.0e}", 42.0)); + EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01)); + EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5)); + EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.5)); + EXPECT_THROW_MSG(fmt::format(+"{0:#", 'c'), format_error, + "missing '}' in format string"); + EXPECT_THROW_MSG(fmt::format(+"{0:#}", 'c'), format_error, + "invalid format specifier for char"); + EXPECT_THROW_MSG(fmt::format(+"{0:#}", "abc"), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{0:#}", reinterpret_cast<void*>(0x42)), + format_error, "format specifier requires numeric argument"); +} + +TEST(format_test, zero_flag) { + EXPECT_EQ("42", fmt::format("{0:0}", 42)); + EXPECT_EQ("-0042", fmt::format("{0:05}", -42)); + EXPECT_EQ("00042", fmt::format("{0:05}", 42u)); + EXPECT_EQ("-0042", fmt::format("{0:05}", -42l)); + EXPECT_EQ("00042", fmt::format("{0:05}", 42ul)); + EXPECT_EQ("-0042", fmt::format("{0:05}", -42ll)); + EXPECT_EQ("00042", fmt::format("{0:05}", 42ull)); + EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0)); + EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0l)); + EXPECT_THROW_MSG(fmt::format(+"{0:0", 'c'), format_error, + "missing '}' in format string"); + EXPECT_THROW_MSG(fmt::format(+"{0:05}", 'c'), format_error, + "invalid format specifier for char"); + EXPECT_THROW_MSG(fmt::format(+"{0:05}", "abc"), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{0:05}", reinterpret_cast<void*>(0x42)), + format_error, "format specifier requires numeric argument"); +} + +TEST(format_test, width) { + char format_str[buffer_size]; + safe_sprintf(format_str, "{0:%u", UINT_MAX); + increment(format_str + 3); + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + size_t size = std::strlen(format_str); + format_str[size] = '}'; + format_str[size + 1] = 0; + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + + safe_sprintf(format_str, "{0:%u", INT_MAX + 1u); + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + safe_sprintf(format_str, "{0:%u}", INT_MAX + 1u); + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + EXPECT_EQ(" -42", fmt::format("{0:4}", -42)); + EXPECT_EQ(" 42", fmt::format("{0:5}", 42u)); + EXPECT_EQ(" -42", fmt::format("{0:6}", -42l)); + EXPECT_EQ(" 42", fmt::format("{0:7}", 42ul)); + EXPECT_EQ(" -42", fmt::format("{0:6}", -42ll)); + EXPECT_EQ(" 42", fmt::format("{0:7}", 42ull)); + EXPECT_EQ(" -1.23", fmt::format("{0:8}", -1.23)); + EXPECT_EQ(" -1.23", fmt::format("{0:9}", -1.23l)); + EXPECT_EQ(" 0xcafe", + fmt::format("{0:10}", reinterpret_cast<void*>(0xcafe))); + EXPECT_EQ("x ", fmt::format("{0:11}", 'x')); + EXPECT_EQ("str ", fmt::format("{0:12}", "str")); + EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**"); + EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**"); + EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.0"); + EXPECT_EQ(fmt::format("{:6c}", static_cast<int>('x')), "x "); + EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), "000000"); +} + +TEST(format_test, runtime_width) { + char format_str[buffer_size]; + safe_sprintf(format_str, "{0:{%u", UINT_MAX); + increment(format_str + 4); + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + size_t size = std::strlen(format_str); + format_str[size] = '}'; + format_str[size + 1] = 0; + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + format_str[size + 1] = '}'; + format_str[size + 2] = 0; + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + + EXPECT_THROW_MSG(fmt::format(+"{0:{", 0), format_error, + "invalid format string"); + EXPECT_THROW_MSG(fmt::format(+"{0:{}", 0), format_error, + "cannot switch from manual to automatic argument indexing"); + EXPECT_THROW_MSG(fmt::format(+"{0:{?}}", 0), format_error, + "invalid format string"); + EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0), format_error, + "argument not found"); + + EXPECT_THROW_MSG(fmt::format(+"{0:{0:}}", 0), format_error, + "invalid format string"); + + EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, -1), format_error, + "negative width"); + EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, (INT_MAX + 1u)), format_error, + "number is too big"); + EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, -1l), format_error, + "negative width"); + if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { + long value = INT_MAX; + EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, (value + 1)), format_error, + "number is too big"); + } + EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, (INT_MAX + 1ul)), format_error, + "number is too big"); + + EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, '0'), format_error, + "width is not integer"); + EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, 0.0), format_error, + "width is not integer"); + + EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42, 4)); + EXPECT_EQ(" 42", fmt::format("{0:{1}}", 42u, 5)); + EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42l, 6)); + EXPECT_EQ(" 42", fmt::format("{0:{1}}", 42ul, 7)); + EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42ll, 6)); + EXPECT_EQ(" 42", fmt::format("{0:{1}}", 42ull, 7)); + EXPECT_EQ(" -1.23", fmt::format("{0:{1}}", -1.23, 8)); + EXPECT_EQ(" -1.23", fmt::format("{0:{1}}", -1.23l, 9)); + EXPECT_EQ(" 0xcafe", + fmt::format("{0:{1}}", reinterpret_cast<void*>(0xcafe), 10)); + EXPECT_EQ("x ", fmt::format("{0:{1}}", 'x', 11)); + EXPECT_EQ("str ", fmt::format("{0:{1}}", "str", 12)); +} + +TEST(format_test, precision) { + char format_str[buffer_size]; + safe_sprintf(format_str, "{0:.%u", UINT_MAX); + increment(format_str + 4); + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + size_t size = std::strlen(format_str); + format_str[size] = '}'; + format_str[size + 1] = 0; + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + + safe_sprintf(format_str, "{0:.%u", INT_MAX + 1u); + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + safe_sprintf(format_str, "{0:.%u}", INT_MAX + 1u); + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + + EXPECT_THROW_MSG(fmt::format(+"{0:.", 0), format_error, + "missing precision specifier"); + EXPECT_THROW_MSG(fmt::format(+"{0:.}", 0), format_error, + "missing precision specifier"); + + EXPECT_THROW_MSG(fmt::format(+"{0:.2", 0), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42u), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42u), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42l), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42l), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42ul), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42ul), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42ll), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42ll), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42ull), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42ull), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:3.0}", 'x'), format_error, + "precision not allowed for this argument type"); + EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345)); + EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l)); + EXPECT_EQ("1.2e+56", fmt::format("{:.2}", 1.234e56)); + EXPECT_EQ("1.1", fmt::format("{0:.3}", 1.1)); + EXPECT_EQ("1e+00", fmt::format("{:.0e}", 1.0L)); + EXPECT_EQ(" 0.0e+00", fmt::format("{:9.1e}", 0.0)); + EXPECT_EQ( + fmt::format("{:.494}", 4.9406564584124654E-324), + "4.9406564584124654417656879286822137236505980261432476442558568250067550" + "727020875186529983636163599237979656469544571773092665671035593979639877" + "479601078187812630071319031140452784581716784898210368871863605699873072" + "305000638740915356498438731247339727316961514003171538539807412623856559" + "117102665855668676818703956031062493194527159149245532930545654440112748" + "012970999954193198940908041656332452475714786901472678015935523861155013" + "480352649347201937902681071074917033322268447533357208324319361e-324"); + + std::string outputs[] = { + "-0X1.41FE3FFE71C9E000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000P+127", + "-0XA.0FF1FFF38E4F0000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000P+124"}; + EXPECT_THAT(outputs, + testing::Contains(fmt::format("{:.838A}", -2.14001164E+38))); + + EXPECT_EQ("123.", fmt::format("{:#.0f}", 123.0)); + EXPECT_EQ("1.23", fmt::format("{:.02f}", 1.234)); + EXPECT_EQ("0.001", fmt::format("{:.1g}", 0.001)); + EXPECT_EQ("1019666400", fmt::format("{}", 1019666432.0f)); + EXPECT_EQ("1e+01", fmt::format("{:.0e}", 9.5)); + EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34)); + + EXPECT_THROW_MSG(fmt::format(+"{0:.2}", reinterpret_cast<void*>(0xcafe)), + format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", reinterpret_cast<void*>(0xcafe)), + format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{:.{}e}", 42.0, fmt::detail::max_value<int>()), + format_error, "number is too big"); + + EXPECT_EQ("st", fmt::format("{0:.2}", "str")); +} + +TEST(format_test, runtime_precision) { + char format_str[buffer_size]; + safe_sprintf(format_str, "{0:.{%u", UINT_MAX); + increment(format_str + 5); + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + size_t size = std::strlen(format_str); + format_str[size] = '}'; + format_str[size + 1] = 0; + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + format_str[size + 1] = '}'; + format_str[size + 2] = 0; + EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error, + "number is too big"); + + EXPECT_THROW_MSG(fmt::format(+"{0:.{", 0), format_error, + "invalid format string"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{}", 0), format_error, + "cannot switch from manual to automatic argument indexing"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{?}}", 0), format_error, + "invalid format string"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}", 0, 0), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0), format_error, + "argument not found"); + + EXPECT_THROW_MSG(fmt::format(+"{0:.{0:}}", 0), format_error, + "invalid format string"); + + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, -1), format_error, + "negative precision"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, (INT_MAX + 1u)), format_error, + "number is too big"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, -1l), format_error, + "negative precision"); + if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { + long value = INT_MAX; + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, (value + 1)), format_error, + "number is too big"); + } + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, (INT_MAX + 1ul)), format_error, + "number is too big"); + + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, '0'), format_error, + "precision is not integer"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, 0.0), format_error, + "precision is not integer"); + + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42u, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42u, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42l, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42l, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42ul, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42ul, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42ll, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42ll, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42ull, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42ull, 2), format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG(fmt::format(+"{0:3.{1}}", 'x', 0), format_error, + "precision not allowed for this argument type"); + EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2)); + EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l)); + + EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", reinterpret_cast<void*>(0xcafe), 2), + format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG( + fmt::format(+"{0:.{1}f}", reinterpret_cast<void*>(0xcafe), 2), + format_error, "precision not allowed for this argument type"); + + EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2)); +} + +TEST(format_test, format_bool) { + EXPECT_EQ("true", fmt::format("{}", true)); + EXPECT_EQ("false", fmt::format("{}", false)); + EXPECT_EQ("1", fmt::format("{:d}", true)); + EXPECT_EQ("true ", fmt::format("{:5}", true)); + EXPECT_EQ(L"true", fmt::format(L"{}", true)); + EXPECT_EQ("true", fmt::format("{:s}", true)); + EXPECT_EQ("false", fmt::format("{:s}", false)); + EXPECT_EQ("false ", fmt::format("{:6s}", false)); +} + +TEST(format_test, format_short) { + short s = 42; + EXPECT_EQ("42", fmt::format("{0:d}", s)); + unsigned short us = 42; + EXPECT_EQ("42", fmt::format("{0:d}", us)); +} + +template <typename T> +void check_unknown_types(const T& value, const char* types, const char*) { + char format_str[buffer_size]; + const char* special = ".0123456789L}"; + for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) { + char c = static_cast<char>(i); + if (std::strchr(types, c) || std::strchr(special, c) || !c) continue; + safe_sprintf(format_str, "{0:10%c}", c); + const char* message = "invalid type specifier"; + EXPECT_THROW_MSG(fmt::format(+format_str, value), format_error, message) + << format_str << " " << message; + } +} + +TEST(format_test, format_int) { + EXPECT_THROW_MSG(fmt::format(+"{0:v", 42), format_error, + "missing '}' in format string"); + check_unknown_types(42, "bBdoxXnLc", "integer"); + EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x'))); +} + +TEST(format_test, format_bin) { + EXPECT_EQ("0", fmt::format("{0:b}", 0)); + EXPECT_EQ("101010", fmt::format("{0:b}", 42)); + EXPECT_EQ("101010", fmt::format("{0:b}", 42u)); + EXPECT_EQ("-101010", fmt::format("{0:b}", -42)); + EXPECT_EQ("11000000111001", fmt::format("{0:b}", 12345)); + EXPECT_EQ("10010001101000101011001111000", fmt::format("{0:b}", 0x12345678)); + EXPECT_EQ("10010000101010111100110111101111", + fmt::format("{0:b}", 0x90ABCDEF)); + EXPECT_EQ("11111111111111111111111111111111", + fmt::format("{0:b}", max_value<uint32_t>())); +} + +#if FMT_USE_INT128 +constexpr auto int128_max = static_cast<__int128_t>( + (static_cast<__uint128_t>(1) << ((__SIZEOF_INT128__ * CHAR_BIT) - 1)) - 1); +constexpr auto int128_min = -int128_max - 1; + +constexpr auto uint128_max = ~static_cast<__uint128_t>(0); +#endif + +TEST(format_test, format_dec) { + EXPECT_EQ("0", fmt::format("{0}", 0)); + EXPECT_EQ("42", fmt::format("{0}", 42)); + EXPECT_EQ("42", fmt::format("{0:d}", 42)); + EXPECT_EQ("42", fmt::format("{0}", 42u)); + EXPECT_EQ("-42", fmt::format("{0}", -42)); + EXPECT_EQ("12345", fmt::format("{0}", 12345)); + EXPECT_EQ("67890", fmt::format("{0}", 67890)); +#if FMT_USE_INT128 + EXPECT_EQ("0", fmt::format("{0}", static_cast<__int128_t>(0))); + EXPECT_EQ("0", fmt::format("{0}", static_cast<__uint128_t>(0))); + EXPECT_EQ("9223372036854775808", + fmt::format("{0}", static_cast<__int128_t>(INT64_MAX) + 1)); + EXPECT_EQ("-9223372036854775809", + fmt::format("{0}", static_cast<__int128_t>(INT64_MIN) - 1)); + EXPECT_EQ("18446744073709551616", + fmt::format("{0}", static_cast<__int128_t>(UINT64_MAX) + 1)); + EXPECT_EQ("170141183460469231731687303715884105727", + fmt::format("{0}", int128_max)); + EXPECT_EQ("-170141183460469231731687303715884105728", + fmt::format("{0}", int128_min)); + EXPECT_EQ("340282366920938463463374607431768211455", + fmt::format("{0}", uint128_max)); +#endif + + char buffer[buffer_size]; + safe_sprintf(buffer, "%d", INT_MIN); + EXPECT_EQ(buffer, fmt::format("{0}", INT_MIN)); + safe_sprintf(buffer, "%d", INT_MAX); + EXPECT_EQ(buffer, fmt::format("{0}", INT_MAX)); + safe_sprintf(buffer, "%u", UINT_MAX); + EXPECT_EQ(buffer, fmt::format("{0}", UINT_MAX)); + safe_sprintf(buffer, "%ld", 0 - static_cast<unsigned long>(LONG_MIN)); + EXPECT_EQ(buffer, fmt::format("{0}", LONG_MIN)); + safe_sprintf(buffer, "%ld", LONG_MAX); + EXPECT_EQ(buffer, fmt::format("{0}", LONG_MAX)); + safe_sprintf(buffer, "%lu", ULONG_MAX); + EXPECT_EQ(buffer, fmt::format("{0}", ULONG_MAX)); +} + +TEST(format_test, format_hex) { + EXPECT_EQ("0", fmt::format("{0:x}", 0)); + EXPECT_EQ("42", fmt::format("{0:x}", 0x42)); + EXPECT_EQ("42", fmt::format("{0:x}", 0x42u)); + EXPECT_EQ("-42", fmt::format("{0:x}", -0x42)); + EXPECT_EQ("12345678", fmt::format("{0:x}", 0x12345678)); + EXPECT_EQ("90abcdef", fmt::format("{0:x}", 0x90abcdef)); + EXPECT_EQ("12345678", fmt::format("{0:X}", 0x12345678)); + EXPECT_EQ("90ABCDEF", fmt::format("{0:X}", 0x90ABCDEF)); +#if FMT_USE_INT128 + EXPECT_EQ("0", fmt::format("{0:x}", static_cast<__int128_t>(0))); + EXPECT_EQ("0", fmt::format("{0:x}", static_cast<__uint128_t>(0))); + EXPECT_EQ("8000000000000000", + fmt::format("{0:x}", static_cast<__int128_t>(INT64_MAX) + 1)); + EXPECT_EQ("-8000000000000001", + fmt::format("{0:x}", static_cast<__int128_t>(INT64_MIN) - 1)); + EXPECT_EQ("10000000000000000", + fmt::format("{0:x}", static_cast<__int128_t>(UINT64_MAX) + 1)); + EXPECT_EQ("7fffffffffffffffffffffffffffffff", + fmt::format("{0:x}", int128_max)); + EXPECT_EQ("-80000000000000000000000000000000", + fmt::format("{0:x}", int128_min)); + EXPECT_EQ("ffffffffffffffffffffffffffffffff", + fmt::format("{0:x}", uint128_max)); +#endif + + char buffer[buffer_size]; + safe_sprintf(buffer, "-%x", 0 - static_cast<unsigned>(INT_MIN)); + EXPECT_EQ(buffer, fmt::format("{0:x}", INT_MIN)); + safe_sprintf(buffer, "%x", INT_MAX); + EXPECT_EQ(buffer, fmt::format("{0:x}", INT_MAX)); + safe_sprintf(buffer, "%x", UINT_MAX); + EXPECT_EQ(buffer, fmt::format("{0:x}", UINT_MAX)); + safe_sprintf(buffer, "-%lx", 0 - static_cast<unsigned long>(LONG_MIN)); + EXPECT_EQ(buffer, fmt::format("{0:x}", LONG_MIN)); + safe_sprintf(buffer, "%lx", LONG_MAX); + EXPECT_EQ(buffer, fmt::format("{0:x}", LONG_MAX)); + safe_sprintf(buffer, "%lx", ULONG_MAX); + EXPECT_EQ(buffer, fmt::format("{0:x}", ULONG_MAX)); +} + +TEST(format_test, format_oct) { + EXPECT_EQ("0", fmt::format("{0:o}", 0)); + EXPECT_EQ("42", fmt::format("{0:o}", 042)); + EXPECT_EQ("42", fmt::format("{0:o}", 042u)); + EXPECT_EQ("-42", fmt::format("{0:o}", -042)); + EXPECT_EQ("12345670", fmt::format("{0:o}", 012345670)); +#if FMT_USE_INT128 + EXPECT_EQ("0", fmt::format("{0:o}", static_cast<__int128_t>(0))); + EXPECT_EQ("0", fmt::format("{0:o}", static_cast<__uint128_t>(0))); + EXPECT_EQ("1000000000000000000000", + fmt::format("{0:o}", static_cast<__int128_t>(INT64_MAX) + 1)); + EXPECT_EQ("-1000000000000000000001", + fmt::format("{0:o}", static_cast<__int128_t>(INT64_MIN) - 1)); + EXPECT_EQ("2000000000000000000000", + fmt::format("{0:o}", static_cast<__int128_t>(UINT64_MAX) + 1)); + EXPECT_EQ("1777777777777777777777777777777777777777777", + fmt::format("{0:o}", int128_max)); + EXPECT_EQ("-2000000000000000000000000000000000000000000", + fmt::format("{0:o}", int128_min)); + EXPECT_EQ("3777777777777777777777777777777777777777777", + fmt::format("{0:o}", uint128_max)); +#endif + + char buffer[buffer_size]; + safe_sprintf(buffer, "-%o", 0 - static_cast<unsigned>(INT_MIN)); + EXPECT_EQ(buffer, fmt::format("{0:o}", INT_MIN)); + safe_sprintf(buffer, "%o", INT_MAX); + EXPECT_EQ(buffer, fmt::format("{0:o}", INT_MAX)); + safe_sprintf(buffer, "%o", UINT_MAX); + EXPECT_EQ(buffer, fmt::format("{0:o}", UINT_MAX)); + safe_sprintf(buffer, "-%lo", 0 - static_cast<unsigned long>(LONG_MIN)); + EXPECT_EQ(buffer, fmt::format("{0:o}", LONG_MIN)); + safe_sprintf(buffer, "%lo", LONG_MAX); + EXPECT_EQ(buffer, fmt::format("{0:o}", LONG_MAX)); + safe_sprintf(buffer, "%lo", ULONG_MAX); + EXPECT_EQ(buffer, fmt::format("{0:o}", ULONG_MAX)); +} + +TEST(format_test, format_int_locale) { + EXPECT_EQ("1234", fmt::format("{:L}", 1234)); +} + +TEST(format_test, format_float) { + EXPECT_EQ("0", fmt::format("{}", 0.0f)); + EXPECT_EQ("392.500000", fmt::format("{0:f}", 392.5f)); +} + +TEST(format_test, format_double) { + EXPECT_EQ("0", fmt::format("{}", 0.0)); + check_unknown_types(1.2, "eEfFgGaAnL%", "double"); + EXPECT_EQ("0", fmt::format("{:}", 0.0)); + EXPECT_EQ("0.000000", fmt::format("{:f}", 0.0)); + EXPECT_EQ("0", fmt::format("{:g}", 0.0)); + EXPECT_EQ("392.65", fmt::format("{:}", 392.65)); + EXPECT_EQ("392.65", fmt::format("{:g}", 392.65)); + EXPECT_EQ("392.65", fmt::format("{:G}", 392.65)); + EXPECT_EQ("4.9014e+06", fmt::format("{:g}", 4.9014e6)); + EXPECT_EQ("392.650000", fmt::format("{:f}", 392.65)); + EXPECT_EQ("392.650000", fmt::format("{:F}", 392.65)); + EXPECT_EQ("42", fmt::format("{:L}", 42.0)); + char buffer[buffer_size]; + safe_sprintf(buffer, "%e", 392.65); + EXPECT_EQ(buffer, fmt::format("{0:e}", 392.65)); + safe_sprintf(buffer, "%E", 392.65); + EXPECT_EQ(buffer, fmt::format("{0:E}", 392.65)); + EXPECT_EQ("+0000392.6", fmt::format("{0:+010.4g}", 392.65)); + safe_sprintf(buffer, "%a", -42.0); + EXPECT_EQ(buffer, fmt::format("{:a}", -42.0)); + safe_sprintf(buffer, "%A", -42.0); + EXPECT_EQ(buffer, fmt::format("{:A}", -42.0)); + EXPECT_EQ("9223372036854775808.000000", + fmt::format("{:f}", 9223372036854775807.0)); +} + +TEST(format_test, precision_rounding) { + EXPECT_EQ("0", fmt::format("{:.0f}", 0.0)); + EXPECT_EQ("0", fmt::format("{:.0f}", 0.01)); + EXPECT_EQ("0", fmt::format("{:.0f}", 0.1)); + EXPECT_EQ("0.000", fmt::format("{:.3f}", 0.00049)); + EXPECT_EQ("0.001", fmt::format("{:.3f}", 0.0005)); + EXPECT_EQ("0.001", fmt::format("{:.3f}", 0.00149)); + EXPECT_EQ("0.002", fmt::format("{:.3f}", 0.0015)); + EXPECT_EQ("1.000", fmt::format("{:.3f}", 0.9999)); + EXPECT_EQ("0.00123", fmt::format("{:.3}", 0.00123)); + EXPECT_EQ("0.1", fmt::format("{:.16g}", 0.1)); + EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); + EXPECT_EQ("225.51575035152063720", + fmt::format("{:.17f}", 225.51575035152064)); + EXPECT_EQ("-761519619559038.2", fmt::format("{:.1f}", -761519619559038.2)); + EXPECT_EQ("1.9156918820264798e-56", + fmt::format("{}", 1.9156918820264798e-56)); + EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15)); + + // Trigger a rounding error in Grisu by a specially chosen number. + EXPECT_EQ("3788512123356.985352", fmt::format("{:f}", 3788512123356.985352)); +} + +TEST(format_test, prettify_float) { + EXPECT_EQ("0.0001", fmt::format("{}", 1e-4)); + EXPECT_EQ("1e-05", fmt::format("{}", 1e-5)); + EXPECT_EQ("1000000000000000", fmt::format("{}", 1e15)); + EXPECT_EQ("1e+16", fmt::format("{}", 1e16)); + EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5)); + EXPECT_EQ("10000000000", fmt::format("{}", 1e10)); + EXPECT_EQ("100000000000", fmt::format("{}", 1e11)); + EXPECT_EQ("12340000000", fmt::format("{}", 1234e7)); + EXPECT_EQ("12.34", fmt::format("{}", 1234e-2)); + EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6)); + EXPECT_EQ("0.1", fmt::format("{}", 0.1f)); + EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f))); + EXPECT_EQ("1.3563156e-19", fmt::format("{}", 1.35631564e-19f)); +} + +TEST(format_test, format_nan) { + double nan = std::numeric_limits<double>::quiet_NaN(); + EXPECT_EQ("nan", fmt::format("{}", nan)); + EXPECT_EQ("+nan", fmt::format("{:+}", nan)); + if (std::signbit(-nan)) + EXPECT_EQ("-nan", fmt::format("{}", -nan)); + else + fmt::print("Warning: compiler doesn't handle negative NaN correctly"); + EXPECT_EQ(" nan", fmt::format("{: }", nan)); + EXPECT_EQ("NAN", fmt::format("{:F}", nan)); + EXPECT_EQ("nan ", fmt::format("{:<7}", nan)); + EXPECT_EQ(" nan ", fmt::format("{:^7}", nan)); + EXPECT_EQ(" nan", fmt::format("{:>7}", nan)); +} + +TEST(format_test, format_infinity) { + double inf = std::numeric_limits<double>::infinity(); + EXPECT_EQ("inf", fmt::format("{}", inf)); + EXPECT_EQ("+inf", fmt::format("{:+}", inf)); + EXPECT_EQ("-inf", fmt::format("{}", -inf)); + EXPECT_EQ(" inf", fmt::format("{: }", inf)); + EXPECT_EQ("INF", fmt::format("{:F}", inf)); + EXPECT_EQ("inf ", fmt::format("{:<7}", inf)); + EXPECT_EQ(" inf ", fmt::format("{:^7}", inf)); + EXPECT_EQ(" inf", fmt::format("{:>7}", inf)); +} + +TEST(format_test, format_long_double) { + EXPECT_EQ("0", fmt::format("{0:}", 0.0l)); + EXPECT_EQ("0.000000", fmt::format("{0:f}", 0.0l)); + EXPECT_EQ("392.65", fmt::format("{0:}", 392.65l)); + EXPECT_EQ("392.65", fmt::format("{0:g}", 392.65l)); + EXPECT_EQ("392.65", fmt::format("{0:G}", 392.65l)); + EXPECT_EQ("392.650000", fmt::format("{0:f}", 392.65l)); + EXPECT_EQ("392.650000", fmt::format("{0:F}", 392.65l)); + char buffer[buffer_size]; + safe_sprintf(buffer, "%Le", 392.65l); + EXPECT_EQ(buffer, fmt::format("{0:e}", 392.65l)); + EXPECT_EQ("+0000392.6", fmt::format("{0:+010.4g}", 392.64l)); + safe_sprintf(buffer, "%La", 3.31l); + EXPECT_EQ(buffer, fmt::format("{:a}", 3.31l)); +} + +TEST(format_test, format_char) { + const char types[] = "cbBdoxX"; + check_unknown_types('a', types, "char"); + EXPECT_EQ("a", fmt::format("{0}", 'a')); + EXPECT_EQ("z", fmt::format("{0:c}", 'z')); + EXPECT_EQ(L"a", fmt::format(L"{0}", 'a')); + int n = 'x'; + for (const char* type = types + 1; *type; ++type) { + std::string format_str = fmt::format("{{:{}}}", *type); + EXPECT_EQ(fmt::format(format_str, n), fmt::format(format_str, 'x')) + << format_str; + } + EXPECT_EQ(fmt::format("{:02X}", n), fmt::format("{:02X}", 'x')); +} + +TEST(format_test, format_volatile_char) { + volatile char c = 'x'; + EXPECT_EQ("x", fmt::format("{}", c)); +} + +TEST(format_test, format_unsigned_char) { + EXPECT_EQ("42", fmt::format("{}", static_cast<unsigned char>(42))); + EXPECT_EQ("42", fmt::format("{}", static_cast<uint8_t>(42))); +} + +TEST(format_test, format_wchar) { + EXPECT_EQ(L"a", fmt::format(L"{0}", L'a')); + // This shouldn't compile: + // format("{}", L'a'); +} + +TEST(format_test, format_cstring) { + check_unknown_types("test", "sp", "string"); + EXPECT_EQ("test", fmt::format("{0}", "test")); + EXPECT_EQ("test", fmt::format("{0:s}", "test")); + char nonconst[] = "nonconst"; + EXPECT_EQ("nonconst", fmt::format("{0}", nonconst)); + EXPECT_THROW_MSG(fmt::format(+"{0}", static_cast<const char*>(nullptr)), + format_error, "string pointer is null"); +} + +TEST(format_test, format_schar_string) { + signed char str[] = "test"; + EXPECT_EQ("test", fmt::format("{0:s}", str)); + const signed char* const_str = str; + EXPECT_EQ("test", fmt::format("{0:s}", const_str)); +} + +TEST(format_test, format_uchar_string) { + unsigned char str[] = "test"; + EXPECT_EQ("test", fmt::format("{0:s}", str)); + const unsigned char* const_str = str; + EXPECT_EQ("test", fmt::format("{0:s}", const_str)); + unsigned char* ptr = str; + EXPECT_EQ("test", fmt::format("{0:s}", ptr)); +} + +void function_pointer_test(int, double, std::string) {} + +TEST(format_test, format_pointer) { + check_unknown_types(reinterpret_cast<void*>(0x1234), "p", "pointer"); + EXPECT_EQ("0x0", fmt::format("{0}", static_cast<void*>(nullptr))); + EXPECT_EQ("0x1234", fmt::format("{0}", reinterpret_cast<void*>(0x1234))); + EXPECT_EQ("0x1234", fmt::format("{0:p}", reinterpret_cast<void*>(0x1234))); + EXPECT_EQ("0x" + std::string(sizeof(void*) * CHAR_BIT / 4, 'f'), + fmt::format("{0}", reinterpret_cast<void*>(~uintptr_t()))); + EXPECT_EQ("0x1234", + fmt::format("{}", fmt::ptr(reinterpret_cast<int*>(0x1234)))); + std::unique_ptr<int> up(new int(1)); + EXPECT_EQ(fmt::format("{}", fmt::ptr(up.get())), + fmt::format("{}", fmt::ptr(up))); + std::shared_ptr<int> sp(new int(1)); + EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())), + fmt::format("{}", fmt::ptr(sp))); + EXPECT_EQ(fmt::format("{}", fmt::detail::bit_cast<const void*>( + &function_pointer_test)), + fmt::format("{}", fmt::ptr(function_pointer_test))); + EXPECT_EQ("0x0", fmt::format("{}", nullptr)); +} + +TEST(format_test, format_string) { + EXPECT_EQ("test", fmt::format("{0}", std::string("test"))); +} + +TEST(format_test, format_string_view) { + EXPECT_EQ("test", fmt::format("{}", string_view("test"))); + EXPECT_EQ("", fmt::format("{}", string_view())); +} + +#ifdef FMT_USE_STRING_VIEW +struct string_viewable {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<string_viewable> : formatter<std::string_view> { + auto format(string_viewable, format_context& ctx) -> decltype(ctx.out()) { + return formatter<std::string_view>::format("foo", ctx); + } +}; +FMT_END_NAMESPACE + +TEST(format_test, format_std_string_view) { + EXPECT_EQ("test", fmt::format("{}", std::string_view("test"))); + EXPECT_EQ("foo", fmt::format("{}", string_viewable())); +} + +struct explicitly_convertible_to_std_string_view { + explicit operator std::string_view() const { return "foo"; } +}; + +template <> +struct fmt::formatter<explicitly_convertible_to_std_string_view> + : formatter<std::string_view> { + auto format(explicitly_convertible_to_std_string_view v, format_context& ctx) + -> decltype(ctx.out()) { + return format_to(ctx.out(), "'{}'", std::string_view(v)); + } +}; + +TEST(format_test, format_explicitly_convertible_to_std_string_view) { + EXPECT_EQ("'foo'", + fmt::format("{}", explicitly_convertible_to_std_string_view())); +} +#endif + +// std::is_constructible is broken in MSVC until version 2015. +#if !FMT_MSC_VER || FMT_MSC_VER >= 1900 +struct explicitly_convertible_to_wstring_view { + explicit operator fmt::wstring_view() const { return L"foo"; } +}; + +TEST(format_test, format_explicitly_convertible_to_wstring_view) { + EXPECT_EQ(L"foo", + fmt::format(L"{}", explicitly_convertible_to_wstring_view())); +} +#endif + +namespace fake_qt { +class QString { + public: + QString(const wchar_t* s) : s_(s) {} + const wchar_t* utf16() const FMT_NOEXCEPT { return s_.data(); } + int size() const FMT_NOEXCEPT { return static_cast<int>(s_.size()); } + + private: + std::wstring s_; +}; + +fmt::basic_string_view<wchar_t> to_string_view(const QString& s) FMT_NOEXCEPT { + return {s.utf16(), static_cast<size_t>(s.size())}; +} +} // namespace fake_qt + +TEST(format_test, format_foreign_strings) { + using fake_qt::QString; + EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42"); + EXPECT_EQ(fmt::format(QString(L"{}"), QString(L"42")), L"42"); +} + +class Answer {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<date> { + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + auto it = ctx.begin(); + if (it != ctx.end() && *it == 'd') ++it; + return it; + } + + auto format(const date& d, format_context& ctx) -> decltype(ctx.out()) { + format_to(ctx.out(), "{}-{}-{}", d.year(), d.month(), d.day()); + return ctx.out(); + } +}; + +template <> struct formatter<Answer> : formatter<int> { + template <typename FormatContext> + auto format(Answer, FormatContext& ctx) -> decltype(ctx.out()) { + return formatter<int>::format(42, ctx); + } +}; +FMT_END_NAMESPACE + +TEST(format_test, format_custom) { + EXPECT_THROW_MSG(fmt::format(+"{:s}", date(2012, 12, 9)), format_error, + "unknown format specifier"); + EXPECT_EQ("42", fmt::format("{0}", Answer())); + EXPECT_EQ("0042", fmt::format("{:04}", Answer())); +} + +TEST(format_test, format_to_custom) { + char buf[10] = {}; + auto end = + &*fmt::format_to(fmt::detail::make_checked(buf, 10), "{}", Answer()); + EXPECT_EQ(end, buf + 2); + EXPECT_STREQ(buf, "42"); +} + +TEST(format_test, wide_format_string) { + EXPECT_EQ(L"42", fmt::format(L"{}", 42)); + EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2)); + EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc")); + EXPECT_EQ(L"z", fmt::format(L"{}", L'z')); + EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error); +} + +TEST(format_test, format_string_from_speed_test) { + EXPECT_EQ("1.2340000000:0042:+3.13:str:0x3e8:X:%", + fmt::format("{0:0.10f}:{1:04}:{2:+g}:{3}:{4}:{5}:%", 1.234, 42, + 3.13, "str", reinterpret_cast<void*>(1000), 'X')); +} + +TEST(format_test, format_examples) { + std::string message = fmt::format("The answer is {}", 42); + EXPECT_EQ("The answer is 42", message); + + EXPECT_EQ("42", fmt::format("{}", 42)); + EXPECT_EQ("42", fmt::format(std::string("{}"), 42)); + + memory_buffer out; + format_to(out, "The answer is {}.", 42); + EXPECT_EQ("The answer is 42.", to_string(out)); + + const char* filename = "nonexistent"; + FILE* ftest = safe_fopen(filename, "r"); + if (ftest) fclose(ftest); + int error_code = errno; + EXPECT_TRUE(ftest == nullptr); + EXPECT_SYSTEM_ERROR( + { + FILE* f = safe_fopen(filename, "r"); + if (!f) + throw fmt::system_error(errno, "Cannot open file '{}'", filename); + fclose(f); + }, + error_code, "Cannot open file 'nonexistent'"); + + EXPECT_EQ("First, thou shalt count to three", + fmt::format("First, thou shalt count to {0}", "three")); + EXPECT_EQ("Bring me a shrubbery", fmt::format("Bring me a {}", "shrubbery")); + EXPECT_EQ("From 1 to 3", fmt::format("From {} to {}", 1, 3)); + + char buffer[buffer_size]; + safe_sprintf(buffer, "%03.2f", -1.2); + EXPECT_EQ(buffer, fmt::format("{:03.2f}", -1.2)); + + EXPECT_EQ("a, b, c", fmt::format("{0}, {1}, {2}", 'a', 'b', 'c')); + EXPECT_EQ("a, b, c", fmt::format("{}, {}, {}", 'a', 'b', 'c')); + EXPECT_EQ("c, b, a", fmt::format("{2}, {1}, {0}", 'a', 'b', 'c')); + EXPECT_EQ("abracadabra", fmt::format("{0}{1}{0}", "abra", "cad")); + + EXPECT_EQ("left aligned ", + fmt::format("{:<30}", "left aligned")); + EXPECT_EQ(" right aligned", + fmt::format("{:>30}", "right aligned")); + EXPECT_EQ(" centered ", + fmt::format("{:^30}", "centered")); + EXPECT_EQ("***********centered***********", + fmt::format("{:*^30}", "centered")); + + EXPECT_EQ("+3.140000; -3.140000", fmt::format("{:+f}; {:+f}", 3.14, -3.14)); + EXPECT_EQ(" 3.140000; -3.140000", fmt::format("{: f}; {: f}", 3.14, -3.14)); + EXPECT_EQ("3.140000; -3.140000", fmt::format("{:-f}; {:-f}", 3.14, -3.14)); + + EXPECT_EQ("int: 42; hex: 2a; oct: 52", + fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}", 42)); + EXPECT_EQ("int: 42; hex: 0x2a; oct: 052", + fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}", 42)); + + EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42)); + EXPECT_THROW_MSG(fmt::format(+"The answer is {:d}", "forty-two"), + format_error, "invalid type specifier"); + + EXPECT_EQ(L"Cyrillic letter \x42e", + fmt::format(L"Cyrillic letter {}", L'\x42e')); + + EXPECT_WRITE( + stdout, fmt::print("{}", std::numeric_limits<double>::infinity()), "inf"); +} + +TEST(format_test, print) { + EXPECT_WRITE(stdout, fmt::print("Don't {}!", "panic"), "Don't panic!"); + EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"), + "Don't panic!"); + // Check that the wide print overload compiles. + if (fmt::detail::const_check(false)) fmt::print(L"test"); +} + +TEST(format_test, variadic) { + EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1)); + EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1)); +} + +TEST(format_test, dynamic) { + using ctx = fmt::format_context; + auto args = std::vector<fmt::basic_format_arg<ctx>>(); + args.emplace_back(fmt::detail::make_arg<ctx>(42)); + args.emplace_back(fmt::detail::make_arg<ctx>("abc1")); + args.emplace_back(fmt::detail::make_arg<ctx>(1.5f)); + + std::string result = fmt::vformat( + "{} and {} and {}", + fmt::format_args(args.data(), static_cast<int>(args.size()))); + + EXPECT_EQ("42 and abc1 and 1.5", result); +} + +TEST(format_test, bytes) { + auto s = fmt::format("{:10}", fmt::bytes("ёжик")); + EXPECT_EQ("ёжик ", s); + EXPECT_EQ(10, s.size()); +} + +enum test_enum { foo, bar }; + +TEST(format_test, join) { + using fmt::join; + int v1[3] = {1, 2, 3}; + auto v2 = std::vector<float>(); + v2.push_back(1.2f); + v2.push_back(3.4f); + void* v3[2] = {&v1[0], &v1[1]}; + + EXPECT_EQ("(1, 2, 3)", fmt::format("({})", join(v1, v1 + 3, ", "))); + EXPECT_EQ("(1)", fmt::format("({})", join(v1, v1 + 1, ", "))); + EXPECT_EQ("()", fmt::format("({})", join(v1, v1, ", "))); + EXPECT_EQ("(001, 002, 003)", fmt::format("({:03})", join(v1, v1 + 3, ", "))); + EXPECT_EQ("(+01.20, +03.40)", + fmt::format("({:+06.2f})", join(v2.begin(), v2.end(), ", "))); + + EXPECT_EQ(L"(1, 2, 3)", fmt::format(L"({})", join(v1, v1 + 3, L", "))); + EXPECT_EQ("1, 2, 3", fmt::format("{0:{1}}", join(v1, v1 + 3, ", "), 1)); + + EXPECT_EQ(fmt::format("{}, {}", v3[0], v3[1]), + fmt::format("{}", join(v3, v3 + 2, ", "))); + + EXPECT_EQ("(1, 2, 3)", fmt::format("({})", join(v1, ", "))); + EXPECT_EQ("(+01.20, +03.40)", fmt::format("({:+06.2f})", join(v2, ", "))); + + auto v4 = std::vector<test_enum>{foo, bar, foo}; + EXPECT_EQ("0 1 0", fmt::format("{}", join(v4, " "))); +} + +#ifdef __cpp_lib_byte +TEST(format_test, join_bytes) { + auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)}; + EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, ", "))); +} +#endif + +std::string vformat_message(int id, const char* format, fmt::format_args args) { + fmt::memory_buffer buffer; + format_to(buffer, "[{}] ", id); + vformat_to(buffer, format, args); + return to_string(buffer); +} + +template <typename... Args> +std::string format_message(int id, const char* format, const Args&... args) { + auto va = fmt::make_format_args(args...); + return vformat_message(id, format, va); +} + +TEST(format_test, format_message_example) { + EXPECT_EQ("[42] something happened", + format_message(42, "{} happened", "something")); +} + +template <typename... Args> +void print_error(const char* file, int line, const char* format, + const Args&... args) { + fmt::print("{}: {}: ", file, line); + fmt::print(format, args...); +} + +TEST(format_test, unpacked_args) { + EXPECT_EQ("0123456789abcdefg", + fmt::format("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g')); +} + +struct string_like {}; +fmt::string_view to_string_view(string_like) { return "foo"; } + +constexpr char with_null[3] = {'{', '}', '\0'}; +constexpr char no_null[2] = {'{', '}'}; +static FMT_CONSTEXPR_DECL const char static_with_null[3] = {'{', '}', '\0'}; +static FMT_CONSTEXPR_DECL const wchar_t static_with_null_wide[3] = {'{', '}', + '\0'}; +static FMT_CONSTEXPR_DECL const char static_no_null[2] = {'{', '}'}; +static FMT_CONSTEXPR_DECL const wchar_t static_no_null_wide[2] = {'{', '}'}; + +TEST(format_test, compile_time_string) { + EXPECT_EQ("foo", fmt::format(FMT_STRING("foo"))); + EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42)); + EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42)); + EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like())); + + (void)static_with_null; + (void)static_with_null_wide; + (void)static_no_null; + (void)static_no_null_wide; +#ifndef _MSC_VER + EXPECT_EQ("42", fmt::format(FMT_STRING(static_with_null), 42)); + EXPECT_EQ(L"42", fmt::format(FMT_STRING(static_with_null_wide), 42)); + EXPECT_EQ("42", fmt::format(FMT_STRING(static_no_null), 42)); + EXPECT_EQ(L"42", fmt::format(FMT_STRING(static_no_null_wide), 42)); +#endif + + (void)with_null; + (void)no_null; +#if __cplusplus >= 201703L + EXPECT_EQ("42", fmt::format(FMT_STRING(with_null), 42)); + EXPECT_EQ("42", fmt::format(FMT_STRING(no_null), 42)); +#endif +#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L + EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42)); + EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42)); +#endif +} + +TEST(format_test, custom_format_compile_time_string) { + EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), Answer())); + auto answer = Answer(); + EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), answer)); + char buf[10] = {}; + fmt::format_to(buf, FMT_STRING("{}"), answer); + const Answer const_answer = Answer(); + EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), const_answer)); +} + +#if FMT_USE_USER_DEFINED_LITERALS +// Passing user-defined literals directly to EXPECT_EQ causes problems +// with macro argument stringification (#) on some versions of GCC. +// Workaround: Assing the UDL result to a variable before the macro. + +using namespace fmt::literals; + +TEST(format_test, format_udl) { + auto udl_format = "{}c{}"_format("ab", 1); + EXPECT_EQ(fmt::format("{}c{}", "ab", 1), udl_format); + auto udl_format_w = L"{}c{}"_format(L"ab", 1); + EXPECT_EQ(fmt::format(L"{}c{}", L"ab", 1), udl_format_w); +} + +TEST(format_test, named_arg_udl) { + auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra", + "second"_a = "cad", "third"_a = 99); + EXPECT_EQ( + fmt::format("{first}{second}{first}{third}", fmt::arg("first", "abra"), + fmt::arg("second", "cad"), fmt::arg("third", 99)), + udl_a); + auto udl_a_w = + fmt::format(L"{first}{second}{first}{third}", L"first"_a = L"abra", + L"second"_a = L"cad", L"third"_a = 99); + EXPECT_EQ( + fmt::format(L"{first}{second}{first}{third}", fmt::arg(L"first", L"abra"), + fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)), + udl_a_w); +} + +TEST(format_test, udl_template) { + EXPECT_EQ("foo", "foo"_format()); + EXPECT_EQ(" 42", "{0:10}"_format(42)); +} + +TEST(format_test, udl_pass_user_defined_object_as_lvalue) { + EXPECT_EQ("2015-10-21", "{}"_format(date(2015, 10, 21))); +} +#endif // FMT_USE_USER_DEFINED_LITERALS + +TEST(format_test, enum) { EXPECT_EQ("0", fmt::format("{}", foo)); } + +TEST(format_test, formatter_not_specialized) { + static_assert(!fmt::has_formatter<fmt::formatter<test_enum>, + fmt::format_context>::value, + ""); +} + +#if FMT_HAS_FEATURE(cxx_strong_enums) +enum big_enum : unsigned long long { big_enum_value = 5000000000ULL }; + +TEST(format_test, strong_enum) { + EXPECT_EQ("5000000000", fmt::format("{}", big_enum_value)); +} +#endif + +TEST(format_test, non_null_terminated_format_string) { + EXPECT_EQ("42", fmt::format(string_view("{}foo", 2), 42)); +} + +struct variant { + enum { int_type, string_type } type; + explicit variant(int) : type(int_type) {} + explicit variant(const char*) : type(string_type) {} +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<variant> : dynamic_formatter<> { + auto format(variant value, format_context& ctx) -> decltype(ctx.out()) { + if (value.type == variant::int_type) + return dynamic_formatter<>::format(42, ctx); + return dynamic_formatter<>::format("foo", ctx); + } +}; +FMT_END_NAMESPACE + +TEST(format_test, dynamic_formatter) { + auto num = variant(42); + auto str = variant("foo"); + EXPECT_EQ("42", fmt::format("{:d}", num)); + EXPECT_EQ("foo", fmt::format("{:s}", str)); + EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4)); + EXPECT_THROW_MSG(fmt::format(+"{0:{}}", num), format_error, + "cannot switch from manual to automatic argument indexing"); + EXPECT_THROW_MSG(fmt::format(+"{:{0}}", num), format_error, + "cannot switch from automatic to manual argument indexing"); + EXPECT_THROW_MSG(fmt::format(+"{:+}", str), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{:-}", str), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{: }", str), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{:#}", str), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{:0}", str), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{:.2}", num), format_error, + "precision not allowed for this argument type"); +} + +namespace adl_test { +namespace fmt { +namespace detail { +struct foo {}; +template <typename, typename OutputIt> void write(OutputIt, foo) = delete; +} // namespace detail +} // namespace fmt +} // namespace adl_test + +FMT_BEGIN_NAMESPACE +template <> +struct formatter<adl_test::fmt::detail::foo> : formatter<std::string> { + template <typename FormatContext> + auto format(adl_test::fmt::detail::foo, FormatContext& ctx) + -> decltype(ctx.out()) { + return formatter<std::string>::format("foo", ctx); + } +}; +FMT_END_NAMESPACE + +TEST(format_test, to_string) { + EXPECT_EQ("42", fmt::to_string(42)); + EXPECT_EQ("0x1234", fmt::to_string(reinterpret_cast<void*>(0x1234))); + EXPECT_EQ("foo", fmt::to_string(adl_test::fmt::detail::foo())); + + enum test_enum2 : unsigned char { test_value }; + EXPECT_EQ("0", fmt::to_string(test_value)); +} + +TEST(format_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); } + +TEST(format_test, output_iterators) { + std::list<char> out; + fmt::format_to(std::back_inserter(out), "{}", 42); + EXPECT_EQ("42", std::string(out.begin(), out.end())); + std::stringstream s; + fmt::format_to(std::ostream_iterator<char>(s), "{}", 42); + EXPECT_EQ("42", s.str()); +} + +TEST(format_test, formatted_size) { + EXPECT_EQ(2u, fmt::formatted_size("{}", 42)); +} + +TEST(format_test, format_to_no_args) { + std::string s; + fmt::format_to(std::back_inserter(s), "test"); + EXPECT_EQ("test", s); +} + +TEST(format_test, format_to) { + std::string s; + fmt::format_to(std::back_inserter(s), "part{0}", 1); + EXPECT_EQ("part1", s); + fmt::format_to(std::back_inserter(s), "part{0}", 2); + EXPECT_EQ("part1part2", s); +} + +TEST(format_test, format_to_wide) { + std::vector<wchar_t> buf; + fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0'); + EXPECT_STREQ(buf.data(), L"42"); +} + +TEST(format_test, format_to_memory_buffer) { + fmt::basic_memory_buffer<char, 100> buffer; + fmt::format_to(buffer, "{}", "foo"); + EXPECT_EQ("foo", to_string(buffer)); + fmt::wmemory_buffer wbuffer; + fmt::format_to(wbuffer, L"{}", L"foo"); + EXPECT_EQ(L"foo", to_string(wbuffer)); +} + +TEST(format_test, format_to_vector) { + std::vector<char> v; + fmt::format_to(std::back_inserter(v), "{}", "foo"); + EXPECT_EQ(string_view(v.data(), v.size()), "foo"); +} + +struct nongrowing_container { + using value_type = char; + void push_back(char) { throw std::runtime_error("can't take it any more"); } +}; + +TEST(format_test, format_to_propagates_exceptions) { + auto c = nongrowing_container(); + EXPECT_THROW(fmt::format_to(std::back_inserter(c), "{}", 42), + std::runtime_error); +} + +TEST(format_test, format_to_n) { + char buffer[4]; + buffer[3] = 'x'; + auto result = fmt::format_to_n(buffer, 3, "{}", 12345); + EXPECT_EQ(5u, result.size); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ("123x", fmt::string_view(buffer, 4)); + + result = fmt::format_to_n(buffer, 3, "{:s}", "foobar"); + EXPECT_EQ(6u, result.size); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ("foox", fmt::string_view(buffer, 4)); + + buffer[0] = 'x'; + buffer[1] = 'x'; + buffer[2] = 'x'; + result = fmt::format_to_n(buffer, 3, "{}", 'A'); + EXPECT_EQ(1u, result.size); + EXPECT_EQ(buffer + 1, result.out); + EXPECT_EQ("Axxx", fmt::string_view(buffer, 4)); + + result = fmt::format_to_n(buffer, 3, "{}{} ", 'B', 'C'); + EXPECT_EQ(3u, result.size); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ("BC x", fmt::string_view(buffer, 4)); + + result = fmt::format_to_n(buffer, 4, "{}", "ABCDE"); + EXPECT_EQ(5u, result.size); + EXPECT_EQ("ABCD", fmt::string_view(buffer, 4)); + + buffer[3] = 'x'; + result = fmt::format_to_n(buffer, 3, "{}", std::string(1000, '*')); + EXPECT_EQ(1000u, result.size); + EXPECT_EQ("***x", fmt::string_view(buffer, 4)); +} + +TEST(format_test, wide_format_to_n) { + wchar_t buffer[4]; + buffer[3] = L'x'; + auto result = fmt::format_to_n(buffer, 3, L"{}", 12345); + EXPECT_EQ(5u, result.size); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ(L"123x", fmt::wstring_view(buffer, 4)); + buffer[0] = L'x'; + buffer[1] = L'x'; + buffer[2] = L'x'; + result = fmt::format_to_n(buffer, 3, L"{}", L'A'); + EXPECT_EQ(1u, result.size); + EXPECT_EQ(buffer + 1, result.out); + EXPECT_EQ(L"Axxx", fmt::wstring_view(buffer, 4)); + result = fmt::format_to_n(buffer, 3, L"{}{} ", L'B', L'C'); + EXPECT_EQ(3u, result.size); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4)); +} + +struct test_output_iterator { + char* data; + + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = void; + using pointer = void; + using reference = void; + + test_output_iterator& operator++() { + ++data; + return *this; + } + test_output_iterator operator++(int) { + auto tmp = *this; + ++data; + return tmp; + } + char& operator*() { return *data; } +}; + +TEST(format_test, format_to_n_output_iterator) { + char buf[10] = {}; + fmt::format_to_n(test_output_iterator{buf}, 10, "{}", 42); + EXPECT_STREQ(buf, "42"); +} + +#if FMT_USE_CONSTEXPR +enum class arg_id_result { none, empty, index, name, error }; +struct test_arg_id_handler { + arg_id_result res = arg_id_result::none; + int index = 0; + string_view name; + + FMT_CONSTEXPR void operator()() { res = arg_id_result::empty; } + + FMT_CONSTEXPR void operator()(int i) { + res = arg_id_result::index; + index = i; + } + + FMT_CONSTEXPR void operator()(string_view n) { + res = arg_id_result::name; + name = n; + } + + FMT_CONSTEXPR void on_error(const char*) { res = arg_id_result::error; } +}; + +template <size_t N> +FMT_CONSTEXPR test_arg_id_handler parse_arg_id(const char (&s)[N]) { + test_arg_id_handler h; + fmt::detail::parse_arg_id(s, s + N, h); + return h; +} + +TEST(format_test, constexpr_parse_arg_id) { + static_assert(parse_arg_id(":").res == arg_id_result::empty, ""); + static_assert(parse_arg_id("}").res == arg_id_result::empty, ""); + static_assert(parse_arg_id("42:").res == arg_id_result::index, ""); + static_assert(parse_arg_id("42:").index == 42, ""); + static_assert(parse_arg_id("foo:").res == arg_id_result::name, ""); + static_assert(parse_arg_id("foo:").name.size() == 3, ""); + static_assert(parse_arg_id("!").res == arg_id_result::error, ""); +} + +struct test_format_specs_handler { + enum result { none, plus, minus, space, hash, zero, loc, error }; + result res = none; + + fmt::align_t alignment = fmt::align::none; + char fill = 0; + int width = 0; + fmt::detail::arg_ref<char> width_ref; + int precision = 0; + fmt::detail::arg_ref<char> precision_ref; + char type = 0; + + // Workaround for MSVC2017 bug that results in "expression did not evaluate + // to a constant" with compiler-generated copy ctor. + FMT_CONSTEXPR test_format_specs_handler() {} + FMT_CONSTEXPR test_format_specs_handler( + const test_format_specs_handler& other) + : res(other.res), + alignment(other.alignment), + fill(other.fill), + width(other.width), + width_ref(other.width_ref), + precision(other.precision), + precision_ref(other.precision_ref), + type(other.type) {} + + FMT_CONSTEXPR void on_align(fmt::align_t a) { alignment = a; } + FMT_CONSTEXPR void on_fill(fmt::string_view f) { fill = f[0]; } + FMT_CONSTEXPR void on_plus() { res = plus; } + FMT_CONSTEXPR void on_minus() { res = minus; } + FMT_CONSTEXPR void on_space() { res = space; } + FMT_CONSTEXPR void on_hash() { res = hash; } + FMT_CONSTEXPR void on_zero() { res = zero; } + FMT_CONSTEXPR void on_localized() { res = loc; } + + FMT_CONSTEXPR void on_width(int w) { width = w; } + FMT_CONSTEXPR void on_dynamic_width(fmt::detail::auto_id) {} + FMT_CONSTEXPR void on_dynamic_width(int index) { width_ref = index; } + FMT_CONSTEXPR void on_dynamic_width(string_view) {} + + FMT_CONSTEXPR void on_precision(int p) { precision = p; } + FMT_CONSTEXPR void on_dynamic_precision(fmt::detail::auto_id) {} + FMT_CONSTEXPR void on_dynamic_precision(int index) { precision_ref = index; } + FMT_CONSTEXPR void on_dynamic_precision(string_view) {} + + FMT_CONSTEXPR void end_precision() {} + FMT_CONSTEXPR void on_type(char t) { type = t; } + FMT_CONSTEXPR void on_error(const char*) { res = error; } +}; + +template <size_t N> +FMT_CONSTEXPR test_format_specs_handler parse_test_specs(const char (&s)[N]) { + auto h = test_format_specs_handler(); + fmt::detail::parse_format_specs(s, s + N, h); + return h; +} + +TEST(format_test, constexpr_parse_format_specs) { + using handler = test_format_specs_handler; + static_assert(parse_test_specs("<").alignment == fmt::align::left, ""); + static_assert(parse_test_specs("*^").fill == '*', ""); + static_assert(parse_test_specs("+").res == handler::plus, ""); + static_assert(parse_test_specs("-").res == handler::minus, ""); + static_assert(parse_test_specs(" ").res == handler::space, ""); + static_assert(parse_test_specs("#").res == handler::hash, ""); + static_assert(parse_test_specs("0").res == handler::zero, ""); + static_assert(parse_test_specs("L").res == handler::loc, ""); + static_assert(parse_test_specs("42").width == 42, ""); + static_assert(parse_test_specs("{42}").width_ref.val.index == 42, ""); + static_assert(parse_test_specs(".42").precision == 42, ""); + static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, ""); + static_assert(parse_test_specs("d").type == 'd', ""); + static_assert(parse_test_specs("{<").res == handler::error, ""); +} + +struct test_parse_context { + typedef char char_type; + + FMT_CONSTEXPR int next_arg_id() { return 11; } + template <typename Id> FMT_CONSTEXPR void check_arg_id(Id) {} + + FMT_CONSTEXPR const char* begin() { return nullptr; } + FMT_CONSTEXPR const char* end() { return nullptr; } + + void on_error(const char*) {} +}; + +struct test_context { + using char_type = char; + using format_arg = fmt::basic_format_arg<test_context>; + using parse_context_type = fmt::format_parse_context; + + template <typename T> struct formatter_type { + using type = fmt::formatter<T, char_type>; + }; + + template <typename Id> + FMT_CONSTEXPR fmt::basic_format_arg<test_context> arg(Id id) { + return fmt::detail::make_arg<test_context>(id); + } + + void on_error(const char*) {} + + FMT_CONSTEXPR test_context error_handler() { return *this; } +}; + +template <size_t N> +FMT_CONSTEXPR fmt::format_specs parse_specs(const char (&s)[N]) { + auto specs = fmt::format_specs(); + auto parse_ctx = test_parse_context(); + auto ctx = test_context(); + fmt::detail::specs_handler<test_parse_context, test_context> h( + specs, parse_ctx, ctx); + parse_format_specs(s, s + N, h); + return specs; +} + +TEST(format_test, constexpr_specs_handler) { + static_assert(parse_specs("<").align == fmt::align::left, ""); + static_assert(parse_specs("*^").fill[0] == '*', ""); + static_assert(parse_specs("+").sign == fmt::sign::plus, ""); + static_assert(parse_specs("-").sign == fmt::sign::minus, ""); + static_assert(parse_specs(" ").sign == fmt::sign::space, ""); + static_assert(parse_specs("#").alt, ""); + static_assert(parse_specs("0").align == fmt::align::numeric, ""); + static_assert(parse_specs("42").width == 42, ""); + static_assert(parse_specs("{}").width == 11, ""); + static_assert(parse_specs("{22}").width == 22, ""); + static_assert(parse_specs(".42").precision == 42, ""); + static_assert(parse_specs(".{}").precision == 11, ""); + static_assert(parse_specs(".{22}").precision == 22, ""); + static_assert(parse_specs("d").type == 'd', ""); +} + +template <size_t N> +FMT_CONSTEXPR fmt::detail::dynamic_format_specs<char> parse_dynamic_specs( + const char (&s)[N]) { + auto specs = fmt::detail::dynamic_format_specs<char>(); + auto ctx = test_parse_context(); + auto h = fmt::detail::dynamic_specs_handler<test_parse_context>(specs, ctx); + parse_format_specs(s, s + N, h); + return specs; +} + +TEST(format_test, constexpr_dynamic_specs_handler) { + static_assert(parse_dynamic_specs("<").align == fmt::align::left, ""); + static_assert(parse_dynamic_specs("*^").fill[0] == '*', ""); + static_assert(parse_dynamic_specs("+").sign == fmt::sign::plus, ""); + static_assert(parse_dynamic_specs("-").sign == fmt::sign::minus, ""); + static_assert(parse_dynamic_specs(" ").sign == fmt::sign::space, ""); + static_assert(parse_dynamic_specs("#").alt, ""); + static_assert(parse_dynamic_specs("0").align == fmt::align::numeric, ""); + static_assert(parse_dynamic_specs("42").width == 42, ""); + static_assert(parse_dynamic_specs("{}").width_ref.val.index == 11, ""); + static_assert(parse_dynamic_specs("{42}").width_ref.val.index == 42, ""); + static_assert(parse_dynamic_specs(".42").precision == 42, ""); + static_assert(parse_dynamic_specs(".{}").precision_ref.val.index == 11, ""); + static_assert(parse_dynamic_specs(".{42}").precision_ref.val.index == 42, ""); + static_assert(parse_dynamic_specs("d").type == 'd', ""); +} + +template <size_t N> +FMT_CONSTEXPR test_format_specs_handler check_specs(const char (&s)[N]) { + fmt::detail::specs_checker<test_format_specs_handler> checker( + test_format_specs_handler(), fmt::detail::type::double_type); + parse_format_specs(s, s + N, checker); + return checker; +} + +TEST(format_test, constexpr_specs_checker) { + using handler = test_format_specs_handler; + static_assert(check_specs("<").alignment == fmt::align::left, ""); + static_assert(check_specs("*^").fill == '*', ""); + static_assert(check_specs("+").res == handler::plus, ""); + static_assert(check_specs("-").res == handler::minus, ""); + static_assert(check_specs(" ").res == handler::space, ""); + static_assert(check_specs("#").res == handler::hash, ""); + static_assert(check_specs("0").res == handler::zero, ""); + static_assert(check_specs("42").width == 42, ""); + static_assert(check_specs("{42}").width_ref.val.index == 42, ""); + static_assert(check_specs(".42").precision == 42, ""); + static_assert(check_specs(".{42}").precision_ref.val.index == 42, ""); + static_assert(check_specs("d").type == 'd', ""); + static_assert(check_specs("{<").res == handler::error, ""); +} + +struct test_format_string_handler { + FMT_CONSTEXPR void on_text(const char*, const char*) {} + + FMT_CONSTEXPR int on_arg_id() { return 0; } + + template <typename T> FMT_CONSTEXPR int on_arg_id(T) { return 0; } + + FMT_CONSTEXPR void on_replacement_field(int, const char*) {} + + FMT_CONSTEXPR const char* on_format_specs(int, const char* begin, + const char*) { + return begin; + } + + FMT_CONSTEXPR void on_error(const char*) { error = true; } + + bool error = false; +}; + +template <size_t N> FMT_CONSTEXPR bool parse_string(const char (&s)[N]) { + auto h = test_format_string_handler(); + fmt::detail::parse_format_string<true>(fmt::string_view(s, N - 1), h); + return !h.error; +} + +TEST(format_test, constexpr_parse_format_string) { + static_assert(parse_string("foo"), ""); + static_assert(!parse_string("}"), ""); + static_assert(parse_string("{}"), ""); + static_assert(parse_string("{42}"), ""); + static_assert(parse_string("{foo}"), ""); + static_assert(parse_string("{:}"), ""); +} + +struct test_error_handler { + const char*& error; + + FMT_CONSTEXPR test_error_handler(const char*& err) : error(err) {} + + FMT_CONSTEXPR test_error_handler(const test_error_handler& other) + : error(other.error) {} + + FMT_CONSTEXPR void on_error(const char* message) { + if (!error) error = message; + } +}; + +FMT_CONSTEXPR size_t len(const char* s) { + size_t len = 0; + while (*s++) ++len; + return len; +} + +FMT_CONSTEXPR bool equal(const char* s1, const char* s2) { + if (!s1 || !s2) return s1 == s2; + while (*s1 && *s1 == *s2) { + ++s1; + ++s2; + } + return *s1 == *s2; +} + +template <typename... Args> +FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) { + const char* actual_error = nullptr; + auto s = string_view(fmt, len(fmt)); + auto checker = + fmt::detail::format_string_checker<char, test_error_handler, Args...>( + s, test_error_handler(actual_error)); + fmt::detail::parse_format_string<true>(s, checker); + return equal(actual_error, expected_error); +} + +# define EXPECT_ERROR_NOARGS(fmt, error) \ + static_assert(test_error(fmt, error), "") +# define EXPECT_ERROR(fmt, error, ...) \ + static_assert(test_error<__VA_ARGS__>(fmt, error), "") + +TEST(format_test, format_string_errors) { + EXPECT_ERROR_NOARGS("foo", nullptr); + EXPECT_ERROR_NOARGS("}", "unmatched '}' in format string"); + EXPECT_ERROR("{0:s", "unknown format specifier", date); +# if !FMT_MSC_VER || FMT_MSC_VER >= 1916 + // This causes an detail compiler error in MSVC2017. + EXPECT_ERROR("{:{<}", "invalid fill character '{'", int); + EXPECT_ERROR("{:10000000000}", "number is too big", int); + EXPECT_ERROR("{:.10000000000}", "number is too big", int); + EXPECT_ERROR_NOARGS("{:x}", "argument not found"); + EXPECT_ERROR("{:+}", "format specifier requires numeric argument", + const char*); + EXPECT_ERROR("{:-}", "format specifier requires numeric argument", + const char*); + EXPECT_ERROR("{:#}", "format specifier requires numeric argument", + const char*); + EXPECT_ERROR("{: }", "format specifier requires numeric argument", + const char*); + EXPECT_ERROR("{:0}", "format specifier requires numeric argument", + const char*); + EXPECT_ERROR("{:+}", "format specifier requires signed argument", unsigned); + EXPECT_ERROR("{:-}", "format specifier requires signed argument", unsigned); + EXPECT_ERROR("{: }", "format specifier requires signed argument", unsigned); + EXPECT_ERROR("{:{}}", "argument not found", int); + EXPECT_ERROR("{:.{}}", "argument not found", double); + EXPECT_ERROR("{:.2}", "precision not allowed for this argument type", int); + EXPECT_ERROR("{:s}", "invalid type specifier", int); + EXPECT_ERROR("{:s}", "invalid type specifier", char); + EXPECT_ERROR("{:+}", "invalid format specifier for char", char); + EXPECT_ERROR("{:s}", "invalid type specifier", double); + EXPECT_ERROR("{:d}", "invalid type specifier", const char*); + EXPECT_ERROR("{:d}", "invalid type specifier", std::string); + EXPECT_ERROR("{:s}", "invalid type specifier", void*); +# else + fmt::print("warning: constexpr is broken in this version of MSVC\n"); +# endif + EXPECT_ERROR("{foo", "compile-time checks don't support named arguments", + int); + EXPECT_ERROR_NOARGS("{10000000000}", "number is too big"); + EXPECT_ERROR_NOARGS("{0x}", "invalid format string"); + EXPECT_ERROR_NOARGS("{-}", "invalid format string"); + EXPECT_ERROR("{:{0x}}", "invalid format string", int); + EXPECT_ERROR("{:{-}}", "invalid format string", int); + EXPECT_ERROR("{:.{0x}}", "invalid format string", int); + EXPECT_ERROR("{:.{-}}", "invalid format string", int); + EXPECT_ERROR("{:.x}", "missing precision specifier", int); + EXPECT_ERROR_NOARGS("{}", "argument not found"); + EXPECT_ERROR("{1}", "argument not found", int); + EXPECT_ERROR("{1}{}", + "cannot switch from manual to automatic argument indexing", int, + int); + EXPECT_ERROR("{}{1}", + "cannot switch from automatic to manual argument indexing", int, + int); +} + +TEST(format_test, vformat_to) { + using context = fmt::format_context; + fmt::basic_format_arg<context> arg = fmt::detail::make_arg<context>(42); + auto args = fmt::basic_format_args<context>(&arg, 1); + auto s = std::string(); + fmt::vformat_to(std::back_inserter(s), "{}", args); + EXPECT_EQ("42", s); + s.clear(); + fmt::vformat_to(std::back_inserter(s), FMT_STRING("{}"), args); + EXPECT_EQ("42", s); + + using wcontext = fmt::wformat_context; + fmt::basic_format_arg<wcontext> warg = fmt::detail::make_arg<wcontext>(42); + auto wargs = fmt::basic_format_args<wcontext>(&warg, 1); + auto w = std::wstring(); + fmt::vformat_to(std::back_inserter(w), L"{}", wargs); + EXPECT_EQ(L"42", w); + w.clear(); + fmt::vformat_to(std::back_inserter(w), FMT_STRING(L"{}"), wargs); + EXPECT_EQ(L"42", w); +} + +template <typename T> static std::string fmt_to_string(const T& t) { + return fmt::format(FMT_STRING("{}"), t); +} + +TEST(format_test, fmt_string_in_template) { + EXPECT_EQ(fmt_to_string(1), "1"); + EXPECT_EQ(fmt_to_string(0), "0"); +} + +#endif // FMT_USE_CONSTEXPR + +TEST(format_test, char_traits_is_not_ambiguous) { + // Test that we don't inject detail names into the std namespace. + using namespace std; + auto c = char_traits<char>::char_type(); + (void)c; +#if __cplusplus >= 201103L + auto s = std::string(); + auto lval = begin(s); + (void)lval; +#endif +} + +#if __cplusplus > 201103L +struct custom_char { + int value; + custom_char() = default; + + template <typename T> + constexpr custom_char(T val) : value(static_cast<int>(val)) {} + + operator int() const { return value; } +}; + +int to_ascii(custom_char c) { return c; } + +FMT_BEGIN_NAMESPACE +template <> struct is_char<custom_char> : std::true_type {}; +FMT_END_NAMESPACE + +TEST(format_test, format_custom_char) { + const custom_char format[] = {'{', '}', 0}; + auto result = fmt::format(format, custom_char('x')); + EXPECT_EQ(result.size(), 1); + EXPECT_EQ(result[0], custom_char('x')); +} +#endif + +// Convert a char8_t string to std::string. Otherwise GTest will insist on +// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423. +template <typename S> std::string from_u8str(const S& str) { + return std::string(str.begin(), str.end()); +} + +TEST(format_test, format_utf8_precision) { + using str_type = std::basic_string<fmt::detail::char8_type>; + auto format = + str_type(reinterpret_cast<const fmt::detail::char8_type*>(u8"{:.4}")); + auto str = str_type(reinterpret_cast<const fmt::detail::char8_type*>( + u8"caf\u00e9s")); // cafés + auto result = fmt::format(format, str); + EXPECT_EQ(fmt::detail::compute_width(result), 4); + EXPECT_EQ(result.size(), 5); + EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5))); +} + +struct check_back_appender {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<check_back_appender> { + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template <typename Context> + auto format(check_back_appender, Context& ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + static_assert(std::is_same<decltype(++out), decltype(out)&>::value, + "needs to satisfy weakly_incrementable"); + *out = 'y'; + return ++out; + } +}; +FMT_END_NAMESPACE + +TEST(format_test, back_insert_slicing) { + EXPECT_EQ(fmt::format("{}", check_back_appender{}), "y"); +} + +template <typename Char, typename T> bool check_enabled_formatter() { + static_assert(std::is_default_constructible<fmt::formatter<T, Char>>::value, + ""); + return true; +} + +template <typename Char, typename... T> void check_enabled_formatters() { + auto dummy = {check_enabled_formatter<Char, T>()...}; + (void)dummy; +} + +TEST(format_test, test_formatters_enabled) { + check_enabled_formatters<char, bool, char, signed char, unsigned char, short, + unsigned short, int, unsigned, long, unsigned long, + long long, unsigned long long, float, double, + long double, void*, const void*, char*, const char*, + std::string, std::nullptr_t>(); + check_enabled_formatters<wchar_t, bool, wchar_t, signed char, unsigned char, + short, unsigned short, int, unsigned, long, + unsigned long, long long, unsigned long long, float, + double, long double, void*, const void*, wchar_t*, + const wchar_t*, std::wstring, std::nullptr_t>(); +} + +TEST(format_int_test, data) { + fmt::format_int format_int(42); + EXPECT_EQ("42", std::string(format_int.data(), format_int.size())); +} + +TEST(format_int_test, format_int) { + EXPECT_EQ("42", fmt::format_int(42).str()); + EXPECT_EQ(2u, fmt::format_int(42).size()); + EXPECT_EQ("-42", fmt::format_int(-42).str()); + EXPECT_EQ(3u, fmt::format_int(-42).size()); + EXPECT_EQ("42", fmt::format_int(42ul).str()); + EXPECT_EQ("-42", fmt::format_int(-42l).str()); + EXPECT_EQ("42", fmt::format_int(42ull).str()); + EXPECT_EQ("-42", fmt::format_int(-42ll).str()); + std::ostringstream os; + os << max_value<int64_t>(); + EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str()); +} diff --git a/libs/fmt/test/fuzzing/.gitignore b/libs/fmt/test/fuzzing/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ea4104026128ae8a2930bfbc01a91d33d3b6f920 --- /dev/null +++ b/libs/fmt/test/fuzzing/.gitignore @@ -0,0 +1,3 @@ +# ignore artifacts from the build.sh script +build-*/ + diff --git a/libs/fmt/test/fuzzing/CMakeLists.txt b/libs/fmt/test/fuzzing/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..2f716d8365065fb17f2c9296bf149a91d410e1d4 --- /dev/null +++ b/libs/fmt/test/fuzzing/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (c) 2019, Paul Dreik +# License: see LICENSE.rst in the fmt root directory + +# Link in the main function. Useful for reproducing, kcov, gdb, afl, valgrind. +# (Note that libFuzzer can also reproduce, just pass it the files.) +option(FMT_FUZZ_LINKMAIN "Enables the reproduce mode, instead of libFuzzer" On) + +# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for +# the fuzz targets, otherwise the CMake configuration step fails. +set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets") + +# Adds a binary for reproducing, i.e. no fuzzing, just enables replaying data +# through the fuzzers. +function(add_fuzzer source) + get_filename_component(basename ${source} NAME_WE) + set(name ${basename}-fuzzer) + add_executable(${name} ${source} fuzzer-common.h) + if (FMT_FUZZ_LINKMAIN) + target_sources(${name} PRIVATE main.cc) + endif () + target_link_libraries(${name} PRIVATE fmt) + if (FMT_FUZZ_LDFLAGS) + target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS}) + endif () + target_compile_features(${name} PRIVATE cxx_generic_lambdas) +endfunction() + +foreach (source chrono-duration.cc float.cc named-arg.cc one-arg.cc two-args.cc) + add_fuzzer(${source}) +endforeach () diff --git a/libs/fmt/test/fuzzing/README.md b/libs/fmt/test/fuzzing/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bb3d0e04f19bed04852e7154f56857d466064646 --- /dev/null +++ b/libs/fmt/test/fuzzing/README.md @@ -0,0 +1,25 @@ +# Running the fuzzers locally + +There is a [helper script](build.sh) to build the fuzzers, which has only been +tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on +Windows (using clang>=8) or on Mac, but the script will probably not work out of +the box. + +Something along +```sh +mkdir build +cd build +export CXX=clang++ +export CXXFLAGS="-fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g" +cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" +cmake --build . +``` +should work to build the fuzzers for all platforms which clang supports. + +Execute a fuzzer with for instance +```sh +cd build +export UBSAN_OPTIONS=halt_on_error=1 +mkdir out_chrono +bin/fuzzer_chrono_duration out_chrono +``` diff --git a/libs/fmt/test/fuzzing/build.sh b/libs/fmt/test/fuzzing/build.sh new file mode 100644 index 0000000000000000000000000000000000000000..28c506332e5e9093aa65c92f8432de40713a82c4 --- /dev/null +++ b/libs/fmt/test/fuzzing/build.sh @@ -0,0 +1,88 @@ +#!/bin/sh +# +# Creates fuzzer builds of various kinds +# - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works) +# - libFuzzer build (you will need clang) +# - afl build (you will need afl) +# +# +# Copyright (c) 2019 Paul Dreik +# +# For the license information refer to format.h. + +set -e +me=$(basename $0) +root=$(readlink -f "$(dirname "$0")/../..") + + +echo $me: root=$root + +here=$(pwd) + +CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g" +CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17" + +# For performance analysis of the fuzzers. +builddir=$here/build-fuzzers-perfanalysis +mkdir -p $builddir +cd $builddir +CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL -g" cmake \ +$CMAKEFLAGSALL \ +-DFMT_FUZZ_LINKMAIN=On \ +-DCMAKE_BUILD_TYPE=Release + +cmake --build $builddir + +# Builds the fuzzers as oss-fuzz does. +builddir=$here/build-fuzzers-ossfuzz +mkdir -p $builddir +cd $builddir +CXX="clang++" \ +CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link" cmake \ +cmake $CMAKEFLAGSALL \ +-DFMT_FUZZ_LINKMAIN=Off \ +-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" + +cmake --build $builddir + + +# Builds fuzzers for local fuzzing with libfuzzer with asan+usan. +builddir=$here/build-fuzzers-libfuzzer +mkdir -p $builddir +cd $builddir +CXX="clang++" \ +CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,address,undefined" cmake \ +cmake $CMAKEFLAGSALL \ +-DFMT_FUZZ_LINKMAIN=Off \ +-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" + +cmake --build $builddir + +# Builds a fast fuzzer for making coverage fast. +builddir=$here/build-fuzzers-fast +mkdir -p $builddir +cd $builddir +CXX="clang++" \ +CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link -O3" cmake \ +cmake $CMAKEFLAGSALL \ +-DFMT_FUZZ_LINKMAIN=Off \ +-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" \ + -DCMAKE_BUILD_TYPE=Release + +cmake --build $builddir + + +# Builds fuzzers for local fuzzing with afl. +builddir=$here/build-fuzzers-afl +mkdir -p $builddir +cd $builddir +CXX="afl-g++" \ +CXXFLAGS="$CXXFLAGSALL -fsanitize=address,undefined" \ +cmake $CMAKEFLAGSALL \ +-DFMT_FUZZ_LINKMAIN=On + +cmake --build $builddir + + +echo $me: all good + diff --git a/libs/fmt/test/fuzzing/chrono-duration.cc b/libs/fmt/test/fuzzing/chrono-duration.cc new file mode 100644 index 0000000000000000000000000000000000000000..fdad9894b6d866bde7bc8ad62f9bce31488b6e79 --- /dev/null +++ b/libs/fmt/test/fuzzing/chrono-duration.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#include <cstdint> +#include <fmt/chrono.h> + +#include "fuzzer-common.h" + +template <typename Period, typename Rep> +void invoke_inner(fmt::string_view format_str, Rep rep) { + auto value = std::chrono::duration<Rep, Period>(rep); + try { +#if FMT_FUZZ_FORMAT_TO_STRING + std::string message = fmt::format(format_str, value); +#else + fmt::memory_buffer buf; + fmt::format_to(buf, format_str, value); +#endif + } catch (std::exception&) { + } +} + +// Rep is a duration's representation type. +template <typename Rep> +void invoke_outer(const uint8_t* data, size_t size, int period) { + // Always use a fixed location of the data. + static_assert(sizeof(Rep) <= fixed_size, "fixed size is too small"); + if (size <= fixed_size + 1) return; + + const Rep rep = assign_from_buf<Rep>(data); + data += fixed_size; + size -= fixed_size; + + // data is already allocated separately in libFuzzer so reading past the end + // will most likely be detected anyway. + const auto format_str = fmt::string_view(as_chars(data), size); + + // yocto, zepto, zetta and yotta are not handled. + switch (period) { + case 1: + invoke_inner<std::atto>(format_str, rep); + break; + case 2: + invoke_inner<std::femto>(format_str, rep); + break; + case 3: + invoke_inner<std::pico>(format_str, rep); + break; + case 4: + invoke_inner<std::nano>(format_str, rep); + break; + case 5: + invoke_inner<std::micro>(format_str, rep); + break; + case 6: + invoke_inner<std::milli>(format_str, rep); + break; + case 7: + invoke_inner<std::centi>(format_str, rep); + break; + case 8: + invoke_inner<std::deci>(format_str, rep); + break; + case 9: + invoke_inner<std::deca>(format_str, rep); + break; + case 10: + invoke_inner<std::kilo>(format_str, rep); + break; + case 11: + invoke_inner<std::mega>(format_str, rep); + break; + case 12: + invoke_inner<std::giga>(format_str, rep); + break; + case 13: + invoke_inner<std::tera>(format_str, rep); + break; + case 14: + invoke_inner<std::peta>(format_str, rep); + break; + case 15: + invoke_inner<std::exa>(format_str, rep); + break; + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= 4) return 0; + + const auto representation = data[0]; + const auto period = data[1]; + data += 2; + size -= 2; + + switch (representation) { + case 1: + invoke_outer<char>(data, size, period); + break; + case 2: + invoke_outer<signed char>(data, size, period); + break; + case 3: + invoke_outer<unsigned char>(data, size, period); + break; + case 4: + invoke_outer<short>(data, size, period); + break; + case 5: + invoke_outer<unsigned short>(data, size, period); + break; + case 6: + invoke_outer<int>(data, size, period); + break; + case 7: + invoke_outer<unsigned int>(data, size, period); + break; + case 8: + invoke_outer<long>(data, size, period); + break; + case 9: + invoke_outer<unsigned long>(data, size, period); + break; + case 10: + invoke_outer<float>(data, size, period); + break; + case 11: + invoke_outer<double>(data, size, period); + break; + case 12: + invoke_outer<long double>(data, size, period); + break; + } + return 0; +} diff --git a/libs/fmt/test/fuzzing/float.cc b/libs/fmt/test/fuzzing/float.cc new file mode 100644 index 0000000000000000000000000000000000000000..073e4bcd60361a67e19ae54a267d1fc9f7db3bab --- /dev/null +++ b/libs/fmt/test/fuzzing/float.cc @@ -0,0 +1,39 @@ +// A fuzzer for floating-point formatter. +// For the license information refer to format.h. + +#include <cstdint> +#include <cstdlib> +#include <stdexcept> +#include <limits> +#include <fmt/format.h> + +#include "fuzzer-common.h" + +void check_round_trip(fmt::string_view format_str, double value) { + auto buffer = fmt::memory_buffer(); + fmt::format_to(buffer, format_str, value); + + if (std::isnan(value)) { + auto nan = std::signbit(value) ? "-nan" : "nan"; + if (fmt::string_view(buffer.data(), buffer.size()) != nan) + throw std::runtime_error("round trip failure"); + return; + } + + buffer.push_back('\0'); + char* ptr = nullptr; + if (std::strtod(buffer.data(), &ptr) != value) + throw std::runtime_error("round trip failure"); + if (ptr + 1 != buffer.end()) + throw std::runtime_error("unparsed output"); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= sizeof(double) || !std::numeric_limits<double>::is_iec559) + return 0; + check_round_trip("{}", assign_from_buf<double>(data)); + // A larger than necessary precision is used to trigger the fallback + // formatter. + check_round_trip("{:.50g}", assign_from_buf<double>(data)); + return 0; +} diff --git a/libs/fmt/test/fuzzing/fuzzer-common.h b/libs/fmt/test/fuzzing/fuzzer-common.h new file mode 100644 index 0000000000000000000000000000000000000000..635a5d996191a7590e93cd4c802de0d3c9098ae8 --- /dev/null +++ b/libs/fmt/test/fuzzing/fuzzer-common.h @@ -0,0 +1,75 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#ifndef FUZZER_COMMON_H +#define FUZZER_COMMON_H + +#include <cstdint> // std::uint8_t +#include <cstring> // memcpy +#include <vector> + +#include <fmt/core.h> + +// One can format to either a string, or a buffer. The latter is faster, but +// one may be interested in formatting to a string instead to verify it works +// as intended. To avoid a combinatoric explosion, select this at compile time +// instead of dynamically from the fuzz data. +#define FMT_FUZZ_FORMAT_TO_STRING 0 + +// If {fmt} is given a buffer that is separately allocated, chances that address +// sanitizer detects out of bound reads is much higher. However, it slows down +// the fuzzing. +#define FMT_FUZZ_SEPARATE_ALLOCATION 1 + +// The size of the largest possible type in use. +// To let the the fuzzer mutation be efficient at cross pollinating between +// different types, use a fixed size format. The same bit pattern, interpreted +// as another type, is likely interesting. +constexpr auto fixed_size = 16; + +// Casts data to a char pointer. +template <typename T> inline const char* as_chars(const T* data) { + return reinterpret_cast<const char*>(data); +} + +// Casts data to a byte pointer. +template <typename T> inline const std::uint8_t* as_bytes(const T* data) { + return reinterpret_cast<const std::uint8_t*>(data); +} + +// Blits bytes from data to form an (assumed trivially constructible) object +// of type Item. +template <class Item> inline Item assign_from_buf(const std::uint8_t* data) { + auto item = Item(); + std::memcpy(&item, data, sizeof(Item)); + return item; +} + +// Reads a boolean value by looking at the first byte from data. +template <> inline bool assign_from_buf<bool>(const std::uint8_t* data) { + return *data != 0; +} + +struct data_to_string { +#if FMT_FUZZ_SEPARATE_ALLOCATION + std::vector<char> buffer; + + data_to_string(const uint8_t* data, size_t size, bool add_terminator = false) + : buffer(size + (add_terminator ? 1 : 0)) { + std::memcpy(buffer.data(), data, size); + } + + fmt::string_view get() const { return {buffer.data(), buffer.size()}; } +#else + fmt::string_view sv; + + data_to_string(const uint8_t* data, size_t size, bool = false) + : str(as_chars(data), size) {} + + fmt::string_view get() const { return sv; } +#endif + + const char* data() const { return get().data(); } +}; + +#endif // FUZZER_COMMON_H diff --git a/libs/fmt/test/fuzzing/main.cc b/libs/fmt/test/fuzzing/main.cc new file mode 100644 index 0000000000000000000000000000000000000000..8f8c719b7263ec5328f9277cf53efd0156f51ef7 --- /dev/null +++ b/libs/fmt/test/fuzzing/main.cc @@ -0,0 +1,22 @@ +#include <cassert> +#include <fstream> +#include <vector> + +#include "fuzzer-common.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); + +int main(int argc, char** argv) { + for (int i = 1; i < argc; ++i) { + std::ifstream in(argv[i]); + assert(in); + in.seekg(0, std::ios_base::end); + const auto size = in.tellg(); + assert(size >= 0); + in.seekg(0, std::ios_base::beg); + std::vector<char> buf(static_cast<size_t>(size)); + in.read(buf.data(), size); + assert(in.gcount() == size); + LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size()); + } +} diff --git a/libs/fmt/test/fuzzing/named-arg.cc b/libs/fmt/test/fuzzing/named-arg.cc new file mode 100644 index 0000000000000000000000000000000000000000..ffd8e903b8c13ea3bbf25abda5746e1bff6230d1 --- /dev/null +++ b/libs/fmt/test/fuzzing/named-arg.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#include <cstdint> +#include <type_traits> +#include <vector> +#include <fmt/chrono.h> + +#include "fuzzer-common.h" + +template <typename T> +void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) { + static_assert(sizeof(T) <= fixed_size, "fixed_size too small"); + if (size <= fixed_size) return; + const T value = assign_from_buf<T>(data); + data += fixed_size; + size -= fixed_size; + + if (arg_name_size <= 0 || arg_name_size >= size) return; + data_to_string arg_name(data, arg_name_size, true); + data += arg_name_size; + size -= arg_name_size; + + data_to_string format_str(data, size); + try { +#if FMT_FUZZ_FORMAT_TO_STRING + std::string message = + fmt::format(format_str.get(), fmt::arg(arg_name.data(), value)); +#else + fmt::memory_buffer out; + fmt::format_to(out, format_str.get(), fmt::arg(arg_name.data(), value)); +#endif + } catch (std::exception&) { + } +} + +// For dynamic dispatching to an explicit instantiation. +template <typename Callback> void invoke(int type, Callback callback) { + switch (type) { + case 0: + callback(bool()); + break; + case 1: + callback(char()); + break; + case 2: + using sc = signed char; + callback(sc()); + break; + case 3: + using uc = unsigned char; + callback(uc()); + break; + case 4: + callback(short()); + break; + case 5: + using us = unsigned short; + callback(us()); + break; + case 6: + callback(int()); + break; + case 7: + callback(unsigned()); + break; + case 8: + callback(long()); + break; + case 9: + using ul = unsigned long; + callback(ul()); + break; + case 10: + callback(float()); + break; + case 11: + callback(double()); + break; + case 12: + using LD = long double; + callback(LD()); + break; + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= 3) return 0; + + // Switch types depending on the first byte of the input. + const auto type = data[0] & 0x0F; + const unsigned arg_name_size = (data[0] & 0xF0) >> 4; + data++; + size--; + + invoke(type, [=](auto arg) { + invoke_fmt<decltype(arg)>(data, size, arg_name_size); + }); + return 0; +} diff --git a/libs/fmt/test/fuzzing/one-arg.cc b/libs/fmt/test/fuzzing/one-arg.cc new file mode 100644 index 0000000000000000000000000000000000000000..df1734324380e82ca6eb89550041cdc29f8c2ed3 --- /dev/null +++ b/libs/fmt/test/fuzzing/one-arg.cc @@ -0,0 +1,91 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#include <cstdint> +#include <exception> +#include <fmt/chrono.h> + +#include "fuzzer-common.h" + +template <typename T, typename Repr> +const T* from_repr(const Repr& r) { return &r; } + +template <> +const std::tm* from_repr<std::tm>(const std::time_t& t) { + return std::localtime(&t); +} + +template <typename T, typename Repr = T> +void invoke_fmt(const uint8_t* data, size_t size) { + static_assert(sizeof(Repr) <= fixed_size, "Nfixed is too small"); + if (size <= fixed_size) return; + auto repr = assign_from_buf<Repr>(data); + const T* value = from_repr<T>(repr); + if (!value) return; + data += fixed_size; + size -= fixed_size; + data_to_string format_str(data, size); + try { +#if FMT_FUZZ_FORMAT_TO_STRING + std::string message = fmt::format(format_str.get(), *value); +#else + fmt::memory_buffer message; + fmt::format_to(message, format_str.get(), *value); +#endif + } catch (std::exception&) { + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= 3) return 0; + + const auto first = data[0]; + data++; + size--; + + switch (first) { + case 0: + invoke_fmt<bool>(data, size); + break; + case 1: + invoke_fmt<char>(data, size); + break; + case 2: + invoke_fmt<unsigned char>(data, size); + break; + case 3: + invoke_fmt<signed char>(data, size); + break; + case 4: + invoke_fmt<short>(data, size); + break; + case 5: + invoke_fmt<unsigned short>(data, size); + break; + case 6: + invoke_fmt<int>(data, size); + break; + case 7: + invoke_fmt<unsigned int>(data, size); + break; + case 8: + invoke_fmt<long>(data, size); + break; + case 9: + invoke_fmt<unsigned long>(data, size); + break; + case 10: + invoke_fmt<float>(data, size); + break; + case 11: + invoke_fmt<double>(data, size); + break; + case 12: + invoke_fmt<long double>(data, size); + break; + case 13: + invoke_fmt<std::tm, std::time_t>(data, size); + break; + } + return 0; +} diff --git a/libs/fmt/test/fuzzing/two-args.cc b/libs/fmt/test/fuzzing/two-args.cc new file mode 100644 index 0000000000000000000000000000000000000000..4d7d34531779a88741db59dea55e2996cea6a4ab --- /dev/null +++ b/libs/fmt/test/fuzzing/two-args.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2019, Paul Dreik +// For the license information refer to format.h. + +#include <cstdint> +#include <exception> +#include <string> +#include <fmt/format.h> + +#include "fuzzer-common.h" + +template <typename Item1, typename Item2> +void invoke_fmt(const uint8_t* data, size_t size) { + static_assert(sizeof(Item1) <= fixed_size, "size1 exceeded"); + static_assert(sizeof(Item2) <= fixed_size, "size2 exceeded"); + if (size <= fixed_size + fixed_size) return; + + const Item1 item1 = assign_from_buf<Item1>(data); + data += fixed_size; + size -= fixed_size; + + const Item2 item2 = assign_from_buf<Item2>(data); + data += fixed_size; + size -= fixed_size; + + auto format_str = fmt::string_view(as_chars(data), size); +#if FMT_FUZZ_FORMAT_TO_STRING + std::string message = fmt::format(format_str, item1, item2); +#else + fmt::memory_buffer message; + fmt::format_to(message, format_str, item1, item2); +#endif +} + +// For dynamic dispatching to an explicit instantiation. +template <typename Callback> void invoke(int index, Callback callback) { + switch (index) { + case 0: + callback(bool()); + break; + case 1: + callback(char()); + break; + case 2: + using sc = signed char; + callback(sc()); + break; + case 3: + using uc = unsigned char; + callback(uc()); + break; + case 4: + callback(short()); + break; + case 5: + using us = unsigned short; + callback(us()); + break; + case 6: + callback(int()); + break; + case 7: + callback(unsigned()); + break; + case 8: + callback(long()); + break; + case 9: + using ul = unsigned long; + callback(ul()); + break; + case 10: + callback(float()); + break; + case 11: + callback(double()); + break; + case 12: + using LD = long double; + callback(LD()); + break; + case 13: + using ptr = void*; + callback(ptr()); + break; + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size <= 3) return 0; + + // Switch types depending on the first byte of the input. + const auto type1 = data[0] & 0x0F; + const auto type2 = (data[0] & 0xF0) >> 4; + data++; + size--; + try { + invoke(type1, [=](auto param1) { + invoke(type2, [=](auto param2) { + invoke_fmt<decltype(param1), decltype(param2)>(data, size); + }); + }); + } catch (std::exception&) { + } + return 0; +} diff --git a/libs/fmt/test/gtest-extra-test.cc b/libs/fmt/test/gtest-extra-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..46be75b8602f7ed7aa24ed885434456599e7f44d --- /dev/null +++ b/libs/fmt/test/gtest-extra-test.cc @@ -0,0 +1,433 @@ +// Formatting library for C++ - tests of custom Google Test assertions +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "gtest-extra.h" + +#include <gtest/gtest-spi.h> + +#include <algorithm> +#include <cstring> +#include <memory> +#include <stdexcept> + +#include "fmt/os.h" + +#if defined(_WIN32) && !defined(__MINGW32__) +# include <crtdbg.h> // for _CrtSetReportMode +#endif // _WIN32 + +#include "util.h" + +namespace { + +// Tests that assertion macros evaluate their arguments exactly once. +class SingleEvaluationTest : public ::testing::Test { + protected: + SingleEvaluationTest() { + p_ = s_; + a_ = 0; + b_ = 0; + } + + static const char* const s_; + static const char* p_; + + static int a_; + static int b_; +}; + +const char* const SingleEvaluationTest::s_ = "01234"; +const char* SingleEvaluationTest::p_; +int SingleEvaluationTest::a_; +int SingleEvaluationTest::b_; + +void do_nothing() {} + +FMT_NORETURN void throw_exception() { throw std::runtime_error("test"); } + +FMT_NORETURN void throw_system_error() { + throw fmt::system_error(EDOM, "test"); +} + +// Tests that when EXPECT_THROW_MSG fails, it evaluates its message argument +// exactly once. +TEST_F(SingleEvaluationTest, FailedEXPECT_THROW_MSG) { + EXPECT_NONFATAL_FAILURE( + EXPECT_THROW_MSG(throw_exception(), std::exception, p_++), "01234"); + EXPECT_EQ(s_ + 1, p_); +} + +// Tests that when EXPECT_SYSTEM_ERROR fails, it evaluates its message argument +// exactly once. +TEST_F(SingleEvaluationTest, FailedEXPECT_SYSTEM_ERROR) { + EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, p_++), + "01234"); + EXPECT_EQ(s_ + 1, p_); +} + +// Tests that assertion arguments are evaluated exactly once. +TEST_F(SingleEvaluationTest, ExceptionTests) { + // successful EXPECT_THROW_MSG + EXPECT_THROW_MSG( + { // NOLINT + a_++; + throw_exception(); + }, + std::exception, (b_++, "test")); + EXPECT_EQ(1, a_); + EXPECT_EQ(1, b_); + + // failed EXPECT_THROW_MSG, throws different type + EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG( + { // NOLINT + a_++; + throw_exception(); + }, + std::logic_error, (b_++, "test")), + "throws a different type"); + EXPECT_EQ(2, a_); + EXPECT_EQ(2, b_); + + // failed EXPECT_THROW_MSG, throws an exception with different message + EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG( + { // NOLINT + a_++; + throw_exception(); + }, + std::exception, (b_++, "other")), + "throws an exception with a different message"); + EXPECT_EQ(3, a_); + EXPECT_EQ(3, b_); + + // failed EXPECT_THROW_MSG, throws nothing + EXPECT_NONFATAL_FAILURE( + EXPECT_THROW_MSG(a_++, std::exception, (b_++, "test")), "throws nothing"); + EXPECT_EQ(4, a_); + EXPECT_EQ(4, b_); +} + +TEST_F(SingleEvaluationTest, SystemErrorTests) { + // successful EXPECT_SYSTEM_ERROR + EXPECT_SYSTEM_ERROR( + { // NOLINT + a_++; + throw_system_error(); + }, + EDOM, (b_++, "test")); + EXPECT_EQ(1, a_); + EXPECT_EQ(1, b_); + + // failed EXPECT_SYSTEM_ERROR, throws different type + EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR( + { // NOLINT + a_++; + throw_exception(); + }, + EDOM, (b_++, "test")), + "throws a different type"); + EXPECT_EQ(2, a_); + EXPECT_EQ(2, b_); + + // failed EXPECT_SYSTEM_ERROR, throws an exception with different message + EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR( + { // NOLINT + a_++; + throw_system_error(); + }, + EDOM, (b_++, "other")), + "throws an exception with a different message"); + EXPECT_EQ(3, a_); + EXPECT_EQ(3, b_); + + // failed EXPECT_SYSTEM_ERROR, throws nothing + EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(a_++, EDOM, (b_++, "test")), + "throws nothing"); + EXPECT_EQ(4, a_); + EXPECT_EQ(4, b_); +} + +#if FMT_USE_FCNTL +// Tests that when EXPECT_WRITE fails, it evaluates its message argument +// exactly once. +TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) { + EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++), + "01234"); + EXPECT_EQ(s_ + 1, p_); +} + +// Tests that assertion arguments are evaluated exactly once. +TEST_F(SingleEvaluationTest, WriteTests) { + // successful EXPECT_WRITE + EXPECT_WRITE( + stdout, + { // NOLINT + a_++; + std::printf("test"); + }, + (b_++, "test")); + EXPECT_EQ(1, a_); + EXPECT_EQ(1, b_); + + // failed EXPECT_WRITE + EXPECT_NONFATAL_FAILURE(EXPECT_WRITE( + stdout, + { // NOLINT + a_++; + std::printf("test"); + }, + (b_++, "other")), + "Actual: test"); + EXPECT_EQ(2, a_); + EXPECT_EQ(2, b_); +} + +// Tests EXPECT_WRITE. +TEST(ExpectTest, EXPECT_WRITE) { + EXPECT_WRITE(stdout, do_nothing(), ""); + EXPECT_WRITE(stdout, std::printf("test"), "test"); + EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test"); + EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("that"), "this"), + "Expected: this\n" + " Actual: that"); +} + +TEST(StreamingAssertionsTest, EXPECT_WRITE) { + EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure"; + EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other") + << "expected failure", + "expected failure"); +} +#endif // FMT_USE_FCNTL + +// Tests that the compiler will not complain about unreachable code in the +// EXPECT_THROW_MSG macro. +TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) { + int n = 0; + using std::runtime_error; + EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, ""); + EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), ""); + EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(throw 1, runtime_error, ""), ""); + EXPECT_NONFATAL_FAILURE( + EXPECT_THROW_MSG(throw runtime_error("a"), runtime_error, "b"), ""); +} + +// Tests that the compiler will not complain about unreachable code in the +// EXPECT_SYSTEM_ERROR macro. +TEST(ExpectSystemErrorTest, DoesNotGenerateUnreachableCodeWarning) { + int n = 0; + EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test"); + EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), ""); + EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw 1, EDOM, ""), ""); + EXPECT_NONFATAL_FAILURE( + EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "aaa"), EDOM, "bbb"), + ""); +} + +TEST(AssertionSyntaxTest, ExceptionAssertionBehavesLikeSingleStatement) { + if (::testing::internal::AlwaysFalse()) + EXPECT_THROW_MSG(do_nothing(), std::exception, ""); + + if (::testing::internal::AlwaysTrue()) + EXPECT_THROW_MSG(throw_exception(), std::exception, "test"); + else + do_nothing(); +} + +TEST(AssertionSyntaxTest, SystemErrorAssertionBehavesLikeSingleStatement) { + if (::testing::internal::AlwaysFalse()) + EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, ""); + + if (::testing::internal::AlwaysTrue()) + EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test"); + else + do_nothing(); +} + +TEST(AssertionSyntaxTest, WriteAssertionBehavesLikeSingleStatement) { + if (::testing::internal::AlwaysFalse()) + EXPECT_WRITE(stdout, std::printf("x"), "x"); + + if (::testing::internal::AlwaysTrue()) + EXPECT_WRITE(stdout, std::printf("x"), "x"); + else + do_nothing(); +} + +// Tests EXPECT_THROW_MSG. +TEST(ExpectTest, EXPECT_THROW_MSG) { + EXPECT_THROW_MSG(throw_exception(), std::exception, "test"); + EXPECT_NONFATAL_FAILURE( + EXPECT_THROW_MSG(throw_exception(), std::logic_error, "test"), + "Expected: throw_exception() throws an exception of " + "type std::logic_error.\n Actual: it throws a different type."); + EXPECT_NONFATAL_FAILURE( + EXPECT_THROW_MSG(do_nothing(), std::exception, "test"), + "Expected: do_nothing() throws an exception of type std::exception.\n" + " Actual: it throws nothing."); + EXPECT_NONFATAL_FAILURE( + EXPECT_THROW_MSG(throw_exception(), std::exception, "other"), + "throw_exception() throws an exception with a different message.\n" + "Expected: other\n" + " Actual: test"); +} + +// Tests EXPECT_SYSTEM_ERROR. +TEST(ExpectTest, EXPECT_SYSTEM_ERROR) { + EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test"); + EXPECT_NONFATAL_FAILURE( + EXPECT_SYSTEM_ERROR(throw_exception(), EDOM, "test"), + "Expected: throw_exception() throws an exception of " + "type fmt::system_error.\n Actual: it throws a different type."); + EXPECT_NONFATAL_FAILURE( + EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "test"), + "Expected: do_nothing() throws an exception of type fmt::system_error.\n" + " Actual: it throws nothing."); + EXPECT_NONFATAL_FAILURE( + EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other"), + fmt::format( + "throw_system_error() throws an exception with a different message.\n" + "Expected: {}\n" + " Actual: {}", + format_system_error(EDOM, "other"), + format_system_error(EDOM, "test"))); +} + +TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) { + EXPECT_THROW_MSG(throw_exception(), std::exception, "test") + << "unexpected failure"; + EXPECT_NONFATAL_FAILURE( + EXPECT_THROW_MSG(throw_exception(), std::exception, "other") + << "expected failure", + "expected failure"); +} + +TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) { + EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test") + << "unexpected failure"; + EXPECT_NONFATAL_FAILURE( + EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other") + << "expected failure", + "expected failure"); +} + +TEST(UtilTest, FormatSystemError) { + fmt::memory_buffer out; + fmt::format_system_error(out, EDOM, "test message"); + EXPECT_EQ(to_string(out), format_system_error(EDOM, "test message")); +} + +#if FMT_USE_FCNTL + +using fmt::buffered_file; +using fmt::error_code; +using fmt::file; + +TEST(ErrorCodeTest, Ctor) { + EXPECT_EQ(error_code().get(), 0); + EXPECT_EQ(error_code(42).get(), 42); +} + +TEST(OutputRedirectTest, ScopedRedirect) { + file read_end, write_end; + file::pipe(read_end, write_end); + { + buffered_file file(write_end.fdopen("w")); + std::fprintf(file.get(), "[[["); + { + output_redirect redir(file.get()); + std::fprintf(file.get(), "censored"); + } + std::fprintf(file.get(), "]]]"); + } + EXPECT_READ(read_end, "[[[]]]"); +} + +// Test that output_redirect handles errors in flush correctly. +TEST(OutputRedirectTest, FlushErrorInCtor) { + file read_end, write_end; + file::pipe(read_end, write_end); + int write_fd = write_end.descriptor(); + file write_copy = write_end.dup(write_fd); + buffered_file f = write_end.fdopen("w"); + // Put a character in a file buffer. + EXPECT_EQ('x', fputc('x', f.get())); + FMT_POSIX(close(write_fd)); + std::unique_ptr<output_redirect> redir{nullptr}; + EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new output_redirect(f.get())), EBADF, + "cannot flush stream"); + redir.reset(nullptr); + write_copy.dup2(write_fd); // "undo" close or dtor will fail +} + +TEST(OutputRedirectTest, DupErrorInCtor) { + buffered_file f = open_buffered_file(); + int fd = (f.fileno)(); + file copy = file::dup(fd); + FMT_POSIX(close(fd)); + std::unique_ptr<output_redirect> redir{nullptr}; + EXPECT_SYSTEM_ERROR_NOASSERT( + redir.reset(new output_redirect(f.get())), EBADF, + fmt::format("cannot duplicate file descriptor {}", fd)); + copy.dup2(fd); // "undo" close or dtor will fail +} + +TEST(OutputRedirectTest, RestoreAndRead) { + file read_end, write_end; + file::pipe(read_end, write_end); + buffered_file file(write_end.fdopen("w")); + std::fprintf(file.get(), "[[["); + output_redirect redir(file.get()); + std::fprintf(file.get(), "censored"); + EXPECT_EQ("censored", redir.restore_and_read()); + EXPECT_EQ("", redir.restore_and_read()); + std::fprintf(file.get(), "]]]"); + file = buffered_file(); + EXPECT_READ(read_end, "[[[]]]"); +} + +// Test that OutputRedirect handles errors in flush correctly. +TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) { + file read_end, write_end; + file::pipe(read_end, write_end); + int write_fd = write_end.descriptor(); + file write_copy = write_end.dup(write_fd); + buffered_file f = write_end.fdopen("w"); + output_redirect redir(f.get()); + // Put a character in a file buffer. + EXPECT_EQ('x', fputc('x', f.get())); + FMT_POSIX(close(write_fd)); + EXPECT_SYSTEM_ERROR_NOASSERT(redir.restore_and_read(), EBADF, + "cannot flush stream"); + write_copy.dup2(write_fd); // "undo" close or dtor will fail +} + +TEST(OutputRedirectTest, ErrorInDtor) { + file read_end, write_end; + file::pipe(read_end, write_end); + int write_fd = write_end.descriptor(); + file write_copy = write_end.dup(write_fd); + buffered_file f = write_end.fdopen("w"); + std::unique_ptr<output_redirect> redir(new output_redirect(f.get())); + // Put a character in a file buffer. + EXPECT_EQ('x', fputc('x', f.get())); + EXPECT_WRITE( + stderr, + { + // The close function must be called inside EXPECT_WRITE, + // otherwise the system may recycle closed file descriptor when + // redirecting the output in EXPECT_STDERR and the second close + // will break output redirection. + FMT_POSIX(close(write_fd)); + SUPPRESS_ASSERT(redir.reset(nullptr)); + }, + format_system_error(EBADF, "cannot flush stream")); + write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail +} + +#endif // FMT_USE_FILE_DESCRIPTORS + +} // namespace diff --git a/libs/fmt/test/gtest-extra.cc b/libs/fmt/test/gtest-extra.cc new file mode 100644 index 0000000000000000000000000000000000000000..d71b951e19c31cd3c3448682e81da40e1b48da5b --- /dev/null +++ b/libs/fmt/test/gtest-extra.cc @@ -0,0 +1,87 @@ +// Formatting library for C++ - custom Google Test assertions +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "gtest-extra.h" + +#if FMT_USE_FCNTL + +using fmt::file; + +output_redirect::output_redirect(FILE* f) : file_(f) { + flush(); + int fd = FMT_POSIX(fileno(f)); + // Create a file object referring to the original file. + original_ = file::dup(fd); + // Create a pipe. + file write_end; + file::pipe(read_end_, write_end); + // Connect the passed FILE object to the write end of the pipe. + write_end.dup2(fd); +} + +output_redirect::~output_redirect() FMT_NOEXCEPT { + try { + restore(); + } catch (const std::exception& e) { + std::fputs(e.what(), stderr); + } +} + +void output_redirect::flush() { +# if EOF != -1 +# error "FMT_RETRY assumes return value of -1 indicating failure" +# endif + int result = 0; + FMT_RETRY(result, fflush(file_)); + if (result != 0) throw fmt::system_error(errno, "cannot flush stream"); +} + +void output_redirect::restore() { + if (original_.descriptor() == -1) return; // Already restored. + flush(); + // Restore the original file. + original_.dup2(FMT_POSIX(fileno(file_))); + original_.close(); +} + +std::string output_redirect::restore_and_read() { + // Restore output. + restore(); + + // Read everything from the pipe. + std::string content; + if (read_end_.descriptor() == -1) return content; // Already read. + enum { BUFFER_SIZE = 4096 }; + char buffer[BUFFER_SIZE]; + size_t count = 0; + do { + count = read_end_.read(buffer, BUFFER_SIZE); + content.append(buffer, count); + } while (count != 0); + read_end_.close(); + return content; +} + +std::string read(file& f, size_t count) { + std::string buffer(count, '\0'); + size_t n = 0, offset = 0; + do { + n = f.read(&buffer[offset], count - offset); + // We can't read more than size_t bytes since count has type size_t. + offset += n; + } while (offset < count && n != 0); + buffer.resize(offset); + return buffer; +} + +#endif // FMT_USE_FCNTL + +std::string format_system_error(int error_code, fmt::string_view message) { + fmt::memory_buffer out; + format_system_error(out, error_code, message); + return to_string(out); +} diff --git a/libs/fmt/test/gtest-extra.h b/libs/fmt/test/gtest-extra.h new file mode 100644 index 0000000000000000000000000000000000000000..84a6ee676b38ca46a5d0882227309afed3978fe2 --- /dev/null +++ b/libs/fmt/test/gtest-extra.h @@ -0,0 +1,160 @@ +// Formatting library for C++ - custom Google Test assertions +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_GTEST_EXTRA_H_ +#define FMT_GTEST_EXTRA_H_ + +#include <stdlib.h> // _invalid_parameter_handler + +#include <string> + +#include "fmt/os.h" +#include "gmock/gmock.h" + +#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \ + std::string gtest_expected_message = expected_message; \ + bool gtest_caught_expected = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (expected_exception const& e) { \ + if (gtest_expected_message != e.what()) { \ + gtest_ar << #statement \ + " throws an exception with a different message.\n" \ + << "Expected: " << gtest_expected_message << "\n" \ + << " Actual: " << e.what(); \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + gtest_caught_expected = true; \ + } catch (...) { \ + gtest_ar << "Expected: " #statement \ + " throws an exception of type " #expected_exception \ + ".\n Actual: it throws a different type."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + if (!gtest_caught_expected) { \ + gtest_ar << "Expected: " #statement \ + " throws an exception of type " #expected_exception \ + ".\n Actual: it throws nothing."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__) \ + : fail(gtest_ar.failure_message()) + +// Tests that the statement throws the expected exception and the exception's +// what() method returns expected message. +#define EXPECT_THROW_MSG(statement, expected_exception, expected_message) \ + FMT_TEST_THROW_(statement, expected_exception, expected_message, \ + GTEST_NONFATAL_FAILURE_) + +std::string format_system_error(int error_code, fmt::string_view message); + +#define EXPECT_SYSTEM_ERROR(statement, error_code, message) \ + EXPECT_THROW_MSG(statement, fmt::system_error, \ + format_system_error(error_code, message)) + +#if FMT_USE_FCNTL + +// Captures file output by redirecting it to a pipe. +// The output it can handle is limited by the pipe capacity. +class output_redirect { + private: + FILE* file_; + fmt::file original_; // Original file passed to redirector. + fmt::file read_end_; // Read end of the pipe where the output is redirected. + + void flush(); + void restore(); + + public: + explicit output_redirect(FILE* file); + ~output_redirect() FMT_NOEXCEPT; + + output_redirect(const output_redirect&) = delete; + void operator=(const output_redirect&) = delete; + + // Restores the original file, reads output from the pipe into a string + // and returns it. + std::string restore_and_read(); +}; + +# define FMT_TEST_WRITE_(statement, expected_output, file, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \ + std::string gtest_expected_output = expected_output; \ + output_redirect gtest_redir(file); \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + std::string gtest_output = gtest_redir.restore_and_read(); \ + if (gtest_output != gtest_expected_output) { \ + gtest_ar << #statement " produces different output.\n" \ + << "Expected: " << gtest_expected_output << "\n" \ + << " Actual: " << gtest_output; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__) \ + : fail(gtest_ar.failure_message()) + +// Tests that the statement writes the expected output to file. +# define EXPECT_WRITE(file, statement, expected_output) \ + FMT_TEST_WRITE_(statement, expected_output, file, GTEST_NONFATAL_FAILURE_) + +# ifdef _MSC_VER + +// Suppresses Windows assertions on invalid file descriptors, making +// POSIX functions return proper error codes instead of crashing on Windows. +class suppress_assert { + private: + _invalid_parameter_handler original_handler_; + int original_report_mode_; + + static void handle_invalid_parameter(const wchar_t*, const wchar_t*, + const wchar_t*, unsigned, uintptr_t) {} + + public: + suppress_assert() + : original_handler_( + _set_invalid_parameter_handler(handle_invalid_parameter)), + original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) {} + ~suppress_assert() { + _set_invalid_parameter_handler(original_handler_); + _CrtSetReportMode(_CRT_ASSERT, original_report_mode_); + } +}; + +# define SUPPRESS_ASSERT(statement) \ + { \ + suppress_assert sa; \ + statement; \ + } +# else +# define SUPPRESS_ASSERT(statement) statement +# endif // _MSC_VER + +# define EXPECT_SYSTEM_ERROR_NOASSERT(statement, error_code, message) \ + EXPECT_SYSTEM_ERROR(SUPPRESS_ASSERT(statement), error_code, message) + +// Attempts to read count characters from a file. +std::string read(fmt::file& f, size_t count); + +# define EXPECT_READ(file, expected_content) \ + EXPECT_EQ(expected_content, \ + read(file, fmt::string_view(expected_content).size())) + +#else +# define EXPECT_WRITE(file, statement, expected_output) \ + do { \ + (void)(file); \ + (void)(statement); \ + (void)(expected_output); \ + SUCCEED(); \ + } while (false) +#endif // FMT_USE_FCNTL + +#endif // FMT_GTEST_EXTRA_H_ diff --git a/libs/fmt/test/gtest/.clang-format b/libs/fmt/test/gtest/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..ec09b9b6bb1af079f4918169ee0255d8558ad400 --- /dev/null +++ b/libs/fmt/test/gtest/.clang-format @@ -0,0 +1,3 @@ +# Disable clang-format here +DisableFormat: true +SortIncludes: Never diff --git a/libs/fmt/test/gtest/CMakeLists.txt b/libs/fmt/test/gtest/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..1282d62afac659e12c65d96d8fd119c167aac827 --- /dev/null +++ b/libs/fmt/test/gtest/CMakeLists.txt @@ -0,0 +1,31 @@ +#------------------------------------------------------------------------------ +# Build the google test library + +# We compile Google Test ourselves instead of using pre-compiled libraries. +# See the Google Test FAQ "Why is it not recommended to install a +# pre-compiled copy of Google Test (for example, into /usr/local)?" +# at http://code.google.com/p/googletest/wiki/FAQ for more details. +add_library(gtest STATIC + gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h) +target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1) +target_include_directories(gtest SYSTEM PUBLIC .) + +find_package(Threads) +if (Threads_FOUND) + target_link_libraries(gtest ${CMAKE_THREAD_LIBS_INIT}) +else () + target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0) +endif () + +if (MSVC) + # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions. + target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Disable MSVC warnings of POSIX functions. + target_compile_options(gtest PUBLIC -Wno-deprecated-declarations) + endif () +endif () + +# Silence MSVC tr1 deprecation warning in gmock. +target_compile_definitions(gtest + PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1) diff --git a/libs/fmt/test/gtest/gmock-gtest-all.cc b/libs/fmt/test/gtest/gmock-gtest-all.cc new file mode 100644 index 0000000000000000000000000000000000000000..b67316c3b9941a2975fb5684af0ace71c4e611c7 --- /dev/null +++ b/libs/fmt/test/gtest/gmock-gtest-all.cc @@ -0,0 +1,14434 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// Google C++ Testing and Mocking Framework (Google Test) +// +// Sometimes it's desirable to build Google Test by compiling a single file. +// This file serves this purpose. + +// This line ensures that gtest.h can be compiled on its own, even +// when it's fused. +#include "gtest/gtest.h" + +// The following lines pull in the real gtest *.cc files. +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// The Google C++ Testing and Mocking Framework (Google Test) + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// Utilities for testing Google Test itself and code that uses Google Test +// (e.g. frameworks built on top of Google Test). + +// GOOGLETEST_CM0004 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ + + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// This helper class can be used to mock out Google Test failure reporting +// so that we can test Google Test or code that builds on Google Test. +// +// An object of this class appends a TestPartResult object to the +// TestPartResultArray object given in the constructor whenever a Google Test +// failure is reported. It can either intercept only failures that are +// generated in the same thread that created this object or it can intercept +// all generated failures. The scope of this mock object can be controlled with +// the second argument to the two arguments constructor. +class GTEST_API_ ScopedFakeTestPartResultReporter + : public TestPartResultReporterInterface { + public: + // The two possible mocking modes of this object. + enum InterceptMode { + INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures. + INTERCEPT_ALL_THREADS // Intercepts all failures. + }; + + // The c'tor sets this object as the test part result reporter used + // by Google Test. The 'result' parameter specifies where to report the + // results. This reporter will only catch failures generated in the current + // thread. DEPRECATED + explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result); + + // Same as above, but you can choose the interception scope of this object. + ScopedFakeTestPartResultReporter(InterceptMode intercept_mode, + TestPartResultArray* result); + + // The d'tor restores the previous test part result reporter. + ~ScopedFakeTestPartResultReporter() override; + + // Appends the TestPartResult object to the TestPartResultArray + // received in the constructor. + // + // This method is from the TestPartResultReporterInterface + // interface. + void ReportTestPartResult(const TestPartResult& result) override; + + private: + void Init(); + + const InterceptMode intercept_mode_; + TestPartResultReporterInterface* old_reporter_; + TestPartResultArray* const result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter); +}; + +namespace internal { + +// A helper class for implementing EXPECT_FATAL_FAILURE() and +// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +class GTEST_API_ SingleFailureChecker { + public: + // The constructor remembers the arguments. + SingleFailureChecker(const TestPartResultArray* results, + TestPartResult::Type type, const std::string& substr); + ~SingleFailureChecker(); + private: + const TestPartResultArray* const results_; + const TestPartResult::Type type_; + const std::string substr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker); +}; + +} // namespace internal + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +// A set of macros for testing Google Test assertions or code that's expected +// to generate Google Test fatal failures. It verifies that the given +// statement will cause exactly one fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_FATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - 'statement' cannot reference local non-static variables or +// non-static members of the current object. +// - 'statement' cannot return a value. +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. The AcceptsMacroThatExpandsToUnprotectedComma test in +// gtest_unittest.cc will fail to compile if we do that. +#define EXPECT_FATAL_FAILURE(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ALL_THREADS, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +// A macro for testing Google Test assertions or code that's expected to +// generate Google Test non-fatal failures. It asserts that the given +// statement will cause exactly one non-fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// 'statement' is allowed to reference local variables and members of +// the current object. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. If we do that, the code won't compile when the user gives +// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that +// expands to code containing an unprotected comma. The +// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc +// catches that. +// +// For the same reason, we have to write +// if (::testing::internal::AlwaysTrue()) { statement; } +// instead of +// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) +// to avoid an MSVC warning on unreachable code. +#define EXPECT_NONFATAL_FAILURE(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \ + >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <wchar.h> +#include <wctype.h> + +#include <algorithm> +#include <chrono> // NOLINT +#include <cmath> +#include <cstdint> +#include <iomanip> +#include <limits> +#include <list> +#include <map> +#include <ostream> // NOLINT +#include <sstream> +#include <vector> + +#if GTEST_OS_LINUX + +# include <fcntl.h> // NOLINT +# include <limits.h> // NOLINT +# include <sched.h> // NOLINT +// Declares vsnprintf(). This header is not available on Windows. +# include <strings.h> // NOLINT +# include <sys/mman.h> // NOLINT +# include <sys/time.h> // NOLINT +# include <unistd.h> // NOLINT +# include <string> + +#elif GTEST_OS_ZOS +# include <sys/time.h> // NOLINT + +// On z/OS we additionally need strings.h for strcasecmp. +# include <strings.h> // NOLINT + +#elif GTEST_OS_WINDOWS_MOBILE // We are on Windows CE. + +# include <windows.h> // NOLINT +# undef min + +#elif GTEST_OS_WINDOWS // We are on Windows proper. + +# include <windows.h> // NOLINT +# undef min + +#ifdef _MSC_VER +# include <crtdbg.h> // NOLINT +#endif + +# include <io.h> // NOLINT +# include <sys/timeb.h> // NOLINT +# include <sys/types.h> // NOLINT +# include <sys/stat.h> // NOLINT + +# if GTEST_OS_WINDOWS_MINGW +# include <sys/time.h> // NOLINT +# endif // GTEST_OS_WINDOWS_MINGW + +#else + +// cpplint thinks that the header is already included, so we want to +// silence it. +# include <sys/time.h> // NOLINT +# include <unistd.h> // NOLINT + +#endif // GTEST_OS_LINUX + +#if GTEST_HAS_EXCEPTIONS +# include <stdexcept> +#endif + +#if GTEST_CAN_STREAM_RESULTS_ +# include <arpa/inet.h> // NOLINT +# include <netdb.h> // NOLINT +# include <sys/socket.h> // NOLINT +# include <sys/types.h> // NOLINT +#endif + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Utility functions and classes used by the Google C++ testing framework.// +// This file contains purely Google Test's internal implementation. Please +// DO NOT #INCLUDE IT IN A USER PROGRAM. + +#ifndef GOOGLETEST_SRC_GTEST_INTERNAL_INL_H_ +#define GOOGLETEST_SRC_GTEST_INTERNAL_INL_H_ + +#ifndef _WIN32_WCE +# include <errno.h> +#endif // !_WIN32_WCE +#include <stddef.h> +#include <stdlib.h> // For strtoll/_strtoul64/malloc/free. +#include <string.h> // For memmove. + +#include <algorithm> +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + + +#if GTEST_CAN_STREAM_RESULTS_ +# include <arpa/inet.h> // NOLINT +# include <netdb.h> // NOLINT +#endif + +#if GTEST_OS_WINDOWS +# include <windows.h> // NOLINT +#endif // GTEST_OS_WINDOWS + + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// Declares the flags. +// +// We don't want the users to modify this flag in the code, but want +// Google Test's own unit tests to be able to access it. Therefore we +// declare it here as opposed to in gtest.h. +GTEST_DECLARE_bool_(death_test_use_fork); + +namespace internal { + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +GTEST_API_ extern const TypeId kTestTypeIdInGoogleTest; + +// Names of the flags (needed for parsing Google Test flags). +const char kAlsoRunDisabledTestsFlag[] = "also_run_disabled_tests"; +const char kBreakOnFailureFlag[] = "break_on_failure"; +const char kCatchExceptionsFlag[] = "catch_exceptions"; +const char kColorFlag[] = "color"; +const char kFailFast[] = "fail_fast"; +const char kFilterFlag[] = "filter"; +const char kListTestsFlag[] = "list_tests"; +const char kOutputFlag[] = "output"; +const char kBriefFlag[] = "brief"; +const char kPrintTimeFlag[] = "print_time"; +const char kPrintUTF8Flag[] = "print_utf8"; +const char kRandomSeedFlag[] = "random_seed"; +const char kRepeatFlag[] = "repeat"; +const char kShuffleFlag[] = "shuffle"; +const char kStackTraceDepthFlag[] = "stack_trace_depth"; +const char kStreamResultToFlag[] = "stream_result_to"; +const char kThrowOnFailureFlag[] = "throw_on_failure"; +const char kFlagfileFlag[] = "flagfile"; + +// A valid random seed must be in [1, kMaxRandomSeed]. +const int kMaxRandomSeed = 99999; + +// g_help_flag is true if and only if the --help flag or an equivalent form +// is specified on the command line. +GTEST_API_ extern bool g_help_flag; + +// Returns the current time in milliseconds. +GTEST_API_ TimeInMillis GetTimeInMillis(); + +// Returns true if and only if Google Test should use colors in the output. +GTEST_API_ bool ShouldUseColor(bool stdout_is_tty); + +// Formats the given time in milliseconds as seconds. +GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms); + +// Converts the given time in milliseconds to a date string in the ISO 8601 +// format, without the timezone information. N.B.: due to the use the +// non-reentrant localtime() function, this function is not thread safe. Do +// not use it in any code that can be called from multiple threads. +GTEST_API_ std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms); + +// Parses a string for an Int32 flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +GTEST_API_ bool ParseInt32Flag( + const char* str, const char* flag, int32_t* value); + +// Returns a random seed in range [1, kMaxRandomSeed] based on the +// given --gtest_random_seed flag value. +inline int GetRandomSeedFromFlag(int32_t random_seed_flag) { + const unsigned int raw_seed = (random_seed_flag == 0) ? + static_cast<unsigned int>(GetTimeInMillis()) : + static_cast<unsigned int>(random_seed_flag); + + // Normalizes the actual seed to range [1, kMaxRandomSeed] such that + // it's easy to type. + const int normalized_seed = + static_cast<int>((raw_seed - 1U) % + static_cast<unsigned int>(kMaxRandomSeed)) + 1; + return normalized_seed; +} + +// Returns the first valid random seed after 'seed'. The behavior is +// undefined if 'seed' is invalid. The seed after kMaxRandomSeed is +// considered to be 1. +inline int GetNextRandomSeed(int seed) { + GTEST_CHECK_(1 <= seed && seed <= kMaxRandomSeed) + << "Invalid random seed " << seed << " - must be in [1, " + << kMaxRandomSeed << "]."; + const int next_seed = seed + 1; + return (next_seed > kMaxRandomSeed) ? 1 : next_seed; +} + +// This class saves the values of all Google Test flags in its c'tor, and +// restores them in its d'tor. +class GTestFlagSaver { + public: + // The c'tor. + GTestFlagSaver() { + also_run_disabled_tests_ = GTEST_FLAG(also_run_disabled_tests); + break_on_failure_ = GTEST_FLAG(break_on_failure); + catch_exceptions_ = GTEST_FLAG(catch_exceptions); + color_ = GTEST_FLAG(color); + death_test_style_ = GTEST_FLAG(death_test_style); + death_test_use_fork_ = GTEST_FLAG(death_test_use_fork); + fail_fast_ = GTEST_FLAG(fail_fast); + filter_ = GTEST_FLAG(filter); + internal_run_death_test_ = GTEST_FLAG(internal_run_death_test); + list_tests_ = GTEST_FLAG(list_tests); + output_ = GTEST_FLAG(output); + brief_ = GTEST_FLAG(brief); + print_time_ = GTEST_FLAG(print_time); + print_utf8_ = GTEST_FLAG(print_utf8); + random_seed_ = GTEST_FLAG(random_seed); + repeat_ = GTEST_FLAG(repeat); + shuffle_ = GTEST_FLAG(shuffle); + stack_trace_depth_ = GTEST_FLAG(stack_trace_depth); + stream_result_to_ = GTEST_FLAG(stream_result_to); + throw_on_failure_ = GTEST_FLAG(throw_on_failure); + } + + // The d'tor is not virtual. DO NOT INHERIT FROM THIS CLASS. + ~GTestFlagSaver() { + GTEST_FLAG(also_run_disabled_tests) = also_run_disabled_tests_; + GTEST_FLAG(break_on_failure) = break_on_failure_; + GTEST_FLAG(catch_exceptions) = catch_exceptions_; + GTEST_FLAG(color) = color_; + GTEST_FLAG(death_test_style) = death_test_style_; + GTEST_FLAG(death_test_use_fork) = death_test_use_fork_; + GTEST_FLAG(filter) = filter_; + GTEST_FLAG(fail_fast) = fail_fast_; + GTEST_FLAG(internal_run_death_test) = internal_run_death_test_; + GTEST_FLAG(list_tests) = list_tests_; + GTEST_FLAG(output) = output_; + GTEST_FLAG(brief) = brief_; + GTEST_FLAG(print_time) = print_time_; + GTEST_FLAG(print_utf8) = print_utf8_; + GTEST_FLAG(random_seed) = random_seed_; + GTEST_FLAG(repeat) = repeat_; + GTEST_FLAG(shuffle) = shuffle_; + GTEST_FLAG(stack_trace_depth) = stack_trace_depth_; + GTEST_FLAG(stream_result_to) = stream_result_to_; + GTEST_FLAG(throw_on_failure) = throw_on_failure_; + } + + private: + // Fields for saving the original values of flags. + bool also_run_disabled_tests_; + bool break_on_failure_; + bool catch_exceptions_; + std::string color_; + std::string death_test_style_; + bool death_test_use_fork_; + bool fail_fast_; + std::string filter_; + std::string internal_run_death_test_; + bool list_tests_; + std::string output_; + bool brief_; + bool print_time_; + bool print_utf8_; + int32_t random_seed_; + int32_t repeat_; + bool shuffle_; + int32_t stack_trace_depth_; + std::string stream_result_to_; + bool throw_on_failure_; +} GTEST_ATTRIBUTE_UNUSED_; + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type UInt32 because wchar_t may not be +// wide enough to contain a code point. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted +// to "(Invalid Unicode 0xXXXXXXXX)". +GTEST_API_ std::string CodePointToUtf8(uint32_t code_point); + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +GTEST_API_ std::string WideStringToUtf8(const wchar_t* str, int num_chars); + +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded(); + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (e.g., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +GTEST_API_ bool ShouldShard(const char* total_shards_str, + const char* shard_index_str, + bool in_subprocess_for_death_test); + +// Parses the environment variable var as a 32-bit integer. If it is unset, +// returns default_val. If it is not a 32-bit integer, prints an error and +// and aborts. +GTEST_API_ int32_t Int32FromEnvOrDie(const char* env_var, int32_t default_val); + +// Given the total number of shards, the shard index, and the test id, +// returns true if and only if the test should be run on this shard. The test id +// is some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +GTEST_API_ bool ShouldRunTestOnShard( + int total_shards, int shard_index, int test_id); + +// STL container utilities. + +// Returns the number of elements in the given container that satisfy +// the given predicate. +template <class Container, typename Predicate> +inline int CountIf(const Container& c, Predicate predicate) { + // Implemented as an explicit loop since std::count_if() in libCstd on + // Solaris has a non-standard signature. + int count = 0; + for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) { + if (predicate(*it)) + ++count; + } + return count; +} + +// Applies a function/functor to each element in the container. +template <class Container, typename Functor> +void ForEach(const Container& c, Functor functor) { + std::for_each(c.begin(), c.end(), functor); +} + +// Returns the i-th element of the vector, or default_value if i is not +// in range [0, v.size()). +template <typename E> +inline E GetElementOr(const std::vector<E>& v, int i, E default_value) { + return (i < 0 || i >= static_cast<int>(v.size())) ? default_value + : v[static_cast<size_t>(i)]; +} + +// Performs an in-place shuffle of a range of the vector's elements. +// 'begin' and 'end' are element indices as an STL-style range; +// i.e. [begin, end) are shuffled, where 'end' == size() means to +// shuffle to the end of the vector. +template <typename E> +void ShuffleRange(internal::Random* random, int begin, int end, + std::vector<E>* v) { + const int size = static_cast<int>(v->size()); + GTEST_CHECK_(0 <= begin && begin <= size) + << "Invalid shuffle range start " << begin << ": must be in range [0, " + << size << "]."; + GTEST_CHECK_(begin <= end && end <= size) + << "Invalid shuffle range finish " << end << ": must be in range [" + << begin << ", " << size << "]."; + + // Fisher-Yates shuffle, from + // http://en.wikipedia.org/wiki/Fisher-Yates_shuffle + for (int range_width = end - begin; range_width >= 2; range_width--) { + const int last_in_range = begin + range_width - 1; + const int selected = + begin + + static_cast<int>(random->Generate(static_cast<uint32_t>(range_width))); + std::swap((*v)[static_cast<size_t>(selected)], + (*v)[static_cast<size_t>(last_in_range)]); + } +} + +// Performs an in-place shuffle of the vector's elements. +template <typename E> +inline void Shuffle(internal::Random* random, std::vector<E>* v) { + ShuffleRange(random, 0, static_cast<int>(v->size()), v); +} + +// A function for deleting an object. Handy for being used as a +// functor. +template <typename T> +static void Delete(T* x) { + delete x; +} + +// A predicate that checks the key of a TestProperty against a known key. +// +// TestPropertyKeyIs is copyable. +class TestPropertyKeyIs { + public: + // Constructor. + // + // TestPropertyKeyIs has NO default constructor. + explicit TestPropertyKeyIs(const std::string& key) : key_(key) {} + + // Returns true if and only if the test name of test property matches on key_. + bool operator()(const TestProperty& test_property) const { + return test_property.key() == key_; + } + + private: + std::string key_; +}; + +// Class UnitTestOptions. +// +// This class contains functions for processing options the user +// specifies when running the tests. It has only static members. +// +// In most cases, the user can specify an option using either an +// environment variable or a command line flag. E.g. you can set the +// test filter using either GTEST_FILTER or --gtest_filter. If both +// the variable and the flag are present, the latter overrides the +// former. +class GTEST_API_ UnitTestOptions { + public: + // Functions for processing the gtest_output flag. + + // Returns the output format, or "" for normal printed output. + static std::string GetOutputFormat(); + + // Returns the absolute path of the requested output file, or the + // default (test_detail.xml in the original working directory) if + // none was explicitly specified. + static std::string GetAbsolutePathToOutputFile(); + + // Functions for processing the gtest_filter flag. + + // Returns true if and only if the user-specified filter matches the test + // suite name and the test name. + static bool FilterMatchesTest(const std::string& test_suite_name, + const std::string& test_name); + +#if GTEST_OS_WINDOWS + // Function for supporting the gtest_catch_exception flag. + + // Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the + // given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. + // This function is useful as an __except condition. + static int GTestShouldProcessSEH(DWORD exception_code); +#endif // GTEST_OS_WINDOWS + + // Returns true if "name" matches the ':' separated list of glob-style + // filters in "filter". + static bool MatchesFilter(const std::string& name, const char* filter); +}; + +// Returns the current application's name, removing directory path if that +// is present. Used by UnitTestOptions::GetOutputFile. +GTEST_API_ FilePath GetCurrentExecutableName(); + +// The role interface for getting the OS stack trace as a string. +class OsStackTraceGetterInterface { + public: + OsStackTraceGetterInterface() {} + virtual ~OsStackTraceGetterInterface() {} + + // Returns the current OS stack trace as an std::string. Parameters: + // + // max_depth - the maximum number of stack frames to be included + // in the trace. + // skip_count - the number of top frames to be skipped; doesn't count + // against max_depth. + virtual std::string CurrentStackTrace(int max_depth, int skip_count) = 0; + + // UponLeavingGTest() should be called immediately before Google Test calls + // user code. It saves some information about the current stack that + // CurrentStackTrace() will use to find and hide Google Test stack frames. + virtual void UponLeavingGTest() = 0; + + // This string is inserted in place of stack frames that are part of + // Google Test's implementation. + static const char* const kElidedFramesMarker; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetterInterface); +}; + +// A working implementation of the OsStackTraceGetterInterface interface. +class OsStackTraceGetter : public OsStackTraceGetterInterface { + public: + OsStackTraceGetter() {} + + std::string CurrentStackTrace(int max_depth, int skip_count) override; + void UponLeavingGTest() override; + + private: +#if GTEST_HAS_ABSL + Mutex mutex_; // Protects all internal state. + + // We save the stack frame below the frame that calls user code. + // We do this because the address of the frame immediately below + // the user code changes between the call to UponLeavingGTest() + // and any calls to the stack trace code from within the user code. + void* caller_frame_ = nullptr; +#endif // GTEST_HAS_ABSL + + GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetter); +}; + +// Information about a Google Test trace point. +struct TraceInfo { + const char* file; + int line; + std::string message; +}; + +// This is the default global test part result reporter used in UnitTestImpl. +// This class should only be used by UnitTestImpl. +class DefaultGlobalTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultGlobalTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. Reports the test part + // result in the current test. + void ReportTestPartResult(const TestPartResult& result) override; + + private: + UnitTestImpl* const unit_test_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultGlobalTestPartResultReporter); +}; + +// This is the default per thread test part result reporter used in +// UnitTestImpl. This class should only be used by UnitTestImpl. +class DefaultPerThreadTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultPerThreadTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. The implementation just + // delegates to the current global test part result reporter of *unit_test_. + void ReportTestPartResult(const TestPartResult& result) override; + + private: + UnitTestImpl* const unit_test_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultPerThreadTestPartResultReporter); +}; + +// The private implementation of the UnitTest class. We don't protect +// the methods under a mutex, as this class is not accessible by a +// user and the UnitTest class that delegates work to this class does +// proper locking. +class GTEST_API_ UnitTestImpl { + public: + explicit UnitTestImpl(UnitTest* parent); + virtual ~UnitTestImpl(); + + // There are two different ways to register your own TestPartResultReporter. + // You can register your own repoter to listen either only for test results + // from the current thread or for results from all threads. + // By default, each per-thread test result repoter just passes a new + // TestPartResult to the global test result reporter, which registers the + // test part result for the currently running test. + + // Returns the global test part result reporter. + TestPartResultReporterInterface* GetGlobalTestPartResultReporter(); + + // Sets the global test part result reporter. + void SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter); + + // Returns the test part result reporter for the current thread. + TestPartResultReporterInterface* GetTestPartResultReporterForCurrentThread(); + + // Sets the test part result reporter for the current thread. + void SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter); + + // Gets the number of successful test suites. + int successful_test_suite_count() const; + + // Gets the number of failed test suites. + int failed_test_suite_count() const; + + // Gets the number of all test suites. + int total_test_suite_count() const; + + // Gets the number of all test suites that contain at least one test + // that should run. + int test_suite_to_run_count() const; + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of skipped tests. + int skipped_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests that will be reported in the XML report. + int reportable_disabled_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of tests to be printed in the XML report. + int reportable_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the time of the test program start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const { return start_timestamp_; } + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns true if and only if the unit test passed (i.e. all test suites + // passed). + bool Passed() const { return !Failed(); } + + // Returns true if and only if the unit test failed (i.e. some test suite + // failed or something outside of all tests failed). + bool Failed() const { + return failed_test_suite_count() > 0 || ad_hoc_test_result()->Failed(); + } + + // Gets the i-th test suite among all the test suites. i can range from 0 to + // total_test_suite_count() - 1. If i is not in that range, returns NULL. + const TestSuite* GetTestSuite(int i) const { + const int index = GetElementOr(test_suite_indices_, i, -1); + return index < 0 ? nullptr : test_suites_[static_cast<size_t>(i)]; + } + + // Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + const TestCase* GetTestCase(int i) const { return GetTestSuite(i); } +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Gets the i-th test suite among all the test suites. i can range from 0 to + // total_test_suite_count() - 1. If i is not in that range, returns NULL. + TestSuite* GetMutableSuiteCase(int i) { + const int index = GetElementOr(test_suite_indices_, i, -1); + return index < 0 ? nullptr : test_suites_[static_cast<size_t>(index)]; + } + + // Provides access to the event listener list. + TestEventListeners* listeners() { return &listeners_; } + + // Returns the TestResult for the test that's currently running, or + // the TestResult for the ad hoc test if no test is running. + TestResult* current_test_result(); + + // Returns the TestResult for the ad hoc test. + const TestResult* ad_hoc_test_result() const { return &ad_hoc_test_result_; } + + // Sets the OS stack trace getter. + // + // Does nothing if the input and the current OS stack trace getter + // are the same; otherwise, deletes the old getter and makes the + // input the current getter. + void set_os_stack_trace_getter(OsStackTraceGetterInterface* getter); + + // Returns the current OS stack trace getter if it is not NULL; + // otherwise, creates an OsStackTraceGetter, makes it the current + // getter, and returns it. + OsStackTraceGetterInterface* os_stack_trace_getter(); + + // Returns the current OS stack trace as an std::string. + // + // The maximum number of stack frames to be included is specified by + // the gtest_stack_trace_depth flag. The skip_count parameter + // specifies the number of top frames to be skipped, which doesn't + // count against the number of frames to be included. + // + // For example, if Foo() calls Bar(), which in turn calls + // CurrentOsStackTraceExceptTop(1), Foo() will be included in the + // trace but Bar() and CurrentOsStackTraceExceptTop() won't. + std::string CurrentOsStackTraceExceptTop(int skip_count) GTEST_NO_INLINE_; + + // Finds and returns a TestSuite with the given name. If one doesn't + // exist, creates one and returns it. + // + // Arguments: + // + // test_suite_name: name of the test suite + // type_param: the name of the test's type parameter, or NULL if + // this is not a typed or a type-parameterized test. + // set_up_tc: pointer to the function that sets up the test suite + // tear_down_tc: pointer to the function that tears down the test suite + TestSuite* GetTestSuite(const char* test_suite_name, const char* type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc); + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + TestCase* GetTestCase(const char* test_case_name, const char* type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc) { + return GetTestSuite(test_case_name, type_param, set_up_tc, tear_down_tc); + } +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Adds a TestInfo to the unit test. + // + // Arguments: + // + // set_up_tc: pointer to the function that sets up the test suite + // tear_down_tc: pointer to the function that tears down the test suite + // test_info: the TestInfo object + void AddTestInfo(internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc, + TestInfo* test_info) { +#if GTEST_HAS_DEATH_TEST + // In order to support thread-safe death tests, we need to + // remember the original working directory when the test program + // was first invoked. We cannot do this in RUN_ALL_TESTS(), as + // the user may have changed the current directory before calling + // RUN_ALL_TESTS(). Therefore we capture the current directory in + // AddTestInfo(), which is called to register a TEST or TEST_F + // before main() is reached. + if (original_working_dir_.IsEmpty()) { + original_working_dir_.Set(FilePath::GetCurrentDir()); + GTEST_CHECK_(!original_working_dir_.IsEmpty()) + << "Failed to get the current working directory."; + } +#endif // GTEST_HAS_DEATH_TEST + + GetTestSuite(test_info->test_suite_name(), test_info->type_param(), + set_up_tc, tear_down_tc) + ->AddTestInfo(test_info); + } + + // Returns ParameterizedTestSuiteRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + internal::ParameterizedTestSuiteRegistry& parameterized_test_registry() { + return parameterized_test_registry_; + } + + std::set<std::string>* ignored_parameterized_test_suites() { + return &ignored_parameterized_test_suites_; + } + + // Returns TypeParameterizedTestSuiteRegistry object used to keep track of + // type-parameterized tests and instantiations of them. + internal::TypeParameterizedTestSuiteRegistry& + type_parameterized_test_registry() { + return type_parameterized_test_registry_; + } + + // Sets the TestSuite object for the test that's currently running. + void set_current_test_suite(TestSuite* a_current_test_suite) { + current_test_suite_ = a_current_test_suite; + } + + // Sets the TestInfo object for the test that's currently running. If + // current_test_info is NULL, the assertion results will be stored in + // ad_hoc_test_result_. + void set_current_test_info(TestInfo* a_current_test_info) { + current_test_info_ = a_current_test_info; + } + + // Registers all parameterized tests defined using TEST_P and + // INSTANTIATE_TEST_SUITE_P, creating regular tests for each test/parameter + // combination. This method can be called more then once; it has guards + // protecting from registering the tests more then once. If + // value-parameterized tests are disabled, RegisterParameterizedTests is + // present but does nothing. + void RegisterParameterizedTests(); + + // Runs all tests in this UnitTest object, prints the result, and + // returns true if all tests are successful. If any exception is + // thrown during a test, this test is considered to be failed, but + // the rest of the tests will still be run. + bool RunAllTests(); + + // Clears the results of all tests, except the ad hoc tests. + void ClearNonAdHocTestResult() { + ForEach(test_suites_, TestSuite::ClearTestSuiteResult); + } + + // Clears the results of ad-hoc test assertions. + void ClearAdHocTestResult() { + ad_hoc_test_result_.Clear(); + } + + // Adds a TestProperty to the current TestResult object when invoked in a + // context of a test or a test suite, or to the global property set. If the + // result already contains a property with the same key, the value will be + // updated. + void RecordProperty(const TestProperty& test_property); + + enum ReactionToSharding { + HONOR_SHARDING_PROTOCOL, + IGNORE_SHARDING_PROTOCOL + }; + + // Matches the full name of each test against the user-specified + // filter to decide whether the test should run, then records the + // result in each TestSuite and TestInfo object. + // If shard_tests == HONOR_SHARDING_PROTOCOL, further filters tests + // based on sharding variables in the environment. + // Returns the number of tests that should run. + int FilterTests(ReactionToSharding shard_tests); + + // Prints the names of the tests matching the user-specified filter flag. + void ListTestsMatchingFilter(); + + const TestSuite* current_test_suite() const { return current_test_suite_; } + TestInfo* current_test_info() { return current_test_info_; } + const TestInfo* current_test_info() const { return current_test_info_; } + + // Returns the vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector<Environment*>& environments() { return environments_; } + + // Getters for the per-thread Google Test trace stack. + std::vector<TraceInfo>& gtest_trace_stack() { + return *(gtest_trace_stack_.pointer()); + } + const std::vector<TraceInfo>& gtest_trace_stack() const { + return gtest_trace_stack_.get(); + } + +#if GTEST_HAS_DEATH_TEST + void InitDeathTestSubprocessControlInfo() { + internal_run_death_test_flag_.reset(ParseInternalRunDeathTestFlag()); + } + // Returns a pointer to the parsed --gtest_internal_run_death_test + // flag, or NULL if that flag was not specified. + // This information is useful only in a death test child process. + // Must not be called before a call to InitGoogleTest. + const InternalRunDeathTestFlag* internal_run_death_test_flag() const { + return internal_run_death_test_flag_.get(); + } + + // Returns a pointer to the current death test factory. + internal::DeathTestFactory* death_test_factory() { + return death_test_factory_.get(); + } + + void SuppressTestEventsIfInSubprocess(); + + friend class ReplaceDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + + // Initializes the event listener performing XML output as specified by + // UnitTestOptions. Must not be called before InitGoogleTest. + void ConfigureXmlOutput(); + +#if GTEST_CAN_STREAM_RESULTS_ + // Initializes the event listener for streaming test results to a socket. + // Must not be called before InitGoogleTest. + void ConfigureStreamingOutput(); +#endif + + // Performs initialization dependent upon flag values obtained in + // ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to + // ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest + // this function is also called from RunAllTests. Since this function can be + // called more than once, it has to be idempotent. + void PostFlagParsingInit(); + + // Gets the random seed used at the start of the current test iteration. + int random_seed() const { return random_seed_; } + + // Gets the random number generator. + internal::Random* random() { return &random_; } + + // Shuffles all test suites, and the tests within each test suite, + // making sure that death tests are still run first. + void ShuffleTests(); + + // Restores the test suites and tests to their order before the first shuffle. + void UnshuffleTests(); + + // Returns the value of GTEST_FLAG(catch_exceptions) at the moment + // UnitTest::Run() starts. + bool catch_exceptions() const { return catch_exceptions_; } + + private: + friend class ::testing::UnitTest; + + // Used by UnitTest::Run() to capture the state of + // GTEST_FLAG(catch_exceptions) at the moment it starts. + void set_catch_exceptions(bool value) { catch_exceptions_ = value; } + + // The UnitTest object that owns this implementation object. + UnitTest* const parent_; + + // The working directory when the first TEST() or TEST_F() was + // executed. + internal::FilePath original_working_dir_; + + // The default test part result reporters. + DefaultGlobalTestPartResultReporter default_global_test_part_result_reporter_; + DefaultPerThreadTestPartResultReporter + default_per_thread_test_part_result_reporter_; + + // Points to (but doesn't own) the global test part result reporter. + TestPartResultReporterInterface* global_test_part_result_repoter_; + + // Protects read and write access to global_test_part_result_reporter_. + internal::Mutex global_test_part_result_reporter_mutex_; + + // Points to (but doesn't own) the per-thread test part result reporter. + internal::ThreadLocal<TestPartResultReporterInterface*> + per_thread_test_part_result_reporter_; + + // The vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector<Environment*> environments_; + + // The vector of TestSuites in their original order. It owns the + // elements in the vector. + std::vector<TestSuite*> test_suites_; + + // Provides a level of indirection for the test suite list to allow + // easy shuffling and restoring the test suite order. The i-th + // element of this vector is the index of the i-th test suite in the + // shuffled order. + std::vector<int> test_suite_indices_; + + // ParameterizedTestRegistry object used to register value-parameterized + // tests. + internal::ParameterizedTestSuiteRegistry parameterized_test_registry_; + internal::TypeParameterizedTestSuiteRegistry + type_parameterized_test_registry_; + + // The set holding the name of parameterized + // test suites that may go uninstantiated. + std::set<std::string> ignored_parameterized_test_suites_; + + // Indicates whether RegisterParameterizedTests() has been called already. + bool parameterized_tests_registered_; + + // Index of the last death test suite registered. Initially -1. + int last_death_test_suite_; + + // This points to the TestSuite for the currently running test. It + // changes as Google Test goes through one test suite after another. + // When no test is running, this is set to NULL and Google Test + // stores assertion results in ad_hoc_test_result_. Initially NULL. + TestSuite* current_test_suite_; + + // This points to the TestInfo for the currently running test. It + // changes as Google Test goes through one test after another. When + // no test is running, this is set to NULL and Google Test stores + // assertion results in ad_hoc_test_result_. Initially NULL. + TestInfo* current_test_info_; + + // Normally, a user only writes assertions inside a TEST or TEST_F, + // or inside a function called by a TEST or TEST_F. Since Google + // Test keeps track of which test is current running, it can + // associate such an assertion with the test it belongs to. + // + // If an assertion is encountered when no TEST or TEST_F is running, + // Google Test attributes the assertion result to an imaginary "ad hoc" + // test, and records the result in ad_hoc_test_result_. + TestResult ad_hoc_test_result_; + + // The list of event listeners that can be used to track events inside + // Google Test. + TestEventListeners listeners_; + + // The OS stack trace getter. Will be deleted when the UnitTest + // object is destructed. By default, an OsStackTraceGetter is used, + // but the user can set this field to use a custom getter if that is + // desired. + OsStackTraceGetterInterface* os_stack_trace_getter_; + + // True if and only if PostFlagParsingInit() has been called. + bool post_flag_parse_init_performed_; + + // The random number seed used at the beginning of the test run. + int random_seed_; + + // Our random number generator. + internal::Random random_; + + // The time of the test program start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp_; + + // How long the test took to run, in milliseconds. + TimeInMillis elapsed_time_; + +#if GTEST_HAS_DEATH_TEST + // The decomposed components of the gtest_internal_run_death_test flag, + // parsed when RUN_ALL_TESTS is called. + std::unique_ptr<InternalRunDeathTestFlag> internal_run_death_test_flag_; + std::unique_ptr<internal::DeathTestFactory> death_test_factory_; +#endif // GTEST_HAS_DEATH_TEST + + // A per-thread stack of traces created by the SCOPED_TRACE() macro. + internal::ThreadLocal<std::vector<TraceInfo> > gtest_trace_stack_; + + // The value of GTEST_FLAG(catch_exceptions) at the moment RunAllTests() + // starts. + bool catch_exceptions_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTestImpl); +}; // class UnitTestImpl + +// Convenience function for accessing the global UnitTest +// implementation object. +inline UnitTestImpl* GetUnitTestImpl() { + return UnitTest::GetInstance()->impl(); +} + +#if GTEST_USES_SIMPLE_RE + +// Internal helper functions for implementing the simple regular +// expression matcher. +GTEST_API_ bool IsInSet(char ch, const char* str); +GTEST_API_ bool IsAsciiDigit(char ch); +GTEST_API_ bool IsAsciiPunct(char ch); +GTEST_API_ bool IsRepeat(char ch); +GTEST_API_ bool IsAsciiWhiteSpace(char ch); +GTEST_API_ bool IsAsciiWordChar(char ch); +GTEST_API_ bool IsValidEscape(char ch); +GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch); +GTEST_API_ bool ValidateRegex(const char* regex); +GTEST_API_ bool MatchRegexAtHead(const char* regex, const char* str); +GTEST_API_ bool MatchRepetitionAndRegexAtHead( + bool escaped, char ch, char repeat, const char* regex, const char* str); +GTEST_API_ bool MatchRegexAnywhere(const char* regex, const char* str); + +#endif // GTEST_USES_SIMPLE_RE + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, char** argv); +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv); + +#if GTEST_HAS_DEATH_TEST + +// Returns the message describing the last system error, regardless of the +// platform. +GTEST_API_ std::string GetLastErrnoDescription(); + +// Attempts to parse a string into a positive integer pointed to by the +// number parameter. Returns true if that is possible. +// GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use +// it here. +template <typename Integer> +bool ParseNaturalNumber(const ::std::string& str, Integer* number) { + // Fail fast if the given string does not begin with a digit; + // this bypasses strtoXXX's "optional leading whitespace and plus + // or minus sign" semantics, which are undesirable here. + if (str.empty() || !IsDigit(str[0])) { + return false; + } + errno = 0; + + char* end; + // BiggestConvertible is the largest integer type that system-provided + // string-to-number conversion routines can return. + using BiggestConvertible = unsigned long long; // NOLINT + + const BiggestConvertible parsed = strtoull(str.c_str(), &end, 10); // NOLINT + const bool parse_success = *end == '\0' && errno == 0; + + GTEST_CHECK_(sizeof(Integer) <= sizeof(parsed)); + + const Integer result = static_cast<Integer>(parsed); + if (parse_success && static_cast<BiggestConvertible>(result) == parsed) { + *number = result; + return true; + } + return false; +} +#endif // GTEST_HAS_DEATH_TEST + +// TestResult contains some private methods that should be hidden from +// Google Test user but are required for testing. This class allow our tests +// to access them. +// +// This class is supplied only for the purpose of testing Google Test's own +// constructs. Do not use it in user tests, either directly or indirectly. +class TestResultAccessor { + public: + static void RecordProperty(TestResult* test_result, + const std::string& xml_element, + const TestProperty& property) { + test_result->RecordProperty(xml_element, property); + } + + static void ClearTestPartResults(TestResult* test_result) { + test_result->ClearTestPartResults(); + } + + static const std::vector<testing::TestPartResult>& test_part_results( + const TestResult& test_result) { + return test_result.test_part_results(); + } +}; + +#if GTEST_CAN_STREAM_RESULTS_ + +// Streams test results to the given port on the given host machine. +class StreamingListener : public EmptyTestEventListener { + public: + // Abstract base class for writing strings to a socket. + class AbstractSocketWriter { + public: + virtual ~AbstractSocketWriter() {} + + // Sends a string to the socket. + virtual void Send(const std::string& message) = 0; + + // Closes the socket. + virtual void CloseConnection() {} + + // Sends a string and a newline to the socket. + void SendLn(const std::string& message) { Send(message + "\n"); } + }; + + // Concrete class for actually writing strings to a socket. + class SocketWriter : public AbstractSocketWriter { + public: + SocketWriter(const std::string& host, const std::string& port) + : sockfd_(-1), host_name_(host), port_num_(port) { + MakeConnection(); + } + + ~SocketWriter() override { + if (sockfd_ != -1) + CloseConnection(); + } + + // Sends a string to the socket. + void Send(const std::string& message) override { + GTEST_CHECK_(sockfd_ != -1) + << "Send() can be called only when there is a connection."; + + const auto len = static_cast<size_t>(message.length()); + if (write(sockfd_, message.c_str(), len) != static_cast<ssize_t>(len)) { + GTEST_LOG_(WARNING) + << "stream_result_to: failed to stream to " + << host_name_ << ":" << port_num_; + } + } + + private: + // Creates a client socket and connects to the server. + void MakeConnection(); + + // Closes the socket. + void CloseConnection() override { + GTEST_CHECK_(sockfd_ != -1) + << "CloseConnection() can be called only when there is a connection."; + + close(sockfd_); + sockfd_ = -1; + } + + int sockfd_; // socket file descriptor + const std::string host_name_; + const std::string port_num_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(SocketWriter); + }; // class SocketWriter + + // Escapes '=', '&', '%', and '\n' characters in str as "%xx". + static std::string UrlEncode(const char* str); + + StreamingListener(const std::string& host, const std::string& port) + : socket_writer_(new SocketWriter(host, port)) { + Start(); + } + + explicit StreamingListener(AbstractSocketWriter* socket_writer) + : socket_writer_(socket_writer) { Start(); } + + void OnTestProgramStart(const UnitTest& /* unit_test */) override { + SendLn("event=TestProgramStart"); + } + + void OnTestProgramEnd(const UnitTest& unit_test) override { + // Note that Google Test current only report elapsed time for each + // test iteration, not for the entire test program. + SendLn("event=TestProgramEnd&passed=" + FormatBool(unit_test.Passed())); + + // Notify the streaming server to stop. + socket_writer_->CloseConnection(); + } + + void OnTestIterationStart(const UnitTest& /* unit_test */, + int iteration) override { + SendLn("event=TestIterationStart&iteration=" + + StreamableToString(iteration)); + } + + void OnTestIterationEnd(const UnitTest& unit_test, + int /* iteration */) override { + SendLn("event=TestIterationEnd&passed=" + + FormatBool(unit_test.Passed()) + "&elapsed_time=" + + StreamableToString(unit_test.elapsed_time()) + "ms"); + } + + // Note that "event=TestCaseStart" is a wire format and has to remain + // "case" for compatibility + void OnTestCaseStart(const TestCase& test_case) override { + SendLn(std::string("event=TestCaseStart&name=") + test_case.name()); + } + + // Note that "event=TestCaseEnd" is a wire format and has to remain + // "case" for compatibility + void OnTestCaseEnd(const TestCase& test_case) override { + SendLn("event=TestCaseEnd&passed=" + FormatBool(test_case.Passed()) + + "&elapsed_time=" + StreamableToString(test_case.elapsed_time()) + + "ms"); + } + + void OnTestStart(const TestInfo& test_info) override { + SendLn(std::string("event=TestStart&name=") + test_info.name()); + } + + void OnTestEnd(const TestInfo& test_info) override { + SendLn("event=TestEnd&passed=" + + FormatBool((test_info.result())->Passed()) + + "&elapsed_time=" + + StreamableToString((test_info.result())->elapsed_time()) + "ms"); + } + + void OnTestPartResult(const TestPartResult& test_part_result) override { + const char* file_name = test_part_result.file_name(); + if (file_name == nullptr) file_name = ""; + SendLn("event=TestPartResult&file=" + UrlEncode(file_name) + + "&line=" + StreamableToString(test_part_result.line_number()) + + "&message=" + UrlEncode(test_part_result.message())); + } + + private: + // Sends the given message and a newline to the socket. + void SendLn(const std::string& message) { socket_writer_->SendLn(message); } + + // Called at the start of streaming to notify the receiver what + // protocol we are using. + void Start() { SendLn("gtest_streaming_protocol_version=1.0"); } + + std::string FormatBool(bool value) { return value ? "1" : "0"; } + + const std::unique_ptr<AbstractSocketWriter> socket_writer_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamingListener); +}; // class StreamingListener + +#endif // GTEST_CAN_STREAM_RESULTS_ + +} // namespace internal +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_SRC_GTEST_INTERNAL_INL_H_ + +#if GTEST_OS_WINDOWS +# define vsnprintf _vsnprintf +#endif // GTEST_OS_WINDOWS + +#if GTEST_OS_MAC +#ifndef GTEST_OS_IOS +#include <crt_externs.h> +#endif +#endif + +#if GTEST_HAS_ABSL +#include "absl/debugging/failure_signal_handler.h" +#include "absl/debugging/stacktrace.h" +#include "absl/debugging/symbolize.h" +#include "absl/strings/str_cat.h" +#endif // GTEST_HAS_ABSL + +namespace testing { + +using internal::CountIf; +using internal::ForEach; +using internal::GetElementOr; +using internal::Shuffle; + +// Constants. + +// A test whose test suite name or test name matches this filter is +// disabled and not run. +static const char kDisableTestFilter[] = "DISABLED_*:*/DISABLED_*"; + +// A test suite whose name matches this filter is considered a death +// test suite and will be run before test suites whose name doesn't +// match this filter. +static const char kDeathTestSuiteFilter[] = "*DeathTest:*DeathTest/*"; + +// A test filter that matches everything. +static const char kUniversalFilter[] = "*"; + +// The default output format. +static const char kDefaultOutputFormat[] = "xml"; +// The default output file. +static const char kDefaultOutputFile[] = "test_detail"; + +// The environment variable name for the test shard index. +static const char kTestShardIndex[] = "GTEST_SHARD_INDEX"; +// The environment variable name for the total number of test shards. +static const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS"; +// The environment variable name for the test shard status file. +static const char kTestShardStatusFile[] = "GTEST_SHARD_STATUS_FILE"; + +namespace internal { + +// The text used in failure messages to indicate the start of the +// stack trace. +const char kStackTraceMarker[] = "\nStack trace:\n"; + +// g_help_flag is true if and only if the --help flag or an equivalent form +// is specified on the command line. +bool g_help_flag = false; + +// Utilty function to Open File for Writing +static FILE* OpenFileForWriting(const std::string& output_file) { + FILE* fileout = nullptr; + FilePath output_file_path(output_file); + FilePath output_dir(output_file_path.RemoveFileName()); + + if (output_dir.CreateDirectoriesRecursively()) { + fileout = posix::FOpen(output_file.c_str(), "w"); + } + if (fileout == nullptr) { + GTEST_LOG_(FATAL) << "Unable to open file \"" << output_file << "\""; + } + return fileout; +} + +} // namespace internal + +// Bazel passes in the argument to '--test_filter' via the TESTBRIDGE_TEST_ONLY +// environment variable. +static const char* GetDefaultFilter() { + const char* const testbridge_test_only = + internal::posix::GetEnv("TESTBRIDGE_TEST_ONLY"); + if (testbridge_test_only != nullptr) { + return testbridge_test_only; + } + return kUniversalFilter; +} + +// Bazel passes in the argument to '--test_runner_fail_fast' via the +// TESTBRIDGE_TEST_RUNNER_FAIL_FAST environment variable. +static bool GetDefaultFailFast() { + const char* const testbridge_test_runner_fail_fast = + internal::posix::GetEnv("TESTBRIDGE_TEST_RUNNER_FAIL_FAST"); + if (testbridge_test_runner_fail_fast != nullptr) { + return strcmp(testbridge_test_runner_fail_fast, "1") == 0; + } + return false; +} + +GTEST_DEFINE_bool_( + fail_fast, internal::BoolFromGTestEnv("fail_fast", GetDefaultFailFast()), + "True if and only if a test failure should stop further test execution."); + +GTEST_DEFINE_bool_( + also_run_disabled_tests, + internal::BoolFromGTestEnv("also_run_disabled_tests", false), + "Run disabled tests too, in addition to the tests normally being run."); + +GTEST_DEFINE_bool_( + break_on_failure, internal::BoolFromGTestEnv("break_on_failure", false), + "True if and only if a failed assertion should be a debugger " + "break-point."); + +GTEST_DEFINE_bool_(catch_exceptions, + internal::BoolFromGTestEnv("catch_exceptions", true), + "True if and only if " GTEST_NAME_ + " should catch exceptions and treat them as test failures."); + +GTEST_DEFINE_string_( + color, + internal::StringFromGTestEnv("color", "auto"), + "Whether to use colors in the output. Valid values: yes, no, " + "and auto. 'auto' means to use colors if the output is " + "being sent to a terminal and the TERM environment variable " + "is set to a terminal type that supports colors."); + +GTEST_DEFINE_string_( + filter, + internal::StringFromGTestEnv("filter", GetDefaultFilter()), + "A colon-separated list of glob (not regex) patterns " + "for filtering the tests to run, optionally followed by a " + "'-' and a : separated list of negative patterns (tests to " + "exclude). A test is run if it matches one of the positive " + "patterns and does not match any of the negative patterns."); + +GTEST_DEFINE_bool_( + install_failure_signal_handler, + internal::BoolFromGTestEnv("install_failure_signal_handler", false), + "If true and supported on the current platform, " GTEST_NAME_ " should " + "install a signal handler that dumps debugging information when fatal " + "signals are raised."); + +GTEST_DEFINE_bool_(list_tests, false, + "List all tests without running them."); + +// The net priority order after flag processing is thus: +// --gtest_output command line flag +// GTEST_OUTPUT environment variable +// XML_OUTPUT_FILE environment variable +// '' +GTEST_DEFINE_string_( + output, + internal::StringFromGTestEnv("output", + internal::OutputFlagAlsoCheckEnvVar().c_str()), + "A format (defaults to \"xml\" but can be specified to be \"json\"), " + "optionally followed by a colon and an output file name or directory. " + "A directory is indicated by a trailing pathname separator. " + "Examples: \"xml:filename.xml\", \"xml::directoryname/\". " + "If a directory is specified, output files will be created " + "within that directory, with file-names based on the test " + "executable's name and, if necessary, made unique by adding " + "digits."); + +GTEST_DEFINE_bool_( + brief, internal::BoolFromGTestEnv("brief", false), + "True if only test failures should be displayed in text output."); + +GTEST_DEFINE_bool_(print_time, internal::BoolFromGTestEnv("print_time", true), + "True if and only if " GTEST_NAME_ + " should display elapsed time in text output."); + +GTEST_DEFINE_bool_(print_utf8, internal::BoolFromGTestEnv("print_utf8", true), + "True if and only if " GTEST_NAME_ + " prints UTF8 characters as text."); + +GTEST_DEFINE_int32_( + random_seed, + internal::Int32FromGTestEnv("random_seed", 0), + "Random number seed to use when shuffling test orders. Must be in range " + "[1, 99999], or 0 to use a seed based on the current time."); + +GTEST_DEFINE_int32_( + repeat, + internal::Int32FromGTestEnv("repeat", 1), + "How many times to repeat each test. Specify a negative number " + "for repeating forever. Useful for shaking out flaky tests."); + +GTEST_DEFINE_bool_(show_internal_stack_frames, false, + "True if and only if " GTEST_NAME_ + " should include internal stack frames when " + "printing test failure stack traces."); + +GTEST_DEFINE_bool_(shuffle, internal::BoolFromGTestEnv("shuffle", false), + "True if and only if " GTEST_NAME_ + " should randomize tests' order on every run."); + +GTEST_DEFINE_int32_( + stack_trace_depth, + internal::Int32FromGTestEnv("stack_trace_depth", kMaxStackTraceDepth), + "The maximum number of stack frames to print when an " + "assertion fails. The valid range is 0 through 100, inclusive."); + +GTEST_DEFINE_string_( + stream_result_to, + internal::StringFromGTestEnv("stream_result_to", ""), + "This flag specifies the host name and the port number on which to stream " + "test results. Example: \"localhost:555\". The flag is effective only on " + "Linux."); + +GTEST_DEFINE_bool_( + throw_on_failure, + internal::BoolFromGTestEnv("throw_on_failure", false), + "When this flag is specified, a failed assertion will throw an exception " + "if exceptions are enabled or exit the program with a non-zero code " + "otherwise. For use with an external test framework."); + +#if GTEST_USE_OWN_FLAGFILE_FLAG_ +GTEST_DEFINE_string_( + flagfile, + internal::StringFromGTestEnv("flagfile", ""), + "This flag specifies the flagfile to read command-line flags from."); +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ + +namespace internal { + +// Generates a random number from [0, range), using a Linear +// Congruential Generator (LCG). Crashes if 'range' is 0 or greater +// than kMaxRange. +uint32_t Random::Generate(uint32_t range) { + // These constants are the same as are used in glibc's rand(3). + // Use wider types than necessary to prevent unsigned overflow diagnostics. + state_ = static_cast<uint32_t>(1103515245ULL*state_ + 12345U) % kMaxRange; + + GTEST_CHECK_(range > 0) + << "Cannot generate a number in the range [0, 0)."; + GTEST_CHECK_(range <= kMaxRange) + << "Generation of a number in [0, " << range << ") was requested, " + << "but this can only generate numbers in [0, " << kMaxRange << ")."; + + // Converting via modulus introduces a bit of downward bias, but + // it's simple, and a linear congruential generator isn't too good + // to begin with. + return state_ % range; +} + +// GTestIsInitialized() returns true if and only if the user has initialized +// Google Test. Useful for catching the user mistake of not initializing +// Google Test before calling RUN_ALL_TESTS(). +static bool GTestIsInitialized() { return GetArgvs().size() > 0; } + +// Iterates over a vector of TestSuites, keeping a running sum of the +// results of calling a given int-returning method on each. +// Returns the sum. +static int SumOverTestSuiteList(const std::vector<TestSuite*>& case_list, + int (TestSuite::*method)() const) { + int sum = 0; + for (size_t i = 0; i < case_list.size(); i++) { + sum += (case_list[i]->*method)(); + } + return sum; +} + +// Returns true if and only if the test suite passed. +static bool TestSuitePassed(const TestSuite* test_suite) { + return test_suite->should_run() && test_suite->Passed(); +} + +// Returns true if and only if the test suite failed. +static bool TestSuiteFailed(const TestSuite* test_suite) { + return test_suite->should_run() && test_suite->Failed(); +} + +// Returns true if and only if test_suite contains at least one test that +// should run. +static bool ShouldRunTestSuite(const TestSuite* test_suite) { + return test_suite->should_run(); +} + +// AssertHelper constructor. +AssertHelper::AssertHelper(TestPartResult::Type type, + const char* file, + int line, + const char* message) + : data_(new AssertHelperData(type, file, line, message)) { +} + +AssertHelper::~AssertHelper() { + delete data_; +} + +// Message assignment, for assertion streaming support. +void AssertHelper::operator=(const Message& message) const { + UnitTest::GetInstance()-> + AddTestPartResult(data_->type, data_->file, data_->line, + AppendUserMessage(data_->message, message), + UnitTest::GetInstance()->impl() + ->CurrentOsStackTraceExceptTop(1) + // Skips the stack frame for this function itself. + ); // NOLINT +} + +namespace { + +// When TEST_P is found without a matching INSTANTIATE_TEST_SUITE_P +// to creates test cases for it, a syntetic test case is +// inserted to report ether an error or a log message. +// +// This configuration bit will likely be removed at some point. +constexpr bool kErrorOnUninstantiatedParameterizedTest = true; +constexpr bool kErrorOnUninstantiatedTypeParameterizedTest = true; + +// A test that fails at a given file/line location with a given message. +class FailureTest : public Test { + public: + explicit FailureTest(const CodeLocation& loc, std::string error_message, + bool as_error) + : loc_(loc), + error_message_(std::move(error_message)), + as_error_(as_error) {} + + void TestBody() override { + if (as_error_) { + AssertHelper(TestPartResult::kNonFatalFailure, loc_.file.c_str(), + loc_.line, "") = Message() << error_message_; + } else { + std::cout << error_message_ << std::endl; + } + } + + private: + const CodeLocation loc_; + const std::string error_message_; + const bool as_error_; +}; + + +} // namespace + +std::set<std::string>* GetIgnoredParameterizedTestSuites() { + return UnitTest::GetInstance()->impl()->ignored_parameterized_test_suites(); +} + +// Add a given test_suit to the list of them allow to go un-instantiated. +MarkAsIgnored::MarkAsIgnored(const char* test_suite) { + GetIgnoredParameterizedTestSuites()->insert(test_suite); +} + +// If this parameterized test suite has no instantiations (and that +// has not been marked as okay), emit a test case reporting that. +void InsertSyntheticTestCase(const std::string& name, CodeLocation location, + bool has_test_p) { + const auto& ignored = *GetIgnoredParameterizedTestSuites(); + if (ignored.find(name) != ignored.end()) return; + + const char kMissingInstantiation[] = // + " is defined via TEST_P, but never instantiated. None of the test cases " + "will run. Either no INSTANTIATE_TEST_SUITE_P is provided or the only " + "ones provided expand to nothing." + "\n\n" + "Ideally, TEST_P definitions should only ever be included as part of " + "binaries that intend to use them. (As opposed to, for example, being " + "placed in a library that may be linked in to get other utilities.)"; + + const char kMissingTestCase[] = // + " is instantiated via INSTANTIATE_TEST_SUITE_P, but no tests are " + "defined via TEST_P . No test cases will run." + "\n\n" + "Ideally, INSTANTIATE_TEST_SUITE_P should only ever be invoked from " + "code that always depend on code that provides TEST_P. Failing to do " + "so is often an indication of dead code, e.g. the last TEST_P was " + "removed but the rest got left behind."; + + std::string message = + "Parameterized test suite " + name + + (has_test_p ? kMissingInstantiation : kMissingTestCase) + + "\n\n" + "To suppress this error for this test suite, insert the following line " + "(in a non-header) in the namespace it is defined in:" + "\n\n" + "GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(" + name + ");"; + + std::string full_name = "UninstantiatedParameterizedTestSuite<" + name + ">"; + RegisterTest( // + "GoogleTestVerification", full_name.c_str(), + nullptr, // No type parameter. + nullptr, // No value parameter. + location.file.c_str(), location.line, [message, location] { + return new FailureTest(location, message, + kErrorOnUninstantiatedParameterizedTest); + }); +} + +void RegisterTypeParameterizedTestSuite(const char* test_suite_name, + CodeLocation code_location) { + GetUnitTestImpl()->type_parameterized_test_registry().RegisterTestSuite( + test_suite_name, code_location); +} + +void RegisterTypeParameterizedTestSuiteInstantiation(const char* case_name) { + GetUnitTestImpl() + ->type_parameterized_test_registry() + .RegisterInstantiation(case_name); +} + +void TypeParameterizedTestSuiteRegistry::RegisterTestSuite( + const char* test_suite_name, CodeLocation code_location) { + suites_.emplace(std::string(test_suite_name), + TypeParameterizedTestSuiteInfo(code_location)); +} + +void TypeParameterizedTestSuiteRegistry::RegisterInstantiation( + const char* test_suite_name) { + auto it = suites_.find(std::string(test_suite_name)); + if (it != suites_.end()) { + it->second.instantiated = true; + } else { + GTEST_LOG_(ERROR) << "Unknown type parameterized test suit '" + << test_suite_name << "'"; + } +} + +void TypeParameterizedTestSuiteRegistry::CheckForInstantiations() { + const auto& ignored = *GetIgnoredParameterizedTestSuites(); + for (const auto& testcase : suites_) { + if (testcase.second.instantiated) continue; + if (ignored.find(testcase.first) != ignored.end()) continue; + + std::string message = + "Type parameterized test suite " + testcase.first + + " is defined via REGISTER_TYPED_TEST_SUITE_P, but never instantiated " + "via INSTANTIATE_TYPED_TEST_SUITE_P. None of the test cases will run." + "\n\n" + "Ideally, TYPED_TEST_P definitions should only ever be included as " + "part of binaries that intend to use them. (As opposed to, for " + "example, being placed in a library that may be linked in to get other " + "utilities.)" + "\n\n" + "To suppress this error for this test suite, insert the following line " + "(in a non-header) in the namespace it is defined in:" + "\n\n" + "GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(" + + testcase.first + ");"; + + std::string full_name = + "UninstantiatedTypeParameterizedTestSuite<" + testcase.first + ">"; + RegisterTest( // + "GoogleTestVerification", full_name.c_str(), + nullptr, // No type parameter. + nullptr, // No value parameter. + testcase.second.code_location.file.c_str(), + testcase.second.code_location.line, [message, testcase] { + return new FailureTest(testcase.second.code_location, message, + kErrorOnUninstantiatedTypeParameterizedTest); + }); + } +} + +// A copy of all command line arguments. Set by InitGoogleTest(). +static ::std::vector<std::string> g_argvs; + +::std::vector<std::string> GetArgvs() { +#if defined(GTEST_CUSTOM_GET_ARGVS_) + // GTEST_CUSTOM_GET_ARGVS_() may return a container of std::string or + // ::string. This code converts it to the appropriate type. + const auto& custom = GTEST_CUSTOM_GET_ARGVS_(); + return ::std::vector<std::string>(custom.begin(), custom.end()); +#else // defined(GTEST_CUSTOM_GET_ARGVS_) + return g_argvs; +#endif // defined(GTEST_CUSTOM_GET_ARGVS_) +} + +// Returns the current application's name, removing directory path if that +// is present. +FilePath GetCurrentExecutableName() { + FilePath result; + +#if GTEST_OS_WINDOWS || GTEST_OS_OS2 + result.Set(FilePath(GetArgvs()[0]).RemoveExtension("exe")); +#else + result.Set(FilePath(GetArgvs()[0])); +#endif // GTEST_OS_WINDOWS + + return result.RemoveDirectoryName(); +} + +// Functions for processing the gtest_output flag. + +// Returns the output format, or "" for normal printed output. +std::string UnitTestOptions::GetOutputFormat() { + const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); + const char* const colon = strchr(gtest_output_flag, ':'); + return (colon == nullptr) + ? std::string(gtest_output_flag) + : std::string(gtest_output_flag, + static_cast<size_t>(colon - gtest_output_flag)); +} + +// Returns the name of the requested output file, or the default if none +// was explicitly specified. +std::string UnitTestOptions::GetAbsolutePathToOutputFile() { + const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); + + std::string format = GetOutputFormat(); + if (format.empty()) + format = std::string(kDefaultOutputFormat); + + const char* const colon = strchr(gtest_output_flag, ':'); + if (colon == nullptr) + return internal::FilePath::MakeFileName( + internal::FilePath( + UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(kDefaultOutputFile), 0, + format.c_str()).string(); + + internal::FilePath output_name(colon + 1); + if (!output_name.IsAbsolutePath()) + output_name = internal::FilePath::ConcatPaths( + internal::FilePath(UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(colon + 1)); + + if (!output_name.IsDirectory()) + return output_name.string(); + + internal::FilePath result(internal::FilePath::GenerateUniqueFileName( + output_name, internal::GetCurrentExecutableName(), + GetOutputFormat().c_str())); + return result.string(); +} + +// Returns true if and only if the wildcard pattern matches the string. Each +// pattern consists of regular characters, single-character wildcards (?), and +// multi-character wildcards (*). +// +// This function implements a linear-time string globbing algorithm based on +// https://research.swtch.com/glob. +static bool PatternMatchesString(const std::string& name_str, + const char* pattern, const char* pattern_end) { + const char* name = name_str.c_str(); + const char* const name_begin = name; + const char* const name_end = name + name_str.size(); + + const char* pattern_next = pattern; + const char* name_next = name; + + while (pattern < pattern_end || name < name_end) { + if (pattern < pattern_end) { + switch (*pattern) { + default: // Match an ordinary character. + if (name < name_end && *name == *pattern) { + ++pattern; + ++name; + continue; + } + break; + case '?': // Match any single character. + if (name < name_end) { + ++pattern; + ++name; + continue; + } + break; + case '*': + // Match zero or more characters. Start by skipping over the wildcard + // and matching zero characters from name. If that fails, restart and + // match one more character than the last attempt. + pattern_next = pattern; + name_next = name + 1; + ++pattern; + continue; + } + } + // Failed to match a character. Restart if possible. + if (name_begin < name_next && name_next <= name_end) { + pattern = pattern_next; + name = name_next; + continue; + } + return false; + } + return true; +} + +bool UnitTestOptions::MatchesFilter(const std::string& name_str, + const char* filter) { + // The filter is a list of patterns separated by colons (:). + const char* pattern = filter; + while (true) { + // Find the bounds of this pattern. + const char* const next_sep = strchr(pattern, ':'); + const char* const pattern_end = + next_sep != nullptr ? next_sep : pattern + strlen(pattern); + + // Check if this pattern matches name_str. + if (PatternMatchesString(name_str, pattern, pattern_end)) { + return true; + } + + // Give up on this pattern. However, if we found a pattern separator (:), + // advance to the next pattern (skipping over the separator) and restart. + if (next_sep == nullptr) { + return false; + } + pattern = next_sep + 1; + } + return true; +} + +// Returns true if and only if the user-specified filter matches the test +// suite name and the test name. +bool UnitTestOptions::FilterMatchesTest(const std::string& test_suite_name, + const std::string& test_name) { + const std::string& full_name = test_suite_name + "." + test_name.c_str(); + + // Split --gtest_filter at '-', if there is one, to separate into + // positive filter and negative filter portions + const char* const p = GTEST_FLAG(filter).c_str(); + const char* const dash = strchr(p, '-'); + std::string positive; + std::string negative; + if (dash == nullptr) { + positive = GTEST_FLAG(filter).c_str(); // Whole string is a positive filter + negative = ""; + } else { + positive = std::string(p, dash); // Everything up to the dash + negative = std::string(dash + 1); // Everything after the dash + if (positive.empty()) { + // Treat '-test1' as the same as '*-test1' + positive = kUniversalFilter; + } + } + + // A filter is a colon-separated list of patterns. It matches a + // test if any pattern in it matches the test. + return (MatchesFilter(full_name, positive.c_str()) && + !MatchesFilter(full_name, negative.c_str())); +} + +#if GTEST_HAS_SEH +// Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the +// given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. +// This function is useful as an __except condition. +int UnitTestOptions::GTestShouldProcessSEH(DWORD exception_code) { + // Google Test should handle a SEH exception if: + // 1. the user wants it to, AND + // 2. this is not a breakpoint exception, AND + // 3. this is not a C++ exception (VC++ implements them via SEH, + // apparently). + // + // SEH exception code for C++ exceptions. + // (see http://support.microsoft.com/kb/185294 for more information). + const DWORD kCxxExceptionCode = 0xe06d7363; + + bool should_handle = true; + + if (!GTEST_FLAG(catch_exceptions)) + should_handle = false; + else if (exception_code == EXCEPTION_BREAKPOINT) + should_handle = false; + else if (exception_code == kCxxExceptionCode) + should_handle = false; + + return should_handle ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH; +} +#endif // GTEST_HAS_SEH + +} // namespace internal + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. Intercepts only failures from the current thread. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + TestPartResultArray* result) + : intercept_mode_(INTERCEPT_ONLY_CURRENT_THREAD), + result_(result) { + Init(); +} + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + InterceptMode intercept_mode, TestPartResultArray* result) + : intercept_mode_(intercept_mode), + result_(result) { + Init(); +} + +void ScopedFakeTestPartResultReporter::Init() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + old_reporter_ = impl->GetGlobalTestPartResultReporter(); + impl->SetGlobalTestPartResultReporter(this); + } else { + old_reporter_ = impl->GetTestPartResultReporterForCurrentThread(); + impl->SetTestPartResultReporterForCurrentThread(this); + } +} + +// The d'tor restores the test part result reporter used by Google Test +// before. +ScopedFakeTestPartResultReporter::~ScopedFakeTestPartResultReporter() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + impl->SetGlobalTestPartResultReporter(old_reporter_); + } else { + impl->SetTestPartResultReporterForCurrentThread(old_reporter_); + } +} + +// Increments the test part result count and remembers the result. +// This method is from the TestPartResultReporterInterface interface. +void ScopedFakeTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + result_->Append(result); +} + +namespace internal { + +// Returns the type ID of ::testing::Test. We should always call this +// instead of GetTypeId< ::testing::Test>() to get the type ID of +// testing::Test. This is to work around a suspected linker bug when +// using Google Test as a framework on Mac OS X. The bug causes +// GetTypeId< ::testing::Test>() to return different values depending +// on whether the call is from the Google Test framework itself or +// from user test code. GetTestTypeId() is guaranteed to always +// return the same value, as it always calls GetTypeId<>() from the +// gtest.cc, which is within the Google Test framework. +TypeId GetTestTypeId() { + return GetTypeId<Test>(); +} + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +extern const TypeId kTestTypeIdInGoogleTest = GetTestTypeId(); + +// This predicate-formatter checks that 'results' contains a test part +// failure of the given type and that the failure message contains the +// given substring. +static AssertionResult HasOneFailure(const char* /* results_expr */, + const char* /* type_expr */, + const char* /* substr_expr */, + const TestPartResultArray& results, + TestPartResult::Type type, + const std::string& substr) { + const std::string expected(type == TestPartResult::kFatalFailure ? + "1 fatal failure" : + "1 non-fatal failure"); + Message msg; + if (results.size() != 1) { + msg << "Expected: " << expected << "\n" + << " Actual: " << results.size() << " failures"; + for (int i = 0; i < results.size(); i++) { + msg << "\n" << results.GetTestPartResult(i); + } + return AssertionFailure() << msg; + } + + const TestPartResult& r = results.GetTestPartResult(0); + if (r.type() != type) { + return AssertionFailure() << "Expected: " << expected << "\n" + << " Actual:\n" + << r; + } + + if (strstr(r.message(), substr.c_str()) == nullptr) { + return AssertionFailure() << "Expected: " << expected << " containing \"" + << substr << "\"\n" + << " Actual:\n" + << r; + } + + return AssertionSuccess(); +} + +// The constructor of SingleFailureChecker remembers where to look up +// test part results, what type of failure we expect, and what +// substring the failure message should contain. +SingleFailureChecker::SingleFailureChecker(const TestPartResultArray* results, + TestPartResult::Type type, + const std::string& substr) + : results_(results), type_(type), substr_(substr) {} + +// The destructor of SingleFailureChecker verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +SingleFailureChecker::~SingleFailureChecker() { + EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_); +} + +DefaultGlobalTestPartResultReporter::DefaultGlobalTestPartResultReporter( + UnitTestImpl* unit_test) : unit_test_(unit_test) {} + +void DefaultGlobalTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->current_test_result()->AddTestPartResult(result); + unit_test_->listeners()->repeater()->OnTestPartResult(result); +} + +DefaultPerThreadTestPartResultReporter::DefaultPerThreadTestPartResultReporter( + UnitTestImpl* unit_test) : unit_test_(unit_test) {} + +void DefaultPerThreadTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->GetGlobalTestPartResultReporter()->ReportTestPartResult(result); +} + +// Returns the global test part result reporter. +TestPartResultReporterInterface* +UnitTestImpl::GetGlobalTestPartResultReporter() { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + return global_test_part_result_repoter_; +} + +// Sets the global test part result reporter. +void UnitTestImpl::SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter) { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + global_test_part_result_repoter_ = reporter; +} + +// Returns the test part result reporter for the current thread. +TestPartResultReporterInterface* +UnitTestImpl::GetTestPartResultReporterForCurrentThread() { + return per_thread_test_part_result_reporter_.get(); +} + +// Sets the test part result reporter for the current thread. +void UnitTestImpl::SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter) { + per_thread_test_part_result_reporter_.set(reporter); +} + +// Gets the number of successful test suites. +int UnitTestImpl::successful_test_suite_count() const { + return CountIf(test_suites_, TestSuitePassed); +} + +// Gets the number of failed test suites. +int UnitTestImpl::failed_test_suite_count() const { + return CountIf(test_suites_, TestSuiteFailed); +} + +// Gets the number of all test suites. +int UnitTestImpl::total_test_suite_count() const { + return static_cast<int>(test_suites_.size()); +} + +// Gets the number of all test suites that contain at least one test +// that should run. +int UnitTestImpl::test_suite_to_run_count() const { + return CountIf(test_suites_, ShouldRunTestSuite); +} + +// Gets the number of successful tests. +int UnitTestImpl::successful_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::successful_test_count); +} + +// Gets the number of skipped tests. +int UnitTestImpl::skipped_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::skipped_test_count); +} + +// Gets the number of failed tests. +int UnitTestImpl::failed_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::failed_test_count); +} + +// Gets the number of disabled tests that will be reported in the XML report. +int UnitTestImpl::reportable_disabled_test_count() const { + return SumOverTestSuiteList(test_suites_, + &TestSuite::reportable_disabled_test_count); +} + +// Gets the number of disabled tests. +int UnitTestImpl::disabled_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::disabled_test_count); +} + +// Gets the number of tests to be printed in the XML report. +int UnitTestImpl::reportable_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::reportable_test_count); +} + +// Gets the number of all tests. +int UnitTestImpl::total_test_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::total_test_count); +} + +// Gets the number of tests that should run. +int UnitTestImpl::test_to_run_count() const { + return SumOverTestSuiteList(test_suites_, &TestSuite::test_to_run_count); +} + +// Returns the current OS stack trace as an std::string. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// CurrentOsStackTraceExceptTop(1), Foo() will be included in the +// trace but Bar() and CurrentOsStackTraceExceptTop() won't. +std::string UnitTestImpl::CurrentOsStackTraceExceptTop(int skip_count) { + return os_stack_trace_getter()->CurrentStackTrace( + static_cast<int>(GTEST_FLAG(stack_trace_depth)), + skip_count + 1 + // Skips the user-specified number of frames plus this function + // itself. + ); // NOLINT +} + +// A helper class for measuring elapsed times. +class Timer { + public: + Timer() : start_(std::chrono::steady_clock::now()) {} + + // Return time elapsed in milliseconds since the timer was created. + TimeInMillis Elapsed() { + return std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::steady_clock::now() - start_) + .count(); + } + + private: + std::chrono::steady_clock::time_point start_; +}; + +// Returns a timestamp as milliseconds since the epoch. Note this time may jump +// around subject to adjustments by the system, to measure elapsed time use +// Timer instead. +TimeInMillis GetTimeInMillis() { + return std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::system_clock::now() - + std::chrono::system_clock::from_time_t(0)) + .count(); +} + +// Utilities + +// class String. + +#if GTEST_OS_WINDOWS_MOBILE +// Creates a UTF-16 wide string from the given ANSI string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the wide string, or NULL if the +// input is NULL. +LPCWSTR String::AnsiToUtf16(const char* ansi) { + if (!ansi) return nullptr; + const int length = strlen(ansi); + const int unicode_length = + MultiByteToWideChar(CP_ACP, 0, ansi, length, nullptr, 0); + WCHAR* unicode = new WCHAR[unicode_length + 1]; + MultiByteToWideChar(CP_ACP, 0, ansi, length, + unicode, unicode_length); + unicode[unicode_length] = 0; + return unicode; +} + +// Creates an ANSI string from the given wide string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the ANSI string, or NULL if the +// input is NULL. +const char* String::Utf16ToAnsi(LPCWSTR utf16_str) { + if (!utf16_str) return nullptr; + const int ansi_length = WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, nullptr, + 0, nullptr, nullptr); + char* ansi = new char[ansi_length + 1]; + WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, ansi, ansi_length, nullptr, + nullptr); + ansi[ansi_length] = 0; + return ansi; +} + +#endif // GTEST_OS_WINDOWS_MOBILE + +// Compares two C strings. Returns true if and only if they have the same +// content. +// +// Unlike strcmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CStringEquals(const char * lhs, const char * rhs) { + if (lhs == nullptr) return rhs == nullptr; + + if (rhs == nullptr) return false; + + return strcmp(lhs, rhs) == 0; +} + +#if GTEST_HAS_STD_WSTRING + +// Converts an array of wide chars to a narrow string using the UTF-8 +// encoding, and streams the result to the given Message object. +static void StreamWideCharsToMessage(const wchar_t* wstr, size_t length, + Message* msg) { + for (size_t i = 0; i != length; ) { // NOLINT + if (wstr[i] != L'\0') { + *msg << WideStringToUtf8(wstr + i, static_cast<int>(length - i)); + while (i != length && wstr[i] != L'\0') + i++; + } else { + *msg << '\0'; + i++; + } + } +} + +#endif // GTEST_HAS_STD_WSTRING + +void SplitString(const ::std::string& str, char delimiter, + ::std::vector< ::std::string>* dest) { + ::std::vector< ::std::string> parsed; + ::std::string::size_type pos = 0; + while (::testing::internal::AlwaysTrue()) { + const ::std::string::size_type colon = str.find(delimiter, pos); + if (colon == ::std::string::npos) { + parsed.push_back(str.substr(pos)); + break; + } else { + parsed.push_back(str.substr(pos, colon - pos)); + pos = colon + 1; + } + } + dest->swap(parsed); +} + +} // namespace internal + +// Constructs an empty Message. +// We allocate the stringstream separately because otherwise each use of +// ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's +// stack frame leading to huge stack frames in some cases; gcc does not reuse +// the stack space. +Message::Message() : ss_(new ::std::stringstream) { + // By default, we want there to be enough precision when printing + // a double to a Message. + *ss_ << std::setprecision(std::numeric_limits<double>::digits10 + 2); +} + +// These two overloads allow streaming a wide C string to a Message +// using the UTF-8 encoding. +Message& Message::operator <<(const wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); +} +Message& Message::operator <<(wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); +} + +#if GTEST_HAS_STD_WSTRING +// Converts the given wide string to a narrow string using the UTF-8 +// encoding, and streams the result to this Message object. +Message& Message::operator <<(const ::std::wstring& wstr) { + internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this); + return *this; +} +#endif // GTEST_HAS_STD_WSTRING + +// Gets the text streamed to this object so far as an std::string. +// Each '\0' character in the buffer is replaced with "\\0". +std::string Message::GetString() const { + return internal::StringStreamToString(ss_.get()); +} + +// AssertionResult constructors. +// Used in EXPECT_TRUE/FALSE(assertion_result). +AssertionResult::AssertionResult(const AssertionResult& other) + : success_(other.success_), + message_(other.message_.get() != nullptr + ? new ::std::string(*other.message_) + : static_cast< ::std::string*>(nullptr)) {} + +// Swaps two AssertionResults. +void AssertionResult::swap(AssertionResult& other) { + using std::swap; + swap(success_, other.success_); + swap(message_, other.message_); +} + +// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. +AssertionResult AssertionResult::operator!() const { + AssertionResult negation(!success_); + if (message_.get() != nullptr) negation << *message_; + return negation; +} + +// Makes a successful assertion result. +AssertionResult AssertionSuccess() { + return AssertionResult(true); +} + +// Makes a failed assertion result. +AssertionResult AssertionFailure() { + return AssertionResult(false); +} + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << message. +AssertionResult AssertionFailure(const Message& message) { + return AssertionFailure() << message; +} + +namespace internal { + +namespace edit_distance { +std::vector<EditType> CalculateOptimalEdits(const std::vector<size_t>& left, + const std::vector<size_t>& right) { + std::vector<std::vector<double> > costs( + left.size() + 1, std::vector<double>(right.size() + 1)); + std::vector<std::vector<EditType> > best_move( + left.size() + 1, std::vector<EditType>(right.size() + 1)); + + // Populate for empty right. + for (size_t l_i = 0; l_i < costs.size(); ++l_i) { + costs[l_i][0] = static_cast<double>(l_i); + best_move[l_i][0] = kRemove; + } + // Populate for empty left. + for (size_t r_i = 1; r_i < costs[0].size(); ++r_i) { + costs[0][r_i] = static_cast<double>(r_i); + best_move[0][r_i] = kAdd; + } + + for (size_t l_i = 0; l_i < left.size(); ++l_i) { + for (size_t r_i = 0; r_i < right.size(); ++r_i) { + if (left[l_i] == right[r_i]) { + // Found a match. Consume it. + costs[l_i + 1][r_i + 1] = costs[l_i][r_i]; + best_move[l_i + 1][r_i + 1] = kMatch; + continue; + } + + const double add = costs[l_i + 1][r_i]; + const double remove = costs[l_i][r_i + 1]; + const double replace = costs[l_i][r_i]; + if (add < remove && add < replace) { + costs[l_i + 1][r_i + 1] = add + 1; + best_move[l_i + 1][r_i + 1] = kAdd; + } else if (remove < add && remove < replace) { + costs[l_i + 1][r_i + 1] = remove + 1; + best_move[l_i + 1][r_i + 1] = kRemove; + } else { + // We make replace a little more expensive than add/remove to lower + // their priority. + costs[l_i + 1][r_i + 1] = replace + 1.00001; + best_move[l_i + 1][r_i + 1] = kReplace; + } + } + } + + // Reconstruct the best path. We do it in reverse order. + std::vector<EditType> best_path; + for (size_t l_i = left.size(), r_i = right.size(); l_i > 0 || r_i > 0;) { + EditType move = best_move[l_i][r_i]; + best_path.push_back(move); + l_i -= move != kAdd; + r_i -= move != kRemove; + } + std::reverse(best_path.begin(), best_path.end()); + return best_path; +} + +namespace { + +// Helper class to convert string into ids with deduplication. +class InternalStrings { + public: + size_t GetId(const std::string& str) { + IdMap::iterator it = ids_.find(str); + if (it != ids_.end()) return it->second; + size_t id = ids_.size(); + return ids_[str] = id; + } + + private: + typedef std::map<std::string, size_t> IdMap; + IdMap ids_; +}; + +} // namespace + +std::vector<EditType> CalculateOptimalEdits( + const std::vector<std::string>& left, + const std::vector<std::string>& right) { + std::vector<size_t> left_ids, right_ids; + { + InternalStrings intern_table; + for (size_t i = 0; i < left.size(); ++i) { + left_ids.push_back(intern_table.GetId(left[i])); + } + for (size_t i = 0; i < right.size(); ++i) { + right_ids.push_back(intern_table.GetId(right[i])); + } + } + return CalculateOptimalEdits(left_ids, right_ids); +} + +namespace { + +// Helper class that holds the state for one hunk and prints it out to the +// stream. +// It reorders adds/removes when possible to group all removes before all +// adds. It also adds the hunk header before printint into the stream. +class Hunk { + public: + Hunk(size_t left_start, size_t right_start) + : left_start_(left_start), + right_start_(right_start), + adds_(), + removes_(), + common_() {} + + void PushLine(char edit, const char* line) { + switch (edit) { + case ' ': + ++common_; + FlushEdits(); + hunk_.push_back(std::make_pair(' ', line)); + break; + case '-': + ++removes_; + hunk_removes_.push_back(std::make_pair('-', line)); + break; + case '+': + ++adds_; + hunk_adds_.push_back(std::make_pair('+', line)); + break; + } + } + + void PrintTo(std::ostream* os) { + PrintHeader(os); + FlushEdits(); + for (std::list<std::pair<char, const char*> >::const_iterator it = + hunk_.begin(); + it != hunk_.end(); ++it) { + *os << it->first << it->second << "\n"; + } + } + + bool has_edits() const { return adds_ || removes_; } + + private: + void FlushEdits() { + hunk_.splice(hunk_.end(), hunk_removes_); + hunk_.splice(hunk_.end(), hunk_adds_); + } + + // Print a unified diff header for one hunk. + // The format is + // "@@ -<left_start>,<left_length> +<right_start>,<right_length> @@" + // where the left/right parts are omitted if unnecessary. + void PrintHeader(std::ostream* ss) const { + *ss << "@@ "; + if (removes_) { + *ss << "-" << left_start_ << "," << (removes_ + common_); + } + if (removes_ && adds_) { + *ss << " "; + } + if (adds_) { + *ss << "+" << right_start_ << "," << (adds_ + common_); + } + *ss << " @@\n"; + } + + size_t left_start_, right_start_; + size_t adds_, removes_, common_; + std::list<std::pair<char, const char*> > hunk_, hunk_adds_, hunk_removes_; +}; + +} // namespace + +// Create a list of diff hunks in Unified diff format. +// Each hunk has a header generated by PrintHeader above plus a body with +// lines prefixed with ' ' for no change, '-' for deletion and '+' for +// addition. +// 'context' represents the desired unchanged prefix/suffix around the diff. +// If two hunks are close enough that their contexts overlap, then they are +// joined into one hunk. +std::string CreateUnifiedDiff(const std::vector<std::string>& left, + const std::vector<std::string>& right, + size_t context) { + const std::vector<EditType> edits = CalculateOptimalEdits(left, right); + + size_t l_i = 0, r_i = 0, edit_i = 0; + std::stringstream ss; + while (edit_i < edits.size()) { + // Find first edit. + while (edit_i < edits.size() && edits[edit_i] == kMatch) { + ++l_i; + ++r_i; + ++edit_i; + } + + // Find the first line to include in the hunk. + const size_t prefix_context = std::min(l_i, context); + Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1); + for (size_t i = prefix_context; i > 0; --i) { + hunk.PushLine(' ', left[l_i - i].c_str()); + } + + // Iterate the edits until we found enough suffix for the hunk or the input + // is over. + size_t n_suffix = 0; + for (; edit_i < edits.size(); ++edit_i) { + if (n_suffix >= context) { + // Continue only if the next hunk is very close. + auto it = edits.begin() + static_cast<int>(edit_i); + while (it != edits.end() && *it == kMatch) ++it; + if (it == edits.end() || + static_cast<size_t>(it - edits.begin()) - edit_i >= context) { + // There is no next edit or it is too far away. + break; + } + } + + EditType edit = edits[edit_i]; + // Reset count when a non match is found. + n_suffix = edit == kMatch ? n_suffix + 1 : 0; + + if (edit == kMatch || edit == kRemove || edit == kReplace) { + hunk.PushLine(edit == kMatch ? ' ' : '-', left[l_i].c_str()); + } + if (edit == kAdd || edit == kReplace) { + hunk.PushLine('+', right[r_i].c_str()); + } + + // Advance indices, depending on edit type. + l_i += edit != kAdd; + r_i += edit != kRemove; + } + + if (!hunk.has_edits()) { + // We are done. We don't want this hunk. + break; + } + + hunk.PrintTo(&ss); + } + return ss.str(); +} + +} // namespace edit_distance + +namespace { + +// The string representation of the values received in EqFailure() are already +// escaped. Split them on escaped '\n' boundaries. Leave all other escaped +// characters the same. +std::vector<std::string> SplitEscapedString(const std::string& str) { + std::vector<std::string> lines; + size_t start = 0, end = str.size(); + if (end > 2 && str[0] == '"' && str[end - 1] == '"') { + ++start; + --end; + } + bool escaped = false; + for (size_t i = start; i + 1 < end; ++i) { + if (escaped) { + escaped = false; + if (str[i] == 'n') { + lines.push_back(str.substr(start, i - start - 1)); + start = i + 1; + } + } else { + escaped = str[i] == '\\'; + } + } + lines.push_back(str.substr(start, end - start)); + return lines; +} + +} // namespace + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// lhs_expression: "foo" +// rhs_expression: "bar" +// lhs_value: "5" +// rhs_value: "6" +// +// The ignoring_case parameter is true if and only if the assertion is a +// *_STRCASEEQ*. When it's true, the string "Ignoring case" will +// be inserted into the message. +AssertionResult EqFailure(const char* lhs_expression, + const char* rhs_expression, + const std::string& lhs_value, + const std::string& rhs_value, + bool ignoring_case) { + Message msg; + msg << "Expected equality of these values:"; + msg << "\n " << lhs_expression; + if (lhs_value != lhs_expression) { + msg << "\n Which is: " << lhs_value; + } + msg << "\n " << rhs_expression; + if (rhs_value != rhs_expression) { + msg << "\n Which is: " << rhs_value; + } + + if (ignoring_case) { + msg << "\nIgnoring case"; + } + + if (!lhs_value.empty() && !rhs_value.empty()) { + const std::vector<std::string> lhs_lines = + SplitEscapedString(lhs_value); + const std::vector<std::string> rhs_lines = + SplitEscapedString(rhs_value); + if (lhs_lines.size() > 1 || rhs_lines.size() > 1) { + msg << "\nWith diff:\n" + << edit_distance::CreateUnifiedDiff(lhs_lines, rhs_lines); + } + } + + return AssertionFailure() << msg; +} + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +std::string GetBoolAssertionFailureMessage( + const AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value) { + const char* actual_message = assertion_result.message(); + Message msg; + msg << "Value of: " << expression_text + << "\n Actual: " << actual_predicate_value; + if (actual_message[0] != '\0') + msg << " (" << actual_message << ")"; + msg << "\nExpected: " << expected_predicate_value; + return msg.GetString(); +} + +// Helper function for implementing ASSERT_NEAR. +AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, + double val2, + double abs_error) { + const double diff = fabs(val1 - val2); + if (diff <= abs_error) return AssertionSuccess(); + + // Find the value which is closest to zero. + const double min_abs = std::min(fabs(val1), fabs(val2)); + // Find the distance to the next double from that value. + const double epsilon = + nextafter(min_abs, std::numeric_limits<double>::infinity()) - min_abs; + // Detect the case where abs_error is so small that EXPECT_NEAR is + // effectively the same as EXPECT_EQUAL, and give an informative error + // message so that the situation can be more easily understood without + // requiring exotic floating-point knowledge. + // Don't do an epsilon check if abs_error is zero because that implies + // that an equality check was actually intended. + if (!(std::isnan)(val1) && !(std::isnan)(val2) && abs_error > 0 && + abs_error < epsilon) { + return AssertionFailure() + << "The difference between " << expr1 << " and " << expr2 << " is " + << diff << ", where\n" + << expr1 << " evaluates to " << val1 << ",\n" + << expr2 << " evaluates to " << val2 << ".\nThe abs_error parameter " + << abs_error_expr << " evaluates to " << abs_error + << " which is smaller than the minimum distance between doubles for " + "numbers of this magnitude which is " + << epsilon + << ", thus making this EXPECT_NEAR check equivalent to " + "EXPECT_EQUAL. Consider using EXPECT_DOUBLE_EQ instead."; + } + return AssertionFailure() + << "The difference between " << expr1 << " and " << expr2 + << " is " << diff << ", which exceeds " << abs_error_expr << ", where\n" + << expr1 << " evaluates to " << val1 << ",\n" + << expr2 << " evaluates to " << val2 << ", and\n" + << abs_error_expr << " evaluates to " << abs_error << "."; +} + + +// Helper template for implementing FloatLE() and DoubleLE(). +template <typename RawType> +AssertionResult FloatingPointLE(const char* expr1, + const char* expr2, + RawType val1, + RawType val2) { + // Returns success if val1 is less than val2, + if (val1 < val2) { + return AssertionSuccess(); + } + + // or if val1 is almost equal to val2. + const FloatingPoint<RawType> lhs(val1), rhs(val2); + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + // Note that the above two checks will both fail if either val1 or + // val2 is NaN, as the IEEE floating-point standard requires that + // any predicate involving a NaN must return false. + + ::std::stringstream val1_ss; + val1_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2) + << val1; + + ::std::stringstream val2_ss; + val2_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2) + << val2; + + return AssertionFailure() + << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n" + << " Actual: " << StringStreamToString(&val1_ss) << " vs " + << StringStreamToString(&val2_ss); +} + +} // namespace internal + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2) { + return internal::FloatingPointLE<float>(expr1, expr2, val1, val2); +} + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2) { + return internal::FloatingPointLE<double>(expr1, expr2, val1, val2); +} + +namespace internal { + +// The helper function for {ASSERT|EXPECT}_STREQ. +AssertionResult CmpHelperSTREQ(const char* lhs_expression, + const char* rhs_expression, + const char* lhs, + const char* rhs) { + if (String::CStringEquals(lhs, rhs)) { + return AssertionSuccess(); + } + + return EqFailure(lhs_expression, + rhs_expression, + PrintToString(lhs), + PrintToString(rhs), + false); +} + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +AssertionResult CmpHelperSTRCASEEQ(const char* lhs_expression, + const char* rhs_expression, + const char* lhs, + const char* rhs) { + if (String::CaseInsensitiveCStringEquals(lhs, rhs)) { + return AssertionSuccess(); + } + + return EqFailure(lhs_expression, + rhs_expression, + PrintToString(lhs), + PrintToString(rhs), + true); +} + +// The helper function for {ASSERT|EXPECT}_STRNE. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2) { + if (!String::CStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + return AssertionFailure() << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: \"" + << s1 << "\" vs \"" << s2 << "\""; + } +} + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2) { + if (!String::CaseInsensitiveCStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + return AssertionFailure() + << "Expected: (" << s1_expression << ") != (" + << s2_expression << ") (ignoring case), actual: \"" + << s1 << "\" vs \"" << s2 << "\""; + } +} + +} // namespace internal + +namespace { + +// Helper functions for implementing IsSubString() and IsNotSubstring(). + +// This group of overloaded functions return true if and only if needle +// is a substring of haystack. NULL is considered a substring of +// itself only. + +bool IsSubstringPred(const char* needle, const char* haystack) { + if (needle == nullptr || haystack == nullptr) return needle == haystack; + + return strstr(haystack, needle) != nullptr; +} + +bool IsSubstringPred(const wchar_t* needle, const wchar_t* haystack) { + if (needle == nullptr || haystack == nullptr) return needle == haystack; + + return wcsstr(haystack, needle) != nullptr; +} + +// StringType here can be either ::std::string or ::std::wstring. +template <typename StringType> +bool IsSubstringPred(const StringType& needle, + const StringType& haystack) { + return haystack.find(needle) != StringType::npos; +} + +// This function implements either IsSubstring() or IsNotSubstring(), +// depending on the value of the expected_to_be_substring parameter. +// StringType here can be const char*, const wchar_t*, ::std::string, +// or ::std::wstring. +template <typename StringType> +AssertionResult IsSubstringImpl( + bool expected_to_be_substring, + const char* needle_expr, const char* haystack_expr, + const StringType& needle, const StringType& haystack) { + if (IsSubstringPred(needle, haystack) == expected_to_be_substring) + return AssertionSuccess(); + + const bool is_wide_string = sizeof(needle[0]) > 1; + const char* const begin_string_quote = is_wide_string ? "L\"" : "\""; + return AssertionFailure() + << "Value of: " << needle_expr << "\n" + << " Actual: " << begin_string_quote << needle << "\"\n" + << "Expected: " << (expected_to_be_substring ? "" : "not ") + << "a substring of " << haystack_expr << "\n" + << "Which is: " << begin_string_quote << haystack << "\""; +} + +} // namespace + +// IsSubstring() and IsNotSubstring() check whether needle is a +// substring of haystack (NULL is considered a substring of itself +// only), and return an appropriate error message when they fail. + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +#if GTEST_HAS_STD_WSTRING +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +#if GTEST_OS_WINDOWS + +namespace { + +// Helper function for IsHRESULT{SuccessFailure} predicates +AssertionResult HRESULTFailureHelper(const char* expr, + const char* expected, + long hr) { // NOLINT +# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_TV_TITLE + + // Windows CE doesn't support FormatMessage. + const char error_text[] = ""; + +# else + + // Looks up the human-readable system message for the HRESULT code + // and since we're not passing any params to FormatMessage, we don't + // want inserts expanded. + const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + const DWORD kBufSize = 4096; + // Gets the system's human readable message string for this HRESULT. + char error_text[kBufSize] = { '\0' }; + DWORD message_length = ::FormatMessageA(kFlags, + 0, // no source, we're asking system + static_cast<DWORD>(hr), // the error + 0, // no line width restrictions + error_text, // output buffer + kBufSize, // buf size + nullptr); // no arguments for inserts + // Trims tailing white space (FormatMessage leaves a trailing CR-LF) + for (; message_length && IsSpace(error_text[message_length - 1]); + --message_length) { + error_text[message_length - 1] = '\0'; + } + +# endif // GTEST_OS_WINDOWS_MOBILE + + const std::string error_hex("0x" + String::FormatHexInt(hr)); + return ::testing::AssertionFailure() + << "Expected: " << expr << " " << expected << ".\n" + << " Actual: " << error_hex << " " << error_text << "\n"; +} + +} // namespace + +AssertionResult IsHRESULTSuccess(const char* expr, long hr) { // NOLINT + if (SUCCEEDED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "succeeds", hr); +} + +AssertionResult IsHRESULTFailure(const char* expr, long hr) { // NOLINT + if (FAILED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "fails", hr); +} + +#endif // GTEST_OS_WINDOWS + +// Utility functions for encoding Unicode text (wide strings) in +// UTF-8. + +// A Unicode code-point can have up to 21 bits, and is encoded in UTF-8 +// like this: +// +// Code-point length Encoding +// 0 - 7 bits 0xxxxxxx +// 8 - 11 bits 110xxxxx 10xxxxxx +// 12 - 16 bits 1110xxxx 10xxxxxx 10xxxxxx +// 17 - 21 bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + +// The maximum code-point a one-byte UTF-8 sequence can represent. +constexpr uint32_t kMaxCodePoint1 = (static_cast<uint32_t>(1) << 7) - 1; + +// The maximum code-point a two-byte UTF-8 sequence can represent. +constexpr uint32_t kMaxCodePoint2 = (static_cast<uint32_t>(1) << (5 + 6)) - 1; + +// The maximum code-point a three-byte UTF-8 sequence can represent. +constexpr uint32_t kMaxCodePoint3 = (static_cast<uint32_t>(1) << (4 + 2*6)) - 1; + +// The maximum code-point a four-byte UTF-8 sequence can represent. +constexpr uint32_t kMaxCodePoint4 = (static_cast<uint32_t>(1) << (3 + 3*6)) - 1; + +// Chops off the n lowest bits from a bit pattern. Returns the n +// lowest bits. As a side effect, the original bit pattern will be +// shifted to the right by n bits. +inline uint32_t ChopLowBits(uint32_t* bits, int n) { + const uint32_t low_bits = *bits & ((static_cast<uint32_t>(1) << n) - 1); + *bits >>= n; + return low_bits; +} + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type uint32_t because wchar_t may not be +// wide enough to contain a code point. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted +// to "(Invalid Unicode 0xXXXXXXXX)". +std::string CodePointToUtf8(uint32_t code_point) { + if (code_point > kMaxCodePoint4) { + return "(Invalid Unicode 0x" + String::FormatHexUInt32(code_point) + ")"; + } + + char str[5]; // Big enough for the largest valid code point. + if (code_point <= kMaxCodePoint1) { + str[1] = '\0'; + str[0] = static_cast<char>(code_point); // 0xxxxxxx + } else if (code_point <= kMaxCodePoint2) { + str[2] = '\0'; + str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast<char>(0xC0 | code_point); // 110xxxxx + } else if (code_point <= kMaxCodePoint3) { + str[3] = '\0'; + str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast<char>(0xE0 | code_point); // 1110xxxx + } else { // code_point <= kMaxCodePoint4 + str[4] = '\0'; + str[3] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast<char>(0xF0 | code_point); // 11110xxx + } + return str; +} + +// The following two functions only make sense if the system +// uses UTF-16 for wide string encoding. All supported systems +// with 16 bit wchar_t (Windows, Cygwin) do use UTF-16. + +// Determines if the arguments constitute UTF-16 surrogate pair +// and thus should be combined into a single Unicode code point +// using CreateCodePointFromUtf16SurrogatePair. +inline bool IsUtf16SurrogatePair(wchar_t first, wchar_t second) { + return sizeof(wchar_t) == 2 && + (first & 0xFC00) == 0xD800 && (second & 0xFC00) == 0xDC00; +} + +// Creates a Unicode code point from UTF16 surrogate pair. +inline uint32_t CreateCodePointFromUtf16SurrogatePair(wchar_t first, + wchar_t second) { + const auto first_u = static_cast<uint32_t>(first); + const auto second_u = static_cast<uint32_t>(second); + const uint32_t mask = (1 << 10) - 1; + return (sizeof(wchar_t) == 2) + ? (((first_u & mask) << 10) | (second_u & mask)) + 0x10000 + : + // This function should not be called when the condition is + // false, but we provide a sensible default in case it is. + first_u; +} + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +std::string WideStringToUtf8(const wchar_t* str, int num_chars) { + if (num_chars == -1) + num_chars = static_cast<int>(wcslen(str)); + + ::std::stringstream stream; + for (int i = 0; i < num_chars; ++i) { + uint32_t unicode_code_point; + + if (str[i] == L'\0') { + break; + } else if (i + 1 < num_chars && IsUtf16SurrogatePair(str[i], str[i + 1])) { + unicode_code_point = CreateCodePointFromUtf16SurrogatePair(str[i], + str[i + 1]); + i++; + } else { + unicode_code_point = static_cast<uint32_t>(str[i]); + } + + stream << CodePointToUtf8(unicode_code_point); + } + return StringStreamToString(&stream); +} + +// Converts a wide C string to an std::string using the UTF-8 encoding. +// NULL will be converted to "(null)". +std::string String::ShowWideCString(const wchar_t * wide_c_str) { + if (wide_c_str == nullptr) return "(null)"; + + return internal::WideStringToUtf8(wide_c_str, -1); +} + +// Compares two wide C strings. Returns true if and only if they have the +// same content. +// +// Unlike wcscmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::WideCStringEquals(const wchar_t * lhs, const wchar_t * rhs) { + if (lhs == nullptr) return rhs == nullptr; + + if (rhs == nullptr) return false; + + return wcscmp(lhs, rhs) == 0; +} + +// Helper function for *_STREQ on wide strings. +AssertionResult CmpHelperSTREQ(const char* lhs_expression, + const char* rhs_expression, + const wchar_t* lhs, + const wchar_t* rhs) { + if (String::WideCStringEquals(lhs, rhs)) { + return AssertionSuccess(); + } + + return EqFailure(lhs_expression, + rhs_expression, + PrintToString(lhs), + PrintToString(rhs), + false); +} + +// Helper function for *_STRNE on wide strings. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2) { + if (!String::WideCStringEquals(s1, s2)) { + return AssertionSuccess(); + } + + return AssertionFailure() << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: " + << PrintToString(s1) + << " vs " << PrintToString(s2); +} + +// Compares two C strings, ignoring case. Returns true if and only if they have +// the same content. +// +// Unlike strcasecmp(), this function can handle NULL argument(s). A +// NULL C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CaseInsensitiveCStringEquals(const char * lhs, const char * rhs) { + if (lhs == nullptr) return rhs == nullptr; + if (rhs == nullptr) return false; + return posix::StrCaseCmp(lhs, rhs) == 0; +} + +// Compares two wide C strings, ignoring case. Returns true if and only if they +// have the same content. +// +// Unlike wcscasecmp(), this function can handle NULL argument(s). +// A NULL C string is considered different to any non-NULL wide C string, +// including the empty string. +// NB: The implementations on different platforms slightly differ. +// On windows, this method uses _wcsicmp which compares according to LC_CTYPE +// environment variable. On GNU platform this method uses wcscasecmp +// which compares according to LC_CTYPE category of the current locale. +// On MacOS X, it uses towlower, which also uses LC_CTYPE category of the +// current locale. +bool String::CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs) { + if (lhs == nullptr) return rhs == nullptr; + + if (rhs == nullptr) return false; + +#if GTEST_OS_WINDOWS + return _wcsicmp(lhs, rhs) == 0; +#elif GTEST_OS_LINUX && !GTEST_OS_LINUX_ANDROID + return wcscasecmp(lhs, rhs) == 0; +#else + // Android, Mac OS X and Cygwin don't define wcscasecmp. + // Other unknown OSes may not define it either. + wint_t left, right; + do { + left = towlower(static_cast<wint_t>(*lhs++)); + right = towlower(static_cast<wint_t>(*rhs++)); + } while (left && left == right); + return left == right; +#endif // OS selector +} + +// Returns true if and only if str ends with the given suffix, ignoring case. +// Any string is considered to end with an empty suffix. +bool String::EndsWithCaseInsensitive( + const std::string& str, const std::string& suffix) { + const size_t str_len = str.length(); + const size_t suffix_len = suffix.length(); + return (str_len >= suffix_len) && + CaseInsensitiveCStringEquals(str.c_str() + str_len - suffix_len, + suffix.c_str()); +} + +// Formats an int value as "%02d". +std::string String::FormatIntWidth2(int value) { + return FormatIntWidthN(value, 2); +} + +// Formats an int value to given width with leading zeros. +std::string String::FormatIntWidthN(int value, int width) { + std::stringstream ss; + ss << std::setfill('0') << std::setw(width) << value; + return ss.str(); +} + +// Formats an int value as "%X". +std::string String::FormatHexUInt32(uint32_t value) { + std::stringstream ss; + ss << std::hex << std::uppercase << value; + return ss.str(); +} + +// Formats an int value as "%X". +std::string String::FormatHexInt(int value) { + return FormatHexUInt32(static_cast<uint32_t>(value)); +} + +// Formats a byte as "%02X". +std::string String::FormatByte(unsigned char value) { + std::stringstream ss; + ss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase + << static_cast<unsigned int>(value); + return ss.str(); +} + +// Converts the buffer in a stringstream to an std::string, converting NUL +// bytes to "\\0" along the way. +std::string StringStreamToString(::std::stringstream* ss) { + const ::std::string& str = ss->str(); + const char* const start = str.c_str(); + const char* const end = start + str.length(); + + std::string result; + result.reserve(static_cast<size_t>(2 * (end - start))); + for (const char* ch = start; ch != end; ++ch) { + if (*ch == '\0') { + result += "\\0"; // Replaces NUL with "\\0"; + } else { + result += *ch; + } + } + + return result; +} + +// Appends the user-supplied message to the Google-Test-generated message. +std::string AppendUserMessage(const std::string& gtest_msg, + const Message& user_msg) { + // Appends the user message if it's non-empty. + const std::string user_msg_string = user_msg.GetString(); + if (user_msg_string.empty()) { + return gtest_msg; + } + if (gtest_msg.empty()) { + return user_msg_string; + } + return gtest_msg + "\n" + user_msg_string; +} + +} // namespace internal + +// class TestResult + +// Creates an empty TestResult. +TestResult::TestResult() + : death_test_count_(0), start_timestamp_(0), elapsed_time_(0) {} + +// D'tor. +TestResult::~TestResult() { +} + +// Returns the i-th test part result among all the results. i can +// range from 0 to total_part_count() - 1. If i is not in that range, +// aborts the program. +const TestPartResult& TestResult::GetTestPartResult(int i) const { + if (i < 0 || i >= total_part_count()) + internal::posix::Abort(); + return test_part_results_.at(static_cast<size_t>(i)); +} + +// Returns the i-th test property. i can range from 0 to +// test_property_count() - 1. If i is not in that range, aborts the +// program. +const TestProperty& TestResult::GetTestProperty(int i) const { + if (i < 0 || i >= test_property_count()) + internal::posix::Abort(); + return test_properties_.at(static_cast<size_t>(i)); +} + +// Clears the test part results. +void TestResult::ClearTestPartResults() { + test_part_results_.clear(); +} + +// Adds a test part result to the list. +void TestResult::AddTestPartResult(const TestPartResult& test_part_result) { + test_part_results_.push_back(test_part_result); +} + +// Adds a test property to the list. If a property with the same key as the +// supplied property is already represented, the value of this test_property +// replaces the old value for that key. +void TestResult::RecordProperty(const std::string& xml_element, + const TestProperty& test_property) { + if (!ValidateTestProperty(xml_element, test_property)) { + return; + } + internal::MutexLock lock(&test_properties_mutex_); + const std::vector<TestProperty>::iterator property_with_matching_key = + std::find_if(test_properties_.begin(), test_properties_.end(), + internal::TestPropertyKeyIs(test_property.key())); + if (property_with_matching_key == test_properties_.end()) { + test_properties_.push_back(test_property); + return; + } + property_with_matching_key->SetValue(test_property.value()); +} + +// The list of reserved attributes used in the <testsuites> element of XML +// output. +static const char* const kReservedTestSuitesAttributes[] = { + "disabled", + "errors", + "failures", + "name", + "random_seed", + "tests", + "time", + "timestamp" +}; + +// The list of reserved attributes used in the <testsuite> element of XML +// output. +static const char* const kReservedTestSuiteAttributes[] = { + "disabled", "errors", "failures", "name", + "tests", "time", "timestamp", "skipped"}; + +// The list of reserved attributes used in the <testcase> element of XML output. +static const char* const kReservedTestCaseAttributes[] = { + "classname", "name", "status", "time", "type_param", + "value_param", "file", "line"}; + +// Use a slightly different set for allowed output to ensure existing tests can +// still RecordProperty("result") or "RecordProperty(timestamp") +static const char* const kReservedOutputTestCaseAttributes[] = { + "classname", "name", "status", "time", "type_param", + "value_param", "file", "line", "result", "timestamp"}; + +template <size_t kSize> +std::vector<std::string> ArrayAsVector(const char* const (&array)[kSize]) { + return std::vector<std::string>(array, array + kSize); +} + +static std::vector<std::string> GetReservedAttributesForElement( + const std::string& xml_element) { + if (xml_element == "testsuites") { + return ArrayAsVector(kReservedTestSuitesAttributes); + } else if (xml_element == "testsuite") { + return ArrayAsVector(kReservedTestSuiteAttributes); + } else if (xml_element == "testcase") { + return ArrayAsVector(kReservedTestCaseAttributes); + } else { + GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element; + } + // This code is unreachable but some compilers may not realizes that. + return std::vector<std::string>(); +} + +// TODO(jdesprez): Merge the two getReserved attributes once skip is improved +static std::vector<std::string> GetReservedOutputAttributesForElement( + const std::string& xml_element) { + if (xml_element == "testsuites") { + return ArrayAsVector(kReservedTestSuitesAttributes); + } else if (xml_element == "testsuite") { + return ArrayAsVector(kReservedTestSuiteAttributes); + } else if (xml_element == "testcase") { + return ArrayAsVector(kReservedOutputTestCaseAttributes); + } else { + GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element; + } + // This code is unreachable but some compilers may not realizes that. + return std::vector<std::string>(); +} + +static std::string FormatWordList(const std::vector<std::string>& words) { + Message word_list; + for (size_t i = 0; i < words.size(); ++i) { + if (i > 0 && words.size() > 2) { + word_list << ", "; + } + if (i == words.size() - 1) { + word_list << "and "; + } + word_list << "'" << words[i] << "'"; + } + return word_list.GetString(); +} + +static bool ValidateTestPropertyName( + const std::string& property_name, + const std::vector<std::string>& reserved_names) { + if (std::find(reserved_names.begin(), reserved_names.end(), property_name) != + reserved_names.end()) { + ADD_FAILURE() << "Reserved key used in RecordProperty(): " << property_name + << " (" << FormatWordList(reserved_names) + << " are reserved by " << GTEST_NAME_ << ")"; + return false; + } + return true; +} + +// Adds a failure if the key is a reserved attribute of the element named +// xml_element. Returns true if the property is valid. +bool TestResult::ValidateTestProperty(const std::string& xml_element, + const TestProperty& test_property) { + return ValidateTestPropertyName(test_property.key(), + GetReservedAttributesForElement(xml_element)); +} + +// Clears the object. +void TestResult::Clear() { + test_part_results_.clear(); + test_properties_.clear(); + death_test_count_ = 0; + elapsed_time_ = 0; +} + +// Returns true off the test part was skipped. +static bool TestPartSkipped(const TestPartResult& result) { + return result.skipped(); +} + +// Returns true if and only if the test was skipped. +bool TestResult::Skipped() const { + return !Failed() && CountIf(test_part_results_, TestPartSkipped) > 0; +} + +// Returns true if and only if the test failed. +bool TestResult::Failed() const { + for (int i = 0; i < total_part_count(); ++i) { + if (GetTestPartResult(i).failed()) + return true; + } + return false; +} + +// Returns true if and only if the test part fatally failed. +static bool TestPartFatallyFailed(const TestPartResult& result) { + return result.fatally_failed(); +} + +// Returns true if and only if the test fatally failed. +bool TestResult::HasFatalFailure() const { + return CountIf(test_part_results_, TestPartFatallyFailed) > 0; +} + +// Returns true if and only if the test part non-fatally failed. +static bool TestPartNonfatallyFailed(const TestPartResult& result) { + return result.nonfatally_failed(); +} + +// Returns true if and only if the test has a non-fatal failure. +bool TestResult::HasNonfatalFailure() const { + return CountIf(test_part_results_, TestPartNonfatallyFailed) > 0; +} + +// Gets the number of all test parts. This is the sum of the number +// of successful test parts and the number of failed test parts. +int TestResult::total_part_count() const { + return static_cast<int>(test_part_results_.size()); +} + +// Returns the number of the test properties. +int TestResult::test_property_count() const { + return static_cast<int>(test_properties_.size()); +} + +// class Test + +// Creates a Test object. + +// The c'tor saves the states of all flags. +Test::Test() + : gtest_flag_saver_(new GTEST_FLAG_SAVER_) { +} + +// The d'tor restores the states of all flags. The actual work is +// done by the d'tor of the gtest_flag_saver_ field, and thus not +// visible here. +Test::~Test() { +} + +// Sets up the test fixture. +// +// A sub-class may override this. +void Test::SetUp() { +} + +// Tears down the test fixture. +// +// A sub-class may override this. +void Test::TearDown() { +} + +// Allows user supplied key value pairs to be recorded for later output. +void Test::RecordProperty(const std::string& key, const std::string& value) { + UnitTest::GetInstance()->RecordProperty(key, value); +} + +// Allows user supplied key value pairs to be recorded for later output. +void Test::RecordProperty(const std::string& key, int value) { + Message value_message; + value_message << value; + RecordProperty(key, value_message.GetString().c_str()); +} + +namespace internal { + +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const std::string& message) { + // This function is a friend of UnitTest and as such has access to + // AddTestPartResult. + UnitTest::GetInstance()->AddTestPartResult( + result_type, + nullptr, // No info about the source file where the exception occurred. + -1, // We have no info on which line caused the exception. + message, + ""); // No stack trace, either. +} + +} // namespace internal + +// Google Test requires all tests in the same test suite to use the same test +// fixture class. This function checks if the current test has the +// same fixture class as the first test in the current test suite. If +// yes, it returns true; otherwise it generates a Google Test failure and +// returns false. +bool Test::HasSameFixtureClass() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + const TestSuite* const test_suite = impl->current_test_suite(); + + // Info about the first test in the current test suite. + const TestInfo* const first_test_info = test_suite->test_info_list()[0]; + const internal::TypeId first_fixture_id = first_test_info->fixture_class_id_; + const char* const first_test_name = first_test_info->name(); + + // Info about the current test. + const TestInfo* const this_test_info = impl->current_test_info(); + const internal::TypeId this_fixture_id = this_test_info->fixture_class_id_; + const char* const this_test_name = this_test_info->name(); + + if (this_fixture_id != first_fixture_id) { + // Is the first test defined using TEST? + const bool first_is_TEST = first_fixture_id == internal::GetTestTypeId(); + // Is this test defined using TEST? + const bool this_is_TEST = this_fixture_id == internal::GetTestTypeId(); + + if (first_is_TEST || this_is_TEST) { + // Both TEST and TEST_F appear in same test suite, which is incorrect. + // Tell the user how to fix this. + + // Gets the name of the TEST and the name of the TEST_F. Note + // that first_is_TEST and this_is_TEST cannot both be true, as + // the fixture IDs are different for the two tests. + const char* const TEST_name = + first_is_TEST ? first_test_name : this_test_name; + const char* const TEST_F_name = + first_is_TEST ? this_test_name : first_test_name; + + ADD_FAILURE() + << "All tests in the same test suite must use the same test fixture\n" + << "class, so mixing TEST_F and TEST in the same test suite is\n" + << "illegal. In test suite " << this_test_info->test_suite_name() + << ",\n" + << "test " << TEST_F_name << " is defined using TEST_F but\n" + << "test " << TEST_name << " is defined using TEST. You probably\n" + << "want to change the TEST to TEST_F or move it to another test\n" + << "case."; + } else { + // Two fixture classes with the same name appear in two different + // namespaces, which is not allowed. Tell the user how to fix this. + ADD_FAILURE() + << "All tests in the same test suite must use the same test fixture\n" + << "class. However, in test suite " + << this_test_info->test_suite_name() << ",\n" + << "you defined test " << first_test_name << " and test " + << this_test_name << "\n" + << "using two different test fixture classes. This can happen if\n" + << "the two classes are from different namespaces or translation\n" + << "units and have the same name. You should probably rename one\n" + << "of the classes to put the tests into different test suites."; + } + return false; + } + + return true; +} + +#if GTEST_HAS_SEH + +// Adds an "exception thrown" fatal failure to the current test. This +// function returns its result via an output parameter pointer because VC++ +// prohibits creation of objects with destructors on stack in functions +// using __try (see error C2712). +static std::string* FormatSehExceptionMessage(DWORD exception_code, + const char* location) { + Message message; + message << "SEH exception with code 0x" << std::setbase(16) << + exception_code << std::setbase(10) << " thrown in " << location << "."; + + return new std::string(message.GetString()); +} + +#endif // GTEST_HAS_SEH + +namespace internal { + +#if GTEST_HAS_EXCEPTIONS + +// Adds an "exception thrown" fatal failure to the current test. +static std::string FormatCxxExceptionMessage(const char* description, + const char* location) { + Message message; + if (description != nullptr) { + message << "C++ exception with description \"" << description << "\""; + } else { + message << "Unknown C++ exception"; + } + message << " thrown in " << location << "."; + + return message.GetString(); +} + +static std::string PrintTestPartResultToString( + const TestPartResult& test_part_result); + +GoogleTestFailureException::GoogleTestFailureException( + const TestPartResult& failure) + : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {} + +#endif // GTEST_HAS_EXCEPTIONS + +// We put these helper functions in the internal namespace as IBM's xlC +// compiler rejects the code if they were declared static. + +// Runs the given method and handles SEH exceptions it throws, when +// SEH is supported; returns the 0-value for type Result in case of an +// SEH exception. (Microsoft compilers cannot handle SEH and C++ +// exceptions in the same function. Therefore, we provide a separate +// wrapper function for handling SEH exceptions.) +template <class T, typename Result> +Result HandleSehExceptionsInMethodIfSupported( + T* object, Result (T::*method)(), const char* location) { +#if GTEST_HAS_SEH + __try { + return (object->*method)(); + } __except (internal::UnitTestOptions::GTestShouldProcessSEH( // NOLINT + GetExceptionCode())) { + // We create the exception message on the heap because VC++ prohibits + // creation of objects with destructors on stack in functions using __try + // (see error C2712). + std::string* exception_message = FormatSehExceptionMessage( + GetExceptionCode(), location); + internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure, + *exception_message); + delete exception_message; + return static_cast<Result>(0); + } +#else + (void)location; + return (object->*method)(); +#endif // GTEST_HAS_SEH +} + +// Runs the given method and catches and reports C++ and/or SEH-style +// exceptions, if they are supported; returns the 0-value for type +// Result in case of an SEH exception. +template <class T, typename Result> +Result HandleExceptionsInMethodIfSupported( + T* object, Result (T::*method)(), const char* location) { + // NOTE: The user code can affect the way in which Google Test handles + // exceptions by setting GTEST_FLAG(catch_exceptions), but only before + // RUN_ALL_TESTS() starts. It is technically possible to check the flag + // after the exception is caught and either report or re-throw the + // exception based on the flag's value: + // + // try { + // // Perform the test method. + // } catch (...) { + // if (GTEST_FLAG(catch_exceptions)) + // // Report the exception as failure. + // else + // throw; // Re-throws the original exception. + // } + // + // However, the purpose of this flag is to allow the program to drop into + // the debugger when the exception is thrown. On most platforms, once the + // control enters the catch block, the exception origin information is + // lost and the debugger will stop the program at the point of the + // re-throw in this function -- instead of at the point of the original + // throw statement in the code under test. For this reason, we perform + // the check early, sacrificing the ability to affect Google Test's + // exception handling in the method where the exception is thrown. + if (internal::GetUnitTestImpl()->catch_exceptions()) { +#if GTEST_HAS_EXCEPTIONS + try { + return HandleSehExceptionsInMethodIfSupported(object, method, location); + } catch (const AssertionException&) { // NOLINT + // This failure was reported already. + } catch (const internal::GoogleTestFailureException&) { // NOLINT + // This exception type can only be thrown by a failed Google + // Test assertion with the intention of letting another testing + // framework catch it. Therefore we just re-throw it. + throw; + } catch (const std::exception& e) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(e.what(), location)); + } catch (...) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(nullptr, location)); + } + return static_cast<Result>(0); +#else + return HandleSehExceptionsInMethodIfSupported(object, method, location); +#endif // GTEST_HAS_EXCEPTIONS + } else { + return (object->*method)(); + } +} + +} // namespace internal + +// Runs the test and updates the test result. +void Test::Run() { + if (!HasSameFixtureClass()) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()"); + // We will run the test only if SetUp() was successful and didn't call + // GTEST_SKIP(). + if (!HasFatalFailure() && !IsSkipped()) { + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &Test::TestBody, "the test body"); + } + + // However, we want to clean up as much as possible. Hence we will + // always call TearDown(), even if SetUp() or the test body has + // failed. + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &Test::TearDown, "TearDown()"); +} + +// Returns true if and only if the current test has a fatal failure. +bool Test::HasFatalFailure() { + return internal::GetUnitTestImpl()->current_test_result()->HasFatalFailure(); +} + +// Returns true if and only if the current test has a non-fatal failure. +bool Test::HasNonfatalFailure() { + return internal::GetUnitTestImpl()->current_test_result()-> + HasNonfatalFailure(); +} + +// Returns true if and only if the current test was skipped. +bool Test::IsSkipped() { + return internal::GetUnitTestImpl()->current_test_result()->Skipped(); +} + +// class TestInfo + +// Constructs a TestInfo object. It assumes ownership of the test factory +// object. +TestInfo::TestInfo(const std::string& a_test_suite_name, + const std::string& a_name, const char* a_type_param, + const char* a_value_param, + internal::CodeLocation a_code_location, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory) + : test_suite_name_(a_test_suite_name), + name_(a_name), + type_param_(a_type_param ? new std::string(a_type_param) : nullptr), + value_param_(a_value_param ? new std::string(a_value_param) : nullptr), + location_(a_code_location), + fixture_class_id_(fixture_class_id), + should_run_(false), + is_disabled_(false), + matches_filter_(false), + is_in_another_shard_(false), + factory_(factory), + result_() {} + +// Destructs a TestInfo object. +TestInfo::~TestInfo() { delete factory_; } + +namespace internal { + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_suite_name: name of the test suite +// name: name of the test +// type_param: the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param: text representation of the test's value parameter, +// or NULL if this is not a value-parameterized test. +// code_location: code location where the test is defined +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test suite +// tear_down_tc: pointer to the function that tears down the test suite +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +TestInfo* MakeAndRegisterTestInfo( + const char* test_suite_name, const char* name, const char* type_param, + const char* value_param, CodeLocation code_location, + TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc, + TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory) { + TestInfo* const test_info = + new TestInfo(test_suite_name, name, type_param, value_param, + code_location, fixture_class_id, factory); + GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); + return test_info; +} + +void ReportInvalidTestSuiteType(const char* test_suite_name, + CodeLocation code_location) { + Message errors; + errors + << "Attempted redefinition of test suite " << test_suite_name << ".\n" + << "All tests in the same test suite must use the same test fixture\n" + << "class. However, in test suite " << test_suite_name << ", you tried\n" + << "to define a test using a fixture class different from the one\n" + << "used earlier. This can happen if the two fixture classes are\n" + << "from different namespaces and have the same name. You should\n" + << "probably rename one of the classes to put the tests into different\n" + << "test suites."; + + GTEST_LOG_(ERROR) << FormatFileLocation(code_location.file.c_str(), + code_location.line) + << " " << errors.GetString(); +} +} // namespace internal + +namespace { + +// A predicate that checks the test name of a TestInfo against a known +// value. +// +// This is used for implementation of the TestSuite class only. We put +// it in the anonymous namespace to prevent polluting the outer +// namespace. +// +// TestNameIs is copyable. +class TestNameIs { + public: + // Constructor. + // + // TestNameIs has NO default constructor. + explicit TestNameIs(const char* name) + : name_(name) {} + + // Returns true if and only if the test name of test_info matches name_. + bool operator()(const TestInfo * test_info) const { + return test_info && test_info->name() == name_; + } + + private: + std::string name_; +}; + +} // namespace + +namespace internal { + +// This method expands all parameterized tests registered with macros TEST_P +// and INSTANTIATE_TEST_SUITE_P into regular tests and registers those. +// This will be done just once during the program runtime. +void UnitTestImpl::RegisterParameterizedTests() { + if (!parameterized_tests_registered_) { + parameterized_test_registry_.RegisterTests(); + type_parameterized_test_registry_.CheckForInstantiations(); + parameterized_tests_registered_ = true; + } +} + +} // namespace internal + +// Creates the test object, runs it, records its result, and then +// deletes it. +void TestInfo::Run() { + if (!should_run_) return; + + // Tells UnitTest where to store test result. + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_info(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Notifies the unit test event listeners that a test is about to start. + repeater->OnTestStart(*this); + + result_.set_start_timestamp(internal::GetTimeInMillis()); + internal::Timer timer; + + impl->os_stack_trace_getter()->UponLeavingGTest(); + + // Creates the test object. + Test* const test = internal::HandleExceptionsInMethodIfSupported( + factory_, &internal::TestFactoryBase::CreateTest, + "the test fixture's constructor"); + + // Runs the test if the constructor didn't generate a fatal failure or invoke + // GTEST_SKIP(). + // Note that the object will not be null + if (!Test::HasFatalFailure() && !Test::IsSkipped()) { + // This doesn't throw as all user code that can throw are wrapped into + // exception handling code. + test->Run(); + } + + if (test != nullptr) { + // Deletes the test object. + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + test, &Test::DeleteSelf_, "the test fixture's destructor"); + } + + result_.set_elapsed_time(timer.Elapsed()); + + // Notifies the unit test event listener that a test has just finished. + repeater->OnTestEnd(*this); + + // Tells UnitTest to stop associating assertion results to this + // test. + impl->set_current_test_info(nullptr); +} + +// Skip and records a skipped test result for this object. +void TestInfo::Skip() { + if (!should_run_) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_info(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Notifies the unit test event listeners that a test is about to start. + repeater->OnTestStart(*this); + + const TestPartResult test_part_result = + TestPartResult(TestPartResult::kSkip, this->file(), this->line(), ""); + impl->GetTestPartResultReporterForCurrentThread()->ReportTestPartResult( + test_part_result); + + // Notifies the unit test event listener that a test has just finished. + repeater->OnTestEnd(*this); + impl->set_current_test_info(nullptr); +} + +// class TestSuite + +// Gets the number of successful tests in this test suite. +int TestSuite::successful_test_count() const { + return CountIf(test_info_list_, TestPassed); +} + +// Gets the number of successful tests in this test suite. +int TestSuite::skipped_test_count() const { + return CountIf(test_info_list_, TestSkipped); +} + +// Gets the number of failed tests in this test suite. +int TestSuite::failed_test_count() const { + return CountIf(test_info_list_, TestFailed); +} + +// Gets the number of disabled tests that will be reported in the XML report. +int TestSuite::reportable_disabled_test_count() const { + return CountIf(test_info_list_, TestReportableDisabled); +} + +// Gets the number of disabled tests in this test suite. +int TestSuite::disabled_test_count() const { + return CountIf(test_info_list_, TestDisabled); +} + +// Gets the number of tests to be printed in the XML report. +int TestSuite::reportable_test_count() const { + return CountIf(test_info_list_, TestReportable); +} + +// Get the number of tests in this test suite that should run. +int TestSuite::test_to_run_count() const { + return CountIf(test_info_list_, ShouldRunTest); +} + +// Gets the number of all tests. +int TestSuite::total_test_count() const { + return static_cast<int>(test_info_list_.size()); +} + +// Creates a TestSuite with the given name. +// +// Arguments: +// +// a_name: name of the test suite +// a_type_param: the name of the test suite's type parameter, or NULL if +// this is not a typed or a type-parameterized test suite. +// set_up_tc: pointer to the function that sets up the test suite +// tear_down_tc: pointer to the function that tears down the test suite +TestSuite::TestSuite(const char* a_name, const char* a_type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc) + : name_(a_name), + type_param_(a_type_param ? new std::string(a_type_param) : nullptr), + set_up_tc_(set_up_tc), + tear_down_tc_(tear_down_tc), + should_run_(false), + start_timestamp_(0), + elapsed_time_(0) {} + +// Destructor of TestSuite. +TestSuite::~TestSuite() { + // Deletes every Test in the collection. + ForEach(test_info_list_, internal::Delete<TestInfo>); +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +const TestInfo* TestSuite::GetTestInfo(int i) const { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? nullptr : test_info_list_[static_cast<size_t>(index)]; +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +TestInfo* TestSuite::GetMutableTestInfo(int i) { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? nullptr : test_info_list_[static_cast<size_t>(index)]; +} + +// Adds a test to this test suite. Will delete the test upon +// destruction of the TestSuite object. +void TestSuite::AddTestInfo(TestInfo* test_info) { + test_info_list_.push_back(test_info); + test_indices_.push_back(static_cast<int>(test_indices_.size())); +} + +// Runs every test in this TestSuite. +void TestSuite::Run() { + if (!should_run_) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_suite(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Call both legacy and the new API + repeater->OnTestSuiteStart(*this); +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + repeater->OnTestCaseStart(*this); +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestSuite::RunSetUpTestSuite, "SetUpTestSuite()"); + + start_timestamp_ = internal::GetTimeInMillis(); + internal::Timer timer; + for (int i = 0; i < total_test_count(); i++) { + GetMutableTestInfo(i)->Run(); + if (GTEST_FLAG(fail_fast) && GetMutableTestInfo(i)->result()->Failed()) { + for (int j = i + 1; j < total_test_count(); j++) { + GetMutableTestInfo(j)->Skip(); + } + break; + } + } + elapsed_time_ = timer.Elapsed(); + + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestSuite::RunTearDownTestSuite, "TearDownTestSuite()"); + + // Call both legacy and the new API + repeater->OnTestSuiteEnd(*this); +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + repeater->OnTestCaseEnd(*this); +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + impl->set_current_test_suite(nullptr); +} + +// Skips all tests under this TestSuite. +void TestSuite::Skip() { + if (!should_run_) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_suite(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Call both legacy and the new API + repeater->OnTestSuiteStart(*this); +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + repeater->OnTestCaseStart(*this); +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + for (int i = 0; i < total_test_count(); i++) { + GetMutableTestInfo(i)->Skip(); + } + + // Call both legacy and the new API + repeater->OnTestSuiteEnd(*this); + // Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + repeater->OnTestCaseEnd(*this); +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + impl->set_current_test_suite(nullptr); +} + +// Clears the results of all tests in this test suite. +void TestSuite::ClearResult() { + ad_hoc_test_result_.Clear(); + ForEach(test_info_list_, TestInfo::ClearTestResult); +} + +// Shuffles the tests in this test suite. +void TestSuite::ShuffleTests(internal::Random* random) { + Shuffle(random, &test_indices_); +} + +// Restores the test order to before the first shuffle. +void TestSuite::UnshuffleTests() { + for (size_t i = 0; i < test_indices_.size(); i++) { + test_indices_[i] = static_cast<int>(i); + } +} + +// Formats a countable noun. Depending on its quantity, either the +// singular form or the plural form is used. e.g. +// +// FormatCountableNoun(1, "formula", "formuli") returns "1 formula". +// FormatCountableNoun(5, "book", "books") returns "5 books". +static std::string FormatCountableNoun(int count, + const char * singular_form, + const char * plural_form) { + return internal::StreamableToString(count) + " " + + (count == 1 ? singular_form : plural_form); +} + +// Formats the count of tests. +static std::string FormatTestCount(int test_count) { + return FormatCountableNoun(test_count, "test", "tests"); +} + +// Formats the count of test suites. +static std::string FormatTestSuiteCount(int test_suite_count) { + return FormatCountableNoun(test_suite_count, "test suite", "test suites"); +} + +// Converts a TestPartResult::Type enum to human-friendly string +// representation. Both kNonFatalFailure and kFatalFailure are translated +// to "Failure", as the user usually doesn't care about the difference +// between the two when viewing the test result. +static const char * TestPartResultTypeToString(TestPartResult::Type type) { + switch (type) { + case TestPartResult::kSkip: + return "Skipped\n"; + case TestPartResult::kSuccess: + return "Success"; + + case TestPartResult::kNonFatalFailure: + case TestPartResult::kFatalFailure: +#ifdef _MSC_VER + return "error: "; +#else + return "Failure\n"; +#endif + default: + return "Unknown result type"; + } +} + +namespace internal { +namespace { +enum class GTestColor { kDefault, kRed, kGreen, kYellow }; +} // namespace + +// Prints a TestPartResult to an std::string. +static std::string PrintTestPartResultToString( + const TestPartResult& test_part_result) { + return (Message() + << internal::FormatFileLocation(test_part_result.file_name(), + test_part_result.line_number()) + << " " << TestPartResultTypeToString(test_part_result.type()) + << test_part_result.message()).GetString(); +} + +// Prints a TestPartResult. +static void PrintTestPartResult(const TestPartResult& test_part_result) { + const std::string& result = + PrintTestPartResultToString(test_part_result); + printf("%s\n", result.c_str()); + fflush(stdout); + // If the test program runs in Visual Studio or a debugger, the + // following statements add the test part result message to the Output + // window such that the user can double-click on it to jump to the + // corresponding source code location; otherwise they do nothing. +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + // We don't call OutputDebugString*() on Windows Mobile, as printing + // to stdout is done by OutputDebugString() there already - we don't + // want the same message printed twice. + ::OutputDebugStringA(result.c_str()); + ::OutputDebugStringA("\n"); +#endif +} + +// class PrettyUnitTestResultPrinter +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \ + !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT && !GTEST_OS_WINDOWS_MINGW + +// Returns the character attribute for the given color. +static WORD GetColorAttribute(GTestColor color) { + switch (color) { + case GTestColor::kRed: + return FOREGROUND_RED; + case GTestColor::kGreen: + return FOREGROUND_GREEN; + case GTestColor::kYellow: + return FOREGROUND_RED | FOREGROUND_GREEN; + default: return 0; + } +} + +static int GetBitOffset(WORD color_mask) { + if (color_mask == 0) return 0; + + int bitOffset = 0; + while ((color_mask & 1) == 0) { + color_mask >>= 1; + ++bitOffset; + } + return bitOffset; +} + +static WORD GetNewColor(GTestColor color, WORD old_color_attrs) { + // Let's reuse the BG + static const WORD background_mask = BACKGROUND_BLUE | BACKGROUND_GREEN | + BACKGROUND_RED | BACKGROUND_INTENSITY; + static const WORD foreground_mask = FOREGROUND_BLUE | FOREGROUND_GREEN | + FOREGROUND_RED | FOREGROUND_INTENSITY; + const WORD existing_bg = old_color_attrs & background_mask; + + WORD new_color = + GetColorAttribute(color) | existing_bg | FOREGROUND_INTENSITY; + static const int bg_bitOffset = GetBitOffset(background_mask); + static const int fg_bitOffset = GetBitOffset(foreground_mask); + + if (((new_color & background_mask) >> bg_bitOffset) == + ((new_color & foreground_mask) >> fg_bitOffset)) { + new_color ^= FOREGROUND_INTENSITY; // invert intensity + } + return new_color; +} + +#else + +// Returns the ANSI color code for the given color. GTestColor::kDefault is +// an invalid input. +static const char* GetAnsiColorCode(GTestColor color) { + switch (color) { + case GTestColor::kRed: + return "1"; + case GTestColor::kGreen: + return "2"; + case GTestColor::kYellow: + return "3"; + default: + return nullptr; + } +} + +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + +// Returns true if and only if Google Test should use colors in the output. +bool ShouldUseColor(bool stdout_is_tty) { + const char* const gtest_color = GTEST_FLAG(color).c_str(); + + if (String::CaseInsensitiveCStringEquals(gtest_color, "auto")) { +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW + // On Windows the TERM variable is usually not set, but the + // console there does support colors. + return stdout_is_tty; +#else + // On non-Windows platforms, we rely on the TERM variable. + const char* const term = posix::GetEnv("TERM"); + const bool term_supports_color = + String::CStringEquals(term, "xterm") || + String::CStringEquals(term, "xterm-color") || + String::CStringEquals(term, "xterm-256color") || + String::CStringEquals(term, "screen") || + String::CStringEquals(term, "screen-256color") || + String::CStringEquals(term, "tmux") || + String::CStringEquals(term, "tmux-256color") || + String::CStringEquals(term, "rxvt-unicode") || + String::CStringEquals(term, "rxvt-unicode-256color") || + String::CStringEquals(term, "linux") || + String::CStringEquals(term, "cygwin"); + return stdout_is_tty && term_supports_color; +#endif // GTEST_OS_WINDOWS + } + + return String::CaseInsensitiveCStringEquals(gtest_color, "yes") || + String::CaseInsensitiveCStringEquals(gtest_color, "true") || + String::CaseInsensitiveCStringEquals(gtest_color, "t") || + String::CStringEquals(gtest_color, "1"); + // We take "yes", "true", "t", and "1" as meaning "yes". If the + // value is neither one of these nor "auto", we treat it as "no" to + // be conservative. +} + +// Helpers for printing colored strings to stdout. Note that on Windows, we +// cannot simply emit special characters and have the terminal change colors. +// This routine must actually emit the characters rather than return a string +// that would be colored when printed, as can be done on Linux. + +GTEST_ATTRIBUTE_PRINTF_(2, 3) +static void ColoredPrintf(GTestColor color, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_ZOS || GTEST_OS_IOS || \ + GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT || defined(ESP_PLATFORM) + const bool use_color = AlwaysFalse(); +#else + static const bool in_color_mode = + ShouldUseColor(posix::IsATTY(posix::FileNo(stdout)) != 0); + const bool use_color = in_color_mode && (color != GTestColor::kDefault); +#endif // GTEST_OS_WINDOWS_MOBILE || GTEST_OS_ZOS + + if (!use_color) { + vprintf(fmt, args); + va_end(args); + return; + } + +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \ + !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT && !GTEST_OS_WINDOWS_MINGW + const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); + + // Gets the current text color. + CONSOLE_SCREEN_BUFFER_INFO buffer_info; + GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); + const WORD old_color_attrs = buffer_info.wAttributes; + const WORD new_color = GetNewColor(color, old_color_attrs); + + // We need to flush the stream buffers into the console before each + // SetConsoleTextAttribute call lest it affect the text that is already + // printed but has not yet reached the console. + fflush(stdout); + SetConsoleTextAttribute(stdout_handle, new_color); + + vprintf(fmt, args); + + fflush(stdout); + // Restores the text color. + SetConsoleTextAttribute(stdout_handle, old_color_attrs); +#else + printf("\033[0;3%sm", GetAnsiColorCode(color)); + vprintf(fmt, args); + printf("\033[m"); // Resets the terminal to default. +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + va_end(args); +} + +// Text printed in Google Test's text output and --gtest_list_tests +// output to label the type parameter and value parameter for a test. +static const char kTypeParamLabel[] = "TypeParam"; +static const char kValueParamLabel[] = "GetParam()"; + +static void PrintFullTestCommentIfPresent(const TestInfo& test_info) { + const char* const type_param = test_info.type_param(); + const char* const value_param = test_info.value_param(); + + if (type_param != nullptr || value_param != nullptr) { + printf(", where "); + if (type_param != nullptr) { + printf("%s = %s", kTypeParamLabel, type_param); + if (value_param != nullptr) printf(" and "); + } + if (value_param != nullptr) { + printf("%s = %s", kValueParamLabel, value_param); + } + } +} + +// This class implements the TestEventListener interface. +// +// Class PrettyUnitTestResultPrinter is copyable. +class PrettyUnitTestResultPrinter : public TestEventListener { + public: + PrettyUnitTestResultPrinter() {} + static void PrintTestName(const char* test_suite, const char* test) { + printf("%s.%s", test_suite, test); + } + + // The following methods override what's in the TestEventListener class. + void OnTestProgramStart(const UnitTest& /*unit_test*/) override {} + void OnTestIterationStart(const UnitTest& unit_test, int iteration) override; + void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override; + void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {} +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseStart(const TestCase& test_case) override; +#else + void OnTestSuiteStart(const TestSuite& test_suite) override; +#endif // OnTestCaseStart + + void OnTestStart(const TestInfo& test_info) override; + + void OnTestPartResult(const TestPartResult& result) override; + void OnTestEnd(const TestInfo& test_info) override; +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseEnd(const TestCase& test_case) override; +#else + void OnTestSuiteEnd(const TestSuite& test_suite) override; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override; + void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {} + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {} + + private: + static void PrintFailedTests(const UnitTest& unit_test); + static void PrintFailedTestSuites(const UnitTest& unit_test); + static void PrintSkippedTests(const UnitTest& unit_test); +}; + + // Fired before each iteration of tests starts. +void PrettyUnitTestResultPrinter::OnTestIterationStart( + const UnitTest& unit_test, int iteration) { + if (GTEST_FLAG(repeat) != 1) + printf("\nRepeating all tests (iteration %d) . . .\n\n", iteration + 1); + + const char* const filter = GTEST_FLAG(filter).c_str(); + + // Prints the filter if it's not *. This reminds the user that some + // tests may be skipped. + if (!String::CStringEquals(filter, kUniversalFilter)) { + ColoredPrintf(GTestColor::kYellow, "Note: %s filter = %s\n", GTEST_NAME_, + filter); + } + + if (internal::ShouldShard(kTestTotalShards, kTestShardIndex, false)) { + const int32_t shard_index = Int32FromEnvOrDie(kTestShardIndex, -1); + ColoredPrintf(GTestColor::kYellow, "Note: This is test shard %d of %s.\n", + static_cast<int>(shard_index) + 1, + internal::posix::GetEnv(kTestTotalShards)); + } + + if (GTEST_FLAG(shuffle)) { + ColoredPrintf(GTestColor::kYellow, + "Note: Randomizing tests' orders with a seed of %d .\n", + unit_test.random_seed()); + } + + ColoredPrintf(GTestColor::kGreen, "[==========] "); + printf("Running %s from %s.\n", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str()); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnEnvironmentsSetUpStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("Global test environment set-up.\n"); + fflush(stdout); +} + +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestCase& test_case) { + const std::string counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("%s from %s", counts.c_str(), test_case.name()); + if (test_case.type_param() == nullptr) { + printf("\n"); + } else { + printf(", where %s = %s\n", kTypeParamLabel, test_case.type_param()); + } + fflush(stdout); +} +#else +void PrettyUnitTestResultPrinter::OnTestSuiteStart( + const TestSuite& test_suite) { + const std::string counts = + FormatCountableNoun(test_suite.test_to_run_count(), "test", "tests"); + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("%s from %s", counts.c_str(), test_suite.name()); + if (test_suite.type_param() == nullptr) { + printf("\n"); + } else { + printf(", where %s = %s\n", kTypeParamLabel, test_suite.type_param()); + } + fflush(stdout); +} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) { + ColoredPrintf(GTestColor::kGreen, "[ RUN ] "); + PrintTestName(test_info.test_suite_name(), test_info.name()); + printf("\n"); + fflush(stdout); +} + +// Called after an assertion failure. +void PrettyUnitTestResultPrinter::OnTestPartResult( + const TestPartResult& result) { + switch (result.type()) { + // If the test part succeeded, we don't need to do anything. + case TestPartResult::kSuccess: + return; + default: + // Print failure message from the assertion + // (e.g. expected this and got that). + PrintTestPartResult(result); + fflush(stdout); + } +} + +void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { + if (test_info.result()->Passed()) { + ColoredPrintf(GTestColor::kGreen, "[ OK ] "); + } else if (test_info.result()->Skipped()) { + ColoredPrintf(GTestColor::kGreen, "[ SKIPPED ] "); + } else { + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + } + PrintTestName(test_info.test_suite_name(), test_info.name()); + if (test_info.result()->Failed()) + PrintFullTestCommentIfPresent(test_info); + + if (GTEST_FLAG(print_time)) { + printf(" (%s ms)\n", internal::StreamableToString( + test_info.result()->elapsed_time()).c_str()); + } else { + printf("\n"); + } + fflush(stdout); +} + +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +void PrettyUnitTestResultPrinter::OnTestCaseEnd(const TestCase& test_case) { + if (!GTEST_FLAG(print_time)) return; + + const std::string counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("%s from %s (%s ms total)\n\n", counts.c_str(), test_case.name(), + internal::StreamableToString(test_case.elapsed_time()).c_str()); + fflush(stdout); +} +#else +void PrettyUnitTestResultPrinter::OnTestSuiteEnd(const TestSuite& test_suite) { + if (!GTEST_FLAG(print_time)) return; + + const std::string counts = + FormatCountableNoun(test_suite.test_to_run_count(), "test", "tests"); + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("%s from %s (%s ms total)\n\n", counts.c_str(), test_suite.name(), + internal::StreamableToString(test_suite.elapsed_time()).c_str()); + fflush(stdout); +} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +void PrettyUnitTestResultPrinter::OnEnvironmentsTearDownStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(GTestColor::kGreen, "[----------] "); + printf("Global test environment tear-down\n"); + fflush(stdout); +} + +// Internal helper for printing the list of failed tests. +void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) { + const int failed_test_count = unit_test.failed_test_count(); + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + printf("%s, listed below:\n", FormatTestCount(failed_test_count).c_str()); + + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + const TestSuite& test_suite = *unit_test.GetTestSuite(i); + if (!test_suite.should_run() || (test_suite.failed_test_count() == 0)) { + continue; + } + for (int j = 0; j < test_suite.total_test_count(); ++j) { + const TestInfo& test_info = *test_suite.GetTestInfo(j); + if (!test_info.should_run() || !test_info.result()->Failed()) { + continue; + } + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + printf("%s.%s", test_suite.name(), test_info.name()); + PrintFullTestCommentIfPresent(test_info); + printf("\n"); + } + } + printf("\n%2d FAILED %s\n", failed_test_count, + failed_test_count == 1 ? "TEST" : "TESTS"); +} + +// Internal helper for printing the list of test suite failures not covered by +// PrintFailedTests. +void PrettyUnitTestResultPrinter::PrintFailedTestSuites( + const UnitTest& unit_test) { + int suite_failure_count = 0; + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + const TestSuite& test_suite = *unit_test.GetTestSuite(i); + if (!test_suite.should_run()) { + continue; + } + if (test_suite.ad_hoc_test_result().Failed()) { + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + printf("%s: SetUpTestSuite or TearDownTestSuite\n", test_suite.name()); + ++suite_failure_count; + } + } + if (suite_failure_count > 0) { + printf("\n%2d FAILED TEST %s\n", suite_failure_count, + suite_failure_count == 1 ? "SUITE" : "SUITES"); + } +} + +// Internal helper for printing the list of skipped tests. +void PrettyUnitTestResultPrinter::PrintSkippedTests(const UnitTest& unit_test) { + const int skipped_test_count = unit_test.skipped_test_count(); + if (skipped_test_count == 0) { + return; + } + + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + const TestSuite& test_suite = *unit_test.GetTestSuite(i); + if (!test_suite.should_run() || (test_suite.skipped_test_count() == 0)) { + continue; + } + for (int j = 0; j < test_suite.total_test_count(); ++j) { + const TestInfo& test_info = *test_suite.GetTestInfo(j); + if (!test_info.should_run() || !test_info.result()->Skipped()) { + continue; + } + ColoredPrintf(GTestColor::kGreen, "[ SKIPPED ] "); + printf("%s.%s", test_suite.name(), test_info.name()); + printf("\n"); + } + } +} + +void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + ColoredPrintf(GTestColor::kGreen, "[==========] "); + printf("%s from %s ran.", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str()); + if (GTEST_FLAG(print_time)) { + printf(" (%s ms total)", + internal::StreamableToString(unit_test.elapsed_time()).c_str()); + } + printf("\n"); + ColoredPrintf(GTestColor::kGreen, "[ PASSED ] "); + printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str()); + + const int skipped_test_count = unit_test.skipped_test_count(); + if (skipped_test_count > 0) { + ColoredPrintf(GTestColor::kGreen, "[ SKIPPED ] "); + printf("%s, listed below:\n", FormatTestCount(skipped_test_count).c_str()); + PrintSkippedTests(unit_test); + } + + if (!unit_test.Passed()) { + PrintFailedTests(unit_test); + PrintFailedTestSuites(unit_test); + } + + int num_disabled = unit_test.reportable_disabled_test_count(); + if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) { + if (unit_test.Passed()) { + printf("\n"); // Add a spacer if no FAILURE banner is displayed. + } + ColoredPrintf(GTestColor::kYellow, " YOU HAVE %d DISABLED %s\n\n", + num_disabled, num_disabled == 1 ? "TEST" : "TESTS"); + } + // Ensure that Google Test output is printed before, e.g., heapchecker output. + fflush(stdout); +} + +// End PrettyUnitTestResultPrinter + +// This class implements the TestEventListener interface. +// +// Class BriefUnitTestResultPrinter is copyable. +class BriefUnitTestResultPrinter : public TestEventListener { + public: + BriefUnitTestResultPrinter() {} + static void PrintTestName(const char* test_suite, const char* test) { + printf("%s.%s", test_suite, test); + } + + // The following methods override what's in the TestEventListener class. + void OnTestProgramStart(const UnitTest& /*unit_test*/) override {} + void OnTestIterationStart(const UnitTest& /*unit_test*/, + int /*iteration*/) override {} + void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) override {} + void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {} +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseStart(const TestCase& /*test_case*/) override {} +#else + void OnTestSuiteStart(const TestSuite& /*test_suite*/) override {} +#endif // OnTestCaseStart + + void OnTestStart(const TestInfo& /*test_info*/) override {} + + void OnTestPartResult(const TestPartResult& result) override; + void OnTestEnd(const TestInfo& test_info) override; +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseEnd(const TestCase& /*test_case*/) override {} +#else + void OnTestSuiteEnd(const TestSuite& /*test_suite*/) override {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) override {} + void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {} + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {} +}; + +// Called after an assertion failure. +void BriefUnitTestResultPrinter::OnTestPartResult( + const TestPartResult& result) { + switch (result.type()) { + // If the test part succeeded, we don't need to do anything. + case TestPartResult::kSuccess: + return; + default: + // Print failure message from the assertion + // (e.g. expected this and got that). + PrintTestPartResult(result); + fflush(stdout); + } +} + +void BriefUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { + if (test_info.result()->Failed()) { + ColoredPrintf(GTestColor::kRed, "[ FAILED ] "); + PrintTestName(test_info.test_suite_name(), test_info.name()); + PrintFullTestCommentIfPresent(test_info); + + if (GTEST_FLAG(print_time)) { + printf(" (%s ms)\n", + internal::StreamableToString(test_info.result()->elapsed_time()) + .c_str()); + } else { + printf("\n"); + } + fflush(stdout); + } +} + +void BriefUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + ColoredPrintf(GTestColor::kGreen, "[==========] "); + printf("%s from %s ran.", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str()); + if (GTEST_FLAG(print_time)) { + printf(" (%s ms total)", + internal::StreamableToString(unit_test.elapsed_time()).c_str()); + } + printf("\n"); + ColoredPrintf(GTestColor::kGreen, "[ PASSED ] "); + printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str()); + + const int skipped_test_count = unit_test.skipped_test_count(); + if (skipped_test_count > 0) { + ColoredPrintf(GTestColor::kGreen, "[ SKIPPED ] "); + printf("%s.\n", FormatTestCount(skipped_test_count).c_str()); + } + + int num_disabled = unit_test.reportable_disabled_test_count(); + if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) { + if (unit_test.Passed()) { + printf("\n"); // Add a spacer if no FAILURE banner is displayed. + } + ColoredPrintf(GTestColor::kYellow, " YOU HAVE %d DISABLED %s\n\n", + num_disabled, num_disabled == 1 ? "TEST" : "TESTS"); + } + // Ensure that Google Test output is printed before, e.g., heapchecker output. + fflush(stdout); +} + +// End BriefUnitTestResultPrinter + +// class TestEventRepeater +// +// This class forwards events to other event listeners. +class TestEventRepeater : public TestEventListener { + public: + TestEventRepeater() : forwarding_enabled_(true) {} + ~TestEventRepeater() override; + void Append(TestEventListener *listener); + TestEventListener* Release(TestEventListener* listener); + + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled() const { return forwarding_enabled_; } + void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; } + + void OnTestProgramStart(const UnitTest& unit_test) override; + void OnTestIterationStart(const UnitTest& unit_test, int iteration) override; + void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override; + void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) override; +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseStart(const TestSuite& parameter) override; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestSuiteStart(const TestSuite& parameter) override; + void OnTestStart(const TestInfo& test_info) override; + void OnTestPartResult(const TestPartResult& result) override; + void OnTestEnd(const TestInfo& test_info) override; +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseEnd(const TestCase& parameter) override; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestSuiteEnd(const TestSuite& parameter) override; + void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override; + void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) override; + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + void OnTestProgramEnd(const UnitTest& unit_test) override; + + private: + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled_; + // The list of listeners that receive events. + std::vector<TestEventListener*> listeners_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventRepeater); +}; + +TestEventRepeater::~TestEventRepeater() { + ForEach(listeners_, Delete<TestEventListener>); +} + +void TestEventRepeater::Append(TestEventListener *listener) { + listeners_.push_back(listener); +} + +TestEventListener* TestEventRepeater::Release(TestEventListener *listener) { + for (size_t i = 0; i < listeners_.size(); ++i) { + if (listeners_[i] == listener) { + listeners_.erase(listeners_.begin() + static_cast<int>(i)); + return listener; + } + } + + return nullptr; +} + +// Since most methods are very similar, use macros to reduce boilerplate. +// This defines a member that forwards the call to all listeners. +#define GTEST_REPEATER_METHOD_(Name, Type) \ +void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (size_t i = 0; i < listeners_.size(); i++) { \ + listeners_[i]->Name(parameter); \ + } \ + } \ +} +// This defines a member that forwards the call to all listeners in reverse +// order. +#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \ + void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (size_t i = listeners_.size(); i != 0; i--) { \ + listeners_[i - 1]->Name(parameter); \ + } \ + } \ + } + +GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest) +GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest) +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +GTEST_REPEATER_METHOD_(OnTestCaseStart, TestSuite) +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +GTEST_REPEATER_METHOD_(OnTestSuiteStart, TestSuite) +GTEST_REPEATER_METHOD_(OnTestStart, TestInfo) +GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult) +GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo) +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestSuite) +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +GTEST_REVERSE_REPEATER_METHOD_(OnTestSuiteEnd, TestSuite) +GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest) + +#undef GTEST_REPEATER_METHOD_ +#undef GTEST_REVERSE_REPEATER_METHOD_ + +void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (size_t i = 0; i < listeners_.size(); i++) { + listeners_[i]->OnTestIterationStart(unit_test, iteration); + } + } +} + +void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (size_t i = listeners_.size(); i > 0; i--) { + listeners_[i - 1]->OnTestIterationEnd(unit_test, iteration); + } + } +} + +// End TestEventRepeater + +// This class generates an XML output file. +class XmlUnitTestResultPrinter : public EmptyTestEventListener { + public: + explicit XmlUnitTestResultPrinter(const char* output_file); + + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + void ListTestsMatchingFilter(const std::vector<TestSuite*>& test_suites); + + // Prints an XML summary of all unit tests. + static void PrintXmlTestsList(std::ostream* stream, + const std::vector<TestSuite*>& test_suites); + + private: + // Is c a whitespace character that is normalized to a space character + // when it appears in an XML attribute value? + static bool IsNormalizableWhitespace(char c) { + return c == 0x9 || c == 0xA || c == 0xD; + } + + // May c appear in a well-formed XML document? + static bool IsValidXmlCharacter(char c) { + return IsNormalizableWhitespace(c) || c >= 0x20; + } + + // Returns an XML-escaped copy of the input string str. If + // is_attribute is true, the text is meant to appear as an attribute + // value, and normalizable whitespace is preserved by replacing it + // with character references. + static std::string EscapeXml(const std::string& str, bool is_attribute); + + // Returns the given string with all characters invalid in XML removed. + static std::string RemoveInvalidXmlCharacters(const std::string& str); + + // Convenience wrapper around EscapeXml when str is an attribute value. + static std::string EscapeXmlAttribute(const std::string& str) { + return EscapeXml(str, true); + } + + // Convenience wrapper around EscapeXml when str is not an attribute value. + static std::string EscapeXmlText(const char* str) { + return EscapeXml(str, false); + } + + // Verifies that the given attribute belongs to the given element and + // streams the attribute as XML. + static void OutputXmlAttribute(std::ostream* stream, + const std::string& element_name, + const std::string& name, + const std::string& value); + + // Streams an XML CDATA section, escaping invalid CDATA sequences as needed. + static void OutputXmlCDataSection(::std::ostream* stream, const char* data); + + // Streams a test suite XML stanza containing the given test result. + // + // Requires: result.Failed() + static void OutputXmlTestSuiteForTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams an XML representation of a TestResult object. + static void OutputXmlTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams an XML representation of a TestInfo object. + static void OutputXmlTestInfo(::std::ostream* stream, + const char* test_suite_name, + const TestInfo& test_info); + + // Prints an XML representation of a TestSuite object + static void PrintXmlTestSuite(::std::ostream* stream, + const TestSuite& test_suite); + + // Prints an XML summary of unit_test to output stream out. + static void PrintXmlUnitTest(::std::ostream* stream, + const UnitTest& unit_test); + + // Produces a string representing the test properties in a result as space + // delimited XML attributes based on the property key="value" pairs. + // When the std::string is not empty, it includes a space at the beginning, + // to delimit this attribute from prior attributes. + static std::string TestPropertiesAsXmlAttributes(const TestResult& result); + + // Streams an XML representation of the test properties of a TestResult + // object. + static void OutputXmlTestProperties(std::ostream* stream, + const TestResult& result); + + // The output file. + const std::string output_file_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(XmlUnitTestResultPrinter); +}; + +// Creates a new XmlUnitTestResultPrinter. +XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) + : output_file_(output_file) { + if (output_file_.empty()) { + GTEST_LOG_(FATAL) << "XML output file may not be null"; + } +} + +// Called after the unit test ends. +void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + FILE* xmlout = OpenFileForWriting(output_file_); + std::stringstream stream; + PrintXmlUnitTest(&stream, unit_test); + fprintf(xmlout, "%s", StringStreamToString(&stream).c_str()); + fclose(xmlout); +} + +void XmlUnitTestResultPrinter::ListTestsMatchingFilter( + const std::vector<TestSuite*>& test_suites) { + FILE* xmlout = OpenFileForWriting(output_file_); + std::stringstream stream; + PrintXmlTestsList(&stream, test_suites); + fprintf(xmlout, "%s", StringStreamToString(&stream).c_str()); + fclose(xmlout); +} + +// Returns an XML-escaped copy of the input string str. If is_attribute +// is true, the text is meant to appear as an attribute value, and +// normalizable whitespace is preserved by replacing it with character +// references. +// +// Invalid XML characters in str, if any, are stripped from the output. +// It is expected that most, if not all, of the text processed by this +// module will consist of ordinary English text. +// If this module is ever modified to produce version 1.1 XML output, +// most invalid characters can be retained using character references. +std::string XmlUnitTestResultPrinter::EscapeXml( + const std::string& str, bool is_attribute) { + Message m; + + for (size_t i = 0; i < str.size(); ++i) { + const char ch = str[i]; + switch (ch) { + case '<': + m << "<"; + break; + case '>': + m << ">"; + break; + case '&': + m << "&"; + break; + case '\'': + if (is_attribute) + m << "'"; + else + m << '\''; + break; + case '"': + if (is_attribute) + m << """; + else + m << '"'; + break; + default: + if (IsValidXmlCharacter(ch)) { + if (is_attribute && IsNormalizableWhitespace(ch)) + m << "&#x" << String::FormatByte(static_cast<unsigned char>(ch)) + << ";"; + else + m << ch; + } + break; + } + } + + return m.GetString(); +} + +// Returns the given string with all characters invalid in XML removed. +// Currently invalid characters are dropped from the string. An +// alternative is to replace them with certain characters such as . or ?. +std::string XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters( + const std::string& str) { + std::string output; + output.reserve(str.size()); + for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) + if (IsValidXmlCharacter(*it)) + output.push_back(*it); + + return output; +} + +// The following routines generate an XML representation of a UnitTest +// object. +// GOOGLETEST_CM0009 DO NOT DELETE +// +// This is how Google Test concepts map to the DTD: +// +// <testsuites name="AllTests"> <-- corresponds to a UnitTest object +// <testsuite name="testcase-name"> <-- corresponds to a TestSuite object +// <testcase name="test-name"> <-- corresponds to a TestInfo object +// <failure message="...">...</failure> +// <failure message="...">...</failure> +// <failure message="...">...</failure> +// <-- individual assertion failures +// </testcase> +// </testsuite> +// </testsuites> + +// Formats the given time in milliseconds as seconds. +std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) { + ::std::stringstream ss; + ss << (static_cast<double>(ms) * 1e-3); + return ss.str(); +} + +static bool PortableLocaltime(time_t seconds, struct tm* out) { +#if defined(_MSC_VER) + return localtime_s(out, &seconds) == 0; +#elif defined(__MINGW32__) || defined(__MINGW64__) + // MINGW <time.h> provides neither localtime_r nor localtime_s, but uses + // Windows' localtime(), which has a thread-local tm buffer. + struct tm* tm_ptr = localtime(&seconds); // NOLINT + if (tm_ptr == nullptr) return false; + *out = *tm_ptr; + return true; +#elif defined(__STDC_LIB_EXT1__) + // Uses localtime_s when available as localtime_r is only available from + // C23 standard. + return localtime_s(&seconds, out) != nullptr; +#else + return localtime_r(&seconds, out) != nullptr; +#endif +} + +// Converts the given epoch time in milliseconds to a date string in the ISO +// 8601 format, without the timezone information. +std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) { + struct tm time_struct; + if (!PortableLocaltime(static_cast<time_t>(ms / 1000), &time_struct)) + return ""; + // YYYY-MM-DDThh:mm:ss.sss + return StreamableToString(time_struct.tm_year + 1900) + "-" + + String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" + + String::FormatIntWidth2(time_struct.tm_mday) + "T" + + String::FormatIntWidth2(time_struct.tm_hour) + ":" + + String::FormatIntWidth2(time_struct.tm_min) + ":" + + String::FormatIntWidth2(time_struct.tm_sec) + "." + + String::FormatIntWidthN(static_cast<int>(ms % 1000), 3); +} + +// Streams an XML CDATA section, escaping invalid CDATA sequences as needed. +void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream, + const char* data) { + const char* segment = data; + *stream << "<![CDATA["; + for (;;) { + const char* const next_segment = strstr(segment, "]]>"); + if (next_segment != nullptr) { + stream->write( + segment, static_cast<std::streamsize>(next_segment - segment)); + *stream << "]]>]]><![CDATA["; + segment = next_segment + strlen("]]>"); + } else { + *stream << segment; + break; + } + } + *stream << "]]>"; +} + +void XmlUnitTestResultPrinter::OutputXmlAttribute( + std::ostream* stream, + const std::string& element_name, + const std::string& name, + const std::string& value) { + const std::vector<std::string>& allowed_names = + GetReservedOutputAttributesForElement(element_name); + + GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != + allowed_names.end()) + << "Attribute " << name << " is not allowed for element <" << element_name + << ">."; + + *stream << " " << name << "=\"" << EscapeXmlAttribute(value) << "\""; +} + +// Streams a test suite XML stanza containing the given test result. +void XmlUnitTestResultPrinter::OutputXmlTestSuiteForTestResult( + ::std::ostream* stream, const TestResult& result) { + // Output the boilerplate for a minimal test suite with one test. + *stream << " <testsuite"; + OutputXmlAttribute(stream, "testsuite", "name", "NonTestSuiteFailure"); + OutputXmlAttribute(stream, "testsuite", "tests", "1"); + OutputXmlAttribute(stream, "testsuite", "failures", "1"); + OutputXmlAttribute(stream, "testsuite", "disabled", "0"); + OutputXmlAttribute(stream, "testsuite", "skipped", "0"); + OutputXmlAttribute(stream, "testsuite", "errors", "0"); + OutputXmlAttribute(stream, "testsuite", "time", + FormatTimeInMillisAsSeconds(result.elapsed_time())); + OutputXmlAttribute( + stream, "testsuite", "timestamp", + FormatEpochTimeInMillisAsIso8601(result.start_timestamp())); + *stream << ">"; + + // Output the boilerplate for a minimal test case with a single test. + *stream << " <testcase"; + OutputXmlAttribute(stream, "testcase", "name", ""); + OutputXmlAttribute(stream, "testcase", "status", "run"); + OutputXmlAttribute(stream, "testcase", "result", "completed"); + OutputXmlAttribute(stream, "testcase", "classname", ""); + OutputXmlAttribute(stream, "testcase", "time", + FormatTimeInMillisAsSeconds(result.elapsed_time())); + OutputXmlAttribute( + stream, "testcase", "timestamp", + FormatEpochTimeInMillisAsIso8601(result.start_timestamp())); + + // Output the actual test result. + OutputXmlTestResult(stream, result); + + // Complete the test suite. + *stream << " </testsuite>\n"; +} + +// Prints an XML representation of a TestInfo object. +void XmlUnitTestResultPrinter::OutputXmlTestInfo(::std::ostream* stream, + const char* test_suite_name, + const TestInfo& test_info) { + const TestResult& result = *test_info.result(); + const std::string kTestsuite = "testcase"; + + if (test_info.is_in_another_shard()) { + return; + } + + *stream << " <testcase"; + OutputXmlAttribute(stream, kTestsuite, "name", test_info.name()); + + if (test_info.value_param() != nullptr) { + OutputXmlAttribute(stream, kTestsuite, "value_param", + test_info.value_param()); + } + if (test_info.type_param() != nullptr) { + OutputXmlAttribute(stream, kTestsuite, "type_param", + test_info.type_param()); + } + if (GTEST_FLAG(list_tests)) { + OutputXmlAttribute(stream, kTestsuite, "file", test_info.file()); + OutputXmlAttribute(stream, kTestsuite, "line", + StreamableToString(test_info.line())); + *stream << " />\n"; + return; + } + + OutputXmlAttribute(stream, kTestsuite, "status", + test_info.should_run() ? "run" : "notrun"); + OutputXmlAttribute(stream, kTestsuite, "result", + test_info.should_run() + ? (result.Skipped() ? "skipped" : "completed") + : "suppressed"); + OutputXmlAttribute(stream, kTestsuite, "time", + FormatTimeInMillisAsSeconds(result.elapsed_time())); + OutputXmlAttribute( + stream, kTestsuite, "timestamp", + FormatEpochTimeInMillisAsIso8601(result.start_timestamp())); + OutputXmlAttribute(stream, kTestsuite, "classname", test_suite_name); + + OutputXmlTestResult(stream, result); +} + +void XmlUnitTestResultPrinter::OutputXmlTestResult(::std::ostream* stream, + const TestResult& result) { + int failures = 0; + int skips = 0; + for (int i = 0; i < result.total_part_count(); ++i) { + const TestPartResult& part = result.GetTestPartResult(i); + if (part.failed()) { + if (++failures == 1 && skips == 0) { + *stream << ">\n"; + } + const std::string location = + internal::FormatCompilerIndependentFileLocation(part.file_name(), + part.line_number()); + const std::string summary = location + "\n" + part.summary(); + *stream << " <failure message=\"" + << EscapeXmlAttribute(summary) + << "\" type=\"\">"; + const std::string detail = location + "\n" + part.message(); + OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str()); + *stream << "</failure>\n"; + } else if (part.skipped()) { + if (++skips == 1 && failures == 0) { + *stream << ">\n"; + } + const std::string location = + internal::FormatCompilerIndependentFileLocation(part.file_name(), + part.line_number()); + const std::string summary = location + "\n" + part.summary(); + *stream << " <skipped message=\"" + << EscapeXmlAttribute(summary.c_str()) << "\">"; + const std::string detail = location + "\n" + part.message(); + OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str()); + *stream << "</skipped>\n"; + } + } + + if (failures == 0 && skips == 0 && result.test_property_count() == 0) { + *stream << " />\n"; + } else { + if (failures == 0 && skips == 0) { + *stream << ">\n"; + } + OutputXmlTestProperties(stream, result); + *stream << " </testcase>\n"; + } +} + +// Prints an XML representation of a TestSuite object +void XmlUnitTestResultPrinter::PrintXmlTestSuite(std::ostream* stream, + const TestSuite& test_suite) { + const std::string kTestsuite = "testsuite"; + *stream << " <" << kTestsuite; + OutputXmlAttribute(stream, kTestsuite, "name", test_suite.name()); + OutputXmlAttribute(stream, kTestsuite, "tests", + StreamableToString(test_suite.reportable_test_count())); + if (!GTEST_FLAG(list_tests)) { + OutputXmlAttribute(stream, kTestsuite, "failures", + StreamableToString(test_suite.failed_test_count())); + OutputXmlAttribute( + stream, kTestsuite, "disabled", + StreamableToString(test_suite.reportable_disabled_test_count())); + OutputXmlAttribute(stream, kTestsuite, "skipped", + StreamableToString(test_suite.skipped_test_count())); + + OutputXmlAttribute(stream, kTestsuite, "errors", "0"); + + OutputXmlAttribute(stream, kTestsuite, "time", + FormatTimeInMillisAsSeconds(test_suite.elapsed_time())); + OutputXmlAttribute( + stream, kTestsuite, "timestamp", + FormatEpochTimeInMillisAsIso8601(test_suite.start_timestamp())); + *stream << TestPropertiesAsXmlAttributes(test_suite.ad_hoc_test_result()); + } + *stream << ">\n"; + for (int i = 0; i < test_suite.total_test_count(); ++i) { + if (test_suite.GetTestInfo(i)->is_reportable()) + OutputXmlTestInfo(stream, test_suite.name(), *test_suite.GetTestInfo(i)); + } + *stream << " </" << kTestsuite << ">\n"; +} + +// Prints an XML summary of unit_test to output stream out. +void XmlUnitTestResultPrinter::PrintXmlUnitTest(std::ostream* stream, + const UnitTest& unit_test) { + const std::string kTestsuites = "testsuites"; + + *stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + *stream << "<" << kTestsuites; + + OutputXmlAttribute(stream, kTestsuites, "tests", + StreamableToString(unit_test.reportable_test_count())); + OutputXmlAttribute(stream, kTestsuites, "failures", + StreamableToString(unit_test.failed_test_count())); + OutputXmlAttribute( + stream, kTestsuites, "disabled", + StreamableToString(unit_test.reportable_disabled_test_count())); + OutputXmlAttribute(stream, kTestsuites, "errors", "0"); + OutputXmlAttribute(stream, kTestsuites, "time", + FormatTimeInMillisAsSeconds(unit_test.elapsed_time())); + OutputXmlAttribute( + stream, kTestsuites, "timestamp", + FormatEpochTimeInMillisAsIso8601(unit_test.start_timestamp())); + + if (GTEST_FLAG(shuffle)) { + OutputXmlAttribute(stream, kTestsuites, "random_seed", + StreamableToString(unit_test.random_seed())); + } + *stream << TestPropertiesAsXmlAttributes(unit_test.ad_hoc_test_result()); + + OutputXmlAttribute(stream, kTestsuites, "name", "AllTests"); + *stream << ">\n"; + + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + if (unit_test.GetTestSuite(i)->reportable_test_count() > 0) + PrintXmlTestSuite(stream, *unit_test.GetTestSuite(i)); + } + + // If there was a test failure outside of one of the test suites (like in a + // test environment) include that in the output. + if (unit_test.ad_hoc_test_result().Failed()) { + OutputXmlTestSuiteForTestResult(stream, unit_test.ad_hoc_test_result()); + } + + *stream << "</" << kTestsuites << ">\n"; +} + +void XmlUnitTestResultPrinter::PrintXmlTestsList( + std::ostream* stream, const std::vector<TestSuite*>& test_suites) { + const std::string kTestsuites = "testsuites"; + + *stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + *stream << "<" << kTestsuites; + + int total_tests = 0; + for (auto test_suite : test_suites) { + total_tests += test_suite->total_test_count(); + } + OutputXmlAttribute(stream, kTestsuites, "tests", + StreamableToString(total_tests)); + OutputXmlAttribute(stream, kTestsuites, "name", "AllTests"); + *stream << ">\n"; + + for (auto test_suite : test_suites) { + PrintXmlTestSuite(stream, *test_suite); + } + *stream << "</" << kTestsuites << ">\n"; +} + +// Produces a string representing the test properties in a result as space +// delimited XML attributes based on the property key="value" pairs. +std::string XmlUnitTestResultPrinter::TestPropertiesAsXmlAttributes( + const TestResult& result) { + Message attributes; + for (int i = 0; i < result.test_property_count(); ++i) { + const TestProperty& property = result.GetTestProperty(i); + attributes << " " << property.key() << "=" + << "\"" << EscapeXmlAttribute(property.value()) << "\""; + } + return attributes.GetString(); +} + +void XmlUnitTestResultPrinter::OutputXmlTestProperties( + std::ostream* stream, const TestResult& result) { + const std::string kProperties = "properties"; + const std::string kProperty = "property"; + + if (result.test_property_count() <= 0) { + return; + } + + *stream << "<" << kProperties << ">\n"; + for (int i = 0; i < result.test_property_count(); ++i) { + const TestProperty& property = result.GetTestProperty(i); + *stream << "<" << kProperty; + *stream << " name=\"" << EscapeXmlAttribute(property.key()) << "\""; + *stream << " value=\"" << EscapeXmlAttribute(property.value()) << "\""; + *stream << "/>\n"; + } + *stream << "</" << kProperties << ">\n"; +} + +// End XmlUnitTestResultPrinter + +// This class generates an JSON output file. +class JsonUnitTestResultPrinter : public EmptyTestEventListener { + public: + explicit JsonUnitTestResultPrinter(const char* output_file); + + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + + // Prints an JSON summary of all unit tests. + static void PrintJsonTestList(::std::ostream* stream, + const std::vector<TestSuite*>& test_suites); + + private: + // Returns an JSON-escaped copy of the input string str. + static std::string EscapeJson(const std::string& str); + + //// Verifies that the given attribute belongs to the given element and + //// streams the attribute as JSON. + static void OutputJsonKey(std::ostream* stream, + const std::string& element_name, + const std::string& name, + const std::string& value, + const std::string& indent, + bool comma = true); + static void OutputJsonKey(std::ostream* stream, + const std::string& element_name, + const std::string& name, + int value, + const std::string& indent, + bool comma = true); + + // Streams a test suite JSON stanza containing the given test result. + // + // Requires: result.Failed() + static void OutputJsonTestSuiteForTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams a JSON representation of a TestResult object. + static void OutputJsonTestResult(::std::ostream* stream, + const TestResult& result); + + // Streams a JSON representation of a TestInfo object. + static void OutputJsonTestInfo(::std::ostream* stream, + const char* test_suite_name, + const TestInfo& test_info); + + // Prints a JSON representation of a TestSuite object + static void PrintJsonTestSuite(::std::ostream* stream, + const TestSuite& test_suite); + + // Prints a JSON summary of unit_test to output stream out. + static void PrintJsonUnitTest(::std::ostream* stream, + const UnitTest& unit_test); + + // Produces a string representing the test properties in a result as + // a JSON dictionary. + static std::string TestPropertiesAsJson(const TestResult& result, + const std::string& indent); + + // The output file. + const std::string output_file_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(JsonUnitTestResultPrinter); +}; + +// Creates a new JsonUnitTestResultPrinter. +JsonUnitTestResultPrinter::JsonUnitTestResultPrinter(const char* output_file) + : output_file_(output_file) { + if (output_file_.empty()) { + GTEST_LOG_(FATAL) << "JSON output file may not be null"; + } +} + +void JsonUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + FILE* jsonout = OpenFileForWriting(output_file_); + std::stringstream stream; + PrintJsonUnitTest(&stream, unit_test); + fprintf(jsonout, "%s", StringStreamToString(&stream).c_str()); + fclose(jsonout); +} + +// Returns an JSON-escaped copy of the input string str. +std::string JsonUnitTestResultPrinter::EscapeJson(const std::string& str) { + Message m; + + for (size_t i = 0; i < str.size(); ++i) { + const char ch = str[i]; + switch (ch) { + case '\\': + case '"': + case '/': + m << '\\' << ch; + break; + case '\b': + m << "\\b"; + break; + case '\t': + m << "\\t"; + break; + case '\n': + m << "\\n"; + break; + case '\f': + m << "\\f"; + break; + case '\r': + m << "\\r"; + break; + default: + if (ch < ' ') { + m << "\\u00" << String::FormatByte(static_cast<unsigned char>(ch)); + } else { + m << ch; + } + break; + } + } + + return m.GetString(); +} + +// The following routines generate an JSON representation of a UnitTest +// object. + +// Formats the given time in milliseconds as seconds. +static std::string FormatTimeInMillisAsDuration(TimeInMillis ms) { + ::std::stringstream ss; + ss << (static_cast<double>(ms) * 1e-3) << "s"; + return ss.str(); +} + +// Converts the given epoch time in milliseconds to a date string in the +// RFC3339 format, without the timezone information. +static std::string FormatEpochTimeInMillisAsRFC3339(TimeInMillis ms) { + struct tm time_struct; + if (!PortableLocaltime(static_cast<time_t>(ms / 1000), &time_struct)) + return ""; + // YYYY-MM-DDThh:mm:ss + return StreamableToString(time_struct.tm_year + 1900) + "-" + + String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" + + String::FormatIntWidth2(time_struct.tm_mday) + "T" + + String::FormatIntWidth2(time_struct.tm_hour) + ":" + + String::FormatIntWidth2(time_struct.tm_min) + ":" + + String::FormatIntWidth2(time_struct.tm_sec) + "Z"; +} + +static inline std::string Indent(size_t width) { + return std::string(width, ' '); +} + +void JsonUnitTestResultPrinter::OutputJsonKey( + std::ostream* stream, + const std::string& element_name, + const std::string& name, + const std::string& value, + const std::string& indent, + bool comma) { + const std::vector<std::string>& allowed_names = + GetReservedOutputAttributesForElement(element_name); + + GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != + allowed_names.end()) + << "Key \"" << name << "\" is not allowed for value \"" << element_name + << "\"."; + + *stream << indent << "\"" << name << "\": \"" << EscapeJson(value) << "\""; + if (comma) + *stream << ",\n"; +} + +void JsonUnitTestResultPrinter::OutputJsonKey( + std::ostream* stream, + const std::string& element_name, + const std::string& name, + int value, + const std::string& indent, + bool comma) { + const std::vector<std::string>& allowed_names = + GetReservedOutputAttributesForElement(element_name); + + GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != + allowed_names.end()) + << "Key \"" << name << "\" is not allowed for value \"" << element_name + << "\"."; + + *stream << indent << "\"" << name << "\": " << StreamableToString(value); + if (comma) + *stream << ",\n"; +} + +// Streams a test suite JSON stanza containing the given test result. +void JsonUnitTestResultPrinter::OutputJsonTestSuiteForTestResult( + ::std::ostream* stream, const TestResult& result) { + // Output the boilerplate for a new test suite. + *stream << Indent(4) << "{\n"; + OutputJsonKey(stream, "testsuite", "name", "NonTestSuiteFailure", Indent(6)); + OutputJsonKey(stream, "testsuite", "tests", 1, Indent(6)); + if (!GTEST_FLAG(list_tests)) { + OutputJsonKey(stream, "testsuite", "failures", 1, Indent(6)); + OutputJsonKey(stream, "testsuite", "disabled", 0, Indent(6)); + OutputJsonKey(stream, "testsuite", "skipped", 0, Indent(6)); + OutputJsonKey(stream, "testsuite", "errors", 0, Indent(6)); + OutputJsonKey(stream, "testsuite", "time", + FormatTimeInMillisAsDuration(result.elapsed_time()), + Indent(6)); + OutputJsonKey(stream, "testsuite", "timestamp", + FormatEpochTimeInMillisAsRFC3339(result.start_timestamp()), + Indent(6)); + } + *stream << Indent(6) << "\"testsuite\": [\n"; + + // Output the boilerplate for a new test case. + *stream << Indent(8) << "{\n"; + OutputJsonKey(stream, "testcase", "name", "", Indent(10)); + OutputJsonKey(stream, "testcase", "status", "RUN", Indent(10)); + OutputJsonKey(stream, "testcase", "result", "COMPLETED", Indent(10)); + OutputJsonKey(stream, "testcase", "timestamp", + FormatEpochTimeInMillisAsRFC3339(result.start_timestamp()), + Indent(10)); + OutputJsonKey(stream, "testcase", "time", + FormatTimeInMillisAsDuration(result.elapsed_time()), + Indent(10)); + OutputJsonKey(stream, "testcase", "classname", "", Indent(10), false); + *stream << TestPropertiesAsJson(result, Indent(10)); + + // Output the actual test result. + OutputJsonTestResult(stream, result); + + // Finish the test suite. + *stream << "\n" << Indent(6) << "]\n" << Indent(4) << "}"; +} + +// Prints a JSON representation of a TestInfo object. +void JsonUnitTestResultPrinter::OutputJsonTestInfo(::std::ostream* stream, + const char* test_suite_name, + const TestInfo& test_info) { + const TestResult& result = *test_info.result(); + const std::string kTestsuite = "testcase"; + const std::string kIndent = Indent(10); + + *stream << Indent(8) << "{\n"; + OutputJsonKey(stream, kTestsuite, "name", test_info.name(), kIndent); + + if (test_info.value_param() != nullptr) { + OutputJsonKey(stream, kTestsuite, "value_param", test_info.value_param(), + kIndent); + } + if (test_info.type_param() != nullptr) { + OutputJsonKey(stream, kTestsuite, "type_param", test_info.type_param(), + kIndent); + } + if (GTEST_FLAG(list_tests)) { + OutputJsonKey(stream, kTestsuite, "file", test_info.file(), kIndent); + OutputJsonKey(stream, kTestsuite, "line", test_info.line(), kIndent, false); + *stream << "\n" << Indent(8) << "}"; + return; + } + + OutputJsonKey(stream, kTestsuite, "status", + test_info.should_run() ? "RUN" : "NOTRUN", kIndent); + OutputJsonKey(stream, kTestsuite, "result", + test_info.should_run() + ? (result.Skipped() ? "SKIPPED" : "COMPLETED") + : "SUPPRESSED", + kIndent); + OutputJsonKey(stream, kTestsuite, "timestamp", + FormatEpochTimeInMillisAsRFC3339(result.start_timestamp()), + kIndent); + OutputJsonKey(stream, kTestsuite, "time", + FormatTimeInMillisAsDuration(result.elapsed_time()), kIndent); + OutputJsonKey(stream, kTestsuite, "classname", test_suite_name, kIndent, + false); + *stream << TestPropertiesAsJson(result, kIndent); + + OutputJsonTestResult(stream, result); +} + +void JsonUnitTestResultPrinter::OutputJsonTestResult(::std::ostream* stream, + const TestResult& result) { + const std::string kIndent = Indent(10); + + int failures = 0; + for (int i = 0; i < result.total_part_count(); ++i) { + const TestPartResult& part = result.GetTestPartResult(i); + if (part.failed()) { + *stream << ",\n"; + if (++failures == 1) { + *stream << kIndent << "\"" << "failures" << "\": [\n"; + } + const std::string location = + internal::FormatCompilerIndependentFileLocation(part.file_name(), + part.line_number()); + const std::string message = EscapeJson(location + "\n" + part.message()); + *stream << kIndent << " {\n" + << kIndent << " \"failure\": \"" << message << "\",\n" + << kIndent << " \"type\": \"\"\n" + << kIndent << " }"; + } + } + + if (failures > 0) + *stream << "\n" << kIndent << "]"; + *stream << "\n" << Indent(8) << "}"; +} + +// Prints an JSON representation of a TestSuite object +void JsonUnitTestResultPrinter::PrintJsonTestSuite( + std::ostream* stream, const TestSuite& test_suite) { + const std::string kTestsuite = "testsuite"; + const std::string kIndent = Indent(6); + + *stream << Indent(4) << "{\n"; + OutputJsonKey(stream, kTestsuite, "name", test_suite.name(), kIndent); + OutputJsonKey(stream, kTestsuite, "tests", test_suite.reportable_test_count(), + kIndent); + if (!GTEST_FLAG(list_tests)) { + OutputJsonKey(stream, kTestsuite, "failures", + test_suite.failed_test_count(), kIndent); + OutputJsonKey(stream, kTestsuite, "disabled", + test_suite.reportable_disabled_test_count(), kIndent); + OutputJsonKey(stream, kTestsuite, "errors", 0, kIndent); + OutputJsonKey( + stream, kTestsuite, "timestamp", + FormatEpochTimeInMillisAsRFC3339(test_suite.start_timestamp()), + kIndent); + OutputJsonKey(stream, kTestsuite, "time", + FormatTimeInMillisAsDuration(test_suite.elapsed_time()), + kIndent, false); + *stream << TestPropertiesAsJson(test_suite.ad_hoc_test_result(), kIndent) + << ",\n"; + } + + *stream << kIndent << "\"" << kTestsuite << "\": [\n"; + + bool comma = false; + for (int i = 0; i < test_suite.total_test_count(); ++i) { + if (test_suite.GetTestInfo(i)->is_reportable()) { + if (comma) { + *stream << ",\n"; + } else { + comma = true; + } + OutputJsonTestInfo(stream, test_suite.name(), *test_suite.GetTestInfo(i)); + } + } + *stream << "\n" << kIndent << "]\n" << Indent(4) << "}"; +} + +// Prints a JSON summary of unit_test to output stream out. +void JsonUnitTestResultPrinter::PrintJsonUnitTest(std::ostream* stream, + const UnitTest& unit_test) { + const std::string kTestsuites = "testsuites"; + const std::string kIndent = Indent(2); + *stream << "{\n"; + + OutputJsonKey(stream, kTestsuites, "tests", unit_test.reportable_test_count(), + kIndent); + OutputJsonKey(stream, kTestsuites, "failures", unit_test.failed_test_count(), + kIndent); + OutputJsonKey(stream, kTestsuites, "disabled", + unit_test.reportable_disabled_test_count(), kIndent); + OutputJsonKey(stream, kTestsuites, "errors", 0, kIndent); + if (GTEST_FLAG(shuffle)) { + OutputJsonKey(stream, kTestsuites, "random_seed", unit_test.random_seed(), + kIndent); + } + OutputJsonKey(stream, kTestsuites, "timestamp", + FormatEpochTimeInMillisAsRFC3339(unit_test.start_timestamp()), + kIndent); + OutputJsonKey(stream, kTestsuites, "time", + FormatTimeInMillisAsDuration(unit_test.elapsed_time()), kIndent, + false); + + *stream << TestPropertiesAsJson(unit_test.ad_hoc_test_result(), kIndent) + << ",\n"; + + OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent); + *stream << kIndent << "\"" << kTestsuites << "\": [\n"; + + bool comma = false; + for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { + if (unit_test.GetTestSuite(i)->reportable_test_count() > 0) { + if (comma) { + *stream << ",\n"; + } else { + comma = true; + } + PrintJsonTestSuite(stream, *unit_test.GetTestSuite(i)); + } + } + + // If there was a test failure outside of one of the test suites (like in a + // test environment) include that in the output. + if (unit_test.ad_hoc_test_result().Failed()) { + OutputJsonTestSuiteForTestResult(stream, unit_test.ad_hoc_test_result()); + } + + *stream << "\n" << kIndent << "]\n" << "}\n"; +} + +void JsonUnitTestResultPrinter::PrintJsonTestList( + std::ostream* stream, const std::vector<TestSuite*>& test_suites) { + const std::string kTestsuites = "testsuites"; + const std::string kIndent = Indent(2); + *stream << "{\n"; + int total_tests = 0; + for (auto test_suite : test_suites) { + total_tests += test_suite->total_test_count(); + } + OutputJsonKey(stream, kTestsuites, "tests", total_tests, kIndent); + + OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent); + *stream << kIndent << "\"" << kTestsuites << "\": [\n"; + + for (size_t i = 0; i < test_suites.size(); ++i) { + if (i != 0) { + *stream << ",\n"; + } + PrintJsonTestSuite(stream, *test_suites[i]); + } + + *stream << "\n" + << kIndent << "]\n" + << "}\n"; +} +// Produces a string representing the test properties in a result as +// a JSON dictionary. +std::string JsonUnitTestResultPrinter::TestPropertiesAsJson( + const TestResult& result, const std::string& indent) { + Message attributes; + for (int i = 0; i < result.test_property_count(); ++i) { + const TestProperty& property = result.GetTestProperty(i); + attributes << ",\n" << indent << "\"" << property.key() << "\": " + << "\"" << EscapeJson(property.value()) << "\""; + } + return attributes.GetString(); +} + +// End JsonUnitTestResultPrinter + +#if GTEST_CAN_STREAM_RESULTS_ + +// Checks if str contains '=', '&', '%' or '\n' characters. If yes, +// replaces them by "%xx" where xx is their hexadecimal value. For +// example, replaces "=" with "%3D". This algorithm is O(strlen(str)) +// in both time and space -- important as the input str may contain an +// arbitrarily long test failure message and stack trace. +std::string StreamingListener::UrlEncode(const char* str) { + std::string result; + result.reserve(strlen(str) + 1); + for (char ch = *str; ch != '\0'; ch = *++str) { + switch (ch) { + case '%': + case '=': + case '&': + case '\n': + result.append("%" + String::FormatByte(static_cast<unsigned char>(ch))); + break; + default: + result.push_back(ch); + break; + } + } + return result; +} + +void StreamingListener::SocketWriter::MakeConnection() { + GTEST_CHECK_(sockfd_ == -1) + << "MakeConnection() can't be called when there is already a connection."; + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; // To allow both IPv4 and IPv6 addresses. + hints.ai_socktype = SOCK_STREAM; + addrinfo* servinfo = nullptr; + + // Use the getaddrinfo() to get a linked list of IP addresses for + // the given host name. + const int error_num = getaddrinfo( + host_name_.c_str(), port_num_.c_str(), &hints, &servinfo); + if (error_num != 0) { + GTEST_LOG_(WARNING) << "stream_result_to: getaddrinfo() failed: " + << gai_strerror(error_num); + } + + // Loop through all the results and connect to the first we can. + for (addrinfo* cur_addr = servinfo; sockfd_ == -1 && cur_addr != nullptr; + cur_addr = cur_addr->ai_next) { + sockfd_ = socket( + cur_addr->ai_family, cur_addr->ai_socktype, cur_addr->ai_protocol); + if (sockfd_ != -1) { + // Connect the client socket to the server socket. + if (connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) { + close(sockfd_); + sockfd_ = -1; + } + } + } + + freeaddrinfo(servinfo); // all done with this structure + + if (sockfd_ == -1) { + GTEST_LOG_(WARNING) << "stream_result_to: failed to connect to " + << host_name_ << ":" << port_num_; + } +} + +// End of class Streaming Listener +#endif // GTEST_CAN_STREAM_RESULTS__ + +// class OsStackTraceGetter + +const char* const OsStackTraceGetterInterface::kElidedFramesMarker = + "... " GTEST_NAME_ " internal frames ..."; + +std::string OsStackTraceGetter::CurrentStackTrace(int max_depth, int skip_count) + GTEST_LOCK_EXCLUDED_(mutex_) { +#if GTEST_HAS_ABSL + std::string result; + + if (max_depth <= 0) { + return result; + } + + max_depth = std::min(max_depth, kMaxStackTraceDepth); + + std::vector<void*> raw_stack(max_depth); + // Skips the frames requested by the caller, plus this function. + const int raw_stack_size = + absl::GetStackTrace(&raw_stack[0], max_depth, skip_count + 1); + + void* caller_frame = nullptr; + { + MutexLock lock(&mutex_); + caller_frame = caller_frame_; + } + + for (int i = 0; i < raw_stack_size; ++i) { + if (raw_stack[i] == caller_frame && + !GTEST_FLAG(show_internal_stack_frames)) { + // Add a marker to the trace and stop adding frames. + absl::StrAppend(&result, kElidedFramesMarker, "\n"); + break; + } + + char tmp[1024]; + const char* symbol = "(unknown)"; + if (absl::Symbolize(raw_stack[i], tmp, sizeof(tmp))) { + symbol = tmp; + } + + char line[1024]; + snprintf(line, sizeof(line), " %p: %s\n", raw_stack[i], symbol); + result += line; + } + + return result; + +#else // !GTEST_HAS_ABSL + static_cast<void>(max_depth); + static_cast<void>(skip_count); + return ""; +#endif // GTEST_HAS_ABSL +} + +void OsStackTraceGetter::UponLeavingGTest() GTEST_LOCK_EXCLUDED_(mutex_) { +#if GTEST_HAS_ABSL + void* caller_frame = nullptr; + if (absl::GetStackTrace(&caller_frame, 1, 3) <= 0) { + caller_frame = nullptr; + } + + MutexLock lock(&mutex_); + caller_frame_ = caller_frame; +#endif // GTEST_HAS_ABSL +} + +// A helper class that creates the premature-exit file in its +// constructor and deletes the file in its destructor. +class ScopedPrematureExitFile { + public: + explicit ScopedPrematureExitFile(const char* premature_exit_filepath) + : premature_exit_filepath_(premature_exit_filepath ? + premature_exit_filepath : "") { + // If a path to the premature-exit file is specified... + if (!premature_exit_filepath_.empty()) { + // create the file with a single "0" character in it. I/O + // errors are ignored as there's nothing better we can do and we + // don't want to fail the test because of this. + FILE* pfile = posix::FOpen(premature_exit_filepath, "w"); + fwrite("0", 1, 1, pfile); + fclose(pfile); + } + } + + ~ScopedPrematureExitFile() { +#if !defined GTEST_OS_ESP8266 + if (!premature_exit_filepath_.empty()) { + int retval = remove(premature_exit_filepath_.c_str()); + if (retval) { + GTEST_LOG_(ERROR) << "Failed to remove premature exit filepath \"" + << premature_exit_filepath_ << "\" with error " + << retval; + } + } +#endif + } + + private: + const std::string premature_exit_filepath_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedPrematureExitFile); +}; + +} // namespace internal + +// class TestEventListeners + +TestEventListeners::TestEventListeners() + : repeater_(new internal::TestEventRepeater()), + default_result_printer_(nullptr), + default_xml_generator_(nullptr) {} + +TestEventListeners::~TestEventListeners() { delete repeater_; } + +// Returns the standard listener responsible for the default console +// output. Can be removed from the listeners list to shut down default +// console output. Note that removing this object from the listener list +// with Release transfers its ownership to the user. +void TestEventListeners::Append(TestEventListener* listener) { + repeater_->Append(listener); +} + +// Removes the given event listener from the list and returns it. It then +// becomes the caller's responsibility to delete the listener. Returns +// NULL if the listener is not found in the list. +TestEventListener* TestEventListeners::Release(TestEventListener* listener) { + if (listener == default_result_printer_) + default_result_printer_ = nullptr; + else if (listener == default_xml_generator_) + default_xml_generator_ = nullptr; + return repeater_->Release(listener); +} + +// Returns repeater that broadcasts the TestEventListener events to all +// subscribers. +TestEventListener* TestEventListeners::repeater() { return repeater_; } + +// Sets the default_result_printer attribute to the provided listener. +// The listener is also added to the listener list and previous +// default_result_printer is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) { + if (default_result_printer_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_result_printer_); + default_result_printer_ = listener; + if (listener != nullptr) Append(listener); + } +} + +// Sets the default_xml_generator attribute to the provided listener. The +// listener is also added to the listener list and previous +// default_xml_generator is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultXmlGenerator(TestEventListener* listener) { + if (default_xml_generator_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_xml_generator_); + default_xml_generator_ = listener; + if (listener != nullptr) Append(listener); + } +} + +// Controls whether events will be forwarded by the repeater to the +// listeners in the list. +bool TestEventListeners::EventForwardingEnabled() const { + return repeater_->forwarding_enabled(); +} + +void TestEventListeners::SuppressEventForwarding() { + repeater_->set_forwarding_enabled(false); +} + +// class UnitTest + +// Gets the singleton UnitTest object. The first time this method is +// called, a UnitTest object is constructed and returned. Consecutive +// calls will return the same object. +// +// We don't protect this under mutex_ as a user is not supposed to +// call this before main() starts, from which point on the return +// value will never change. +UnitTest* UnitTest::GetInstance() { + // CodeGear C++Builder insists on a public destructor for the + // default implementation. Use this implementation to keep good OO + // design with private destructor. + +#if defined(__BORLANDC__) + static UnitTest* const instance = new UnitTest; + return instance; +#else + static UnitTest instance; + return &instance; +#endif // defined(__BORLANDC__) +} + +// Gets the number of successful test suites. +int UnitTest::successful_test_suite_count() const { + return impl()->successful_test_suite_count(); +} + +// Gets the number of failed test suites. +int UnitTest::failed_test_suite_count() const { + return impl()->failed_test_suite_count(); +} + +// Gets the number of all test suites. +int UnitTest::total_test_suite_count() const { + return impl()->total_test_suite_count(); +} + +// Gets the number of all test suites that contain at least one test +// that should run. +int UnitTest::test_suite_to_run_count() const { + return impl()->test_suite_to_run_count(); +} + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +int UnitTest::successful_test_case_count() const { + return impl()->successful_test_suite_count(); +} +int UnitTest::failed_test_case_count() const { + return impl()->failed_test_suite_count(); +} +int UnitTest::total_test_case_count() const { + return impl()->total_test_suite_count(); +} +int UnitTest::test_case_to_run_count() const { + return impl()->test_suite_to_run_count(); +} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +// Gets the number of successful tests. +int UnitTest::successful_test_count() const { + return impl()->successful_test_count(); +} + +// Gets the number of skipped tests. +int UnitTest::skipped_test_count() const { + return impl()->skipped_test_count(); +} + +// Gets the number of failed tests. +int UnitTest::failed_test_count() const { return impl()->failed_test_count(); } + +// Gets the number of disabled tests that will be reported in the XML report. +int UnitTest::reportable_disabled_test_count() const { + return impl()->reportable_disabled_test_count(); +} + +// Gets the number of disabled tests. +int UnitTest::disabled_test_count() const { + return impl()->disabled_test_count(); +} + +// Gets the number of tests to be printed in the XML report. +int UnitTest::reportable_test_count() const { + return impl()->reportable_test_count(); +} + +// Gets the number of all tests. +int UnitTest::total_test_count() const { return impl()->total_test_count(); } + +// Gets the number of tests that should run. +int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); } + +// Gets the time of the test program start, in ms from the start of the +// UNIX epoch. +internal::TimeInMillis UnitTest::start_timestamp() const { + return impl()->start_timestamp(); +} + +// Gets the elapsed time, in milliseconds. +internal::TimeInMillis UnitTest::elapsed_time() const { + return impl()->elapsed_time(); +} + +// Returns true if and only if the unit test passed (i.e. all test suites +// passed). +bool UnitTest::Passed() const { return impl()->Passed(); } + +// Returns true if and only if the unit test failed (i.e. some test suite +// failed or something outside of all tests failed). +bool UnitTest::Failed() const { return impl()->Failed(); } + +// Gets the i-th test suite among all the test suites. i can range from 0 to +// total_test_suite_count() - 1. If i is not in that range, returns NULL. +const TestSuite* UnitTest::GetTestSuite(int i) const { + return impl()->GetTestSuite(i); +} + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +const TestCase* UnitTest::GetTestCase(int i) const { + return impl()->GetTestCase(i); +} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +// Returns the TestResult containing information on test failures and +// properties logged outside of individual test suites. +const TestResult& UnitTest::ad_hoc_test_result() const { + return *impl()->ad_hoc_test_result(); +} + +// Gets the i-th test suite among all the test suites. i can range from 0 to +// total_test_suite_count() - 1. If i is not in that range, returns NULL. +TestSuite* UnitTest::GetMutableTestSuite(int i) { + return impl()->GetMutableSuiteCase(i); +} + +// Returns the list of event listeners that can be used to track events +// inside Google Test. +TestEventListeners& UnitTest::listeners() { + return *impl()->listeners(); +} + +// Registers and returns a global test environment. When a test +// program is run, all global test environments will be set-up in the +// order they were registered. After all tests in the program have +// finished, all global test environments will be torn-down in the +// *reverse* order they were registered. +// +// The UnitTest object takes ownership of the given environment. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +Environment* UnitTest::AddEnvironment(Environment* env) { + if (env == nullptr) { + return nullptr; + } + + impl_->environments().push_back(env); + return env; +} + +// Adds a TestPartResult to the current TestResult object. All Google Test +// assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) eventually call +// this to report their results. The user code should use the +// assertion macros instead of calling this directly. +void UnitTest::AddTestPartResult( + TestPartResult::Type result_type, + const char* file_name, + int line_number, + const std::string& message, + const std::string& os_stack_trace) GTEST_LOCK_EXCLUDED_(mutex_) { + Message msg; + msg << message; + + internal::MutexLock lock(&mutex_); + if (impl_->gtest_trace_stack().size() > 0) { + msg << "\n" << GTEST_NAME_ << " trace:"; + + for (size_t i = impl_->gtest_trace_stack().size(); i > 0; --i) { + const internal::TraceInfo& trace = impl_->gtest_trace_stack()[i - 1]; + msg << "\n" << internal::FormatFileLocation(trace.file, trace.line) + << " " << trace.message; + } + } + + if (os_stack_trace.c_str() != nullptr && !os_stack_trace.empty()) { + msg << internal::kStackTraceMarker << os_stack_trace; + } + + const TestPartResult result = TestPartResult( + result_type, file_name, line_number, msg.GetString().c_str()); + impl_->GetTestPartResultReporterForCurrentThread()-> + ReportTestPartResult(result); + + if (result_type != TestPartResult::kSuccess && + result_type != TestPartResult::kSkip) { + // gtest_break_on_failure takes precedence over + // gtest_throw_on_failure. This allows a user to set the latter + // in the code (perhaps in order to use Google Test assertions + // with another testing framework) and specify the former on the + // command line for debugging. + if (GTEST_FLAG(break_on_failure)) { +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT + // Using DebugBreak on Windows allows gtest to still break into a debugger + // when a failure happens and both the --gtest_break_on_failure and + // the --gtest_catch_exceptions flags are specified. + DebugBreak(); +#elif (!defined(__native_client__)) && \ + ((defined(__clang__) || defined(__GNUC__)) && \ + (defined(__x86_64__) || defined(__i386__))) + // with clang/gcc we can achieve the same effect on x86 by invoking int3 + asm("int3"); +#else + // Dereference nullptr through a volatile pointer to prevent the compiler + // from removing. We use this rather than abort() or __builtin_trap() for + // portability: some debuggers don't correctly trap abort(). + *static_cast<volatile int*>(nullptr) = 1; +#endif // GTEST_OS_WINDOWS + } else if (GTEST_FLAG(throw_on_failure)) { +#if GTEST_HAS_EXCEPTIONS + throw internal::GoogleTestFailureException(result); +#else + // We cannot call abort() as it generates a pop-up in debug mode + // that cannot be suppressed in VC 7.1 or below. + exit(1); +#endif + } + } +} + +// Adds a TestProperty to the current TestResult object when invoked from +// inside a test, to current TestSuite's ad_hoc_test_result_ when invoked +// from SetUpTestSuite or TearDownTestSuite, or to the global property set +// when invoked elsewhere. If the result already contains a property with +// the same key, the value will be updated. +void UnitTest::RecordProperty(const std::string& key, + const std::string& value) { + impl_->RecordProperty(TestProperty(key, value)); +} + +// Runs all tests in this UnitTest object and prints the result. +// Returns 0 if successful, or 1 otherwise. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +int UnitTest::Run() { + const bool in_death_test_child_process = + internal::GTEST_FLAG(internal_run_death_test).length() > 0; + + // Google Test implements this protocol for catching that a test + // program exits before returning control to Google Test: + // + // 1. Upon start, Google Test creates a file whose absolute path + // is specified by the environment variable + // TEST_PREMATURE_EXIT_FILE. + // 2. When Google Test has finished its work, it deletes the file. + // + // This allows a test runner to set TEST_PREMATURE_EXIT_FILE before + // running a Google-Test-based test program and check the existence + // of the file at the end of the test execution to see if it has + // exited prematurely. + + // If we are in the child process of a death test, don't + // create/delete the premature exit file, as doing so is unnecessary + // and will confuse the parent process. Otherwise, create/delete + // the file upon entering/leaving this function. If the program + // somehow exits before this function has a chance to return, the + // premature-exit file will be left undeleted, causing a test runner + // that understands the premature-exit-file protocol to report the + // test as having failed. + const internal::ScopedPrematureExitFile premature_exit_file( + in_death_test_child_process + ? nullptr + : internal::posix::GetEnv("TEST_PREMATURE_EXIT_FILE")); + + // Captures the value of GTEST_FLAG(catch_exceptions). This value will be + // used for the duration of the program. + impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions)); + +#if GTEST_OS_WINDOWS + // Either the user wants Google Test to catch exceptions thrown by the + // tests or this is executing in the context of death test child + // process. In either case the user does not want to see pop-up dialogs + // about crashes - they are expected. + if (impl()->catch_exceptions() || in_death_test_child_process) { +# if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT + // SetErrorMode doesn't exist on CE. + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); +# endif // !GTEST_OS_WINDOWS_MOBILE + +# if (defined(_MSC_VER) || GTEST_OS_WINDOWS_MINGW) && !GTEST_OS_WINDOWS_MOBILE + // Death test children can be terminated with _abort(). On Windows, + // _abort() can show a dialog with a warning message. This forces the + // abort message to go to stderr instead. + _set_error_mode(_OUT_TO_STDERR); +# endif + +# if defined(_MSC_VER) && !GTEST_OS_WINDOWS_MOBILE + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program. We need to suppress + // this dialog or it will pop up for every EXPECT/ASSERT_DEATH statement + // executed. Google Test will notify the user of any unexpected + // failure via stderr. + if (!GTEST_FLAG(break_on_failure)) + _set_abort_behavior( + 0x0, // Clear the following flags: + _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // pop-up window, core dump. + + // In debug mode, the Windows CRT can crash with an assertion over invalid + // input (e.g. passing an invalid file descriptor). The default handling + // for these assertions is to pop up a dialog and wait for user input. + // Instead ask the CRT to dump such assertions to stderr non-interactively. + if (!IsDebuggerPresent()) { + (void)_CrtSetReportMode(_CRT_ASSERT, + _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + (void)_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + } +# endif + } +#endif // GTEST_OS_WINDOWS + + return internal::HandleExceptionsInMethodIfSupported( + impl(), + &internal::UnitTestImpl::RunAllTests, + "auxiliary test code (environments or event listeners)") ? 0 : 1; +} + +// Returns the working directory when the first TEST() or TEST_F() was +// executed. +const char* UnitTest::original_working_dir() const { + return impl_->original_working_dir_.c_str(); +} + +// Returns the TestSuite object for the test that's currently running, +// or NULL if no test is running. +const TestSuite* UnitTest::current_test_suite() const + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + return impl_->current_test_suite(); +} + +// Legacy API is still available but deprecated +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +const TestCase* UnitTest::current_test_case() const + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + return impl_->current_test_suite(); +} +#endif + +// Returns the TestInfo object for the test that's currently running, +// or NULL if no test is running. +const TestInfo* UnitTest::current_test_info() const + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + return impl_->current_test_info(); +} + +// Returns the random seed used at the start of the current test run. +int UnitTest::random_seed() const { return impl_->random_seed(); } + +// Returns ParameterizedTestSuiteRegistry object used to keep track of +// value-parameterized tests and instantiate and register them. +internal::ParameterizedTestSuiteRegistry& +UnitTest::parameterized_test_registry() GTEST_LOCK_EXCLUDED_(mutex_) { + return impl_->parameterized_test_registry(); +} + +// Creates an empty UnitTest. +UnitTest::UnitTest() { + impl_ = new internal::UnitTestImpl(this); +} + +// Destructor of UnitTest. +UnitTest::~UnitTest() { + delete impl_; +} + +// Pushes a trace defined by SCOPED_TRACE() on to the per-thread +// Google Test trace stack. +void UnitTest::PushGTestTrace(const internal::TraceInfo& trace) + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().push_back(trace); +} + +// Pops a trace from the per-thread Google Test trace stack. +void UnitTest::PopGTestTrace() + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().pop_back(); +} + +namespace internal { + +UnitTestImpl::UnitTestImpl(UnitTest* parent) + : parent_(parent), + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4355 /* using this in initializer */) + default_global_test_part_result_reporter_(this), + default_per_thread_test_part_result_reporter_(this), + GTEST_DISABLE_MSC_WARNINGS_POP_() global_test_part_result_repoter_( + &default_global_test_part_result_reporter_), + per_thread_test_part_result_reporter_( + &default_per_thread_test_part_result_reporter_), + parameterized_test_registry_(), + parameterized_tests_registered_(false), + last_death_test_suite_(-1), + current_test_suite_(nullptr), + current_test_info_(nullptr), + ad_hoc_test_result_(), + os_stack_trace_getter_(nullptr), + post_flag_parse_init_performed_(false), + random_seed_(0), // Will be overridden by the flag before first use. + random_(0), // Will be reseeded before first use. + start_timestamp_(0), + elapsed_time_(0), +#if GTEST_HAS_DEATH_TEST + death_test_factory_(new DefaultDeathTestFactory), +#endif + // Will be overridden by the flag before first use. + catch_exceptions_(false) { + listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter); +} + +UnitTestImpl::~UnitTestImpl() { + // Deletes every TestSuite. + ForEach(test_suites_, internal::Delete<TestSuite>); + + // Deletes every Environment. + ForEach(environments_, internal::Delete<Environment>); + + delete os_stack_trace_getter_; +} + +// Adds a TestProperty to the current TestResult object when invoked in a +// context of a test, to current test suite's ad_hoc_test_result when invoke +// from SetUpTestSuite/TearDownTestSuite, or to the global property set +// otherwise. If the result already contains a property with the same key, +// the value will be updated. +void UnitTestImpl::RecordProperty(const TestProperty& test_property) { + std::string xml_element; + TestResult* test_result; // TestResult appropriate for property recording. + + if (current_test_info_ != nullptr) { + xml_element = "testcase"; + test_result = &(current_test_info_->result_); + } else if (current_test_suite_ != nullptr) { + xml_element = "testsuite"; + test_result = &(current_test_suite_->ad_hoc_test_result_); + } else { + xml_element = "testsuites"; + test_result = &ad_hoc_test_result_; + } + test_result->RecordProperty(xml_element, test_property); +} + +#if GTEST_HAS_DEATH_TEST +// Disables event forwarding if the control is currently in a death test +// subprocess. Must not be called before InitGoogleTest. +void UnitTestImpl::SuppressTestEventsIfInSubprocess() { + if (internal_run_death_test_flag_.get() != nullptr) + listeners()->SuppressEventForwarding(); +} +#endif // GTEST_HAS_DEATH_TEST + +// Initializes event listeners performing XML output as specified by +// UnitTestOptions. Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureXmlOutput() { + const std::string& output_format = UnitTestOptions::GetOutputFormat(); + if (output_format == "xml") { + listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); + } else if (output_format == "json") { + listeners()->SetDefaultXmlGenerator(new JsonUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); + } else if (output_format != "") { + GTEST_LOG_(WARNING) << "WARNING: unrecognized output format \"" + << output_format << "\" ignored."; + } +} + +#if GTEST_CAN_STREAM_RESULTS_ +// Initializes event listeners for streaming test results in string form. +// Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureStreamingOutput() { + const std::string& target = GTEST_FLAG(stream_result_to); + if (!target.empty()) { + const size_t pos = target.find(':'); + if (pos != std::string::npos) { + listeners()->Append(new StreamingListener(target.substr(0, pos), + target.substr(pos+1))); + } else { + GTEST_LOG_(WARNING) << "unrecognized streaming target \"" << target + << "\" ignored."; + } + } +} +#endif // GTEST_CAN_STREAM_RESULTS_ + +// Performs initialization dependent upon flag values obtained in +// ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to +// ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest +// this function is also called from RunAllTests. Since this function can be +// called more than once, it has to be idempotent. +void UnitTestImpl::PostFlagParsingInit() { + // Ensures that this function does not execute more than once. + if (!post_flag_parse_init_performed_) { + post_flag_parse_init_performed_ = true; + +#if defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_) + // Register to send notifications about key process state changes. + listeners()->Append(new GTEST_CUSTOM_TEST_EVENT_LISTENER_()); +#endif // defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_) + +#if GTEST_HAS_DEATH_TEST + InitDeathTestSubprocessControlInfo(); + SuppressTestEventsIfInSubprocess(); +#endif // GTEST_HAS_DEATH_TEST + + // Registers parameterized tests. This makes parameterized tests + // available to the UnitTest reflection API without running + // RUN_ALL_TESTS. + RegisterParameterizedTests(); + + // Configures listeners for XML output. This makes it possible for users + // to shut down the default XML output before invoking RUN_ALL_TESTS. + ConfigureXmlOutput(); + + if (GTEST_FLAG(brief)) { + listeners()->SetDefaultResultPrinter(new BriefUnitTestResultPrinter); + } + +#if GTEST_CAN_STREAM_RESULTS_ + // Configures listeners for streaming test results to the specified server. + ConfigureStreamingOutput(); +#endif // GTEST_CAN_STREAM_RESULTS_ + +#if GTEST_HAS_ABSL + if (GTEST_FLAG(install_failure_signal_handler)) { + absl::FailureSignalHandlerOptions options; + absl::InstallFailureSignalHandler(options); + } +#endif // GTEST_HAS_ABSL + } +} + +// A predicate that checks the name of a TestSuite against a known +// value. +// +// This is used for implementation of the UnitTest class only. We put +// it in the anonymous namespace to prevent polluting the outer +// namespace. +// +// TestSuiteNameIs is copyable. +class TestSuiteNameIs { + public: + // Constructor. + explicit TestSuiteNameIs(const std::string& name) : name_(name) {} + + // Returns true if and only if the name of test_suite matches name_. + bool operator()(const TestSuite* test_suite) const { + return test_suite != nullptr && + strcmp(test_suite->name(), name_.c_str()) == 0; + } + + private: + std::string name_; +}; + +// Finds and returns a TestSuite with the given name. If one doesn't +// exist, creates one and returns it. It's the CALLER'S +// RESPONSIBILITY to ensure that this function is only called WHEN THE +// TESTS ARE NOT SHUFFLED. +// +// Arguments: +// +// test_suite_name: name of the test suite +// type_param: the name of the test suite's type parameter, or NULL if +// this is not a typed or a type-parameterized test suite. +// set_up_tc: pointer to the function that sets up the test suite +// tear_down_tc: pointer to the function that tears down the test suite +TestSuite* UnitTestImpl::GetTestSuite( + const char* test_suite_name, const char* type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc) { + // Can we find a TestSuite with the given name? + const auto test_suite = + std::find_if(test_suites_.rbegin(), test_suites_.rend(), + TestSuiteNameIs(test_suite_name)); + + if (test_suite != test_suites_.rend()) return *test_suite; + + // No. Let's create one. + auto* const new_test_suite = + new TestSuite(test_suite_name, type_param, set_up_tc, tear_down_tc); + + // Is this a death test suite? + if (internal::UnitTestOptions::MatchesFilter(test_suite_name, + kDeathTestSuiteFilter)) { + // Yes. Inserts the test suite after the last death test suite + // defined so far. This only works when the test suites haven't + // been shuffled. Otherwise we may end up running a death test + // after a non-death test. + ++last_death_test_suite_; + test_suites_.insert(test_suites_.begin() + last_death_test_suite_, + new_test_suite); + } else { + // No. Appends to the end of the list. + test_suites_.push_back(new_test_suite); + } + + test_suite_indices_.push_back(static_cast<int>(test_suite_indices_.size())); + return new_test_suite; +} + +// Helpers for setting up / tearing down the given environment. They +// are for use in the ForEach() function. +static void SetUpEnvironment(Environment* env) { env->SetUp(); } +static void TearDownEnvironment(Environment* env) { env->TearDown(); } + +// Runs all tests in this UnitTest object, prints the result, and +// returns true if all tests are successful. If any exception is +// thrown during a test, the test is considered to be failed, but the +// rest of the tests will still be run. +// +// When parameterized tests are enabled, it expands and registers +// parameterized tests first in RegisterParameterizedTests(). +// All other functions called from RunAllTests() may safely assume that +// parameterized tests are ready to be counted and run. +bool UnitTestImpl::RunAllTests() { + // True if and only if Google Test is initialized before RUN_ALL_TESTS() is + // called. + const bool gtest_is_initialized_before_run_all_tests = GTestIsInitialized(); + + // Do not run any test if the --help flag was specified. + if (g_help_flag) + return true; + + // Repeats the call to the post-flag parsing initialization in case the + // user didn't call InitGoogleTest. + PostFlagParsingInit(); + + // Even if sharding is not on, test runners may want to use the + // GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding + // protocol. + internal::WriteToShardStatusFileIfNeeded(); + + // True if and only if we are in a subprocess for running a thread-safe-style + // death test. + bool in_subprocess_for_death_test = false; + +#if GTEST_HAS_DEATH_TEST + in_subprocess_for_death_test = + (internal_run_death_test_flag_.get() != nullptr); +# if defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) + if (in_subprocess_for_death_test) { + GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_(); + } +# endif // defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) +#endif // GTEST_HAS_DEATH_TEST + + const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex, + in_subprocess_for_death_test); + + // Compares the full test names with the filter to decide which + // tests to run. + const bool has_tests_to_run = FilterTests(should_shard + ? HONOR_SHARDING_PROTOCOL + : IGNORE_SHARDING_PROTOCOL) > 0; + + // Lists the tests and exits if the --gtest_list_tests flag was specified. + if (GTEST_FLAG(list_tests)) { + // This must be called *after* FilterTests() has been called. + ListTestsMatchingFilter(); + return true; + } + + random_seed_ = GTEST_FLAG(shuffle) ? + GetRandomSeedFromFlag(GTEST_FLAG(random_seed)) : 0; + + // True if and only if at least one test has failed. + bool failed = false; + + TestEventListener* repeater = listeners()->repeater(); + + start_timestamp_ = GetTimeInMillis(); + repeater->OnTestProgramStart(*parent_); + + // How many times to repeat the tests? We don't want to repeat them + // when we are inside the subprocess of a death test. + const int repeat = in_subprocess_for_death_test ? 1 : GTEST_FLAG(repeat); + // Repeats forever if the repeat count is negative. + const bool gtest_repeat_forever = repeat < 0; + for (int i = 0; gtest_repeat_forever || i != repeat; i++) { + // We want to preserve failures generated by ad-hoc test + // assertions executed before RUN_ALL_TESTS(). + ClearNonAdHocTestResult(); + + Timer timer; + + // Shuffles test suites and tests if requested. + if (has_tests_to_run && GTEST_FLAG(shuffle)) { + random()->Reseed(static_cast<uint32_t>(random_seed_)); + // This should be done before calling OnTestIterationStart(), + // such that a test event listener can see the actual test order + // in the event. + ShuffleTests(); + } + + // Tells the unit test event listeners that the tests are about to start. + repeater->OnTestIterationStart(*parent_, i); + + // Runs each test suite if there is at least one test to run. + if (has_tests_to_run) { + // Sets up all environments beforehand. + repeater->OnEnvironmentsSetUpStart(*parent_); + ForEach(environments_, SetUpEnvironment); + repeater->OnEnvironmentsSetUpEnd(*parent_); + + // Runs the tests only if there was no fatal failure or skip triggered + // during global set-up. + if (Test::IsSkipped()) { + // Emit diagnostics when global set-up calls skip, as it will not be + // emitted by default. + TestResult& test_result = + *internal::GetUnitTestImpl()->current_test_result(); + for (int j = 0; j < test_result.total_part_count(); ++j) { + const TestPartResult& test_part_result = + test_result.GetTestPartResult(j); + if (test_part_result.type() == TestPartResult::kSkip) { + const std::string& result = test_part_result.message(); + printf("%s\n", result.c_str()); + } + } + fflush(stdout); + } else if (!Test::HasFatalFailure()) { + for (int test_index = 0; test_index < total_test_suite_count(); + test_index++) { + GetMutableSuiteCase(test_index)->Run(); + if (GTEST_FLAG(fail_fast) && + GetMutableSuiteCase(test_index)->Failed()) { + for (int j = test_index + 1; j < total_test_suite_count(); j++) { + GetMutableSuiteCase(j)->Skip(); + } + break; + } + } + } else if (Test::HasFatalFailure()) { + // If there was a fatal failure during the global setup then we know we + // aren't going to run any tests. Explicitly mark all of the tests as + // skipped to make this obvious in the output. + for (int test_index = 0; test_index < total_test_suite_count(); + test_index++) { + GetMutableSuiteCase(test_index)->Skip(); + } + } + + // Tears down all environments in reverse order afterwards. + repeater->OnEnvironmentsTearDownStart(*parent_); + std::for_each(environments_.rbegin(), environments_.rend(), + TearDownEnvironment); + repeater->OnEnvironmentsTearDownEnd(*parent_); + } + + elapsed_time_ = timer.Elapsed(); + + // Tells the unit test event listener that the tests have just finished. + repeater->OnTestIterationEnd(*parent_, i); + + // Gets the result and clears it. + if (!Passed()) { + failed = true; + } + + // Restores the original test order after the iteration. This + // allows the user to quickly repro a failure that happens in the + // N-th iteration without repeating the first (N - 1) iterations. + // This is not enclosed in "if (GTEST_FLAG(shuffle)) { ... }", in + // case the user somehow changes the value of the flag somewhere + // (it's always safe to unshuffle the tests). + UnshuffleTests(); + + if (GTEST_FLAG(shuffle)) { + // Picks a new random seed for each iteration. + random_seed_ = GetNextRandomSeed(random_seed_); + } + } + + repeater->OnTestProgramEnd(*parent_); + + if (!gtest_is_initialized_before_run_all_tests) { + ColoredPrintf( + GTestColor::kRed, + "\nIMPORTANT NOTICE - DO NOT IGNORE:\n" + "This test program did NOT call " GTEST_INIT_GOOGLE_TEST_NAME_ + "() before calling RUN_ALL_TESTS(). This is INVALID. Soon " GTEST_NAME_ + " will start to enforce the valid usage. " + "Please fix it ASAP, or IT WILL START TO FAIL.\n"); // NOLINT +#if GTEST_FOR_GOOGLE_ + ColoredPrintf(GTestColor::kRed, + "For more details, see http://wiki/Main/ValidGUnitMain.\n"); +#endif // GTEST_FOR_GOOGLE_ + } + + return !failed; +} + +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded() { + const char* const test_shard_file = posix::GetEnv(kTestShardStatusFile); + if (test_shard_file != nullptr) { + FILE* const file = posix::FOpen(test_shard_file, "w"); + if (file == nullptr) { + ColoredPrintf(GTestColor::kRed, + "Could not write to the test shard status file \"%s\" " + "specified by the %s environment variable.\n", + test_shard_file, kTestShardStatusFile); + fflush(stdout); + exit(EXIT_FAILURE); + } + fclose(file); + } +} + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (i.e., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +bool ShouldShard(const char* total_shards_env, + const char* shard_index_env, + bool in_subprocess_for_death_test) { + if (in_subprocess_for_death_test) { + return false; + } + + const int32_t total_shards = Int32FromEnvOrDie(total_shards_env, -1); + const int32_t shard_index = Int32FromEnvOrDie(shard_index_env, -1); + + if (total_shards == -1 && shard_index == -1) { + return false; + } else if (total_shards == -1 && shard_index != -1) { + const Message msg = Message() + << "Invalid environment variables: you have " + << kTestShardIndex << " = " << shard_index + << ", but have left " << kTestTotalShards << " unset.\n"; + ColoredPrintf(GTestColor::kRed, "%s", msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (total_shards != -1 && shard_index == -1) { + const Message msg = Message() + << "Invalid environment variables: you have " + << kTestTotalShards << " = " << total_shards + << ", but have left " << kTestShardIndex << " unset.\n"; + ColoredPrintf(GTestColor::kRed, "%s", msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (shard_index < 0 || shard_index >= total_shards) { + const Message msg = Message() + << "Invalid environment variables: we require 0 <= " + << kTestShardIndex << " < " << kTestTotalShards + << ", but you have " << kTestShardIndex << "=" << shard_index + << ", " << kTestTotalShards << "=" << total_shards << ".\n"; + ColoredPrintf(GTestColor::kRed, "%s", msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } + + return total_shards > 1; +} + +// Parses the environment variable var as an Int32. If it is unset, +// returns default_val. If it is not an Int32, prints an error +// and aborts. +int32_t Int32FromEnvOrDie(const char* var, int32_t default_val) { + const char* str_val = posix::GetEnv(var); + if (str_val == nullptr) { + return default_val; + } + + int32_t result; + if (!ParseInt32(Message() << "The value of environment variable " << var, + str_val, &result)) { + exit(EXIT_FAILURE); + } + return result; +} + +// Given the total number of shards, the shard index, and the test id, +// returns true if and only if the test should be run on this shard. The test id +// is some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +bool ShouldRunTestOnShard(int total_shards, int shard_index, int test_id) { + return (test_id % total_shards) == shard_index; +} + +// Compares the name of each test with the user-specified filter to +// decide whether the test should be run, then records the result in +// each TestSuite and TestInfo object. +// If shard_tests == true, further filters tests based on sharding +// variables in the environment - see +// https://github.com/google/googletest/blob/master/googletest/docs/advanced.md +// . Returns the number of tests that should run. +int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) { + const int32_t total_shards = shard_tests == HONOR_SHARDING_PROTOCOL ? + Int32FromEnvOrDie(kTestTotalShards, -1) : -1; + const int32_t shard_index = shard_tests == HONOR_SHARDING_PROTOCOL ? + Int32FromEnvOrDie(kTestShardIndex, -1) : -1; + + // num_runnable_tests are the number of tests that will + // run across all shards (i.e., match filter and are not disabled). + // num_selected_tests are the number of tests to be run on + // this shard. + int num_runnable_tests = 0; + int num_selected_tests = 0; + for (auto* test_suite : test_suites_) { + const std::string& test_suite_name = test_suite->name(); + test_suite->set_should_run(false); + + for (size_t j = 0; j < test_suite->test_info_list().size(); j++) { + TestInfo* const test_info = test_suite->test_info_list()[j]; + const std::string test_name(test_info->name()); + // A test is disabled if test suite name or test name matches + // kDisableTestFilter. + const bool is_disabled = internal::UnitTestOptions::MatchesFilter( + test_suite_name, kDisableTestFilter) || + internal::UnitTestOptions::MatchesFilter( + test_name, kDisableTestFilter); + test_info->is_disabled_ = is_disabled; + + const bool matches_filter = internal::UnitTestOptions::FilterMatchesTest( + test_suite_name, test_name); + test_info->matches_filter_ = matches_filter; + + const bool is_runnable = + (GTEST_FLAG(also_run_disabled_tests) || !is_disabled) && + matches_filter; + + const bool is_in_another_shard = + shard_tests != IGNORE_SHARDING_PROTOCOL && + !ShouldRunTestOnShard(total_shards, shard_index, num_runnable_tests); + test_info->is_in_another_shard_ = is_in_another_shard; + const bool is_selected = is_runnable && !is_in_another_shard; + + num_runnable_tests += is_runnable; + num_selected_tests += is_selected; + + test_info->should_run_ = is_selected; + test_suite->set_should_run(test_suite->should_run() || is_selected); + } + } + return num_selected_tests; +} + +// Prints the given C-string on a single line by replacing all '\n' +// characters with string "\\n". If the output takes more than +// max_length characters, only prints the first max_length characters +// and "...". +static void PrintOnOneLine(const char* str, int max_length) { + if (str != nullptr) { + for (int i = 0; *str != '\0'; ++str) { + if (i >= max_length) { + printf("..."); + break; + } + if (*str == '\n') { + printf("\\n"); + i += 2; + } else { + printf("%c", *str); + ++i; + } + } + } +} + +// Prints the names of the tests matching the user-specified filter flag. +void UnitTestImpl::ListTestsMatchingFilter() { + // Print at most this many characters for each type/value parameter. + const int kMaxParamLength = 250; + + for (auto* test_suite : test_suites_) { + bool printed_test_suite_name = false; + + for (size_t j = 0; j < test_suite->test_info_list().size(); j++) { + const TestInfo* const test_info = test_suite->test_info_list()[j]; + if (test_info->matches_filter_) { + if (!printed_test_suite_name) { + printed_test_suite_name = true; + printf("%s.", test_suite->name()); + if (test_suite->type_param() != nullptr) { + printf(" # %s = ", kTypeParamLabel); + // We print the type parameter on a single line to make + // the output easy to parse by a program. + PrintOnOneLine(test_suite->type_param(), kMaxParamLength); + } + printf("\n"); + } + printf(" %s", test_info->name()); + if (test_info->value_param() != nullptr) { + printf(" # %s = ", kValueParamLabel); + // We print the value parameter on a single line to make the + // output easy to parse by a program. + PrintOnOneLine(test_info->value_param(), kMaxParamLength); + } + printf("\n"); + } + } + } + fflush(stdout); + const std::string& output_format = UnitTestOptions::GetOutputFormat(); + if (output_format == "xml" || output_format == "json") { + FILE* fileout = OpenFileForWriting( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str()); + std::stringstream stream; + if (output_format == "xml") { + XmlUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str()) + .PrintXmlTestsList(&stream, test_suites_); + } else if (output_format == "json") { + JsonUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str()) + .PrintJsonTestList(&stream, test_suites_); + } + fprintf(fileout, "%s", StringStreamToString(&stream).c_str()); + fclose(fileout); + } +} + +// Sets the OS stack trace getter. +// +// Does nothing if the input and the current OS stack trace getter are +// the same; otherwise, deletes the old getter and makes the input the +// current getter. +void UnitTestImpl::set_os_stack_trace_getter( + OsStackTraceGetterInterface* getter) { + if (os_stack_trace_getter_ != getter) { + delete os_stack_trace_getter_; + os_stack_trace_getter_ = getter; + } +} + +// Returns the current OS stack trace getter if it is not NULL; +// otherwise, creates an OsStackTraceGetter, makes it the current +// getter, and returns it. +OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() { + if (os_stack_trace_getter_ == nullptr) { +#ifdef GTEST_OS_STACK_TRACE_GETTER_ + os_stack_trace_getter_ = new GTEST_OS_STACK_TRACE_GETTER_; +#else + os_stack_trace_getter_ = new OsStackTraceGetter; +#endif // GTEST_OS_STACK_TRACE_GETTER_ + } + + return os_stack_trace_getter_; +} + +// Returns the most specific TestResult currently running. +TestResult* UnitTestImpl::current_test_result() { + if (current_test_info_ != nullptr) { + return ¤t_test_info_->result_; + } + if (current_test_suite_ != nullptr) { + return ¤t_test_suite_->ad_hoc_test_result_; + } + return &ad_hoc_test_result_; +} + +// Shuffles all test suites, and the tests within each test suite, +// making sure that death tests are still run first. +void UnitTestImpl::ShuffleTests() { + // Shuffles the death test suites. + ShuffleRange(random(), 0, last_death_test_suite_ + 1, &test_suite_indices_); + + // Shuffles the non-death test suites. + ShuffleRange(random(), last_death_test_suite_ + 1, + static_cast<int>(test_suites_.size()), &test_suite_indices_); + + // Shuffles the tests inside each test suite. + for (auto& test_suite : test_suites_) { + test_suite->ShuffleTests(random()); + } +} + +// Restores the test suites and tests to their order before the first shuffle. +void UnitTestImpl::UnshuffleTests() { + for (size_t i = 0; i < test_suites_.size(); i++) { + // Unshuffles the tests in each test suite. + test_suites_[i]->UnshuffleTests(); + // Resets the index of each test suite. + test_suite_indices_[i] = static_cast<int>(i); + } +} + +// Returns the current OS stack trace as an std::string. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +std::string GetCurrentOsStackTraceExceptTop(UnitTest* /*unit_test*/, + int skip_count) { + // We pass skip_count + 1 to skip this wrapper function in addition + // to what the user really wants to skip. + return GetUnitTestImpl()->CurrentOsStackTraceExceptTop(skip_count + 1); +} + +// Used by the GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_ macro to +// suppress unreachable code warnings. +namespace { +class ClassUniqueToAlwaysTrue {}; +} + +bool IsTrue(bool condition) { return condition; } + +bool AlwaysTrue() { +#if GTEST_HAS_EXCEPTIONS + // This condition is always false so AlwaysTrue() never actually throws, + // but it makes the compiler think that it may throw. + if (IsTrue(false)) + throw ClassUniqueToAlwaysTrue(); +#endif // GTEST_HAS_EXCEPTIONS + return true; +} + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +bool SkipPrefix(const char* prefix, const char** pstr) { + const size_t prefix_len = strlen(prefix); + if (strncmp(*pstr, prefix, prefix_len) == 0) { + *pstr += prefix_len; + return true; + } + return false; +} + +// Parses a string as a command line flag. The string should have +// the format "--flag=value". When def_optional is true, the "=value" +// part can be omitted. +// +// Returns the value of the flag, or NULL if the parsing failed. +static const char* ParseFlagValue(const char* str, const char* flag, + bool def_optional) { + // str and flag must not be NULL. + if (str == nullptr || flag == nullptr) return nullptr; + + // The flag must start with "--" followed by GTEST_FLAG_PREFIX_. + const std::string flag_str = std::string("--") + GTEST_FLAG_PREFIX_ + flag; + const size_t flag_len = flag_str.length(); + if (strncmp(str, flag_str.c_str(), flag_len) != 0) return nullptr; + + // Skips the flag name. + const char* flag_end = str + flag_len; + + // When def_optional is true, it's OK to not have a "=value" part. + if (def_optional && (flag_end[0] == '\0')) { + return flag_end; + } + + // If def_optional is true and there are more characters after the + // flag name, or if def_optional is false, there must be a '=' after + // the flag name. + if (flag_end[0] != '=') return nullptr; + + // Returns the string after "=". + return flag_end + 1; +} + +// Parses a string for a bool flag, in the form of either +// "--flag=value" or "--flag". +// +// In the former case, the value is taken as true as long as it does +// not start with '0', 'f', or 'F'. +// +// In the latter case, the value is taken as true. +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +static bool ParseBoolFlag(const char* str, const char* flag, bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Converts the string value to a bool. + *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F'); + return true; +} + +// Parses a string for an int32_t flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseInt32Flag(const char* str, const char* flag, int32_t* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + return ParseInt32(Message() << "The value of flag --" << flag, + value_str, value); +} + +// Parses a string for a string flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +template <typename String> +static bool ParseStringFlag(const char* str, const char* flag, String* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + *value = value_str; + return true; +} + +// Determines whether a string has a prefix that Google Test uses for its +// flags, i.e., starts with GTEST_FLAG_PREFIX_ or GTEST_FLAG_PREFIX_DASH_. +// If Google Test detects that a command line flag has its prefix but is not +// recognized, it will print its help message. Flags starting with +// GTEST_INTERNAL_PREFIX_ followed by "internal_" are considered Google Test +// internal flags and do not trigger the help message. +static bool HasGoogleTestFlagPrefix(const char* str) { + return (SkipPrefix("--", &str) || + SkipPrefix("-", &str) || + SkipPrefix("/", &str)) && + !SkipPrefix(GTEST_FLAG_PREFIX_ "internal_", &str) && + (SkipPrefix(GTEST_FLAG_PREFIX_, &str) || + SkipPrefix(GTEST_FLAG_PREFIX_DASH_, &str)); +} + +// Prints a string containing code-encoded text. The following escape +// sequences can be used in the string to control the text color: +// +// @@ prints a single '@' character. +// @R changes the color to red. +// @G changes the color to green. +// @Y changes the color to yellow. +// @D changes to the default terminal text color. +// +static void PrintColorEncoded(const char* str) { + GTestColor color = GTestColor::kDefault; // The current color. + + // Conceptually, we split the string into segments divided by escape + // sequences. Then we print one segment at a time. At the end of + // each iteration, the str pointer advances to the beginning of the + // next segment. + for (;;) { + const char* p = strchr(str, '@'); + if (p == nullptr) { + ColoredPrintf(color, "%s", str); + return; + } + + ColoredPrintf(color, "%s", std::string(str, p).c_str()); + + const char ch = p[1]; + str = p + 2; + if (ch == '@') { + ColoredPrintf(color, "@"); + } else if (ch == 'D') { + color = GTestColor::kDefault; + } else if (ch == 'R') { + color = GTestColor::kRed; + } else if (ch == 'G') { + color = GTestColor::kGreen; + } else if (ch == 'Y') { + color = GTestColor::kYellow; + } else { + --str; + } + } +} + +static const char kColorEncodedHelpMessage[] = + "This program contains tests written using " GTEST_NAME_ + ". You can use the\n" + "following command line flags to control its behavior:\n" + "\n" + "Test Selection:\n" + " @G--" GTEST_FLAG_PREFIX_ + "list_tests@D\n" + " List the names of all tests instead of running them. The name of\n" + " TEST(Foo, Bar) is \"Foo.Bar\".\n" + " @G--" GTEST_FLAG_PREFIX_ + "filter=@YPOSITIVE_PATTERNS" + "[@G-@YNEGATIVE_PATTERNS]@D\n" + " Run only the tests whose name matches one of the positive patterns " + "but\n" + " none of the negative patterns. '?' matches any single character; " + "'*'\n" + " matches any substring; ':' separates two patterns.\n" + " @G--" GTEST_FLAG_PREFIX_ + "also_run_disabled_tests@D\n" + " Run all disabled tests too.\n" + "\n" + "Test Execution:\n" + " @G--" GTEST_FLAG_PREFIX_ + "repeat=@Y[COUNT]@D\n" + " Run the tests repeatedly; use a negative count to repeat forever.\n" + " @G--" GTEST_FLAG_PREFIX_ + "shuffle@D\n" + " Randomize tests' orders on every iteration.\n" + " @G--" GTEST_FLAG_PREFIX_ + "random_seed=@Y[NUMBER]@D\n" + " Random number seed to use for shuffling test orders (between 1 and\n" + " 99999, or 0 to use a seed based on the current time).\n" + "\n" + "Test Output:\n" + " @G--" GTEST_FLAG_PREFIX_ + "color=@Y(@Gyes@Y|@Gno@Y|@Gauto@Y)@D\n" + " Enable/disable colored output. The default is @Gauto@D.\n" + " @G--" GTEST_FLAG_PREFIX_ + "brief=1@D\n" + " Only print test failures.\n" + " @G--" GTEST_FLAG_PREFIX_ + "print_time=0@D\n" + " Don't print the elapsed time of each test.\n" + " @G--" GTEST_FLAG_PREFIX_ + "output=@Y(@Gjson@Y|@Gxml@Y)[@G:@YDIRECTORY_PATH@G" GTEST_PATH_SEP_ + "@Y|@G:@YFILE_PATH]@D\n" + " Generate a JSON or XML report in the given directory or with the " + "given\n" + " file name. @YFILE_PATH@D defaults to @Gtest_detail.xml@D.\n" +# if GTEST_CAN_STREAM_RESULTS_ + " @G--" GTEST_FLAG_PREFIX_ + "stream_result_to=@YHOST@G:@YPORT@D\n" + " Stream test results to the given server.\n" +# endif // GTEST_CAN_STREAM_RESULTS_ + "\n" + "Assertion Behavior:\n" +# if GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS + " @G--" GTEST_FLAG_PREFIX_ + "death_test_style=@Y(@Gfast@Y|@Gthreadsafe@Y)@D\n" + " Set the default death test style.\n" +# endif // GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS + " @G--" GTEST_FLAG_PREFIX_ + "break_on_failure@D\n" + " Turn assertion failures into debugger break-points.\n" + " @G--" GTEST_FLAG_PREFIX_ + "throw_on_failure@D\n" + " Turn assertion failures into C++ exceptions for use by an external\n" + " test framework.\n" + " @G--" GTEST_FLAG_PREFIX_ + "catch_exceptions=0@D\n" + " Do not report exceptions as test failures. Instead, allow them\n" + " to crash the program or throw a pop-up (on Windows).\n" + "\n" + "Except for @G--" GTEST_FLAG_PREFIX_ + "list_tests@D, you can alternatively set " + "the corresponding\n" + "environment variable of a flag (all letters in upper-case). For example, " + "to\n" + "disable colored text output, you can either specify " + "@G--" GTEST_FLAG_PREFIX_ + "color=no@D or set\n" + "the @G" GTEST_FLAG_PREFIX_UPPER_ + "COLOR@D environment variable to @Gno@D.\n" + "\n" + "For more information, please read the " GTEST_NAME_ + " documentation at\n" + "@G" GTEST_PROJECT_URL_ "@D. If you find a bug in " GTEST_NAME_ + "\n" + "(not one in your own code or tests), please report it to\n" + "@G<" GTEST_DEV_EMAIL_ ">@D.\n"; + +static bool ParseGoogleTestFlag(const char* const arg) { + return ParseBoolFlag(arg, kAlsoRunDisabledTestsFlag, + >EST_FLAG(also_run_disabled_tests)) || + ParseBoolFlag(arg, kBreakOnFailureFlag, + >EST_FLAG(break_on_failure)) || + ParseBoolFlag(arg, kCatchExceptionsFlag, + >EST_FLAG(catch_exceptions)) || + ParseStringFlag(arg, kColorFlag, >EST_FLAG(color)) || + ParseStringFlag(arg, kDeathTestStyleFlag, + >EST_FLAG(death_test_style)) || + ParseBoolFlag(arg, kDeathTestUseFork, + >EST_FLAG(death_test_use_fork)) || + ParseBoolFlag(arg, kFailFast, >EST_FLAG(fail_fast)) || + ParseStringFlag(arg, kFilterFlag, >EST_FLAG(filter)) || + ParseStringFlag(arg, kInternalRunDeathTestFlag, + >EST_FLAG(internal_run_death_test)) || + ParseBoolFlag(arg, kListTestsFlag, >EST_FLAG(list_tests)) || + ParseStringFlag(arg, kOutputFlag, >EST_FLAG(output)) || + ParseBoolFlag(arg, kBriefFlag, >EST_FLAG(brief)) || + ParseBoolFlag(arg, kPrintTimeFlag, >EST_FLAG(print_time)) || + ParseBoolFlag(arg, kPrintUTF8Flag, >EST_FLAG(print_utf8)) || + ParseInt32Flag(arg, kRandomSeedFlag, >EST_FLAG(random_seed)) || + ParseInt32Flag(arg, kRepeatFlag, >EST_FLAG(repeat)) || + ParseBoolFlag(arg, kShuffleFlag, >EST_FLAG(shuffle)) || + ParseInt32Flag(arg, kStackTraceDepthFlag, + >EST_FLAG(stack_trace_depth)) || + ParseStringFlag(arg, kStreamResultToFlag, + >EST_FLAG(stream_result_to)) || + ParseBoolFlag(arg, kThrowOnFailureFlag, >EST_FLAG(throw_on_failure)); +} + +#if GTEST_USE_OWN_FLAGFILE_FLAG_ +static void LoadFlagsFromFile(const std::string& path) { + FILE* flagfile = posix::FOpen(path.c_str(), "r"); + if (!flagfile) { + GTEST_LOG_(FATAL) << "Unable to open file \"" << GTEST_FLAG(flagfile) + << "\""; + } + std::string contents(ReadEntireFile(flagfile)); + posix::FClose(flagfile); + std::vector<std::string> lines; + SplitString(contents, '\n', &lines); + for (size_t i = 0; i < lines.size(); ++i) { + if (lines[i].empty()) + continue; + if (!ParseGoogleTestFlag(lines[i].c_str())) + g_help_flag = true; + } +} +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. The type parameter CharType can be +// instantiated to either char or wchar_t. +template <typename CharType> +void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) { + for (int i = 1; i < *argc; i++) { + const std::string arg_string = StreamableToString(argv[i]); + const char* const arg = arg_string.c_str(); + + using internal::ParseBoolFlag; + using internal::ParseInt32Flag; + using internal::ParseStringFlag; + + bool remove_flag = false; + if (ParseGoogleTestFlag(arg)) { + remove_flag = true; +#if GTEST_USE_OWN_FLAGFILE_FLAG_ + } else if (ParseStringFlag(arg, kFlagfileFlag, >EST_FLAG(flagfile))) { + LoadFlagsFromFile(GTEST_FLAG(flagfile)); + remove_flag = true; +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ + } else if (arg_string == "--help" || arg_string == "-h" || + arg_string == "-?" || arg_string == "/?" || + HasGoogleTestFlagPrefix(arg)) { + // Both help flag and unrecognized Google Test flags (excluding + // internal ones) trigger help display. + g_help_flag = true; + } + + if (remove_flag) { + // Shift the remainder of the argv list left by one. Note + // that argv has (*argc + 1) elements, the last one always being + // NULL. The following loop moves the trailing NULL element as + // well. + for (int j = i; j != *argc; j++) { + argv[j] = argv[j + 1]; + } + + // Decrements the argument count. + (*argc)--; + + // We also need to decrement the iterator as we just removed + // an element. + i--; + } + } + + if (g_help_flag) { + // We print the help here instead of in RUN_ALL_TESTS(), as the + // latter may not be called at all if the user is using Google + // Test with another testing framework. + PrintColorEncoded(kColorEncodedHelpMessage); + } +} + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. +void ParseGoogleTestFlagsOnly(int* argc, char** argv) { + ParseGoogleTestFlagsOnlyImpl(argc, argv); + + // Fix the value of *_NSGetArgc() on macOS, but if and only if + // *_NSGetArgv() == argv + // Only applicable to char** version of argv +#if GTEST_OS_MAC +#ifndef GTEST_OS_IOS + if (*_NSGetArgv() == argv) { + *_NSGetArgc() = *argc; + } +#endif +#endif +} +void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv) { + ParseGoogleTestFlagsOnlyImpl(argc, argv); +} + +// The internal implementation of InitGoogleTest(). +// +// The type parameter CharType can be instantiated to either char or +// wchar_t. +template <typename CharType> +void InitGoogleTestImpl(int* argc, CharType** argv) { + // We don't want to run the initialization code twice. + if (GTestIsInitialized()) return; + + if (*argc <= 0) return; + + g_argvs.clear(); + for (int i = 0; i != *argc; i++) { + g_argvs.push_back(StreamableToString(argv[i])); + } + +#if GTEST_HAS_ABSL + absl::InitializeSymbolizer(g_argvs[0].c_str()); +#endif // GTEST_HAS_ABSL + + ParseGoogleTestFlagsOnly(argc, argv); + GetUnitTestImpl()->PostFlagParsingInit(); +} + +} // namespace internal + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +void InitGoogleTest(int* argc, char** argv) { +#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv); +#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + internal::InitGoogleTestImpl(argc, argv); +#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) +} + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +void InitGoogleTest(int* argc, wchar_t** argv) { +#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv); +#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + internal::InitGoogleTestImpl(argc, argv); +#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) +} + +// This overloaded version can be used on Arduino/embedded platforms where +// there is no argc/argv. +void InitGoogleTest() { + // Since Arduino doesn't have a command line, fake out the argc/argv arguments + int argc = 1; + const auto arg0 = "dummy"; + char* argv0 = const_cast<char*>(arg0); + char** argv = &argv0; + +#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(&argc, argv); +#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) + internal::InitGoogleTestImpl(&argc, argv); +#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) +} + +std::string TempDir() { +#if defined(GTEST_CUSTOM_TEMPDIR_FUNCTION_) + return GTEST_CUSTOM_TEMPDIR_FUNCTION_(); +#elif GTEST_OS_WINDOWS_MOBILE + return "\\temp\\"; +#elif GTEST_OS_WINDOWS + const char* temp_dir = internal::posix::GetEnv("TEMP"); + if (temp_dir == nullptr || temp_dir[0] == '\0') { + return "\\temp\\"; + } else if (temp_dir[strlen(temp_dir) - 1] == '\\') { + return temp_dir; + } else { + return std::string(temp_dir) + "\\"; + } +#elif GTEST_OS_LINUX_ANDROID + const char* temp_dir = internal::posix::GetEnv("TEST_TMPDIR"); + if (temp_dir == nullptr || temp_dir[0] == '\0') { + return "/data/local/tmp/"; + } else { + return temp_dir; + } +#elif GTEST_OS_LINUX + const char* temp_dir = internal::posix::GetEnv("TEST_TMPDIR"); + if (temp_dir == nullptr || temp_dir[0] == '\0') { + return "/tmp/"; + } else { + return temp_dir; + } +#else + return "/tmp/"; +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Class ScopedTrace + +// Pushes the given source file location and message onto a per-thread +// trace stack maintained by Google Test. +void ScopedTrace::PushTrace(const char* file, int line, std::string message) { + internal::TraceInfo trace; + trace.file = file; + trace.line = line; + trace.message.swap(message); + + UnitTest::GetInstance()->PushGTestTrace(trace); +} + +// Pops the info pushed by the c'tor. +ScopedTrace::~ScopedTrace() + GTEST_LOCK_EXCLUDED_(&UnitTest::mutex_) { + UnitTest::GetInstance()->PopGTestTrace(); +} + +} // namespace testing +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// This file implements death tests. + + +#include <functional> +#include <utility> + + +#if GTEST_HAS_DEATH_TEST + +# if GTEST_OS_MAC +# include <crt_externs.h> +# endif // GTEST_OS_MAC + +# include <errno.h> +# include <fcntl.h> +# include <limits.h> + +# if GTEST_OS_LINUX +# include <signal.h> +# endif // GTEST_OS_LINUX + +# include <stdarg.h> + +# if GTEST_OS_WINDOWS +# include <windows.h> +# else +# include <sys/mman.h> +# include <sys/wait.h> +# endif // GTEST_OS_WINDOWS + +# if GTEST_OS_QNX +# include <spawn.h> +# endif // GTEST_OS_QNX + +# if GTEST_OS_FUCHSIA +# include <lib/fdio/fd.h> +# include <lib/fdio/io.h> +# include <lib/fdio/spawn.h> +# include <lib/zx/channel.h> +# include <lib/zx/port.h> +# include <lib/zx/process.h> +# include <lib/zx/socket.h> +# include <zircon/processargs.h> +# include <zircon/syscalls.h> +# include <zircon/syscalls/policy.h> +# include <zircon/syscalls/port.h> +# endif // GTEST_OS_FUCHSIA + +#endif // GTEST_HAS_DEATH_TEST + + +namespace testing { + +// Constants. + +// The default death test style. +// +// This is defined in internal/gtest-port.h as "fast", but can be overridden by +// a definition in internal/custom/gtest-port.h. The recommended value, which is +// used internally at Google, is "threadsafe". +static const char kDefaultDeathTestStyle[] = GTEST_DEFAULT_DEATH_TEST_STYLE; + +GTEST_DEFINE_string_( + death_test_style, + internal::StringFromGTestEnv("death_test_style", kDefaultDeathTestStyle), + "Indicates how to run a death test in a forked child process: " + "\"threadsafe\" (child process re-executes the test binary " + "from the beginning, running only the specific death test) or " + "\"fast\" (child process runs the death test immediately " + "after forking)."); + +GTEST_DEFINE_bool_( + death_test_use_fork, + internal::BoolFromGTestEnv("death_test_use_fork", false), + "Instructs to use fork()/_exit() instead of clone() in death tests. " + "Ignored and always uses fork() on POSIX systems where clone() is not " + "implemented. Useful when running under valgrind or similar tools if " + "those do not support clone(). Valgrind 3.3.1 will just fail if " + "it sees an unsupported combination of clone() flags. " + "It is not recommended to use this flag w/o valgrind though it will " + "work in 99% of the cases. Once valgrind is fixed, this flag will " + "most likely be removed."); + +namespace internal { +GTEST_DEFINE_string_( + internal_run_death_test, "", + "Indicates the file, line number, temporal index of " + "the single death test to run, and a file descriptor to " + "which a success code may be sent, all separated by " + "the '|' characters. This flag is specified if and only if the " + "current process is a sub-process launched for running a thread-safe " + "death test. FOR INTERNAL USE ONLY."); +} // namespace internal + +#if GTEST_HAS_DEATH_TEST + +namespace internal { + +// Valid only for fast death tests. Indicates the code is running in the +// child process of a fast style death test. +# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA +static bool g_in_fast_death_test_child = false; +# endif + +// Returns a Boolean value indicating whether the caller is currently +// executing in the context of the death test child process. Tools such as +// Valgrind heap checkers may need this to modify their behavior in death +// tests. IMPORTANT: This is an internal utility. Using it may break the +// implementation of death tests. User code MUST NOT use it. +bool InDeathTestChild() { +# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA + + // On Windows and Fuchsia, death tests are thread-safe regardless of the value + // of the death_test_style flag. + return !GTEST_FLAG(internal_run_death_test).empty(); + +# else + + if (GTEST_FLAG(death_test_style) == "threadsafe") + return !GTEST_FLAG(internal_run_death_test).empty(); + else + return g_in_fast_death_test_child; +#endif +} + +} // namespace internal + +// ExitedWithCode constructor. +ExitedWithCode::ExitedWithCode(int exit_code) : exit_code_(exit_code) { +} + +// ExitedWithCode function-call operator. +bool ExitedWithCode::operator()(int exit_status) const { +# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA + + return exit_status == exit_code_; + +# else + + return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == exit_code_; + +# endif // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA +} + +# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA +// KilledBySignal constructor. +KilledBySignal::KilledBySignal(int signum) : signum_(signum) { +} + +// KilledBySignal function-call operator. +bool KilledBySignal::operator()(int exit_status) const { +# if defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_) + { + bool result; + if (GTEST_KILLED_BY_SIGNAL_OVERRIDE_(signum_, exit_status, &result)) { + return result; + } + } +# endif // defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_) + return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_; +} +# endif // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA + +namespace internal { + +// Utilities needed for death tests. + +// Generates a textual description of a given exit code, in the format +// specified by wait(2). +static std::string ExitSummary(int exit_code) { + Message m; + +# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA + + m << "Exited with exit status " << exit_code; + +# else + + if (WIFEXITED(exit_code)) { + m << "Exited with exit status " << WEXITSTATUS(exit_code); + } else if (WIFSIGNALED(exit_code)) { + m << "Terminated by signal " << WTERMSIG(exit_code); + } +# ifdef WCOREDUMP + if (WCOREDUMP(exit_code)) { + m << " (core dumped)"; + } +# endif +# endif // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA + + return m.GetString(); +} + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +bool ExitedUnsuccessfully(int exit_status) { + return !ExitedWithCode(0)(exit_status); +} + +# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA +// Generates a textual failure message when a death test finds more than +// one thread running, or cannot determine the number of threads, prior +// to executing the given statement. It is the responsibility of the +// caller not to pass a thread_count of 1. +static std::string DeathTestThreadWarning(size_t thread_count) { + Message msg; + msg << "Death tests use fork(), which is unsafe particularly" + << " in a threaded context. For this test, " << GTEST_NAME_ << " "; + if (thread_count == 0) { + msg << "couldn't detect the number of threads."; + } else { + msg << "detected " << thread_count << " threads."; + } + msg << " See " + "https://github.com/google/googletest/blob/master/docs/" + "advanced.md#death-tests-and-threads" + << " for more explanation and suggested solutions, especially if" + << " this is the last message you see before your test times out."; + return msg.GetString(); +} +# endif // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA + +// Flag characters for reporting a death test that did not die. +static const char kDeathTestLived = 'L'; +static const char kDeathTestReturned = 'R'; +static const char kDeathTestThrew = 'T'; +static const char kDeathTestInternalError = 'I'; + +#if GTEST_OS_FUCHSIA + +// File descriptor used for the pipe in the child process. +static const int kFuchsiaReadPipeFd = 3; + +#endif + +// An enumeration describing all of the possible ways that a death test can +// conclude. DIED means that the process died while executing the test +// code; LIVED means that process lived beyond the end of the test code; +// RETURNED means that the test statement attempted to execute a return +// statement, which is not allowed; THREW means that the test statement +// returned control by throwing an exception. IN_PROGRESS means the test +// has not yet concluded. +enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED, THREW }; + +// Routine for aborting the program which is safe to call from an +// exec-style death test child process, in which case the error +// message is propagated back to the parent process. Otherwise, the +// message is simply printed to stderr. In either case, the program +// then exits with status 1. +static void DeathTestAbort(const std::string& message) { + // On a POSIX system, this function may be called from a threadsafe-style + // death test child process, which operates on a very small stack. Use + // the heap for any additional non-minuscule memory requirements. + const InternalRunDeathTestFlag* const flag = + GetUnitTestImpl()->internal_run_death_test_flag(); + if (flag != nullptr) { + FILE* parent = posix::FDOpen(flag->write_fd(), "w"); + fputc(kDeathTestInternalError, parent); + fprintf(parent, "%s", message.c_str()); + fflush(parent); + _exit(1); + } else { + fprintf(stderr, "%s", message.c_str()); + fflush(stderr); + posix::Abort(); + } +} + +// A replacement for CHECK that calls DeathTestAbort if the assertion +// fails. +# define GTEST_DEATH_TEST_CHECK_(expression) \ + do { \ + if (!::testing::internal::IsTrue(expression)) { \ + DeathTestAbort( \ + ::std::string("CHECK failed: File ") + __FILE__ + ", line " \ + + ::testing::internal::StreamableToString(__LINE__) + ": " \ + + #expression); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// This macro is similar to GTEST_DEATH_TEST_CHECK_, but it is meant for +// evaluating any system call that fulfills two conditions: it must return +// -1 on failure, and set errno to EINTR when it is interrupted and +// should be tried again. The macro expands to a loop that repeatedly +// evaluates the expression as long as it evaluates to -1 and sets +// errno to EINTR. If the expression evaluates to -1 but errno is +// something other than EINTR, DeathTestAbort is called. +# define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \ + do { \ + int gtest_retval; \ + do { \ + gtest_retval = (expression); \ + } while (gtest_retval == -1 && errno == EINTR); \ + if (gtest_retval == -1) { \ + DeathTestAbort( \ + ::std::string("CHECK failed: File ") + __FILE__ + ", line " \ + + ::testing::internal::StreamableToString(__LINE__) + ": " \ + + #expression + " != -1"); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// Returns the message describing the last system error in errno. +std::string GetLastErrnoDescription() { + return errno == 0 ? "" : posix::StrError(errno); +} + +// This is called from a death test parent process to read a failure +// message from the death test child process and log it with the FATAL +// severity. On Windows, the message is read from a pipe handle. On other +// platforms, it is read from a file descriptor. +static void FailFromInternalError(int fd) { + Message error; + char buffer[256]; + int num_read; + + do { + while ((num_read = posix::Read(fd, buffer, 255)) > 0) { + buffer[num_read] = '\0'; + error << buffer; + } + } while (num_read == -1 && errno == EINTR); + + if (num_read == 0) { + GTEST_LOG_(FATAL) << error.GetString(); + } else { + const int last_error = errno; + GTEST_LOG_(FATAL) << "Error while reading death test internal: " + << GetLastErrnoDescription() << " [" << last_error << "]"; + } +} + +// Death test constructor. Increments the running death test count +// for the current test. +DeathTest::DeathTest() { + TestInfo* const info = GetUnitTestImpl()->current_test_info(); + if (info == nullptr) { + DeathTestAbort("Cannot run a death test outside of a TEST or " + "TEST_F construct"); + } +} + +// Creates and returns a death test by dispatching to the current +// death test factory. +bool DeathTest::Create(const char* statement, + Matcher<const std::string&> matcher, const char* file, + int line, DeathTest** test) { + return GetUnitTestImpl()->death_test_factory()->Create( + statement, std::move(matcher), file, line, test); +} + +const char* DeathTest::LastMessage() { + return last_death_test_message_.c_str(); +} + +void DeathTest::set_last_death_test_message(const std::string& message) { + last_death_test_message_ = message; +} + +std::string DeathTest::last_death_test_message_; + +// Provides cross platform implementation for some death functionality. +class DeathTestImpl : public DeathTest { + protected: + DeathTestImpl(const char* a_statement, Matcher<const std::string&> matcher) + : statement_(a_statement), + matcher_(std::move(matcher)), + spawned_(false), + status_(-1), + outcome_(IN_PROGRESS), + read_fd_(-1), + write_fd_(-1) {} + + // read_fd_ is expected to be closed and cleared by a derived class. + ~DeathTestImpl() override { GTEST_DEATH_TEST_CHECK_(read_fd_ == -1); } + + void Abort(AbortReason reason) override; + bool Passed(bool status_ok) override; + + const char* statement() const { return statement_; } + bool spawned() const { return spawned_; } + void set_spawned(bool is_spawned) { spawned_ = is_spawned; } + int status() const { return status_; } + void set_status(int a_status) { status_ = a_status; } + DeathTestOutcome outcome() const { return outcome_; } + void set_outcome(DeathTestOutcome an_outcome) { outcome_ = an_outcome; } + int read_fd() const { return read_fd_; } + void set_read_fd(int fd) { read_fd_ = fd; } + int write_fd() const { return write_fd_; } + void set_write_fd(int fd) { write_fd_ = fd; } + + // Called in the parent process only. Reads the result code of the death + // test child process via a pipe, interprets it to set the outcome_ + // member, and closes read_fd_. Outputs diagnostics and terminates in + // case of unexpected codes. + void ReadAndInterpretStatusByte(); + + // Returns stderr output from the child process. + virtual std::string GetErrorLogs(); + + private: + // The textual content of the code this object is testing. This class + // doesn't own this string and should not attempt to delete it. + const char* const statement_; + // A matcher that's expected to match the stderr output by the child process. + Matcher<const std::string&> matcher_; + // True if the death test child process has been successfully spawned. + bool spawned_; + // The exit status of the child process. + int status_; + // How the death test concluded. + DeathTestOutcome outcome_; + // Descriptor to the read end of the pipe to the child process. It is + // always -1 in the child process. The child keeps its write end of the + // pipe in write_fd_. + int read_fd_; + // Descriptor to the child's write end of the pipe to the parent process. + // It is always -1 in the parent process. The parent keeps its end of the + // pipe in read_fd_. + int write_fd_; +}; + +// Called in the parent process only. Reads the result code of the death +// test child process via a pipe, interprets it to set the outcome_ +// member, and closes read_fd_. Outputs diagnostics and terminates in +// case of unexpected codes. +void DeathTestImpl::ReadAndInterpretStatusByte() { + char flag; + int bytes_read; + + // The read() here blocks until data is available (signifying the + // failure of the death test) or until the pipe is closed (signifying + // its success), so it's okay to call this in the parent before + // the child process has exited. + do { + bytes_read = posix::Read(read_fd(), &flag, 1); + } while (bytes_read == -1 && errno == EINTR); + + if (bytes_read == 0) { + set_outcome(DIED); + } else if (bytes_read == 1) { + switch (flag) { + case kDeathTestReturned: + set_outcome(RETURNED); + break; + case kDeathTestThrew: + set_outcome(THREW); + break; + case kDeathTestLived: + set_outcome(LIVED); + break; + case kDeathTestInternalError: + FailFromInternalError(read_fd()); // Does not return. + break; + default: + GTEST_LOG_(FATAL) << "Death test child process reported " + << "unexpected status byte (" + << static_cast<unsigned int>(flag) << ")"; + } + } else { + GTEST_LOG_(FATAL) << "Read from death test child process failed: " + << GetLastErrnoDescription(); + } + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd())); + set_read_fd(-1); +} + +std::string DeathTestImpl::GetErrorLogs() { + return GetCapturedStderr(); +} + +// Signals that the death test code which should have exited, didn't. +// Should be called only in a death test child process. +// Writes a status byte to the child's status file descriptor, then +// calls _exit(1). +void DeathTestImpl::Abort(AbortReason reason) { + // The parent process considers the death test to be a failure if + // it finds any data in our pipe. So, here we write a single flag byte + // to the pipe, then exit. + const char status_ch = + reason == TEST_DID_NOT_DIE ? kDeathTestLived : + reason == TEST_THREW_EXCEPTION ? kDeathTestThrew : kDeathTestReturned; + + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1)); + // We are leaking the descriptor here because on some platforms (i.e., + // when built as Windows DLL), destructors of global objects will still + // run after calling _exit(). On such systems, write_fd_ will be + // indirectly closed from the destructor of UnitTestImpl, causing double + // close if it is also closed here. On debug configurations, double close + // may assert. As there are no in-process buffers to flush here, we are + // relying on the OS to close the descriptor after the process terminates + // when the destructors are not run. + _exit(1); // Exits w/o any normal exit hooks (we were supposed to crash) +} + +// Returns an indented copy of stderr output for a death test. +// This makes distinguishing death test output lines from regular log lines +// much easier. +static ::std::string FormatDeathTestOutput(const ::std::string& output) { + ::std::string ret; + for (size_t at = 0; ; ) { + const size_t line_end = output.find('\n', at); + ret += "[ DEATH ] "; + if (line_end == ::std::string::npos) { + ret += output.substr(at); + break; + } + ret += output.substr(at, line_end + 1 - at); + at = line_end + 1; + } + return ret; +} + +// Assesses the success or failure of a death test, using both private +// members which have previously been set, and one argument: +// +// Private data members: +// outcome: An enumeration describing how the death test +// concluded: DIED, LIVED, THREW, or RETURNED. The death test +// fails in the latter three cases. +// status: The exit status of the child process. On *nix, it is in the +// in the format specified by wait(2). On Windows, this is the +// value supplied to the ExitProcess() API or a numeric code +// of the exception that terminated the program. +// matcher_: A matcher that's expected to match the stderr output by the child +// process. +// +// Argument: +// status_ok: true if exit_status is acceptable in the context of +// this particular death test, which fails if it is false +// +// Returns true if and only if all of the above conditions are met. Otherwise, +// the first failing condition, in the order given above, is the one that is +// reported. Also sets the last death test message string. +bool DeathTestImpl::Passed(bool status_ok) { + if (!spawned()) + return false; + + const std::string error_message = GetErrorLogs(); + + bool success = false; + Message buffer; + + buffer << "Death test: " << statement() << "\n"; + switch (outcome()) { + case LIVED: + buffer << " Result: failed to die.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case THREW: + buffer << " Result: threw an exception.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case RETURNED: + buffer << " Result: illegal return in test statement.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case DIED: + if (status_ok) { + if (matcher_.Matches(error_message)) { + success = true; + } else { + std::ostringstream stream; + matcher_.DescribeTo(&stream); + buffer << " Result: died but not with expected error.\n" + << " Expected: " << stream.str() << "\n" + << "Actual msg:\n" + << FormatDeathTestOutput(error_message); + } + } else { + buffer << " Result: died but not with expected exit code:\n" + << " " << ExitSummary(status()) << "\n" + << "Actual msg:\n" << FormatDeathTestOutput(error_message); + } + break; + case IN_PROGRESS: + default: + GTEST_LOG_(FATAL) + << "DeathTest::Passed somehow called before conclusion of test"; + } + + DeathTest::set_last_death_test_message(buffer.GetString()); + return success; +} + +# if GTEST_OS_WINDOWS +// WindowsDeathTest implements death tests on Windows. Due to the +// specifics of starting new processes on Windows, death tests there are +// always threadsafe, and Google Test considers the +// --gtest_death_test_style=fast setting to be equivalent to +// --gtest_death_test_style=threadsafe there. +// +// A few implementation notes: Like the Linux version, the Windows +// implementation uses pipes for child-to-parent communication. But due to +// the specifics of pipes on Windows, some extra steps are required: +// +// 1. The parent creates a communication pipe and stores handles to both +// ends of it. +// 2. The parent starts the child and provides it with the information +// necessary to acquire the handle to the write end of the pipe. +// 3. The child acquires the write end of the pipe and signals the parent +// using a Windows event. +// 4. Now the parent can release the write end of the pipe on its side. If +// this is done before step 3, the object's reference count goes down to +// 0 and it is destroyed, preventing the child from acquiring it. The +// parent now has to release it, or read operations on the read end of +// the pipe will not return when the child terminates. +// 5. The parent reads child's output through the pipe (outcome code and +// any possible error messages) from the pipe, and its stderr and then +// determines whether to fail the test. +// +// Note: to distinguish Win32 API calls from the local method and function +// calls, the former are explicitly resolved in the global namespace. +// +class WindowsDeathTest : public DeathTestImpl { + public: + WindowsDeathTest(const char* a_statement, Matcher<const std::string&> matcher, + const char* file, int line) + : DeathTestImpl(a_statement, std::move(matcher)), + file_(file), + line_(line) {} + + // All of these virtual functions are inherited from DeathTest. + virtual int Wait(); + virtual TestRole AssumeRole(); + + private: + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; + // Handle to the write end of the pipe to the child process. + AutoHandle write_handle_; + // Child process handle. + AutoHandle child_handle_; + // Event the child process uses to signal the parent that it has + // acquired the handle to the write end of the pipe. After seeing this + // event the parent can release its own handles to make sure its + // ReadFile() calls return when the child terminates. + AutoHandle event_handle_; +}; + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int WindowsDeathTest::Wait() { + if (!spawned()) + return 0; + + // Wait until the child either signals that it has acquired the write end + // of the pipe or it dies. + const HANDLE wait_handles[2] = { child_handle_.Get(), event_handle_.Get() }; + switch (::WaitForMultipleObjects(2, + wait_handles, + FALSE, // Waits for any of the handles. + INFINITE)) { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + break; + default: + GTEST_DEATH_TEST_CHECK_(false); // Should not get here. + } + + // The child has acquired the write end of the pipe or exited. + // We release the handle on our side and continue. + write_handle_.Reset(); + event_handle_.Reset(); + + ReadAndInterpretStatusByte(); + + // Waits for the child process to exit if it haven't already. This + // returns immediately if the child has already exited, regardless of + // whether previous calls to WaitForMultipleObjects synchronized on this + // handle or not. + GTEST_DEATH_TEST_CHECK_( + WAIT_OBJECT_0 == ::WaitForSingleObject(child_handle_.Get(), + INFINITE)); + DWORD status_code; + GTEST_DEATH_TEST_CHECK_( + ::GetExitCodeProcess(child_handle_.Get(), &status_code) != FALSE); + child_handle_.Reset(); + set_status(static_cast<int>(status_code)); + return status(); +} + +// The AssumeRole process for a Windows death test. It creates a child +// process with the same executable as the current process to run the +// death test. The child process is given the --gtest_filter and +// --gtest_internal_run_death_test flags such that it knows to run the +// current death test only. +DeathTest::TestRole WindowsDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != nullptr) { + // ParseInternalRunDeathTestFlag() has performed all the necessary + // processing. + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + // WindowsDeathTest uses an anonymous pipe to communicate results of + // a death test. + SECURITY_ATTRIBUTES handles_are_inheritable = {sizeof(SECURITY_ATTRIBUTES), + nullptr, TRUE}; + HANDLE read_handle, write_handle; + GTEST_DEATH_TEST_CHECK_( + ::CreatePipe(&read_handle, &write_handle, &handles_are_inheritable, + 0) // Default buffer size. + != FALSE); + set_read_fd(::_open_osfhandle(reinterpret_cast<intptr_t>(read_handle), + O_RDONLY)); + write_handle_.Reset(write_handle); + event_handle_.Reset(::CreateEvent( + &handles_are_inheritable, + TRUE, // The event will automatically reset to non-signaled state. + FALSE, // The initial state is non-signalled. + nullptr)); // The even is unnamed. + GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != nullptr); + const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ + + kFilterFlag + "=" + info->test_suite_name() + + "." + info->name(); + const std::string internal_flag = + std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + + "=" + file_ + "|" + StreamableToString(line_) + "|" + + StreamableToString(death_test_index) + "|" + + StreamableToString(static_cast<unsigned int>(::GetCurrentProcessId())) + + // size_t has the same width as pointers on both 32-bit and 64-bit + // Windows platforms. + // See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx. + "|" + StreamableToString(reinterpret_cast<size_t>(write_handle)) + + "|" + StreamableToString(reinterpret_cast<size_t>(event_handle_.Get())); + + char executable_path[_MAX_PATH + 1]; // NOLINT + GTEST_DEATH_TEST_CHECK_(_MAX_PATH + 1 != ::GetModuleFileNameA(nullptr, + executable_path, + _MAX_PATH)); + + std::string command_line = + std::string(::GetCommandLineA()) + " " + filter_flag + " \"" + + internal_flag + "\""; + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // Flush the log buffers since the log streams are shared with the child. + FlushInfoLog(); + + // The child process will share the standard handles with the parent. + STARTUPINFOA startup_info; + memset(&startup_info, 0, sizeof(STARTUPINFO)); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); + + PROCESS_INFORMATION process_info; + GTEST_DEATH_TEST_CHECK_( + ::CreateProcessA( + executable_path, const_cast<char*>(command_line.c_str()), + nullptr, // Retuned process handle is not inheritable. + nullptr, // Retuned thread handle is not inheritable. + TRUE, // Child inherits all inheritable handles (for write_handle_). + 0x0, // Default creation flags. + nullptr, // Inherit the parent's environment. + UnitTest::GetInstance()->original_working_dir(), &startup_info, + &process_info) != FALSE); + child_handle_.Reset(process_info.hProcess); + ::CloseHandle(process_info.hThread); + set_spawned(true); + return OVERSEE_TEST; +} + +# elif GTEST_OS_FUCHSIA + +class FuchsiaDeathTest : public DeathTestImpl { + public: + FuchsiaDeathTest(const char* a_statement, Matcher<const std::string&> matcher, + const char* file, int line) + : DeathTestImpl(a_statement, std::move(matcher)), + file_(file), + line_(line) {} + + // All of these virtual functions are inherited from DeathTest. + int Wait() override; + TestRole AssumeRole() override; + std::string GetErrorLogs() override; + + private: + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; + // The stderr data captured by the child process. + std::string captured_stderr_; + + zx::process child_process_; + zx::channel exception_channel_; + zx::socket stderr_socket_; +}; + +// Utility class for accumulating command-line arguments. +class Arguments { + public: + Arguments() { args_.push_back(nullptr); } + + ~Arguments() { + for (std::vector<char*>::iterator i = args_.begin(); i != args_.end(); + ++i) { + free(*i); + } + } + void AddArgument(const char* argument) { + args_.insert(args_.end() - 1, posix::StrDup(argument)); + } + + template <typename Str> + void AddArguments(const ::std::vector<Str>& arguments) { + for (typename ::std::vector<Str>::const_iterator i = arguments.begin(); + i != arguments.end(); + ++i) { + args_.insert(args_.end() - 1, posix::StrDup(i->c_str())); + } + } + char* const* Argv() { + return &args_[0]; + } + + int size() { + return static_cast<int>(args_.size()) - 1; + } + + private: + std::vector<char*> args_; +}; + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int FuchsiaDeathTest::Wait() { + const int kProcessKey = 0; + const int kSocketKey = 1; + const int kExceptionKey = 2; + + if (!spawned()) + return 0; + + // Create a port to wait for socket/task/exception events. + zx_status_t status_zx; + zx::port port; + status_zx = zx::port::create(0, &port); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + // Register to wait for the child process to terminate. + status_zx = child_process_.wait_async( + port, kProcessKey, ZX_PROCESS_TERMINATED, 0); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + // Register to wait for the socket to be readable or closed. + status_zx = stderr_socket_.wait_async( + port, kSocketKey, ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, 0); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + // Register to wait for an exception. + status_zx = exception_channel_.wait_async( + port, kExceptionKey, ZX_CHANNEL_READABLE, 0); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + bool process_terminated = false; + bool socket_closed = false; + do { + zx_port_packet_t packet = {}; + status_zx = port.wait(zx::time::infinite(), &packet); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + if (packet.key == kExceptionKey) { + // Process encountered an exception. Kill it directly rather than + // letting other handlers process the event. We will get a kProcessKey + // event when the process actually terminates. + status_zx = child_process_.kill(); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + } else if (packet.key == kProcessKey) { + // Process terminated. + GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type)); + GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_PROCESS_TERMINATED); + process_terminated = true; + } else if (packet.key == kSocketKey) { + GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type)); + if (packet.signal.observed & ZX_SOCKET_READABLE) { + // Read data from the socket. + constexpr size_t kBufferSize = 1024; + do { + size_t old_length = captured_stderr_.length(); + size_t bytes_read = 0; + captured_stderr_.resize(old_length + kBufferSize); + status_zx = stderr_socket_.read( + 0, &captured_stderr_.front() + old_length, kBufferSize, + &bytes_read); + captured_stderr_.resize(old_length + bytes_read); + } while (status_zx == ZX_OK); + if (status_zx == ZX_ERR_PEER_CLOSED) { + socket_closed = true; + } else { + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_ERR_SHOULD_WAIT); + status_zx = stderr_socket_.wait_async( + port, kSocketKey, ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, 0); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + } + } else { + GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_SOCKET_PEER_CLOSED); + socket_closed = true; + } + } + } while (!process_terminated && !socket_closed); + + ReadAndInterpretStatusByte(); + + zx_info_process_v2_t buffer; + status_zx = child_process_.get_info( + ZX_INFO_PROCESS_V2, &buffer, sizeof(buffer), nullptr, nullptr); + GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); + + GTEST_DEATH_TEST_CHECK_(buffer.flags & ZX_INFO_PROCESS_FLAG_EXITED); + set_status(static_cast<int>(buffer.return_code)); + return status(); +} + +// The AssumeRole process for a Fuchsia death test. It creates a child +// process with the same executable as the current process to run the +// death test. The child process is given the --gtest_filter and +// --gtest_internal_run_death_test flags such that it knows to run the +// current death test only. +DeathTest::TestRole FuchsiaDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != nullptr) { + // ParseInternalRunDeathTestFlag() has performed all the necessary + // processing. + set_write_fd(kFuchsiaReadPipeFd); + return EXECUTE_TEST; + } + + // Flush the log buffers since the log streams are shared with the child. + FlushInfoLog(); + + // Build the child process command line. + const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ + + kFilterFlag + "=" + info->test_suite_name() + + "." + info->name(); + const std::string internal_flag = + std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "=" + + file_ + "|" + + StreamableToString(line_) + "|" + + StreamableToString(death_test_index); + Arguments args; + args.AddArguments(GetInjectableArgvs()); + args.AddArgument(filter_flag.c_str()); + args.AddArgument(internal_flag.c_str()); + + // Build the pipe for communication with the child. + zx_status_t status; + zx_handle_t child_pipe_handle; + int child_pipe_fd; + status = fdio_pipe_half(&child_pipe_fd, &child_pipe_handle); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + set_read_fd(child_pipe_fd); + + // Set the pipe handle for the child. + fdio_spawn_action_t spawn_actions[2] = {}; + fdio_spawn_action_t* add_handle_action = &spawn_actions[0]; + add_handle_action->action = FDIO_SPAWN_ACTION_ADD_HANDLE; + add_handle_action->h.id = PA_HND(PA_FD, kFuchsiaReadPipeFd); + add_handle_action->h.handle = child_pipe_handle; + + // Create a socket pair will be used to receive the child process' stderr. + zx::socket stderr_producer_socket; + status = + zx::socket::create(0, &stderr_producer_socket, &stderr_socket_); + GTEST_DEATH_TEST_CHECK_(status >= 0); + int stderr_producer_fd = -1; + status = + fdio_fd_create(stderr_producer_socket.release(), &stderr_producer_fd); + GTEST_DEATH_TEST_CHECK_(status >= 0); + + // Make the stderr socket nonblocking. + GTEST_DEATH_TEST_CHECK_(fcntl(stderr_producer_fd, F_SETFL, 0) == 0); + + fdio_spawn_action_t* add_stderr_action = &spawn_actions[1]; + add_stderr_action->action = FDIO_SPAWN_ACTION_CLONE_FD; + add_stderr_action->fd.local_fd = stderr_producer_fd; + add_stderr_action->fd.target_fd = STDERR_FILENO; + + // Create a child job. + zx_handle_t child_job = ZX_HANDLE_INVALID; + status = zx_job_create(zx_job_default(), 0, & child_job); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + zx_policy_basic_t policy; + policy.condition = ZX_POL_NEW_ANY; + policy.policy = ZX_POL_ACTION_ALLOW; + status = zx_job_set_policy( + child_job, ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC, &policy, 1); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + + // Create an exception channel attached to the |child_job|, to allow + // us to suppress the system default exception handler from firing. + status = + zx_task_create_exception_channel( + child_job, 0, exception_channel_.reset_and_get_address()); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + + // Spawn the child process. + status = fdio_spawn_etc( + child_job, FDIO_SPAWN_CLONE_ALL, args.Argv()[0], args.Argv(), nullptr, + 2, spawn_actions, child_process_.reset_and_get_address(), nullptr); + GTEST_DEATH_TEST_CHECK_(status == ZX_OK); + + set_spawned(true); + return OVERSEE_TEST; +} + +std::string FuchsiaDeathTest::GetErrorLogs() { + return captured_stderr_; +} + +#else // We are neither on Windows, nor on Fuchsia. + +// ForkingDeathTest provides implementations for most of the abstract +// methods of the DeathTest interface. Only the AssumeRole method is +// left undefined. +class ForkingDeathTest : public DeathTestImpl { + public: + ForkingDeathTest(const char* statement, Matcher<const std::string&> matcher); + + // All of these virtual functions are inherited from DeathTest. + int Wait() override; + + protected: + void set_child_pid(pid_t child_pid) { child_pid_ = child_pid; } + + private: + // PID of child process during death test; 0 in the child process itself. + pid_t child_pid_; +}; + +// Constructs a ForkingDeathTest. +ForkingDeathTest::ForkingDeathTest(const char* a_statement, + Matcher<const std::string&> matcher) + : DeathTestImpl(a_statement, std::move(matcher)), child_pid_(-1) {} + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int ForkingDeathTest::Wait() { + if (!spawned()) + return 0; + + ReadAndInterpretStatusByte(); + + int status_value; + GTEST_DEATH_TEST_CHECK_SYSCALL_(waitpid(child_pid_, &status_value, 0)); + set_status(status_value); + return status_value; +} + +// A concrete death test class that forks, then immediately runs the test +// in the child process. +class NoExecDeathTest : public ForkingDeathTest { + public: + NoExecDeathTest(const char* a_statement, Matcher<const std::string&> matcher) + : ForkingDeathTest(a_statement, std::move(matcher)) {} + TestRole AssumeRole() override; +}; + +// The AssumeRole process for a fork-and-run death test. It implements a +// straightforward fork, with a simple pipe to transmit the status byte. +DeathTest::TestRole NoExecDeathTest::AssumeRole() { + const size_t thread_count = GetThreadCount(); + if (thread_count != 1) { + GTEST_LOG_(WARNING) << DeathTestThreadWarning(thread_count); + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + + DeathTest::set_last_death_test_message(""); + CaptureStderr(); + // When we fork the process below, the log file buffers are copied, but the + // file descriptors are shared. We flush all log files here so that closing + // the file descriptors in the child process doesn't throw off the + // synchronization between descriptors and buffers in the parent process. + // This is as close to the fork as possible to avoid a race condition in case + // there are multiple threads running before the death test, and another + // thread writes to the log file. + FlushInfoLog(); + + const pid_t child_pid = fork(); + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + set_child_pid(child_pid); + if (child_pid == 0) { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[0])); + set_write_fd(pipe_fd[1]); + // Redirects all logging to stderr in the child process to prevent + // concurrent writes to the log files. We capture stderr in the parent + // process and append the child process' output to a log. + LogToStderr(); + // Event forwarding to the listeners of event listener API mush be shut + // down in death test subprocesses. + GetUnitTestImpl()->listeners()->SuppressEventForwarding(); + g_in_fast_death_test_child = true; + return EXECUTE_TEST; + } else { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; + } +} + +// A concrete death test class that forks and re-executes the main +// program from the beginning, with command-line flags set that cause +// only this specific death test to be run. +class ExecDeathTest : public ForkingDeathTest { + public: + ExecDeathTest(const char* a_statement, Matcher<const std::string&> matcher, + const char* file, int line) + : ForkingDeathTest(a_statement, std::move(matcher)), + file_(file), + line_(line) {} + TestRole AssumeRole() override; + + private: + static ::std::vector<std::string> GetArgvsForDeathTestChildProcess() { + ::std::vector<std::string> args = GetInjectableArgvs(); +# if defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_) + ::std::vector<std::string> extra_args = + GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_(); + args.insert(args.end(), extra_args.begin(), extra_args.end()); +# endif // defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_) + return args; + } + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; +}; + +// Utility class for accumulating command-line arguments. +class Arguments { + public: + Arguments() { args_.push_back(nullptr); } + + ~Arguments() { + for (std::vector<char*>::iterator i = args_.begin(); i != args_.end(); + ++i) { + free(*i); + } + } + void AddArgument(const char* argument) { + args_.insert(args_.end() - 1, posix::StrDup(argument)); + } + + template <typename Str> + void AddArguments(const ::std::vector<Str>& arguments) { + for (typename ::std::vector<Str>::const_iterator i = arguments.begin(); + i != arguments.end(); + ++i) { + args_.insert(args_.end() - 1, posix::StrDup(i->c_str())); + } + } + char* const* Argv() { + return &args_[0]; + } + + private: + std::vector<char*> args_; +}; + +// A struct that encompasses the arguments to the child process of a +// threadsafe-style death test process. +struct ExecDeathTestArgs { + char* const* argv; // Command-line arguments for the child's call to exec + int close_fd; // File descriptor to close; the read end of a pipe +}; + +# if GTEST_OS_QNX +extern "C" char** environ; +# else // GTEST_OS_QNX +// The main function for a threadsafe-style death test child process. +// This function is called in a clone()-ed process and thus must avoid +// any potentially unsafe operations like malloc or libc functions. +static int ExecDeathTestChildMain(void* child_arg) { + ExecDeathTestArgs* const args = static_cast<ExecDeathTestArgs*>(child_arg); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(args->close_fd)); + + // We need to execute the test program in the same environment where + // it was originally invoked. Therefore we change to the original + // working directory first. + const char* const original_dir = + UnitTest::GetInstance()->original_working_dir(); + // We can safely call chdir() as it's a direct system call. + if (chdir(original_dir) != 0) { + DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " + + GetLastErrnoDescription()); + return EXIT_FAILURE; + } + + // We can safely call execv() as it's almost a direct system call. We + // cannot use execvp() as it's a libc function and thus potentially + // unsafe. Since execv() doesn't search the PATH, the user must + // invoke the test program via a valid path that contains at least + // one path separator. + execv(args->argv[0], args->argv); + DeathTestAbort(std::string("execv(") + args->argv[0] + ", ...) in " + + original_dir + " failed: " + + GetLastErrnoDescription()); + return EXIT_FAILURE; +} +# endif // GTEST_OS_QNX + +# if GTEST_HAS_CLONE +// Two utility routines that together determine the direction the stack +// grows. +// This could be accomplished more elegantly by a single recursive +// function, but we want to guard against the unlikely possibility of +// a smart compiler optimizing the recursion away. +// +// GTEST_NO_INLINE_ is required to prevent GCC 4.6 from inlining +// StackLowerThanAddress into StackGrowsDown, which then doesn't give +// correct answer. +static void StackLowerThanAddress(const void* ptr, + bool* result) GTEST_NO_INLINE_; +// Make sure sanitizers do not tamper with the stack here. +// Ideally, we want to use `__builtin_frame_address` instead of a local variable +// address with sanitizer disabled, but it does not work when the +// compiler optimizes the stack frame out, which happens on PowerPC targets. +// HWAddressSanitizer add a random tag to the MSB of the local variable address, +// making comparison result unpredictable. +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +static void StackLowerThanAddress(const void* ptr, bool* result) { + int dummy = 0; + *result = std::less<const void*>()(&dummy, ptr); +} + +// Make sure AddressSanitizer does not tamper with the stack here. +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +static bool StackGrowsDown() { + int dummy = 0; + bool result; + StackLowerThanAddress(&dummy, &result); + return result; +} +# endif // GTEST_HAS_CLONE + +// Spawns a child process with the same executable as the current process in +// a thread-safe manner and instructs it to run the death test. The +// implementation uses fork(2) + exec. On systems where clone(2) is +// available, it is used instead, being slightly more thread-safe. On QNX, +// fork supports only single-threaded environments, so this function uses +// spawn(2) there instead. The function dies with an error message if +// anything goes wrong. +static pid_t ExecDeathTestSpawnChild(char* const* argv, int close_fd) { + ExecDeathTestArgs args = { argv, close_fd }; + pid_t child_pid = -1; + +# if GTEST_OS_QNX + // Obtains the current directory and sets it to be closed in the child + // process. + const int cwd_fd = open(".", O_RDONLY); + GTEST_DEATH_TEST_CHECK_(cwd_fd != -1); + GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(cwd_fd, F_SETFD, FD_CLOEXEC)); + // We need to execute the test program in the same environment where + // it was originally invoked. Therefore we change to the original + // working directory first. + const char* const original_dir = + UnitTest::GetInstance()->original_working_dir(); + // We can safely call chdir() as it's a direct system call. + if (chdir(original_dir) != 0) { + DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " + + GetLastErrnoDescription()); + return EXIT_FAILURE; + } + + int fd_flags; + // Set close_fd to be closed after spawn. + GTEST_DEATH_TEST_CHECK_SYSCALL_(fd_flags = fcntl(close_fd, F_GETFD)); + GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(close_fd, F_SETFD, + fd_flags | FD_CLOEXEC)); + struct inheritance inherit = {0}; + // spawn is a system call. + child_pid = spawn(args.argv[0], 0, nullptr, &inherit, args.argv, environ); + // Restores the current working directory. + GTEST_DEATH_TEST_CHECK_(fchdir(cwd_fd) != -1); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(cwd_fd)); + +# else // GTEST_OS_QNX +# if GTEST_OS_LINUX + // When a SIGPROF signal is received while fork() or clone() are executing, + // the process may hang. To avoid this, we ignore SIGPROF here and re-enable + // it after the call to fork()/clone() is complete. + struct sigaction saved_sigprof_action; + struct sigaction ignore_sigprof_action; + memset(&ignore_sigprof_action, 0, sizeof(ignore_sigprof_action)); + sigemptyset(&ignore_sigprof_action.sa_mask); + ignore_sigprof_action.sa_handler = SIG_IGN; + GTEST_DEATH_TEST_CHECK_SYSCALL_(sigaction( + SIGPROF, &ignore_sigprof_action, &saved_sigprof_action)); +# endif // GTEST_OS_LINUX + +# if GTEST_HAS_CLONE + const bool use_fork = GTEST_FLAG(death_test_use_fork); + + if (!use_fork) { + static const bool stack_grows_down = StackGrowsDown(); + const auto stack_size = static_cast<size_t>(getpagesize() * 2); + // MMAP_ANONYMOUS is not defined on Mac, so we use MAP_ANON instead. + void* const stack = mmap(nullptr, stack_size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + GTEST_DEATH_TEST_CHECK_(stack != MAP_FAILED); + + // Maximum stack alignment in bytes: For a downward-growing stack, this + // amount is subtracted from size of the stack space to get an address + // that is within the stack space and is aligned on all systems we care + // about. As far as I know there is no ABI with stack alignment greater + // than 64. We assume stack and stack_size already have alignment of + // kMaxStackAlignment. + const size_t kMaxStackAlignment = 64; + void* const stack_top = + static_cast<char*>(stack) + + (stack_grows_down ? stack_size - kMaxStackAlignment : 0); + GTEST_DEATH_TEST_CHECK_( + static_cast<size_t>(stack_size) > kMaxStackAlignment && + reinterpret_cast<uintptr_t>(stack_top) % kMaxStackAlignment == 0); + + child_pid = clone(&ExecDeathTestChildMain, stack_top, SIGCHLD, &args); + + GTEST_DEATH_TEST_CHECK_(munmap(stack, stack_size) != -1); + } +# else + const bool use_fork = true; +# endif // GTEST_HAS_CLONE + + if (use_fork && (child_pid = fork()) == 0) { + ExecDeathTestChildMain(&args); + _exit(0); + } +# endif // GTEST_OS_QNX +# if GTEST_OS_LINUX + GTEST_DEATH_TEST_CHECK_SYSCALL_( + sigaction(SIGPROF, &saved_sigprof_action, nullptr)); +# endif // GTEST_OS_LINUX + + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + return child_pid; +} + +// The AssumeRole process for a fork-and-exec death test. It re-executes the +// main program from the beginning, setting the --gtest_filter +// and --gtest_internal_run_death_test flags to cause only the current +// death test to be re-run. +DeathTest::TestRole ExecDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != nullptr) { + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + // Clear the close-on-exec flag on the write end of the pipe, lest + // it be closed when the child process does an exec: + GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1); + + const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ + + kFilterFlag + "=" + info->test_suite_name() + + "." + info->name(); + const std::string internal_flag = + std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "=" + + file_ + "|" + StreamableToString(line_) + "|" + + StreamableToString(death_test_index) + "|" + + StreamableToString(pipe_fd[1]); + Arguments args; + args.AddArguments(GetArgvsForDeathTestChildProcess()); + args.AddArgument(filter_flag.c_str()); + args.AddArgument(internal_flag.c_str()); + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // See the comment in NoExecDeathTest::AssumeRole for why the next line + // is necessary. + FlushInfoLog(); + + const pid_t child_pid = ExecDeathTestSpawnChild(args.Argv(), pipe_fd[0]); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_child_pid(child_pid); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; +} + +# endif // !GTEST_OS_WINDOWS + +// Creates a concrete DeathTest-derived class that depends on the +// --gtest_death_test_style flag, and sets the pointer pointed to +// by the "test" argument to its address. If the test should be +// skipped, sets that pointer to NULL. Returns true, unless the +// flag is set to an invalid value. +bool DefaultDeathTestFactory::Create(const char* statement, + Matcher<const std::string&> matcher, + const char* file, int line, + DeathTest** test) { + UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const int death_test_index = impl->current_test_info() + ->increment_death_test_count(); + + if (flag != nullptr) { + if (death_test_index > flag->index()) { + DeathTest::set_last_death_test_message( + "Death test count (" + StreamableToString(death_test_index) + + ") somehow exceeded expected maximum (" + + StreamableToString(flag->index()) + ")"); + return false; + } + + if (!(flag->file() == file && flag->line() == line && + flag->index() == death_test_index)) { + *test = nullptr; + return true; + } + } + +# if GTEST_OS_WINDOWS + + if (GTEST_FLAG(death_test_style) == "threadsafe" || + GTEST_FLAG(death_test_style) == "fast") { + *test = new WindowsDeathTest(statement, std::move(matcher), file, line); + } + +# elif GTEST_OS_FUCHSIA + + if (GTEST_FLAG(death_test_style) == "threadsafe" || + GTEST_FLAG(death_test_style) == "fast") { + *test = new FuchsiaDeathTest(statement, std::move(matcher), file, line); + } + +# else + + if (GTEST_FLAG(death_test_style) == "threadsafe") { + *test = new ExecDeathTest(statement, std::move(matcher), file, line); + } else if (GTEST_FLAG(death_test_style) == "fast") { + *test = new NoExecDeathTest(statement, std::move(matcher)); + } + +# endif // GTEST_OS_WINDOWS + + else { // NOLINT - this is more readable than unbalanced brackets inside #if. + DeathTest::set_last_death_test_message( + "Unknown death test style \"" + GTEST_FLAG(death_test_style) + + "\" encountered"); + return false; + } + + return true; +} + +# if GTEST_OS_WINDOWS +// Recreates the pipe and event handles from the provided parameters, +// signals the event, and returns a file descriptor wrapped around the pipe +// handle. This function is called in the child process only. +static int GetStatusFileDescriptor(unsigned int parent_process_id, + size_t write_handle_as_size_t, + size_t event_handle_as_size_t) { + AutoHandle parent_process_handle(::OpenProcess(PROCESS_DUP_HANDLE, + FALSE, // Non-inheritable. + parent_process_id)); + if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) { + DeathTestAbort("Unable to open parent process " + + StreamableToString(parent_process_id)); + } + + GTEST_CHECK_(sizeof(HANDLE) <= sizeof(size_t)); + + const HANDLE write_handle = + reinterpret_cast<HANDLE>(write_handle_as_size_t); + HANDLE dup_write_handle; + + // The newly initialized handle is accessible only in the parent + // process. To obtain one accessible within the child, we need to use + // DuplicateHandle. + if (!::DuplicateHandle(parent_process_handle.Get(), write_handle, + ::GetCurrentProcess(), &dup_write_handle, + 0x0, // Requested privileges ignored since + // DUPLICATE_SAME_ACCESS is used. + FALSE, // Request non-inheritable handler. + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort("Unable to duplicate the pipe handle " + + StreamableToString(write_handle_as_size_t) + + " from the parent process " + + StreamableToString(parent_process_id)); + } + + const HANDLE event_handle = reinterpret_cast<HANDLE>(event_handle_as_size_t); + HANDLE dup_event_handle; + + if (!::DuplicateHandle(parent_process_handle.Get(), event_handle, + ::GetCurrentProcess(), &dup_event_handle, + 0x0, + FALSE, + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort("Unable to duplicate the event handle " + + StreamableToString(event_handle_as_size_t) + + " from the parent process " + + StreamableToString(parent_process_id)); + } + + const int write_fd = + ::_open_osfhandle(reinterpret_cast<intptr_t>(dup_write_handle), O_APPEND); + if (write_fd == -1) { + DeathTestAbort("Unable to convert pipe handle " + + StreamableToString(write_handle_as_size_t) + + " to a file descriptor"); + } + + // Signals the parent that the write end of the pipe has been acquired + // so the parent can release its own write end. + ::SetEvent(dup_event_handle); + + return write_fd; +} +# endif // GTEST_OS_WINDOWS + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { + if (GTEST_FLAG(internal_run_death_test) == "") return nullptr; + + // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we + // can use it here. + int line = -1; + int index = -1; + ::std::vector< ::std::string> fields; + SplitString(GTEST_FLAG(internal_run_death_test).c_str(), '|', &fields); + int write_fd = -1; + +# if GTEST_OS_WINDOWS + + unsigned int parent_process_id = 0; + size_t write_handle_as_size_t = 0; + size_t event_handle_as_size_t = 0; + + if (fields.size() != 6 + || !ParseNaturalNumber(fields[1], &line) + || !ParseNaturalNumber(fields[2], &index) + || !ParseNaturalNumber(fields[3], &parent_process_id) + || !ParseNaturalNumber(fields[4], &write_handle_as_size_t) + || !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) { + DeathTestAbort("Bad --gtest_internal_run_death_test flag: " + + GTEST_FLAG(internal_run_death_test)); + } + write_fd = GetStatusFileDescriptor(parent_process_id, + write_handle_as_size_t, + event_handle_as_size_t); + +# elif GTEST_OS_FUCHSIA + + if (fields.size() != 3 + || !ParseNaturalNumber(fields[1], &line) + || !ParseNaturalNumber(fields[2], &index)) { + DeathTestAbort("Bad --gtest_internal_run_death_test flag: " + + GTEST_FLAG(internal_run_death_test)); + } + +# else + + if (fields.size() != 4 + || !ParseNaturalNumber(fields[1], &line) + || !ParseNaturalNumber(fields[2], &index) + || !ParseNaturalNumber(fields[3], &write_fd)) { + DeathTestAbort("Bad --gtest_internal_run_death_test flag: " + + GTEST_FLAG(internal_run_death_test)); + } + +# endif // GTEST_OS_WINDOWS + + return new InternalRunDeathTestFlag(fields[0], line, index, write_fd); +} + +} // namespace internal + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace testing +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#include <stdlib.h> + +#if GTEST_OS_WINDOWS_MOBILE +# include <windows.h> +#elif GTEST_OS_WINDOWS +# include <direct.h> +# include <io.h> +#else +# include <limits.h> +# include <climits> // Some Linux distributions define PATH_MAX here. +#endif // GTEST_OS_WINDOWS_MOBILE + + +#if GTEST_OS_WINDOWS +# define GTEST_PATH_MAX_ _MAX_PATH +#elif defined(PATH_MAX) +# define GTEST_PATH_MAX_ PATH_MAX +#elif defined(_XOPEN_PATH_MAX) +# define GTEST_PATH_MAX_ _XOPEN_PATH_MAX +#else +# define GTEST_PATH_MAX_ _POSIX_PATH_MAX +#endif // GTEST_OS_WINDOWS + +namespace testing { +namespace internal { + +#if GTEST_OS_WINDOWS +// On Windows, '\\' is the standard path separator, but many tools and the +// Windows API also accept '/' as an alternate path separator. Unless otherwise +// noted, a file path can contain either kind of path separators, or a mixture +// of them. +const char kPathSeparator = '\\'; +const char kAlternatePathSeparator = '/'; +const char kAlternatePathSeparatorString[] = "/"; +# if GTEST_OS_WINDOWS_MOBILE +// Windows CE doesn't have a current directory. You should not use +// the current directory in tests on Windows CE, but this at least +// provides a reasonable fallback. +const char kCurrentDirectoryString[] = "\\"; +// Windows CE doesn't define INVALID_FILE_ATTRIBUTES +const DWORD kInvalidFileAttributes = 0xffffffff; +# else +const char kCurrentDirectoryString[] = ".\\"; +# endif // GTEST_OS_WINDOWS_MOBILE +#else +const char kPathSeparator = '/'; +const char kCurrentDirectoryString[] = "./"; +#endif // GTEST_OS_WINDOWS + +// Returns whether the given character is a valid path separator. +static bool IsPathSeparator(char c) { +#if GTEST_HAS_ALT_PATH_SEP_ + return (c == kPathSeparator) || (c == kAlternatePathSeparator); +#else + return c == kPathSeparator; +#endif +} + +// Returns the current working directory, or "" if unsuccessful. +FilePath FilePath::GetCurrentDir() { +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || \ + GTEST_OS_WINDOWS_RT || GTEST_OS_ESP8266 || GTEST_OS_ESP32 || \ + GTEST_OS_XTENSA + // These platforms do not have a current directory, so we just return + // something reasonable. + return FilePath(kCurrentDirectoryString); +#elif GTEST_OS_WINDOWS + char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; + return FilePath(_getcwd(cwd, sizeof(cwd)) == nullptr ? "" : cwd); +#else + char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; + char* result = getcwd(cwd, sizeof(cwd)); +# if GTEST_OS_NACL + // getcwd will likely fail in NaCl due to the sandbox, so return something + // reasonable. The user may have provided a shim implementation for getcwd, + // however, so fallback only when failure is detected. + return FilePath(result == nullptr ? kCurrentDirectoryString : cwd); +# endif // GTEST_OS_NACL + return FilePath(result == nullptr ? "" : cwd); +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns a copy of the FilePath with the case-insensitive extension removed. +// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns +// FilePath("dir/file"). If a case-insensitive extension is not +// found, returns a copy of the original FilePath. +FilePath FilePath::RemoveExtension(const char* extension) const { + const std::string dot_extension = std::string(".") + extension; + if (String::EndsWithCaseInsensitive(pathname_, dot_extension)) { + return FilePath(pathname_.substr( + 0, pathname_.length() - dot_extension.length())); + } + return *this; +} + +// Returns a pointer to the last occurrence of a valid path separator in +// the FilePath. On Windows, for example, both '/' and '\' are valid path +// separators. Returns NULL if no path separator was found. +const char* FilePath::FindLastPathSeparator() const { + const char* const last_sep = strrchr(c_str(), kPathSeparator); +#if GTEST_HAS_ALT_PATH_SEP_ + const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator); + // Comparing two pointers of which only one is NULL is undefined. + if (last_alt_sep != nullptr && + (last_sep == nullptr || last_alt_sep > last_sep)) { + return last_alt_sep; + } +#endif + return last_sep; +} + +// Returns a copy of the FilePath with the directory part removed. +// Example: FilePath("path/to/file").RemoveDirectoryName() returns +// FilePath("file"). If there is no directory part ("just_a_file"), it returns +// the FilePath unmodified. If there is no file part ("just_a_dir/") it +// returns an empty FilePath (""). +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveDirectoryName() const { + const char* const last_sep = FindLastPathSeparator(); + return last_sep ? FilePath(last_sep + 1) : *this; +} + +// RemoveFileName returns the directory path with the filename removed. +// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". +// If the FilePath is "a_file" or "/a_file", RemoveFileName returns +// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does +// not have a file, like "just/a/dir/", it returns the FilePath unmodified. +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveFileName() const { + const char* const last_sep = FindLastPathSeparator(); + std::string dir; + if (last_sep) { + dir = std::string(c_str(), static_cast<size_t>(last_sep + 1 - c_str())); + } else { + dir = kCurrentDirectoryString; + } + return FilePath(dir); +} + +// Helper functions for naming files in a directory for xml output. + +// Given directory = "dir", base_name = "test", number = 0, +// extension = "xml", returns "dir/test.xml". If number is greater +// than zero (e.g., 12), returns "dir/test_12.xml". +// On Windows platform, uses \ as the separator rather than /. +FilePath FilePath::MakeFileName(const FilePath& directory, + const FilePath& base_name, + int number, + const char* extension) { + std::string file; + if (number == 0) { + file = base_name.string() + "." + extension; + } else { + file = base_name.string() + "_" + StreamableToString(number) + + "." + extension; + } + return ConcatPaths(directory, FilePath(file)); +} + +// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml". +// On Windows, uses \ as the separator rather than /. +FilePath FilePath::ConcatPaths(const FilePath& directory, + const FilePath& relative_path) { + if (directory.IsEmpty()) + return relative_path; + const FilePath dir(directory.RemoveTrailingPathSeparator()); + return FilePath(dir.string() + kPathSeparator + relative_path.string()); +} + +// Returns true if pathname describes something findable in the file-system, +// either a file, directory, or whatever. +bool FilePath::FileOrDirectoryExists() const { +#if GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete [] unicode; + return attributes != kInvalidFileAttributes; +#else + posix::StatStruct file_stat; + return posix::Stat(pathname_.c_str(), &file_stat) == 0; +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns true if pathname describes a directory in the file-system +// that exists. +bool FilePath::DirectoryExists() const { + bool result = false; +#if GTEST_OS_WINDOWS + // Don't strip off trailing separator if path is a root directory on + // Windows (like "C:\\"). + const FilePath& path(IsRootDirectory() ? *this : + RemoveTrailingPathSeparator()); +#else + const FilePath& path(*this); +#endif + +#if GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(path.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete [] unicode; + if ((attributes != kInvalidFileAttributes) && + (attributes & FILE_ATTRIBUTE_DIRECTORY)) { + result = true; + } +#else + posix::StatStruct file_stat; + result = posix::Stat(path.c_str(), &file_stat) == 0 && + posix::IsDir(file_stat); +#endif // GTEST_OS_WINDOWS_MOBILE + + return result; +} + +// Returns true if pathname describes a root directory. (Windows has one +// root directory per disk drive.) +bool FilePath::IsRootDirectory() const { +#if GTEST_OS_WINDOWS + return pathname_.length() == 3 && IsAbsolutePath(); +#else + return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]); +#endif +} + +// Returns true if pathname describes an absolute path. +bool FilePath::IsAbsolutePath() const { + const char* const name = pathname_.c_str(); +#if GTEST_OS_WINDOWS + return pathname_.length() >= 3 && + ((name[0] >= 'a' && name[0] <= 'z') || + (name[0] >= 'A' && name[0] <= 'Z')) && + name[1] == ':' && + IsPathSeparator(name[2]); +#else + return IsPathSeparator(name[0]); +#endif +} + +// Returns a pathname for a file that does not currently exist. The pathname +// will be directory/base_name.extension or +// directory/base_name_<number>.extension if directory/base_name.extension +// already exists. The number will be incremented until a pathname is found +// that does not already exist. +// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. +// There could be a race condition if two or more processes are calling this +// function at the same time -- they could both pick the same filename. +FilePath FilePath::GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension) { + FilePath full_pathname; + int number = 0; + do { + full_pathname.Set(MakeFileName(directory, base_name, number++, extension)); + } while (full_pathname.FileOrDirectoryExists()); + return full_pathname; +} + +// Returns true if FilePath ends with a path separator, which indicates that +// it is intended to represent a directory. Returns false otherwise. +// This does NOT check that a directory (or file) actually exists. +bool FilePath::IsDirectory() const { + return !pathname_.empty() && + IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]); +} + +// Create directories so that path exists. Returns true if successful or if +// the directories already exist; returns false if unable to create directories +// for any reason. +bool FilePath::CreateDirectoriesRecursively() const { + if (!this->IsDirectory()) { + return false; + } + + if (pathname_.length() == 0 || this->DirectoryExists()) { + return true; + } + + const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName()); + return parent.CreateDirectoriesRecursively() && this->CreateFolder(); +} + +// Create the directory so that path exists. Returns true if successful or +// if the directory already exists; returns false if unable to create the +// directory for any reason, including if the parent directory does not +// exist. Not named "CreateDirectory" because that's a macro on Windows. +bool FilePath::CreateFolder() const { +#if GTEST_OS_WINDOWS_MOBILE + FilePath removed_sep(this->RemoveTrailingPathSeparator()); + LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str()); + int result = CreateDirectory(unicode, nullptr) ? 0 : -1; + delete [] unicode; +#elif GTEST_OS_WINDOWS + int result = _mkdir(pathname_.c_str()); +#elif GTEST_OS_ESP8266 || GTEST_OS_XTENSA + // do nothing + int result = 0; +#else + int result = mkdir(pathname_.c_str(), 0777); +#endif // GTEST_OS_WINDOWS_MOBILE + + if (result == -1) { + return this->DirectoryExists(); // An error is OK if the directory exists. + } + return true; // No error. +} + +// If input name has a trailing separator character, remove it and return the +// name, otherwise return the name string unmodified. +// On Windows platform, uses \ as the separator, other platforms use /. +FilePath FilePath::RemoveTrailingPathSeparator() const { + return IsDirectory() + ? FilePath(pathname_.substr(0, pathname_.length() - 1)) + : *this; +} + +// Removes any redundant separators that might be in the pathname. +// For example, "bar///foo" becomes "bar/foo". Does not eliminate other +// redundancies that might be in a pathname involving "." or "..". +void FilePath::Normalize() { + auto out = pathname_.begin(); + + for (const char character : pathname_) { + if (!IsPathSeparator(character)) { + *(out++) = character; + } else if (out == pathname_.begin() || *std::prev(out) != kPathSeparator) { + *(out++) = kPathSeparator; + } else { + continue; + } + } + + pathname_.erase(out, pathname_.end()); +} + +} // namespace internal +} // namespace testing +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This file implements just enough of the matcher interface to allow +// EXPECT_DEATH and friends to accept a matcher argument. + + +#include <string> + +namespace testing { + +// Constructs a matcher that matches a const std::string& whose value is +// equal to s. +Matcher<const std::string&>::Matcher(const std::string& s) { *this = Eq(s); } + +// Constructs a matcher that matches a const std::string& whose value is +// equal to s. +Matcher<const std::string&>::Matcher(const char* s) { + *this = Eq(std::string(s)); +} + +// Constructs a matcher that matches a std::string whose value is equal to +// s. +Matcher<std::string>::Matcher(const std::string& s) { *this = Eq(s); } + +// Constructs a matcher that matches a std::string whose value is equal to +// s. +Matcher<std::string>::Matcher(const char* s) { *this = Eq(std::string(s)); } + +#if GTEST_INTERNAL_HAS_STRING_VIEW +// Constructs a matcher that matches a const StringView& whose value is +// equal to s. +Matcher<const internal::StringView&>::Matcher(const std::string& s) { + *this = Eq(s); +} + +// Constructs a matcher that matches a const StringView& whose value is +// equal to s. +Matcher<const internal::StringView&>::Matcher(const char* s) { + *this = Eq(std::string(s)); +} + +// Constructs a matcher that matches a const StringView& whose value is +// equal to s. +Matcher<const internal::StringView&>::Matcher(internal::StringView s) { + *this = Eq(std::string(s)); +} + +// Constructs a matcher that matches a StringView whose value is equal to +// s. +Matcher<internal::StringView>::Matcher(const std::string& s) { *this = Eq(s); } + +// Constructs a matcher that matches a StringView whose value is equal to +// s. +Matcher<internal::StringView>::Matcher(const char* s) { + *this = Eq(std::string(s)); +} + +// Constructs a matcher that matches a StringView whose value is equal to +// s. +Matcher<internal::StringView>::Matcher(internal::StringView s) { + *this = Eq(std::string(s)); +} +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + +} // namespace testing +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <cstdint> +#include <fstream> +#include <memory> + +#if GTEST_OS_WINDOWS +# include <windows.h> +# include <io.h> +# include <sys/stat.h> +# include <map> // Used in ThreadLocal. +# ifdef _MSC_VER +# include <crtdbg.h> +# endif // _MSC_VER +#else +# include <unistd.h> +#endif // GTEST_OS_WINDOWS + +#if GTEST_OS_MAC +# include <mach/mach_init.h> +# include <mach/task.h> +# include <mach/vm_map.h> +#endif // GTEST_OS_MAC + +#if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \ + GTEST_OS_NETBSD || GTEST_OS_OPENBSD +# include <sys/sysctl.h> +# if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD +# include <sys/user.h> +# endif +#endif + +#if GTEST_OS_QNX +# include <devctl.h> +# include <fcntl.h> +# include <sys/procfs.h> +#endif // GTEST_OS_QNX + +#if GTEST_OS_AIX +# include <procinfo.h> +# include <sys/types.h> +#endif // GTEST_OS_AIX + +#if GTEST_OS_FUCHSIA +# include <zircon/process.h> +# include <zircon/syscalls.h> +#endif // GTEST_OS_FUCHSIA + + +namespace testing { +namespace internal { + +#if defined(_MSC_VER) || defined(__BORLANDC__) +// MSVC and C++Builder do not provide a definition of STDERR_FILENO. +const int kStdOutFileno = 1; +const int kStdErrFileno = 2; +#else +const int kStdOutFileno = STDOUT_FILENO; +const int kStdErrFileno = STDERR_FILENO; +#endif // _MSC_VER + +#if GTEST_OS_LINUX + +namespace { +template <typename T> +T ReadProcFileField(const std::string& filename, int field) { + std::string dummy; + std::ifstream file(filename.c_str()); + while (field-- > 0) { + file >> dummy; + } + T output = 0; + file >> output; + return output; +} +} // namespace + +// Returns the number of active threads, or 0 when there is an error. +size_t GetThreadCount() { + const std::string filename = + (Message() << "/proc/" << getpid() << "/stat").GetString(); + return ReadProcFileField<size_t>(filename, 19); +} + +#elif GTEST_OS_MAC + +size_t GetThreadCount() { + const task_t task = mach_task_self(); + mach_msg_type_number_t thread_count; + thread_act_array_t thread_list; + const kern_return_t status = task_threads(task, &thread_list, &thread_count); + if (status == KERN_SUCCESS) { + // task_threads allocates resources in thread_list and we need to free them + // to avoid leaks. + vm_deallocate(task, + reinterpret_cast<vm_address_t>(thread_list), + sizeof(thread_t) * thread_count); + return static_cast<size_t>(thread_count); + } else { + return 0; + } +} + +#elif GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \ + GTEST_OS_NETBSD + +#if GTEST_OS_NETBSD +#undef KERN_PROC +#define KERN_PROC KERN_PROC2 +#define kinfo_proc kinfo_proc2 +#endif + +#if GTEST_OS_DRAGONFLY +#define KP_NLWP(kp) (kp.kp_nthreads) +#elif GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD +#define KP_NLWP(kp) (kp.ki_numthreads) +#elif GTEST_OS_NETBSD +#define KP_NLWP(kp) (kp.p_nlwps) +#endif + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), +#if GTEST_OS_NETBSD + sizeof(struct kinfo_proc), + 1, +#endif + }; + u_int miblen = sizeof(mib) / sizeof(mib[0]); + struct kinfo_proc info; + size_t size = sizeof(info); + if (sysctl(mib, miblen, &info, &size, NULL, 0)) { + return 0; + } + return static_cast<size_t>(KP_NLWP(info)); +} +#elif GTEST_OS_OPENBSD + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID | KERN_PROC_SHOW_THREADS, + getpid(), + sizeof(struct kinfo_proc), + 0, + }; + u_int miblen = sizeof(mib) / sizeof(mib[0]); + + // get number of structs + size_t size; + if (sysctl(mib, miblen, NULL, &size, NULL, 0)) { + return 0; + } + + mib[5] = static_cast<int>(size / static_cast<size_t>(mib[4])); + + // populate array of structs + struct kinfo_proc info[mib[5]]; + if (sysctl(mib, miblen, &info, &size, NULL, 0)) { + return 0; + } + + // exclude empty members + size_t nthreads = 0; + for (size_t i = 0; i < size / static_cast<size_t>(mib[4]); i++) { + if (info[i].p_tid != -1) + nthreads++; + } + return nthreads; +} + +#elif GTEST_OS_QNX + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + const int fd = open("/proc/self/as", O_RDONLY); + if (fd < 0) { + return 0; + } + procfs_info process_info; + const int status = + devctl(fd, DCMD_PROC_INFO, &process_info, sizeof(process_info), nullptr); + close(fd); + if (status == EOK) { + return static_cast<size_t>(process_info.num_threads); + } else { + return 0; + } +} + +#elif GTEST_OS_AIX + +size_t GetThreadCount() { + struct procentry64 entry; + pid_t pid = getpid(); + int status = getprocs64(&entry, sizeof(entry), nullptr, 0, &pid, 1); + if (status == 1) { + return entry.pi_thcount; + } else { + return 0; + } +} + +#elif GTEST_OS_FUCHSIA + +size_t GetThreadCount() { + int dummy_buffer; + size_t avail; + zx_status_t status = zx_object_get_info( + zx_process_self(), + ZX_INFO_PROCESS_THREADS, + &dummy_buffer, + 0, + nullptr, + &avail); + if (status == ZX_OK) { + return avail; + } else { + return 0; + } +} + +#else + +size_t GetThreadCount() { + // There's no portable way to detect the number of threads, so we just + // return 0 to indicate that we cannot detect it. + return 0; +} + +#endif // GTEST_OS_LINUX + +#if GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS + +void SleepMilliseconds(int n) { + ::Sleep(static_cast<DWORD>(n)); +} + +AutoHandle::AutoHandle() + : handle_(INVALID_HANDLE_VALUE) {} + +AutoHandle::AutoHandle(Handle handle) + : handle_(handle) {} + +AutoHandle::~AutoHandle() { + Reset(); +} + +AutoHandle::Handle AutoHandle::Get() const { + return handle_; +} + +void AutoHandle::Reset() { + Reset(INVALID_HANDLE_VALUE); +} + +void AutoHandle::Reset(HANDLE handle) { + // Resetting with the same handle we already own is invalid. + if (handle_ != handle) { + if (IsCloseable()) { + ::CloseHandle(handle_); + } + handle_ = handle; + } else { + GTEST_CHECK_(!IsCloseable()) + << "Resetting a valid handle to itself is likely a programmer error " + "and thus not allowed."; + } +} + +bool AutoHandle::IsCloseable() const { + // Different Windows APIs may use either of these values to represent an + // invalid handle. + return handle_ != nullptr && handle_ != INVALID_HANDLE_VALUE; +} + +Notification::Notification() + : event_(::CreateEvent(nullptr, // Default security attributes. + TRUE, // Do not reset automatically. + FALSE, // Initially unset. + nullptr)) { // Anonymous event. + GTEST_CHECK_(event_.Get() != nullptr); +} + +void Notification::Notify() { + GTEST_CHECK_(::SetEvent(event_.Get()) != FALSE); +} + +void Notification::WaitForNotification() { + GTEST_CHECK_( + ::WaitForSingleObject(event_.Get(), INFINITE) == WAIT_OBJECT_0); +} + +Mutex::Mutex() + : owner_thread_id_(0), + type_(kDynamic), + critical_section_init_phase_(0), + critical_section_(new CRITICAL_SECTION) { + ::InitializeCriticalSection(critical_section_); +} + +Mutex::~Mutex() { + // Static mutexes are leaked intentionally. It is not thread-safe to try + // to clean them up. + if (type_ == kDynamic) { + ::DeleteCriticalSection(critical_section_); + delete critical_section_; + critical_section_ = nullptr; + } +} + +void Mutex::Lock() { + ThreadSafeLazyInit(); + ::EnterCriticalSection(critical_section_); + owner_thread_id_ = ::GetCurrentThreadId(); +} + +void Mutex::Unlock() { + ThreadSafeLazyInit(); + // We don't protect writing to owner_thread_id_ here, as it's the + // caller's responsibility to ensure that the current thread holds the + // mutex when this is called. + owner_thread_id_ = 0; + ::LeaveCriticalSection(critical_section_); +} + +// Does nothing if the current thread holds the mutex. Otherwise, crashes +// with high probability. +void Mutex::AssertHeld() { + ThreadSafeLazyInit(); + GTEST_CHECK_(owner_thread_id_ == ::GetCurrentThreadId()) + << "The current thread is not holding the mutex @" << this; +} + +namespace { + +#ifdef _MSC_VER +// Use the RAII idiom to flag mem allocs that are intentionally never +// deallocated. The motivation is to silence the false positive mem leaks +// that are reported by the debug version of MS's CRT which can only detect +// if an alloc is missing a matching deallocation. +// Example: +// MemoryIsNotDeallocated memory_is_not_deallocated; +// critical_section_ = new CRITICAL_SECTION; +// +class MemoryIsNotDeallocated +{ + public: + MemoryIsNotDeallocated() : old_crtdbg_flag_(0) { + old_crtdbg_flag_ = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + // Set heap allocation block type to _IGNORE_BLOCK so that MS debug CRT + // doesn't report mem leak if there's no matching deallocation. + _CrtSetDbgFlag(old_crtdbg_flag_ & ~_CRTDBG_ALLOC_MEM_DF); + } + + ~MemoryIsNotDeallocated() { + // Restore the original _CRTDBG_ALLOC_MEM_DF flag + _CrtSetDbgFlag(old_crtdbg_flag_); + } + + private: + int old_crtdbg_flag_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(MemoryIsNotDeallocated); +}; +#endif // _MSC_VER + +} // namespace + +// Initializes owner_thread_id_ and critical_section_ in static mutexes. +void Mutex::ThreadSafeLazyInit() { + // Dynamic mutexes are initialized in the constructor. + if (type_ == kStatic) { + switch ( + ::InterlockedCompareExchange(&critical_section_init_phase_, 1L, 0L)) { + case 0: + // If critical_section_init_phase_ was 0 before the exchange, we + // are the first to test it and need to perform the initialization. + owner_thread_id_ = 0; + { + // Use RAII to flag that following mem alloc is never deallocated. +#ifdef _MSC_VER + MemoryIsNotDeallocated memory_is_not_deallocated; +#endif // _MSC_VER + critical_section_ = new CRITICAL_SECTION; + } + ::InitializeCriticalSection(critical_section_); + // Updates the critical_section_init_phase_ to 2 to signal + // initialization complete. + GTEST_CHECK_(::InterlockedCompareExchange( + &critical_section_init_phase_, 2L, 1L) == + 1L); + break; + case 1: + // Somebody else is already initializing the mutex; spin until they + // are done. + while (::InterlockedCompareExchange(&critical_section_init_phase_, + 2L, + 2L) != 2L) { + // Possibly yields the rest of the thread's time slice to other + // threads. + ::Sleep(0); + } + break; + + case 2: + break; // The mutex is already initialized and ready for use. + + default: + GTEST_CHECK_(false) + << "Unexpected value of critical_section_init_phase_ " + << "while initializing a static mutex."; + } + } +} + +namespace { + +class ThreadWithParamSupport : public ThreadWithParamBase { + public: + static HANDLE CreateThread(Runnable* runnable, + Notification* thread_can_start) { + ThreadMainParam* param = new ThreadMainParam(runnable, thread_can_start); + DWORD thread_id; + HANDLE thread_handle = ::CreateThread( + nullptr, // Default security. + 0, // Default stack size. + &ThreadWithParamSupport::ThreadMain, + param, // Parameter to ThreadMainStatic + 0x0, // Default creation flags. + &thread_id); // Need a valid pointer for the call to work under Win98. + GTEST_CHECK_(thread_handle != nullptr) + << "CreateThread failed with error " << ::GetLastError() << "."; + if (thread_handle == nullptr) { + delete param; + } + return thread_handle; + } + + private: + struct ThreadMainParam { + ThreadMainParam(Runnable* runnable, Notification* thread_can_start) + : runnable_(runnable), + thread_can_start_(thread_can_start) { + } + std::unique_ptr<Runnable> runnable_; + // Does not own. + Notification* thread_can_start_; + }; + + static DWORD WINAPI ThreadMain(void* ptr) { + // Transfers ownership. + std::unique_ptr<ThreadMainParam> param(static_cast<ThreadMainParam*>(ptr)); + if (param->thread_can_start_ != nullptr) + param->thread_can_start_->WaitForNotification(); + param->runnable_->Run(); + return 0; + } + + // Prohibit instantiation. + ThreadWithParamSupport(); + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParamSupport); +}; + +} // namespace + +ThreadWithParamBase::ThreadWithParamBase(Runnable *runnable, + Notification* thread_can_start) + : thread_(ThreadWithParamSupport::CreateThread(runnable, + thread_can_start)) { +} + +ThreadWithParamBase::~ThreadWithParamBase() { + Join(); +} + +void ThreadWithParamBase::Join() { + GTEST_CHECK_(::WaitForSingleObject(thread_.Get(), INFINITE) == WAIT_OBJECT_0) + << "Failed to join the thread with error " << ::GetLastError() << "."; +} + +// Maps a thread to a set of ThreadIdToThreadLocals that have values +// instantiated on that thread and notifies them when the thread exits. A +// ThreadLocal instance is expected to persist until all threads it has +// values on have terminated. +class ThreadLocalRegistryImpl { + public: + // Registers thread_local_instance as having value on the current thread. + // Returns a value that can be used to identify the thread from other threads. + static ThreadLocalValueHolderBase* GetValueOnCurrentThread( + const ThreadLocalBase* thread_local_instance) { +#ifdef _MSC_VER + MemoryIsNotDeallocated memory_is_not_deallocated; +#endif // _MSC_VER + DWORD current_thread = ::GetCurrentThreadId(); + MutexLock lock(&mutex_); + ThreadIdToThreadLocals* const thread_to_thread_locals = + GetThreadLocalsMapLocked(); + ThreadIdToThreadLocals::iterator thread_local_pos = + thread_to_thread_locals->find(current_thread); + if (thread_local_pos == thread_to_thread_locals->end()) { + thread_local_pos = thread_to_thread_locals->insert( + std::make_pair(current_thread, ThreadLocalValues())).first; + StartWatcherThreadFor(current_thread); + } + ThreadLocalValues& thread_local_values = thread_local_pos->second; + ThreadLocalValues::iterator value_pos = + thread_local_values.find(thread_local_instance); + if (value_pos == thread_local_values.end()) { + value_pos = + thread_local_values + .insert(std::make_pair( + thread_local_instance, + std::shared_ptr<ThreadLocalValueHolderBase>( + thread_local_instance->NewValueForCurrentThread()))) + .first; + } + return value_pos->second.get(); + } + + static void OnThreadLocalDestroyed( + const ThreadLocalBase* thread_local_instance) { + std::vector<std::shared_ptr<ThreadLocalValueHolderBase> > value_holders; + // Clean up the ThreadLocalValues data structure while holding the lock, but + // defer the destruction of the ThreadLocalValueHolderBases. + { + MutexLock lock(&mutex_); + ThreadIdToThreadLocals* const thread_to_thread_locals = + GetThreadLocalsMapLocked(); + for (ThreadIdToThreadLocals::iterator it = + thread_to_thread_locals->begin(); + it != thread_to_thread_locals->end(); + ++it) { + ThreadLocalValues& thread_local_values = it->second; + ThreadLocalValues::iterator value_pos = + thread_local_values.find(thread_local_instance); + if (value_pos != thread_local_values.end()) { + value_holders.push_back(value_pos->second); + thread_local_values.erase(value_pos); + // This 'if' can only be successful at most once, so theoretically we + // could break out of the loop here, but we don't bother doing so. + } + } + } + // Outside the lock, let the destructor for 'value_holders' deallocate the + // ThreadLocalValueHolderBases. + } + + static void OnThreadExit(DWORD thread_id) { + GTEST_CHECK_(thread_id != 0) << ::GetLastError(); + std::vector<std::shared_ptr<ThreadLocalValueHolderBase> > value_holders; + // Clean up the ThreadIdToThreadLocals data structure while holding the + // lock, but defer the destruction of the ThreadLocalValueHolderBases. + { + MutexLock lock(&mutex_); + ThreadIdToThreadLocals* const thread_to_thread_locals = + GetThreadLocalsMapLocked(); + ThreadIdToThreadLocals::iterator thread_local_pos = + thread_to_thread_locals->find(thread_id); + if (thread_local_pos != thread_to_thread_locals->end()) { + ThreadLocalValues& thread_local_values = thread_local_pos->second; + for (ThreadLocalValues::iterator value_pos = + thread_local_values.begin(); + value_pos != thread_local_values.end(); + ++value_pos) { + value_holders.push_back(value_pos->second); + } + thread_to_thread_locals->erase(thread_local_pos); + } + } + // Outside the lock, let the destructor for 'value_holders' deallocate the + // ThreadLocalValueHolderBases. + } + + private: + // In a particular thread, maps a ThreadLocal object to its value. + typedef std::map<const ThreadLocalBase*, + std::shared_ptr<ThreadLocalValueHolderBase> > + ThreadLocalValues; + // Stores all ThreadIdToThreadLocals having values in a thread, indexed by + // thread's ID. + typedef std::map<DWORD, ThreadLocalValues> ThreadIdToThreadLocals; + + // Holds the thread id and thread handle that we pass from + // StartWatcherThreadFor to WatcherThreadFunc. + typedef std::pair<DWORD, HANDLE> ThreadIdAndHandle; + + static void StartWatcherThreadFor(DWORD thread_id) { + // The returned handle will be kept in thread_map and closed by + // watcher_thread in WatcherThreadFunc. + HANDLE thread = ::OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION, + FALSE, + thread_id); + GTEST_CHECK_(thread != nullptr); + // We need to pass a valid thread ID pointer into CreateThread for it + // to work correctly under Win98. + DWORD watcher_thread_id; + HANDLE watcher_thread = ::CreateThread( + nullptr, // Default security. + 0, // Default stack size + &ThreadLocalRegistryImpl::WatcherThreadFunc, + reinterpret_cast<LPVOID>(new ThreadIdAndHandle(thread_id, thread)), + CREATE_SUSPENDED, &watcher_thread_id); + GTEST_CHECK_(watcher_thread != nullptr); + // Give the watcher thread the same priority as ours to avoid being + // blocked by it. + ::SetThreadPriority(watcher_thread, + ::GetThreadPriority(::GetCurrentThread())); + ::ResumeThread(watcher_thread); + ::CloseHandle(watcher_thread); + } + + // Monitors exit from a given thread and notifies those + // ThreadIdToThreadLocals about thread termination. + static DWORD WINAPI WatcherThreadFunc(LPVOID param) { + const ThreadIdAndHandle* tah = + reinterpret_cast<const ThreadIdAndHandle*>(param); + GTEST_CHECK_( + ::WaitForSingleObject(tah->second, INFINITE) == WAIT_OBJECT_0); + OnThreadExit(tah->first); + ::CloseHandle(tah->second); + delete tah; + return 0; + } + + // Returns map of thread local instances. + static ThreadIdToThreadLocals* GetThreadLocalsMapLocked() { + mutex_.AssertHeld(); +#ifdef _MSC_VER + MemoryIsNotDeallocated memory_is_not_deallocated; +#endif // _MSC_VER + static ThreadIdToThreadLocals* map = new ThreadIdToThreadLocals(); + return map; + } + + // Protects access to GetThreadLocalsMapLocked() and its return value. + static Mutex mutex_; + // Protects access to GetThreadMapLocked() and its return value. + static Mutex thread_map_mutex_; +}; + +Mutex ThreadLocalRegistryImpl::mutex_(Mutex::kStaticMutex); +Mutex ThreadLocalRegistryImpl::thread_map_mutex_(Mutex::kStaticMutex); + +ThreadLocalValueHolderBase* ThreadLocalRegistry::GetValueOnCurrentThread( + const ThreadLocalBase* thread_local_instance) { + return ThreadLocalRegistryImpl::GetValueOnCurrentThread( + thread_local_instance); +} + +void ThreadLocalRegistry::OnThreadLocalDestroyed( + const ThreadLocalBase* thread_local_instance) { + ThreadLocalRegistryImpl::OnThreadLocalDestroyed(thread_local_instance); +} + +#endif // GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS + +#if GTEST_USES_POSIX_RE + +// Implements RE. Currently only needed for death tests. + +RE::~RE() { + if (is_valid_) { + // regfree'ing an invalid regex might crash because the content + // of the regex is undefined. Since the regex's are essentially + // the same, one cannot be valid (or invalid) without the other + // being so too. + regfree(&partial_regex_); + regfree(&full_regex_); + } + free(const_cast<char*>(pattern_)); +} + +// Returns true if and only if regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.full_regex_, str, 1, &match, 0) == 0; +} + +// Returns true if and only if regular expression re matches a substring of +// str (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.partial_regex_, str, 1, &match, 0) == 0; +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + pattern_ = posix::StrDup(regex); + + // Reserves enough bytes to hold the regular expression used for a + // full match. + const size_t full_regex_len = strlen(regex) + 10; + char* const full_pattern = new char[full_regex_len]; + + snprintf(full_pattern, full_regex_len, "^(%s)$", regex); + is_valid_ = regcomp(&full_regex_, full_pattern, REG_EXTENDED) == 0; + // We want to call regcomp(&partial_regex_, ...) even if the + // previous expression returns false. Otherwise partial_regex_ may + // not be properly initialized can may cause trouble when it's + // freed. + // + // Some implementation of POSIX regex (e.g. on at least some + // versions of Cygwin) doesn't accept the empty string as a valid + // regex. We change it to an equivalent form "()" to be safe. + if (is_valid_) { + const char* const partial_regex = (*regex == '\0') ? "()" : regex; + is_valid_ = regcomp(&partial_regex_, partial_regex, REG_EXTENDED) == 0; + } + EXPECT_TRUE(is_valid_) + << "Regular expression \"" << regex + << "\" is not a valid POSIX Extended regular expression."; + + delete[] full_pattern; +} + +#elif GTEST_USES_SIMPLE_RE + +// Returns true if and only if ch appears anywhere in str (excluding the +// terminating '\0' character). +bool IsInSet(char ch, const char* str) { + return ch != '\0' && strchr(str, ch) != nullptr; +} + +// Returns true if and only if ch belongs to the given classification. +// Unlike similar functions in <ctype.h>, these aren't affected by the +// current locale. +bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; } +bool IsAsciiPunct(char ch) { + return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"); +} +bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); } +bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } +bool IsAsciiWordChar(char ch) { + return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || + ('0' <= ch && ch <= '9') || ch == '_'; +} + +// Returns true if and only if "\\c" is a supported escape sequence. +bool IsValidEscape(char c) { + return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW")); +} + +// Returns true if and only if the given atom (specified by escaped and +// pattern) matches ch. The result is undefined if the atom is invalid. +bool AtomMatchesChar(bool escaped, char pattern_char, char ch) { + if (escaped) { // "\\p" where p is pattern_char. + switch (pattern_char) { + case 'd': return IsAsciiDigit(ch); + case 'D': return !IsAsciiDigit(ch); + case 'f': return ch == '\f'; + case 'n': return ch == '\n'; + case 'r': return ch == '\r'; + case 's': return IsAsciiWhiteSpace(ch); + case 'S': return !IsAsciiWhiteSpace(ch); + case 't': return ch == '\t'; + case 'v': return ch == '\v'; + case 'w': return IsAsciiWordChar(ch); + case 'W': return !IsAsciiWordChar(ch); + } + return IsAsciiPunct(pattern_char) && pattern_char == ch; + } + + return (pattern_char == '.' && ch != '\n') || pattern_char == ch; +} + +// Helper function used by ValidateRegex() to format error messages. +static std::string FormatRegexSyntaxError(const char* regex, int index) { + return (Message() << "Syntax error at index " << index + << " in simple regular expression \"" << regex << "\": ").GetString(); +} + +// Generates non-fatal failures and returns false if regex is invalid; +// otherwise returns true. +bool ValidateRegex(const char* regex) { + if (regex == nullptr) { + ADD_FAILURE() << "NULL is not a valid simple regular expression."; + return false; + } + + bool is_valid = true; + + // True if and only if ?, *, or + can follow the previous atom. + bool prev_repeatable = false; + for (int i = 0; regex[i]; i++) { + if (regex[i] == '\\') { // An escape sequence + i++; + if (regex[i] == '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "'\\' cannot appear at the end."; + return false; + } + + if (!IsValidEscape(regex[i])) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "invalid escape sequence \"\\" << regex[i] << "\"."; + is_valid = false; + } + prev_repeatable = true; + } else { // Not an escape sequence. + const char ch = regex[i]; + + if (ch == '^' && i > 0) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'^' can only appear at the beginning."; + is_valid = false; + } else if (ch == '$' && regex[i + 1] != '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'$' can only appear at the end."; + is_valid = false; + } else if (IsInSet(ch, "()[]{}|")) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'" << ch << "' is unsupported."; + is_valid = false; + } else if (IsRepeat(ch) && !prev_repeatable) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'" << ch << "' can only follow a repeatable token."; + is_valid = false; + } + + prev_repeatable = !IsInSet(ch, "^$?*+"); + } + } + + return is_valid; +} + +// Matches a repeated regex atom followed by a valid simple regular +// expression. The regex atom is defined as c if escaped is false, +// or \c otherwise. repeat is the repetition meta character (?, *, +// or +). The behavior is undefined if str contains too many +// characters to be indexable by size_t, in which case the test will +// probably time out anyway. We are fine with this limitation as +// std::string has it too. +bool MatchRepetitionAndRegexAtHead( + bool escaped, char c, char repeat, const char* regex, + const char* str) { + const size_t min_count = (repeat == '+') ? 1 : 0; + const size_t max_count = (repeat == '?') ? 1 : + static_cast<size_t>(-1) - 1; + // We cannot call numeric_limits::max() as it conflicts with the + // max() macro on Windows. + + for (size_t i = 0; i <= max_count; ++i) { + // We know that the atom matches each of the first i characters in str. + if (i >= min_count && MatchRegexAtHead(regex, str + i)) { + // We have enough matches at the head, and the tail matches too. + // Since we only care about *whether* the pattern matches str + // (as opposed to *how* it matches), there is no need to find a + // greedy match. + return true; + } + if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i])) + return false; + } + return false; +} + +// Returns true if and only if regex matches a prefix of str. regex must +// be a valid simple regular expression and not start with "^", or the +// result is undefined. +bool MatchRegexAtHead(const char* regex, const char* str) { + if (*regex == '\0') // An empty regex matches a prefix of anything. + return true; + + // "$" only matches the end of a string. Note that regex being + // valid guarantees that there's nothing after "$" in it. + if (*regex == '$') + return *str == '\0'; + + // Is the first thing in regex an escape sequence? + const bool escaped = *regex == '\\'; + if (escaped) + ++regex; + if (IsRepeat(regex[1])) { + // MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so + // here's an indirect recursion. It terminates as the regex gets + // shorter in each recursion. + return MatchRepetitionAndRegexAtHead( + escaped, regex[0], regex[1], regex + 2, str); + } else { + // regex isn't empty, isn't "$", and doesn't start with a + // repetition. We match the first atom of regex with the first + // character of str and recurse. + return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) && + MatchRegexAtHead(regex + 1, str + 1); + } +} + +// Returns true if and only if regex matches any substring of str. regex must +// be a valid simple regular expression, or the result is undefined. +// +// The algorithm is recursive, but the recursion depth doesn't exceed +// the regex length, so we won't need to worry about running out of +// stack space normally. In rare cases the time complexity can be +// exponential with respect to the regex length + the string length, +// but usually it's must faster (often close to linear). +bool MatchRegexAnywhere(const char* regex, const char* str) { + if (regex == nullptr || str == nullptr) return false; + + if (*regex == '^') + return MatchRegexAtHead(regex + 1, str); + + // A successful match can be anywhere in str. + do { + if (MatchRegexAtHead(regex, str)) + return true; + } while (*str++ != '\0'); + return false; +} + +// Implements the RE class. + +RE::~RE() { + free(const_cast<char*>(pattern_)); + free(const_cast<char*>(full_pattern_)); +} + +// Returns true if and only if regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.full_pattern_, str); +} + +// Returns true if and only if regular expression re matches a substring of +// str (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.pattern_, str); +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + pattern_ = full_pattern_ = nullptr; + if (regex != nullptr) { + pattern_ = posix::StrDup(regex); + } + + is_valid_ = ValidateRegex(regex); + if (!is_valid_) { + // No need to calculate the full pattern when the regex is invalid. + return; + } + + const size_t len = strlen(regex); + // Reserves enough bytes to hold the regular expression used for a + // full match: we need space to prepend a '^', append a '$', and + // terminate the string with '\0'. + char* buffer = static_cast<char*>(malloc(len + 3)); + full_pattern_ = buffer; + + if (*regex != '^') + *buffer++ = '^'; // Makes sure full_pattern_ starts with '^'. + + // We don't use snprintf or strncpy, as they trigger a warning when + // compiled with VC++ 8.0. + memcpy(buffer, regex, len); + buffer += len; + + if (len == 0 || regex[len - 1] != '$') + *buffer++ = '$'; // Makes sure full_pattern_ ends with '$'. + + *buffer = '\0'; +} + +#endif // GTEST_USES_POSIX_RE + +const char kUnknownFile[] = "unknown file"; + +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) { + const std::string file_name(file == nullptr ? kUnknownFile : file); + + if (line < 0) { + return file_name + ":"; + } +#ifdef _MSC_VER + return file_name + "(" + StreamableToString(line) + "):"; +#else + return file_name + ":" + StreamableToString(line) + ":"; +#endif // _MSC_VER +} + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +// Note that FormatCompilerIndependentFileLocation() does NOT append colon +// to the file location it produces, unlike FormatFileLocation(). +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation( + const char* file, int line) { + const std::string file_name(file == nullptr ? kUnknownFile : file); + + if (line < 0) + return file_name; + else + return file_name + ":" + StreamableToString(line); +} + +GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) + : severity_(severity) { + const char* const marker = + severity == GTEST_INFO ? "[ INFO ]" : + severity == GTEST_WARNING ? "[WARNING]" : + severity == GTEST_ERROR ? "[ ERROR ]" : "[ FATAL ]"; + GetStream() << ::std::endl << marker << " " + << FormatFileLocation(file, line).c_str() << ": "; +} + +// Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. +GTestLog::~GTestLog() { + GetStream() << ::std::endl; + if (severity_ == GTEST_FATAL) { + fflush(stderr); + posix::Abort(); + } +} + +// Disable Microsoft deprecation warnings for POSIX functions called from +// this class (creat, dup, dup2, and close) +GTEST_DISABLE_MSC_DEPRECATED_PUSH_() + +#if GTEST_HAS_STREAM_REDIRECTION + +// Object that captures an output stream (stdout/stderr). +class CapturedStream { + public: + // The ctor redirects the stream to a temporary file. + explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { +# if GTEST_OS_WINDOWS + char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT + char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT + + ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); + const UINT success = ::GetTempFileNameA(temp_dir_path, + "gtest_redir", + 0, // Generate unique file name. + temp_file_path); + GTEST_CHECK_(success != 0) + << "Unable to create a temporary file in " << temp_dir_path; + const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); + GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " + << temp_file_path; + filename_ = temp_file_path; +# else + // There's no guarantee that a test has write access to the current + // directory, so we create the temporary file in the /tmp directory + // instead. We use /tmp on most systems, and /sdcard on Android. + // That's because Android doesn't have /tmp. +# if GTEST_OS_LINUX_ANDROID + // Note: Android applications are expected to call the framework's + // Context.getExternalStorageDirectory() method through JNI to get + // the location of the world-writable SD Card directory. However, + // this requires a Context handle, which cannot be retrieved + // globally from native code. Doing so also precludes running the + // code as part of a regular standalone executable, which doesn't + // run in a Dalvik process (e.g. when running it through 'adb shell'). + // + // The location /data/local/tmp is directly accessible from native code. + // '/sdcard' and other variants cannot be relied on, as they are not + // guaranteed to be mounted, or may have a delay in mounting. + char name_template[] = "/data/local/tmp/gtest_captured_stream.XXXXXX"; +# else + char name_template[] = "/tmp/captured_stream.XXXXXX"; +# endif // GTEST_OS_LINUX_ANDROID + const int captured_fd = mkstemp(name_template); + if (captured_fd == -1) { + GTEST_LOG_(WARNING) + << "Failed to create tmp file " << name_template + << " for test; does the test have access to the /tmp directory?"; + } + filename_ = name_template; +# endif // GTEST_OS_WINDOWS + fflush(nullptr); + dup2(captured_fd, fd_); + close(captured_fd); + } + + ~CapturedStream() { + remove(filename_.c_str()); + } + + std::string GetCapturedString() { + if (uncaptured_fd_ != -1) { + // Restores the original stream. + fflush(nullptr); + dup2(uncaptured_fd_, fd_); + close(uncaptured_fd_); + uncaptured_fd_ = -1; + } + + FILE* const file = posix::FOpen(filename_.c_str(), "r"); + if (file == nullptr) { + GTEST_LOG_(FATAL) << "Failed to open tmp file " << filename_ + << " for capturing stream."; + } + const std::string content = ReadEntireFile(file); + posix::FClose(file); + return content; + } + + private: + const int fd_; // A stream to capture. + int uncaptured_fd_; + // Name of the temporary file holding the stderr output. + ::std::string filename_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream); +}; + +GTEST_DISABLE_MSC_DEPRECATED_POP_() + +static CapturedStream* g_captured_stderr = nullptr; +static CapturedStream* g_captured_stdout = nullptr; + +// Starts capturing an output stream (stdout/stderr). +static void CaptureStream(int fd, const char* stream_name, + CapturedStream** stream) { + if (*stream != nullptr) { + GTEST_LOG_(FATAL) << "Only one " << stream_name + << " capturer can exist at a time."; + } + *stream = new CapturedStream(fd); +} + +// Stops capturing the output stream and returns the captured string. +static std::string GetCapturedStream(CapturedStream** captured_stream) { + const std::string content = (*captured_stream)->GetCapturedString(); + + delete *captured_stream; + *captured_stream = nullptr; + + return content; +} + +// Starts capturing stdout. +void CaptureStdout() { + CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); +} + +// Starts capturing stderr. +void CaptureStderr() { + CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); +} + +// Stops capturing stdout and returns the captured string. +std::string GetCapturedStdout() { + return GetCapturedStream(&g_captured_stdout); +} + +// Stops capturing stderr and returns the captured string. +std::string GetCapturedStderr() { + return GetCapturedStream(&g_captured_stderr); +} + +#endif // GTEST_HAS_STREAM_REDIRECTION + + + + + +size_t GetFileSize(FILE* file) { + fseek(file, 0, SEEK_END); + return static_cast<size_t>(ftell(file)); +} + +std::string ReadEntireFile(FILE* file) { + const size_t file_size = GetFileSize(file); + char* const buffer = new char[file_size]; + + size_t bytes_last_read = 0; // # of bytes read in the last fread() + size_t bytes_read = 0; // # of bytes read so far + + fseek(file, 0, SEEK_SET); + + // Keeps reading the file until we cannot read further or the + // pre-determined file size is reached. + do { + bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file); + bytes_read += bytes_last_read; + } while (bytes_last_read > 0 && bytes_read < file_size); + + const std::string content(buffer, bytes_read); + delete[] buffer; + + return content; +} + +#if GTEST_HAS_DEATH_TEST +static const std::vector<std::string>* g_injected_test_argvs = + nullptr; // Owned. + +std::vector<std::string> GetInjectableArgvs() { + if (g_injected_test_argvs != nullptr) { + return *g_injected_test_argvs; + } + return GetArgvs(); +} + +void SetInjectableArgvs(const std::vector<std::string>* new_argvs) { + if (g_injected_test_argvs != new_argvs) delete g_injected_test_argvs; + g_injected_test_argvs = new_argvs; +} + +void SetInjectableArgvs(const std::vector<std::string>& new_argvs) { + SetInjectableArgvs( + new std::vector<std::string>(new_argvs.begin(), new_argvs.end())); +} + +void ClearInjectableArgvs() { + delete g_injected_test_argvs; + g_injected_test_argvs = nullptr; +} +#endif // GTEST_HAS_DEATH_TEST + +#if GTEST_OS_WINDOWS_MOBILE +namespace posix { +void Abort() { + DebugBreak(); + TerminateProcess(GetCurrentProcess(), 1); +} +} // namespace posix +#endif // GTEST_OS_WINDOWS_MOBILE + +// Returns the name of the environment variable corresponding to the +// given flag. For example, FlagToEnvVar("foo") will return +// "GTEST_FOO" in the open-source version. +static std::string FlagToEnvVar(const char* flag) { + const std::string full_flag = + (Message() << GTEST_FLAG_PREFIX_ << flag).GetString(); + + Message env_var; + for (size_t i = 0; i != full_flag.length(); i++) { + env_var << ToUpper(full_flag.c_str()[i]); + } + + return env_var.GetString(); +} + +// Parses 'str' for a 32-bit signed integer. If successful, writes +// the result to *value and returns true; otherwise leaves *value +// unchanged and returns false. +bool ParseInt32(const Message& src_text, const char* str, int32_t* value) { + // Parses the environment variable as a decimal integer. + char* end = nullptr; + const long long_value = strtol(str, &end, 10); // NOLINT + + // Has strtol() consumed all characters in the string? + if (*end != '\0') { + // No - an invalid character was encountered. + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" + << " has value \"" << str << "\".\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + // Is the parsed value in the range of an int32_t? + const auto result = static_cast<int32_t>(long_value); + if (long_value == LONG_MAX || long_value == LONG_MIN || + // The parsed value overflows as a long. (strtol() returns + // LONG_MAX or LONG_MIN when the input overflows.) + result != long_value + // The parsed value overflows as an int32_t. + ) { + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" + << " has value " << str << ", which overflows.\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + *value = result; + return true; +} + +// Reads and returns the Boolean environment variable corresponding to +// the given flag; if it's not set, returns default_value. +// +// The value is considered true if and only if it's not "0". +bool BoolFromGTestEnv(const char* flag, bool default_value) { +#if defined(GTEST_GET_BOOL_FROM_ENV_) + return GTEST_GET_BOOL_FROM_ENV_(flag, default_value); +#else + const std::string env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + return string_value == nullptr ? default_value + : strcmp(string_value, "0") != 0; +#endif // defined(GTEST_GET_BOOL_FROM_ENV_) +} + +// Reads and returns a 32-bit integer stored in the environment +// variable corresponding to the given flag; if it isn't set or +// doesn't represent a valid 32-bit integer, returns default_value. +int32_t Int32FromGTestEnv(const char* flag, int32_t default_value) { +#if defined(GTEST_GET_INT32_FROM_ENV_) + return GTEST_GET_INT32_FROM_ENV_(flag, default_value); +#else + const std::string env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + if (string_value == nullptr) { + // The environment variable is not set. + return default_value; + } + + int32_t result = default_value; + if (!ParseInt32(Message() << "Environment variable " << env_var, + string_value, &result)) { + printf("The default value %s is used.\n", + (Message() << default_value).GetString().c_str()); + fflush(stdout); + return default_value; + } + + return result; +#endif // defined(GTEST_GET_INT32_FROM_ENV_) +} + +// As a special case for the 'output' flag, if GTEST_OUTPUT is not +// set, we look for XML_OUTPUT_FILE, which is set by the Bazel build +// system. The value of XML_OUTPUT_FILE is a filename without the +// "xml:" prefix of GTEST_OUTPUT. +// Note that this is meant to be called at the call site so it does +// not check that the flag is 'output' +// In essence this checks an env variable called XML_OUTPUT_FILE +// and if it is set we prepend "xml:" to its value, if it not set we return "" +std::string OutputFlagAlsoCheckEnvVar(){ + std::string default_value_for_output_flag = ""; + const char* xml_output_file_env = posix::GetEnv("XML_OUTPUT_FILE"); + if (nullptr != xml_output_file_env) { + default_value_for_output_flag = std::string("xml:") + xml_output_file_env; + } + return default_value_for_output_flag; +} + +// Reads and returns the string environment variable corresponding to +// the given flag; if it's not set, returns default_value. +const char* StringFromGTestEnv(const char* flag, const char* default_value) { +#if defined(GTEST_GET_STRING_FROM_ENV_) + return GTEST_GET_STRING_FROM_ENV_(flag, default_value); +#else + const std::string env_var = FlagToEnvVar(flag); + const char* const value = posix::GetEnv(env_var.c_str()); + return value == nullptr ? default_value : value; +#endif // defined(GTEST_GET_STRING_FROM_ENV_) +} + +} // namespace internal +} // namespace testing +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Test - The Google C++ Testing and Mocking Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr); +// +// It uses the << operator when possible, and prints the bytes in the +// object otherwise. A user can override its behavior for a class +// type Foo by defining either operator<<(::std::ostream&, const Foo&) +// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that +// defines Foo. + + +#include <stdio.h> + +#include <cctype> +#include <cstdint> +#include <cwchar> +#include <ostream> // NOLINT +#include <string> +#include <type_traits> + + +namespace testing { + +namespace { + +using ::std::ostream; + +// Prints a segment of bytes in the given object. +GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ +void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start, + size_t count, ostream* os) { + char text[5] = ""; + for (size_t i = 0; i != count; i++) { + const size_t j = start + i; + if (i != 0) { + // Organizes the bytes into groups of 2 for easy parsing by + // human. + if ((j % 2) == 0) + *os << ' '; + else + *os << '-'; + } + GTEST_SNPRINTF_(text, sizeof(text), "%02X", obj_bytes[j]); + *os << text; + } +} + +// Prints the bytes in the given value to the given ostream. +void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count, + ostream* os) { + // Tells the user how big the object is. + *os << count << "-byte object <"; + + const size_t kThreshold = 132; + const size_t kChunkSize = 64; + // If the object size is bigger than kThreshold, we'll have to omit + // some details by printing only the first and the last kChunkSize + // bytes. + if (count < kThreshold) { + PrintByteSegmentInObjectTo(obj_bytes, 0, count, os); + } else { + PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os); + *os << " ... "; + // Rounds up to 2-byte boundary. + const size_t resume_pos = (count - kChunkSize + 1)/2*2; + PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os); + } + *os << ">"; +} + +// Helpers for widening a character to char32_t. Since the standard does not +// specify if char / wchar_t is signed or unsigned, it is important to first +// convert it to the unsigned type of the same width before widening it to +// char32_t. +template <typename CharType> +char32_t ToChar32(CharType in) { + return static_cast<char32_t>( + static_cast<typename std::make_unsigned<CharType>::type>(in)); +} + +} // namespace + +namespace internal { + +// Delegates to PrintBytesInObjectToImpl() to print the bytes in the +// given object. The delegation simplifies the implementation, which +// uses the << operator and thus is easier done outside of the +// ::testing::internal namespace, which contains a << operator that +// sometimes conflicts with the one in STL. +void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count, + ostream* os) { + PrintBytesInObjectToImpl(obj_bytes, count, os); +} + +// Depending on the value of a char (or wchar_t), we print it in one +// of three formats: +// - as is if it's a printable ASCII (e.g. 'a', '2', ' '), +// - as a hexadecimal escape sequence (e.g. '\x7F'), or +// - as a special escape sequence (e.g. '\r', '\n'). +enum CharFormat { + kAsIs, + kHexEscape, + kSpecialEscape +}; + +// Returns true if c is a printable ASCII character. We test the +// value of c directly instead of calling isprint(), which is buggy on +// Windows Mobile. +inline bool IsPrintableAscii(char32_t c) { return 0x20 <= c && c <= 0x7E; } + +// Prints c (of type char, char8_t, char16_t, char32_t, or wchar_t) as a +// character literal without the quotes, escaping it when necessary; returns how +// c was formatted. +template <typename Char> +static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) { + const char32_t u_c = ToChar32(c); + switch (u_c) { + case L'\0': + *os << "\\0"; + break; + case L'\'': + *os << "\\'"; + break; + case L'\\': + *os << "\\\\"; + break; + case L'\a': + *os << "\\a"; + break; + case L'\b': + *os << "\\b"; + break; + case L'\f': + *os << "\\f"; + break; + case L'\n': + *os << "\\n"; + break; + case L'\r': + *os << "\\r"; + break; + case L'\t': + *os << "\\t"; + break; + case L'\v': + *os << "\\v"; + break; + default: + if (IsPrintableAscii(u_c)) { + *os << static_cast<char>(c); + return kAsIs; + } else { + ostream::fmtflags flags = os->flags(); + *os << "\\x" << std::hex << std::uppercase << static_cast<int>(u_c); + os->flags(flags); + return kHexEscape; + } + } + return kSpecialEscape; +} + +// Prints a char32_t c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsStringLiteralTo(char32_t c, ostream* os) { + switch (c) { + case L'\'': + *os << "'"; + return kAsIs; + case L'"': + *os << "\\\""; + return kSpecialEscape; + default: + return PrintAsCharLiteralTo(c, os); + } +} + +static const char* GetCharWidthPrefix(char) { + return ""; +} + +static const char* GetCharWidthPrefix(signed char) { + return ""; +} + +static const char* GetCharWidthPrefix(unsigned char) { + return ""; +} + +#ifdef __cpp_char8_t +static const char* GetCharWidthPrefix(char8_t) { + return "u8"; +} +#endif + +static const char* GetCharWidthPrefix(char16_t) { + return "u"; +} + +static const char* GetCharWidthPrefix(char32_t) { + return "U"; +} + +static const char* GetCharWidthPrefix(wchar_t) { + return "L"; +} + +// Prints a char c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsStringLiteralTo(char c, ostream* os) { + return PrintAsStringLiteralTo(ToChar32(c), os); +} + +#ifdef __cpp_char8_t +static CharFormat PrintAsStringLiteralTo(char8_t c, ostream* os) { + return PrintAsStringLiteralTo(ToChar32(c), os); +} +#endif + +static CharFormat PrintAsStringLiteralTo(char16_t c, ostream* os) { + return PrintAsStringLiteralTo(ToChar32(c), os); +} + +static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) { + return PrintAsStringLiteralTo(ToChar32(c), os); +} + +// Prints a character c (of type char, char8_t, char16_t, char32_t, or wchar_t) +// and its code. '\0' is printed as "'\\0'", other unprintable characters are +// also properly escaped using the standard C++ escape sequence. +template <typename Char> +void PrintCharAndCodeTo(Char c, ostream* os) { + // First, print c as a literal in the most readable form we can find. + *os << GetCharWidthPrefix(c) << "'"; + const CharFormat format = PrintAsCharLiteralTo(c, os); + *os << "'"; + + // To aid user debugging, we also print c's code in decimal, unless + // it's 0 (in which case c was printed as '\\0', making the code + // obvious). + if (c == 0) + return; + *os << " (" << static_cast<int>(c); + + // For more convenience, we print c's code again in hexadecimal, + // unless c was already printed in the form '\x##' or the code is in + // [1, 9]. + if (format == kHexEscape || (1 <= c && c <= 9)) { + // Do nothing. + } else { + *os << ", 0x" << String::FormatHexInt(static_cast<int>(c)); + } + *os << ")"; +} + +void PrintTo(unsigned char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); } +void PrintTo(signed char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); } + +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its code. L'\0' is printed as "L'\\0'". +void PrintTo(wchar_t wc, ostream* os) { PrintCharAndCodeTo(wc, os); } + +// TODO(dcheng): Consider making this delegate to PrintCharAndCodeTo() as well. +void PrintTo(char32_t c, ::std::ostream* os) { + *os << std::hex << "U+" << std::uppercase << std::setfill('0') << std::setw(4) + << static_cast<uint32_t>(c); +} + +// Prints the given array of characters to the ostream. CharType must be either +// char, char8_t, char16_t, char32_t, or wchar_t. +// The array starts at begin, the length is len, it may include '\0' characters +// and may not be NUL-terminated. +template <typename CharType> +GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ +static CharFormat PrintCharsAsStringTo( + const CharType* begin, size_t len, ostream* os) { + const char* const quote_prefix = GetCharWidthPrefix(*begin); + *os << quote_prefix << "\""; + bool is_previous_hex = false; + CharFormat print_format = kAsIs; + for (size_t index = 0; index < len; ++index) { + const CharType cur = begin[index]; + if (is_previous_hex && IsXDigit(cur)) { + // Previous character is of '\x..' form and this character can be + // interpreted as another hexadecimal digit in its number. Break string to + // disambiguate. + *os << "\" " << quote_prefix << "\""; + } + is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape; + // Remember if any characters required hex escaping. + if (is_previous_hex) { + print_format = kHexEscape; + } + } + *os << "\""; + return print_format; +} + +// Prints a (const) char/wchar_t array of 'len' elements, starting at address +// 'begin'. CharType must be either char or wchar_t. +template <typename CharType> +GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ +static void UniversalPrintCharArray( + const CharType* begin, size_t len, ostream* os) { + // The code + // const char kFoo[] = "foo"; + // generates an array of 4, not 3, elements, with the last one being '\0'. + // + // Therefore when printing a char array, we don't print the last element if + // it's '\0', such that the output matches the string literal as it's + // written in the source code. + if (len > 0 && begin[len - 1] == '\0') { + PrintCharsAsStringTo(begin, len - 1, os); + return; + } + + // If, however, the last element in the array is not '\0', e.g. + // const char kFoo[] = { 'f', 'o', 'o' }; + // we must print the entire array. We also print a message to indicate + // that the array is not NUL-terminated. + PrintCharsAsStringTo(begin, len, os); + *os << " (no terminating NUL)"; +} + +// Prints a (const) char array of 'len' elements, starting at address 'begin'. +void UniversalPrintArray(const char* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +#ifdef __cpp_char8_t +// Prints a (const) char8_t array of 'len' elements, starting at address +// 'begin'. +void UniversalPrintArray(const char8_t* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} +#endif + +// Prints a (const) char16_t array of 'len' elements, starting at address +// 'begin'. +void UniversalPrintArray(const char16_t* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +// Prints a (const) char32_t array of 'len' elements, starting at address +// 'begin'. +void UniversalPrintArray(const char32_t* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +// Prints a (const) wchar_t array of 'len' elements, starting at address +// 'begin'. +void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +namespace { + +// Prints a null-terminated C-style string to the ostream. +template <typename Char> +void PrintCStringTo(const Char* s, ostream* os) { + if (s == nullptr) { + *os << "NULL"; + } else { + *os << ImplicitCast_<const void*>(s) << " pointing to "; + PrintCharsAsStringTo(s, std::char_traits<Char>::length(s), os); + } +} + +} // anonymous namespace + +void PrintTo(const char* s, ostream* os) { PrintCStringTo(s, os); } + +#ifdef __cpp_char8_t +void PrintTo(const char8_t* s, ostream* os) { PrintCStringTo(s, os); } +#endif + +void PrintTo(const char16_t* s, ostream* os) { PrintCStringTo(s, os); } + +void PrintTo(const char32_t* s, ostream* os) { PrintCStringTo(s, os); } + +// MSVC compiler can be configured to define whar_t as a typedef +// of unsigned short. Defining an overload for const wchar_t* in that case +// would cause pointers to unsigned shorts be printed as wide strings, +// possibly accessing more memory than intended and causing invalid +// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when +// wchar_t is implemented as a native type. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Prints the given wide C string to the ostream. +void PrintTo(const wchar_t* s, ostream* os) { PrintCStringTo(s, os); } +#endif // wchar_t is native + +namespace { + +bool ContainsUnprintableControlCodes(const char* str, size_t length) { + const unsigned char *s = reinterpret_cast<const unsigned char *>(str); + + for (size_t i = 0; i < length; i++) { + unsigned char ch = *s++; + if (std::iscntrl(ch)) { + switch (ch) { + case '\t': + case '\n': + case '\r': + break; + default: + return true; + } + } + } + return false; +} + +bool IsUTF8TrailByte(unsigned char t) { return 0x80 <= t && t<= 0xbf; } + +bool IsValidUTF8(const char* str, size_t length) { + const unsigned char *s = reinterpret_cast<const unsigned char *>(str); + + for (size_t i = 0; i < length;) { + unsigned char lead = s[i++]; + + if (lead <= 0x7f) { + continue; // single-byte character (ASCII) 0..7F + } + if (lead < 0xc2) { + return false; // trail byte or non-shortest form + } else if (lead <= 0xdf && (i + 1) <= length && IsUTF8TrailByte(s[i])) { + ++i; // 2-byte character + } else if (0xe0 <= lead && lead <= 0xef && (i + 2) <= length && + IsUTF8TrailByte(s[i]) && + IsUTF8TrailByte(s[i + 1]) && + // check for non-shortest form and surrogate + (lead != 0xe0 || s[i] >= 0xa0) && + (lead != 0xed || s[i] < 0xa0)) { + i += 2; // 3-byte character + } else if (0xf0 <= lead && lead <= 0xf4 && (i + 3) <= length && + IsUTF8TrailByte(s[i]) && + IsUTF8TrailByte(s[i + 1]) && + IsUTF8TrailByte(s[i + 2]) && + // check for non-shortest form + (lead != 0xf0 || s[i] >= 0x90) && + (lead != 0xf4 || s[i] < 0x90)) { + i += 3; // 4-byte character + } else { + return false; + } + } + return true; +} + +void ConditionalPrintAsText(const char* str, size_t length, ostream* os) { + if (!ContainsUnprintableControlCodes(str, length) && + IsValidUTF8(str, length)) { + *os << "\n As Text: \"" << str << "\""; + } +} + +} // anonymous namespace + +void PrintStringTo(const ::std::string& s, ostream* os) { + if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) { + if (GTEST_FLAG(print_utf8)) { + ConditionalPrintAsText(s.data(), s.size(), os); + } + } +} + +#ifdef __cpp_char8_t +void PrintU8StringTo(const ::std::u8string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif + +void PrintU16StringTo(const ::std::u16string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} + +void PrintU32StringTo(const ::std::u32string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} + +#if GTEST_HAS_STD_WSTRING +void PrintWideStringTo(const ::std::wstring& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_STD_WSTRING + +} // namespace internal + +} // namespace testing +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// The Google C++ Testing and Mocking Framework (Google Test) + + + +namespace testing { + +using internal::GetUnitTestImpl; + +// Gets the summary of the failure message by omitting the stack trace +// in it. +std::string TestPartResult::ExtractSummary(const char* message) { + const char* const stack_trace = strstr(message, internal::kStackTraceMarker); + return stack_trace == nullptr ? message : std::string(message, stack_trace); +} + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result) { + return os << internal::FormatFileLocation(result.file_name(), + result.line_number()) + << " " + << (result.type() == TestPartResult::kSuccess + ? "Success" + : result.type() == TestPartResult::kSkip + ? "Skipped" + : result.type() == TestPartResult::kFatalFailure + ? "Fatal failure" + : "Non-fatal failure") + << ":\n" + << result.message() << std::endl; +} + +// Appends a TestPartResult to the array. +void TestPartResultArray::Append(const TestPartResult& result) { + array_.push_back(result); +} + +// Returns the TestPartResult at the given index (0-based). +const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const { + if (index < 0 || index >= size()) { + printf("\nInvalid index (%d) into TestPartResultArray.\n", index); + internal::posix::Abort(); + } + + return array_[static_cast<size_t>(index)]; +} + +// Returns the number of TestPartResult objects in the array. +int TestPartResultArray::size() const { + return static_cast<int>(array_.size()); +} + +namespace internal { + +HasNewFatalFailureHelper::HasNewFatalFailureHelper() + : has_new_fatal_failure_(false), + original_reporter_(GetUnitTestImpl()-> + GetTestPartResultReporterForCurrentThread()) { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this); +} + +HasNewFatalFailureHelper::~HasNewFatalFailureHelper() { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread( + original_reporter_); +} + +void HasNewFatalFailureHelper::ReportTestPartResult( + const TestPartResult& result) { + if (result.fatally_failed()) + has_new_fatal_failure_ = true; + original_reporter_->ReportTestPartResult(result); +} + +} // namespace internal + +} // namespace testing +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + +namespace testing { +namespace internal { + +// Skips to the first non-space char in str. Returns an empty string if str +// contains only whitespace characters. +static const char* SkipSpaces(const char* str) { + while (IsSpace(*str)) + str++; + return str; +} + +static std::vector<std::string> SplitIntoTestNames(const char* src) { + std::vector<std::string> name_vec; + src = SkipSpaces(src); + for (; src != nullptr; src = SkipComma(src)) { + name_vec.push_back(StripTrailingSpaces(GetPrefixUntilComma(src))); + } + return name_vec; +} + +// Verifies that registered_tests match the test names in +// registered_tests_; returns registered_tests if successful, or +// aborts the program otherwise. +const char* TypedTestSuitePState::VerifyRegisteredTestNames( + const char* test_suite_name, const char* file, int line, + const char* registered_tests) { + RegisterTypeParameterizedTestSuite(test_suite_name, CodeLocation(file, line)); + + typedef RegisteredTestsMap::const_iterator RegisteredTestIter; + registered_ = true; + + std::vector<std::string> name_vec = SplitIntoTestNames(registered_tests); + + Message errors; + + std::set<std::string> tests; + for (std::vector<std::string>::const_iterator name_it = name_vec.begin(); + name_it != name_vec.end(); ++name_it) { + const std::string& name = *name_it; + if (tests.count(name) != 0) { + errors << "Test " << name << " is listed more than once.\n"; + continue; + } + + if (registered_tests_.count(name) != 0) { + tests.insert(name); + } else { + errors << "No test named " << name + << " can be found in this test suite.\n"; + } + } + + for (RegisteredTestIter it = registered_tests_.begin(); + it != registered_tests_.end(); + ++it) { + if (tests.count(it->first) == 0) { + errors << "You forgot to list test " << it->first << ".\n"; + } + } + + const std::string& errors_str = errors.GetString(); + if (errors_str != "") { + fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), + errors_str.c_str()); + fflush(stderr); + posix::Abort(); + } + + return registered_tests; +} + +} // namespace internal +} // namespace testing +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// Google C++ Mocking Framework (Google Mock) +// +// This file #includes all Google Mock implementation .cc files. The +// purpose is to allow a user to build Google Mock by compiling this +// file alone. + +// This line ensures that gmock.h can be compiled on its own, even +// when it's fused. +#include "gmock/gmock.h" + +// The following lines pull in the real gmock *.cc files. +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements cardinalities. + + +#include <limits.h> +#include <ostream> // NOLINT +#include <sstream> +#include <string> + +namespace testing { + +namespace { + +// Implements the Between(m, n) cardinality. +class BetweenCardinalityImpl : public CardinalityInterface { + public: + BetweenCardinalityImpl(int min, int max) + : min_(min >= 0 ? min : 0), + max_(max >= min_ ? max : min_) { + std::stringstream ss; + if (min < 0) { + ss << "The invocation lower bound must be >= 0, " + << "but is actually " << min << "."; + internal::Expect(false, __FILE__, __LINE__, ss.str()); + } else if (max < 0) { + ss << "The invocation upper bound must be >= 0, " + << "but is actually " << max << "."; + internal::Expect(false, __FILE__, __LINE__, ss.str()); + } else if (min > max) { + ss << "The invocation upper bound (" << max + << ") must be >= the invocation lower bound (" << min + << ")."; + internal::Expect(false, __FILE__, __LINE__, ss.str()); + } + } + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + int ConservativeLowerBound() const override { return min_; } + int ConservativeUpperBound() const override { return max_; } + + bool IsSatisfiedByCallCount(int call_count) const override { + return min_ <= call_count && call_count <= max_; + } + + bool IsSaturatedByCallCount(int call_count) const override { + return call_count >= max_; + } + + void DescribeTo(::std::ostream* os) const override; + + private: + const int min_; + const int max_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(BetweenCardinalityImpl); +}; + +// Formats "n times" in a human-friendly way. +inline std::string FormatTimes(int n) { + if (n == 1) { + return "once"; + } else if (n == 2) { + return "twice"; + } else { + std::stringstream ss; + ss << n << " times"; + return ss.str(); + } +} + +// Describes the Between(m, n) cardinality in human-friendly text. +void BetweenCardinalityImpl::DescribeTo(::std::ostream* os) const { + if (min_ == 0) { + if (max_ == 0) { + *os << "never called"; + } else if (max_ == INT_MAX) { + *os << "called any number of times"; + } else { + *os << "called at most " << FormatTimes(max_); + } + } else if (min_ == max_) { + *os << "called " << FormatTimes(min_); + } else if (max_ == INT_MAX) { + *os << "called at least " << FormatTimes(min_); + } else { + // 0 < min_ < max_ < INT_MAX + *os << "called between " << min_ << " and " << max_ << " times"; + } +} + +} // Unnamed namespace + +// Describes the given call count to an ostream. +void Cardinality::DescribeActualCallCountTo(int actual_call_count, + ::std::ostream* os) { + if (actual_call_count > 0) { + *os << "called " << FormatTimes(actual_call_count); + } else { + *os << "never called"; + } +} + +// Creates a cardinality that allows at least n calls. +GTEST_API_ Cardinality AtLeast(int n) { return Between(n, INT_MAX); } + +// Creates a cardinality that allows at most n calls. +GTEST_API_ Cardinality AtMost(int n) { return Between(0, n); } + +// Creates a cardinality that allows any number of calls. +GTEST_API_ Cardinality AnyNumber() { return AtLeast(0); } + +// Creates a cardinality that allows between min and max calls. +GTEST_API_ Cardinality Between(int min, int max) { + return Cardinality(new BetweenCardinalityImpl(min, max)); +} + +// Creates a cardinality that allows exactly n calls. +GTEST_API_ Cardinality Exactly(int n) { return Between(n, n); } + +} // namespace testing +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This file defines some utilities useful for implementing Google +// Mock. They are subject to change without notice, so please DO NOT +// USE THEM IN USER CODE. + + +#include <ctype.h> +#include <ostream> // NOLINT +#include <string> + +namespace testing { +namespace internal { + +// Joins a vector of strings as if they are fields of a tuple; returns +// the joined string. +GTEST_API_ std::string JoinAsTuple(const Strings& fields) { + switch (fields.size()) { + case 0: + return ""; + case 1: + return fields[0]; + default: + std::string result = "(" + fields[0]; + for (size_t i = 1; i < fields.size(); i++) { + result += ", "; + result += fields[i]; + } + result += ")"; + return result; + } +} + +// Converts an identifier name to a space-separated list of lower-case +// words. Each maximum substring of the form [A-Za-z][a-z]*|\d+ is +// treated as one word. For example, both "FooBar123" and +// "foo_bar_123" are converted to "foo bar 123". +GTEST_API_ std::string ConvertIdentifierNameToWords(const char* id_name) { + std::string result; + char prev_char = '\0'; + for (const char* p = id_name; *p != '\0'; prev_char = *(p++)) { + // We don't care about the current locale as the input is + // guaranteed to be a valid C++ identifier name. + const bool starts_new_word = IsUpper(*p) || + (!IsAlpha(prev_char) && IsLower(*p)) || + (!IsDigit(prev_char) && IsDigit(*p)); + + if (IsAlNum(*p)) { + if (starts_new_word && result != "") + result += ' '; + result += ToLower(*p); + } + } + return result; +} + +// This class reports Google Mock failures as Google Test failures. A +// user can define another class in a similar fashion if they intend to +// use Google Mock with a testing framework other than Google Test. +class GoogleTestFailureReporter : public FailureReporterInterface { + public: + void ReportFailure(FailureType type, const char* file, int line, + const std::string& message) override { + AssertHelper(type == kFatal ? + TestPartResult::kFatalFailure : + TestPartResult::kNonFatalFailure, + file, + line, + message.c_str()) = Message(); + if (type == kFatal) { + posix::Abort(); + } + } +}; + +// Returns the global failure reporter. Will create a +// GoogleTestFailureReporter and return it the first time called. +GTEST_API_ FailureReporterInterface* GetFailureReporter() { + // Points to the global failure reporter used by Google Mock. gcc + // guarantees that the following use of failure_reporter is + // thread-safe. We may need to add additional synchronization to + // protect failure_reporter if we port Google Mock to other + // compilers. + static FailureReporterInterface* const failure_reporter = + new GoogleTestFailureReporter(); + return failure_reporter; +} + +// Protects global resources (stdout in particular) used by Log(). +static GTEST_DEFINE_STATIC_MUTEX_(g_log_mutex); + +// Returns true if and only if a log with the given severity is visible +// according to the --gmock_verbose flag. +GTEST_API_ bool LogIsVisible(LogSeverity severity) { + if (GMOCK_FLAG(verbose) == kInfoVerbosity) { + // Always show the log if --gmock_verbose=info. + return true; + } else if (GMOCK_FLAG(verbose) == kErrorVerbosity) { + // Always hide it if --gmock_verbose=error. + return false; + } else { + // If --gmock_verbose is neither "info" nor "error", we treat it + // as "warning" (its default value). + return severity == kWarning; + } +} + +// Prints the given message to stdout if and only if 'severity' >= the level +// specified by the --gmock_verbose flag. If stack_frames_to_skip >= +// 0, also prints the stack trace excluding the top +// stack_frames_to_skip frames. In opt mode, any positive +// stack_frames_to_skip is treated as 0, since we don't know which +// function calls will be inlined by the compiler and need to be +// conservative. +GTEST_API_ void Log(LogSeverity severity, const std::string& message, + int stack_frames_to_skip) { + if (!LogIsVisible(severity)) + return; + + // Ensures that logs from different threads don't interleave. + MutexLock l(&g_log_mutex); + + if (severity == kWarning) { + // Prints a GMOCK WARNING marker to make the warnings easily searchable. + std::cout << "\nGMOCK WARNING:"; + } + // Pre-pends a new-line to message if it doesn't start with one. + if (message.empty() || message[0] != '\n') { + std::cout << "\n"; + } + std::cout << message; + if (stack_frames_to_skip >= 0) { +#ifdef NDEBUG + // In opt mode, we have to be conservative and skip no stack frame. + const int actual_to_skip = 0; +#else + // In dbg mode, we can do what the caller tell us to do (plus one + // for skipping this function's stack frame). + const int actual_to_skip = stack_frames_to_skip + 1; +#endif // NDEBUG + + // Appends a new-line to message if it doesn't end with one. + if (!message.empty() && *message.rbegin() != '\n') { + std::cout << "\n"; + } + std::cout << "Stack trace:\n" + << ::testing::internal::GetCurrentOsStackTraceExceptTop( + ::testing::UnitTest::GetInstance(), actual_to_skip); + } + std::cout << ::std::flush; +} + +GTEST_API_ WithoutMatchers GetWithoutMatchers() { return WithoutMatchers(); } + +GTEST_API_ void IllegalDoDefault(const char* file, int line) { + internal::Assert( + false, file, line, + "You are using DoDefault() inside a composite action like " + "DoAll() or WithArgs(). This is not supported for technical " + "reasons. Please instead spell out the default action, or " + "assign the default action to an Action variable and use " + "the variable in various places."); +} + +} // namespace internal +} // namespace testing +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements Matcher<const string&>, Matcher<string>, and +// utilities for defining matchers. + + +#include <string.h> +#include <iostream> +#include <sstream> +#include <string> + +namespace testing { +namespace internal { + +// Returns the description for a matcher defined using the MATCHER*() +// macro where the user-supplied description string is "", if +// 'negation' is false; otherwise returns the description of the +// negation of the matcher. 'param_values' contains a list of strings +// that are the print-out of the matcher's parameters. +GTEST_API_ std::string FormatMatcherDescription(bool negation, + const char* matcher_name, + const Strings& param_values) { + std::string result = ConvertIdentifierNameToWords(matcher_name); + if (param_values.size() >= 1) result += " " + JoinAsTuple(param_values); + return negation ? "not (" + result + ")" : result; +} + +// FindMaxBipartiteMatching and its helper class. +// +// Uses the well-known Ford-Fulkerson max flow method to find a maximum +// bipartite matching. Flow is considered to be from left to right. +// There is an implicit source node that is connected to all of the left +// nodes, and an implicit sink node that is connected to all of the +// right nodes. All edges have unit capacity. +// +// Neither the flow graph nor the residual flow graph are represented +// explicitly. Instead, they are implied by the information in 'graph' and +// a vector<int> called 'left_' whose elements are initialized to the +// value kUnused. This represents the initial state of the algorithm, +// where the flow graph is empty, and the residual flow graph has the +// following edges: +// - An edge from source to each left_ node +// - An edge from each right_ node to sink +// - An edge from each left_ node to each right_ node, if the +// corresponding edge exists in 'graph'. +// +// When the TryAugment() method adds a flow, it sets left_[l] = r for some +// nodes l and r. This induces the following changes: +// - The edges (source, l), (l, r), and (r, sink) are added to the +// flow graph. +// - The same three edges are removed from the residual flow graph. +// - The reverse edges (l, source), (r, l), and (sink, r) are added +// to the residual flow graph, which is a directional graph +// representing unused flow capacity. +// +// When the method augments a flow (moving left_[l] from some r1 to some +// other r2), this can be thought of as "undoing" the above steps with +// respect to r1 and "redoing" them with respect to r2. +// +// It bears repeating that the flow graph and residual flow graph are +// never represented explicitly, but can be derived by looking at the +// information in 'graph' and in left_. +// +// As an optimization, there is a second vector<int> called right_ which +// does not provide any new information. Instead, it enables more +// efficient queries about edges entering or leaving the right-side nodes +// of the flow or residual flow graphs. The following invariants are +// maintained: +// +// left[l] == kUnused or right[left[l]] == l +// right[r] == kUnused or left[right[r]] == r +// +// . [ source ] . +// . ||| . +// . ||| . +// . ||\--> left[0]=1 ---\ right[0]=-1 ----\ . +// . || | | . +// . |\---> left[1]=-1 \--> right[1]=0 ---\| . +// . | || . +// . \----> left[2]=2 ------> right[2]=2 --\|| . +// . ||| . +// . elements matchers vvv . +// . [ sink ] . +// +// See Also: +// [1] Cormen, et al (2001). "Section 26.2: The Ford-Fulkerson method". +// "Introduction to Algorithms (Second ed.)", pp. 651-664. +// [2] "Ford-Fulkerson algorithm", Wikipedia, +// 'http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm' +class MaxBipartiteMatchState { + public: + explicit MaxBipartiteMatchState(const MatchMatrix& graph) + : graph_(&graph), + left_(graph_->LhsSize(), kUnused), + right_(graph_->RhsSize(), kUnused) {} + + // Returns the edges of a maximal match, each in the form {left, right}. + ElementMatcherPairs Compute() { + // 'seen' is used for path finding { 0: unseen, 1: seen }. + ::std::vector<char> seen; + // Searches the residual flow graph for a path from each left node to + // the sink in the residual flow graph, and if one is found, add flow + // to the graph. It's okay to search through the left nodes once. The + // edge from the implicit source node to each previously-visited left + // node will have flow if that left node has any path to the sink + // whatsoever. Subsequent augmentations can only add flow to the + // network, and cannot take away that previous flow unit from the source. + // Since the source-to-left edge can only carry one flow unit (or, + // each element can be matched to only one matcher), there is no need + // to visit the left nodes more than once looking for augmented paths. + // The flow is known to be possible or impossible by looking at the + // node once. + for (size_t ilhs = 0; ilhs < graph_->LhsSize(); ++ilhs) { + // Reset the path-marking vector and try to find a path from + // source to sink starting at the left_[ilhs] node. + GTEST_CHECK_(left_[ilhs] == kUnused) + << "ilhs: " << ilhs << ", left_[ilhs]: " << left_[ilhs]; + // 'seen' initialized to 'graph_->RhsSize()' copies of 0. + seen.assign(graph_->RhsSize(), 0); + TryAugment(ilhs, &seen); + } + ElementMatcherPairs result; + for (size_t ilhs = 0; ilhs < left_.size(); ++ilhs) { + size_t irhs = left_[ilhs]; + if (irhs == kUnused) continue; + result.push_back(ElementMatcherPair(ilhs, irhs)); + } + return result; + } + + private: + static const size_t kUnused = static_cast<size_t>(-1); + + // Perform a depth-first search from left node ilhs to the sink. If a + // path is found, flow is added to the network by linking the left and + // right vector elements corresponding each segment of the path. + // Returns true if a path to sink was found, which means that a unit of + // flow was added to the network. The 'seen' vector elements correspond + // to right nodes and are marked to eliminate cycles from the search. + // + // Left nodes will only be explored at most once because they + // are accessible from at most one right node in the residual flow + // graph. + // + // Note that left_[ilhs] is the only element of left_ that TryAugment will + // potentially transition from kUnused to another value. Any other + // left_ element holding kUnused before TryAugment will be holding it + // when TryAugment returns. + // + bool TryAugment(size_t ilhs, ::std::vector<char>* seen) { + for (size_t irhs = 0; irhs < graph_->RhsSize(); ++irhs) { + if ((*seen)[irhs]) continue; + if (!graph_->HasEdge(ilhs, irhs)) continue; + // There's an available edge from ilhs to irhs. + (*seen)[irhs] = 1; + // Next a search is performed to determine whether + // this edge is a dead end or leads to the sink. + // + // right_[irhs] == kUnused means that there is residual flow from + // right node irhs to the sink, so we can use that to finish this + // flow path and return success. + // + // Otherwise there is residual flow to some ilhs. We push flow + // along that path and call ourselves recursively to see if this + // ultimately leads to sink. + if (right_[irhs] == kUnused || TryAugment(right_[irhs], seen)) { + // Add flow from left_[ilhs] to right_[irhs]. + left_[ilhs] = irhs; + right_[irhs] = ilhs; + return true; + } + } + return false; + } + + const MatchMatrix* graph_; // not owned + // Each element of the left_ vector represents a left hand side node + // (i.e. an element) and each element of right_ is a right hand side + // node (i.e. a matcher). The values in the left_ vector indicate + // outflow from that node to a node on the right_ side. The values + // in the right_ indicate inflow, and specify which left_ node is + // feeding that right_ node, if any. For example, left_[3] == 1 means + // there's a flow from element #3 to matcher #1. Such a flow would also + // be redundantly represented in the right_ vector as right_[1] == 3. + // Elements of left_ and right_ are either kUnused or mutually + // referent. Mutually referent means that left_[right_[i]] = i and + // right_[left_[i]] = i. + ::std::vector<size_t> left_; + ::std::vector<size_t> right_; +}; + +const size_t MaxBipartiteMatchState::kUnused; + +GTEST_API_ ElementMatcherPairs FindMaxBipartiteMatching(const MatchMatrix& g) { + return MaxBipartiteMatchState(g).Compute(); +} + +static void LogElementMatcherPairVec(const ElementMatcherPairs& pairs, + ::std::ostream* stream) { + typedef ElementMatcherPairs::const_iterator Iter; + ::std::ostream& os = *stream; + os << "{"; + const char* sep = ""; + for (Iter it = pairs.begin(); it != pairs.end(); ++it) { + os << sep << "\n (" + << "element #" << it->first << ", " + << "matcher #" << it->second << ")"; + sep = ","; + } + os << "\n}"; +} + +bool MatchMatrix::NextGraph() { + for (size_t ilhs = 0; ilhs < LhsSize(); ++ilhs) { + for (size_t irhs = 0; irhs < RhsSize(); ++irhs) { + char& b = matched_[SpaceIndex(ilhs, irhs)]; + if (!b) { + b = 1; + return true; + } + b = 0; + } + } + return false; +} + +void MatchMatrix::Randomize() { + for (size_t ilhs = 0; ilhs < LhsSize(); ++ilhs) { + for (size_t irhs = 0; irhs < RhsSize(); ++irhs) { + char& b = matched_[SpaceIndex(ilhs, irhs)]; + b = static_cast<char>(rand() & 1); // NOLINT + } + } +} + +std::string MatchMatrix::DebugString() const { + ::std::stringstream ss; + const char* sep = ""; + for (size_t i = 0; i < LhsSize(); ++i) { + ss << sep; + for (size_t j = 0; j < RhsSize(); ++j) { + ss << HasEdge(i, j); + } + sep = ";"; + } + return ss.str(); +} + +void UnorderedElementsAreMatcherImplBase::DescribeToImpl( + ::std::ostream* os) const { + switch (match_flags()) { + case UnorderedMatcherRequire::ExactMatch: + if (matcher_describers_.empty()) { + *os << "is empty"; + return; + } + if (matcher_describers_.size() == 1) { + *os << "has " << Elements(1) << " and that element "; + matcher_describers_[0]->DescribeTo(os); + return; + } + *os << "has " << Elements(matcher_describers_.size()) + << " and there exists some permutation of elements such that:\n"; + break; + case UnorderedMatcherRequire::Superset: + *os << "a surjection from elements to requirements exists such that:\n"; + break; + case UnorderedMatcherRequire::Subset: + *os << "an injection from elements to requirements exists such that:\n"; + break; + } + + const char* sep = ""; + for (size_t i = 0; i != matcher_describers_.size(); ++i) { + *os << sep; + if (match_flags() == UnorderedMatcherRequire::ExactMatch) { + *os << " - element #" << i << " "; + } else { + *os << " - an element "; + } + matcher_describers_[i]->DescribeTo(os); + if (match_flags() == UnorderedMatcherRequire::ExactMatch) { + sep = ", and\n"; + } else { + sep = "\n"; + } + } +} + +void UnorderedElementsAreMatcherImplBase::DescribeNegationToImpl( + ::std::ostream* os) const { + switch (match_flags()) { + case UnorderedMatcherRequire::ExactMatch: + if (matcher_describers_.empty()) { + *os << "isn't empty"; + return; + } + if (matcher_describers_.size() == 1) { + *os << "doesn't have " << Elements(1) << ", or has " << Elements(1) + << " that "; + matcher_describers_[0]->DescribeNegationTo(os); + return; + } + *os << "doesn't have " << Elements(matcher_describers_.size()) + << ", or there exists no permutation of elements such that:\n"; + break; + case UnorderedMatcherRequire::Superset: + *os << "no surjection from elements to requirements exists such that:\n"; + break; + case UnorderedMatcherRequire::Subset: + *os << "no injection from elements to requirements exists such that:\n"; + break; + } + const char* sep = ""; + for (size_t i = 0; i != matcher_describers_.size(); ++i) { + *os << sep; + if (match_flags() == UnorderedMatcherRequire::ExactMatch) { + *os << " - element #" << i << " "; + } else { + *os << " - an element "; + } + matcher_describers_[i]->DescribeTo(os); + if (match_flags() == UnorderedMatcherRequire::ExactMatch) { + sep = ", and\n"; + } else { + sep = "\n"; + } + } +} + +// Checks that all matchers match at least one element, and that all +// elements match at least one matcher. This enables faster matching +// and better error reporting. +// Returns false, writing an explanation to 'listener', if and only +// if the success criteria are not met. +bool UnorderedElementsAreMatcherImplBase::VerifyMatchMatrix( + const ::std::vector<std::string>& element_printouts, + const MatchMatrix& matrix, MatchResultListener* listener) const { + bool result = true; + ::std::vector<char> element_matched(matrix.LhsSize(), 0); + ::std::vector<char> matcher_matched(matrix.RhsSize(), 0); + + for (size_t ilhs = 0; ilhs < matrix.LhsSize(); ilhs++) { + for (size_t irhs = 0; irhs < matrix.RhsSize(); irhs++) { + char matched = matrix.HasEdge(ilhs, irhs); + element_matched[ilhs] |= matched; + matcher_matched[irhs] |= matched; + } + } + + if (match_flags() & UnorderedMatcherRequire::Superset) { + const char* sep = + "where the following matchers don't match any elements:\n"; + for (size_t mi = 0; mi < matcher_matched.size(); ++mi) { + if (matcher_matched[mi]) continue; + result = false; + if (listener->IsInterested()) { + *listener << sep << "matcher #" << mi << ": "; + matcher_describers_[mi]->DescribeTo(listener->stream()); + sep = ",\n"; + } + } + } + + if (match_flags() & UnorderedMatcherRequire::Subset) { + const char* sep = + "where the following elements don't match any matchers:\n"; + const char* outer_sep = ""; + if (!result) { + outer_sep = "\nand "; + } + for (size_t ei = 0; ei < element_matched.size(); ++ei) { + if (element_matched[ei]) continue; + result = false; + if (listener->IsInterested()) { + *listener << outer_sep << sep << "element #" << ei << ": " + << element_printouts[ei]; + sep = ",\n"; + outer_sep = ""; + } + } + } + return result; +} + +bool UnorderedElementsAreMatcherImplBase::FindPairing( + const MatchMatrix& matrix, MatchResultListener* listener) const { + ElementMatcherPairs matches = FindMaxBipartiteMatching(matrix); + + size_t max_flow = matches.size(); + if ((match_flags() & UnorderedMatcherRequire::Superset) && + max_flow < matrix.RhsSize()) { + if (listener->IsInterested()) { + *listener << "where no permutation of the elements can satisfy all " + "matchers, and the closest match is " + << max_flow << " of " << matrix.RhsSize() + << " matchers with the pairings:\n"; + LogElementMatcherPairVec(matches, listener->stream()); + } + return false; + } + if ((match_flags() & UnorderedMatcherRequire::Subset) && + max_flow < matrix.LhsSize()) { + if (listener->IsInterested()) { + *listener + << "where not all elements can be matched, and the closest match is " + << max_flow << " of " << matrix.RhsSize() + << " matchers with the pairings:\n"; + LogElementMatcherPairVec(matches, listener->stream()); + } + return false; + } + + if (matches.size() > 1) { + if (listener->IsInterested()) { + const char* sep = "where:\n"; + for (size_t mi = 0; mi < matches.size(); ++mi) { + *listener << sep << " - element #" << matches[mi].first + << " is matched by matcher #" << matches[mi].second; + sep = ",\n"; + } + } + } + return true; +} + +} // namespace internal +} // namespace testing +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements the spec builder syntax (ON_CALL and +// EXPECT_CALL). + + +#include <stdlib.h> + +#include <iostream> // NOLINT +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + + +#if GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC +# include <unistd.h> // NOLINT +#endif + +// Silence C4800 (C4800: 'int *const ': forcing value +// to bool 'true' or 'false') for MSVC 15 +#ifdef _MSC_VER +#if _MSC_VER == 1900 +# pragma warning(push) +# pragma warning(disable:4800) +#endif +#endif + +namespace testing { +namespace internal { + +// Protects the mock object registry (in class Mock), all function +// mockers, and all expectations. +GTEST_API_ GTEST_DEFINE_STATIC_MUTEX_(g_gmock_mutex); + +// Logs a message including file and line number information. +GTEST_API_ void LogWithLocation(testing::internal::LogSeverity severity, + const char* file, int line, + const std::string& message) { + ::std::ostringstream s; + s << internal::FormatFileLocation(file, line) << " " << message + << ::std::endl; + Log(severity, s.str(), 0); +} + +// Constructs an ExpectationBase object. +ExpectationBase::ExpectationBase(const char* a_file, int a_line, + const std::string& a_source_text) + : file_(a_file), + line_(a_line), + source_text_(a_source_text), + cardinality_specified_(false), + cardinality_(Exactly(1)), + call_count_(0), + retired_(false), + extra_matcher_specified_(false), + repeated_action_specified_(false), + retires_on_saturation_(false), + last_clause_(kNone), + action_count_checked_(false) {} + +// Destructs an ExpectationBase object. +ExpectationBase::~ExpectationBase() {} + +// Explicitly specifies the cardinality of this expectation. Used by +// the subclasses to implement the .Times() clause. +void ExpectationBase::SpecifyCardinality(const Cardinality& a_cardinality) { + cardinality_specified_ = true; + cardinality_ = a_cardinality; +} + +// Retires all pre-requisites of this expectation. +void ExpectationBase::RetireAllPreRequisites() + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + if (is_retired()) { + // We can take this short-cut as we never retire an expectation + // until we have retired all its pre-requisites. + return; + } + + ::std::vector<ExpectationBase*> expectations(1, this); + while (!expectations.empty()) { + ExpectationBase* exp = expectations.back(); + expectations.pop_back(); + + for (ExpectationSet::const_iterator it = + exp->immediate_prerequisites_.begin(); + it != exp->immediate_prerequisites_.end(); ++it) { + ExpectationBase* next = it->expectation_base().get(); + if (!next->is_retired()) { + next->Retire(); + expectations.push_back(next); + } + } + } +} + +// Returns true if and only if all pre-requisites of this expectation +// have been satisfied. +bool ExpectationBase::AllPrerequisitesAreSatisfied() const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + ::std::vector<const ExpectationBase*> expectations(1, this); + while (!expectations.empty()) { + const ExpectationBase* exp = expectations.back(); + expectations.pop_back(); + + for (ExpectationSet::const_iterator it = + exp->immediate_prerequisites_.begin(); + it != exp->immediate_prerequisites_.end(); ++it) { + const ExpectationBase* next = it->expectation_base().get(); + if (!next->IsSatisfied()) return false; + expectations.push_back(next); + } + } + return true; +} + +// Adds unsatisfied pre-requisites of this expectation to 'result'. +void ExpectationBase::FindUnsatisfiedPrerequisites(ExpectationSet* result) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + ::std::vector<const ExpectationBase*> expectations(1, this); + while (!expectations.empty()) { + const ExpectationBase* exp = expectations.back(); + expectations.pop_back(); + + for (ExpectationSet::const_iterator it = + exp->immediate_prerequisites_.begin(); + it != exp->immediate_prerequisites_.end(); ++it) { + const ExpectationBase* next = it->expectation_base().get(); + + if (next->IsSatisfied()) { + // If *it is satisfied and has a call count of 0, some of its + // pre-requisites may not be satisfied yet. + if (next->call_count_ == 0) { + expectations.push_back(next); + } + } else { + // Now that we know next is unsatisfied, we are not so interested + // in whether its pre-requisites are satisfied. Therefore we + // don't iterate into it here. + *result += *it; + } + } + } +} + +// Describes how many times a function call matching this +// expectation has occurred. +void ExpectationBase::DescribeCallCountTo(::std::ostream* os) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + + // Describes how many times the function is expected to be called. + *os << " Expected: to be "; + cardinality().DescribeTo(os); + *os << "\n Actual: "; + Cardinality::DescribeActualCallCountTo(call_count(), os); + + // Describes the state of the expectation (e.g. is it satisfied? + // is it active?). + *os << " - " << (IsOverSaturated() ? "over-saturated" : + IsSaturated() ? "saturated" : + IsSatisfied() ? "satisfied" : "unsatisfied") + << " and " + << (is_retired() ? "retired" : "active"); +} + +// Checks the action count (i.e. the number of WillOnce() and +// WillRepeatedly() clauses) against the cardinality if this hasn't +// been done before. Prints a warning if there are too many or too +// few actions. +void ExpectationBase::CheckActionCountIfNotDone() const + GTEST_LOCK_EXCLUDED_(mutex_) { + bool should_check = false; + { + MutexLock l(&mutex_); + if (!action_count_checked_) { + action_count_checked_ = true; + should_check = true; + } + } + + if (should_check) { + if (!cardinality_specified_) { + // The cardinality was inferred - no need to check the action + // count against it. + return; + } + + // The cardinality was explicitly specified. + const int action_count = static_cast<int>(untyped_actions_.size()); + const int upper_bound = cardinality().ConservativeUpperBound(); + const int lower_bound = cardinality().ConservativeLowerBound(); + bool too_many; // True if there are too many actions, or false + // if there are too few. + if (action_count > upper_bound || + (action_count == upper_bound && repeated_action_specified_)) { + too_many = true; + } else if (0 < action_count && action_count < lower_bound && + !repeated_action_specified_) { + too_many = false; + } else { + return; + } + + ::std::stringstream ss; + DescribeLocationTo(&ss); + ss << "Too " << (too_many ? "many" : "few") + << " actions specified in " << source_text() << "...\n" + << "Expected to be "; + cardinality().DescribeTo(&ss); + ss << ", but has " << (too_many ? "" : "only ") + << action_count << " WillOnce()" + << (action_count == 1 ? "" : "s"); + if (repeated_action_specified_) { + ss << " and a WillRepeatedly()"; + } + ss << "."; + Log(kWarning, ss.str(), -1); // -1 means "don't print stack trace". + } +} + +// Implements the .Times() clause. +void ExpectationBase::UntypedTimes(const Cardinality& a_cardinality) { + if (last_clause_ == kTimes) { + ExpectSpecProperty(false, + ".Times() cannot appear " + "more than once in an EXPECT_CALL()."); + } else { + ExpectSpecProperty(last_clause_ < kTimes, + ".Times() cannot appear after " + ".InSequence(), .WillOnce(), .WillRepeatedly(), " + "or .RetiresOnSaturation()."); + } + last_clause_ = kTimes; + + SpecifyCardinality(a_cardinality); +} + +// Points to the implicit sequence introduced by a living InSequence +// object (if any) in the current thread or NULL. +GTEST_API_ ThreadLocal<Sequence*> g_gmock_implicit_sequence; + +// Reports an uninteresting call (whose description is in msg) in the +// manner specified by 'reaction'. +void ReportUninterestingCall(CallReaction reaction, const std::string& msg) { + // Include a stack trace only if --gmock_verbose=info is specified. + const int stack_frames_to_skip = + GMOCK_FLAG(verbose) == kInfoVerbosity ? 3 : -1; + switch (reaction) { + case kAllow: + Log(kInfo, msg, stack_frames_to_skip); + break; + case kWarn: + Log(kWarning, + msg + + "\nNOTE: You can safely ignore the above warning unless this " + "call should not happen. Do not suppress it by blindly adding " + "an EXPECT_CALL() if you don't mean to enforce the call. " + "See " + "https://github.com/google/googletest/blob/master/docs/" + "gmock_cook_book.md#" + "knowing-when-to-expect for details.\n", + stack_frames_to_skip); + break; + default: // FAIL + Expect(false, nullptr, -1, msg); + } +} + +UntypedFunctionMockerBase::UntypedFunctionMockerBase() + : mock_obj_(nullptr), name_("") {} + +UntypedFunctionMockerBase::~UntypedFunctionMockerBase() {} + +// Sets the mock object this mock method belongs to, and registers +// this information in the global mock registry. Will be called +// whenever an EXPECT_CALL() or ON_CALL() is executed on this mock +// method. +void UntypedFunctionMockerBase::RegisterOwner(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + { + MutexLock l(&g_gmock_mutex); + mock_obj_ = mock_obj; + } + Mock::Register(mock_obj, this); +} + +// Sets the mock object this mock method belongs to, and sets the name +// of the mock function. Will be called upon each invocation of this +// mock function. +void UntypedFunctionMockerBase::SetOwnerAndName(const void* mock_obj, + const char* name) + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + // We protect name_ under g_gmock_mutex in case this mock function + // is called from two threads concurrently. + MutexLock l(&g_gmock_mutex); + mock_obj_ = mock_obj; + name_ = name; +} + +// Returns the name of the function being mocked. Must be called +// after RegisterOwner() or SetOwnerAndName() has been called. +const void* UntypedFunctionMockerBase::MockObject() const + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + const void* mock_obj; + { + // We protect mock_obj_ under g_gmock_mutex in case this mock + // function is called from two threads concurrently. + MutexLock l(&g_gmock_mutex); + Assert(mock_obj_ != nullptr, __FILE__, __LINE__, + "MockObject() must not be called before RegisterOwner() or " + "SetOwnerAndName() has been called."); + mock_obj = mock_obj_; + } + return mock_obj; +} + +// Returns the name of this mock method. Must be called after +// SetOwnerAndName() has been called. +const char* UntypedFunctionMockerBase::Name() const + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + const char* name; + { + // We protect name_ under g_gmock_mutex in case this mock + // function is called from two threads concurrently. + MutexLock l(&g_gmock_mutex); + Assert(name_ != nullptr, __FILE__, __LINE__, + "Name() must not be called before SetOwnerAndName() has " + "been called."); + name = name_; + } + return name; +} + +// Calculates the result of invoking this mock function with the given +// arguments, prints it, and returns it. The caller is responsible +// for deleting the result. +UntypedActionResultHolderBase* UntypedFunctionMockerBase::UntypedInvokeWith( + void* const untyped_args) GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + // See the definition of untyped_expectations_ for why access to it + // is unprotected here. + if (untyped_expectations_.size() == 0) { + // No expectation is set on this mock method - we have an + // uninteresting call. + + // We must get Google Mock's reaction on uninteresting calls + // made on this mock object BEFORE performing the action, + // because the action may DELETE the mock object and make the + // following expression meaningless. + const CallReaction reaction = + Mock::GetReactionOnUninterestingCalls(MockObject()); + + // True if and only if we need to print this call's arguments and return + // value. This definition must be kept in sync with + // the behavior of ReportUninterestingCall(). + const bool need_to_report_uninteresting_call = + // If the user allows this uninteresting call, we print it + // only when they want informational messages. + reaction == kAllow ? LogIsVisible(kInfo) : + // If the user wants this to be a warning, we print + // it only when they want to see warnings. + reaction == kWarn + ? LogIsVisible(kWarning) + : + // Otherwise, the user wants this to be an error, and we + // should always print detailed information in the error. + true; + + if (!need_to_report_uninteresting_call) { + // Perform the action without printing the call information. + return this->UntypedPerformDefaultAction( + untyped_args, "Function call: " + std::string(Name())); + } + + // Warns about the uninteresting call. + ::std::stringstream ss; + this->UntypedDescribeUninterestingCall(untyped_args, &ss); + + // Calculates the function result. + UntypedActionResultHolderBase* const result = + this->UntypedPerformDefaultAction(untyped_args, ss.str()); + + // Prints the function result. + if (result != nullptr) result->PrintAsActionResult(&ss); + + ReportUninterestingCall(reaction, ss.str()); + return result; + } + + bool is_excessive = false; + ::std::stringstream ss; + ::std::stringstream why; + ::std::stringstream loc; + const void* untyped_action = nullptr; + + // The UntypedFindMatchingExpectation() function acquires and + // releases g_gmock_mutex. + + const ExpectationBase* const untyped_expectation = + this->UntypedFindMatchingExpectation(untyped_args, &untyped_action, + &is_excessive, &ss, &why); + const bool found = untyped_expectation != nullptr; + + // True if and only if we need to print the call's arguments + // and return value. + // This definition must be kept in sync with the uses of Expect() + // and Log() in this function. + const bool need_to_report_call = + !found || is_excessive || LogIsVisible(kInfo); + if (!need_to_report_call) { + // Perform the action without printing the call information. + return untyped_action == nullptr + ? this->UntypedPerformDefaultAction(untyped_args, "") + : this->UntypedPerformAction(untyped_action, untyped_args); + } + + ss << " Function call: " << Name(); + this->UntypedPrintArgs(untyped_args, &ss); + + // In case the action deletes a piece of the expectation, we + // generate the message beforehand. + if (found && !is_excessive) { + untyped_expectation->DescribeLocationTo(&loc); + } + + UntypedActionResultHolderBase* result = nullptr; + + auto perform_action = [&] { + return untyped_action == nullptr + ? this->UntypedPerformDefaultAction(untyped_args, ss.str()) + : this->UntypedPerformAction(untyped_action, untyped_args); + }; + auto handle_failures = [&] { + ss << "\n" << why.str(); + + if (!found) { + // No expectation matches this call - reports a failure. + Expect(false, nullptr, -1, ss.str()); + } else if (is_excessive) { + // We had an upper-bound violation and the failure message is in ss. + Expect(false, untyped_expectation->file(), untyped_expectation->line(), + ss.str()); + } else { + // We had an expected call and the matching expectation is + // described in ss. + Log(kInfo, loc.str() + ss.str(), 2); + } + }; +#if GTEST_HAS_EXCEPTIONS + try { + result = perform_action(); + } catch (...) { + handle_failures(); + throw; + } +#else + result = perform_action(); +#endif + + if (result != nullptr) result->PrintAsActionResult(&ss); + handle_failures(); + return result; +} + +// Returns an Expectation object that references and co-owns exp, +// which must be an expectation on this mock function. +Expectation UntypedFunctionMockerBase::GetHandleOf(ExpectationBase* exp) { + // See the definition of untyped_expectations_ for why access to it + // is unprotected here. + for (UntypedExpectations::const_iterator it = + untyped_expectations_.begin(); + it != untyped_expectations_.end(); ++it) { + if (it->get() == exp) { + return Expectation(*it); + } + } + + Assert(false, __FILE__, __LINE__, "Cannot find expectation."); + return Expectation(); + // The above statement is just to make the code compile, and will + // never be executed. +} + +// Verifies that all expectations on this mock function have been +// satisfied. Reports one or more Google Test non-fatal failures +// and returns false if not. +bool UntypedFunctionMockerBase::VerifyAndClearExpectationsLocked() + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + bool expectations_met = true; + for (UntypedExpectations::const_iterator it = + untyped_expectations_.begin(); + it != untyped_expectations_.end(); ++it) { + ExpectationBase* const untyped_expectation = it->get(); + if (untyped_expectation->IsOverSaturated()) { + // There was an upper-bound violation. Since the error was + // already reported when it occurred, there is no need to do + // anything here. + expectations_met = false; + } else if (!untyped_expectation->IsSatisfied()) { + expectations_met = false; + ::std::stringstream ss; + ss << "Actual function call count doesn't match " + << untyped_expectation->source_text() << "...\n"; + // No need to show the source file location of the expectation + // in the description, as the Expect() call that follows already + // takes care of it. + untyped_expectation->MaybeDescribeExtraMatcherTo(&ss); + untyped_expectation->DescribeCallCountTo(&ss); + Expect(false, untyped_expectation->file(), + untyped_expectation->line(), ss.str()); + } + } + + // Deleting our expectations may trigger other mock objects to be deleted, for + // example if an action contains a reference counted smart pointer to that + // mock object, and that is the last reference. So if we delete our + // expectations within the context of the global mutex we may deadlock when + // this method is called again. Instead, make a copy of the set of + // expectations to delete, clear our set within the mutex, and then clear the + // copied set outside of it. + UntypedExpectations expectations_to_delete; + untyped_expectations_.swap(expectations_to_delete); + + g_gmock_mutex.Unlock(); + expectations_to_delete.clear(); + g_gmock_mutex.Lock(); + + return expectations_met; +} + +CallReaction intToCallReaction(int mock_behavior) { + if (mock_behavior >= kAllow && mock_behavior <= kFail) { + return static_cast<internal::CallReaction>(mock_behavior); + } + return kWarn; +} + +} // namespace internal + +// Class Mock. + +namespace { + +typedef std::set<internal::UntypedFunctionMockerBase*> FunctionMockers; + +// The current state of a mock object. Such information is needed for +// detecting leaked mock objects and explicitly verifying a mock's +// expectations. +struct MockObjectState { + MockObjectState() + : first_used_file(nullptr), first_used_line(-1), leakable(false) {} + + // Where in the source file an ON_CALL or EXPECT_CALL is first + // invoked on this mock object. + const char* first_used_file; + int first_used_line; + ::std::string first_used_test_suite; + ::std::string first_used_test; + bool leakable; // true if and only if it's OK to leak the object. + FunctionMockers function_mockers; // All registered methods of the object. +}; + +// A global registry holding the state of all mock objects that are +// alive. A mock object is added to this registry the first time +// Mock::AllowLeak(), ON_CALL(), or EXPECT_CALL() is called on it. It +// is removed from the registry in the mock object's destructor. +class MockObjectRegistry { + public: + // Maps a mock object (identified by its address) to its state. + typedef std::map<const void*, MockObjectState> StateMap; + + // This destructor will be called when a program exits, after all + // tests in it have been run. By then, there should be no mock + // object alive. Therefore we report any living object as test + // failure, unless the user explicitly asked us to ignore it. + ~MockObjectRegistry() { + if (!GMOCK_FLAG(catch_leaked_mocks)) + return; + + int leaked_count = 0; + for (StateMap::const_iterator it = states_.begin(); it != states_.end(); + ++it) { + if (it->second.leakable) // The user said it's fine to leak this object. + continue; + + // FIXME: Print the type of the leaked object. + // This can help the user identify the leaked object. + std::cout << "\n"; + const MockObjectState& state = it->second; + std::cout << internal::FormatFileLocation(state.first_used_file, + state.first_used_line); + std::cout << " ERROR: this mock object"; + if (state.first_used_test != "") { + std::cout << " (used in test " << state.first_used_test_suite << "." + << state.first_used_test << ")"; + } + std::cout << " should be deleted but never is. Its address is @" + << it->first << "."; + leaked_count++; + } + if (leaked_count > 0) { + std::cout << "\nERROR: " << leaked_count << " leaked mock " + << (leaked_count == 1 ? "object" : "objects") + << " found at program exit. Expectations on a mock object are " + "verified when the object is destructed. Leaking a mock " + "means that its expectations aren't verified, which is " + "usually a test bug. If you really intend to leak a mock, " + "you can suppress this error using " + "testing::Mock::AllowLeak(mock_object), or you may use a " + "fake or stub instead of a mock.\n"; + std::cout.flush(); + ::std::cerr.flush(); + // RUN_ALL_TESTS() has already returned when this destructor is + // called. Therefore we cannot use the normal Google Test + // failure reporting mechanism. + _exit(1); // We cannot call exit() as it is not reentrant and + // may already have been called. + } + } + + StateMap& states() { return states_; } + + private: + StateMap states_; +}; + +// Protected by g_gmock_mutex. +MockObjectRegistry g_mock_object_registry; + +// Maps a mock object to the reaction Google Mock should have when an +// uninteresting method is called. Protected by g_gmock_mutex. +std::map<const void*, internal::CallReaction> g_uninteresting_call_reaction; + +// Sets the reaction Google Mock should have when an uninteresting +// method of the given mock object is called. +void SetReactionOnUninterestingCalls(const void* mock_obj, + internal::CallReaction reaction) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + internal::MutexLock l(&internal::g_gmock_mutex); + g_uninteresting_call_reaction[mock_obj] = reaction; +} + +} // namespace + +// Tells Google Mock to allow uninteresting calls on the given mock +// object. +void Mock::AllowUninterestingCalls(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + SetReactionOnUninterestingCalls(mock_obj, internal::kAllow); +} + +// Tells Google Mock to warn the user about uninteresting calls on the +// given mock object. +void Mock::WarnUninterestingCalls(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + SetReactionOnUninterestingCalls(mock_obj, internal::kWarn); +} + +// Tells Google Mock to fail uninteresting calls on the given mock +// object. +void Mock::FailUninterestingCalls(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + SetReactionOnUninterestingCalls(mock_obj, internal::kFail); +} + +// Tells Google Mock the given mock object is being destroyed and its +// entry in the call-reaction table should be removed. +void Mock::UnregisterCallReaction(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + internal::MutexLock l(&internal::g_gmock_mutex); + g_uninteresting_call_reaction.erase(mock_obj); +} + +// Returns the reaction Google Mock will have on uninteresting calls +// made on the given mock object. +internal::CallReaction Mock::GetReactionOnUninterestingCalls( + const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + internal::MutexLock l(&internal::g_gmock_mutex); + return (g_uninteresting_call_reaction.count(mock_obj) == 0) ? + internal::intToCallReaction(GMOCK_FLAG(default_mock_behavior)) : + g_uninteresting_call_reaction[mock_obj]; +} + +// Tells Google Mock to ignore mock_obj when checking for leaked mock +// objects. +void Mock::AllowLeak(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + internal::MutexLock l(&internal::g_gmock_mutex); + g_mock_object_registry.states()[mock_obj].leakable = true; +} + +// Verifies and clears all expectations on the given mock object. If +// the expectations aren't satisfied, generates one or more Google +// Test non-fatal failures and returns false. +bool Mock::VerifyAndClearExpectations(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + internal::MutexLock l(&internal::g_gmock_mutex); + return VerifyAndClearExpectationsLocked(mock_obj); +} + +// Verifies all expectations on the given mock object and clears its +// default actions and expectations. Returns true if and only if the +// verification was successful. +bool Mock::VerifyAndClear(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + internal::MutexLock l(&internal::g_gmock_mutex); + ClearDefaultActionsLocked(mock_obj); + return VerifyAndClearExpectationsLocked(mock_obj); +} + +// Verifies and clears all expectations on the given mock object. If +// the expectations aren't satisfied, generates one or more Google +// Test non-fatal failures and returns false. +bool Mock::VerifyAndClearExpectationsLocked(void* mock_obj) + GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex) { + internal::g_gmock_mutex.AssertHeld(); + if (g_mock_object_registry.states().count(mock_obj) == 0) { + // No EXPECT_CALL() was set on the given mock object. + return true; + } + + // Verifies and clears the expectations on each mock method in the + // given mock object. + bool expectations_met = true; + FunctionMockers& mockers = + g_mock_object_registry.states()[mock_obj].function_mockers; + for (FunctionMockers::const_iterator it = mockers.begin(); + it != mockers.end(); ++it) { + if (!(*it)->VerifyAndClearExpectationsLocked()) { + expectations_met = false; + } + } + + // We don't clear the content of mockers, as they may still be + // needed by ClearDefaultActionsLocked(). + return expectations_met; +} + +bool Mock::IsNaggy(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + return Mock::GetReactionOnUninterestingCalls(mock_obj) == internal::kWarn; +} +bool Mock::IsNice(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + return Mock::GetReactionOnUninterestingCalls(mock_obj) == internal::kAllow; +} +bool Mock::IsStrict(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + return Mock::GetReactionOnUninterestingCalls(mock_obj) == internal::kFail; +} + +// Registers a mock object and a mock method it owns. +void Mock::Register(const void* mock_obj, + internal::UntypedFunctionMockerBase* mocker) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + internal::MutexLock l(&internal::g_gmock_mutex); + g_mock_object_registry.states()[mock_obj].function_mockers.insert(mocker); +} + +// Tells Google Mock where in the source code mock_obj is used in an +// ON_CALL or EXPECT_CALL. In case mock_obj is leaked, this +// information helps the user identify which object it is. +void Mock::RegisterUseByOnCallOrExpectCall(const void* mock_obj, + const char* file, int line) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { + internal::MutexLock l(&internal::g_gmock_mutex); + MockObjectState& state = g_mock_object_registry.states()[mock_obj]; + if (state.first_used_file == nullptr) { + state.first_used_file = file; + state.first_used_line = line; + const TestInfo* const test_info = + UnitTest::GetInstance()->current_test_info(); + if (test_info != nullptr) { + state.first_used_test_suite = test_info->test_suite_name(); + state.first_used_test = test_info->name(); + } + } +} + +// Unregisters a mock method; removes the owning mock object from the +// registry when the last mock method associated with it has been +// unregistered. This is called only in the destructor of +// FunctionMockerBase. +void Mock::UnregisterLocked(internal::UntypedFunctionMockerBase* mocker) + GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex) { + internal::g_gmock_mutex.AssertHeld(); + for (MockObjectRegistry::StateMap::iterator it = + g_mock_object_registry.states().begin(); + it != g_mock_object_registry.states().end(); ++it) { + FunctionMockers& mockers = it->second.function_mockers; + if (mockers.erase(mocker) > 0) { + // mocker was in mockers and has been just removed. + if (mockers.empty()) { + g_mock_object_registry.states().erase(it); + } + return; + } + } +} + +// Clears all ON_CALL()s set on the given mock object. +void Mock::ClearDefaultActionsLocked(void* mock_obj) + GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex) { + internal::g_gmock_mutex.AssertHeld(); + + if (g_mock_object_registry.states().count(mock_obj) == 0) { + // No ON_CALL() was set on the given mock object. + return; + } + + // Clears the default actions for each mock method in the given mock + // object. + FunctionMockers& mockers = + g_mock_object_registry.states()[mock_obj].function_mockers; + for (FunctionMockers::const_iterator it = mockers.begin(); + it != mockers.end(); ++it) { + (*it)->ClearDefaultActionsLocked(); + } + + // We don't clear the content of mockers, as they may still be + // needed by VerifyAndClearExpectationsLocked(). +} + +Expectation::Expectation() {} + +Expectation::Expectation( + const std::shared_ptr<internal::ExpectationBase>& an_expectation_base) + : expectation_base_(an_expectation_base) {} + +Expectation::~Expectation() {} + +// Adds an expectation to a sequence. +void Sequence::AddExpectation(const Expectation& expectation) const { + if (*last_expectation_ != expectation) { + if (last_expectation_->expectation_base() != nullptr) { + expectation.expectation_base()->immediate_prerequisites_ + += *last_expectation_; + } + *last_expectation_ = expectation; + } +} + +// Creates the implicit sequence if there isn't one. +InSequence::InSequence() { + if (internal::g_gmock_implicit_sequence.get() == nullptr) { + internal::g_gmock_implicit_sequence.set(new Sequence); + sequence_created_ = true; + } else { + sequence_created_ = false; + } +} + +// Deletes the implicit sequence if it was created by the constructor +// of this object. +InSequence::~InSequence() { + if (sequence_created_) { + delete internal::g_gmock_implicit_sequence.get(); + internal::g_gmock_implicit_sequence.set(nullptr); + } +} + +} // namespace testing + +#ifdef _MSC_VER +#if _MSC_VER == 1900 +# pragma warning(pop) +#endif +#endif +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +namespace testing { + +GMOCK_DEFINE_bool_(catch_leaked_mocks, true, + "true if and only if Google Mock should report leaked " + "mock objects as failures."); + +GMOCK_DEFINE_string_(verbose, internal::kWarningVerbosity, + "Controls how verbose Google Mock's output is." + " Valid values:\n" + " info - prints all messages.\n" + " warning - prints warnings and errors.\n" + " error - prints errors only."); + +GMOCK_DEFINE_int32_(default_mock_behavior, 1, + "Controls the default behavior of mocks." + " Valid values:\n" + " 0 - by default, mocks act as NiceMocks.\n" + " 1 - by default, mocks act as NaggyMocks.\n" + " 2 - by default, mocks act as StrictMocks."); + +namespace internal { + +// Parses a string as a command line flag. The string should have the +// format "--gmock_flag=value". When def_optional is true, the +// "=value" part can be omitted. +// +// Returns the value of the flag, or NULL if the parsing failed. +static const char* ParseGoogleMockFlagValue(const char* str, + const char* flag, + bool def_optional) { + // str and flag must not be NULL. + if (str == nullptr || flag == nullptr) return nullptr; + + // The flag must start with "--gmock_". + const std::string flag_str = std::string("--gmock_") + flag; + const size_t flag_len = flag_str.length(); + if (strncmp(str, flag_str.c_str(), flag_len) != 0) return nullptr; + + // Skips the flag name. + const char* flag_end = str + flag_len; + + // When def_optional is true, it's OK to not have a "=value" part. + if (def_optional && (flag_end[0] == '\0')) { + return flag_end; + } + + // If def_optional is true and there are more characters after the + // flag name, or if def_optional is false, there must be a '=' after + // the flag name. + if (flag_end[0] != '=') return nullptr; + + // Returns the string after "=". + return flag_end + 1; +} + +// Parses a string for a Google Mock bool flag, in the form of +// "--gmock_flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +static bool ParseGoogleMockBoolFlag(const char* str, const char* flag, + bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseGoogleMockFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Converts the string value to a bool. + *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F'); + return true; +} + +// Parses a string for a Google Mock string flag, in the form of +// "--gmock_flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +template <typename String> +static bool ParseGoogleMockStringFlag(const char* str, const char* flag, + String* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseGoogleMockFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + *value = value_str; + return true; +} + +static bool ParseGoogleMockIntFlag(const char* str, const char* flag, + int32_t* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseGoogleMockFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + return ParseInt32(Message() << "The value of flag --" << flag, + value_str, value); +} + +// The internal implementation of InitGoogleMock(). +// +// The type parameter CharType can be instantiated to either char or +// wchar_t. +template <typename CharType> +void InitGoogleMockImpl(int* argc, CharType** argv) { + // Makes sure Google Test is initialized. InitGoogleTest() is + // idempotent, so it's fine if the user has already called it. + InitGoogleTest(argc, argv); + if (*argc <= 0) return; + + for (int i = 1; i != *argc; i++) { + const std::string arg_string = StreamableToString(argv[i]); + const char* const arg = arg_string.c_str(); + + // Do we see a Google Mock flag? + if (ParseGoogleMockBoolFlag(arg, "catch_leaked_mocks", + &GMOCK_FLAG(catch_leaked_mocks)) || + ParseGoogleMockStringFlag(arg, "verbose", &GMOCK_FLAG(verbose)) || + ParseGoogleMockIntFlag(arg, "default_mock_behavior", + &GMOCK_FLAG(default_mock_behavior))) { + // Yes. Shift the remainder of the argv list left by one. Note + // that argv has (*argc + 1) elements, the last one always being + // NULL. The following loop moves the trailing NULL element as + // well. + for (int j = i; j != *argc; j++) { + argv[j] = argv[j + 1]; + } + + // Decrements the argument count. + (*argc)--; + + // We also need to decrement the iterator as we just removed + // an element. + i--; + } + } +} + +} // namespace internal + +// Initializes Google Mock. This must be called before running the +// tests. In particular, it parses a command line for the flags that +// Google Mock recognizes. Whenever a Google Mock flag is seen, it is +// removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Mock flag variables are +// updated. +// +// Since Google Test is needed for Google Mock to work, this function +// also initializes Google Test and parses its flags, if that hasn't +// been done. +GTEST_API_ void InitGoogleMock(int* argc, char** argv) { + internal::InitGoogleMockImpl(argc, argv); +} + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +GTEST_API_ void InitGoogleMock(int* argc, wchar_t** argv) { + internal::InitGoogleMockImpl(argc, argv); +} + +// This overloaded version can be used on Arduino/embedded platforms where +// there is no argc/argv. +GTEST_API_ void InitGoogleMock() { + // Since Arduino doesn't have a command line, fake out the argc/argv arguments + int argc = 1; + const auto arg0 = "dummy"; + char* argv0 = const_cast<char*>(arg0); + char** argv = &argv0; + + internal::InitGoogleMockImpl(&argc, argv); +} + +} // namespace testing diff --git a/libs/fmt/test/gtest/gmock/gmock.h b/libs/fmt/test/gtest/gmock/gmock.h new file mode 100644 index 0000000000000000000000000000000000000000..d121f7df2b7b470fc9f8490ecf0985bbe0ebf7d7 --- /dev/null +++ b/libs/fmt/test/gtest/gmock/gmock.h @@ -0,0 +1,11645 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This is the main header file a user should include. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_H_ + +// This file implements the following syntax: +// +// ON_CALL(mock_object, Method(...)) +// .With(...) ? +// .WillByDefault(...); +// +// where With() is optional and WillByDefault() must appear exactly +// once. +// +// EXPECT_CALL(mock_object, Method(...)) +// .With(...) ? +// .Times(...) ? +// .InSequence(...) * +// .WillOnce(...) * +// .WillRepeatedly(...) ? +// .RetiresOnSaturation() ? ; +// +// where all clauses are optional and WillOnce() can be repeated. + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// The ACTION* family of macros can be used in a namespace scope to +// define custom actions easily. The syntax: +// +// ACTION(name) { statements; } +// +// will define an action with the given name that executes the +// statements. The value returned by the statements will be used as +// the return value of the action. Inside the statements, you can +// refer to the K-th (0-based) argument of the mock function by +// 'argK', and refer to its type by 'argK_type'. For example: +// +// ACTION(IncrementArg1) { +// arg1_type temp = arg1; +// return ++(*temp); +// } +// +// allows you to write +// +// ...WillOnce(IncrementArg1()); +// +// You can also refer to the entire argument tuple and its type by +// 'args' and 'args_type', and refer to the mock function type and its +// return type by 'function_type' and 'return_type'. +// +// Note that you don't need to specify the types of the mock function +// arguments. However rest assured that your code is still type-safe: +// you'll get a compiler error if *arg1 doesn't support the ++ +// operator, or if the type of ++(*arg1) isn't compatible with the +// mock function's return type, for example. +// +// Sometimes you'll want to parameterize the action. For that you can use +// another macro: +// +// ACTION_P(name, param_name) { statements; } +// +// For example: +// +// ACTION_P(Add, n) { return arg0 + n; } +// +// will allow you to write: +// +// ...WillOnce(Add(5)); +// +// Note that you don't need to provide the type of the parameter +// either. If you need to reference the type of a parameter named +// 'foo', you can write 'foo_type'. For example, in the body of +// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type +// of 'n'. +// +// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P10 to support +// multi-parameter actions. +// +// For the purpose of typing, you can view +// +// ACTION_Pk(Foo, p1, ..., pk) { ... } +// +// as shorthand for +// +// template <typename p1_type, ..., typename pk_type> +// FooActionPk<p1_type, ..., pk_type> Foo(p1_type p1, ..., pk_type pk) { ... } +// +// In particular, you can provide the template type arguments +// explicitly when invoking Foo(), as in Foo<long, bool>(5, false); +// although usually you can rely on the compiler to infer the types +// for you automatically. You can assign the result of expression +// Foo(p1, ..., pk) to a variable of type FooActionPk<p1_type, ..., +// pk_type>. This can be useful when composing actions. +// +// You can also overload actions with different numbers of parameters: +// +// ACTION_P(Plus, a) { ... } +// ACTION_P2(Plus, a, b) { ... } +// +// While it's tempting to always use the ACTION* macros when defining +// a new action, you should also consider implementing ActionInterface +// or using MakePolymorphicAction() instead, especially if you need to +// use the action a lot. While these approaches require more work, +// they give you more control on the types of the mock function +// arguments and the action parameters, which in general leads to +// better compiler error messages that pay off in the long run. They +// also allow overloading actions based on parameter types (as opposed +// to just based on the number of parameters). +// +// CAVEAT: +// +// ACTION*() can only be used in a namespace scope as templates cannot be +// declared inside of a local class. +// Users can, however, define any local functors (e.g. a lambda) that +// can be used as actions. +// +// MORE INFORMATION: +// +// To learn more about using these macros, please search for 'ACTION' on +// https://github.com/google/googletest/blob/master/docs/gmock_cook_book.md + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ + +#ifndef _WIN32_WCE +# include <errno.h> +#endif + +#include <algorithm> +#include <functional> +#include <memory> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This file defines some utilities useful for implementing Google +// Mock. They are subject to change without notice, so please DO NOT +// USE THEM IN USER CODE. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_ + +#include <stdio.h> +#include <ostream> // NOLINT +#include <string> +#include <type_traits> +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// Low-level types and utilities for porting Google Mock to various +// platforms. All macros ending with _ and symbols defined in an +// internal namespace are subject to change without notice. Code +// outside Google Mock MUST NOT USE THEM DIRECTLY. Macros that don't +// end with _ are part of Google Mock's public API and can be used by +// code outside Google Mock. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_ + +#include <assert.h> +#include <stdlib.h> +#include <cstdint> +#include <iostream> + +// Most of the utilities needed for porting Google Mock are also +// required for Google Test and are defined in gtest-port.h. +// +// Note to maintainers: to reduce code duplication, prefer adding +// portability utilities to Google Test's gtest-port.h instead of +// here, as Google Mock depends on Google Test. Only add a utility +// here if it's truly specific to Google Mock. + +#include "gtest/gtest.h" +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Injection point for custom user configurations. See README for details +// +// ** Custom implementation starts here ** + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_ + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_ + +// For MS Visual C++, check the compiler version. At least VS 2015 is +// required to compile Google Mock. +#if defined(_MSC_VER) && _MSC_VER < 1900 +# error "At least Visual C++ 2015 (14.0) is required to compile Google Mock." +#endif + +// Macro for referencing flags. This is public as we want the user to +// use this syntax to reference Google Mock flags. +#define GMOCK_FLAG(name) FLAGS_gmock_##name + +#if !defined(GMOCK_DECLARE_bool_) + +// Macros for declaring flags. +# define GMOCK_DECLARE_bool_(name) extern GTEST_API_ bool GMOCK_FLAG(name) +# define GMOCK_DECLARE_int32_(name) extern GTEST_API_ int32_t GMOCK_FLAG(name) +# define GMOCK_DECLARE_string_(name) \ + extern GTEST_API_ ::std::string GMOCK_FLAG(name) + +// Macros for defining flags. +# define GMOCK_DEFINE_bool_(name, default_val, doc) \ + GTEST_API_ bool GMOCK_FLAG(name) = (default_val) +# define GMOCK_DEFINE_int32_(name, default_val, doc) \ + GTEST_API_ int32_t GMOCK_FLAG(name) = (default_val) +# define GMOCK_DEFINE_string_(name, default_val, doc) \ + GTEST_API_ ::std::string GMOCK_FLAG(name) = (default_val) + +#endif // !defined(GMOCK_DECLARE_bool_) + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_ + +namespace testing { + +template <typename> +class Matcher; + +namespace internal { + +// Silence MSVC C4100 (unreferenced formal parameter) and +// C4805('==': unsafe mix of type 'const int' and type 'const bool') +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4100) +# pragma warning(disable:4805) +#endif + +// Joins a vector of strings as if they are fields of a tuple; returns +// the joined string. +GTEST_API_ std::string JoinAsTuple(const Strings& fields); + +// Converts an identifier name to a space-separated list of lower-case +// words. Each maximum substring of the form [A-Za-z][a-z]*|\d+ is +// treated as one word. For example, both "FooBar123" and +// "foo_bar_123" are converted to "foo bar 123". +GTEST_API_ std::string ConvertIdentifierNameToWords(const char* id_name); + +// GetRawPointer(p) returns the raw pointer underlying p when p is a +// smart pointer, or returns p itself when p is already a raw pointer. +// The following default implementation is for the smart pointer case. +template <typename Pointer> +inline const typename Pointer::element_type* GetRawPointer(const Pointer& p) { + return p.get(); +} +// This overloaded version is for the raw pointer case. +template <typename Element> +inline Element* GetRawPointer(Element* p) { return p; } + +// MSVC treats wchar_t as a native type usually, but treats it as the +// same as unsigned short when the compiler option /Zc:wchar_t- is +// specified. It defines _NATIVE_WCHAR_T_DEFINED symbol when wchar_t +// is a native type. +#if defined(_MSC_VER) && !defined(_NATIVE_WCHAR_T_DEFINED) +// wchar_t is a typedef. +#else +# define GMOCK_WCHAR_T_IS_NATIVE_ 1 +#endif + +// In what follows, we use the term "kind" to indicate whether a type +// is bool, an integer type (excluding bool), a floating-point type, +// or none of them. This categorization is useful for determining +// when a matcher argument type can be safely converted to another +// type in the implementation of SafeMatcherCast. +enum TypeKind { + kBool, kInteger, kFloatingPoint, kOther +}; + +// KindOf<T>::value is the kind of type T. +template <typename T> struct KindOf { + enum { value = kOther }; // The default kind. +}; + +// This macro declares that the kind of 'type' is 'kind'. +#define GMOCK_DECLARE_KIND_(type, kind) \ + template <> struct KindOf<type> { enum { value = kind }; } + +GMOCK_DECLARE_KIND_(bool, kBool); + +// All standard integer types. +GMOCK_DECLARE_KIND_(char, kInteger); +GMOCK_DECLARE_KIND_(signed char, kInteger); +GMOCK_DECLARE_KIND_(unsigned char, kInteger); +GMOCK_DECLARE_KIND_(short, kInteger); // NOLINT +GMOCK_DECLARE_KIND_(unsigned short, kInteger); // NOLINT +GMOCK_DECLARE_KIND_(int, kInteger); +GMOCK_DECLARE_KIND_(unsigned int, kInteger); +GMOCK_DECLARE_KIND_(long, kInteger); // NOLINT +GMOCK_DECLARE_KIND_(unsigned long, kInteger); // NOLINT +GMOCK_DECLARE_KIND_(long long, kInteger); // NOLINT +GMOCK_DECLARE_KIND_(unsigned long long, kInteger); // NOLINT + +#if GMOCK_WCHAR_T_IS_NATIVE_ +GMOCK_DECLARE_KIND_(wchar_t, kInteger); +#endif + +// All standard floating-point types. +GMOCK_DECLARE_KIND_(float, kFloatingPoint); +GMOCK_DECLARE_KIND_(double, kFloatingPoint); +GMOCK_DECLARE_KIND_(long double, kFloatingPoint); + +#undef GMOCK_DECLARE_KIND_ + +// Evaluates to the kind of 'type'. +#define GMOCK_KIND_OF_(type) \ + static_cast< ::testing::internal::TypeKind>( \ + ::testing::internal::KindOf<type>::value) + +// LosslessArithmeticConvertibleImpl<kFromKind, From, kToKind, To>::value +// is true if and only if arithmetic type From can be losslessly converted to +// arithmetic type To. +// +// It's the user's responsibility to ensure that both From and To are +// raw (i.e. has no CV modifier, is not a pointer, and is not a +// reference) built-in arithmetic types, kFromKind is the kind of +// From, and kToKind is the kind of To; the value is +// implementation-defined when the above pre-condition is violated. +template <TypeKind kFromKind, typename From, TypeKind kToKind, typename To> +using LosslessArithmeticConvertibleImpl = std::integral_constant< + bool, + // clang-format off + // Converting from bool is always lossless + (kFromKind == kBool) ? true + // Converting between any other type kinds will be lossy if the type + // kinds are not the same. + : (kFromKind != kToKind) ? false + : (kFromKind == kInteger && + // Converting between integers of different widths is allowed so long + // as the conversion does not go from signed to unsigned. + (((sizeof(From) < sizeof(To)) && + !(std::is_signed<From>::value && !std::is_signed<To>::value)) || + // Converting between integers of the same width only requires the + // two types to have the same signedness. + ((sizeof(From) == sizeof(To)) && + (std::is_signed<From>::value == std::is_signed<To>::value))) + ) ? true + // Floating point conversions are lossless if and only if `To` is at least + // as wide as `From`. + : (kFromKind == kFloatingPoint && (sizeof(From) <= sizeof(To))) ? true + : false + // clang-format on + >; + +// LosslessArithmeticConvertible<From, To>::value is true if and only if +// arithmetic type From can be losslessly converted to arithmetic type To. +// +// It's the user's responsibility to ensure that both From and To are +// raw (i.e. has no CV modifier, is not a pointer, and is not a +// reference) built-in arithmetic types; the value is +// implementation-defined when the above pre-condition is violated. +template <typename From, typename To> +using LosslessArithmeticConvertible = + LosslessArithmeticConvertibleImpl<GMOCK_KIND_OF_(From), From, + GMOCK_KIND_OF_(To), To>; + +// This interface knows how to report a Google Mock failure (either +// non-fatal or fatal). +class FailureReporterInterface { + public: + // The type of a failure (either non-fatal or fatal). + enum FailureType { + kNonfatal, kFatal + }; + + virtual ~FailureReporterInterface() {} + + // Reports a failure that occurred at the given source file location. + virtual void ReportFailure(FailureType type, const char* file, int line, + const std::string& message) = 0; +}; + +// Returns the failure reporter used by Google Mock. +GTEST_API_ FailureReporterInterface* GetFailureReporter(); + +// Asserts that condition is true; aborts the process with the given +// message if condition is false. We cannot use LOG(FATAL) or CHECK() +// as Google Mock might be used to mock the log sink itself. We +// inline this function to prevent it from showing up in the stack +// trace. +inline void Assert(bool condition, const char* file, int line, + const std::string& msg) { + if (!condition) { + GetFailureReporter()->ReportFailure(FailureReporterInterface::kFatal, + file, line, msg); + } +} +inline void Assert(bool condition, const char* file, int line) { + Assert(condition, file, line, "Assertion failed."); +} + +// Verifies that condition is true; generates a non-fatal failure if +// condition is false. +inline void Expect(bool condition, const char* file, int line, + const std::string& msg) { + if (!condition) { + GetFailureReporter()->ReportFailure(FailureReporterInterface::kNonfatal, + file, line, msg); + } +} +inline void Expect(bool condition, const char* file, int line) { + Expect(condition, file, line, "Expectation failed."); +} + +// Severity level of a log. +enum LogSeverity { + kInfo = 0, + kWarning = 1 +}; + +// Valid values for the --gmock_verbose flag. + +// All logs (informational and warnings) are printed. +const char kInfoVerbosity[] = "info"; +// Only warnings are printed. +const char kWarningVerbosity[] = "warning"; +// No logs are printed. +const char kErrorVerbosity[] = "error"; + +// Returns true if and only if a log with the given severity is visible +// according to the --gmock_verbose flag. +GTEST_API_ bool LogIsVisible(LogSeverity severity); + +// Prints the given message to stdout if and only if 'severity' >= the level +// specified by the --gmock_verbose flag. If stack_frames_to_skip >= +// 0, also prints the stack trace excluding the top +// stack_frames_to_skip frames. In opt mode, any positive +// stack_frames_to_skip is treated as 0, since we don't know which +// function calls will be inlined by the compiler and need to be +// conservative. +GTEST_API_ void Log(LogSeverity severity, const std::string& message, + int stack_frames_to_skip); + +// A marker class that is used to resolve parameterless expectations to the +// correct overload. This must not be instantiable, to prevent client code from +// accidentally resolving to the overload; for example: +// +// ON_CALL(mock, Method({}, nullptr))... +// +class WithoutMatchers { + private: + WithoutMatchers() {} + friend GTEST_API_ WithoutMatchers GetWithoutMatchers(); +}; + +// Internal use only: access the singleton instance of WithoutMatchers. +GTEST_API_ WithoutMatchers GetWithoutMatchers(); + +// Disable MSVC warnings for infinite recursion, since in this case the +// the recursion is unreachable. +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4717) +#endif + +// Invalid<T>() is usable as an expression of type T, but will terminate +// the program with an assertion failure if actually run. This is useful +// when a value of type T is needed for compilation, but the statement +// will not really be executed (or we don't care if the statement +// crashes). +template <typename T> +inline T Invalid() { + Assert(false, "", -1, "Internal error: attempt to return invalid value"); + // This statement is unreachable, and would never terminate even if it + // could be reached. It is provided only to placate compiler warnings + // about missing return statements. + return Invalid<T>(); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +// Given a raw type (i.e. having no top-level reference or const +// modifier) RawContainer that's either an STL-style container or a +// native array, class StlContainerView<RawContainer> has the +// following members: +// +// - type is a type that provides an STL-style container view to +// (i.e. implements the STL container concept for) RawContainer; +// - const_reference is a type that provides a reference to a const +// RawContainer; +// - ConstReference(raw_container) returns a const reference to an STL-style +// container view to raw_container, which is a RawContainer. +// - Copy(raw_container) returns an STL-style container view of a +// copy of raw_container, which is a RawContainer. +// +// This generic version is used when RawContainer itself is already an +// STL-style container. +template <class RawContainer> +class StlContainerView { + public: + typedef RawContainer type; + typedef const type& const_reference; + + static const_reference ConstReference(const RawContainer& container) { + static_assert(!std::is_const<RawContainer>::value, + "RawContainer type must not be const"); + return container; + } + static type Copy(const RawContainer& container) { return container; } +}; + +// This specialization is used when RawContainer is a native array type. +template <typename Element, size_t N> +class StlContainerView<Element[N]> { + public: + typedef typename std::remove_const<Element>::type RawElement; + typedef internal::NativeArray<RawElement> type; + // NativeArray<T> can represent a native array either by value or by + // reference (selected by a constructor argument), so 'const type' + // can be used to reference a const native array. We cannot + // 'typedef const type& const_reference' here, as that would mean + // ConstReference() has to return a reference to a local variable. + typedef const type const_reference; + + static const_reference ConstReference(const Element (&array)[N]) { + static_assert(std::is_same<Element, RawElement>::value, + "Element type must not be const"); + return type(array, N, RelationToSourceReference()); + } + static type Copy(const Element (&array)[N]) { + return type(array, N, RelationToSourceCopy()); + } +}; + +// This specialization is used when RawContainer is a native array +// represented as a (pointer, size) tuple. +template <typename ElementPointer, typename Size> +class StlContainerView< ::std::tuple<ElementPointer, Size> > { + public: + typedef typename std::remove_const< + typename std::pointer_traits<ElementPointer>::element_type>::type + RawElement; + typedef internal::NativeArray<RawElement> type; + typedef const type const_reference; + + static const_reference ConstReference( + const ::std::tuple<ElementPointer, Size>& array) { + return type(std::get<0>(array), std::get<1>(array), + RelationToSourceReference()); + } + static type Copy(const ::std::tuple<ElementPointer, Size>& array) { + return type(std::get<0>(array), std::get<1>(array), RelationToSourceCopy()); + } +}; + +// The following specialization prevents the user from instantiating +// StlContainer with a reference type. +template <typename T> class StlContainerView<T&>; + +// A type transform to remove constness from the first part of a pair. +// Pairs like that are used as the value_type of associative containers, +// and this transform produces a similar but assignable pair. +template <typename T> +struct RemoveConstFromKey { + typedef T type; +}; + +// Partially specialized to remove constness from std::pair<const K, V>. +template <typename K, typename V> +struct RemoveConstFromKey<std::pair<const K, V> > { + typedef std::pair<K, V> type; +}; + +// Emit an assertion failure due to incorrect DoDefault() usage. Out-of-lined to +// reduce code size. +GTEST_API_ void IllegalDoDefault(const char* file, int line); + +template <typename F, typename Tuple, size_t... Idx> +auto ApplyImpl(F&& f, Tuple&& args, IndexSequence<Idx...>) -> decltype( + std::forward<F>(f)(std::get<Idx>(std::forward<Tuple>(args))...)) { + return std::forward<F>(f)(std::get<Idx>(std::forward<Tuple>(args))...); +} + +// Apply the function to a tuple of arguments. +template <typename F, typename Tuple> +auto Apply(F&& f, Tuple&& args) -> decltype( + ApplyImpl(std::forward<F>(f), std::forward<Tuple>(args), + MakeIndexSequence<std::tuple_size< + typename std::remove_reference<Tuple>::type>::value>())) { + return ApplyImpl(std::forward<F>(f), std::forward<Tuple>(args), + MakeIndexSequence<std::tuple_size< + typename std::remove_reference<Tuple>::type>::value>()); +} + +// Template struct Function<F>, where F must be a function type, contains +// the following typedefs: +// +// Result: the function's return type. +// Arg<N>: the type of the N-th argument, where N starts with 0. +// ArgumentTuple: the tuple type consisting of all parameters of F. +// ArgumentMatcherTuple: the tuple type consisting of Matchers for all +// parameters of F. +// MakeResultVoid: the function type obtained by substituting void +// for the return type of F. +// MakeResultIgnoredValue: +// the function type obtained by substituting Something +// for the return type of F. +template <typename T> +struct Function; + +template <typename R, typename... Args> +struct Function<R(Args...)> { + using Result = R; + static constexpr size_t ArgumentCount = sizeof...(Args); + template <size_t I> + using Arg = ElemFromList<I, Args...>; + using ArgumentTuple = std::tuple<Args...>; + using ArgumentMatcherTuple = std::tuple<Matcher<Args>...>; + using MakeResultVoid = void(Args...); + using MakeResultIgnoredValue = IgnoredValue(Args...); +}; + +template <typename R, typename... Args> +constexpr size_t Function<R(Args...)>::ArgumentCount; + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +} // namespace internal +} // namespace testing + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_ +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PP_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PP_H_ + +// Expands and concatenates the arguments. Constructed macros reevaluate. +#define GMOCK_PP_CAT(_1, _2) GMOCK_PP_INTERNAL_CAT(_1, _2) + +// Expands and stringifies the only argument. +#define GMOCK_PP_STRINGIZE(...) GMOCK_PP_INTERNAL_STRINGIZE(__VA_ARGS__) + +// Returns empty. Given a variadic number of arguments. +#define GMOCK_PP_EMPTY(...) + +// Returns a comma. Given a variadic number of arguments. +#define GMOCK_PP_COMMA(...) , + +// Returns the only argument. +#define GMOCK_PP_IDENTITY(_1) _1 + +// Evaluates to the number of arguments after expansion. +// +// #define PAIR x, y +// +// GMOCK_PP_NARG() => 1 +// GMOCK_PP_NARG(x) => 1 +// GMOCK_PP_NARG(x, y) => 2 +// GMOCK_PP_NARG(PAIR) => 2 +// +// Requires: the number of arguments after expansion is at most 15. +#define GMOCK_PP_NARG(...) \ + GMOCK_PP_INTERNAL_16TH( \ + (__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)) + +// Returns 1 if the expansion of arguments has an unprotected comma. Otherwise +// returns 0. Requires no more than 15 unprotected commas. +#define GMOCK_PP_HAS_COMMA(...) \ + GMOCK_PP_INTERNAL_16TH( \ + (__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)) + +// Returns the first argument. +#define GMOCK_PP_HEAD(...) GMOCK_PP_INTERNAL_HEAD((__VA_ARGS__, unusedArg)) + +// Returns the tail. A variadic list of all arguments minus the first. Requires +// at least one argument. +#define GMOCK_PP_TAIL(...) GMOCK_PP_INTERNAL_TAIL((__VA_ARGS__)) + +// Calls CAT(_Macro, NARG(__VA_ARGS__))(__VA_ARGS__) +#define GMOCK_PP_VARIADIC_CALL(_Macro, ...) \ + GMOCK_PP_IDENTITY( \ + GMOCK_PP_CAT(_Macro, GMOCK_PP_NARG(__VA_ARGS__))(__VA_ARGS__)) + +// If the arguments after expansion have no tokens, evaluates to `1`. Otherwise +// evaluates to `0`. +// +// Requires: * the number of arguments after expansion is at most 15. +// * If the argument is a macro, it must be able to be called with one +// argument. +// +// Implementation details: +// +// There is one case when it generates a compile error: if the argument is macro +// that cannot be called with one argument. +// +// #define M(a, b) // it doesn't matter what it expands to +// +// // Expected: expands to `0`. +// // Actual: compile error. +// GMOCK_PP_IS_EMPTY(M) +// +// There are 4 cases tested: +// +// * __VA_ARGS__ possible expansion has no unparen'd commas. Expected 0. +// * __VA_ARGS__ possible expansion is not enclosed in parenthesis. Expected 0. +// * __VA_ARGS__ possible expansion is not a macro that ()-evaluates to a comma. +// Expected 0 +// * __VA_ARGS__ is empty, or has unparen'd commas, or is enclosed in +// parenthesis, or is a macro that ()-evaluates to comma. Expected 1. +// +// We trigger detection on '0001', i.e. on empty. +#define GMOCK_PP_IS_EMPTY(...) \ + GMOCK_PP_INTERNAL_IS_EMPTY(GMOCK_PP_HAS_COMMA(__VA_ARGS__), \ + GMOCK_PP_HAS_COMMA(GMOCK_PP_COMMA __VA_ARGS__), \ + GMOCK_PP_HAS_COMMA(__VA_ARGS__()), \ + GMOCK_PP_HAS_COMMA(GMOCK_PP_COMMA __VA_ARGS__())) + +// Evaluates to _Then if _Cond is 1 and _Else if _Cond is 0. +#define GMOCK_PP_IF(_Cond, _Then, _Else) \ + GMOCK_PP_CAT(GMOCK_PP_INTERNAL_IF_, _Cond)(_Then, _Else) + +// Similar to GMOCK_PP_IF but takes _Then and _Else in parentheses. +// +// GMOCK_PP_GENERIC_IF(1, (a, b, c), (d, e, f)) => a, b, c +// GMOCK_PP_GENERIC_IF(0, (a, b, c), (d, e, f)) => d, e, f +// +#define GMOCK_PP_GENERIC_IF(_Cond, _Then, _Else) \ + GMOCK_PP_REMOVE_PARENS(GMOCK_PP_IF(_Cond, _Then, _Else)) + +// Evaluates to the number of arguments after expansion. Identifies 'empty' as +// 0. +// +// #define PAIR x, y +// +// GMOCK_PP_NARG0() => 0 +// GMOCK_PP_NARG0(x) => 1 +// GMOCK_PP_NARG0(x, y) => 2 +// GMOCK_PP_NARG0(PAIR) => 2 +// +// Requires: * the number of arguments after expansion is at most 15. +// * If the argument is a macro, it must be able to be called with one +// argument. +#define GMOCK_PP_NARG0(...) \ + GMOCK_PP_IF(GMOCK_PP_IS_EMPTY(__VA_ARGS__), 0, GMOCK_PP_NARG(__VA_ARGS__)) + +// Expands to 1 if the first argument starts with something in parentheses, +// otherwise to 0. +#define GMOCK_PP_IS_BEGIN_PARENS(...) \ + GMOCK_PP_HEAD(GMOCK_PP_CAT(GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_, \ + GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C __VA_ARGS__)) + +// Expands to 1 is there is only one argument and it is enclosed in parentheses. +#define GMOCK_PP_IS_ENCLOSED_PARENS(...) \ + GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(__VA_ARGS__), \ + GMOCK_PP_IS_EMPTY(GMOCK_PP_EMPTY __VA_ARGS__), 0) + +// Remove the parens, requires GMOCK_PP_IS_ENCLOSED_PARENS(args) => 1. +#define GMOCK_PP_REMOVE_PARENS(...) GMOCK_PP_INTERNAL_REMOVE_PARENS __VA_ARGS__ + +// Expands to _Macro(0, _Data, e1) _Macro(1, _Data, e2) ... _Macro(K -1, _Data, +// eK) as many of GMOCK_INTERNAL_NARG0 _Tuple. +// Requires: * |_Macro| can be called with 3 arguments. +// * |_Tuple| expansion has no more than 15 elements. +#define GMOCK_PP_FOR_EACH(_Macro, _Data, _Tuple) \ + GMOCK_PP_CAT(GMOCK_PP_INTERNAL_FOR_EACH_IMPL_, GMOCK_PP_NARG0 _Tuple) \ + (0, _Macro, _Data, _Tuple) + +// Expands to _Macro(0, _Data, ) _Macro(1, _Data, ) ... _Macro(K - 1, _Data, ) +// Empty if _K = 0. +// Requires: * |_Macro| can be called with 3 arguments. +// * |_K| literal between 0 and 15 +#define GMOCK_PP_REPEAT(_Macro, _Data, _N) \ + GMOCK_PP_CAT(GMOCK_PP_INTERNAL_FOR_EACH_IMPL_, _N) \ + (0, _Macro, _Data, GMOCK_PP_INTENRAL_EMPTY_TUPLE) + +// Increments the argument, requires the argument to be between 0 and 15. +#define GMOCK_PP_INC(_i) GMOCK_PP_CAT(GMOCK_PP_INTERNAL_INC_, _i) + +// Returns comma if _i != 0. Requires _i to be between 0 and 15. +#define GMOCK_PP_COMMA_IF(_i) GMOCK_PP_CAT(GMOCK_PP_INTERNAL_COMMA_IF_, _i) + +// Internal details follow. Do not use any of these symbols outside of this +// file or we will break your code. +#define GMOCK_PP_INTENRAL_EMPTY_TUPLE (, , , , , , , , , , , , , , , ) +#define GMOCK_PP_INTERNAL_CAT(_1, _2) _1##_2 +#define GMOCK_PP_INTERNAL_STRINGIZE(...) #__VA_ARGS__ +#define GMOCK_PP_INTERNAL_CAT_5(_1, _2, _3, _4, _5) _1##_2##_3##_4##_5 +#define GMOCK_PP_INTERNAL_IS_EMPTY(_1, _2, _3, _4) \ + GMOCK_PP_HAS_COMMA(GMOCK_PP_INTERNAL_CAT_5(GMOCK_PP_INTERNAL_IS_EMPTY_CASE_, \ + _1, _2, _3, _4)) +#define GMOCK_PP_INTERNAL_IS_EMPTY_CASE_0001 , +#define GMOCK_PP_INTERNAL_IF_1(_Then, _Else) _Then +#define GMOCK_PP_INTERNAL_IF_0(_Then, _Else) _Else + +// Because of MSVC treating a token with a comma in it as a single token when +// passed to another macro, we need to force it to evaluate it as multiple +// tokens. We do that by using a "IDENTITY(MACRO PARENTHESIZED_ARGS)" macro. We +// define one per possible macro that relies on this behavior. Note "_Args" must +// be parenthesized. +#define GMOCK_PP_INTERNAL_INTERNAL_16TH(_1, _2, _3, _4, _5, _6, _7, _8, _9, \ + _10, _11, _12, _13, _14, _15, _16, \ + ...) \ + _16 +#define GMOCK_PP_INTERNAL_16TH(_Args) \ + GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_16TH _Args) +#define GMOCK_PP_INTERNAL_INTERNAL_HEAD(_1, ...) _1 +#define GMOCK_PP_INTERNAL_HEAD(_Args) \ + GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_HEAD _Args) +#define GMOCK_PP_INTERNAL_INTERNAL_TAIL(_1, ...) __VA_ARGS__ +#define GMOCK_PP_INTERNAL_TAIL(_Args) \ + GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_TAIL _Args) + +#define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C(...) 1 _ +#define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_1 1, +#define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C \ + 0, +#define GMOCK_PP_INTERNAL_REMOVE_PARENS(...) __VA_ARGS__ +#define GMOCK_PP_INTERNAL_INC_0 1 +#define GMOCK_PP_INTERNAL_INC_1 2 +#define GMOCK_PP_INTERNAL_INC_2 3 +#define GMOCK_PP_INTERNAL_INC_3 4 +#define GMOCK_PP_INTERNAL_INC_4 5 +#define GMOCK_PP_INTERNAL_INC_5 6 +#define GMOCK_PP_INTERNAL_INC_6 7 +#define GMOCK_PP_INTERNAL_INC_7 8 +#define GMOCK_PP_INTERNAL_INC_8 9 +#define GMOCK_PP_INTERNAL_INC_9 10 +#define GMOCK_PP_INTERNAL_INC_10 11 +#define GMOCK_PP_INTERNAL_INC_11 12 +#define GMOCK_PP_INTERNAL_INC_12 13 +#define GMOCK_PP_INTERNAL_INC_13 14 +#define GMOCK_PP_INTERNAL_INC_14 15 +#define GMOCK_PP_INTERNAL_INC_15 16 +#define GMOCK_PP_INTERNAL_COMMA_IF_0 +#define GMOCK_PP_INTERNAL_COMMA_IF_1 , +#define GMOCK_PP_INTERNAL_COMMA_IF_2 , +#define GMOCK_PP_INTERNAL_COMMA_IF_3 , +#define GMOCK_PP_INTERNAL_COMMA_IF_4 , +#define GMOCK_PP_INTERNAL_COMMA_IF_5 , +#define GMOCK_PP_INTERNAL_COMMA_IF_6 , +#define GMOCK_PP_INTERNAL_COMMA_IF_7 , +#define GMOCK_PP_INTERNAL_COMMA_IF_8 , +#define GMOCK_PP_INTERNAL_COMMA_IF_9 , +#define GMOCK_PP_INTERNAL_COMMA_IF_10 , +#define GMOCK_PP_INTERNAL_COMMA_IF_11 , +#define GMOCK_PP_INTERNAL_COMMA_IF_12 , +#define GMOCK_PP_INTERNAL_COMMA_IF_13 , +#define GMOCK_PP_INTERNAL_COMMA_IF_14 , +#define GMOCK_PP_INTERNAL_COMMA_IF_15 , +#define GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, _element) \ + _Macro(_i, _Data, _element) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_0(_i, _Macro, _Data, _Tuple) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_1(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_2(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_1(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_3(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_2(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_4(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_3(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_5(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_4(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_6(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_5(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_7(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_6(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_8(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_7(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_9(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_8(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_10(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_9(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_11(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_10(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_12(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_11(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_13(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_12(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_14(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_13(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) +#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_15(_i, _Macro, _Data, _Tuple) \ + GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \ + GMOCK_PP_INTERNAL_FOR_EACH_IMPL_14(GMOCK_PP_INC(_i), _Macro, _Data, \ + (GMOCK_PP_TAIL _Tuple)) + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PP_H_ + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4100) +#endif + +namespace testing { + +// To implement an action Foo, define: +// 1. a class FooAction that implements the ActionInterface interface, and +// 2. a factory function that creates an Action object from a +// const FooAction*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Action objects can now be copied like plain values. + +namespace internal { + +// BuiltInDefaultValueGetter<T, true>::Get() returns a +// default-constructed T value. BuiltInDefaultValueGetter<T, +// false>::Get() crashes with an error. +// +// This primary template is used when kDefaultConstructible is true. +template <typename T, bool kDefaultConstructible> +struct BuiltInDefaultValueGetter { + static T Get() { return T(); } +}; +template <typename T> +struct BuiltInDefaultValueGetter<T, false> { + static T Get() { + Assert(false, __FILE__, __LINE__, + "Default action undefined for the function return type."); + return internal::Invalid<T>(); + // The above statement will never be reached, but is required in + // order for this function to compile. + } +}; + +// BuiltInDefaultValue<T>::Get() returns the "built-in" default value +// for type T, which is NULL when T is a raw pointer type, 0 when T is +// a numeric type, false when T is bool, or "" when T is string or +// std::string. In addition, in C++11 and above, it turns a +// default-constructed T value if T is default constructible. For any +// other type T, the built-in default T value is undefined, and the +// function will abort the process. +template <typename T> +class BuiltInDefaultValue { + public: + // This function returns true if and only if type T has a built-in default + // value. + static bool Exists() { + return ::std::is_default_constructible<T>::value; + } + + static T Get() { + return BuiltInDefaultValueGetter< + T, ::std::is_default_constructible<T>::value>::Get(); + } +}; + +// This partial specialization says that we use the same built-in +// default value for T and const T. +template <typename T> +class BuiltInDefaultValue<const T> { + public: + static bool Exists() { return BuiltInDefaultValue<T>::Exists(); } + static T Get() { return BuiltInDefaultValue<T>::Get(); } +}; + +// This partial specialization defines the default values for pointer +// types. +template <typename T> +class BuiltInDefaultValue<T*> { + public: + static bool Exists() { return true; } + static T* Get() { return nullptr; } +}; + +// The following specializations define the default values for +// specific types we care about. +#define GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(type, value) \ + template <> \ + class BuiltInDefaultValue<type> { \ + public: \ + static bool Exists() { return true; } \ + static type Get() { return value; } \ + } + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(void, ); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(::std::string, ""); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(bool, false); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(char, '\0'); + +// There's no need for a default action for signed wchar_t, as that +// type is the same as wchar_t for gcc, and invalid for MSVC. +// +// There's also no need for a default action for unsigned wchar_t, as +// that type is the same as unsigned int for gcc, and invalid for +// MSVC. +#if GMOCK_WCHAR_T_IS_NATIVE_ +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(wchar_t, 0U); // NOLINT +#endif + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned short, 0U); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed short, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned int, 0U); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed int, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long, 0UL); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long, 0L); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long long, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long long, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(float, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(double, 0); + +#undef GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_ + +// Simple two-arg form of std::disjunction. +template <typename P, typename Q> +using disjunction = typename ::std::conditional<P::value, P, Q>::type; + +} // namespace internal + +// When an unexpected function call is encountered, Google Mock will +// let it return a default value if the user has specified one for its +// return type, or if the return type has a built-in default value; +// otherwise Google Mock won't know what value to return and will have +// to abort the process. +// +// The DefaultValue<T> class allows a user to specify the +// default value for a type T that is both copyable and publicly +// destructible (i.e. anything that can be used as a function return +// type). The usage is: +// +// // Sets the default value for type T to be foo. +// DefaultValue<T>::Set(foo); +template <typename T> +class DefaultValue { + public: + // Sets the default value for type T; requires T to be + // copy-constructable and have a public destructor. + static void Set(T x) { + delete producer_; + producer_ = new FixedValueProducer(x); + } + + // Provides a factory function to be called to generate the default value. + // This method can be used even if T is only move-constructible, but it is not + // limited to that case. + typedef T (*FactoryFunction)(); + static void SetFactory(FactoryFunction factory) { + delete producer_; + producer_ = new FactoryValueProducer(factory); + } + + // Unsets the default value for type T. + static void Clear() { + delete producer_; + producer_ = nullptr; + } + + // Returns true if and only if the user has set the default value for type T. + static bool IsSet() { return producer_ != nullptr; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue<T>::Exists(); + } + + // Returns the default value for type T if the user has set one; + // otherwise returns the built-in default value. Requires that Exists() + // is true, which ensures that the return value is well-defined. + static T Get() { + return producer_ == nullptr ? internal::BuiltInDefaultValue<T>::Get() + : producer_->Produce(); + } + + private: + class ValueProducer { + public: + virtual ~ValueProducer() {} + virtual T Produce() = 0; + }; + + class FixedValueProducer : public ValueProducer { + public: + explicit FixedValueProducer(T value) : value_(value) {} + T Produce() override { return value_; } + + private: + const T value_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(FixedValueProducer); + }; + + class FactoryValueProducer : public ValueProducer { + public: + explicit FactoryValueProducer(FactoryFunction factory) + : factory_(factory) {} + T Produce() override { return factory_(); } + + private: + const FactoryFunction factory_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(FactoryValueProducer); + }; + + static ValueProducer* producer_; +}; + +// This partial specialization allows a user to set default values for +// reference types. +template <typename T> +class DefaultValue<T&> { + public: + // Sets the default value for type T&. + static void Set(T& x) { // NOLINT + address_ = &x; + } + + // Unsets the default value for type T&. + static void Clear() { address_ = nullptr; } + + // Returns true if and only if the user has set the default value for type T&. + static bool IsSet() { return address_ != nullptr; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue<T&>::Exists(); + } + + // Returns the default value for type T& if the user has set one; + // otherwise returns the built-in default value if there is one; + // otherwise aborts the process. + static T& Get() { + return address_ == nullptr ? internal::BuiltInDefaultValue<T&>::Get() + : *address_; + } + + private: + static T* address_; +}; + +// This specialization allows DefaultValue<void>::Get() to +// compile. +template <> +class DefaultValue<void> { + public: + static bool Exists() { return true; } + static void Get() {} +}; + +// Points to the user-set default value for type T. +template <typename T> +typename DefaultValue<T>::ValueProducer* DefaultValue<T>::producer_ = nullptr; + +// Points to the user-set default value for type T&. +template <typename T> +T* DefaultValue<T&>::address_ = nullptr; + +// Implement this interface to define an action for function type F. +template <typename F> +class ActionInterface { + public: + typedef typename internal::Function<F>::Result Result; + typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple; + + ActionInterface() {} + virtual ~ActionInterface() {} + + // Performs the action. This method is not const, as in general an + // action can have side effects and be stateful. For example, a + // get-the-next-element-from-the-collection action will need to + // remember the current element. + virtual Result Perform(const ArgumentTuple& args) = 0; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ActionInterface); +}; + +// An Action<F> is a copyable and IMMUTABLE (except by assignment) +// object that represents an action to be taken when a mock function +// of type F is called. The implementation of Action<T> is just a +// std::shared_ptr to const ActionInterface<T>. Don't inherit from Action! +// You can view an object implementing ActionInterface<F> as a +// concrete action (including its current state), and an Action<F> +// object as a handle to it. +template <typename F> +class Action { + // Adapter class to allow constructing Action from a legacy ActionInterface. + // New code should create Actions from functors instead. + struct ActionAdapter { + // Adapter must be copyable to satisfy std::function requirements. + ::std::shared_ptr<ActionInterface<F>> impl_; + + template <typename... Args> + typename internal::Function<F>::Result operator()(Args&&... args) { + return impl_->Perform( + ::std::forward_as_tuple(::std::forward<Args>(args)...)); + } + }; + + template <typename G> + using IsCompatibleFunctor = std::is_constructible<std::function<F>, G>; + + public: + typedef typename internal::Function<F>::Result Result; + typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple; + + // Constructs a null Action. Needed for storing Action objects in + // STL containers. + Action() {} + + // Construct an Action from a specified callable. + // This cannot take std::function directly, because then Action would not be + // directly constructible from lambda (it would require two conversions). + template < + typename G, + typename = typename std::enable_if<internal::disjunction< + IsCompatibleFunctor<G>, std::is_constructible<std::function<Result()>, + G>>::value>::type> + Action(G&& fun) { // NOLINT + Init(::std::forward<G>(fun), IsCompatibleFunctor<G>()); + } + + // Constructs an Action from its implementation. + explicit Action(ActionInterface<F>* impl) + : fun_(ActionAdapter{::std::shared_ptr<ActionInterface<F>>(impl)}) {} + + // This constructor allows us to turn an Action<Func> object into an + // Action<F>, as long as F's arguments can be implicitly converted + // to Func's and Func's return type can be implicitly converted to F's. + template <typename Func> + explicit Action(const Action<Func>& action) : fun_(action.fun_) {} + + // Returns true if and only if this is the DoDefault() action. + bool IsDoDefault() const { return fun_ == nullptr; } + + // Performs the action. Note that this method is const even though + // the corresponding method in ActionInterface is not. The reason + // is that a const Action<F> means that it cannot be re-bound to + // another concrete action, not that the concrete action it binds to + // cannot change state. (Think of the difference between a const + // pointer and a pointer to const.) + Result Perform(ArgumentTuple args) const { + if (IsDoDefault()) { + internal::IllegalDoDefault(__FILE__, __LINE__); + } + return internal::Apply(fun_, ::std::move(args)); + } + + private: + template <typename G> + friend class Action; + + template <typename G> + void Init(G&& g, ::std::true_type) { + fun_ = ::std::forward<G>(g); + } + + template <typename G> + void Init(G&& g, ::std::false_type) { + fun_ = IgnoreArgs<typename ::std::decay<G>::type>{::std::forward<G>(g)}; + } + + template <typename FunctionImpl> + struct IgnoreArgs { + template <typename... Args> + Result operator()(const Args&...) const { + return function_impl(); + } + + FunctionImpl function_impl; + }; + + // fun_ is an empty function if and only if this is the DoDefault() action. + ::std::function<F> fun_; +}; + +// The PolymorphicAction class template makes it easy to implement a +// polymorphic action (i.e. an action that can be used in mock +// functions of than one type, e.g. Return()). +// +// To define a polymorphic action, a user first provides a COPYABLE +// implementation class that has a Perform() method template: +// +// class FooAction { +// public: +// template <typename Result, typename ArgumentTuple> +// Result Perform(const ArgumentTuple& args) const { +// // Processes the arguments and returns a result, using +// // std::get<N>(args) to get the N-th (0-based) argument in the tuple. +// } +// ... +// }; +// +// Then the user creates the polymorphic action using +// MakePolymorphicAction(object) where object has type FooAction. See +// the definition of Return(void) and SetArgumentPointee<N>(value) for +// complete examples. +template <typename Impl> +class PolymorphicAction { + public: + explicit PolymorphicAction(const Impl& impl) : impl_(impl) {} + + template <typename F> + operator Action<F>() const { + return Action<F>(new MonomorphicImpl<F>(impl_)); + } + + private: + template <typename F> + class MonomorphicImpl : public ActionInterface<F> { + public: + typedef typename internal::Function<F>::Result Result; + typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple; + + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + Result Perform(const ArgumentTuple& args) override { + return impl_.template Perform<Result>(args); + } + + private: + Impl impl_; + }; + + Impl impl_; +}; + +// Creates an Action from its implementation and returns it. The +// created Action object owns the implementation. +template <typename F> +Action<F> MakeAction(ActionInterface<F>* impl) { + return Action<F>(impl); +} + +// Creates a polymorphic action from its implementation. This is +// easier to use than the PolymorphicAction<Impl> constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicAction(foo); +// vs +// PolymorphicAction<TypeOfFoo>(foo); +template <typename Impl> +inline PolymorphicAction<Impl> MakePolymorphicAction(const Impl& impl) { + return PolymorphicAction<Impl>(impl); +} + +namespace internal { + +// Helper struct to specialize ReturnAction to execute a move instead of a copy +// on return. Useful for move-only types, but could be used on any type. +template <typename T> +struct ByMoveWrapper { + explicit ByMoveWrapper(T value) : payload(std::move(value)) {} + T payload; +}; + +// Implements the polymorphic Return(x) action, which can be used in +// any function that returns the type of x, regardless of the argument +// types. +// +// Note: The value passed into Return must be converted into +// Function<F>::Result when this action is cast to Action<F> rather than +// when that action is performed. This is important in scenarios like +// +// MOCK_METHOD1(Method, T(U)); +// ... +// { +// Foo foo; +// X x(&foo); +// EXPECT_CALL(mock, Method(_)).WillOnce(Return(x)); +// } +// +// In the example above the variable x holds reference to foo which leaves +// scope and gets destroyed. If copying X just copies a reference to foo, +// that copy will be left with a hanging reference. If conversion to T +// makes a copy of foo, the above code is safe. To support that scenario, we +// need to make sure that the type conversion happens inside the EXPECT_CALL +// statement, and conversion of the result of Return to Action<T(U)> is a +// good place for that. +// +// The real life example of the above scenario happens when an invocation +// of gtl::Container() is passed into Return. +// +template <typename R> +class ReturnAction { + public: + // Constructs a ReturnAction object from the value to be returned. + // 'value' is passed by value instead of by const reference in order + // to allow Return("string literal") to compile. + explicit ReturnAction(R value) : value_(new R(std::move(value))) {} + + // This template type conversion operator allows Return(x) to be + // used in ANY function that returns x's type. + template <typename F> + operator Action<F>() const { // NOLINT + // Assert statement belongs here because this is the best place to verify + // conditions on F. It produces the clearest error messages + // in most compilers. + // Impl really belongs in this scope as a local class but can't + // because MSVC produces duplicate symbols in different translation units + // in this case. Until MS fixes that bug we put Impl into the class scope + // and put the typedef both here (for use in assert statement) and + // in the Impl class. But both definitions must be the same. + typedef typename Function<F>::Result Result; + GTEST_COMPILE_ASSERT_( + !std::is_reference<Result>::value, + use_ReturnRef_instead_of_Return_to_return_a_reference); + static_assert(!std::is_void<Result>::value, + "Can't use Return() on an action expected to return `void`."); + return Action<F>(new Impl<R, F>(value_)); + } + + private: + // Implements the Return(x) action for a particular function type F. + template <typename R_, typename F> + class Impl : public ActionInterface<F> { + public: + typedef typename Function<F>::Result Result; + typedef typename Function<F>::ArgumentTuple ArgumentTuple; + + // The implicit cast is necessary when Result has more than one + // single-argument constructor (e.g. Result is std::vector<int>) and R + // has a type conversion operator template. In that case, value_(value) + // won't compile as the compiler doesn't known which constructor of + // Result to call. ImplicitCast_ forces the compiler to convert R to + // Result without considering explicit constructors, thus resolving the + // ambiguity. value_ is then initialized using its copy constructor. + explicit Impl(const std::shared_ptr<R>& value) + : value_before_cast_(*value), + value_(ImplicitCast_<Result>(value_before_cast_)) {} + + Result Perform(const ArgumentTuple&) override { return value_; } + + private: + GTEST_COMPILE_ASSERT_(!std::is_reference<Result>::value, + Result_cannot_be_a_reference_type); + // We save the value before casting just in case it is being cast to a + // wrapper type. + R value_before_cast_; + Result value_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Impl); + }; + + // Partially specialize for ByMoveWrapper. This version of ReturnAction will + // move its contents instead. + template <typename R_, typename F> + class Impl<ByMoveWrapper<R_>, F> : public ActionInterface<F> { + public: + typedef typename Function<F>::Result Result; + typedef typename Function<F>::ArgumentTuple ArgumentTuple; + + explicit Impl(const std::shared_ptr<R>& wrapper) + : performed_(false), wrapper_(wrapper) {} + + Result Perform(const ArgumentTuple&) override { + GTEST_CHECK_(!performed_) + << "A ByMove() action should only be performed once."; + performed_ = true; + return std::move(wrapper_->payload); + } + + private: + bool performed_; + const std::shared_ptr<R> wrapper_; + }; + + const std::shared_ptr<R> value_; +}; + +// Implements the ReturnNull() action. +class ReturnNullAction { + public: + // Allows ReturnNull() to be used in any pointer-returning function. In C++11 + // this is enforced by returning nullptr, and in non-C++11 by asserting a + // pointer type on compile time. + template <typename Result, typename ArgumentTuple> + static Result Perform(const ArgumentTuple&) { + return nullptr; + } +}; + +// Implements the Return() action. +class ReturnVoidAction { + public: + // Allows Return() to be used in any void-returning function. + template <typename Result, typename ArgumentTuple> + static void Perform(const ArgumentTuple&) { + static_assert(std::is_void<Result>::value, "Result should be void."); + } +}; + +// Implements the polymorphic ReturnRef(x) action, which can be used +// in any function that returns a reference to the type of x, +// regardless of the argument types. +template <typename T> +class ReturnRefAction { + public: + // Constructs a ReturnRefAction object from the reference to be returned. + explicit ReturnRefAction(T& ref) : ref_(ref) {} // NOLINT + + // This template type conversion operator allows ReturnRef(x) to be + // used in ANY function that returns a reference to x's type. + template <typename F> + operator Action<F>() const { + typedef typename Function<F>::Result Result; + // Asserts that the function return type is a reference. This + // catches the user error of using ReturnRef(x) when Return(x) + // should be used, and generates some helpful error message. + GTEST_COMPILE_ASSERT_(std::is_reference<Result>::value, + use_Return_instead_of_ReturnRef_to_return_a_value); + return Action<F>(new Impl<F>(ref_)); + } + + private: + // Implements the ReturnRef(x) action for a particular function type F. + template <typename F> + class Impl : public ActionInterface<F> { + public: + typedef typename Function<F>::Result Result; + typedef typename Function<F>::ArgumentTuple ArgumentTuple; + + explicit Impl(T& ref) : ref_(ref) {} // NOLINT + + Result Perform(const ArgumentTuple&) override { return ref_; } + + private: + T& ref_; + }; + + T& ref_; +}; + +// Implements the polymorphic ReturnRefOfCopy(x) action, which can be +// used in any function that returns a reference to the type of x, +// regardless of the argument types. +template <typename T> +class ReturnRefOfCopyAction { + public: + // Constructs a ReturnRefOfCopyAction object from the reference to + // be returned. + explicit ReturnRefOfCopyAction(const T& value) : value_(value) {} // NOLINT + + // This template type conversion operator allows ReturnRefOfCopy(x) to be + // used in ANY function that returns a reference to x's type. + template <typename F> + operator Action<F>() const { + typedef typename Function<F>::Result Result; + // Asserts that the function return type is a reference. This + // catches the user error of using ReturnRefOfCopy(x) when Return(x) + // should be used, and generates some helpful error message. + GTEST_COMPILE_ASSERT_( + std::is_reference<Result>::value, + use_Return_instead_of_ReturnRefOfCopy_to_return_a_value); + return Action<F>(new Impl<F>(value_)); + } + + private: + // Implements the ReturnRefOfCopy(x) action for a particular function type F. + template <typename F> + class Impl : public ActionInterface<F> { + public: + typedef typename Function<F>::Result Result; + typedef typename Function<F>::ArgumentTuple ArgumentTuple; + + explicit Impl(const T& value) : value_(value) {} // NOLINT + + Result Perform(const ArgumentTuple&) override { return value_; } + + private: + T value_; + }; + + const T value_; +}; + +// Implements the polymorphic ReturnRoundRobin(v) action, which can be +// used in any function that returns the element_type of v. +template <typename T> +class ReturnRoundRobinAction { + public: + explicit ReturnRoundRobinAction(std::vector<T> values) { + GTEST_CHECK_(!values.empty()) + << "ReturnRoundRobin requires at least one element."; + state_->values = std::move(values); + } + + template <typename... Args> + T operator()(Args&&...) const { + return state_->Next(); + } + + private: + struct State { + T Next() { + T ret_val = values[i++]; + if (i == values.size()) i = 0; + return ret_val; + } + + std::vector<T> values; + size_t i = 0; + }; + std::shared_ptr<State> state_ = std::make_shared<State>(); +}; + +// Implements the polymorphic DoDefault() action. +class DoDefaultAction { + public: + // This template type conversion operator allows DoDefault() to be + // used in any function. + template <typename F> + operator Action<F>() const { return Action<F>(); } // NOLINT +}; + +// Implements the Assign action to set a given pointer referent to a +// particular value. +template <typename T1, typename T2> +class AssignAction { + public: + AssignAction(T1* ptr, T2 value) : ptr_(ptr), value_(value) {} + + template <typename Result, typename ArgumentTuple> + void Perform(const ArgumentTuple& /* args */) const { + *ptr_ = value_; + } + + private: + T1* const ptr_; + const T2 value_; +}; + +#if !GTEST_OS_WINDOWS_MOBILE + +// Implements the SetErrnoAndReturn action to simulate return from +// various system calls and libc functions. +template <typename T> +class SetErrnoAndReturnAction { + public: + SetErrnoAndReturnAction(int errno_value, T result) + : errno_(errno_value), + result_(result) {} + template <typename Result, typename ArgumentTuple> + Result Perform(const ArgumentTuple& /* args */) const { + errno = errno_; + return result_; + } + + private: + const int errno_; + const T result_; +}; + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Implements the SetArgumentPointee<N>(x) action for any function +// whose N-th argument (0-based) is a pointer to x's type. +template <size_t N, typename A, typename = void> +struct SetArgumentPointeeAction { + A value; + + template <typename... Args> + void operator()(const Args&... args) const { + *::std::get<N>(std::tie(args...)) = value; + } +}; + +// Implements the Invoke(object_ptr, &Class::Method) action. +template <class Class, typename MethodPtr> +struct InvokeMethodAction { + Class* const obj_ptr; + const MethodPtr method_ptr; + + template <typename... Args> + auto operator()(Args&&... args) const + -> decltype((obj_ptr->*method_ptr)(std::forward<Args>(args)...)) { + return (obj_ptr->*method_ptr)(std::forward<Args>(args)...); + } +}; + +// Implements the InvokeWithoutArgs(f) action. The template argument +// FunctionImpl is the implementation type of f, which can be either a +// function pointer or a functor. InvokeWithoutArgs(f) can be used as an +// Action<F> as long as f's type is compatible with F. +template <typename FunctionImpl> +struct InvokeWithoutArgsAction { + FunctionImpl function_impl; + + // Allows InvokeWithoutArgs(f) to be used as any action whose type is + // compatible with f. + template <typename... Args> + auto operator()(const Args&...) -> decltype(function_impl()) { + return function_impl(); + } +}; + +// Implements the InvokeWithoutArgs(object_ptr, &Class::Method) action. +template <class Class, typename MethodPtr> +struct InvokeMethodWithoutArgsAction { + Class* const obj_ptr; + const MethodPtr method_ptr; + + using ReturnType = + decltype((std::declval<Class*>()->*std::declval<MethodPtr>())()); + + template <typename... Args> + ReturnType operator()(const Args&...) const { + return (obj_ptr->*method_ptr)(); + } +}; + +// Implements the IgnoreResult(action) action. +template <typename A> +class IgnoreResultAction { + public: + explicit IgnoreResultAction(const A& action) : action_(action) {} + + template <typename F> + operator Action<F>() const { + // Assert statement belongs here because this is the best place to verify + // conditions on F. It produces the clearest error messages + // in most compilers. + // Impl really belongs in this scope as a local class but can't + // because MSVC produces duplicate symbols in different translation units + // in this case. Until MS fixes that bug we put Impl into the class scope + // and put the typedef both here (for use in assert statement) and + // in the Impl class. But both definitions must be the same. + typedef typename internal::Function<F>::Result Result; + + // Asserts at compile time that F returns void. + static_assert(std::is_void<Result>::value, "Result type should be void."); + + return Action<F>(new Impl<F>(action_)); + } + + private: + template <typename F> + class Impl : public ActionInterface<F> { + public: + typedef typename internal::Function<F>::Result Result; + typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple; + + explicit Impl(const A& action) : action_(action) {} + + void Perform(const ArgumentTuple& args) override { + // Performs the action and ignores its result. + action_.Perform(args); + } + + private: + // Type OriginalFunction is the same as F except that its return + // type is IgnoredValue. + typedef typename internal::Function<F>::MakeResultIgnoredValue + OriginalFunction; + + const Action<OriginalFunction> action_; + }; + + const A action_; +}; + +template <typename InnerAction, size_t... I> +struct WithArgsAction { + InnerAction action; + + // The inner action could be anything convertible to Action<X>. + // We use the conversion operator to detect the signature of the inner Action. + template <typename R, typename... Args> + operator Action<R(Args...)>() const { // NOLINT + using TupleType = std::tuple<Args...>; + Action<R(typename std::tuple_element<I, TupleType>::type...)> + converted(action); + + return [converted](Args... args) -> R { + return converted.Perform(std::forward_as_tuple( + std::get<I>(std::forward_as_tuple(std::forward<Args>(args)...))...)); + }; + } +}; + +template <typename... Actions> +struct DoAllAction { + private: + template <typename T> + using NonFinalType = + typename std::conditional<std::is_scalar<T>::value, T, const T&>::type; + + template <typename ActionT, size_t... I> + std::vector<ActionT> Convert(IndexSequence<I...>) const { + return {ActionT(std::get<I>(actions))...}; + } + + public: + std::tuple<Actions...> actions; + + template <typename R, typename... Args> + operator Action<R(Args...)>() const { // NOLINT + struct Op { + std::vector<Action<void(NonFinalType<Args>...)>> converted; + Action<R(Args...)> last; + R operator()(Args... args) const { + auto tuple_args = std::forward_as_tuple(std::forward<Args>(args)...); + for (auto& a : converted) { + a.Perform(tuple_args); + } + return last.Perform(std::move(tuple_args)); + } + }; + return Op{Convert<Action<void(NonFinalType<Args>...)>>( + MakeIndexSequence<sizeof...(Actions) - 1>()), + std::get<sizeof...(Actions) - 1>(actions)}; + } +}; + +template <typename T, typename... Params> +struct ReturnNewAction { + T* operator()() const { + return internal::Apply( + [](const Params&... unpacked_params) { + return new T(unpacked_params...); + }, + params); + } + std::tuple<Params...> params; +}; + +template <size_t k> +struct ReturnArgAction { + template <typename... Args> + auto operator()(const Args&... args) const -> + typename std::tuple_element<k, std::tuple<Args...>>::type { + return std::get<k>(std::tie(args...)); + } +}; + +template <size_t k, typename Ptr> +struct SaveArgAction { + Ptr pointer; + + template <typename... Args> + void operator()(const Args&... args) const { + *pointer = std::get<k>(std::tie(args...)); + } +}; + +template <size_t k, typename Ptr> +struct SaveArgPointeeAction { + Ptr pointer; + + template <typename... Args> + void operator()(const Args&... args) const { + *pointer = *std::get<k>(std::tie(args...)); + } +}; + +template <size_t k, typename T> +struct SetArgRefereeAction { + T value; + + template <typename... Args> + void operator()(Args&&... args) const { + using argk_type = + typename ::std::tuple_element<k, std::tuple<Args...>>::type; + static_assert(std::is_lvalue_reference<argk_type>::value, + "Argument must be a reference type."); + std::get<k>(std::tie(args...)) = value; + } +}; + +template <size_t k, typename I1, typename I2> +struct SetArrayArgumentAction { + I1 first; + I2 last; + + template <typename... Args> + void operator()(const Args&... args) const { + auto value = std::get<k>(std::tie(args...)); + for (auto it = first; it != last; ++it, (void)++value) { + *value = *it; + } + } +}; + +template <size_t k> +struct DeleteArgAction { + template <typename... Args> + void operator()(const Args&... args) const { + delete std::get<k>(std::tie(args...)); + } +}; + +template <typename Ptr> +struct ReturnPointeeAction { + Ptr pointer; + template <typename... Args> + auto operator()(const Args&...) const -> decltype(*pointer) { + return *pointer; + } +}; + +#if GTEST_HAS_EXCEPTIONS +template <typename T> +struct ThrowAction { + T exception; + // We use a conversion operator to adapt to any return type. + template <typename R, typename... Args> + operator Action<R(Args...)>() const { // NOLINT + T copy = exception; + return [copy](Args...) -> R { throw copy; }; + } +}; +#endif // GTEST_HAS_EXCEPTIONS + +} // namespace internal + +// An Unused object can be implicitly constructed from ANY value. +// This is handy when defining actions that ignore some or all of the +// mock function arguments. For example, given +// +// MOCK_METHOD3(Foo, double(const string& label, double x, double y)); +// MOCK_METHOD3(Bar, double(int index, double x, double y)); +// +// instead of +// +// double DistanceToOriginWithLabel(const string& label, double x, double y) { +// return sqrt(x*x + y*y); +// } +// double DistanceToOriginWithIndex(int index, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXPECT_CALL(mock, Foo("abc", _, _)) +// .WillOnce(Invoke(DistanceToOriginWithLabel)); +// EXPECT_CALL(mock, Bar(5, _, _)) +// .WillOnce(Invoke(DistanceToOriginWithIndex)); +// +// you could write +// +// // We can declare any uninteresting argument as Unused. +// double DistanceToOrigin(Unused, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXPECT_CALL(mock, Foo("abc", _, _)).WillOnce(Invoke(DistanceToOrigin)); +// EXPECT_CALL(mock, Bar(5, _, _)).WillOnce(Invoke(DistanceToOrigin)); +typedef internal::IgnoredValue Unused; + +// Creates an action that does actions a1, a2, ..., sequentially in +// each invocation. All but the last action will have a readonly view of the +// arguments. +template <typename... Action> +internal::DoAllAction<typename std::decay<Action>::type...> DoAll( + Action&&... action) { + return {std::forward_as_tuple(std::forward<Action>(action)...)}; +} + +// WithArg<k>(an_action) creates an action that passes the k-th +// (0-based) argument of the mock function to an_action and performs +// it. It adapts an action accepting one argument to one that accepts +// multiple arguments. For convenience, we also provide +// WithArgs<k>(an_action) (defined below) as a synonym. +template <size_t k, typename InnerAction> +internal::WithArgsAction<typename std::decay<InnerAction>::type, k> +WithArg(InnerAction&& action) { + return {std::forward<InnerAction>(action)}; +} + +// WithArgs<N1, N2, ..., Nk>(an_action) creates an action that passes +// the selected arguments of the mock function to an_action and +// performs it. It serves as an adaptor between actions with +// different argument lists. +template <size_t k, size_t... ks, typename InnerAction> +internal::WithArgsAction<typename std::decay<InnerAction>::type, k, ks...> +WithArgs(InnerAction&& action) { + return {std::forward<InnerAction>(action)}; +} + +// WithoutArgs(inner_action) can be used in a mock function with a +// non-empty argument list to perform inner_action, which takes no +// argument. In other words, it adapts an action accepting no +// argument to one that accepts (and ignores) arguments. +template <typename InnerAction> +internal::WithArgsAction<typename std::decay<InnerAction>::type> +WithoutArgs(InnerAction&& action) { + return {std::forward<InnerAction>(action)}; +} + +// Creates an action that returns 'value'. 'value' is passed by value +// instead of const reference - otherwise Return("string literal") +// will trigger a compiler error about using array as initializer. +template <typename R> +internal::ReturnAction<R> Return(R value) { + return internal::ReturnAction<R>(std::move(value)); +} + +// Creates an action that returns NULL. +inline PolymorphicAction<internal::ReturnNullAction> ReturnNull() { + return MakePolymorphicAction(internal::ReturnNullAction()); +} + +// Creates an action that returns from a void function. +inline PolymorphicAction<internal::ReturnVoidAction> Return() { + return MakePolymorphicAction(internal::ReturnVoidAction()); +} + +// Creates an action that returns the reference to a variable. +template <typename R> +inline internal::ReturnRefAction<R> ReturnRef(R& x) { // NOLINT + return internal::ReturnRefAction<R>(x); +} + +// Prevent using ReturnRef on reference to temporary. +template <typename R, R* = nullptr> +internal::ReturnRefAction<R> ReturnRef(R&&) = delete; + +// Creates an action that returns the reference to a copy of the +// argument. The copy is created when the action is constructed and +// lives as long as the action. +template <typename R> +inline internal::ReturnRefOfCopyAction<R> ReturnRefOfCopy(const R& x) { + return internal::ReturnRefOfCopyAction<R>(x); +} + +// Modifies the parent action (a Return() action) to perform a move of the +// argument instead of a copy. +// Return(ByMove()) actions can only be executed once and will assert this +// invariant. +template <typename R> +internal::ByMoveWrapper<R> ByMove(R x) { + return internal::ByMoveWrapper<R>(std::move(x)); +} + +// Creates an action that returns an element of `vals`. Calling this action will +// repeatedly return the next value from `vals` until it reaches the end and +// will restart from the beginning. +template <typename T> +internal::ReturnRoundRobinAction<T> ReturnRoundRobin(std::vector<T> vals) { + return internal::ReturnRoundRobinAction<T>(std::move(vals)); +} + +// Creates an action that returns an element of `vals`. Calling this action will +// repeatedly return the next value from `vals` until it reaches the end and +// will restart from the beginning. +template <typename T> +internal::ReturnRoundRobinAction<T> ReturnRoundRobin( + std::initializer_list<T> vals) { + return internal::ReturnRoundRobinAction<T>(std::vector<T>(vals)); +} + +// Creates an action that does the default action for the give mock function. +inline internal::DoDefaultAction DoDefault() { + return internal::DoDefaultAction(); +} + +// Creates an action that sets the variable pointed by the N-th +// (0-based) function argument to 'value'. +template <size_t N, typename T> +internal::SetArgumentPointeeAction<N, T> SetArgPointee(T value) { + return {std::move(value)}; +} + +// The following version is DEPRECATED. +template <size_t N, typename T> +internal::SetArgumentPointeeAction<N, T> SetArgumentPointee(T value) { + return {std::move(value)}; +} + +// Creates an action that sets a pointer referent to a given value. +template <typename T1, typename T2> +PolymorphicAction<internal::AssignAction<T1, T2> > Assign(T1* ptr, T2 val) { + return MakePolymorphicAction(internal::AssignAction<T1, T2>(ptr, val)); +} + +#if !GTEST_OS_WINDOWS_MOBILE + +// Creates an action that sets errno and returns the appropriate error. +template <typename T> +PolymorphicAction<internal::SetErrnoAndReturnAction<T> > +SetErrnoAndReturn(int errval, T result) { + return MakePolymorphicAction( + internal::SetErrnoAndReturnAction<T>(errval, result)); +} + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Various overloads for Invoke(). + +// Legacy function. +// Actions can now be implicitly constructed from callables. No need to create +// wrapper objects. +// This function exists for backwards compatibility. +template <typename FunctionImpl> +typename std::decay<FunctionImpl>::type Invoke(FunctionImpl&& function_impl) { + return std::forward<FunctionImpl>(function_impl); +} + +// Creates an action that invokes the given method on the given object +// with the mock function's arguments. +template <class Class, typename MethodPtr> +internal::InvokeMethodAction<Class, MethodPtr> Invoke(Class* obj_ptr, + MethodPtr method_ptr) { + return {obj_ptr, method_ptr}; +} + +// Creates an action that invokes 'function_impl' with no argument. +template <typename FunctionImpl> +internal::InvokeWithoutArgsAction<typename std::decay<FunctionImpl>::type> +InvokeWithoutArgs(FunctionImpl function_impl) { + return {std::move(function_impl)}; +} + +// Creates an action that invokes the given method on the given object +// with no argument. +template <class Class, typename MethodPtr> +internal::InvokeMethodWithoutArgsAction<Class, MethodPtr> InvokeWithoutArgs( + Class* obj_ptr, MethodPtr method_ptr) { + return {obj_ptr, method_ptr}; +} + +// Creates an action that performs an_action and throws away its +// result. In other words, it changes the return type of an_action to +// void. an_action MUST NOT return void, or the code won't compile. +template <typename A> +inline internal::IgnoreResultAction<A> IgnoreResult(const A& an_action) { + return internal::IgnoreResultAction<A>(an_action); +} + +// Creates a reference wrapper for the given L-value. If necessary, +// you can explicitly specify the type of the reference. For example, +// suppose 'derived' is an object of type Derived, ByRef(derived) +// would wrap a Derived&. If you want to wrap a const Base& instead, +// where Base is a base class of Derived, just write: +// +// ByRef<const Base>(derived) +// +// N.B. ByRef is redundant with std::ref, std::cref and std::reference_wrapper. +// However, it may still be used for consistency with ByMove(). +template <typename T> +inline ::std::reference_wrapper<T> ByRef(T& l_value) { // NOLINT + return ::std::reference_wrapper<T>(l_value); +} + +// The ReturnNew<T>(a1, a2, ..., a_k) action returns a pointer to a new +// instance of type T, constructed on the heap with constructor arguments +// a1, a2, ..., and a_k. The caller assumes ownership of the returned value. +template <typename T, typename... Params> +internal::ReturnNewAction<T, typename std::decay<Params>::type...> ReturnNew( + Params&&... params) { + return {std::forward_as_tuple(std::forward<Params>(params)...)}; +} + +// Action ReturnArg<k>() returns the k-th argument of the mock function. +template <size_t k> +internal::ReturnArgAction<k> ReturnArg() { + return {}; +} + +// Action SaveArg<k>(pointer) saves the k-th (0-based) argument of the +// mock function to *pointer. +template <size_t k, typename Ptr> +internal::SaveArgAction<k, Ptr> SaveArg(Ptr pointer) { + return {pointer}; +} + +// Action SaveArgPointee<k>(pointer) saves the value pointed to +// by the k-th (0-based) argument of the mock function to *pointer. +template <size_t k, typename Ptr> +internal::SaveArgPointeeAction<k, Ptr> SaveArgPointee(Ptr pointer) { + return {pointer}; +} + +// Action SetArgReferee<k>(value) assigns 'value' to the variable +// referenced by the k-th (0-based) argument of the mock function. +template <size_t k, typename T> +internal::SetArgRefereeAction<k, typename std::decay<T>::type> SetArgReferee( + T&& value) { + return {std::forward<T>(value)}; +} + +// Action SetArrayArgument<k>(first, last) copies the elements in +// source range [first, last) to the array pointed to by the k-th +// (0-based) argument, which can be either a pointer or an +// iterator. The action does not take ownership of the elements in the +// source range. +template <size_t k, typename I1, typename I2> +internal::SetArrayArgumentAction<k, I1, I2> SetArrayArgument(I1 first, + I2 last) { + return {first, last}; +} + +// Action DeleteArg<k>() deletes the k-th (0-based) argument of the mock +// function. +template <size_t k> +internal::DeleteArgAction<k> DeleteArg() { + return {}; +} + +// This action returns the value pointed to by 'pointer'. +template <typename Ptr> +internal::ReturnPointeeAction<Ptr> ReturnPointee(Ptr pointer) { + return {pointer}; +} + +// Action Throw(exception) can be used in a mock function of any type +// to throw the given exception. Any copyable value can be thrown. +#if GTEST_HAS_EXCEPTIONS +template <typename T> +internal::ThrowAction<typename std::decay<T>::type> Throw(T&& exception) { + return {std::forward<T>(exception)}; +} +#endif // GTEST_HAS_EXCEPTIONS + +namespace internal { + +// A macro from the ACTION* family (defined later in gmock-generated-actions.h) +// defines an action that can be used in a mock function. Typically, +// these actions only care about a subset of the arguments of the mock +// function. For example, if such an action only uses the second +// argument, it can be used in any mock function that takes >= 2 +// arguments where the type of the second argument is compatible. +// +// Therefore, the action implementation must be prepared to take more +// arguments than it needs. The ExcessiveArg type is used to +// represent those excessive arguments. In order to keep the compiler +// error messages tractable, we define it in the testing namespace +// instead of testing::internal. However, this is an INTERNAL TYPE +// and subject to change without notice, so a user MUST NOT USE THIS +// TYPE DIRECTLY. +struct ExcessiveArg {}; + +// Builds an implementation of an Action<> for some particular signature, using +// a class defined by an ACTION* macro. +template <typename F, typename Impl> struct ActionImpl; + +template <typename Impl> +struct ImplBase { + struct Holder { + // Allows each copy of the Action<> to get to the Impl. + explicit operator const Impl&() const { return *ptr; } + std::shared_ptr<Impl> ptr; + }; + using type = typename std::conditional<std::is_constructible<Impl>::value, + Impl, Holder>::type; +}; + +template <typename R, typename... Args, typename Impl> +struct ActionImpl<R(Args...), Impl> : ImplBase<Impl>::type { + using Base = typename ImplBase<Impl>::type; + using function_type = R(Args...); + using args_type = std::tuple<Args...>; + + ActionImpl() = default; // Only defined if appropriate for Base. + explicit ActionImpl(std::shared_ptr<Impl> impl) : Base{std::move(impl)} { } + + R operator()(Args&&... arg) const { + static constexpr size_t kMaxArgs = + sizeof...(Args) <= 10 ? sizeof...(Args) : 10; + return Apply(MakeIndexSequence<kMaxArgs>{}, + MakeIndexSequence<10 - kMaxArgs>{}, + args_type{std::forward<Args>(arg)...}); + } + + template <std::size_t... arg_id, std::size_t... excess_id> + R Apply(IndexSequence<arg_id...>, IndexSequence<excess_id...>, + const args_type& args) const { + // Impl need not be specific to the signature of action being implemented; + // only the implementing function body needs to have all of the specific + // types instantiated. Up to 10 of the args that are provided by the + // args_type get passed, followed by a dummy of unspecified type for the + // remainder up to 10 explicit args. + static constexpr ExcessiveArg kExcessArg{}; + return static_cast<const Impl&>(*this).template gmock_PerformImpl< + /*function_type=*/function_type, /*return_type=*/R, + /*args_type=*/args_type, + /*argN_type=*/typename std::tuple_element<arg_id, args_type>::type...>( + /*args=*/args, std::get<arg_id>(args)..., + ((void)excess_id, kExcessArg)...); + } +}; + +// Stores a default-constructed Impl as part of the Action<>'s +// std::function<>. The Impl should be trivial to copy. +template <typename F, typename Impl> +::testing::Action<F> MakeAction() { + return ::testing::Action<F>(ActionImpl<F, Impl>()); +} + +// Stores just the one given instance of Impl. +template <typename F, typename Impl> +::testing::Action<F> MakeAction(std::shared_ptr<Impl> impl) { + return ::testing::Action<F>(ActionImpl<F, Impl>(std::move(impl))); +} + +#define GMOCK_INTERNAL_ARG_UNUSED(i, data, el) \ + , const arg##i##_type& arg##i GTEST_ATTRIBUTE_UNUSED_ +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_ \ + const args_type& args GTEST_ATTRIBUTE_UNUSED_ GMOCK_PP_REPEAT( \ + GMOCK_INTERNAL_ARG_UNUSED, , 10) + +#define GMOCK_INTERNAL_ARG(i, data, el) , const arg##i##_type& arg##i +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_ \ + const args_type& args GMOCK_PP_REPEAT(GMOCK_INTERNAL_ARG, , 10) + +#define GMOCK_INTERNAL_TEMPLATE_ARG(i, data, el) , typename arg##i##_type +#define GMOCK_ACTION_TEMPLATE_ARGS_NAMES_ \ + GMOCK_PP_TAIL(GMOCK_PP_REPEAT(GMOCK_INTERNAL_TEMPLATE_ARG, , 10)) + +#define GMOCK_INTERNAL_TYPENAME_PARAM(i, data, param) , typename param##_type +#define GMOCK_ACTION_TYPENAME_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPENAME_PARAM, , params)) + +#define GMOCK_INTERNAL_TYPE_PARAM(i, data, param) , param##_type +#define GMOCK_ACTION_TYPE_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPE_PARAM, , params)) + +#define GMOCK_INTERNAL_TYPE_GVALUE_PARAM(i, data, param) \ + , param##_type gmock_p##i +#define GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPE_GVALUE_PARAM, , params)) + +#define GMOCK_INTERNAL_GVALUE_PARAM(i, data, param) \ + , std::forward<param##_type>(gmock_p##i) +#define GMOCK_ACTION_GVALUE_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GVALUE_PARAM, , params)) + +#define GMOCK_INTERNAL_INIT_PARAM(i, data, param) \ + , param(::std::forward<param##_type>(gmock_p##i)) +#define GMOCK_ACTION_INIT_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_INIT_PARAM, , params)) + +#define GMOCK_INTERNAL_FIELD_PARAM(i, data, param) param##_type param; +#define GMOCK_ACTION_FIELD_PARAMS_(params) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_FIELD_PARAM, , params) + +#define GMOCK_INTERNAL_ACTION(name, full_name, params) \ + template <GMOCK_ACTION_TYPENAME_PARAMS_(params)> \ + class full_name { \ + public: \ + explicit full_name(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ + : impl_(std::make_shared<gmock_Impl>( \ + GMOCK_ACTION_GVALUE_PARAMS_(params))) { } \ + full_name(const full_name&) = default; \ + full_name(full_name&&) noexcept = default; \ + template <typename F> \ + operator ::testing::Action<F>() const { \ + return ::testing::internal::MakeAction<F>(impl_); \ + } \ + private: \ + class gmock_Impl { \ + public: \ + explicit gmock_Impl(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ + : GMOCK_ACTION_INIT_PARAMS_(params) {} \ + template <typename function_type, typename return_type, \ + typename args_type, GMOCK_ACTION_TEMPLATE_ARGS_NAMES_> \ + return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \ + GMOCK_ACTION_FIELD_PARAMS_(params) \ + }; \ + std::shared_ptr<const gmock_Impl> impl_; \ + }; \ + template <GMOCK_ACTION_TYPENAME_PARAMS_(params)> \ + inline full_name<GMOCK_ACTION_TYPE_PARAMS_(params)> name( \ + GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) { \ + return full_name<GMOCK_ACTION_TYPE_PARAMS_(params)>( \ + GMOCK_ACTION_GVALUE_PARAMS_(params)); \ + } \ + template <GMOCK_ACTION_TYPENAME_PARAMS_(params)> \ + template <typename function_type, typename return_type, typename args_type, \ + GMOCK_ACTION_TEMPLATE_ARGS_NAMES_> \ + return_type full_name<GMOCK_ACTION_TYPE_PARAMS_(params)>::gmock_Impl:: \ + gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +} // namespace internal + +// Similar to GMOCK_INTERNAL_ACTION, but no bound parameters are stored. +#define ACTION(name) \ + class name##Action { \ + public: \ + explicit name##Action() noexcept {} \ + name##Action(const name##Action&) noexcept {} \ + template <typename F> \ + operator ::testing::Action<F>() const { \ + return ::testing::internal::MakeAction<F, gmock_Impl>(); \ + } \ + private: \ + class gmock_Impl { \ + public: \ + template <typename function_type, typename return_type, \ + typename args_type, GMOCK_ACTION_TEMPLATE_ARGS_NAMES_> \ + return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \ + }; \ + }; \ + inline name##Action name() GTEST_MUST_USE_RESULT_; \ + inline name##Action name() { return name##Action(); } \ + template <typename function_type, typename return_type, typename args_type, \ + GMOCK_ACTION_TEMPLATE_ARGS_NAMES_> \ + return_type name##Action::gmock_Impl::gmock_PerformImpl( \ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP, (__VA_ARGS__)) + +#define ACTION_P2(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP2, (__VA_ARGS__)) + +#define ACTION_P3(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP3, (__VA_ARGS__)) + +#define ACTION_P4(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP4, (__VA_ARGS__)) + +#define ACTION_P5(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP5, (__VA_ARGS__)) + +#define ACTION_P6(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP6, (__VA_ARGS__)) + +#define ACTION_P7(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP7, (__VA_ARGS__)) + +#define ACTION_P8(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP8, (__VA_ARGS__)) + +#define ACTION_P9(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP9, (__VA_ARGS__)) + +#define ACTION_P10(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP10, (__VA_ARGS__)) + +} // namespace testing + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used cardinalities. More +// cardinalities can be defined by the user implementing the +// CardinalityInterface interface if necessary. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ + +#include <limits.h> +#include <memory> +#include <ostream> // NOLINT + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// To implement a cardinality Foo, define: +// 1. a class FooCardinality that implements the +// CardinalityInterface interface, and +// 2. a factory function that creates a Cardinality object from a +// const FooCardinality*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Cardinality objects can now be copied like plain values. + +// The implementation of a cardinality. +class CardinalityInterface { + public: + virtual ~CardinalityInterface() {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + virtual int ConservativeLowerBound() const { return 0; } + virtual int ConservativeUpperBound() const { return INT_MAX; } + + // Returns true if and only if call_count calls will satisfy this + // cardinality. + virtual bool IsSatisfiedByCallCount(int call_count) const = 0; + + // Returns true if and only if call_count calls will saturate this + // cardinality. + virtual bool IsSaturatedByCallCount(int call_count) const = 0; + + // Describes self to an ostream. + virtual void DescribeTo(::std::ostream* os) const = 0; +}; + +// A Cardinality is a copyable and IMMUTABLE (except by assignment) +// object that specifies how many times a mock function is expected to +// be called. The implementation of Cardinality is just a std::shared_ptr +// to const CardinalityInterface. Don't inherit from Cardinality! +class GTEST_API_ Cardinality { + public: + // Constructs a null cardinality. Needed for storing Cardinality + // objects in STL containers. + Cardinality() {} + + // Constructs a Cardinality from its implementation. + explicit Cardinality(const CardinalityInterface* impl) : impl_(impl) {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + int ConservativeLowerBound() const { return impl_->ConservativeLowerBound(); } + int ConservativeUpperBound() const { return impl_->ConservativeUpperBound(); } + + // Returns true if and only if call_count calls will satisfy this + // cardinality. + bool IsSatisfiedByCallCount(int call_count) const { + return impl_->IsSatisfiedByCallCount(call_count); + } + + // Returns true if and only if call_count calls will saturate this + // cardinality. + bool IsSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count); + } + + // Returns true if and only if call_count calls will over-saturate this + // cardinality, i.e. exceed the maximum number of allowed calls. + bool IsOverSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count) && + !impl_->IsSatisfiedByCallCount(call_count); + } + + // Describes self to an ostream + void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } + + // Describes the given actual call count to an ostream. + static void DescribeActualCallCountTo(int actual_call_count, + ::std::ostream* os); + + private: + std::shared_ptr<const CardinalityInterface> impl_; +}; + +// Creates a cardinality that allows at least n calls. +GTEST_API_ Cardinality AtLeast(int n); + +// Creates a cardinality that allows at most n calls. +GTEST_API_ Cardinality AtMost(int n); + +// Creates a cardinality that allows any number of calls. +GTEST_API_ Cardinality AnyNumber(); + +// Creates a cardinality that allows between min and max calls. +GTEST_API_ Cardinality Between(int min, int max); + +// Creates a cardinality that allows exactly n calls. +GTEST_API_ Cardinality Exactly(int n); + +// Creates a cardinality from its implementation. +inline Cardinality MakeCardinality(const CardinalityInterface* c) { + return Cardinality(c); +} + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements MOCK_METHOD. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_FUNCTION_MOCKER_H_ // NOLINT +#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_FUNCTION_MOCKER_H_ // NOLINT + +#include <type_traits> // IWYU pragma: keep +#include <utility> // IWYU pragma: keep + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements the ON_CALL() and EXPECT_CALL() macros. +// +// A user can use the ON_CALL() macro to specify the default action of +// a mock method. The syntax is: +// +// ON_CALL(mock_object, Method(argument-matchers)) +// .With(multi-argument-matcher) +// .WillByDefault(action); +// +// where the .With() clause is optional. +// +// A user can use the EXPECT_CALL() macro to specify an expectation on +// a mock method. The syntax is: +// +// EXPECT_CALL(mock_object, Method(argument-matchers)) +// .With(multi-argument-matchers) +// .Times(cardinality) +// .InSequence(sequences) +// .After(expectations) +// .WillOnce(action) +// .WillRepeatedly(action) +// .RetiresOnSaturation(); +// +// where all clauses are optional, and .InSequence()/.After()/ +// .WillOnce() can appear any number of times. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_ + +#include <functional> +#include <map> +#include <memory> +#include <set> +#include <sstream> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// The MATCHER* family of macros can be used in a namespace scope to +// define custom matchers easily. +// +// Basic Usage +// =========== +// +// The syntax +// +// MATCHER(name, description_string) { statements; } +// +// defines a matcher with the given name that executes the statements, +// which must return a bool to indicate if the match succeeds. Inside +// the statements, you can refer to the value being matched by 'arg', +// and refer to its type by 'arg_type'. +// +// The description string documents what the matcher does, and is used +// to generate the failure message when the match fails. Since a +// MATCHER() is usually defined in a header file shared by multiple +// C++ source files, we require the description to be a C-string +// literal to avoid possible side effects. It can be empty, in which +// case we'll use the sequence of words in the matcher name as the +// description. +// +// For example: +// +// MATCHER(IsEven, "") { return (arg % 2) == 0; } +// +// allows you to write +// +// // Expects mock_foo.Bar(n) to be called where n is even. +// EXPECT_CALL(mock_foo, Bar(IsEven())); +// +// or, +// +// // Verifies that the value of some_expression is even. +// EXPECT_THAT(some_expression, IsEven()); +// +// If the above assertion fails, it will print something like: +// +// Value of: some_expression +// Expected: is even +// Actual: 7 +// +// where the description "is even" is automatically calculated from the +// matcher name IsEven. +// +// Argument Type +// ============= +// +// Note that the type of the value being matched (arg_type) is +// determined by the context in which you use the matcher and is +// supplied to you by the compiler, so you don't need to worry about +// declaring it (nor can you). This allows the matcher to be +// polymorphic. For example, IsEven() can be used to match any type +// where the value of "(arg % 2) == 0" can be implicitly converted to +// a bool. In the "Bar(IsEven())" example above, if method Bar() +// takes an int, 'arg_type' will be int; if it takes an unsigned long, +// 'arg_type' will be unsigned long; and so on. +// +// Parameterizing Matchers +// ======================= +// +// Sometimes you'll want to parameterize the matcher. For that you +// can use another macro: +// +// MATCHER_P(name, param_name, description_string) { statements; } +// +// For example: +// +// MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +// +// will allow you to write: +// +// EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +// +// which may lead to this message (assuming n is 10): +// +// Value of: Blah("a") +// Expected: has absolute value 10 +// Actual: -9 +// +// Note that both the matcher description and its parameter are +// printed, making the message human-friendly. +// +// In the matcher definition body, you can write 'foo_type' to +// reference the type of a parameter named 'foo'. For example, in the +// body of MATCHER_P(HasAbsoluteValue, value) above, you can write +// 'value_type' to refer to the type of 'value'. +// +// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P$n to +// support multi-parameter matchers. +// +// Describing Parameterized Matchers +// ================================= +// +// The last argument to MATCHER*() is a string-typed expression. The +// expression can reference all of the matcher's parameters and a +// special bool-typed variable named 'negation'. When 'negation' is +// false, the expression should evaluate to the matcher's description; +// otherwise it should evaluate to the description of the negation of +// the matcher. For example, +// +// using testing::PrintToString; +// +// MATCHER_P2(InClosedRange, low, hi, +// std::string(negation ? "is not" : "is") + " in range [" + +// PrintToString(low) + ", " + PrintToString(hi) + "]") { +// return low <= arg && arg <= hi; +// } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: is in range [4, 6] +// ... +// Expected: is not in range [2, 4] +// +// If you specify "" as the description, the failure message will +// contain the sequence of words in the matcher name followed by the +// parameter values printed as a tuple. For example, +// +// MATCHER_P2(InClosedRange, low, hi, "") { ... } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: in closed range (4, 6) +// ... +// Expected: not (in closed range (2, 4)) +// +// Types of Matcher Parameters +// =========================== +// +// For the purpose of typing, you can view +// +// MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +// +// as shorthand for +// +// template <typename p1_type, ..., typename pk_type> +// FooMatcherPk<p1_type, ..., pk_type> +// Foo(p1_type p1, ..., pk_type pk) { ... } +// +// When you write Foo(v1, ..., vk), the compiler infers the types of +// the parameters v1, ..., and vk for you. If you are not happy with +// the result of the type inference, you can specify the types by +// explicitly instantiating the template, as in Foo<long, bool>(5, +// false). As said earlier, you don't get to (or need to) specify +// 'arg_type' as that's determined by the context in which the matcher +// is used. You can assign the result of expression Foo(p1, ..., pk) +// to a variable of type FooMatcherPk<p1_type, ..., pk_type>. This +// can be useful when composing matchers. +// +// While you can instantiate a matcher template with reference types, +// passing the parameters by pointer usually makes your code more +// readable. If, however, you still want to pass a parameter by +// reference, be aware that in the failure message generated by the +// matcher you will see the value of the referenced object but not its +// address. +// +// Explaining Match Results +// ======================== +// +// Sometimes the matcher description alone isn't enough to explain why +// the match has failed or succeeded. For example, when expecting a +// long string, it can be very helpful to also print the diff between +// the expected string and the actual one. To achieve that, you can +// optionally stream additional information to a special variable +// named result_listener, whose type is a pointer to class +// MatchResultListener: +// +// MATCHER_P(EqualsLongString, str, "") { +// if (arg == str) return true; +// +// *result_listener << "the difference: " +/// << DiffStrings(str, arg); +// return false; +// } +// +// Overloading Matchers +// ==================== +// +// You can overload matchers with different numbers of parameters: +// +// MATCHER_P(Blah, a, description_string1) { ... } +// MATCHER_P2(Blah, a, b, description_string2) { ... } +// +// Caveats +// ======= +// +// When defining a new matcher, you should also consider implementing +// MatcherInterface or using MakePolymorphicMatcher(). These +// approaches require more work than the MATCHER* macros, but also +// give you more control on the types of the value being matched and +// the matcher parameters, which may leads to better compiler error +// messages when the matcher is used wrong. They also allow +// overloading matchers based on parameter types (as opposed to just +// based on the number of parameters). +// +// MATCHER*() can only be used in a namespace scope as templates cannot be +// declared inside of a local class. +// +// More Information +// ================ +// +// To learn more about using these macros, please search for 'MATCHER' +// on +// https://github.com/google/googletest/blob/master/docs/gmock_cook_book.md +// +// This file also implements some commonly used argument matchers. More +// matchers can be defined by the user implementing the +// MatcherInterface<T> interface if necessary. +// +// See googletest/include/gtest/gtest-matchers.h for the definition of class +// Matcher, class MatcherInterface, and others. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ + +#include <algorithm> +#include <cmath> +#include <initializer_list> +#include <iterator> +#include <limits> +#include <memory> +#include <ostream> // NOLINT +#include <sstream> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + + +// MSVC warning C5046 is new as of VS2017 version 15.8. +#if defined(_MSC_VER) && _MSC_VER >= 1915 +#define GMOCK_MAYBE_5046_ 5046 +#else +#define GMOCK_MAYBE_5046_ +#endif + +GTEST_DISABLE_MSC_WARNINGS_PUSH_( + 4251 GMOCK_MAYBE_5046_ /* class A needs to have dll-interface to be used by + clients of class B */ + /* Symbol involving type with internal linkage not defined */) + +namespace testing { + +// To implement a matcher Foo for type T, define: +// 1. a class FooMatcherImpl that implements the +// MatcherInterface<T> interface, and +// 2. a factory function that creates a Matcher<T> object from a +// FooMatcherImpl*. +// +// The two-level delegation design makes it possible to allow a user +// to write "v" instead of "Eq(v)" where a Matcher is expected, which +// is impossible if we pass matchers by pointers. It also eases +// ownership management as Matcher objects can now be copied like +// plain values. + +// A match result listener that stores the explanation in a string. +class StringMatchResultListener : public MatchResultListener { + public: + StringMatchResultListener() : MatchResultListener(&ss_) {} + + // Returns the explanation accumulated so far. + std::string str() const { return ss_.str(); } + + // Clears the explanation accumulated so far. + void Clear() { ss_.str(""); } + + private: + ::std::stringstream ss_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(StringMatchResultListener); +}; + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// The MatcherCastImpl class template is a helper for implementing +// MatcherCast(). We need this helper in order to partially +// specialize the implementation of MatcherCast() (C++ allows +// class/struct templates to be partially specialized, but not +// function templates.). + +// This general version is used when MatcherCast()'s argument is a +// polymorphic matcher (i.e. something that can be converted to a +// Matcher but is not one yet; for example, Eq(value)) or a value (for +// example, "hello"). +template <typename T, typename M> +class MatcherCastImpl { + public: + static Matcher<T> Cast(const M& polymorphic_matcher_or_value) { + // M can be a polymorphic matcher, in which case we want to use + // its conversion operator to create Matcher<T>. Or it can be a value + // that should be passed to the Matcher<T>'s constructor. + // + // We can't call Matcher<T>(polymorphic_matcher_or_value) when M is a + // polymorphic matcher because it'll be ambiguous if T has an implicit + // constructor from M (this usually happens when T has an implicit + // constructor from any type). + // + // It won't work to unconditionally implicit_cast + // polymorphic_matcher_or_value to Matcher<T> because it won't trigger + // a user-defined conversion from M to T if one exists (assuming M is + // a value). + return CastImpl(polymorphic_matcher_or_value, + std::is_convertible<M, Matcher<T>>{}, + std::is_convertible<M, T>{}); + } + + private: + template <bool Ignore> + static Matcher<T> CastImpl(const M& polymorphic_matcher_or_value, + std::true_type /* convertible_to_matcher */, + std::integral_constant<bool, Ignore>) { + // M is implicitly convertible to Matcher<T>, which means that either + // M is a polymorphic matcher or Matcher<T> has an implicit constructor + // from M. In both cases using the implicit conversion will produce a + // matcher. + // + // Even if T has an implicit constructor from M, it won't be called because + // creating Matcher<T> would require a chain of two user-defined conversions + // (first to create T from M and then to create Matcher<T> from T). + return polymorphic_matcher_or_value; + } + + // M can't be implicitly converted to Matcher<T>, so M isn't a polymorphic + // matcher. It's a value of a type implicitly convertible to T. Use direct + // initialization to create a matcher. + static Matcher<T> CastImpl(const M& value, + std::false_type /* convertible_to_matcher */, + std::true_type /* convertible_to_T */) { + return Matcher<T>(ImplicitCast_<T>(value)); + } + + // M can't be implicitly converted to either Matcher<T> or T. Attempt to use + // polymorphic matcher Eq(value) in this case. + // + // Note that we first attempt to perform an implicit cast on the value and + // only fall back to the polymorphic Eq() matcher afterwards because the + // latter calls bool operator==(const Lhs& lhs, const Rhs& rhs) in the end + // which might be undefined even when Rhs is implicitly convertible to Lhs + // (e.g. std::pair<const int, int> vs. std::pair<int, int>). + // + // We don't define this method inline as we need the declaration of Eq(). + static Matcher<T> CastImpl(const M& value, + std::false_type /* convertible_to_matcher */, + std::false_type /* convertible_to_T */); +}; + +// This more specialized version is used when MatcherCast()'s argument +// is already a Matcher. This only compiles when type T can be +// statically converted to type U. +template <typename T, typename U> +class MatcherCastImpl<T, Matcher<U> > { + public: + static Matcher<T> Cast(const Matcher<U>& source_matcher) { + return Matcher<T>(new Impl(source_matcher)); + } + + private: + class Impl : public MatcherInterface<T> { + public: + explicit Impl(const Matcher<U>& source_matcher) + : source_matcher_(source_matcher) {} + + // We delegate the matching logic to the source matcher. + bool MatchAndExplain(T x, MatchResultListener* listener) const override { + using FromType = typename std::remove_cv<typename std::remove_pointer< + typename std::remove_reference<T>::type>::type>::type; + using ToType = typename std::remove_cv<typename std::remove_pointer< + typename std::remove_reference<U>::type>::type>::type; + // Do not allow implicitly converting base*/& to derived*/&. + static_assert( + // Do not trigger if only one of them is a pointer. That implies a + // regular conversion and not a down_cast. + (std::is_pointer<typename std::remove_reference<T>::type>::value != + std::is_pointer<typename std::remove_reference<U>::type>::value) || + std::is_same<FromType, ToType>::value || + !std::is_base_of<FromType, ToType>::value, + "Can't implicitly convert from <base> to <derived>"); + + // Do the cast to `U` explicitly if necessary. + // Otherwise, let implicit conversions do the trick. + using CastType = + typename std::conditional<std::is_convertible<T&, const U&>::value, + T&, U>::type; + + return source_matcher_.MatchAndExplain(static_cast<CastType>(x), + listener); + } + + void DescribeTo(::std::ostream* os) const override { + source_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + source_matcher_.DescribeNegationTo(os); + } + + private: + const Matcher<U> source_matcher_; + }; +}; + +// This even more specialized version is used for efficiently casting +// a matcher to its own type. +template <typename T> +class MatcherCastImpl<T, Matcher<T> > { + public: + static Matcher<T> Cast(const Matcher<T>& matcher) { return matcher; } +}; + +// Template specialization for parameterless Matcher. +template <typename Derived> +class MatcherBaseImpl { + public: + MatcherBaseImpl() = default; + + template <typename T> + operator ::testing::Matcher<T>() const { // NOLINT(runtime/explicit) + return ::testing::Matcher<T>(new + typename Derived::template gmock_Impl<T>()); + } +}; + +// Template specialization for Matcher with parameters. +template <template <typename...> class Derived, typename... Ts> +class MatcherBaseImpl<Derived<Ts...>> { + public: + // Mark the constructor explicit for single argument T to avoid implicit + // conversions. + template <typename E = std::enable_if<sizeof...(Ts) == 1>, + typename E::type* = nullptr> + explicit MatcherBaseImpl(Ts... params) + : params_(std::forward<Ts>(params)...) {} + template <typename E = std::enable_if<sizeof...(Ts) != 1>, + typename = typename E::type> + MatcherBaseImpl(Ts... params) // NOLINT + : params_(std::forward<Ts>(params)...) {} + + template <typename F> + operator ::testing::Matcher<F>() const { // NOLINT(runtime/explicit) + return Apply<F>(MakeIndexSequence<sizeof...(Ts)>{}); + } + + private: + template <typename F, std::size_t... tuple_ids> + ::testing::Matcher<F> Apply(IndexSequence<tuple_ids...>) const { + return ::testing::Matcher<F>( + new typename Derived<Ts...>::template gmock_Impl<F>( + std::get<tuple_ids>(params_)...)); + } + + const std::tuple<Ts...> params_; +}; + +} // namespace internal + +// In order to be safe and clear, casting between different matcher +// types is done explicitly via MatcherCast<T>(m), which takes a +// matcher m and returns a Matcher<T>. It compiles only when T can be +// statically converted to the argument type of m. +template <typename T, typename M> +inline Matcher<T> MatcherCast(const M& matcher) { + return internal::MatcherCastImpl<T, M>::Cast(matcher); +} + +// This overload handles polymorphic matchers and values only since +// monomorphic matchers are handled by the next one. +template <typename T, typename M> +inline Matcher<T> SafeMatcherCast(const M& polymorphic_matcher_or_value) { + return MatcherCast<T>(polymorphic_matcher_or_value); +} + +// This overload handles monomorphic matchers. +// +// In general, if type T can be implicitly converted to type U, we can +// safely convert a Matcher<U> to a Matcher<T> (i.e. Matcher is +// contravariant): just keep a copy of the original Matcher<U>, convert the +// argument from type T to U, and then pass it to the underlying Matcher<U>. +// The only exception is when U is a reference and T is not, as the +// underlying Matcher<U> may be interested in the argument's address, which +// is not preserved in the conversion from T to U. +template <typename T, typename U> +inline Matcher<T> SafeMatcherCast(const Matcher<U>& matcher) { + // Enforce that T can be implicitly converted to U. + static_assert(std::is_convertible<const T&, const U&>::value, + "T must be implicitly convertible to U"); + // Enforce that we are not converting a non-reference type T to a reference + // type U. + GTEST_COMPILE_ASSERT_( + std::is_reference<T>::value || !std::is_reference<U>::value, + cannot_convert_non_reference_arg_to_reference); + // In case both T and U are arithmetic types, enforce that the + // conversion is not lossy. + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(T) RawT; + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(U) RawU; + constexpr bool kTIsOther = GMOCK_KIND_OF_(RawT) == internal::kOther; + constexpr bool kUIsOther = GMOCK_KIND_OF_(RawU) == internal::kOther; + GTEST_COMPILE_ASSERT_( + kTIsOther || kUIsOther || + (internal::LosslessArithmeticConvertible<RawT, RawU>::value), + conversion_of_arithmetic_types_must_be_lossless); + return MatcherCast<T>(matcher); +} + +// A<T>() returns a matcher that matches any value of type T. +template <typename T> +Matcher<T> A(); + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// If the explanation is not empty, prints it to the ostream. +inline void PrintIfNotEmpty(const std::string& explanation, + ::std::ostream* os) { + if (explanation != "" && os != nullptr) { + *os << ", " << explanation; + } +} + +// Returns true if the given type name is easy to read by a human. +// This is used to decide whether printing the type of a value might +// be helpful. +inline bool IsReadableTypeName(const std::string& type_name) { + // We consider a type name readable if it's short or doesn't contain + // a template or function type. + return (type_name.length() <= 20 || + type_name.find_first_of("<(") == std::string::npos); +} + +// Matches the value against the given matcher, prints the value and explains +// the match result to the listener. Returns the match result. +// 'listener' must not be NULL. +// Value cannot be passed by const reference, because some matchers take a +// non-const argument. +template <typename Value, typename T> +bool MatchPrintAndExplain(Value& value, const Matcher<T>& matcher, + MatchResultListener* listener) { + if (!listener->IsInterested()) { + // If the listener is not interested, we do not need to construct the + // inner explanation. + return matcher.Matches(value); + } + + StringMatchResultListener inner_listener; + const bool match = matcher.MatchAndExplain(value, &inner_listener); + + UniversalPrint(value, listener->stream()); +#if GTEST_HAS_RTTI + const std::string& type_name = GetTypeName<Value>(); + if (IsReadableTypeName(type_name)) + *listener->stream() << " (of type " << type_name << ")"; +#endif + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + + return match; +} + +// An internal helper class for doing compile-time loop on a tuple's +// fields. +template <size_t N> +class TuplePrefix { + public: + // TuplePrefix<N>::Matches(matcher_tuple, value_tuple) returns true + // if and only if the first N fields of matcher_tuple matches + // the first N fields of value_tuple, respectively. + template <typename MatcherTuple, typename ValueTuple> + static bool Matches(const MatcherTuple& matcher_tuple, + const ValueTuple& value_tuple) { + return TuplePrefix<N - 1>::Matches(matcher_tuple, value_tuple) && + std::get<N - 1>(matcher_tuple).Matches(std::get<N - 1>(value_tuple)); + } + + // TuplePrefix<N>::ExplainMatchFailuresTo(matchers, values, os) + // describes failures in matching the first N fields of matchers + // against the first N fields of values. If there is no failure, + // nothing will be streamed to os. + template <typename MatcherTuple, typename ValueTuple> + static void ExplainMatchFailuresTo(const MatcherTuple& matchers, + const ValueTuple& values, + ::std::ostream* os) { + // First, describes failures in the first N - 1 fields. + TuplePrefix<N - 1>::ExplainMatchFailuresTo(matchers, values, os); + + // Then describes the failure (if any) in the (N - 1)-th (0-based) + // field. + typename std::tuple_element<N - 1, MatcherTuple>::type matcher = + std::get<N - 1>(matchers); + typedef typename std::tuple_element<N - 1, ValueTuple>::type Value; + const Value& value = std::get<N - 1>(values); + StringMatchResultListener listener; + if (!matcher.MatchAndExplain(value, &listener)) { + *os << " Expected arg #" << N - 1 << ": "; + std::get<N - 1>(matchers).DescribeTo(os); + *os << "\n Actual: "; + // We remove the reference in type Value to prevent the + // universal printer from printing the address of value, which + // isn't interesting to the user most of the time. The + // matcher's MatchAndExplain() method handles the case when + // the address is interesting. + internal::UniversalPrint(value, os); + PrintIfNotEmpty(listener.str(), os); + *os << "\n"; + } + } +}; + +// The base case. +template <> +class TuplePrefix<0> { + public: + template <typename MatcherTuple, typename ValueTuple> + static bool Matches(const MatcherTuple& /* matcher_tuple */, + const ValueTuple& /* value_tuple */) { + return true; + } + + template <typename MatcherTuple, typename ValueTuple> + static void ExplainMatchFailuresTo(const MatcherTuple& /* matchers */, + const ValueTuple& /* values */, + ::std::ostream* /* os */) {} +}; + +// TupleMatches(matcher_tuple, value_tuple) returns true if and only if +// all matchers in matcher_tuple match the corresponding fields in +// value_tuple. It is a compiler error if matcher_tuple and +// value_tuple have different number of fields or incompatible field +// types. +template <typename MatcherTuple, typename ValueTuple> +bool TupleMatches(const MatcherTuple& matcher_tuple, + const ValueTuple& value_tuple) { + // Makes sure that matcher_tuple and value_tuple have the same + // number of fields. + GTEST_COMPILE_ASSERT_(std::tuple_size<MatcherTuple>::value == + std::tuple_size<ValueTuple>::value, + matcher_and_value_have_different_numbers_of_fields); + return TuplePrefix<std::tuple_size<ValueTuple>::value>::Matches(matcher_tuple, + value_tuple); +} + +// Describes failures in matching matchers against values. If there +// is no failure, nothing will be streamed to os. +template <typename MatcherTuple, typename ValueTuple> +void ExplainMatchFailureTupleTo(const MatcherTuple& matchers, + const ValueTuple& values, + ::std::ostream* os) { + TuplePrefix<std::tuple_size<MatcherTuple>::value>::ExplainMatchFailuresTo( + matchers, values, os); +} + +// TransformTupleValues and its helper. +// +// TransformTupleValuesHelper hides the internal machinery that +// TransformTupleValues uses to implement a tuple traversal. +template <typename Tuple, typename Func, typename OutIter> +class TransformTupleValuesHelper { + private: + typedef ::std::tuple_size<Tuple> TupleSize; + + public: + // For each member of tuple 't', taken in order, evaluates '*out++ = f(t)'. + // Returns the final value of 'out' in case the caller needs it. + static OutIter Run(Func f, const Tuple& t, OutIter out) { + return IterateOverTuple<Tuple, TupleSize::value>()(f, t, out); + } + + private: + template <typename Tup, size_t kRemainingSize> + struct IterateOverTuple { + OutIter operator() (Func f, const Tup& t, OutIter out) const { + *out++ = f(::std::get<TupleSize::value - kRemainingSize>(t)); + return IterateOverTuple<Tup, kRemainingSize - 1>()(f, t, out); + } + }; + template <typename Tup> + struct IterateOverTuple<Tup, 0> { + OutIter operator() (Func /* f */, const Tup& /* t */, OutIter out) const { + return out; + } + }; +}; + +// Successively invokes 'f(element)' on each element of the tuple 't', +// appending each result to the 'out' iterator. Returns the final value +// of 'out'. +template <typename Tuple, typename Func, typename OutIter> +OutIter TransformTupleValues(Func f, const Tuple& t, OutIter out) { + return TransformTupleValuesHelper<Tuple, Func, OutIter>::Run(f, t, out); +} + +// Implements _, a matcher that matches any value of any +// type. This is a polymorphic matcher, so we need a template type +// conversion operator to make it appearing as a Matcher<T> for any +// type T. +class AnythingMatcher { + public: + using is_gtest_matcher = void; + + template <typename T> + bool MatchAndExplain(const T& /* x */, std::ostream* /* listener */) const { + return true; + } + void DescribeTo(std::ostream* os) const { *os << "is anything"; } + void DescribeNegationTo(::std::ostream* os) const { + // This is mostly for completeness' sake, as it's not very useful + // to write Not(A<bool>()). However we cannot completely rule out + // such a possibility, and it doesn't hurt to be prepared. + *os << "never matches"; + } +}; + +// Implements the polymorphic IsNull() matcher, which matches any raw or smart +// pointer that is NULL. +class IsNullMatcher { + public: + template <typename Pointer> + bool MatchAndExplain(const Pointer& p, + MatchResultListener* /* listener */) const { + return p == nullptr; + } + + void DescribeTo(::std::ostream* os) const { *os << "is NULL"; } + void DescribeNegationTo(::std::ostream* os) const { + *os << "isn't NULL"; + } +}; + +// Implements the polymorphic NotNull() matcher, which matches any raw or smart +// pointer that is not NULL. +class NotNullMatcher { + public: + template <typename Pointer> + bool MatchAndExplain(const Pointer& p, + MatchResultListener* /* listener */) const { + return p != nullptr; + } + + void DescribeTo(::std::ostream* os) const { *os << "isn't NULL"; } + void DescribeNegationTo(::std::ostream* os) const { + *os << "is NULL"; + } +}; + +// Ref(variable) matches any argument that is a reference to +// 'variable'. This matcher is polymorphic as it can match any +// super type of the type of 'variable'. +// +// The RefMatcher template class implements Ref(variable). It can +// only be instantiated with a reference type. This prevents a user +// from mistakenly using Ref(x) to match a non-reference function +// argument. For example, the following will righteously cause a +// compiler error: +// +// int n; +// Matcher<int> m1 = Ref(n); // This won't compile. +// Matcher<int&> m2 = Ref(n); // This will compile. +template <typename T> +class RefMatcher; + +template <typename T> +class RefMatcher<T&> { + // Google Mock is a generic framework and thus needs to support + // mocking any function types, including those that take non-const + // reference arguments. Therefore the template parameter T (and + // Super below) can be instantiated to either a const type or a + // non-const type. + public: + // RefMatcher() takes a T& instead of const T&, as we want the + // compiler to catch using Ref(const_value) as a matcher for a + // non-const reference. + explicit RefMatcher(T& x) : object_(x) {} // NOLINT + + template <typename Super> + operator Matcher<Super&>() const { + // By passing object_ (type T&) to Impl(), which expects a Super&, + // we make sure that Super is a super type of T. In particular, + // this catches using Ref(const_value) as a matcher for a + // non-const reference, as you cannot implicitly convert a const + // reference to a non-const reference. + return MakeMatcher(new Impl<Super>(object_)); + } + + private: + template <typename Super> + class Impl : public MatcherInterface<Super&> { + public: + explicit Impl(Super& x) : object_(x) {} // NOLINT + + // MatchAndExplain() takes a Super& (as opposed to const Super&) + // in order to match the interface MatcherInterface<Super&>. + bool MatchAndExplain(Super& x, + MatchResultListener* listener) const override { + *listener << "which is located @" << static_cast<const void*>(&x); + return &x == &object_; + } + + void DescribeTo(::std::ostream* os) const override { + *os << "references the variable "; + UniversalPrinter<Super&>::Print(object_, os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "does not reference the variable "; + UniversalPrinter<Super&>::Print(object_, os); + } + + private: + const Super& object_; + }; + + T& object_; +}; + +// Polymorphic helper functions for narrow and wide string matchers. +inline bool CaseInsensitiveCStringEquals(const char* lhs, const char* rhs) { + return String::CaseInsensitiveCStringEquals(lhs, rhs); +} + +inline bool CaseInsensitiveCStringEquals(const wchar_t* lhs, + const wchar_t* rhs) { + return String::CaseInsensitiveWideCStringEquals(lhs, rhs); +} + +// String comparison for narrow or wide strings that can have embedded NUL +// characters. +template <typename StringType> +bool CaseInsensitiveStringEquals(const StringType& s1, + const StringType& s2) { + // Are the heads equal? + if (!CaseInsensitiveCStringEquals(s1.c_str(), s2.c_str())) { + return false; + } + + // Skip the equal heads. + const typename StringType::value_type nul = 0; + const size_t i1 = s1.find(nul), i2 = s2.find(nul); + + // Are we at the end of either s1 or s2? + if (i1 == StringType::npos || i2 == StringType::npos) { + return i1 == i2; + } + + // Are the tails equal? + return CaseInsensitiveStringEquals(s1.substr(i1 + 1), s2.substr(i2 + 1)); +} + +// String matchers. + +// Implements equality-based string matchers like StrEq, StrCaseNe, and etc. +template <typename StringType> +class StrEqualityMatcher { + public: + StrEqualityMatcher(StringType str, bool expect_eq, bool case_sensitive) + : string_(std::move(str)), + expect_eq_(expect_eq), + case_sensitive_(case_sensitive) {} + +#if GTEST_INTERNAL_HAS_STRING_VIEW + bool MatchAndExplain(const internal::StringView& s, + MatchResultListener* listener) const { + // This should fail to compile if StringView is used with wide + // strings. + const StringType& str = std::string(s); + return MatchAndExplain(str, listener); + } +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template <typename CharType> + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + if (s == nullptr) { + return !expect_eq_; + } + return MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringView has some interfering non-explicit constructors. + template <typename MatcheeStringType> + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType s2(s); + const bool eq = case_sensitive_ ? s2 == string_ : + CaseInsensitiveStringEquals(s2, string_); + return expect_eq_ == eq; + } + + void DescribeTo(::std::ostream* os) const { + DescribeToHelper(expect_eq_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + DescribeToHelper(!expect_eq_, os); + } + + private: + void DescribeToHelper(bool expect_eq, ::std::ostream* os) const { + *os << (expect_eq ? "is " : "isn't "); + *os << "equal to "; + if (!case_sensitive_) { + *os << "(ignoring case) "; + } + UniversalPrint(string_, os); + } + + const StringType string_; + const bool expect_eq_; + const bool case_sensitive_; +}; + +// Implements the polymorphic HasSubstr(substring) matcher, which +// can be used as a Matcher<T> as long as T can be converted to a +// string. +template <typename StringType> +class HasSubstrMatcher { + public: + explicit HasSubstrMatcher(const StringType& substring) + : substring_(substring) {} + +#if GTEST_INTERNAL_HAS_STRING_VIEW + bool MatchAndExplain(const internal::StringView& s, + MatchResultListener* listener) const { + // This should fail to compile if StringView is used with wide + // strings. + const StringType& str = std::string(s); + return MatchAndExplain(str, listener); + } +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template <typename CharType> + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != nullptr && MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringView has some interfering non-explicit constructors. + template <typename MatcheeStringType> + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + return StringType(s).find(substring_) != StringType::npos; + } + + // Describes what this matcher matches. + void DescribeTo(::std::ostream* os) const { + *os << "has substring "; + UniversalPrint(substring_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "has no substring "; + UniversalPrint(substring_, os); + } + + private: + const StringType substring_; +}; + +// Implements the polymorphic StartsWith(substring) matcher, which +// can be used as a Matcher<T> as long as T can be converted to a +// string. +template <typename StringType> +class StartsWithMatcher { + public: + explicit StartsWithMatcher(const StringType& prefix) : prefix_(prefix) { + } + +#if GTEST_INTERNAL_HAS_STRING_VIEW + bool MatchAndExplain(const internal::StringView& s, + MatchResultListener* listener) const { + // This should fail to compile if StringView is used with wide + // strings. + const StringType& str = std::string(s); + return MatchAndExplain(str, listener); + } +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template <typename CharType> + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != nullptr && MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringView has some interfering non-explicit constructors. + template <typename MatcheeStringType> + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + return s2.length() >= prefix_.length() && + s2.substr(0, prefix_.length()) == prefix_; + } + + void DescribeTo(::std::ostream* os) const { + *os << "starts with "; + UniversalPrint(prefix_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't start with "; + UniversalPrint(prefix_, os); + } + + private: + const StringType prefix_; +}; + +// Implements the polymorphic EndsWith(substring) matcher, which +// can be used as a Matcher<T> as long as T can be converted to a +// string. +template <typename StringType> +class EndsWithMatcher { + public: + explicit EndsWithMatcher(const StringType& suffix) : suffix_(suffix) {} + +#if GTEST_INTERNAL_HAS_STRING_VIEW + bool MatchAndExplain(const internal::StringView& s, + MatchResultListener* listener) const { + // This should fail to compile if StringView is used with wide + // strings. + const StringType& str = std::string(s); + return MatchAndExplain(str, listener); + } +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template <typename CharType> + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != nullptr && MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringView has some interfering non-explicit constructors. + template <typename MatcheeStringType> + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + return s2.length() >= suffix_.length() && + s2.substr(s2.length() - suffix_.length()) == suffix_; + } + + void DescribeTo(::std::ostream* os) const { + *os << "ends with "; + UniversalPrint(suffix_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't end with "; + UniversalPrint(suffix_, os); + } + + private: + const StringType suffix_; +}; + +// Implements a matcher that compares the two fields of a 2-tuple +// using one of the ==, <=, <, etc, operators. The two fields being +// compared don't have to have the same type. +// +// The matcher defined here is polymorphic (for example, Eq() can be +// used to match a std::tuple<int, short>, a std::tuple<const long&, double>, +// etc). Therefore we use a template type conversion operator in the +// implementation. +template <typename D, typename Op> +class PairMatchBase { + public: + template <typename T1, typename T2> + operator Matcher<::std::tuple<T1, T2>>() const { + return Matcher<::std::tuple<T1, T2>>(new Impl<const ::std::tuple<T1, T2>&>); + } + template <typename T1, typename T2> + operator Matcher<const ::std::tuple<T1, T2>&>() const { + return MakeMatcher(new Impl<const ::std::tuple<T1, T2>&>); + } + + private: + static ::std::ostream& GetDesc(::std::ostream& os) { // NOLINT + return os << D::Desc(); + } + + template <typename Tuple> + class Impl : public MatcherInterface<Tuple> { + public: + bool MatchAndExplain(Tuple args, + MatchResultListener* /* listener */) const override { + return Op()(::std::get<0>(args), ::std::get<1>(args)); + } + void DescribeTo(::std::ostream* os) const override { + *os << "are " << GetDesc; + } + void DescribeNegationTo(::std::ostream* os) const override { + *os << "aren't " << GetDesc; + } + }; +}; + +class Eq2Matcher : public PairMatchBase<Eq2Matcher, AnyEq> { + public: + static const char* Desc() { return "an equal pair"; } +}; +class Ne2Matcher : public PairMatchBase<Ne2Matcher, AnyNe> { + public: + static const char* Desc() { return "an unequal pair"; } +}; +class Lt2Matcher : public PairMatchBase<Lt2Matcher, AnyLt> { + public: + static const char* Desc() { return "a pair where the first < the second"; } +}; +class Gt2Matcher : public PairMatchBase<Gt2Matcher, AnyGt> { + public: + static const char* Desc() { return "a pair where the first > the second"; } +}; +class Le2Matcher : public PairMatchBase<Le2Matcher, AnyLe> { + public: + static const char* Desc() { return "a pair where the first <= the second"; } +}; +class Ge2Matcher : public PairMatchBase<Ge2Matcher, AnyGe> { + public: + static const char* Desc() { return "a pair where the first >= the second"; } +}; + +// Implements the Not(...) matcher for a particular argument type T. +// We do not nest it inside the NotMatcher class template, as that +// will prevent different instantiations of NotMatcher from sharing +// the same NotMatcherImpl<T> class. +template <typename T> +class NotMatcherImpl : public MatcherInterface<const T&> { + public: + explicit NotMatcherImpl(const Matcher<T>& matcher) + : matcher_(matcher) {} + + bool MatchAndExplain(const T& x, + MatchResultListener* listener) const override { + return !matcher_.MatchAndExplain(x, listener); + } + + void DescribeTo(::std::ostream* os) const override { + matcher_.DescribeNegationTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + matcher_.DescribeTo(os); + } + + private: + const Matcher<T> matcher_; +}; + +// Implements the Not(m) matcher, which matches a value that doesn't +// match matcher m. +template <typename InnerMatcher> +class NotMatcher { + public: + explicit NotMatcher(InnerMatcher matcher) : matcher_(matcher) {} + + // This template type conversion operator allows Not(m) to be used + // to match any type m can match. + template <typename T> + operator Matcher<T>() const { + return Matcher<T>(new NotMatcherImpl<T>(SafeMatcherCast<T>(matcher_))); + } + + private: + InnerMatcher matcher_; +}; + +// Implements the AllOf(m1, m2) matcher for a particular argument type +// T. We do not nest it inside the BothOfMatcher class template, as +// that will prevent different instantiations of BothOfMatcher from +// sharing the same BothOfMatcherImpl<T> class. +template <typename T> +class AllOfMatcherImpl : public MatcherInterface<const T&> { + public: + explicit AllOfMatcherImpl(std::vector<Matcher<T> > matchers) + : matchers_(std::move(matchers)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "("; + for (size_t i = 0; i < matchers_.size(); ++i) { + if (i != 0) *os << ") and ("; + matchers_[i].DescribeTo(os); + } + *os << ")"; + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "("; + for (size_t i = 0; i < matchers_.size(); ++i) { + if (i != 0) *os << ") or ("; + matchers_[i].DescribeNegationTo(os); + } + *os << ")"; + } + + bool MatchAndExplain(const T& x, + MatchResultListener* listener) const override { + // If either matcher1_ or matcher2_ doesn't match x, we only need + // to explain why one of them fails. + std::string all_match_result; + + for (size_t i = 0; i < matchers_.size(); ++i) { + StringMatchResultListener slistener; + if (matchers_[i].MatchAndExplain(x, &slistener)) { + if (all_match_result.empty()) { + all_match_result = slistener.str(); + } else { + std::string result = slistener.str(); + if (!result.empty()) { + all_match_result += ", and "; + all_match_result += result; + } + } + } else { + *listener << slistener.str(); + return false; + } + } + + // Otherwise we need to explain why *both* of them match. + *listener << all_match_result; + return true; + } + + private: + const std::vector<Matcher<T> > matchers_; +}; + +// VariadicMatcher is used for the variadic implementation of +// AllOf(m_1, m_2, ...) and AnyOf(m_1, m_2, ...). +// CombiningMatcher<T> is used to recursively combine the provided matchers +// (of type Args...). +template <template <typename T> class CombiningMatcher, typename... Args> +class VariadicMatcher { + public: + VariadicMatcher(const Args&... matchers) // NOLINT + : matchers_(matchers...) { + static_assert(sizeof...(Args) > 0, "Must have at least one matcher."); + } + + VariadicMatcher(const VariadicMatcher&) = default; + VariadicMatcher& operator=(const VariadicMatcher&) = delete; + + // This template type conversion operator allows an + // VariadicMatcher<Matcher1, Matcher2...> object to match any type that + // all of the provided matchers (Matcher1, Matcher2, ...) can match. + template <typename T> + operator Matcher<T>() const { + std::vector<Matcher<T> > values; + CreateVariadicMatcher<T>(&values, std::integral_constant<size_t, 0>()); + return Matcher<T>(new CombiningMatcher<T>(std::move(values))); + } + + private: + template <typename T, size_t I> + void CreateVariadicMatcher(std::vector<Matcher<T> >* values, + std::integral_constant<size_t, I>) const { + values->push_back(SafeMatcherCast<T>(std::get<I>(matchers_))); + CreateVariadicMatcher<T>(values, std::integral_constant<size_t, I + 1>()); + } + + template <typename T> + void CreateVariadicMatcher( + std::vector<Matcher<T> >*, + std::integral_constant<size_t, sizeof...(Args)>) const {} + + std::tuple<Args...> matchers_; +}; + +template <typename... Args> +using AllOfMatcher = VariadicMatcher<AllOfMatcherImpl, Args...>; + +// Implements the AnyOf(m1, m2) matcher for a particular argument type +// T. We do not nest it inside the AnyOfMatcher class template, as +// that will prevent different instantiations of AnyOfMatcher from +// sharing the same EitherOfMatcherImpl<T> class. +template <typename T> +class AnyOfMatcherImpl : public MatcherInterface<const T&> { + public: + explicit AnyOfMatcherImpl(std::vector<Matcher<T> > matchers) + : matchers_(std::move(matchers)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "("; + for (size_t i = 0; i < matchers_.size(); ++i) { + if (i != 0) *os << ") or ("; + matchers_[i].DescribeTo(os); + } + *os << ")"; + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "("; + for (size_t i = 0; i < matchers_.size(); ++i) { + if (i != 0) *os << ") and ("; + matchers_[i].DescribeNegationTo(os); + } + *os << ")"; + } + + bool MatchAndExplain(const T& x, + MatchResultListener* listener) const override { + std::string no_match_result; + + // If either matcher1_ or matcher2_ matches x, we just need to + // explain why *one* of them matches. + for (size_t i = 0; i < matchers_.size(); ++i) { + StringMatchResultListener slistener; + if (matchers_[i].MatchAndExplain(x, &slistener)) { + *listener << slistener.str(); + return true; + } else { + if (no_match_result.empty()) { + no_match_result = slistener.str(); + } else { + std::string result = slistener.str(); + if (!result.empty()) { + no_match_result += ", and "; + no_match_result += result; + } + } + } + } + + // Otherwise we need to explain why *both* of them fail. + *listener << no_match_result; + return false; + } + + private: + const std::vector<Matcher<T> > matchers_; +}; + +// AnyOfMatcher is used for the variadic implementation of AnyOf(m_1, m_2, ...). +template <typename... Args> +using AnyOfMatcher = VariadicMatcher<AnyOfMatcherImpl, Args...>; + +// Wrapper for implementation of Any/AllOfArray(). +template <template <class> class MatcherImpl, typename T> +class SomeOfArrayMatcher { + public: + // Constructs the matcher from a sequence of element values or + // element matchers. + template <typename Iter> + SomeOfArrayMatcher(Iter first, Iter last) : matchers_(first, last) {} + + template <typename U> + operator Matcher<U>() const { // NOLINT + using RawU = typename std::decay<U>::type; + std::vector<Matcher<RawU>> matchers; + for (const auto& matcher : matchers_) { + matchers.push_back(MatcherCast<RawU>(matcher)); + } + return Matcher<U>(new MatcherImpl<RawU>(std::move(matchers))); + } + + private: + const ::std::vector<T> matchers_; +}; + +template <typename T> +using AllOfArrayMatcher = SomeOfArrayMatcher<AllOfMatcherImpl, T>; + +template <typename T> +using AnyOfArrayMatcher = SomeOfArrayMatcher<AnyOfMatcherImpl, T>; + +// Used for implementing Truly(pred), which turns a predicate into a +// matcher. +template <typename Predicate> +class TrulyMatcher { + public: + explicit TrulyMatcher(Predicate pred) : predicate_(pred) {} + + // This method template allows Truly(pred) to be used as a matcher + // for type T where T is the argument type of predicate 'pred'. The + // argument is passed by reference as the predicate may be + // interested in the address of the argument. + template <typename T> + bool MatchAndExplain(T& x, // NOLINT + MatchResultListener* listener) const { + // Without the if-statement, MSVC sometimes warns about converting + // a value to bool (warning 4800). + // + // We cannot write 'return !!predicate_(x);' as that doesn't work + // when predicate_(x) returns a class convertible to bool but + // having no operator!(). + if (predicate_(x)) + return true; + *listener << "didn't satisfy the given predicate"; + return false; + } + + void DescribeTo(::std::ostream* os) const { + *os << "satisfies the given predicate"; + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't satisfy the given predicate"; + } + + private: + Predicate predicate_; +}; + +// Used for implementing Matches(matcher), which turns a matcher into +// a predicate. +template <typename M> +class MatcherAsPredicate { + public: + explicit MatcherAsPredicate(M matcher) : matcher_(matcher) {} + + // This template operator() allows Matches(m) to be used as a + // predicate on type T where m is a matcher on type T. + // + // The argument x is passed by reference instead of by value, as + // some matcher may be interested in its address (e.g. as in + // Matches(Ref(n))(x)). + template <typename T> + bool operator()(const T& x) const { + // We let matcher_ commit to a particular type here instead of + // when the MatcherAsPredicate object was constructed. This + // allows us to write Matches(m) where m is a polymorphic matcher + // (e.g. Eq(5)). + // + // If we write Matcher<T>(matcher_).Matches(x) here, it won't + // compile when matcher_ has type Matcher<const T&>; if we write + // Matcher<const T&>(matcher_).Matches(x) here, it won't compile + // when matcher_ has type Matcher<T>; if we just write + // matcher_.Matches(x), it won't compile when matcher_ is + // polymorphic, e.g. Eq(5). + // + // MatcherCast<const T&>() is necessary for making the code work + // in all of the above situations. + return MatcherCast<const T&>(matcher_).Matches(x); + } + + private: + M matcher_; +}; + +// For implementing ASSERT_THAT() and EXPECT_THAT(). The template +// argument M must be a type that can be converted to a matcher. +template <typename M> +class PredicateFormatterFromMatcher { + public: + explicit PredicateFormatterFromMatcher(M m) : matcher_(std::move(m)) {} + + // This template () operator allows a PredicateFormatterFromMatcher + // object to act as a predicate-formatter suitable for using with + // Google Test's EXPECT_PRED_FORMAT1() macro. + template <typename T> + AssertionResult operator()(const char* value_text, const T& x) const { + // We convert matcher_ to a Matcher<const T&> *now* instead of + // when the PredicateFormatterFromMatcher object was constructed, + // as matcher_ may be polymorphic (e.g. NotNull()) and we won't + // know which type to instantiate it to until we actually see the + // type of x here. + // + // We write SafeMatcherCast<const T&>(matcher_) instead of + // Matcher<const T&>(matcher_), as the latter won't compile when + // matcher_ has type Matcher<T> (e.g. An<int>()). + // We don't write MatcherCast<const T&> either, as that allows + // potentially unsafe downcasting of the matcher argument. + const Matcher<const T&> matcher = SafeMatcherCast<const T&>(matcher_); + + // The expected path here is that the matcher should match (i.e. that most + // tests pass) so optimize for this case. + if (matcher.Matches(x)) { + return AssertionSuccess(); + } + + ::std::stringstream ss; + ss << "Value of: " << value_text << "\n" + << "Expected: "; + matcher.DescribeTo(&ss); + + // Rerun the matcher to "PrintAndExplain" the failure. + StringMatchResultListener listener; + if (MatchPrintAndExplain(x, matcher, &listener)) { + ss << "\n The matcher failed on the initial attempt; but passed when " + "rerun to generate the explanation."; + } + ss << "\n Actual: " << listener.str(); + return AssertionFailure() << ss.str(); + } + + private: + const M matcher_; +}; + +// A helper function for converting a matcher to a predicate-formatter +// without the user needing to explicitly write the type. This is +// used for implementing ASSERT_THAT() and EXPECT_THAT(). +// Implementation detail: 'matcher' is received by-value to force decaying. +template <typename M> +inline PredicateFormatterFromMatcher<M> +MakePredicateFormatterFromMatcher(M matcher) { + return PredicateFormatterFromMatcher<M>(std::move(matcher)); +} + +// Implements the polymorphic IsNan() matcher, which matches any floating type +// value that is Nan. +class IsNanMatcher { + public: + template <typename FloatType> + bool MatchAndExplain(const FloatType& f, + MatchResultListener* /* listener */) const { + return (::std::isnan)(f); + } + + void DescribeTo(::std::ostream* os) const { *os << "is NaN"; } + void DescribeNegationTo(::std::ostream* os) const { + *os << "isn't NaN"; + } +}; + +// Implements the polymorphic floating point equality matcher, which matches +// two float values using ULP-based approximation or, optionally, a +// user-specified epsilon. The template is meant to be instantiated with +// FloatType being either float or double. +template <typename FloatType> +class FloatingEqMatcher { + public: + // Constructor for FloatingEqMatcher. + // The matcher's input will be compared with expected. The matcher treats two + // NANs as equal if nan_eq_nan is true. Otherwise, under IEEE standards, + // equality comparisons between NANs will always return false. We specify a + // negative max_abs_error_ term to indicate that ULP-based approximation will + // be used for comparison. + FloatingEqMatcher(FloatType expected, bool nan_eq_nan) : + expected_(expected), nan_eq_nan_(nan_eq_nan), max_abs_error_(-1) { + } + + // Constructor that supports a user-specified max_abs_error that will be used + // for comparison instead of ULP-based approximation. The max absolute + // should be non-negative. + FloatingEqMatcher(FloatType expected, bool nan_eq_nan, + FloatType max_abs_error) + : expected_(expected), + nan_eq_nan_(nan_eq_nan), + max_abs_error_(max_abs_error) { + GTEST_CHECK_(max_abs_error >= 0) + << ", where max_abs_error is" << max_abs_error; + } + + // Implements floating point equality matcher as a Matcher<T>. + template <typename T> + class Impl : public MatcherInterface<T> { + public: + Impl(FloatType expected, bool nan_eq_nan, FloatType max_abs_error) + : expected_(expected), + nan_eq_nan_(nan_eq_nan), + max_abs_error_(max_abs_error) {} + + bool MatchAndExplain(T value, + MatchResultListener* listener) const override { + const FloatingPoint<FloatType> actual(value), expected(expected_); + + // Compares NaNs first, if nan_eq_nan_ is true. + if (actual.is_nan() || expected.is_nan()) { + if (actual.is_nan() && expected.is_nan()) { + return nan_eq_nan_; + } + // One is nan; the other is not nan. + return false; + } + if (HasMaxAbsError()) { + // We perform an equality check so that inf will match inf, regardless + // of error bounds. If the result of value - expected_ would result in + // overflow or if either value is inf, the default result is infinity, + // which should only match if max_abs_error_ is also infinity. + if (value == expected_) { + return true; + } + + const FloatType diff = value - expected_; + if (::std::fabs(diff) <= max_abs_error_) { + return true; + } + + if (listener->IsInterested()) { + *listener << "which is " << diff << " from " << expected_; + } + return false; + } else { + return actual.AlmostEquals(expected); + } + } + + void DescribeTo(::std::ostream* os) const override { + // os->precision() returns the previously set precision, which we + // store to restore the ostream to its original configuration + // after outputting. + const ::std::streamsize old_precision = os->precision( + ::std::numeric_limits<FloatType>::digits10 + 2); + if (FloatingPoint<FloatType>(expected_).is_nan()) { + if (nan_eq_nan_) { + *os << "is NaN"; + } else { + *os << "never matches"; + } + } else { + *os << "is approximately " << expected_; + if (HasMaxAbsError()) { + *os << " (absolute error <= " << max_abs_error_ << ")"; + } + } + os->precision(old_precision); + } + + void DescribeNegationTo(::std::ostream* os) const override { + // As before, get original precision. + const ::std::streamsize old_precision = os->precision( + ::std::numeric_limits<FloatType>::digits10 + 2); + if (FloatingPoint<FloatType>(expected_).is_nan()) { + if (nan_eq_nan_) { + *os << "isn't NaN"; + } else { + *os << "is anything"; + } + } else { + *os << "isn't approximately " << expected_; + if (HasMaxAbsError()) { + *os << " (absolute error > " << max_abs_error_ << ")"; + } + } + // Restore original precision. + os->precision(old_precision); + } + + private: + bool HasMaxAbsError() const { + return max_abs_error_ >= 0; + } + + const FloatType expected_; + const bool nan_eq_nan_; + // max_abs_error will be used for value comparison when >= 0. + const FloatType max_abs_error_; + }; + + // The following 3 type conversion operators allow FloatEq(expected) and + // NanSensitiveFloatEq(expected) to be used as a Matcher<float>, a + // Matcher<const float&>, or a Matcher<float&>, but nothing else. + operator Matcher<FloatType>() const { + return MakeMatcher( + new Impl<FloatType>(expected_, nan_eq_nan_, max_abs_error_)); + } + + operator Matcher<const FloatType&>() const { + return MakeMatcher( + new Impl<const FloatType&>(expected_, nan_eq_nan_, max_abs_error_)); + } + + operator Matcher<FloatType&>() const { + return MakeMatcher( + new Impl<FloatType&>(expected_, nan_eq_nan_, max_abs_error_)); + } + + private: + const FloatType expected_; + const bool nan_eq_nan_; + // max_abs_error will be used for value comparison when >= 0. + const FloatType max_abs_error_; +}; + +// A 2-tuple ("binary") wrapper around FloatingEqMatcher: +// FloatingEq2Matcher() matches (x, y) by matching FloatingEqMatcher(x, false) +// against y, and FloatingEq2Matcher(e) matches FloatingEqMatcher(x, false, e) +// against y. The former implements "Eq", the latter "Near". At present, there +// is no version that compares NaNs as equal. +template <typename FloatType> +class FloatingEq2Matcher { + public: + FloatingEq2Matcher() { Init(-1, false); } + + explicit FloatingEq2Matcher(bool nan_eq_nan) { Init(-1, nan_eq_nan); } + + explicit FloatingEq2Matcher(FloatType max_abs_error) { + Init(max_abs_error, false); + } + + FloatingEq2Matcher(FloatType max_abs_error, bool nan_eq_nan) { + Init(max_abs_error, nan_eq_nan); + } + + template <typename T1, typename T2> + operator Matcher<::std::tuple<T1, T2>>() const { + return MakeMatcher( + new Impl<::std::tuple<T1, T2>>(max_abs_error_, nan_eq_nan_)); + } + template <typename T1, typename T2> + operator Matcher<const ::std::tuple<T1, T2>&>() const { + return MakeMatcher( + new Impl<const ::std::tuple<T1, T2>&>(max_abs_error_, nan_eq_nan_)); + } + + private: + static ::std::ostream& GetDesc(::std::ostream& os) { // NOLINT + return os << "an almost-equal pair"; + } + + template <typename Tuple> + class Impl : public MatcherInterface<Tuple> { + public: + Impl(FloatType max_abs_error, bool nan_eq_nan) : + max_abs_error_(max_abs_error), + nan_eq_nan_(nan_eq_nan) {} + + bool MatchAndExplain(Tuple args, + MatchResultListener* listener) const override { + if (max_abs_error_ == -1) { + FloatingEqMatcher<FloatType> fm(::std::get<0>(args), nan_eq_nan_); + return static_cast<Matcher<FloatType>>(fm).MatchAndExplain( + ::std::get<1>(args), listener); + } else { + FloatingEqMatcher<FloatType> fm(::std::get<0>(args), nan_eq_nan_, + max_abs_error_); + return static_cast<Matcher<FloatType>>(fm).MatchAndExplain( + ::std::get<1>(args), listener); + } + } + void DescribeTo(::std::ostream* os) const override { + *os << "are " << GetDesc; + } + void DescribeNegationTo(::std::ostream* os) const override { + *os << "aren't " << GetDesc; + } + + private: + FloatType max_abs_error_; + const bool nan_eq_nan_; + }; + + void Init(FloatType max_abs_error_val, bool nan_eq_nan_val) { + max_abs_error_ = max_abs_error_val; + nan_eq_nan_ = nan_eq_nan_val; + } + FloatType max_abs_error_; + bool nan_eq_nan_; +}; + +// Implements the Pointee(m) matcher for matching a pointer whose +// pointee matches matcher m. The pointer can be either raw or smart. +template <typename InnerMatcher> +class PointeeMatcher { + public: + explicit PointeeMatcher(const InnerMatcher& matcher) : matcher_(matcher) {} + + // This type conversion operator template allows Pointee(m) to be + // used as a matcher for any pointer type whose pointee type is + // compatible with the inner matcher, where type Pointer can be + // either a raw pointer or a smart pointer. + // + // The reason we do this instead of relying on + // MakePolymorphicMatcher() is that the latter is not flexible + // enough for implementing the DescribeTo() method of Pointee(). + template <typename Pointer> + operator Matcher<Pointer>() const { + return Matcher<Pointer>(new Impl<const Pointer&>(matcher_)); + } + + private: + // The monomorphic implementation that works for a particular pointer type. + template <typename Pointer> + class Impl : public MatcherInterface<Pointer> { + public: + using Pointee = + typename std::pointer_traits<GTEST_REMOVE_REFERENCE_AND_CONST_( + Pointer)>::element_type; + + explicit Impl(const InnerMatcher& matcher) + : matcher_(MatcherCast<const Pointee&>(matcher)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "points to a value that "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "does not point to a value that "; + matcher_.DescribeTo(os); + } + + bool MatchAndExplain(Pointer pointer, + MatchResultListener* listener) const override { + if (GetRawPointer(pointer) == nullptr) return false; + + *listener << "which points to "; + return MatchPrintAndExplain(*pointer, matcher_, listener); + } + + private: + const Matcher<const Pointee&> matcher_; + }; + + const InnerMatcher matcher_; +}; + +// Implements the Pointer(m) matcher +// Implements the Pointer(m) matcher for matching a pointer that matches matcher +// m. The pointer can be either raw or smart, and will match `m` against the +// raw pointer. +template <typename InnerMatcher> +class PointerMatcher { + public: + explicit PointerMatcher(const InnerMatcher& matcher) : matcher_(matcher) {} + + // This type conversion operator template allows Pointer(m) to be + // used as a matcher for any pointer type whose pointer type is + // compatible with the inner matcher, where type PointerType can be + // either a raw pointer or a smart pointer. + // + // The reason we do this instead of relying on + // MakePolymorphicMatcher() is that the latter is not flexible + // enough for implementing the DescribeTo() method of Pointer(). + template <typename PointerType> + operator Matcher<PointerType>() const { // NOLINT + return Matcher<PointerType>(new Impl<const PointerType&>(matcher_)); + } + + private: + // The monomorphic implementation that works for a particular pointer type. + template <typename PointerType> + class Impl : public MatcherInterface<PointerType> { + public: + using Pointer = + const typename std::pointer_traits<GTEST_REMOVE_REFERENCE_AND_CONST_( + PointerType)>::element_type*; + + explicit Impl(const InnerMatcher& matcher) + : matcher_(MatcherCast<Pointer>(matcher)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "is a pointer that "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "is not a pointer that "; + matcher_.DescribeTo(os); + } + + bool MatchAndExplain(PointerType pointer, + MatchResultListener* listener) const override { + *listener << "which is a pointer that "; + Pointer p = GetRawPointer(pointer); + return MatchPrintAndExplain(p, matcher_, listener); + } + + private: + Matcher<Pointer> matcher_; + }; + + const InnerMatcher matcher_; +}; + +#if GTEST_HAS_RTTI +// Implements the WhenDynamicCastTo<T>(m) matcher that matches a pointer or +// reference that matches inner_matcher when dynamic_cast<T> is applied. +// The result of dynamic_cast<To> is forwarded to the inner matcher. +// If To is a pointer and the cast fails, the inner matcher will receive NULL. +// If To is a reference and the cast fails, this matcher returns false +// immediately. +template <typename To> +class WhenDynamicCastToMatcherBase { + public: + explicit WhenDynamicCastToMatcherBase(const Matcher<To>& matcher) + : matcher_(matcher) {} + + void DescribeTo(::std::ostream* os) const { + GetCastTypeDescription(os); + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const { + GetCastTypeDescription(os); + matcher_.DescribeNegationTo(os); + } + + protected: + const Matcher<To> matcher_; + + static std::string GetToName() { + return GetTypeName<To>(); + } + + private: + static void GetCastTypeDescription(::std::ostream* os) { + *os << "when dynamic_cast to " << GetToName() << ", "; + } +}; + +// Primary template. +// To is a pointer. Cast and forward the result. +template <typename To> +class WhenDynamicCastToMatcher : public WhenDynamicCastToMatcherBase<To> { + public: + explicit WhenDynamicCastToMatcher(const Matcher<To>& matcher) + : WhenDynamicCastToMatcherBase<To>(matcher) {} + + template <typename From> + bool MatchAndExplain(From from, MatchResultListener* listener) const { + To to = dynamic_cast<To>(from); + return MatchPrintAndExplain(to, this->matcher_, listener); + } +}; + +// Specialize for references. +// In this case we return false if the dynamic_cast fails. +template <typename To> +class WhenDynamicCastToMatcher<To&> : public WhenDynamicCastToMatcherBase<To&> { + public: + explicit WhenDynamicCastToMatcher(const Matcher<To&>& matcher) + : WhenDynamicCastToMatcherBase<To&>(matcher) {} + + template <typename From> + bool MatchAndExplain(From& from, MatchResultListener* listener) const { + // We don't want an std::bad_cast here, so do the cast with pointers. + To* to = dynamic_cast<To*>(&from); + if (to == nullptr) { + *listener << "which cannot be dynamic_cast to " << this->GetToName(); + return false; + } + return MatchPrintAndExplain(*to, this->matcher_, listener); + } +}; +#endif // GTEST_HAS_RTTI + +// Implements the Field() matcher for matching a field (i.e. member +// variable) of an object. +template <typename Class, typename FieldType> +class FieldMatcher { + public: + FieldMatcher(FieldType Class::*field, + const Matcher<const FieldType&>& matcher) + : field_(field), matcher_(matcher), whose_field_("whose given field ") {} + + FieldMatcher(const std::string& field_name, FieldType Class::*field, + const Matcher<const FieldType&>& matcher) + : field_(field), + matcher_(matcher), + whose_field_("whose field `" + field_name + "` ") {} + + void DescribeTo(::std::ostream* os) const { + *os << "is an object " << whose_field_; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "is an object " << whose_field_; + matcher_.DescribeNegationTo(os); + } + + template <typename T> + bool MatchAndExplain(const T& value, MatchResultListener* listener) const { + // FIXME: The dispatch on std::is_pointer was introduced as a workaround for + // a compiler bug, and can now be removed. + return MatchAndExplainImpl( + typename std::is_pointer<typename std::remove_const<T>::type>::type(), + value, listener); + } + + private: + bool MatchAndExplainImpl(std::false_type /* is_not_pointer */, + const Class& obj, + MatchResultListener* listener) const { + *listener << whose_field_ << "is "; + return MatchPrintAndExplain(obj.*field_, matcher_, listener); + } + + bool MatchAndExplainImpl(std::true_type /* is_pointer */, const Class* p, + MatchResultListener* listener) const { + if (p == nullptr) return false; + + *listener << "which points to an object "; + // Since *p has a field, it must be a class/struct/union type and + // thus cannot be a pointer. Therefore we pass false_type() as + // the first argument. + return MatchAndExplainImpl(std::false_type(), *p, listener); + } + + const FieldType Class::*field_; + const Matcher<const FieldType&> matcher_; + + // Contains either "whose given field " if the name of the field is unknown + // or "whose field `name_of_field` " if the name is known. + const std::string whose_field_; +}; + +// Implements the Property() matcher for matching a property +// (i.e. return value of a getter method) of an object. +// +// Property is a const-qualified member function of Class returning +// PropertyType. +template <typename Class, typename PropertyType, typename Property> +class PropertyMatcher { + public: + typedef const PropertyType& RefToConstProperty; + + PropertyMatcher(Property property, const Matcher<RefToConstProperty>& matcher) + : property_(property), + matcher_(matcher), + whose_property_("whose given property ") {} + + PropertyMatcher(const std::string& property_name, Property property, + const Matcher<RefToConstProperty>& matcher) + : property_(property), + matcher_(matcher), + whose_property_("whose property `" + property_name + "` ") {} + + void DescribeTo(::std::ostream* os) const { + *os << "is an object " << whose_property_; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "is an object " << whose_property_; + matcher_.DescribeNegationTo(os); + } + + template <typename T> + bool MatchAndExplain(const T&value, MatchResultListener* listener) const { + return MatchAndExplainImpl( + typename std::is_pointer<typename std::remove_const<T>::type>::type(), + value, listener); + } + + private: + bool MatchAndExplainImpl(std::false_type /* is_not_pointer */, + const Class& obj, + MatchResultListener* listener) const { + *listener << whose_property_ << "is "; + // Cannot pass the return value (for example, int) to MatchPrintAndExplain, + // which takes a non-const reference as argument. + RefToConstProperty result = (obj.*property_)(); + return MatchPrintAndExplain(result, matcher_, listener); + } + + bool MatchAndExplainImpl(std::true_type /* is_pointer */, const Class* p, + MatchResultListener* listener) const { + if (p == nullptr) return false; + + *listener << "which points to an object "; + // Since *p has a property method, it must be a class/struct/union + // type and thus cannot be a pointer. Therefore we pass + // false_type() as the first argument. + return MatchAndExplainImpl(std::false_type(), *p, listener); + } + + Property property_; + const Matcher<RefToConstProperty> matcher_; + + // Contains either "whose given property " if the name of the property is + // unknown or "whose property `name_of_property` " if the name is known. + const std::string whose_property_; +}; + +// Type traits specifying various features of different functors for ResultOf. +// The default template specifies features for functor objects. +template <typename Functor> +struct CallableTraits { + typedef Functor StorageType; + + static void CheckIsValid(Functor /* functor */) {} + + template <typename T> + static auto Invoke(Functor f, const T& arg) -> decltype(f(arg)) { + return f(arg); + } +}; + +// Specialization for function pointers. +template <typename ArgType, typename ResType> +struct CallableTraits<ResType(*)(ArgType)> { + typedef ResType ResultType; + typedef ResType(*StorageType)(ArgType); + + static void CheckIsValid(ResType(*f)(ArgType)) { + GTEST_CHECK_(f != nullptr) + << "NULL function pointer is passed into ResultOf()."; + } + template <typename T> + static ResType Invoke(ResType(*f)(ArgType), T arg) { + return (*f)(arg); + } +}; + +// Implements the ResultOf() matcher for matching a return value of a +// unary function of an object. +template <typename Callable, typename InnerMatcher> +class ResultOfMatcher { + public: + ResultOfMatcher(Callable callable, InnerMatcher matcher) + : callable_(std::move(callable)), matcher_(std::move(matcher)) { + CallableTraits<Callable>::CheckIsValid(callable_); + } + + template <typename T> + operator Matcher<T>() const { + return Matcher<T>(new Impl<const T&>(callable_, matcher_)); + } + + private: + typedef typename CallableTraits<Callable>::StorageType CallableStorageType; + + template <typename T> + class Impl : public MatcherInterface<T> { + using ResultType = decltype(CallableTraits<Callable>::template Invoke<T>( + std::declval<CallableStorageType>(), std::declval<T>())); + + public: + template <typename M> + Impl(const CallableStorageType& callable, const M& matcher) + : callable_(callable), matcher_(MatcherCast<ResultType>(matcher)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "is mapped by the given callable to a value that "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "is mapped by the given callable to a value that "; + matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain(T obj, MatchResultListener* listener) const override { + *listener << "which is mapped by the given callable to "; + // Cannot pass the return value directly to MatchPrintAndExplain, which + // takes a non-const reference as argument. + // Also, specifying template argument explicitly is needed because T could + // be a non-const reference (e.g. Matcher<Uncopyable&>). + ResultType result = + CallableTraits<Callable>::template Invoke<T>(callable_, obj); + return MatchPrintAndExplain(result, matcher_, listener); + } + + private: + // Functors often define operator() as non-const method even though + // they are actually stateless. But we need to use them even when + // 'this' is a const pointer. It's the user's responsibility not to + // use stateful callables with ResultOf(), which doesn't guarantee + // how many times the callable will be invoked. + mutable CallableStorageType callable_; + const Matcher<ResultType> matcher_; + }; // class Impl + + const CallableStorageType callable_; + const InnerMatcher matcher_; +}; + +// Implements a matcher that checks the size of an STL-style container. +template <typename SizeMatcher> +class SizeIsMatcher { + public: + explicit SizeIsMatcher(const SizeMatcher& size_matcher) + : size_matcher_(size_matcher) { + } + + template <typename Container> + operator Matcher<Container>() const { + return Matcher<Container>(new Impl<const Container&>(size_matcher_)); + } + + template <typename Container> + class Impl : public MatcherInterface<Container> { + public: + using SizeType = decltype(std::declval<Container>().size()); + explicit Impl(const SizeMatcher& size_matcher) + : size_matcher_(MatcherCast<SizeType>(size_matcher)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "size "; + size_matcher_.DescribeTo(os); + } + void DescribeNegationTo(::std::ostream* os) const override { + *os << "size "; + size_matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain(Container container, + MatchResultListener* listener) const override { + SizeType size = container.size(); + StringMatchResultListener size_listener; + const bool result = size_matcher_.MatchAndExplain(size, &size_listener); + *listener + << "whose size " << size << (result ? " matches" : " doesn't match"); + PrintIfNotEmpty(size_listener.str(), listener->stream()); + return result; + } + + private: + const Matcher<SizeType> size_matcher_; + }; + + private: + const SizeMatcher size_matcher_; +}; + +// Implements a matcher that checks the begin()..end() distance of an STL-style +// container. +template <typename DistanceMatcher> +class BeginEndDistanceIsMatcher { + public: + explicit BeginEndDistanceIsMatcher(const DistanceMatcher& distance_matcher) + : distance_matcher_(distance_matcher) {} + + template <typename Container> + operator Matcher<Container>() const { + return Matcher<Container>(new Impl<const Container&>(distance_matcher_)); + } + + template <typename Container> + class Impl : public MatcherInterface<Container> { + public: + typedef internal::StlContainerView< + GTEST_REMOVE_REFERENCE_AND_CONST_(Container)> ContainerView; + typedef typename std::iterator_traits< + typename ContainerView::type::const_iterator>::difference_type + DistanceType; + explicit Impl(const DistanceMatcher& distance_matcher) + : distance_matcher_(MatcherCast<DistanceType>(distance_matcher)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "distance between begin() and end() "; + distance_matcher_.DescribeTo(os); + } + void DescribeNegationTo(::std::ostream* os) const override { + *os << "distance between begin() and end() "; + distance_matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain(Container container, + MatchResultListener* listener) const override { + using std::begin; + using std::end; + DistanceType distance = std::distance(begin(container), end(container)); + StringMatchResultListener distance_listener; + const bool result = + distance_matcher_.MatchAndExplain(distance, &distance_listener); + *listener << "whose distance between begin() and end() " << distance + << (result ? " matches" : " doesn't match"); + PrintIfNotEmpty(distance_listener.str(), listener->stream()); + return result; + } + + private: + const Matcher<DistanceType> distance_matcher_; + }; + + private: + const DistanceMatcher distance_matcher_; +}; + +// Implements an equality matcher for any STL-style container whose elements +// support ==. This matcher is like Eq(), but its failure explanations provide +// more detailed information that is useful when the container is used as a set. +// The failure message reports elements that are in one of the operands but not +// the other. The failure messages do not report duplicate or out-of-order +// elements in the containers (which don't properly matter to sets, but can +// occur if the containers are vectors or lists, for example). +// +// Uses the container's const_iterator, value_type, operator ==, +// begin(), and end(). +template <typename Container> +class ContainerEqMatcher { + public: + typedef internal::StlContainerView<Container> View; + typedef typename View::type StlContainer; + typedef typename View::const_reference StlContainerReference; + + static_assert(!std::is_const<Container>::value, + "Container type must not be const"); + static_assert(!std::is_reference<Container>::value, + "Container type must not be a reference"); + + // We make a copy of expected in case the elements in it are modified + // after this matcher is created. + explicit ContainerEqMatcher(const Container& expected) + : expected_(View::Copy(expected)) {} + + void DescribeTo(::std::ostream* os) const { + *os << "equals "; + UniversalPrint(expected_, os); + } + void DescribeNegationTo(::std::ostream* os) const { + *os << "does not equal "; + UniversalPrint(expected_, os); + } + + template <typename LhsContainer> + bool MatchAndExplain(const LhsContainer& lhs, + MatchResultListener* listener) const { + typedef internal::StlContainerView< + typename std::remove_const<LhsContainer>::type> + LhsView; + typedef typename LhsView::type LhsStlContainer; + StlContainerReference lhs_stl_container = LhsView::ConstReference(lhs); + if (lhs_stl_container == expected_) + return true; + + ::std::ostream* const os = listener->stream(); + if (os != nullptr) { + // Something is different. Check for extra values first. + bool printed_header = false; + for (typename LhsStlContainer::const_iterator it = + lhs_stl_container.begin(); + it != lhs_stl_container.end(); ++it) { + if (internal::ArrayAwareFind(expected_.begin(), expected_.end(), *it) == + expected_.end()) { + if (printed_header) { + *os << ", "; + } else { + *os << "which has these unexpected elements: "; + printed_header = true; + } + UniversalPrint(*it, os); + } + } + + // Now check for missing values. + bool printed_header2 = false; + for (typename StlContainer::const_iterator it = expected_.begin(); + it != expected_.end(); ++it) { + if (internal::ArrayAwareFind( + lhs_stl_container.begin(), lhs_stl_container.end(), *it) == + lhs_stl_container.end()) { + if (printed_header2) { + *os << ", "; + } else { + *os << (printed_header ? ",\nand" : "which") + << " doesn't have these expected elements: "; + printed_header2 = true; + } + UniversalPrint(*it, os); + } + } + } + + return false; + } + + private: + const StlContainer expected_; +}; + +// A comparator functor that uses the < operator to compare two values. +struct LessComparator { + template <typename T, typename U> + bool operator()(const T& lhs, const U& rhs) const { return lhs < rhs; } +}; + +// Implements WhenSortedBy(comparator, container_matcher). +template <typename Comparator, typename ContainerMatcher> +class WhenSortedByMatcher { + public: + WhenSortedByMatcher(const Comparator& comparator, + const ContainerMatcher& matcher) + : comparator_(comparator), matcher_(matcher) {} + + template <typename LhsContainer> + operator Matcher<LhsContainer>() const { + return MakeMatcher(new Impl<LhsContainer>(comparator_, matcher_)); + } + + template <typename LhsContainer> + class Impl : public MatcherInterface<LhsContainer> { + public: + typedef internal::StlContainerView< + GTEST_REMOVE_REFERENCE_AND_CONST_(LhsContainer)> LhsView; + typedef typename LhsView::type LhsStlContainer; + typedef typename LhsView::const_reference LhsStlContainerReference; + // Transforms std::pair<const Key, Value> into std::pair<Key, Value> + // so that we can match associative containers. + typedef typename RemoveConstFromKey< + typename LhsStlContainer::value_type>::type LhsValue; + + Impl(const Comparator& comparator, const ContainerMatcher& matcher) + : comparator_(comparator), matcher_(matcher) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "(when sorted) "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "(when sorted) "; + matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain(LhsContainer lhs, + MatchResultListener* listener) const override { + LhsStlContainerReference lhs_stl_container = LhsView::ConstReference(lhs); + ::std::vector<LhsValue> sorted_container(lhs_stl_container.begin(), + lhs_stl_container.end()); + ::std::sort( + sorted_container.begin(), sorted_container.end(), comparator_); + + if (!listener->IsInterested()) { + // If the listener is not interested, we do not need to + // construct the inner explanation. + return matcher_.Matches(sorted_container); + } + + *listener << "which is "; + UniversalPrint(sorted_container, listener->stream()); + *listener << " when sorted"; + + StringMatchResultListener inner_listener; + const bool match = matcher_.MatchAndExplain(sorted_container, + &inner_listener); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return match; + } + + private: + const Comparator comparator_; + const Matcher<const ::std::vector<LhsValue>&> matcher_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Impl); + }; + + private: + const Comparator comparator_; + const ContainerMatcher matcher_; +}; + +// Implements Pointwise(tuple_matcher, rhs_container). tuple_matcher +// must be able to be safely cast to Matcher<std::tuple<const T1&, const +// T2&> >, where T1 and T2 are the types of elements in the LHS +// container and the RHS container respectively. +template <typename TupleMatcher, typename RhsContainer> +class PointwiseMatcher { + GTEST_COMPILE_ASSERT_( + !IsHashTable<GTEST_REMOVE_REFERENCE_AND_CONST_(RhsContainer)>::value, + use_UnorderedPointwise_with_hash_tables); + + public: + typedef internal::StlContainerView<RhsContainer> RhsView; + typedef typename RhsView::type RhsStlContainer; + typedef typename RhsStlContainer::value_type RhsValue; + + static_assert(!std::is_const<RhsContainer>::value, + "RhsContainer type must not be const"); + static_assert(!std::is_reference<RhsContainer>::value, + "RhsContainer type must not be a reference"); + + // Like ContainerEq, we make a copy of rhs in case the elements in + // it are modified after this matcher is created. + PointwiseMatcher(const TupleMatcher& tuple_matcher, const RhsContainer& rhs) + : tuple_matcher_(tuple_matcher), rhs_(RhsView::Copy(rhs)) {} + + template <typename LhsContainer> + operator Matcher<LhsContainer>() const { + GTEST_COMPILE_ASSERT_( + !IsHashTable<GTEST_REMOVE_REFERENCE_AND_CONST_(LhsContainer)>::value, + use_UnorderedPointwise_with_hash_tables); + + return Matcher<LhsContainer>( + new Impl<const LhsContainer&>(tuple_matcher_, rhs_)); + } + + template <typename LhsContainer> + class Impl : public MatcherInterface<LhsContainer> { + public: + typedef internal::StlContainerView< + GTEST_REMOVE_REFERENCE_AND_CONST_(LhsContainer)> LhsView; + typedef typename LhsView::type LhsStlContainer; + typedef typename LhsView::const_reference LhsStlContainerReference; + typedef typename LhsStlContainer::value_type LhsValue; + // We pass the LHS value and the RHS value to the inner matcher by + // reference, as they may be expensive to copy. We must use tuple + // instead of pair here, as a pair cannot hold references (C++ 98, + // 20.2.2 [lib.pairs]). + typedef ::std::tuple<const LhsValue&, const RhsValue&> InnerMatcherArg; + + Impl(const TupleMatcher& tuple_matcher, const RhsStlContainer& rhs) + // mono_tuple_matcher_ holds a monomorphic version of the tuple matcher. + : mono_tuple_matcher_(SafeMatcherCast<InnerMatcherArg>(tuple_matcher)), + rhs_(rhs) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "contains " << rhs_.size() + << " values, where each value and its corresponding value in "; + UniversalPrinter<RhsStlContainer>::Print(rhs_, os); + *os << " "; + mono_tuple_matcher_.DescribeTo(os); + } + void DescribeNegationTo(::std::ostream* os) const override { + *os << "doesn't contain exactly " << rhs_.size() + << " values, or contains a value x at some index i" + << " where x and the i-th value of "; + UniversalPrint(rhs_, os); + *os << " "; + mono_tuple_matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain(LhsContainer lhs, + MatchResultListener* listener) const override { + LhsStlContainerReference lhs_stl_container = LhsView::ConstReference(lhs); + const size_t actual_size = lhs_stl_container.size(); + if (actual_size != rhs_.size()) { + *listener << "which contains " << actual_size << " values"; + return false; + } + + typename LhsStlContainer::const_iterator left = lhs_stl_container.begin(); + typename RhsStlContainer::const_iterator right = rhs_.begin(); + for (size_t i = 0; i != actual_size; ++i, ++left, ++right) { + if (listener->IsInterested()) { + StringMatchResultListener inner_listener; + // Create InnerMatcherArg as a temporarily object to avoid it outlives + // *left and *right. Dereference or the conversion to `const T&` may + // return temp objects, e.g for vector<bool>. + if (!mono_tuple_matcher_.MatchAndExplain( + InnerMatcherArg(ImplicitCast_<const LhsValue&>(*left), + ImplicitCast_<const RhsValue&>(*right)), + &inner_listener)) { + *listener << "where the value pair ("; + UniversalPrint(*left, listener->stream()); + *listener << ", "; + UniversalPrint(*right, listener->stream()); + *listener << ") at index #" << i << " don't match"; + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return false; + } + } else { + if (!mono_tuple_matcher_.Matches( + InnerMatcherArg(ImplicitCast_<const LhsValue&>(*left), + ImplicitCast_<const RhsValue&>(*right)))) + return false; + } + } + + return true; + } + + private: + const Matcher<InnerMatcherArg> mono_tuple_matcher_; + const RhsStlContainer rhs_; + }; + + private: + const TupleMatcher tuple_matcher_; + const RhsStlContainer rhs_; +}; + +// Holds the logic common to ContainsMatcherImpl and EachMatcherImpl. +template <typename Container> +class QuantifierMatcherImpl : public MatcherInterface<Container> { + public: + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer; + typedef StlContainerView<RawContainer> View; + typedef typename View::type StlContainer; + typedef typename View::const_reference StlContainerReference; + typedef typename StlContainer::value_type Element; + + template <typename InnerMatcher> + explicit QuantifierMatcherImpl(InnerMatcher inner_matcher) + : inner_matcher_( + testing::SafeMatcherCast<const Element&>(inner_matcher)) {} + + // Checks whether: + // * All elements in the container match, if all_elements_should_match. + // * Any element in the container matches, if !all_elements_should_match. + bool MatchAndExplainImpl(bool all_elements_should_match, + Container container, + MatchResultListener* listener) const { + StlContainerReference stl_container = View::ConstReference(container); + size_t i = 0; + for (typename StlContainer::const_iterator it = stl_container.begin(); + it != stl_container.end(); ++it, ++i) { + StringMatchResultListener inner_listener; + const bool matches = inner_matcher_.MatchAndExplain(*it, &inner_listener); + + if (matches != all_elements_should_match) { + *listener << "whose element #" << i + << (matches ? " matches" : " doesn't match"); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return !all_elements_should_match; + } + } + return all_elements_should_match; + } + + protected: + const Matcher<const Element&> inner_matcher_; +}; + +// Implements Contains(element_matcher) for the given argument type Container. +// Symmetric to EachMatcherImpl. +template <typename Container> +class ContainsMatcherImpl : public QuantifierMatcherImpl<Container> { + public: + template <typename InnerMatcher> + explicit ContainsMatcherImpl(InnerMatcher inner_matcher) + : QuantifierMatcherImpl<Container>(inner_matcher) {} + + // Describes what this matcher does. + void DescribeTo(::std::ostream* os) const override { + *os << "contains at least one element that "; + this->inner_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "doesn't contain any element that "; + this->inner_matcher_.DescribeTo(os); + } + + bool MatchAndExplain(Container container, + MatchResultListener* listener) const override { + return this->MatchAndExplainImpl(false, container, listener); + } +}; + +// Implements Each(element_matcher) for the given argument type Container. +// Symmetric to ContainsMatcherImpl. +template <typename Container> +class EachMatcherImpl : public QuantifierMatcherImpl<Container> { + public: + template <typename InnerMatcher> + explicit EachMatcherImpl(InnerMatcher inner_matcher) + : QuantifierMatcherImpl<Container>(inner_matcher) {} + + // Describes what this matcher does. + void DescribeTo(::std::ostream* os) const override { + *os << "only contains elements that "; + this->inner_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "contains some element that "; + this->inner_matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain(Container container, + MatchResultListener* listener) const override { + return this->MatchAndExplainImpl(true, container, listener); + } +}; + +// Implements polymorphic Contains(element_matcher). +template <typename M> +class ContainsMatcher { + public: + explicit ContainsMatcher(M m) : inner_matcher_(m) {} + + template <typename Container> + operator Matcher<Container>() const { + return Matcher<Container>( + new ContainsMatcherImpl<const Container&>(inner_matcher_)); + } + + private: + const M inner_matcher_; +}; + +// Implements polymorphic Each(element_matcher). +template <typename M> +class EachMatcher { + public: + explicit EachMatcher(M m) : inner_matcher_(m) {} + + template <typename Container> + operator Matcher<Container>() const { + return Matcher<Container>( + new EachMatcherImpl<const Container&>(inner_matcher_)); + } + + private: + const M inner_matcher_; +}; + +struct Rank1 {}; +struct Rank0 : Rank1 {}; + +namespace pair_getters { +using std::get; +template <typename T> +auto First(T& x, Rank1) -> decltype(get<0>(x)) { // NOLINT + return get<0>(x); +} +template <typename T> +auto First(T& x, Rank0) -> decltype((x.first)) { // NOLINT + return x.first; +} + +template <typename T> +auto Second(T& x, Rank1) -> decltype(get<1>(x)) { // NOLINT + return get<1>(x); +} +template <typename T> +auto Second(T& x, Rank0) -> decltype((x.second)) { // NOLINT + return x.second; +} +} // namespace pair_getters + +// Implements Key(inner_matcher) for the given argument pair type. +// Key(inner_matcher) matches an std::pair whose 'first' field matches +// inner_matcher. For example, Contains(Key(Ge(5))) can be used to match an +// std::map that contains at least one element whose key is >= 5. +template <typename PairType> +class KeyMatcherImpl : public MatcherInterface<PairType> { + public: + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(PairType) RawPairType; + typedef typename RawPairType::first_type KeyType; + + template <typename InnerMatcher> + explicit KeyMatcherImpl(InnerMatcher inner_matcher) + : inner_matcher_( + testing::SafeMatcherCast<const KeyType&>(inner_matcher)) { + } + + // Returns true if and only if 'key_value.first' (the key) matches the inner + // matcher. + bool MatchAndExplain(PairType key_value, + MatchResultListener* listener) const override { + StringMatchResultListener inner_listener; + const bool match = inner_matcher_.MatchAndExplain( + pair_getters::First(key_value, Rank0()), &inner_listener); + const std::string explanation = inner_listener.str(); + if (explanation != "") { + *listener << "whose first field is a value " << explanation; + } + return match; + } + + // Describes what this matcher does. + void DescribeTo(::std::ostream* os) const override { + *os << "has a key that "; + inner_matcher_.DescribeTo(os); + } + + // Describes what the negation of this matcher does. + void DescribeNegationTo(::std::ostream* os) const override { + *os << "doesn't have a key that "; + inner_matcher_.DescribeTo(os); + } + + private: + const Matcher<const KeyType&> inner_matcher_; +}; + +// Implements polymorphic Key(matcher_for_key). +template <typename M> +class KeyMatcher { + public: + explicit KeyMatcher(M m) : matcher_for_key_(m) {} + + template <typename PairType> + operator Matcher<PairType>() const { + return Matcher<PairType>( + new KeyMatcherImpl<const PairType&>(matcher_for_key_)); + } + + private: + const M matcher_for_key_; +}; + +// Implements polymorphic Address(matcher_for_address). +template <typename InnerMatcher> +class AddressMatcher { + public: + explicit AddressMatcher(InnerMatcher m) : matcher_(m) {} + + template <typename Type> + operator Matcher<Type>() const { // NOLINT + return Matcher<Type>(new Impl<const Type&>(matcher_)); + } + + private: + // The monomorphic implementation that works for a particular object type. + template <typename Type> + class Impl : public MatcherInterface<Type> { + public: + using Address = const GTEST_REMOVE_REFERENCE_AND_CONST_(Type) *; + explicit Impl(const InnerMatcher& matcher) + : matcher_(MatcherCast<Address>(matcher)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "has address that "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "does not have address that "; + matcher_.DescribeTo(os); + } + + bool MatchAndExplain(Type object, + MatchResultListener* listener) const override { + *listener << "which has address "; + Address address = std::addressof(object); + return MatchPrintAndExplain(address, matcher_, listener); + } + + private: + const Matcher<Address> matcher_; + }; + const InnerMatcher matcher_; +}; + +// Implements Pair(first_matcher, second_matcher) for the given argument pair +// type with its two matchers. See Pair() function below. +template <typename PairType> +class PairMatcherImpl : public MatcherInterface<PairType> { + public: + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(PairType) RawPairType; + typedef typename RawPairType::first_type FirstType; + typedef typename RawPairType::second_type SecondType; + + template <typename FirstMatcher, typename SecondMatcher> + PairMatcherImpl(FirstMatcher first_matcher, SecondMatcher second_matcher) + : first_matcher_( + testing::SafeMatcherCast<const FirstType&>(first_matcher)), + second_matcher_( + testing::SafeMatcherCast<const SecondType&>(second_matcher)) { + } + + // Describes what this matcher does. + void DescribeTo(::std::ostream* os) const override { + *os << "has a first field that "; + first_matcher_.DescribeTo(os); + *os << ", and has a second field that "; + second_matcher_.DescribeTo(os); + } + + // Describes what the negation of this matcher does. + void DescribeNegationTo(::std::ostream* os) const override { + *os << "has a first field that "; + first_matcher_.DescribeNegationTo(os); + *os << ", or has a second field that "; + second_matcher_.DescribeNegationTo(os); + } + + // Returns true if and only if 'a_pair.first' matches first_matcher and + // 'a_pair.second' matches second_matcher. + bool MatchAndExplain(PairType a_pair, + MatchResultListener* listener) const override { + if (!listener->IsInterested()) { + // If the listener is not interested, we don't need to construct the + // explanation. + return first_matcher_.Matches(pair_getters::First(a_pair, Rank0())) && + second_matcher_.Matches(pair_getters::Second(a_pair, Rank0())); + } + StringMatchResultListener first_inner_listener; + if (!first_matcher_.MatchAndExplain(pair_getters::First(a_pair, Rank0()), + &first_inner_listener)) { + *listener << "whose first field does not match"; + PrintIfNotEmpty(first_inner_listener.str(), listener->stream()); + return false; + } + StringMatchResultListener second_inner_listener; + if (!second_matcher_.MatchAndExplain(pair_getters::Second(a_pair, Rank0()), + &second_inner_listener)) { + *listener << "whose second field does not match"; + PrintIfNotEmpty(second_inner_listener.str(), listener->stream()); + return false; + } + ExplainSuccess(first_inner_listener.str(), second_inner_listener.str(), + listener); + return true; + } + + private: + void ExplainSuccess(const std::string& first_explanation, + const std::string& second_explanation, + MatchResultListener* listener) const { + *listener << "whose both fields match"; + if (first_explanation != "") { + *listener << ", where the first field is a value " << first_explanation; + } + if (second_explanation != "") { + *listener << ", "; + if (first_explanation != "") { + *listener << "and "; + } else { + *listener << "where "; + } + *listener << "the second field is a value " << second_explanation; + } + } + + const Matcher<const FirstType&> first_matcher_; + const Matcher<const SecondType&> second_matcher_; +}; + +// Implements polymorphic Pair(first_matcher, second_matcher). +template <typename FirstMatcher, typename SecondMatcher> +class PairMatcher { + public: + PairMatcher(FirstMatcher first_matcher, SecondMatcher second_matcher) + : first_matcher_(first_matcher), second_matcher_(second_matcher) {} + + template <typename PairType> + operator Matcher<PairType> () const { + return Matcher<PairType>( + new PairMatcherImpl<const PairType&>(first_matcher_, second_matcher_)); + } + + private: + const FirstMatcher first_matcher_; + const SecondMatcher second_matcher_; +}; + +template <typename T, size_t... I> +auto UnpackStructImpl(const T& t, IndexSequence<I...>, int) + -> decltype(std::tie(get<I>(t)...)) { + static_assert(std::tuple_size<T>::value == sizeof...(I), + "Number of arguments doesn't match the number of fields."); + return std::tie(get<I>(t)...); +} + +#if defined(__cpp_structured_bindings) && __cpp_structured_bindings >= 201606 +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<1>, char) { + const auto& [a] = t; + return std::tie(a); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<2>, char) { + const auto& [a, b] = t; + return std::tie(a, b); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<3>, char) { + const auto& [a, b, c] = t; + return std::tie(a, b, c); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<4>, char) { + const auto& [a, b, c, d] = t; + return std::tie(a, b, c, d); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<5>, char) { + const auto& [a, b, c, d, e] = t; + return std::tie(a, b, c, d, e); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<6>, char) { + const auto& [a, b, c, d, e, f] = t; + return std::tie(a, b, c, d, e, f); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<7>, char) { + const auto& [a, b, c, d, e, f, g] = t; + return std::tie(a, b, c, d, e, f, g); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<8>, char) { + const auto& [a, b, c, d, e, f, g, h] = t; + return std::tie(a, b, c, d, e, f, g, h); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<9>, char) { + const auto& [a, b, c, d, e, f, g, h, i] = t; + return std::tie(a, b, c, d, e, f, g, h, i); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<10>, char) { + const auto& [a, b, c, d, e, f, g, h, i, j] = t; + return std::tie(a, b, c, d, e, f, g, h, i, j); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<11>, char) { + const auto& [a, b, c, d, e, f, g, h, i, j, k] = t; + return std::tie(a, b, c, d, e, f, g, h, i, j, k); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<12>, char) { + const auto& [a, b, c, d, e, f, g, h, i, j, k, l] = t; + return std::tie(a, b, c, d, e, f, g, h, i, j, k, l); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<13>, char) { + const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m] = t; + return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<14>, char) { + const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m, n] = t; + return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m, n); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<15>, char) { + const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o] = t; + return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o); +} +template <typename T> +auto UnpackStructImpl(const T& t, MakeIndexSequence<16>, char) { + const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = t; + return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p); +} +#endif // defined(__cpp_structured_bindings) + +template <size_t I, typename T> +auto UnpackStruct(const T& t) + -> decltype((UnpackStructImpl)(t, MakeIndexSequence<I>{}, 0)) { + return (UnpackStructImpl)(t, MakeIndexSequence<I>{}, 0); +} + +// Helper function to do comma folding in C++11. +// The array ensures left-to-right order of evaluation. +// Usage: VariadicExpand({expr...}); +template <typename T, size_t N> +void VariadicExpand(const T (&)[N]) {} + +template <typename Struct, typename StructSize> +class FieldsAreMatcherImpl; + +template <typename Struct, size_t... I> +class FieldsAreMatcherImpl<Struct, IndexSequence<I...>> + : public MatcherInterface<Struct> { + using UnpackedType = + decltype(UnpackStruct<sizeof...(I)>(std::declval<const Struct&>())); + using MatchersType = std::tuple< + Matcher<const typename std::tuple_element<I, UnpackedType>::type&>...>; + + public: + template <typename Inner> + explicit FieldsAreMatcherImpl(const Inner& matchers) + : matchers_(testing::SafeMatcherCast< + const typename std::tuple_element<I, UnpackedType>::type&>( + std::get<I>(matchers))...) {} + + void DescribeTo(::std::ostream* os) const override { + const char* separator = ""; + VariadicExpand( + {(*os << separator << "has field #" << I << " that ", + std::get<I>(matchers_).DescribeTo(os), separator = ", and ")...}); + } + + void DescribeNegationTo(::std::ostream* os) const override { + const char* separator = ""; + VariadicExpand({(*os << separator << "has field #" << I << " that ", + std::get<I>(matchers_).DescribeNegationTo(os), + separator = ", or ")...}); + } + + bool MatchAndExplain(Struct t, MatchResultListener* listener) const override { + return MatchInternal((UnpackStruct<sizeof...(I)>)(t), listener); + } + + private: + bool MatchInternal(UnpackedType tuple, MatchResultListener* listener) const { + if (!listener->IsInterested()) { + // If the listener is not interested, we don't need to construct the + // explanation. + bool good = true; + VariadicExpand({good = good && std::get<I>(matchers_).Matches( + std::get<I>(tuple))...}); + return good; + } + + size_t failed_pos = ~size_t{}; + + std::vector<StringMatchResultListener> inner_listener(sizeof...(I)); + + VariadicExpand( + {failed_pos == ~size_t{} && !std::get<I>(matchers_).MatchAndExplain( + std::get<I>(tuple), &inner_listener[I]) + ? failed_pos = I + : 0 ...}); + if (failed_pos != ~size_t{}) { + *listener << "whose field #" << failed_pos << " does not match"; + PrintIfNotEmpty(inner_listener[failed_pos].str(), listener->stream()); + return false; + } + + *listener << "whose all elements match"; + const char* separator = ", where"; + for (size_t index = 0; index < sizeof...(I); ++index) { + const std::string str = inner_listener[index].str(); + if (!str.empty()) { + *listener << separator << " field #" << index << " is a value " << str; + separator = ", and"; + } + } + + return true; + } + + MatchersType matchers_; +}; + +template <typename... Inner> +class FieldsAreMatcher { + public: + explicit FieldsAreMatcher(Inner... inner) : matchers_(std::move(inner)...) {} + + template <typename Struct> + operator Matcher<Struct>() const { // NOLINT + return Matcher<Struct>( + new FieldsAreMatcherImpl<const Struct&, IndexSequenceFor<Inner...>>( + matchers_)); + } + + private: + std::tuple<Inner...> matchers_; +}; + +// Implements ElementsAre() and ElementsAreArray(). +template <typename Container> +class ElementsAreMatcherImpl : public MatcherInterface<Container> { + public: + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer; + typedef internal::StlContainerView<RawContainer> View; + typedef typename View::type StlContainer; + typedef typename View::const_reference StlContainerReference; + typedef typename StlContainer::value_type Element; + + // Constructs the matcher from a sequence of element values or + // element matchers. + template <typename InputIter> + ElementsAreMatcherImpl(InputIter first, InputIter last) { + while (first != last) { + matchers_.push_back(MatcherCast<const Element&>(*first++)); + } + } + + // Describes what this matcher does. + void DescribeTo(::std::ostream* os) const override { + if (count() == 0) { + *os << "is empty"; + } else if (count() == 1) { + *os << "has 1 element that "; + matchers_[0].DescribeTo(os); + } else { + *os << "has " << Elements(count()) << " where\n"; + for (size_t i = 0; i != count(); ++i) { + *os << "element #" << i << " "; + matchers_[i].DescribeTo(os); + if (i + 1 < count()) { + *os << ",\n"; + } + } + } + } + + // Describes what the negation of this matcher does. + void DescribeNegationTo(::std::ostream* os) const override { + if (count() == 0) { + *os << "isn't empty"; + return; + } + + *os << "doesn't have " << Elements(count()) << ", or\n"; + for (size_t i = 0; i != count(); ++i) { + *os << "element #" << i << " "; + matchers_[i].DescribeNegationTo(os); + if (i + 1 < count()) { + *os << ", or\n"; + } + } + } + + bool MatchAndExplain(Container container, + MatchResultListener* listener) const override { + // To work with stream-like "containers", we must only walk + // through the elements in one pass. + + const bool listener_interested = listener->IsInterested(); + + // explanations[i] is the explanation of the element at index i. + ::std::vector<std::string> explanations(count()); + StlContainerReference stl_container = View::ConstReference(container); + typename StlContainer::const_iterator it = stl_container.begin(); + size_t exam_pos = 0; + bool mismatch_found = false; // Have we found a mismatched element yet? + + // Go through the elements and matchers in pairs, until we reach + // the end of either the elements or the matchers, or until we find a + // mismatch. + for (; it != stl_container.end() && exam_pos != count(); ++it, ++exam_pos) { + bool match; // Does the current element match the current matcher? + if (listener_interested) { + StringMatchResultListener s; + match = matchers_[exam_pos].MatchAndExplain(*it, &s); + explanations[exam_pos] = s.str(); + } else { + match = matchers_[exam_pos].Matches(*it); + } + + if (!match) { + mismatch_found = true; + break; + } + } + // If mismatch_found is true, 'exam_pos' is the index of the mismatch. + + // Find how many elements the actual container has. We avoid + // calling size() s.t. this code works for stream-like "containers" + // that don't define size(). + size_t actual_count = exam_pos; + for (; it != stl_container.end(); ++it) { + ++actual_count; + } + + if (actual_count != count()) { + // The element count doesn't match. If the container is empty, + // there's no need to explain anything as Google Mock already + // prints the empty container. Otherwise we just need to show + // how many elements there actually are. + if (listener_interested && (actual_count != 0)) { + *listener << "which has " << Elements(actual_count); + } + return false; + } + + if (mismatch_found) { + // The element count matches, but the exam_pos-th element doesn't match. + if (listener_interested) { + *listener << "whose element #" << exam_pos << " doesn't match"; + PrintIfNotEmpty(explanations[exam_pos], listener->stream()); + } + return false; + } + + // Every element matches its expectation. We need to explain why + // (the obvious ones can be skipped). + if (listener_interested) { + bool reason_printed = false; + for (size_t i = 0; i != count(); ++i) { + const std::string& s = explanations[i]; + if (!s.empty()) { + if (reason_printed) { + *listener << ",\nand "; + } + *listener << "whose element #" << i << " matches, " << s; + reason_printed = true; + } + } + } + return true; + } + + private: + static Message Elements(size_t count) { + return Message() << count << (count == 1 ? " element" : " elements"); + } + + size_t count() const { return matchers_.size(); } + + ::std::vector<Matcher<const Element&> > matchers_; +}; + +// Connectivity matrix of (elements X matchers), in element-major order. +// Initially, there are no edges. +// Use NextGraph() to iterate over all possible edge configurations. +// Use Randomize() to generate a random edge configuration. +class GTEST_API_ MatchMatrix { + public: + MatchMatrix(size_t num_elements, size_t num_matchers) + : num_elements_(num_elements), + num_matchers_(num_matchers), + matched_(num_elements_* num_matchers_, 0) { + } + + size_t LhsSize() const { return num_elements_; } + size_t RhsSize() const { return num_matchers_; } + bool HasEdge(size_t ilhs, size_t irhs) const { + return matched_[SpaceIndex(ilhs, irhs)] == 1; + } + void SetEdge(size_t ilhs, size_t irhs, bool b) { + matched_[SpaceIndex(ilhs, irhs)] = b ? 1 : 0; + } + + // Treating the connectivity matrix as a (LhsSize()*RhsSize())-bit number, + // adds 1 to that number; returns false if incrementing the graph left it + // empty. + bool NextGraph(); + + void Randomize(); + + std::string DebugString() const; + + private: + size_t SpaceIndex(size_t ilhs, size_t irhs) const { + return ilhs * num_matchers_ + irhs; + } + + size_t num_elements_; + size_t num_matchers_; + + // Each element is a char interpreted as bool. They are stored as a + // flattened array in lhs-major order, use 'SpaceIndex()' to translate + // a (ilhs, irhs) matrix coordinate into an offset. + ::std::vector<char> matched_; +}; + +typedef ::std::pair<size_t, size_t> ElementMatcherPair; +typedef ::std::vector<ElementMatcherPair> ElementMatcherPairs; + +// Returns a maximum bipartite matching for the specified graph 'g'. +// The matching is represented as a vector of {element, matcher} pairs. +GTEST_API_ ElementMatcherPairs +FindMaxBipartiteMatching(const MatchMatrix& g); + +struct UnorderedMatcherRequire { + enum Flags { + Superset = 1 << 0, + Subset = 1 << 1, + ExactMatch = Superset | Subset, + }; +}; + +// Untyped base class for implementing UnorderedElementsAre. By +// putting logic that's not specific to the element type here, we +// reduce binary bloat and increase compilation speed. +class GTEST_API_ UnorderedElementsAreMatcherImplBase { + protected: + explicit UnorderedElementsAreMatcherImplBase( + UnorderedMatcherRequire::Flags matcher_flags) + : match_flags_(matcher_flags) {} + + // A vector of matcher describers, one for each element matcher. + // Does not own the describers (and thus can be used only when the + // element matchers are alive). + typedef ::std::vector<const MatcherDescriberInterface*> MatcherDescriberVec; + + // Describes this UnorderedElementsAre matcher. + void DescribeToImpl(::std::ostream* os) const; + + // Describes the negation of this UnorderedElementsAre matcher. + void DescribeNegationToImpl(::std::ostream* os) const; + + bool VerifyMatchMatrix(const ::std::vector<std::string>& element_printouts, + const MatchMatrix& matrix, + MatchResultListener* listener) const; + + bool FindPairing(const MatchMatrix& matrix, + MatchResultListener* listener) const; + + MatcherDescriberVec& matcher_describers() { + return matcher_describers_; + } + + static Message Elements(size_t n) { + return Message() << n << " element" << (n == 1 ? "" : "s"); + } + + UnorderedMatcherRequire::Flags match_flags() const { return match_flags_; } + + private: + UnorderedMatcherRequire::Flags match_flags_; + MatcherDescriberVec matcher_describers_; +}; + +// Implements UnorderedElementsAre, UnorderedElementsAreArray, IsSubsetOf, and +// IsSupersetOf. +template <typename Container> +class UnorderedElementsAreMatcherImpl + : public MatcherInterface<Container>, + public UnorderedElementsAreMatcherImplBase { + public: + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer; + typedef internal::StlContainerView<RawContainer> View; + typedef typename View::type StlContainer; + typedef typename View::const_reference StlContainerReference; + typedef typename StlContainer::const_iterator StlContainerConstIterator; + typedef typename StlContainer::value_type Element; + + template <typename InputIter> + UnorderedElementsAreMatcherImpl(UnorderedMatcherRequire::Flags matcher_flags, + InputIter first, InputIter last) + : UnorderedElementsAreMatcherImplBase(matcher_flags) { + for (; first != last; ++first) { + matchers_.push_back(MatcherCast<const Element&>(*first)); + } + for (const auto& m : matchers_) { + matcher_describers().push_back(m.GetDescriber()); + } + } + + // Describes what this matcher does. + void DescribeTo(::std::ostream* os) const override { + return UnorderedElementsAreMatcherImplBase::DescribeToImpl(os); + } + + // Describes what the negation of this matcher does. + void DescribeNegationTo(::std::ostream* os) const override { + return UnorderedElementsAreMatcherImplBase::DescribeNegationToImpl(os); + } + + bool MatchAndExplain(Container container, + MatchResultListener* listener) const override { + StlContainerReference stl_container = View::ConstReference(container); + ::std::vector<std::string> element_printouts; + MatchMatrix matrix = + AnalyzeElements(stl_container.begin(), stl_container.end(), + &element_printouts, listener); + + if (matrix.LhsSize() == 0 && matrix.RhsSize() == 0) { + return true; + } + + if (match_flags() == UnorderedMatcherRequire::ExactMatch) { + if (matrix.LhsSize() != matrix.RhsSize()) { + // The element count doesn't match. If the container is empty, + // there's no need to explain anything as Google Mock already + // prints the empty container. Otherwise we just need to show + // how many elements there actually are. + if (matrix.LhsSize() != 0 && listener->IsInterested()) { + *listener << "which has " << Elements(matrix.LhsSize()); + } + return false; + } + } + + return VerifyMatchMatrix(element_printouts, matrix, listener) && + FindPairing(matrix, listener); + } + + private: + template <typename ElementIter> + MatchMatrix AnalyzeElements(ElementIter elem_first, ElementIter elem_last, + ::std::vector<std::string>* element_printouts, + MatchResultListener* listener) const { + element_printouts->clear(); + ::std::vector<char> did_match; + size_t num_elements = 0; + DummyMatchResultListener dummy; + for (; elem_first != elem_last; ++num_elements, ++elem_first) { + if (listener->IsInterested()) { + element_printouts->push_back(PrintToString(*elem_first)); + } + for (size_t irhs = 0; irhs != matchers_.size(); ++irhs) { + did_match.push_back( + matchers_[irhs].MatchAndExplain(*elem_first, &dummy)); + } + } + + MatchMatrix matrix(num_elements, matchers_.size()); + ::std::vector<char>::const_iterator did_match_iter = did_match.begin(); + for (size_t ilhs = 0; ilhs != num_elements; ++ilhs) { + for (size_t irhs = 0; irhs != matchers_.size(); ++irhs) { + matrix.SetEdge(ilhs, irhs, *did_match_iter++ != 0); + } + } + return matrix; + } + + ::std::vector<Matcher<const Element&> > matchers_; +}; + +// Functor for use in TransformTuple. +// Performs MatcherCast<Target> on an input argument of any type. +template <typename Target> +struct CastAndAppendTransform { + template <typename Arg> + Matcher<Target> operator()(const Arg& a) const { + return MatcherCast<Target>(a); + } +}; + +// Implements UnorderedElementsAre. +template <typename MatcherTuple> +class UnorderedElementsAreMatcher { + public: + explicit UnorderedElementsAreMatcher(const MatcherTuple& args) + : matchers_(args) {} + + template <typename Container> + operator Matcher<Container>() const { + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer; + typedef typename internal::StlContainerView<RawContainer>::type View; + typedef typename View::value_type Element; + typedef ::std::vector<Matcher<const Element&> > MatcherVec; + MatcherVec matchers; + matchers.reserve(::std::tuple_size<MatcherTuple>::value); + TransformTupleValues(CastAndAppendTransform<const Element&>(), matchers_, + ::std::back_inserter(matchers)); + return Matcher<Container>( + new UnorderedElementsAreMatcherImpl<const Container&>( + UnorderedMatcherRequire::ExactMatch, matchers.begin(), + matchers.end())); + } + + private: + const MatcherTuple matchers_; +}; + +// Implements ElementsAre. +template <typename MatcherTuple> +class ElementsAreMatcher { + public: + explicit ElementsAreMatcher(const MatcherTuple& args) : matchers_(args) {} + + template <typename Container> + operator Matcher<Container>() const { + GTEST_COMPILE_ASSERT_( + !IsHashTable<GTEST_REMOVE_REFERENCE_AND_CONST_(Container)>::value || + ::std::tuple_size<MatcherTuple>::value < 2, + use_UnorderedElementsAre_with_hash_tables); + + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer; + typedef typename internal::StlContainerView<RawContainer>::type View; + typedef typename View::value_type Element; + typedef ::std::vector<Matcher<const Element&> > MatcherVec; + MatcherVec matchers; + matchers.reserve(::std::tuple_size<MatcherTuple>::value); + TransformTupleValues(CastAndAppendTransform<const Element&>(), matchers_, + ::std::back_inserter(matchers)); + return Matcher<Container>(new ElementsAreMatcherImpl<const Container&>( + matchers.begin(), matchers.end())); + } + + private: + const MatcherTuple matchers_; +}; + +// Implements UnorderedElementsAreArray(), IsSubsetOf(), and IsSupersetOf(). +template <typename T> +class UnorderedElementsAreArrayMatcher { + public: + template <typename Iter> + UnorderedElementsAreArrayMatcher(UnorderedMatcherRequire::Flags match_flags, + Iter first, Iter last) + : match_flags_(match_flags), matchers_(first, last) {} + + template <typename Container> + operator Matcher<Container>() const { + return Matcher<Container>( + new UnorderedElementsAreMatcherImpl<const Container&>( + match_flags_, matchers_.begin(), matchers_.end())); + } + + private: + UnorderedMatcherRequire::Flags match_flags_; + ::std::vector<T> matchers_; +}; + +// Implements ElementsAreArray(). +template <typename T> +class ElementsAreArrayMatcher { + public: + template <typename Iter> + ElementsAreArrayMatcher(Iter first, Iter last) : matchers_(first, last) {} + + template <typename Container> + operator Matcher<Container>() const { + GTEST_COMPILE_ASSERT_( + !IsHashTable<GTEST_REMOVE_REFERENCE_AND_CONST_(Container)>::value, + use_UnorderedElementsAreArray_with_hash_tables); + + return Matcher<Container>(new ElementsAreMatcherImpl<const Container&>( + matchers_.begin(), matchers_.end())); + } + + private: + const ::std::vector<T> matchers_; +}; + +// Given a 2-tuple matcher tm of type Tuple2Matcher and a value second +// of type Second, BoundSecondMatcher<Tuple2Matcher, Second>(tm, +// second) is a polymorphic matcher that matches a value x if and only if +// tm matches tuple (x, second). Useful for implementing +// UnorderedPointwise() in terms of UnorderedElementsAreArray(). +// +// BoundSecondMatcher is copyable and assignable, as we need to put +// instances of this class in a vector when implementing +// UnorderedPointwise(). +template <typename Tuple2Matcher, typename Second> +class BoundSecondMatcher { + public: + BoundSecondMatcher(const Tuple2Matcher& tm, const Second& second) + : tuple2_matcher_(tm), second_value_(second) {} + + BoundSecondMatcher(const BoundSecondMatcher& other) = default; + + template <typename T> + operator Matcher<T>() const { + return MakeMatcher(new Impl<T>(tuple2_matcher_, second_value_)); + } + + // We have to define this for UnorderedPointwise() to compile in + // C++98 mode, as it puts BoundSecondMatcher instances in a vector, + // which requires the elements to be assignable in C++98. The + // compiler cannot generate the operator= for us, as Tuple2Matcher + // and Second may not be assignable. + // + // However, this should never be called, so the implementation just + // need to assert. + void operator=(const BoundSecondMatcher& /*rhs*/) { + GTEST_LOG_(FATAL) << "BoundSecondMatcher should never be assigned."; + } + + private: + template <typename T> + class Impl : public MatcherInterface<T> { + public: + typedef ::std::tuple<T, Second> ArgTuple; + + Impl(const Tuple2Matcher& tm, const Second& second) + : mono_tuple2_matcher_(SafeMatcherCast<const ArgTuple&>(tm)), + second_value_(second) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "and "; + UniversalPrint(second_value_, os); + *os << " "; + mono_tuple2_matcher_.DescribeTo(os); + } + + bool MatchAndExplain(T x, MatchResultListener* listener) const override { + return mono_tuple2_matcher_.MatchAndExplain(ArgTuple(x, second_value_), + listener); + } + + private: + const Matcher<const ArgTuple&> mono_tuple2_matcher_; + const Second second_value_; + }; + + const Tuple2Matcher tuple2_matcher_; + const Second second_value_; +}; + +// Given a 2-tuple matcher tm and a value second, +// MatcherBindSecond(tm, second) returns a matcher that matches a +// value x if and only if tm matches tuple (x, second). Useful for +// implementing UnorderedPointwise() in terms of UnorderedElementsAreArray(). +template <typename Tuple2Matcher, typename Second> +BoundSecondMatcher<Tuple2Matcher, Second> MatcherBindSecond( + const Tuple2Matcher& tm, const Second& second) { + return BoundSecondMatcher<Tuple2Matcher, Second>(tm, second); +} + +// Returns the description for a matcher defined using the MATCHER*() +// macro where the user-supplied description string is "", if +// 'negation' is false; otherwise returns the description of the +// negation of the matcher. 'param_values' contains a list of strings +// that are the print-out of the matcher's parameters. +GTEST_API_ std::string FormatMatcherDescription(bool negation, + const char* matcher_name, + const Strings& param_values); + +// Implements a matcher that checks the value of a optional<> type variable. +template <typename ValueMatcher> +class OptionalMatcher { + public: + explicit OptionalMatcher(const ValueMatcher& value_matcher) + : value_matcher_(value_matcher) {} + + template <typename Optional> + operator Matcher<Optional>() const { + return Matcher<Optional>(new Impl<const Optional&>(value_matcher_)); + } + + template <typename Optional> + class Impl : public MatcherInterface<Optional> { + public: + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Optional) OptionalView; + typedef typename OptionalView::value_type ValueType; + explicit Impl(const ValueMatcher& value_matcher) + : value_matcher_(MatcherCast<ValueType>(value_matcher)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "value "; + value_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "value "; + value_matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain(Optional optional, + MatchResultListener* listener) const override { + if (!optional) { + *listener << "which is not engaged"; + return false; + } + const ValueType& value = *optional; + StringMatchResultListener value_listener; + const bool match = value_matcher_.MatchAndExplain(value, &value_listener); + *listener << "whose value " << PrintToString(value) + << (match ? " matches" : " doesn't match"); + PrintIfNotEmpty(value_listener.str(), listener->stream()); + return match; + } + + private: + const Matcher<ValueType> value_matcher_; + }; + + private: + const ValueMatcher value_matcher_; +}; + +namespace variant_matcher { +// Overloads to allow VariantMatcher to do proper ADL lookup. +template <typename T> +void holds_alternative() {} +template <typename T> +void get() {} + +// Implements a matcher that checks the value of a variant<> type variable. +template <typename T> +class VariantMatcher { + public: + explicit VariantMatcher(::testing::Matcher<const T&> matcher) + : matcher_(std::move(matcher)) {} + + template <typename Variant> + bool MatchAndExplain(const Variant& value, + ::testing::MatchResultListener* listener) const { + using std::get; + if (!listener->IsInterested()) { + return holds_alternative<T>(value) && matcher_.Matches(get<T>(value)); + } + + if (!holds_alternative<T>(value)) { + *listener << "whose value is not of type '" << GetTypeName() << "'"; + return false; + } + + const T& elem = get<T>(value); + StringMatchResultListener elem_listener; + const bool match = matcher_.MatchAndExplain(elem, &elem_listener); + *listener << "whose value " << PrintToString(elem) + << (match ? " matches" : " doesn't match"); + PrintIfNotEmpty(elem_listener.str(), listener->stream()); + return match; + } + + void DescribeTo(std::ostream* os) const { + *os << "is a variant<> with value of type '" << GetTypeName() + << "' and the value "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "is a variant<> with value of type other than '" << GetTypeName() + << "' or the value "; + matcher_.DescribeNegationTo(os); + } + + private: + static std::string GetTypeName() { +#if GTEST_HAS_RTTI + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_( + return internal::GetTypeName<T>()); +#endif + return "the element type"; + } + + const ::testing::Matcher<const T&> matcher_; +}; + +} // namespace variant_matcher + +namespace any_cast_matcher { + +// Overloads to allow AnyCastMatcher to do proper ADL lookup. +template <typename T> +void any_cast() {} + +// Implements a matcher that any_casts the value. +template <typename T> +class AnyCastMatcher { + public: + explicit AnyCastMatcher(const ::testing::Matcher<const T&>& matcher) + : matcher_(matcher) {} + + template <typename AnyType> + bool MatchAndExplain(const AnyType& value, + ::testing::MatchResultListener* listener) const { + if (!listener->IsInterested()) { + const T* ptr = any_cast<T>(&value); + return ptr != nullptr && matcher_.Matches(*ptr); + } + + const T* elem = any_cast<T>(&value); + if (elem == nullptr) { + *listener << "whose value is not of type '" << GetTypeName() << "'"; + return false; + } + + StringMatchResultListener elem_listener; + const bool match = matcher_.MatchAndExplain(*elem, &elem_listener); + *listener << "whose value " << PrintToString(*elem) + << (match ? " matches" : " doesn't match"); + PrintIfNotEmpty(elem_listener.str(), listener->stream()); + return match; + } + + void DescribeTo(std::ostream* os) const { + *os << "is an 'any' type with value of type '" << GetTypeName() + << "' and the value "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "is an 'any' type with value of type other than '" << GetTypeName() + << "' or the value "; + matcher_.DescribeNegationTo(os); + } + + private: + static std::string GetTypeName() { +#if GTEST_HAS_RTTI + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_( + return internal::GetTypeName<T>()); +#endif + return "the element type"; + } + + const ::testing::Matcher<const T&> matcher_; +}; + +} // namespace any_cast_matcher + +// Implements the Args() matcher. +template <class ArgsTuple, size_t... k> +class ArgsMatcherImpl : public MatcherInterface<ArgsTuple> { + public: + using RawArgsTuple = typename std::decay<ArgsTuple>::type; + using SelectedArgs = + std::tuple<typename std::tuple_element<k, RawArgsTuple>::type...>; + using MonomorphicInnerMatcher = Matcher<const SelectedArgs&>; + + template <typename InnerMatcher> + explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher) + : inner_matcher_(SafeMatcherCast<const SelectedArgs&>(inner_matcher)) {} + + bool MatchAndExplain(ArgsTuple args, + MatchResultListener* listener) const override { + // Workaround spurious C4100 on MSVC<=15.7 when k is empty. + (void)args; + const SelectedArgs& selected_args = + std::forward_as_tuple(std::get<k>(args)...); + if (!listener->IsInterested()) return inner_matcher_.Matches(selected_args); + + PrintIndices(listener->stream()); + *listener << "are " << PrintToString(selected_args); + + StringMatchResultListener inner_listener; + const bool match = + inner_matcher_.MatchAndExplain(selected_args, &inner_listener); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return match; + } + + void DescribeTo(::std::ostream* os) const override { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeNegationTo(os); + } + + private: + // Prints the indices of the selected fields. + static void PrintIndices(::std::ostream* os) { + *os << "whose fields ("; + const char* sep = ""; + // Workaround spurious C4189 on MSVC<=15.7 when k is empty. + (void)sep; + const char* dummy[] = {"", (*os << sep << "#" << k, sep = ", ")...}; + (void)dummy; + *os << ") "; + } + + MonomorphicInnerMatcher inner_matcher_; +}; + +template <class InnerMatcher, size_t... k> +class ArgsMatcher { + public: + explicit ArgsMatcher(InnerMatcher inner_matcher) + : inner_matcher_(std::move(inner_matcher)) {} + + template <typename ArgsTuple> + operator Matcher<ArgsTuple>() const { // NOLINT + return MakeMatcher(new ArgsMatcherImpl<ArgsTuple, k...>(inner_matcher_)); + } + + private: + InnerMatcher inner_matcher_; +}; + +} // namespace internal + +// ElementsAreArray(iterator_first, iterator_last) +// ElementsAreArray(pointer, count) +// ElementsAreArray(array) +// ElementsAreArray(container) +// ElementsAreArray({ e1, e2, ..., en }) +// +// The ElementsAreArray() functions are like ElementsAre(...), except +// that they are given a homogeneous sequence rather than taking each +// element as a function argument. The sequence can be specified as an +// array, a pointer and count, a vector, an initializer list, or an +// STL iterator range. In each of these cases, the underlying sequence +// can be either a sequence of values or a sequence of matchers. +// +// All forms of ElementsAreArray() make a copy of the input matcher sequence. + +template <typename Iter> +inline internal::ElementsAreArrayMatcher< + typename ::std::iterator_traits<Iter>::value_type> +ElementsAreArray(Iter first, Iter last) { + typedef typename ::std::iterator_traits<Iter>::value_type T; + return internal::ElementsAreArrayMatcher<T>(first, last); +} + +template <typename T> +inline internal::ElementsAreArrayMatcher<T> ElementsAreArray( + const T* pointer, size_t count) { + return ElementsAreArray(pointer, pointer + count); +} + +template <typename T, size_t N> +inline internal::ElementsAreArrayMatcher<T> ElementsAreArray( + const T (&array)[N]) { + return ElementsAreArray(array, N); +} + +template <typename Container> +inline internal::ElementsAreArrayMatcher<typename Container::value_type> +ElementsAreArray(const Container& container) { + return ElementsAreArray(container.begin(), container.end()); +} + +template <typename T> +inline internal::ElementsAreArrayMatcher<T> +ElementsAreArray(::std::initializer_list<T> xs) { + return ElementsAreArray(xs.begin(), xs.end()); +} + +// UnorderedElementsAreArray(iterator_first, iterator_last) +// UnorderedElementsAreArray(pointer, count) +// UnorderedElementsAreArray(array) +// UnorderedElementsAreArray(container) +// UnorderedElementsAreArray({ e1, e2, ..., en }) +// +// UnorderedElementsAreArray() verifies that a bijective mapping onto a +// collection of matchers exists. +// +// The matchers can be specified as an array, a pointer and count, a container, +// an initializer list, or an STL iterator range. In each of these cases, the +// underlying matchers can be either values or matchers. + +template <typename Iter> +inline internal::UnorderedElementsAreArrayMatcher< + typename ::std::iterator_traits<Iter>::value_type> +UnorderedElementsAreArray(Iter first, Iter last) { + typedef typename ::std::iterator_traits<Iter>::value_type T; + return internal::UnorderedElementsAreArrayMatcher<T>( + internal::UnorderedMatcherRequire::ExactMatch, first, last); +} + +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> +UnorderedElementsAreArray(const T* pointer, size_t count) { + return UnorderedElementsAreArray(pointer, pointer + count); +} + +template <typename T, size_t N> +inline internal::UnorderedElementsAreArrayMatcher<T> +UnorderedElementsAreArray(const T (&array)[N]) { + return UnorderedElementsAreArray(array, N); +} + +template <typename Container> +inline internal::UnorderedElementsAreArrayMatcher< + typename Container::value_type> +UnorderedElementsAreArray(const Container& container) { + return UnorderedElementsAreArray(container.begin(), container.end()); +} + +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> +UnorderedElementsAreArray(::std::initializer_list<T> xs) { + return UnorderedElementsAreArray(xs.begin(), xs.end()); +} + +// _ is a matcher that matches anything of any type. +// +// This definition is fine as: +// +// 1. The C++ standard permits using the name _ in a namespace that +// is not the global namespace or ::std. +// 2. The AnythingMatcher class has no data member or constructor, +// so it's OK to create global variables of this type. +// 3. c-style has approved of using _ in this case. +const internal::AnythingMatcher _ = {}; +// Creates a matcher that matches any value of the given type T. +template <typename T> +inline Matcher<T> A() { + return _; +} + +// Creates a matcher that matches any value of the given type T. +template <typename T> +inline Matcher<T> An() { + return _; +} + +template <typename T, typename M> +Matcher<T> internal::MatcherCastImpl<T, M>::CastImpl( + const M& value, std::false_type /* convertible_to_matcher */, + std::false_type /* convertible_to_T */) { + return Eq(value); +} + +// Creates a polymorphic matcher that matches any NULL pointer. +inline PolymorphicMatcher<internal::IsNullMatcher > IsNull() { + return MakePolymorphicMatcher(internal::IsNullMatcher()); +} + +// Creates a polymorphic matcher that matches any non-NULL pointer. +// This is convenient as Not(NULL) doesn't compile (the compiler +// thinks that that expression is comparing a pointer with an integer). +inline PolymorphicMatcher<internal::NotNullMatcher > NotNull() { + return MakePolymorphicMatcher(internal::NotNullMatcher()); +} + +// Creates a polymorphic matcher that matches any argument that +// references variable x. +template <typename T> +inline internal::RefMatcher<T&> Ref(T& x) { // NOLINT + return internal::RefMatcher<T&>(x); +} + +// Creates a polymorphic matcher that matches any NaN floating point. +inline PolymorphicMatcher<internal::IsNanMatcher> IsNan() { + return MakePolymorphicMatcher(internal::IsNanMatcher()); +} + +// Creates a matcher that matches any double argument approximately +// equal to rhs, where two NANs are considered unequal. +inline internal::FloatingEqMatcher<double> DoubleEq(double rhs) { + return internal::FloatingEqMatcher<double>(rhs, false); +} + +// Creates a matcher that matches any double argument approximately +// equal to rhs, including NaN values when rhs is NaN. +inline internal::FloatingEqMatcher<double> NanSensitiveDoubleEq(double rhs) { + return internal::FloatingEqMatcher<double>(rhs, true); +} + +// Creates a matcher that matches any double argument approximately equal to +// rhs, up to the specified max absolute error bound, where two NANs are +// considered unequal. The max absolute error bound must be non-negative. +inline internal::FloatingEqMatcher<double> DoubleNear( + double rhs, double max_abs_error) { + return internal::FloatingEqMatcher<double>(rhs, false, max_abs_error); +} + +// Creates a matcher that matches any double argument approximately equal to +// rhs, up to the specified max absolute error bound, including NaN values when +// rhs is NaN. The max absolute error bound must be non-negative. +inline internal::FloatingEqMatcher<double> NanSensitiveDoubleNear( + double rhs, double max_abs_error) { + return internal::FloatingEqMatcher<double>(rhs, true, max_abs_error); +} + +// Creates a matcher that matches any float argument approximately +// equal to rhs, where two NANs are considered unequal. +inline internal::FloatingEqMatcher<float> FloatEq(float rhs) { + return internal::FloatingEqMatcher<float>(rhs, false); +} + +// Creates a matcher that matches any float argument approximately +// equal to rhs, including NaN values when rhs is NaN. +inline internal::FloatingEqMatcher<float> NanSensitiveFloatEq(float rhs) { + return internal::FloatingEqMatcher<float>(rhs, true); +} + +// Creates a matcher that matches any float argument approximately equal to +// rhs, up to the specified max absolute error bound, where two NANs are +// considered unequal. The max absolute error bound must be non-negative. +inline internal::FloatingEqMatcher<float> FloatNear( + float rhs, float max_abs_error) { + return internal::FloatingEqMatcher<float>(rhs, false, max_abs_error); +} + +// Creates a matcher that matches any float argument approximately equal to +// rhs, up to the specified max absolute error bound, including NaN values when +// rhs is NaN. The max absolute error bound must be non-negative. +inline internal::FloatingEqMatcher<float> NanSensitiveFloatNear( + float rhs, float max_abs_error) { + return internal::FloatingEqMatcher<float>(rhs, true, max_abs_error); +} + +// Creates a matcher that matches a pointer (raw or smart) that points +// to a value that matches inner_matcher. +template <typename InnerMatcher> +inline internal::PointeeMatcher<InnerMatcher> Pointee( + const InnerMatcher& inner_matcher) { + return internal::PointeeMatcher<InnerMatcher>(inner_matcher); +} + +#if GTEST_HAS_RTTI +// Creates a matcher that matches a pointer or reference that matches +// inner_matcher when dynamic_cast<To> is applied. +// The result of dynamic_cast<To> is forwarded to the inner matcher. +// If To is a pointer and the cast fails, the inner matcher will receive NULL. +// If To is a reference and the cast fails, this matcher returns false +// immediately. +template <typename To> +inline PolymorphicMatcher<internal::WhenDynamicCastToMatcher<To> > +WhenDynamicCastTo(const Matcher<To>& inner_matcher) { + return MakePolymorphicMatcher( + internal::WhenDynamicCastToMatcher<To>(inner_matcher)); +} +#endif // GTEST_HAS_RTTI + +// Creates a matcher that matches an object whose given field matches +// 'matcher'. For example, +// Field(&Foo::number, Ge(5)) +// matches a Foo object x if and only if x.number >= 5. +template <typename Class, typename FieldType, typename FieldMatcher> +inline PolymorphicMatcher< + internal::FieldMatcher<Class, FieldType> > Field( + FieldType Class::*field, const FieldMatcher& matcher) { + return MakePolymorphicMatcher( + internal::FieldMatcher<Class, FieldType>( + field, MatcherCast<const FieldType&>(matcher))); + // The call to MatcherCast() is required for supporting inner + // matchers of compatible types. For example, it allows + // Field(&Foo::bar, m) + // to compile where bar is an int32 and m is a matcher for int64. +} + +// Same as Field() but also takes the name of the field to provide better error +// messages. +template <typename Class, typename FieldType, typename FieldMatcher> +inline PolymorphicMatcher<internal::FieldMatcher<Class, FieldType> > Field( + const std::string& field_name, FieldType Class::*field, + const FieldMatcher& matcher) { + return MakePolymorphicMatcher(internal::FieldMatcher<Class, FieldType>( + field_name, field, MatcherCast<const FieldType&>(matcher))); +} + +// Creates a matcher that matches an object whose given property +// matches 'matcher'. For example, +// Property(&Foo::str, StartsWith("hi")) +// matches a Foo object x if and only if x.str() starts with "hi". +template <typename Class, typename PropertyType, typename PropertyMatcher> +inline PolymorphicMatcher<internal::PropertyMatcher< + Class, PropertyType, PropertyType (Class::*)() const> > +Property(PropertyType (Class::*property)() const, + const PropertyMatcher& matcher) { + return MakePolymorphicMatcher( + internal::PropertyMatcher<Class, PropertyType, + PropertyType (Class::*)() const>( + property, MatcherCast<const PropertyType&>(matcher))); + // The call to MatcherCast() is required for supporting inner + // matchers of compatible types. For example, it allows + // Property(&Foo::bar, m) + // to compile where bar() returns an int32 and m is a matcher for int64. +} + +// Same as Property() above, but also takes the name of the property to provide +// better error messages. +template <typename Class, typename PropertyType, typename PropertyMatcher> +inline PolymorphicMatcher<internal::PropertyMatcher< + Class, PropertyType, PropertyType (Class::*)() const> > +Property(const std::string& property_name, + PropertyType (Class::*property)() const, + const PropertyMatcher& matcher) { + return MakePolymorphicMatcher( + internal::PropertyMatcher<Class, PropertyType, + PropertyType (Class::*)() const>( + property_name, property, MatcherCast<const PropertyType&>(matcher))); +} + +// The same as above but for reference-qualified member functions. +template <typename Class, typename PropertyType, typename PropertyMatcher> +inline PolymorphicMatcher<internal::PropertyMatcher< + Class, PropertyType, PropertyType (Class::*)() const &> > +Property(PropertyType (Class::*property)() const &, + const PropertyMatcher& matcher) { + return MakePolymorphicMatcher( + internal::PropertyMatcher<Class, PropertyType, + PropertyType (Class::*)() const&>( + property, MatcherCast<const PropertyType&>(matcher))); +} + +// Three-argument form for reference-qualified member functions. +template <typename Class, typename PropertyType, typename PropertyMatcher> +inline PolymorphicMatcher<internal::PropertyMatcher< + Class, PropertyType, PropertyType (Class::*)() const &> > +Property(const std::string& property_name, + PropertyType (Class::*property)() const &, + const PropertyMatcher& matcher) { + return MakePolymorphicMatcher( + internal::PropertyMatcher<Class, PropertyType, + PropertyType (Class::*)() const&>( + property_name, property, MatcherCast<const PropertyType&>(matcher))); +} + +// Creates a matcher that matches an object if and only if the result of +// applying a callable to x matches 'matcher'. For example, +// ResultOf(f, StartsWith("hi")) +// matches a Foo object x if and only if f(x) starts with "hi". +// `callable` parameter can be a function, function pointer, or a functor. It is +// required to keep no state affecting the results of the calls on it and make +// no assumptions about how many calls will be made. Any state it keeps must be +// protected from the concurrent access. +template <typename Callable, typename InnerMatcher> +internal::ResultOfMatcher<Callable, InnerMatcher> ResultOf( + Callable callable, InnerMatcher matcher) { + return internal::ResultOfMatcher<Callable, InnerMatcher>( + std::move(callable), std::move(matcher)); +} + +// String matchers. + +// Matches a string equal to str. +template <typename T = std::string> +PolymorphicMatcher<internal::StrEqualityMatcher<std::string> > StrEq( + const internal::StringLike<T>& str) { + return MakePolymorphicMatcher( + internal::StrEqualityMatcher<std::string>(std::string(str), true, true)); +} + +// Matches a string not equal to str. +template <typename T = std::string> +PolymorphicMatcher<internal::StrEqualityMatcher<std::string> > StrNe( + const internal::StringLike<T>& str) { + return MakePolymorphicMatcher( + internal::StrEqualityMatcher<std::string>(std::string(str), false, true)); +} + +// Matches a string equal to str, ignoring case. +template <typename T = std::string> +PolymorphicMatcher<internal::StrEqualityMatcher<std::string> > StrCaseEq( + const internal::StringLike<T>& str) { + return MakePolymorphicMatcher( + internal::StrEqualityMatcher<std::string>(std::string(str), true, false)); +} + +// Matches a string not equal to str, ignoring case. +template <typename T = std::string> +PolymorphicMatcher<internal::StrEqualityMatcher<std::string> > StrCaseNe( + const internal::StringLike<T>& str) { + return MakePolymorphicMatcher(internal::StrEqualityMatcher<std::string>( + std::string(str), false, false)); +} + +// Creates a matcher that matches any string, std::string, or C string +// that contains the given substring. +template <typename T = std::string> +PolymorphicMatcher<internal::HasSubstrMatcher<std::string> > HasSubstr( + const internal::StringLike<T>& substring) { + return MakePolymorphicMatcher( + internal::HasSubstrMatcher<std::string>(std::string(substring))); +} + +// Matches a string that starts with 'prefix' (case-sensitive). +template <typename T = std::string> +PolymorphicMatcher<internal::StartsWithMatcher<std::string> > StartsWith( + const internal::StringLike<T>& prefix) { + return MakePolymorphicMatcher( + internal::StartsWithMatcher<std::string>(std::string(prefix))); +} + +// Matches a string that ends with 'suffix' (case-sensitive). +template <typename T = std::string> +PolymorphicMatcher<internal::EndsWithMatcher<std::string> > EndsWith( + const internal::StringLike<T>& suffix) { + return MakePolymorphicMatcher( + internal::EndsWithMatcher<std::string>(std::string(suffix))); +} + +#if GTEST_HAS_STD_WSTRING +// Wide string matchers. + +// Matches a string equal to str. +inline PolymorphicMatcher<internal::StrEqualityMatcher<std::wstring> > StrEq( + const std::wstring& str) { + return MakePolymorphicMatcher( + internal::StrEqualityMatcher<std::wstring>(str, true, true)); +} + +// Matches a string not equal to str. +inline PolymorphicMatcher<internal::StrEqualityMatcher<std::wstring> > StrNe( + const std::wstring& str) { + return MakePolymorphicMatcher( + internal::StrEqualityMatcher<std::wstring>(str, false, true)); +} + +// Matches a string equal to str, ignoring case. +inline PolymorphicMatcher<internal::StrEqualityMatcher<std::wstring> > +StrCaseEq(const std::wstring& str) { + return MakePolymorphicMatcher( + internal::StrEqualityMatcher<std::wstring>(str, true, false)); +} + +// Matches a string not equal to str, ignoring case. +inline PolymorphicMatcher<internal::StrEqualityMatcher<std::wstring> > +StrCaseNe(const std::wstring& str) { + return MakePolymorphicMatcher( + internal::StrEqualityMatcher<std::wstring>(str, false, false)); +} + +// Creates a matcher that matches any ::wstring, std::wstring, or C wide string +// that contains the given substring. +inline PolymorphicMatcher<internal::HasSubstrMatcher<std::wstring> > HasSubstr( + const std::wstring& substring) { + return MakePolymorphicMatcher( + internal::HasSubstrMatcher<std::wstring>(substring)); +} + +// Matches a string that starts with 'prefix' (case-sensitive). +inline PolymorphicMatcher<internal::StartsWithMatcher<std::wstring> > +StartsWith(const std::wstring& prefix) { + return MakePolymorphicMatcher( + internal::StartsWithMatcher<std::wstring>(prefix)); +} + +// Matches a string that ends with 'suffix' (case-sensitive). +inline PolymorphicMatcher<internal::EndsWithMatcher<std::wstring> > EndsWith( + const std::wstring& suffix) { + return MakePolymorphicMatcher( + internal::EndsWithMatcher<std::wstring>(suffix)); +} + +#endif // GTEST_HAS_STD_WSTRING + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field == the second field. +inline internal::Eq2Matcher Eq() { return internal::Eq2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field >= the second field. +inline internal::Ge2Matcher Ge() { return internal::Ge2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field > the second field. +inline internal::Gt2Matcher Gt() { return internal::Gt2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field <= the second field. +inline internal::Le2Matcher Le() { return internal::Le2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field < the second field. +inline internal::Lt2Matcher Lt() { return internal::Lt2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field != the second field. +inline internal::Ne2Matcher Ne() { return internal::Ne2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where +// FloatEq(first field) matches the second field. +inline internal::FloatingEq2Matcher<float> FloatEq() { + return internal::FloatingEq2Matcher<float>(); +} + +// Creates a polymorphic matcher that matches a 2-tuple where +// DoubleEq(first field) matches the second field. +inline internal::FloatingEq2Matcher<double> DoubleEq() { + return internal::FloatingEq2Matcher<double>(); +} + +// Creates a polymorphic matcher that matches a 2-tuple where +// FloatEq(first field) matches the second field with NaN equality. +inline internal::FloatingEq2Matcher<float> NanSensitiveFloatEq() { + return internal::FloatingEq2Matcher<float>(true); +} + +// Creates a polymorphic matcher that matches a 2-tuple where +// DoubleEq(first field) matches the second field with NaN equality. +inline internal::FloatingEq2Matcher<double> NanSensitiveDoubleEq() { + return internal::FloatingEq2Matcher<double>(true); +} + +// Creates a polymorphic matcher that matches a 2-tuple where +// FloatNear(first field, max_abs_error) matches the second field. +inline internal::FloatingEq2Matcher<float> FloatNear(float max_abs_error) { + return internal::FloatingEq2Matcher<float>(max_abs_error); +} + +// Creates a polymorphic matcher that matches a 2-tuple where +// DoubleNear(first field, max_abs_error) matches the second field. +inline internal::FloatingEq2Matcher<double> DoubleNear(double max_abs_error) { + return internal::FloatingEq2Matcher<double>(max_abs_error); +} + +// Creates a polymorphic matcher that matches a 2-tuple where +// FloatNear(first field, max_abs_error) matches the second field with NaN +// equality. +inline internal::FloatingEq2Matcher<float> NanSensitiveFloatNear( + float max_abs_error) { + return internal::FloatingEq2Matcher<float>(max_abs_error, true); +} + +// Creates a polymorphic matcher that matches a 2-tuple where +// DoubleNear(first field, max_abs_error) matches the second field with NaN +// equality. +inline internal::FloatingEq2Matcher<double> NanSensitiveDoubleNear( + double max_abs_error) { + return internal::FloatingEq2Matcher<double>(max_abs_error, true); +} + +// Creates a matcher that matches any value of type T that m doesn't +// match. +template <typename InnerMatcher> +inline internal::NotMatcher<InnerMatcher> Not(InnerMatcher m) { + return internal::NotMatcher<InnerMatcher>(m); +} + +// Returns a matcher that matches anything that satisfies the given +// predicate. The predicate can be any unary function or functor +// whose return type can be implicitly converted to bool. +template <typename Predicate> +inline PolymorphicMatcher<internal::TrulyMatcher<Predicate> > +Truly(Predicate pred) { + return MakePolymorphicMatcher(internal::TrulyMatcher<Predicate>(pred)); +} + +// Returns a matcher that matches the container size. The container must +// support both size() and size_type which all STL-like containers provide. +// Note that the parameter 'size' can be a value of type size_type as well as +// matcher. For instance: +// EXPECT_THAT(container, SizeIs(2)); // Checks container has 2 elements. +// EXPECT_THAT(container, SizeIs(Le(2)); // Checks container has at most 2. +template <typename SizeMatcher> +inline internal::SizeIsMatcher<SizeMatcher> +SizeIs(const SizeMatcher& size_matcher) { + return internal::SizeIsMatcher<SizeMatcher>(size_matcher); +} + +// Returns a matcher that matches the distance between the container's begin() +// iterator and its end() iterator, i.e. the size of the container. This matcher +// can be used instead of SizeIs with containers such as std::forward_list which +// do not implement size(). The container must provide const_iterator (with +// valid iterator_traits), begin() and end(). +template <typename DistanceMatcher> +inline internal::BeginEndDistanceIsMatcher<DistanceMatcher> +BeginEndDistanceIs(const DistanceMatcher& distance_matcher) { + return internal::BeginEndDistanceIsMatcher<DistanceMatcher>(distance_matcher); +} + +// Returns a matcher that matches an equal container. +// This matcher behaves like Eq(), but in the event of mismatch lists the +// values that are included in one container but not the other. (Duplicate +// values and order differences are not explained.) +template <typename Container> +inline PolymorphicMatcher<internal::ContainerEqMatcher< + typename std::remove_const<Container>::type>> +ContainerEq(const Container& rhs) { + return MakePolymorphicMatcher(internal::ContainerEqMatcher<Container>(rhs)); +} + +// Returns a matcher that matches a container that, when sorted using +// the given comparator, matches container_matcher. +template <typename Comparator, typename ContainerMatcher> +inline internal::WhenSortedByMatcher<Comparator, ContainerMatcher> +WhenSortedBy(const Comparator& comparator, + const ContainerMatcher& container_matcher) { + return internal::WhenSortedByMatcher<Comparator, ContainerMatcher>( + comparator, container_matcher); +} + +// Returns a matcher that matches a container that, when sorted using +// the < operator, matches container_matcher. +template <typename ContainerMatcher> +inline internal::WhenSortedByMatcher<internal::LessComparator, ContainerMatcher> +WhenSorted(const ContainerMatcher& container_matcher) { + return + internal::WhenSortedByMatcher<internal::LessComparator, ContainerMatcher>( + internal::LessComparator(), container_matcher); +} + +// Matches an STL-style container or a native array that contains the +// same number of elements as in rhs, where its i-th element and rhs's +// i-th element (as a pair) satisfy the given pair matcher, for all i. +// TupleMatcher must be able to be safely cast to Matcher<std::tuple<const +// T1&, const T2&> >, where T1 and T2 are the types of elements in the +// LHS container and the RHS container respectively. +template <typename TupleMatcher, typename Container> +inline internal::PointwiseMatcher<TupleMatcher, + typename std::remove_const<Container>::type> +Pointwise(const TupleMatcher& tuple_matcher, const Container& rhs) { + return internal::PointwiseMatcher<TupleMatcher, Container>(tuple_matcher, + rhs); +} + + +// Supports the Pointwise(m, {a, b, c}) syntax. +template <typename TupleMatcher, typename T> +inline internal::PointwiseMatcher<TupleMatcher, std::vector<T> > Pointwise( + const TupleMatcher& tuple_matcher, std::initializer_list<T> rhs) { + return Pointwise(tuple_matcher, std::vector<T>(rhs)); +} + + +// UnorderedPointwise(pair_matcher, rhs) matches an STL-style +// container or a native array that contains the same number of +// elements as in rhs, where in some permutation of the container, its +// i-th element and rhs's i-th element (as a pair) satisfy the given +// pair matcher, for all i. Tuple2Matcher must be able to be safely +// cast to Matcher<std::tuple<const T1&, const T2&> >, where T1 and T2 are +// the types of elements in the LHS container and the RHS container +// respectively. +// +// This is like Pointwise(pair_matcher, rhs), except that the element +// order doesn't matter. +template <typename Tuple2Matcher, typename RhsContainer> +inline internal::UnorderedElementsAreArrayMatcher< + typename internal::BoundSecondMatcher< + Tuple2Matcher, + typename internal::StlContainerView< + typename std::remove_const<RhsContainer>::type>::type::value_type>> +UnorderedPointwise(const Tuple2Matcher& tuple2_matcher, + const RhsContainer& rhs_container) { + // RhsView allows the same code to handle RhsContainer being a + // STL-style container and it being a native C-style array. + typedef typename internal::StlContainerView<RhsContainer> RhsView; + typedef typename RhsView::type RhsStlContainer; + typedef typename RhsStlContainer::value_type Second; + const RhsStlContainer& rhs_stl_container = + RhsView::ConstReference(rhs_container); + + // Create a matcher for each element in rhs_container. + ::std::vector<internal::BoundSecondMatcher<Tuple2Matcher, Second> > matchers; + for (typename RhsStlContainer::const_iterator it = rhs_stl_container.begin(); + it != rhs_stl_container.end(); ++it) { + matchers.push_back( + internal::MatcherBindSecond(tuple2_matcher, *it)); + } + + // Delegate the work to UnorderedElementsAreArray(). + return UnorderedElementsAreArray(matchers); +} + + +// Supports the UnorderedPointwise(m, {a, b, c}) syntax. +template <typename Tuple2Matcher, typename T> +inline internal::UnorderedElementsAreArrayMatcher< + typename internal::BoundSecondMatcher<Tuple2Matcher, T> > +UnorderedPointwise(const Tuple2Matcher& tuple2_matcher, + std::initializer_list<T> rhs) { + return UnorderedPointwise(tuple2_matcher, std::vector<T>(rhs)); +} + + +// Matches an STL-style container or a native array that contains at +// least one element matching the given value or matcher. +// +// Examples: +// ::std::set<int> page_ids; +// page_ids.insert(3); +// page_ids.insert(1); +// EXPECT_THAT(page_ids, Contains(1)); +// EXPECT_THAT(page_ids, Contains(Gt(2))); +// EXPECT_THAT(page_ids, Not(Contains(4))); +// +// ::std::map<int, size_t> page_lengths; +// page_lengths[1] = 100; +// EXPECT_THAT(page_lengths, +// Contains(::std::pair<const int, size_t>(1, 100))); +// +// const char* user_ids[] = { "joe", "mike", "tom" }; +// EXPECT_THAT(user_ids, Contains(Eq(::std::string("tom")))); +template <typename M> +inline internal::ContainsMatcher<M> Contains(M matcher) { + return internal::ContainsMatcher<M>(matcher); +} + +// IsSupersetOf(iterator_first, iterator_last) +// IsSupersetOf(pointer, count) +// IsSupersetOf(array) +// IsSupersetOf(container) +// IsSupersetOf({e1, e2, ..., en}) +// +// IsSupersetOf() verifies that a surjective partial mapping onto a collection +// of matchers exists. In other words, a container matches +// IsSupersetOf({e1, ..., en}) if and only if there is a permutation +// {y1, ..., yn} of some of the container's elements where y1 matches e1, +// ..., and yn matches en. Obviously, the size of the container must be >= n +// in order to have a match. Examples: +// +// - {1, 2, 3} matches IsSupersetOf({Ge(3), Ne(0)}), as 3 matches Ge(3) and +// 1 matches Ne(0). +// - {1, 2} doesn't match IsSupersetOf({Eq(1), Lt(2)}), even though 1 matches +// both Eq(1) and Lt(2). The reason is that different matchers must be used +// for elements in different slots of the container. +// - {1, 1, 2} matches IsSupersetOf({Eq(1), Lt(2)}), as (the first) 1 matches +// Eq(1) and (the second) 1 matches Lt(2). +// - {1, 2, 3} matches IsSupersetOf(Gt(1), Gt(1)), as 2 matches (the first) +// Gt(1) and 3 matches (the second) Gt(1). +// +// The matchers can be specified as an array, a pointer and count, a container, +// an initializer list, or an STL iterator range. In each of these cases, the +// underlying matchers can be either values or matchers. + +template <typename Iter> +inline internal::UnorderedElementsAreArrayMatcher< + typename ::std::iterator_traits<Iter>::value_type> +IsSupersetOf(Iter first, Iter last) { + typedef typename ::std::iterator_traits<Iter>::value_type T; + return internal::UnorderedElementsAreArrayMatcher<T>( + internal::UnorderedMatcherRequire::Superset, first, last); +} + +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSupersetOf( + const T* pointer, size_t count) { + return IsSupersetOf(pointer, pointer + count); +} + +template <typename T, size_t N> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSupersetOf( + const T (&array)[N]) { + return IsSupersetOf(array, N); +} + +template <typename Container> +inline internal::UnorderedElementsAreArrayMatcher< + typename Container::value_type> +IsSupersetOf(const Container& container) { + return IsSupersetOf(container.begin(), container.end()); +} + +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSupersetOf( + ::std::initializer_list<T> xs) { + return IsSupersetOf(xs.begin(), xs.end()); +} + +// IsSubsetOf(iterator_first, iterator_last) +// IsSubsetOf(pointer, count) +// IsSubsetOf(array) +// IsSubsetOf(container) +// IsSubsetOf({e1, e2, ..., en}) +// +// IsSubsetOf() verifies that an injective mapping onto a collection of matchers +// exists. In other words, a container matches IsSubsetOf({e1, ..., en}) if and +// only if there is a subset of matchers {m1, ..., mk} which would match the +// container using UnorderedElementsAre. Obviously, the size of the container +// must be <= n in order to have a match. Examples: +// +// - {1} matches IsSubsetOf({Gt(0), Lt(0)}), as 1 matches Gt(0). +// - {1, -1} matches IsSubsetOf({Lt(0), Gt(0)}), as 1 matches Gt(0) and -1 +// matches Lt(0). +// - {1, 2} doesn't matches IsSubsetOf({Gt(0), Lt(0)}), even though 1 and 2 both +// match Gt(0). The reason is that different matchers must be used for +// elements in different slots of the container. +// +// The matchers can be specified as an array, a pointer and count, a container, +// an initializer list, or an STL iterator range. In each of these cases, the +// underlying matchers can be either values or matchers. + +template <typename Iter> +inline internal::UnorderedElementsAreArrayMatcher< + typename ::std::iterator_traits<Iter>::value_type> +IsSubsetOf(Iter first, Iter last) { + typedef typename ::std::iterator_traits<Iter>::value_type T; + return internal::UnorderedElementsAreArrayMatcher<T>( + internal::UnorderedMatcherRequire::Subset, first, last); +} + +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf( + const T* pointer, size_t count) { + return IsSubsetOf(pointer, pointer + count); +} + +template <typename T, size_t N> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf( + const T (&array)[N]) { + return IsSubsetOf(array, N); +} + +template <typename Container> +inline internal::UnorderedElementsAreArrayMatcher< + typename Container::value_type> +IsSubsetOf(const Container& container) { + return IsSubsetOf(container.begin(), container.end()); +} + +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf( + ::std::initializer_list<T> xs) { + return IsSubsetOf(xs.begin(), xs.end()); +} + +// Matches an STL-style container or a native array that contains only +// elements matching the given value or matcher. +// +// Each(m) is semantically equivalent to Not(Contains(Not(m))). Only +// the messages are different. +// +// Examples: +// ::std::set<int> page_ids; +// // Each(m) matches an empty container, regardless of what m is. +// EXPECT_THAT(page_ids, Each(Eq(1))); +// EXPECT_THAT(page_ids, Each(Eq(77))); +// +// page_ids.insert(3); +// EXPECT_THAT(page_ids, Each(Gt(0))); +// EXPECT_THAT(page_ids, Not(Each(Gt(4)))); +// page_ids.insert(1); +// EXPECT_THAT(page_ids, Not(Each(Lt(2)))); +// +// ::std::map<int, size_t> page_lengths; +// page_lengths[1] = 100; +// page_lengths[2] = 200; +// page_lengths[3] = 300; +// EXPECT_THAT(page_lengths, Not(Each(Pair(1, 100)))); +// EXPECT_THAT(page_lengths, Each(Key(Le(3)))); +// +// const char* user_ids[] = { "joe", "mike", "tom" }; +// EXPECT_THAT(user_ids, Not(Each(Eq(::std::string("tom"))))); +template <typename M> +inline internal::EachMatcher<M> Each(M matcher) { + return internal::EachMatcher<M>(matcher); +} + +// Key(inner_matcher) matches an std::pair whose 'first' field matches +// inner_matcher. For example, Contains(Key(Ge(5))) can be used to match an +// std::map that contains at least one element whose key is >= 5. +template <typename M> +inline internal::KeyMatcher<M> Key(M inner_matcher) { + return internal::KeyMatcher<M>(inner_matcher); +} + +// Pair(first_matcher, second_matcher) matches a std::pair whose 'first' field +// matches first_matcher and whose 'second' field matches second_matcher. For +// example, EXPECT_THAT(map_type, ElementsAre(Pair(Ge(5), "foo"))) can be used +// to match a std::map<int, string> that contains exactly one element whose key +// is >= 5 and whose value equals "foo". +template <typename FirstMatcher, typename SecondMatcher> +inline internal::PairMatcher<FirstMatcher, SecondMatcher> +Pair(FirstMatcher first_matcher, SecondMatcher second_matcher) { + return internal::PairMatcher<FirstMatcher, SecondMatcher>( + first_matcher, second_matcher); +} + +namespace no_adl { +// FieldsAre(matchers...) matches piecewise the fields of compatible structs. +// These include those that support `get<I>(obj)`, and when structured bindings +// are enabled any class that supports them. +// In particular, `std::tuple`, `std::pair`, `std::array` and aggregate types. +template <typename... M> +internal::FieldsAreMatcher<typename std::decay<M>::type...> FieldsAre( + M&&... matchers) { + return internal::FieldsAreMatcher<typename std::decay<M>::type...>( + std::forward<M>(matchers)...); +} + +// Creates a matcher that matches a pointer (raw or smart) that matches +// inner_matcher. +template <typename InnerMatcher> +inline internal::PointerMatcher<InnerMatcher> Pointer( + const InnerMatcher& inner_matcher) { + return internal::PointerMatcher<InnerMatcher>(inner_matcher); +} + +// Creates a matcher that matches an object that has an address that matches +// inner_matcher. +template <typename InnerMatcher> +inline internal::AddressMatcher<InnerMatcher> Address( + const InnerMatcher& inner_matcher) { + return internal::AddressMatcher<InnerMatcher>(inner_matcher); +} +} // namespace no_adl + +// Returns a predicate that is satisfied by anything that matches the +// given matcher. +template <typename M> +inline internal::MatcherAsPredicate<M> Matches(M matcher) { + return internal::MatcherAsPredicate<M>(matcher); +} + +// Returns true if and only if the value matches the matcher. +template <typename T, typename M> +inline bool Value(const T& value, M matcher) { + return testing::Matches(matcher)(value); +} + +// Matches the value against the given matcher and explains the match +// result to listener. +template <typename T, typename M> +inline bool ExplainMatchResult( + M matcher, const T& value, MatchResultListener* listener) { + return SafeMatcherCast<const T&>(matcher).MatchAndExplain(value, listener); +} + +// Returns a string representation of the given matcher. Useful for description +// strings of matchers defined using MATCHER_P* macros that accept matchers as +// their arguments. For example: +// +// MATCHER_P(XAndYThat, matcher, +// "X that " + DescribeMatcher<int>(matcher, negation) + +// " and Y that " + DescribeMatcher<double>(matcher, negation)) { +// return ExplainMatchResult(matcher, arg.x(), result_listener) && +// ExplainMatchResult(matcher, arg.y(), result_listener); +// } +template <typename T, typename M> +std::string DescribeMatcher(const M& matcher, bool negation = false) { + ::std::stringstream ss; + Matcher<T> monomorphic_matcher = SafeMatcherCast<T>(matcher); + if (negation) { + monomorphic_matcher.DescribeNegationTo(&ss); + } else { + monomorphic_matcher.DescribeTo(&ss); + } + return ss.str(); +} + +template <typename... Args> +internal::ElementsAreMatcher< + std::tuple<typename std::decay<const Args&>::type...>> +ElementsAre(const Args&... matchers) { + return internal::ElementsAreMatcher< + std::tuple<typename std::decay<const Args&>::type...>>( + std::make_tuple(matchers...)); +} + +template <typename... Args> +internal::UnorderedElementsAreMatcher< + std::tuple<typename std::decay<const Args&>::type...>> +UnorderedElementsAre(const Args&... matchers) { + return internal::UnorderedElementsAreMatcher< + std::tuple<typename std::decay<const Args&>::type...>>( + std::make_tuple(matchers...)); +} + +// Define variadic matcher versions. +template <typename... Args> +internal::AllOfMatcher<typename std::decay<const Args&>::type...> AllOf( + const Args&... matchers) { + return internal::AllOfMatcher<typename std::decay<const Args&>::type...>( + matchers...); +} + +template <typename... Args> +internal::AnyOfMatcher<typename std::decay<const Args&>::type...> AnyOf( + const Args&... matchers) { + return internal::AnyOfMatcher<typename std::decay<const Args&>::type...>( + matchers...); +} + +// AnyOfArray(array) +// AnyOfArray(pointer, count) +// AnyOfArray(container) +// AnyOfArray({ e1, e2, ..., en }) +// AnyOfArray(iterator_first, iterator_last) +// +// AnyOfArray() verifies whether a given value matches any member of a +// collection of matchers. +// +// AllOfArray(array) +// AllOfArray(pointer, count) +// AllOfArray(container) +// AllOfArray({ e1, e2, ..., en }) +// AllOfArray(iterator_first, iterator_last) +// +// AllOfArray() verifies whether a given value matches all members of a +// collection of matchers. +// +// The matchers can be specified as an array, a pointer and count, a container, +// an initializer list, or an STL iterator range. In each of these cases, the +// underlying matchers can be either values or matchers. + +template <typename Iter> +inline internal::AnyOfArrayMatcher< + typename ::std::iterator_traits<Iter>::value_type> +AnyOfArray(Iter first, Iter last) { + return internal::AnyOfArrayMatcher< + typename ::std::iterator_traits<Iter>::value_type>(first, last); +} + +template <typename Iter> +inline internal::AllOfArrayMatcher< + typename ::std::iterator_traits<Iter>::value_type> +AllOfArray(Iter first, Iter last) { + return internal::AllOfArrayMatcher< + typename ::std::iterator_traits<Iter>::value_type>(first, last); +} + +template <typename T> +inline internal::AnyOfArrayMatcher<T> AnyOfArray(const T* ptr, size_t count) { + return AnyOfArray(ptr, ptr + count); +} + +template <typename T> +inline internal::AllOfArrayMatcher<T> AllOfArray(const T* ptr, size_t count) { + return AllOfArray(ptr, ptr + count); +} + +template <typename T, size_t N> +inline internal::AnyOfArrayMatcher<T> AnyOfArray(const T (&array)[N]) { + return AnyOfArray(array, N); +} + +template <typename T, size_t N> +inline internal::AllOfArrayMatcher<T> AllOfArray(const T (&array)[N]) { + return AllOfArray(array, N); +} + +template <typename Container> +inline internal::AnyOfArrayMatcher<typename Container::value_type> AnyOfArray( + const Container& container) { + return AnyOfArray(container.begin(), container.end()); +} + +template <typename Container> +inline internal::AllOfArrayMatcher<typename Container::value_type> AllOfArray( + const Container& container) { + return AllOfArray(container.begin(), container.end()); +} + +template <typename T> +inline internal::AnyOfArrayMatcher<T> AnyOfArray( + ::std::initializer_list<T> xs) { + return AnyOfArray(xs.begin(), xs.end()); +} + +template <typename T> +inline internal::AllOfArrayMatcher<T> AllOfArray( + ::std::initializer_list<T> xs) { + return AllOfArray(xs.begin(), xs.end()); +} + +// Args<N1, N2, ..., Nk>(a_matcher) matches a tuple if the selected +// fields of it matches a_matcher. C++ doesn't support default +// arguments for function templates, so we have to overload it. +template <size_t... k, typename InnerMatcher> +internal::ArgsMatcher<typename std::decay<InnerMatcher>::type, k...> Args( + InnerMatcher&& matcher) { + return internal::ArgsMatcher<typename std::decay<InnerMatcher>::type, k...>( + std::forward<InnerMatcher>(matcher)); +} + +// AllArgs(m) is a synonym of m. This is useful in +// +// EXPECT_CALL(foo, Bar(_, _)).With(AllArgs(Eq())); +// +// which is easier to read than +// +// EXPECT_CALL(foo, Bar(_, _)).With(Eq()); +template <typename InnerMatcher> +inline InnerMatcher AllArgs(const InnerMatcher& matcher) { return matcher; } + +// Returns a matcher that matches the value of an optional<> type variable. +// The matcher implementation only uses '!arg' and requires that the optional<> +// type has a 'value_type' member type and that '*arg' is of type 'value_type' +// and is printable using 'PrintToString'. It is compatible with +// std::optional/std::experimental::optional. +// Note that to compare an optional type variable against nullopt you should +// use Eq(nullopt) and not Eq(Optional(nullopt)). The latter implies that the +// optional value contains an optional itself. +template <typename ValueMatcher> +inline internal::OptionalMatcher<ValueMatcher> Optional( + const ValueMatcher& value_matcher) { + return internal::OptionalMatcher<ValueMatcher>(value_matcher); +} + +// Returns a matcher that matches the value of a absl::any type variable. +template <typename T> +PolymorphicMatcher<internal::any_cast_matcher::AnyCastMatcher<T> > AnyWith( + const Matcher<const T&>& matcher) { + return MakePolymorphicMatcher( + internal::any_cast_matcher::AnyCastMatcher<T>(matcher)); +} + +// Returns a matcher that matches the value of a variant<> type variable. +// The matcher implementation uses ADL to find the holds_alternative and get +// functions. +// It is compatible with std::variant. +template <typename T> +PolymorphicMatcher<internal::variant_matcher::VariantMatcher<T> > VariantWith( + const Matcher<const T&>& matcher) { + return MakePolymorphicMatcher( + internal::variant_matcher::VariantMatcher<T>(matcher)); +} + +#if GTEST_HAS_EXCEPTIONS + +// Anything inside the `internal` namespace is internal to the implementation +// and must not be used in user code! +namespace internal { + +class WithWhatMatcherImpl { + public: + WithWhatMatcherImpl(Matcher<std::string> matcher) + : matcher_(std::move(matcher)) {} + + void DescribeTo(std::ostream* os) const { + *os << "contains .what() that "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "contains .what() that does not "; + matcher_.DescribeTo(os); + } + + template <typename Err> + bool MatchAndExplain(const Err& err, MatchResultListener* listener) const { + *listener << "which contains .what() that "; + return matcher_.MatchAndExplain(err.what(), listener); + } + + private: + const Matcher<std::string> matcher_; +}; + +inline PolymorphicMatcher<WithWhatMatcherImpl> WithWhat( + Matcher<std::string> m) { + return MakePolymorphicMatcher(WithWhatMatcherImpl(std::move(m))); +} + +template <typename Err> +class ExceptionMatcherImpl { + class NeverThrown { + public: + const char* what() const noexcept { + return "this exception should never be thrown"; + } + }; + + // If the matchee raises an exception of a wrong type, we'd like to + // catch it and print its message and type. To do that, we add an additional + // catch clause: + // + // try { ... } + // catch (const Err&) { /* an expected exception */ } + // catch (const std::exception&) { /* exception of a wrong type */ } + // + // However, if the `Err` itself is `std::exception`, we'd end up with two + // identical `catch` clauses: + // + // try { ... } + // catch (const std::exception&) { /* an expected exception */ } + // catch (const std::exception&) { /* exception of a wrong type */ } + // + // This can cause a warning or an error in some compilers. To resolve + // the issue, we use a fake error type whenever `Err` is `std::exception`: + // + // try { ... } + // catch (const std::exception&) { /* an expected exception */ } + // catch (const NeverThrown&) { /* exception of a wrong type */ } + using DefaultExceptionType = typename std::conditional< + std::is_same<typename std::remove_cv< + typename std::remove_reference<Err>::type>::type, + std::exception>::value, + const NeverThrown&, const std::exception&>::type; + + public: + ExceptionMatcherImpl(Matcher<const Err&> matcher) + : matcher_(std::move(matcher)) {} + + void DescribeTo(std::ostream* os) const { + *os << "throws an exception which is a " << GetTypeName<Err>(); + *os << " which "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "throws an exception which is not a " << GetTypeName<Err>(); + *os << " which "; + matcher_.DescribeNegationTo(os); + } + + template <typename T> + bool MatchAndExplain(T&& x, MatchResultListener* listener) const { + try { + (void)(std::forward<T>(x)()); + } catch (const Err& err) { + *listener << "throws an exception which is a " << GetTypeName<Err>(); + *listener << " "; + return matcher_.MatchAndExplain(err, listener); + } catch (DefaultExceptionType err) { +#if GTEST_HAS_RTTI + *listener << "throws an exception of type " << GetTypeName(typeid(err)); + *listener << " "; +#else + *listener << "throws an std::exception-derived type "; +#endif + *listener << "with description \"" << err.what() << "\""; + return false; + } catch (...) { + *listener << "throws an exception of an unknown type"; + return false; + } + + *listener << "does not throw any exception"; + return false; + } + + private: + const Matcher<const Err&> matcher_; +}; + +} // namespace internal + +// Throws() +// Throws(exceptionMatcher) +// ThrowsMessage(messageMatcher) +// +// This matcher accepts a callable and verifies that when invoked, it throws +// an exception with the given type and properties. +// +// Examples: +// +// EXPECT_THAT( +// []() { throw std::runtime_error("message"); }, +// Throws<std::runtime_error>()); +// +// EXPECT_THAT( +// []() { throw std::runtime_error("message"); }, +// ThrowsMessage<std::runtime_error>(HasSubstr("message"))); +// +// EXPECT_THAT( +// []() { throw std::runtime_error("message"); }, +// Throws<std::runtime_error>( +// Property(&std::runtime_error::what, HasSubstr("message")))); + +template <typename Err> +PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>> Throws() { + return MakePolymorphicMatcher( + internal::ExceptionMatcherImpl<Err>(A<const Err&>())); +} + +template <typename Err, typename ExceptionMatcher> +PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>> Throws( + const ExceptionMatcher& exception_matcher) { + // Using matcher cast allows users to pass a matcher of a more broad type. + // For example user may want to pass Matcher<std::exception> + // to Throws<std::runtime_error>, or Matcher<int64> to Throws<int32>. + return MakePolymorphicMatcher(internal::ExceptionMatcherImpl<Err>( + SafeMatcherCast<const Err&>(exception_matcher))); +} + +template <typename Err, typename MessageMatcher> +PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>> ThrowsMessage( + MessageMatcher&& message_matcher) { + static_assert(std::is_base_of<std::exception, Err>::value, + "expected an std::exception-derived type"); + return Throws<Err>(internal::WithWhat( + MatcherCast<std::string>(std::forward<MessageMatcher>(message_matcher)))); +} + +#endif // GTEST_HAS_EXCEPTIONS + +// These macros allow using matchers to check values in Google Test +// tests. ASSERT_THAT(value, matcher) and EXPECT_THAT(value, matcher) +// succeed if and only if the value matches the matcher. If the assertion +// fails, the value and the description of the matcher will be printed. +#define ASSERT_THAT(value, matcher) ASSERT_PRED_FORMAT1(\ + ::testing::internal::MakePredicateFormatterFromMatcher(matcher), value) +#define EXPECT_THAT(value, matcher) EXPECT_PRED_FORMAT1(\ + ::testing::internal::MakePredicateFormatterFromMatcher(matcher), value) + +// MATCHER* macroses itself are listed below. +#define MATCHER(name, description) \ + class name##Matcher \ + : public ::testing::internal::MatcherBaseImpl<name##Matcher> { \ + public: \ + template <typename arg_type> \ + class gmock_Impl : public ::testing::MatcherInterface<const arg_type&> { \ + public: \ + gmock_Impl() {} \ + bool MatchAndExplain( \ + const arg_type& arg, \ + ::testing::MatchResultListener* result_listener) const override; \ + void DescribeTo(::std::ostream* gmock_os) const override { \ + *gmock_os << FormatDescription(false); \ + } \ + void DescribeNegationTo(::std::ostream* gmock_os) const override { \ + *gmock_os << FormatDescription(true); \ + } \ + \ + private: \ + ::std::string FormatDescription(bool negation) const { \ + ::std::string gmock_description = (description); \ + if (!gmock_description.empty()) { \ + return gmock_description; \ + } \ + return ::testing::internal::FormatMatcherDescription(negation, #name, \ + {}); \ + } \ + }; \ + }; \ + GTEST_ATTRIBUTE_UNUSED_ inline name##Matcher name() { return {}; } \ + template <typename arg_type> \ + bool name##Matcher::gmock_Impl<arg_type>::MatchAndExplain( \ + const arg_type& arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_) \ + const + +#define MATCHER_P(name, p0, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP, description, (p0)) +#define MATCHER_P2(name, p0, p1, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP2, description, (p0, p1)) +#define MATCHER_P3(name, p0, p1, p2, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP3, description, (p0, p1, p2)) +#define MATCHER_P4(name, p0, p1, p2, p3, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP4, description, (p0, p1, p2, p3)) +#define MATCHER_P5(name, p0, p1, p2, p3, p4, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP5, description, \ + (p0, p1, p2, p3, p4)) +#define MATCHER_P6(name, p0, p1, p2, p3, p4, p5, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP6, description, \ + (p0, p1, p2, p3, p4, p5)) +#define MATCHER_P7(name, p0, p1, p2, p3, p4, p5, p6, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP7, description, \ + (p0, p1, p2, p3, p4, p5, p6)) +#define MATCHER_P8(name, p0, p1, p2, p3, p4, p5, p6, p7, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP8, description, \ + (p0, p1, p2, p3, p4, p5, p6, p7)) +#define MATCHER_P9(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP9, description, \ + (p0, p1, p2, p3, p4, p5, p6, p7, p8)) +#define MATCHER_P10(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, description) \ + GMOCK_INTERNAL_MATCHER(name, name##MatcherP10, description, \ + (p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) + +#define GMOCK_INTERNAL_MATCHER(name, full_name, description, args) \ + template <GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAMS(args)> \ + class full_name : public ::testing::internal::MatcherBaseImpl< \ + full_name<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)>> { \ + public: \ + using full_name::MatcherBaseImpl::MatcherBaseImpl; \ + template <typename arg_type> \ + class gmock_Impl : public ::testing::MatcherInterface<const arg_type&> { \ + public: \ + explicit gmock_Impl(GMOCK_INTERNAL_MATCHER_FUNCTION_ARGS(args)) \ + : GMOCK_INTERNAL_MATCHER_FORWARD_ARGS(args) {} \ + bool MatchAndExplain( \ + const arg_type& arg, \ + ::testing::MatchResultListener* result_listener) const override; \ + void DescribeTo(::std::ostream* gmock_os) const override { \ + *gmock_os << FormatDescription(false); \ + } \ + void DescribeNegationTo(::std::ostream* gmock_os) const override { \ + *gmock_os << FormatDescription(true); \ + } \ + GMOCK_INTERNAL_MATCHER_MEMBERS(args) \ + \ + private: \ + ::std::string FormatDescription(bool negation) const { \ + ::std::string gmock_description = (description); \ + if (!gmock_description.empty()) { \ + return gmock_description; \ + } \ + return ::testing::internal::FormatMatcherDescription( \ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings( \ + ::std::tuple<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)>( \ + GMOCK_INTERNAL_MATCHER_MEMBERS_USAGE(args)))); \ + } \ + }; \ + }; \ + template <GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAMS(args)> \ + inline full_name<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)> name( \ + GMOCK_INTERNAL_MATCHER_FUNCTION_ARGS(args)) { \ + return full_name<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)>( \ + GMOCK_INTERNAL_MATCHER_ARGS_USAGE(args)); \ + } \ + template <GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAMS(args)> \ + template <typename arg_type> \ + bool full_name<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)>::gmock_Impl< \ + arg_type>::MatchAndExplain(const arg_type& arg, \ + ::testing::MatchResultListener* \ + result_listener GTEST_ATTRIBUTE_UNUSED_) \ + const + +#define GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAMS(args) \ + GMOCK_PP_TAIL( \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAM, , args)) +#define GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAM(i_unused, data_unused, arg) \ + , typename arg##_type + +#define GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_TYPE_PARAM, , args)) +#define GMOCK_INTERNAL_MATCHER_TYPE_PARAM(i_unused, data_unused, arg) \ + , arg##_type + +#define GMOCK_INTERNAL_MATCHER_FUNCTION_ARGS(args) \ + GMOCK_PP_TAIL(dummy_first GMOCK_PP_FOR_EACH( \ + GMOCK_INTERNAL_MATCHER_FUNCTION_ARG, , args)) +#define GMOCK_INTERNAL_MATCHER_FUNCTION_ARG(i, data_unused, arg) \ + , arg##_type gmock_p##i + +#define GMOCK_INTERNAL_MATCHER_FORWARD_ARGS(args) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_FORWARD_ARG, , args)) +#define GMOCK_INTERNAL_MATCHER_FORWARD_ARG(i, data_unused, arg) \ + , arg(::std::forward<arg##_type>(gmock_p##i)) + +#define GMOCK_INTERNAL_MATCHER_MEMBERS(args) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_MEMBER, , args) +#define GMOCK_INTERNAL_MATCHER_MEMBER(i_unused, data_unused, arg) \ + const arg##_type arg; + +#define GMOCK_INTERNAL_MATCHER_MEMBERS_USAGE(args) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_MEMBER_USAGE, , args)) +#define GMOCK_INTERNAL_MATCHER_MEMBER_USAGE(i_unused, data_unused, arg) , arg + +#define GMOCK_INTERNAL_MATCHER_ARGS_USAGE(args) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_ARG_USAGE, , args)) +#define GMOCK_INTERNAL_MATCHER_ARG_USAGE(i, data_unused, arg_unused) \ + , gmock_p##i + +// To prevent ADL on certain functions we put them on a separate namespace. +using namespace no_adl; // NOLINT + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 5046 + +// Include any custom callback matchers added by the local installation. +// We must include this header at the end to make sure it can use the +// declarations from this file. +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Injection point for custom user configurations. See README for details +// +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_ +#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_ + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ + +#if GTEST_HAS_EXCEPTIONS +# include <stdexcept> // NOLINT +#endif + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// An abstract handle of an expectation. +class Expectation; + +// A set of expectation handles. +class ExpectationSet; + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// Implements a mock function. +template <typename F> class FunctionMocker; + +// Base class for expectations. +class ExpectationBase; + +// Implements an expectation. +template <typename F> class TypedExpectation; + +// Helper class for testing the Expectation class template. +class ExpectationTester; + +// Helper classes for implementing NiceMock, StrictMock, and NaggyMock. +template <typename MockClass> +class NiceMockImpl; +template <typename MockClass> +class StrictMockImpl; +template <typename MockClass> +class NaggyMockImpl; + +// Protects the mock object registry (in class Mock), all function +// mockers, and all expectations. +// +// The reason we don't use more fine-grained protection is: when a +// mock function Foo() is called, it needs to consult its expectations +// to see which one should be picked. If another thread is allowed to +// call a mock function (either Foo() or a different one) at the same +// time, it could affect the "retired" attributes of Foo()'s +// expectations when InSequence() is used, and thus affect which +// expectation gets picked. Therefore, we sequence all mock function +// calls to ensure the integrity of the mock objects' states. +GTEST_API_ GTEST_DECLARE_STATIC_MUTEX_(g_gmock_mutex); + +// Untyped base class for ActionResultHolder<R>. +class UntypedActionResultHolderBase; + +// Abstract base class of FunctionMocker. This is the +// type-agnostic part of the function mocker interface. Its pure +// virtual methods are implemented by FunctionMocker. +class GTEST_API_ UntypedFunctionMockerBase { + public: + UntypedFunctionMockerBase(); + virtual ~UntypedFunctionMockerBase(); + + // Verifies that all expectations on this mock function have been + // satisfied. Reports one or more Google Test non-fatal failures + // and returns false if not. + bool VerifyAndClearExpectationsLocked() + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex); + + // Clears the ON_CALL()s set on this mock function. + virtual void ClearDefaultActionsLocked() + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) = 0; + + // In all of the following Untyped* functions, it's the caller's + // responsibility to guarantee the correctness of the arguments' + // types. + + // Performs the default action with the given arguments and returns + // the action's result. The call description string will be used in + // the error message to describe the call in the case the default + // action fails. + // L = * + virtual UntypedActionResultHolderBase* UntypedPerformDefaultAction( + void* untyped_args, const std::string& call_description) const = 0; + + // Performs the given action with the given arguments and returns + // the action's result. + // L = * + virtual UntypedActionResultHolderBase* UntypedPerformAction( + const void* untyped_action, void* untyped_args) const = 0; + + // Writes a message that the call is uninteresting (i.e. neither + // explicitly expected nor explicitly unexpected) to the given + // ostream. + virtual void UntypedDescribeUninterestingCall( + const void* untyped_args, + ::std::ostream* os) const + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) = 0; + + // Returns the expectation that matches the given function arguments + // (or NULL is there's no match); when a match is found, + // untyped_action is set to point to the action that should be + // performed (or NULL if the action is "do default"), and + // is_excessive is modified to indicate whether the call exceeds the + // expected number. + virtual const ExpectationBase* UntypedFindMatchingExpectation( + const void* untyped_args, + const void** untyped_action, bool* is_excessive, + ::std::ostream* what, ::std::ostream* why) + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) = 0; + + // Prints the given function arguments to the ostream. + virtual void UntypedPrintArgs(const void* untyped_args, + ::std::ostream* os) const = 0; + + // Sets the mock object this mock method belongs to, and registers + // this information in the global mock registry. Will be called + // whenever an EXPECT_CALL() or ON_CALL() is executed on this mock + // method. + void RegisterOwner(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(g_gmock_mutex); + + // Sets the mock object this mock method belongs to, and sets the + // name of the mock function. Will be called upon each invocation + // of this mock function. + void SetOwnerAndName(const void* mock_obj, const char* name) + GTEST_LOCK_EXCLUDED_(g_gmock_mutex); + + // Returns the mock object this mock method belongs to. Must be + // called after RegisterOwner() or SetOwnerAndName() has been + // called. + const void* MockObject() const + GTEST_LOCK_EXCLUDED_(g_gmock_mutex); + + // Returns the name of this mock method. Must be called after + // SetOwnerAndName() has been called. + const char* Name() const + GTEST_LOCK_EXCLUDED_(g_gmock_mutex); + + // Returns the result of invoking this mock function with the given + // arguments. This function can be safely called from multiple + // threads concurrently. The caller is responsible for deleting the + // result. + UntypedActionResultHolderBase* UntypedInvokeWith(void* untyped_args) + GTEST_LOCK_EXCLUDED_(g_gmock_mutex); + + protected: + typedef std::vector<const void*> UntypedOnCallSpecs; + + using UntypedExpectations = std::vector<std::shared_ptr<ExpectationBase>>; + + // Returns an Expectation object that references and co-owns exp, + // which must be an expectation on this mock function. + Expectation GetHandleOf(ExpectationBase* exp); + + // Address of the mock object this mock method belongs to. Only + // valid after this mock method has been called or + // ON_CALL/EXPECT_CALL has been invoked on it. + const void* mock_obj_; // Protected by g_gmock_mutex. + + // Name of the function being mocked. Only valid after this mock + // method has been called. + const char* name_; // Protected by g_gmock_mutex. + + // All default action specs for this function mocker. + UntypedOnCallSpecs untyped_on_call_specs_; + + // All expectations for this function mocker. + // + // It's undefined behavior to interleave expectations (EXPECT_CALLs + // or ON_CALLs) and mock function calls. Also, the order of + // expectations is important. Therefore it's a logic race condition + // to read/write untyped_expectations_ concurrently. In order for + // tools like tsan to catch concurrent read/write accesses to + // untyped_expectations, we deliberately leave accesses to it + // unprotected. + UntypedExpectations untyped_expectations_; +}; // class UntypedFunctionMockerBase + +// Untyped base class for OnCallSpec<F>. +class UntypedOnCallSpecBase { + public: + // The arguments are the location of the ON_CALL() statement. + UntypedOnCallSpecBase(const char* a_file, int a_line) + : file_(a_file), line_(a_line), last_clause_(kNone) {} + + // Where in the source file was the default action spec defined? + const char* file() const { return file_; } + int line() const { return line_; } + + protected: + // Gives each clause in the ON_CALL() statement a name. + enum Clause { + // Do not change the order of the enum members! The run-time + // syntax checking relies on it. + kNone, + kWith, + kWillByDefault + }; + + // Asserts that the ON_CALL() statement has a certain property. + void AssertSpecProperty(bool property, + const std::string& failure_message) const { + Assert(property, file_, line_, failure_message); + } + + // Expects that the ON_CALL() statement has a certain property. + void ExpectSpecProperty(bool property, + const std::string& failure_message) const { + Expect(property, file_, line_, failure_message); + } + + const char* file_; + int line_; + + // The last clause in the ON_CALL() statement as seen so far. + // Initially kNone and changes as the statement is parsed. + Clause last_clause_; +}; // class UntypedOnCallSpecBase + +// This template class implements an ON_CALL spec. +template <typename F> +class OnCallSpec : public UntypedOnCallSpecBase { + public: + typedef typename Function<F>::ArgumentTuple ArgumentTuple; + typedef typename Function<F>::ArgumentMatcherTuple ArgumentMatcherTuple; + + // Constructs an OnCallSpec object from the information inside + // the parenthesis of an ON_CALL() statement. + OnCallSpec(const char* a_file, int a_line, + const ArgumentMatcherTuple& matchers) + : UntypedOnCallSpecBase(a_file, a_line), + matchers_(matchers), + // By default, extra_matcher_ should match anything. However, + // we cannot initialize it with _ as that causes ambiguity between + // Matcher's copy and move constructor for some argument types. + extra_matcher_(A<const ArgumentTuple&>()) {} + + // Implements the .With() clause. + OnCallSpec& With(const Matcher<const ArgumentTuple&>& m) { + // Makes sure this is called at most once. + ExpectSpecProperty(last_clause_ < kWith, + ".With() cannot appear " + "more than once in an ON_CALL()."); + last_clause_ = kWith; + + extra_matcher_ = m; + return *this; + } + + // Implements the .WillByDefault() clause. + OnCallSpec& WillByDefault(const Action<F>& action) { + ExpectSpecProperty(last_clause_ < kWillByDefault, + ".WillByDefault() must appear " + "exactly once in an ON_CALL()."); + last_clause_ = kWillByDefault; + + ExpectSpecProperty(!action.IsDoDefault(), + "DoDefault() cannot be used in ON_CALL()."); + action_ = action; + return *this; + } + + // Returns true if and only if the given arguments match the matchers. + bool Matches(const ArgumentTuple& args) const { + return TupleMatches(matchers_, args) && extra_matcher_.Matches(args); + } + + // Returns the action specified by the user. + const Action<F>& GetAction() const { + AssertSpecProperty(last_clause_ == kWillByDefault, + ".WillByDefault() must appear exactly " + "once in an ON_CALL()."); + return action_; + } + + private: + // The information in statement + // + // ON_CALL(mock_object, Method(matchers)) + // .With(multi-argument-matcher) + // .WillByDefault(action); + // + // is recorded in the data members like this: + // + // source file that contains the statement => file_ + // line number of the statement => line_ + // matchers => matchers_ + // multi-argument-matcher => extra_matcher_ + // action => action_ + ArgumentMatcherTuple matchers_; + Matcher<const ArgumentTuple&> extra_matcher_; + Action<F> action_; +}; // class OnCallSpec + +// Possible reactions on uninteresting calls. +enum CallReaction { + kAllow, + kWarn, + kFail, +}; + +} // namespace internal + +// Utilities for manipulating mock objects. +class GTEST_API_ Mock { + public: + // The following public methods can be called concurrently. + + // Tells Google Mock to ignore mock_obj when checking for leaked + // mock objects. + static void AllowLeak(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Verifies and clears all expectations on the given mock object. + // If the expectations aren't satisfied, generates one or more + // Google Test non-fatal failures and returns false. + static bool VerifyAndClearExpectations(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Verifies all expectations on the given mock object and clears its + // default actions and expectations. Returns true if and only if the + // verification was successful. + static bool VerifyAndClear(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Returns whether the mock was created as a naggy mock (default) + static bool IsNaggy(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + // Returns whether the mock was created as a nice mock + static bool IsNice(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + // Returns whether the mock was created as a strict mock + static bool IsStrict(void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + private: + friend class internal::UntypedFunctionMockerBase; + + // Needed for a function mocker to register itself (so that we know + // how to clear a mock object). + template <typename F> + friend class internal::FunctionMocker; + + template <typename MockClass> + friend class internal::NiceMockImpl; + template <typename MockClass> + friend class internal::NaggyMockImpl; + template <typename MockClass> + friend class internal::StrictMockImpl; + + // Tells Google Mock to allow uninteresting calls on the given mock + // object. + static void AllowUninterestingCalls(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Tells Google Mock to warn the user about uninteresting calls on + // the given mock object. + static void WarnUninterestingCalls(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Tells Google Mock to fail uninteresting calls on the given mock + // object. + static void FailUninterestingCalls(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Tells Google Mock the given mock object is being destroyed and + // its entry in the call-reaction table should be removed. + static void UnregisterCallReaction(const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Returns the reaction Google Mock will have on uninteresting calls + // made on the given mock object. + static internal::CallReaction GetReactionOnUninterestingCalls( + const void* mock_obj) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Verifies that all expectations on the given mock object have been + // satisfied. Reports one or more Google Test non-fatal failures + // and returns false if not. + static bool VerifyAndClearExpectationsLocked(void* mock_obj) + GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex); + + // Clears all ON_CALL()s set on the given mock object. + static void ClearDefaultActionsLocked(void* mock_obj) + GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex); + + // Registers a mock object and a mock method it owns. + static void Register( + const void* mock_obj, + internal::UntypedFunctionMockerBase* mocker) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Tells Google Mock where in the source code mock_obj is used in an + // ON_CALL or EXPECT_CALL. In case mock_obj is leaked, this + // information helps the user identify which object it is. + static void RegisterUseByOnCallOrExpectCall( + const void* mock_obj, const char* file, int line) + GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); + + // Unregisters a mock method; removes the owning mock object from + // the registry when the last mock method associated with it has + // been unregistered. This is called only in the destructor of + // FunctionMocker. + static void UnregisterLocked(internal::UntypedFunctionMockerBase* mocker) + GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex); +}; // class Mock + +// An abstract handle of an expectation. Useful in the .After() +// clause of EXPECT_CALL() for setting the (partial) order of +// expectations. The syntax: +// +// Expectation e1 = EXPECT_CALL(...)...; +// EXPECT_CALL(...).After(e1)...; +// +// sets two expectations where the latter can only be matched after +// the former has been satisfied. +// +// Notes: +// - This class is copyable and has value semantics. +// - Constness is shallow: a const Expectation object itself cannot +// be modified, but the mutable methods of the ExpectationBase +// object it references can be called via expectation_base(). + +class GTEST_API_ Expectation { + public: + // Constructs a null object that doesn't reference any expectation. + Expectation(); + Expectation(Expectation&&) = default; + Expectation(const Expectation&) = default; + Expectation& operator=(Expectation&&) = default; + Expectation& operator=(const Expectation&) = default; + ~Expectation(); + + // This single-argument ctor must not be explicit, in order to support the + // Expectation e = EXPECT_CALL(...); + // syntax. + // + // A TypedExpectation object stores its pre-requisites as + // Expectation objects, and needs to call the non-const Retire() + // method on the ExpectationBase objects they reference. Therefore + // Expectation must receive a *non-const* reference to the + // ExpectationBase object. + Expectation(internal::ExpectationBase& exp); // NOLINT + + // The compiler-generated copy ctor and operator= work exactly as + // intended, so we don't need to define our own. + + // Returns true if and only if rhs references the same expectation as this + // object does. + bool operator==(const Expectation& rhs) const { + return expectation_base_ == rhs.expectation_base_; + } + + bool operator!=(const Expectation& rhs) const { return !(*this == rhs); } + + private: + friend class ExpectationSet; + friend class Sequence; + friend class ::testing::internal::ExpectationBase; + friend class ::testing::internal::UntypedFunctionMockerBase; + + template <typename F> + friend class ::testing::internal::FunctionMocker; + + template <typename F> + friend class ::testing::internal::TypedExpectation; + + // This comparator is needed for putting Expectation objects into a set. + class Less { + public: + bool operator()(const Expectation& lhs, const Expectation& rhs) const { + return lhs.expectation_base_.get() < rhs.expectation_base_.get(); + } + }; + + typedef ::std::set<Expectation, Less> Set; + + Expectation( + const std::shared_ptr<internal::ExpectationBase>& expectation_base); + + // Returns the expectation this object references. + const std::shared_ptr<internal::ExpectationBase>& expectation_base() const { + return expectation_base_; + } + + // A shared_ptr that co-owns the expectation this handle references. + std::shared_ptr<internal::ExpectationBase> expectation_base_; +}; + +// A set of expectation handles. Useful in the .After() clause of +// EXPECT_CALL() for setting the (partial) order of expectations. The +// syntax: +// +// ExpectationSet es; +// es += EXPECT_CALL(...)...; +// es += EXPECT_CALL(...)...; +// EXPECT_CALL(...).After(es)...; +// +// sets three expectations where the last one can only be matched +// after the first two have both been satisfied. +// +// This class is copyable and has value semantics. +class ExpectationSet { + public: + // A bidirectional iterator that can read a const element in the set. + typedef Expectation::Set::const_iterator const_iterator; + + // An object stored in the set. This is an alias of Expectation. + typedef Expectation::Set::value_type value_type; + + // Constructs an empty set. + ExpectationSet() {} + + // This single-argument ctor must not be explicit, in order to support the + // ExpectationSet es = EXPECT_CALL(...); + // syntax. + ExpectationSet(internal::ExpectationBase& exp) { // NOLINT + *this += Expectation(exp); + } + + // This single-argument ctor implements implicit conversion from + // Expectation and thus must not be explicit. This allows either an + // Expectation or an ExpectationSet to be used in .After(). + ExpectationSet(const Expectation& e) { // NOLINT + *this += e; + } + + // The compiler-generator ctor and operator= works exactly as + // intended, so we don't need to define our own. + + // Returns true if and only if rhs contains the same set of Expectation + // objects as this does. + bool operator==(const ExpectationSet& rhs) const { + return expectations_ == rhs.expectations_; + } + + bool operator!=(const ExpectationSet& rhs) const { return !(*this == rhs); } + + // Implements the syntax + // expectation_set += EXPECT_CALL(...); + ExpectationSet& operator+=(const Expectation& e) { + expectations_.insert(e); + return *this; + } + + int size() const { return static_cast<int>(expectations_.size()); } + + const_iterator begin() const { return expectations_.begin(); } + const_iterator end() const { return expectations_.end(); } + + private: + Expectation::Set expectations_; +}; + + +// Sequence objects are used by a user to specify the relative order +// in which the expectations should match. They are copyable (we rely +// on the compiler-defined copy constructor and assignment operator). +class GTEST_API_ Sequence { + public: + // Constructs an empty sequence. + Sequence() : last_expectation_(new Expectation) {} + + // Adds an expectation to this sequence. The caller must ensure + // that no other thread is accessing this Sequence object. + void AddExpectation(const Expectation& expectation) const; + + private: + // The last expectation in this sequence. + std::shared_ptr<Expectation> last_expectation_; +}; // class Sequence + +// An object of this type causes all EXPECT_CALL() statements +// encountered in its scope to be put in an anonymous sequence. The +// work is done in the constructor and destructor. You should only +// create an InSequence object on the stack. +// +// The sole purpose for this class is to support easy definition of +// sequential expectations, e.g. +// +// { +// InSequence dummy; // The name of the object doesn't matter. +// +// // The following expectations must match in the order they appear. +// EXPECT_CALL(a, Bar())...; +// EXPECT_CALL(a, Baz())...; +// ... +// EXPECT_CALL(b, Xyz())...; +// } +// +// You can create InSequence objects in multiple threads, as long as +// they are used to affect different mock objects. The idea is that +// each thread can create and set up its own mocks as if it's the only +// thread. However, for clarity of your tests we recommend you to set +// up mocks in the main thread unless you have a good reason not to do +// so. +class GTEST_API_ InSequence { + public: + InSequence(); + ~InSequence(); + private: + bool sequence_created_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InSequence); // NOLINT +} GTEST_ATTRIBUTE_UNUSED_; + +namespace internal { + +// Points to the implicit sequence introduced by a living InSequence +// object (if any) in the current thread or NULL. +GTEST_API_ extern ThreadLocal<Sequence*> g_gmock_implicit_sequence; + +// Base class for implementing expectations. +// +// There are two reasons for having a type-agnostic base class for +// Expectation: +// +// 1. We need to store collections of expectations of different +// types (e.g. all pre-requisites of a particular expectation, all +// expectations in a sequence). Therefore these expectation objects +// must share a common base class. +// +// 2. We can avoid binary code bloat by moving methods not depending +// on the template argument of Expectation to the base class. +// +// This class is internal and mustn't be used by user code directly. +class GTEST_API_ ExpectationBase { + public: + // source_text is the EXPECT_CALL(...) source that created this Expectation. + ExpectationBase(const char* file, int line, const std::string& source_text); + + virtual ~ExpectationBase(); + + // Where in the source file was the expectation spec defined? + const char* file() const { return file_; } + int line() const { return line_; } + const char* source_text() const { return source_text_.c_str(); } + // Returns the cardinality specified in the expectation spec. + const Cardinality& cardinality() const { return cardinality_; } + + // Describes the source file location of this expectation. + void DescribeLocationTo(::std::ostream* os) const { + *os << FormatFileLocation(file(), line()) << " "; + } + + // Describes how many times a function call matching this + // expectation has occurred. + void DescribeCallCountTo(::std::ostream* os) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex); + + // If this mock method has an extra matcher (i.e. .With(matcher)), + // describes it to the ostream. + virtual void MaybeDescribeExtraMatcherTo(::std::ostream* os) = 0; + + protected: + friend class ::testing::Expectation; + friend class UntypedFunctionMockerBase; + + enum Clause { + // Don't change the order of the enum members! + kNone, + kWith, + kTimes, + kInSequence, + kAfter, + kWillOnce, + kWillRepeatedly, + kRetiresOnSaturation + }; + + typedef std::vector<const void*> UntypedActions; + + // Returns an Expectation object that references and co-owns this + // expectation. + virtual Expectation GetHandle() = 0; + + // Asserts that the EXPECT_CALL() statement has the given property. + void AssertSpecProperty(bool property, + const std::string& failure_message) const { + Assert(property, file_, line_, failure_message); + } + + // Expects that the EXPECT_CALL() statement has the given property. + void ExpectSpecProperty(bool property, + const std::string& failure_message) const { + Expect(property, file_, line_, failure_message); + } + + // Explicitly specifies the cardinality of this expectation. Used + // by the subclasses to implement the .Times() clause. + void SpecifyCardinality(const Cardinality& cardinality); + + // Returns true if and only if the user specified the cardinality + // explicitly using a .Times(). + bool cardinality_specified() const { return cardinality_specified_; } + + // Sets the cardinality of this expectation spec. + void set_cardinality(const Cardinality& a_cardinality) { + cardinality_ = a_cardinality; + } + + // The following group of methods should only be called after the + // EXPECT_CALL() statement, and only when g_gmock_mutex is held by + // the current thread. + + // Retires all pre-requisites of this expectation. + void RetireAllPreRequisites() + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex); + + // Returns true if and only if this expectation is retired. + bool is_retired() const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + return retired_; + } + + // Retires this expectation. + void Retire() + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + retired_ = true; + } + + // Returns true if and only if this expectation is satisfied. + bool IsSatisfied() const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + return cardinality().IsSatisfiedByCallCount(call_count_); + } + + // Returns true if and only if this expectation is saturated. + bool IsSaturated() const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + return cardinality().IsSaturatedByCallCount(call_count_); + } + + // Returns true if and only if this expectation is over-saturated. + bool IsOverSaturated() const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + return cardinality().IsOverSaturatedByCallCount(call_count_); + } + + // Returns true if and only if all pre-requisites of this expectation are + // satisfied. + bool AllPrerequisitesAreSatisfied() const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex); + + // Adds unsatisfied pre-requisites of this expectation to 'result'. + void FindUnsatisfiedPrerequisites(ExpectationSet* result) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex); + + // Returns the number this expectation has been invoked. + int call_count() const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + return call_count_; + } + + // Increments the number this expectation has been invoked. + void IncrementCallCount() + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + call_count_++; + } + + // Checks the action count (i.e. the number of WillOnce() and + // WillRepeatedly() clauses) against the cardinality if this hasn't + // been done before. Prints a warning if there are too many or too + // few actions. + void CheckActionCountIfNotDone() const + GTEST_LOCK_EXCLUDED_(mutex_); + + friend class ::testing::Sequence; + friend class ::testing::internal::ExpectationTester; + + template <typename Function> + friend class TypedExpectation; + + // Implements the .Times() clause. + void UntypedTimes(const Cardinality& a_cardinality); + + // This group of fields are part of the spec and won't change after + // an EXPECT_CALL() statement finishes. + const char* file_; // The file that contains the expectation. + int line_; // The line number of the expectation. + const std::string source_text_; // The EXPECT_CALL(...) source text. + // True if and only if the cardinality is specified explicitly. + bool cardinality_specified_; + Cardinality cardinality_; // The cardinality of the expectation. + // The immediate pre-requisites (i.e. expectations that must be + // satisfied before this expectation can be matched) of this + // expectation. We use std::shared_ptr in the set because we want an + // Expectation object to be co-owned by its FunctionMocker and its + // successors. This allows multiple mock objects to be deleted at + // different times. + ExpectationSet immediate_prerequisites_; + + // This group of fields are the current state of the expectation, + // and can change as the mock function is called. + int call_count_; // How many times this expectation has been invoked. + bool retired_; // True if and only if this expectation has retired. + UntypedActions untyped_actions_; + bool extra_matcher_specified_; + bool repeated_action_specified_; // True if a WillRepeatedly() was specified. + bool retires_on_saturation_; + Clause last_clause_; + mutable bool action_count_checked_; // Under mutex_. + mutable Mutex mutex_; // Protects action_count_checked_. +}; // class ExpectationBase + +// Impements an expectation for the given function type. +template <typename F> +class TypedExpectation : public ExpectationBase { + public: + typedef typename Function<F>::ArgumentTuple ArgumentTuple; + typedef typename Function<F>::ArgumentMatcherTuple ArgumentMatcherTuple; + typedef typename Function<F>::Result Result; + + TypedExpectation(FunctionMocker<F>* owner, const char* a_file, int a_line, + const std::string& a_source_text, + const ArgumentMatcherTuple& m) + : ExpectationBase(a_file, a_line, a_source_text), + owner_(owner), + matchers_(m), + // By default, extra_matcher_ should match anything. However, + // we cannot initialize it with _ as that causes ambiguity between + // Matcher's copy and move constructor for some argument types. + extra_matcher_(A<const ArgumentTuple&>()), + repeated_action_(DoDefault()) {} + + ~TypedExpectation() override { + // Check the validity of the action count if it hasn't been done + // yet (for example, if the expectation was never used). + CheckActionCountIfNotDone(); + for (UntypedActions::const_iterator it = untyped_actions_.begin(); + it != untyped_actions_.end(); ++it) { + delete static_cast<const Action<F>*>(*it); + } + } + + // Implements the .With() clause. + TypedExpectation& With(const Matcher<const ArgumentTuple&>& m) { + if (last_clause_ == kWith) { + ExpectSpecProperty(false, + ".With() cannot appear " + "more than once in an EXPECT_CALL()."); + } else { + ExpectSpecProperty(last_clause_ < kWith, + ".With() must be the first " + "clause in an EXPECT_CALL()."); + } + last_clause_ = kWith; + + extra_matcher_ = m; + extra_matcher_specified_ = true; + return *this; + } + + // Implements the .Times() clause. + TypedExpectation& Times(const Cardinality& a_cardinality) { + ExpectationBase::UntypedTimes(a_cardinality); + return *this; + } + + // Implements the .Times() clause. + TypedExpectation& Times(int n) { + return Times(Exactly(n)); + } + + // Implements the .InSequence() clause. + TypedExpectation& InSequence(const Sequence& s) { + ExpectSpecProperty(last_clause_ <= kInSequence, + ".InSequence() cannot appear after .After()," + " .WillOnce(), .WillRepeatedly(), or " + ".RetiresOnSaturation()."); + last_clause_ = kInSequence; + + s.AddExpectation(GetHandle()); + return *this; + } + TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2) { + return InSequence(s1).InSequence(s2); + } + TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2, + const Sequence& s3) { + return InSequence(s1, s2).InSequence(s3); + } + TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2, + const Sequence& s3, const Sequence& s4) { + return InSequence(s1, s2, s3).InSequence(s4); + } + TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2, + const Sequence& s3, const Sequence& s4, + const Sequence& s5) { + return InSequence(s1, s2, s3, s4).InSequence(s5); + } + + // Implements that .After() clause. + TypedExpectation& After(const ExpectationSet& s) { + ExpectSpecProperty(last_clause_ <= kAfter, + ".After() cannot appear after .WillOnce()," + " .WillRepeatedly(), or " + ".RetiresOnSaturation()."); + last_clause_ = kAfter; + + for (ExpectationSet::const_iterator it = s.begin(); it != s.end(); ++it) { + immediate_prerequisites_ += *it; + } + return *this; + } + TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2) { + return After(s1).After(s2); + } + TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2, + const ExpectationSet& s3) { + return After(s1, s2).After(s3); + } + TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2, + const ExpectationSet& s3, const ExpectationSet& s4) { + return After(s1, s2, s3).After(s4); + } + TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2, + const ExpectationSet& s3, const ExpectationSet& s4, + const ExpectationSet& s5) { + return After(s1, s2, s3, s4).After(s5); + } + + // Implements the .WillOnce() clause. + TypedExpectation& WillOnce(const Action<F>& action) { + ExpectSpecProperty(last_clause_ <= kWillOnce, + ".WillOnce() cannot appear after " + ".WillRepeatedly() or .RetiresOnSaturation()."); + last_clause_ = kWillOnce; + + untyped_actions_.push_back(new Action<F>(action)); + if (!cardinality_specified()) { + set_cardinality(Exactly(static_cast<int>(untyped_actions_.size()))); + } + return *this; + } + + // Implements the .WillRepeatedly() clause. + TypedExpectation& WillRepeatedly(const Action<F>& action) { + if (last_clause_ == kWillRepeatedly) { + ExpectSpecProperty(false, + ".WillRepeatedly() cannot appear " + "more than once in an EXPECT_CALL()."); + } else { + ExpectSpecProperty(last_clause_ < kWillRepeatedly, + ".WillRepeatedly() cannot appear " + "after .RetiresOnSaturation()."); + } + last_clause_ = kWillRepeatedly; + repeated_action_specified_ = true; + + repeated_action_ = action; + if (!cardinality_specified()) { + set_cardinality(AtLeast(static_cast<int>(untyped_actions_.size()))); + } + + // Now that no more action clauses can be specified, we check + // whether their count makes sense. + CheckActionCountIfNotDone(); + return *this; + } + + // Implements the .RetiresOnSaturation() clause. + TypedExpectation& RetiresOnSaturation() { + ExpectSpecProperty(last_clause_ < kRetiresOnSaturation, + ".RetiresOnSaturation() cannot appear " + "more than once."); + last_clause_ = kRetiresOnSaturation; + retires_on_saturation_ = true; + + // Now that no more action clauses can be specified, we check + // whether their count makes sense. + CheckActionCountIfNotDone(); + return *this; + } + + // Returns the matchers for the arguments as specified inside the + // EXPECT_CALL() macro. + const ArgumentMatcherTuple& matchers() const { + return matchers_; + } + + // Returns the matcher specified by the .With() clause. + const Matcher<const ArgumentTuple&>& extra_matcher() const { + return extra_matcher_; + } + + // Returns the action specified by the .WillRepeatedly() clause. + const Action<F>& repeated_action() const { return repeated_action_; } + + // If this mock method has an extra matcher (i.e. .With(matcher)), + // describes it to the ostream. + void MaybeDescribeExtraMatcherTo(::std::ostream* os) override { + if (extra_matcher_specified_) { + *os << " Expected args: "; + extra_matcher_.DescribeTo(os); + *os << "\n"; + } + } + + private: + template <typename Function> + friend class FunctionMocker; + + // Returns an Expectation object that references and co-owns this + // expectation. + Expectation GetHandle() override { return owner_->GetHandleOf(this); } + + // The following methods will be called only after the EXPECT_CALL() + // statement finishes and when the current thread holds + // g_gmock_mutex. + + // Returns true if and only if this expectation matches the given arguments. + bool Matches(const ArgumentTuple& args) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + return TupleMatches(matchers_, args) && extra_matcher_.Matches(args); + } + + // Returns true if and only if this expectation should handle the given + // arguments. + bool ShouldHandleArguments(const ArgumentTuple& args) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + + // In case the action count wasn't checked when the expectation + // was defined (e.g. if this expectation has no WillRepeatedly() + // or RetiresOnSaturation() clause), we check it when the + // expectation is used for the first time. + CheckActionCountIfNotDone(); + return !is_retired() && AllPrerequisitesAreSatisfied() && Matches(args); + } + + // Describes the result of matching the arguments against this + // expectation to the given ostream. + void ExplainMatchResultTo( + const ArgumentTuple& args, + ::std::ostream* os) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + + if (is_retired()) { + *os << " Expected: the expectation is active\n" + << " Actual: it is retired\n"; + } else if (!Matches(args)) { + if (!TupleMatches(matchers_, args)) { + ExplainMatchFailureTupleTo(matchers_, args, os); + } + StringMatchResultListener listener; + if (!extra_matcher_.MatchAndExplain(args, &listener)) { + *os << " Expected args: "; + extra_matcher_.DescribeTo(os); + *os << "\n Actual: don't match"; + + internal::PrintIfNotEmpty(listener.str(), os); + *os << "\n"; + } + } else if (!AllPrerequisitesAreSatisfied()) { + *os << " Expected: all pre-requisites are satisfied\n" + << " Actual: the following immediate pre-requisites " + << "are not satisfied:\n"; + ExpectationSet unsatisfied_prereqs; + FindUnsatisfiedPrerequisites(&unsatisfied_prereqs); + int i = 0; + for (ExpectationSet::const_iterator it = unsatisfied_prereqs.begin(); + it != unsatisfied_prereqs.end(); ++it) { + it->expectation_base()->DescribeLocationTo(os); + *os << "pre-requisite #" << i++ << "\n"; + } + *os << " (end of pre-requisites)\n"; + } else { + // This line is here just for completeness' sake. It will never + // be executed as currently the ExplainMatchResultTo() function + // is called only when the mock function call does NOT match the + // expectation. + *os << "The call matches the expectation.\n"; + } + } + + // Returns the action that should be taken for the current invocation. + const Action<F>& GetCurrentAction(const FunctionMocker<F>* mocker, + const ArgumentTuple& args) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + const int count = call_count(); + Assert(count >= 1, __FILE__, __LINE__, + "call_count() is <= 0 when GetCurrentAction() is " + "called - this should never happen."); + + const int action_count = static_cast<int>(untyped_actions_.size()); + if (action_count > 0 && !repeated_action_specified_ && + count > action_count) { + // If there is at least one WillOnce() and no WillRepeatedly(), + // we warn the user when the WillOnce() clauses ran out. + ::std::stringstream ss; + DescribeLocationTo(&ss); + ss << "Actions ran out in " << source_text() << "...\n" + << "Called " << count << " times, but only " + << action_count << " WillOnce()" + << (action_count == 1 ? " is" : "s are") << " specified - "; + mocker->DescribeDefaultActionTo(args, &ss); + Log(kWarning, ss.str(), 1); + } + + return count <= action_count + ? *static_cast<const Action<F>*>( + untyped_actions_[static_cast<size_t>(count - 1)]) + : repeated_action(); + } + + // Given the arguments of a mock function call, if the call will + // over-saturate this expectation, returns the default action; + // otherwise, returns the next action in this expectation. Also + // describes *what* happened to 'what', and explains *why* Google + // Mock does it to 'why'. This method is not const as it calls + // IncrementCallCount(). A return value of NULL means the default + // action. + const Action<F>* GetActionForArguments(const FunctionMocker<F>* mocker, + const ArgumentTuple& args, + ::std::ostream* what, + ::std::ostream* why) + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + if (IsSaturated()) { + // We have an excessive call. + IncrementCallCount(); + *what << "Mock function called more times than expected - "; + mocker->DescribeDefaultActionTo(args, what); + DescribeCallCountTo(why); + + return nullptr; + } + + IncrementCallCount(); + RetireAllPreRequisites(); + + if (retires_on_saturation_ && IsSaturated()) { + Retire(); + } + + // Must be done after IncrementCount()! + *what << "Mock function call matches " << source_text() <<"...\n"; + return &(GetCurrentAction(mocker, args)); + } + + // All the fields below won't change once the EXPECT_CALL() + // statement finishes. + FunctionMocker<F>* const owner_; + ArgumentMatcherTuple matchers_; + Matcher<const ArgumentTuple&> extra_matcher_; + Action<F> repeated_action_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TypedExpectation); +}; // class TypedExpectation + +// A MockSpec object is used by ON_CALL() or EXPECT_CALL() for +// specifying the default behavior of, or expectation on, a mock +// function. + +// Note: class MockSpec really belongs to the ::testing namespace. +// However if we define it in ::testing, MSVC will complain when +// classes in ::testing::internal declare it as a friend class +// template. To workaround this compiler bug, we define MockSpec in +// ::testing::internal and import it into ::testing. + +// Logs a message including file and line number information. +GTEST_API_ void LogWithLocation(testing::internal::LogSeverity severity, + const char* file, int line, + const std::string& message); + +template <typename F> +class MockSpec { + public: + typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple; + typedef typename internal::Function<F>::ArgumentMatcherTuple + ArgumentMatcherTuple; + + // Constructs a MockSpec object, given the function mocker object + // that the spec is associated with. + MockSpec(internal::FunctionMocker<F>* function_mocker, + const ArgumentMatcherTuple& matchers) + : function_mocker_(function_mocker), matchers_(matchers) {} + + // Adds a new default action spec to the function mocker and returns + // the newly created spec. + internal::OnCallSpec<F>& InternalDefaultActionSetAt( + const char* file, int line, const char* obj, const char* call) { + LogWithLocation(internal::kInfo, file, line, + std::string("ON_CALL(") + obj + ", " + call + ") invoked"); + return function_mocker_->AddNewOnCallSpec(file, line, matchers_); + } + + // Adds a new expectation spec to the function mocker and returns + // the newly created spec. + internal::TypedExpectation<F>& InternalExpectedAt( + const char* file, int line, const char* obj, const char* call) { + const std::string source_text(std::string("EXPECT_CALL(") + obj + ", " + + call + ")"); + LogWithLocation(internal::kInfo, file, line, source_text + " invoked"); + return function_mocker_->AddNewExpectation( + file, line, source_text, matchers_); + } + + // This operator overload is used to swallow the superfluous parameter list + // introduced by the ON/EXPECT_CALL macros. See the macro comments for more + // explanation. + MockSpec<F>& operator()(const internal::WithoutMatchers&, void* const) { + return *this; + } + + private: + template <typename Function> + friend class internal::FunctionMocker; + + // The function mocker that owns this spec. + internal::FunctionMocker<F>* const function_mocker_; + // The argument matchers specified in the spec. + ArgumentMatcherTuple matchers_; +}; // class MockSpec + +// Wrapper type for generically holding an ordinary value or lvalue reference. +// If T is not a reference type, it must be copyable or movable. +// ReferenceOrValueWrapper<T> is movable, and will also be copyable unless +// T is a move-only value type (which means that it will always be copyable +// if the current platform does not support move semantics). +// +// The primary template defines handling for values, but function header +// comments describe the contract for the whole template (including +// specializations). +template <typename T> +class ReferenceOrValueWrapper { + public: + // Constructs a wrapper from the given value/reference. + explicit ReferenceOrValueWrapper(T value) + : value_(std::move(value)) { + } + + // Unwraps and returns the underlying value/reference, exactly as + // originally passed. The behavior of calling this more than once on + // the same object is unspecified. + T Unwrap() { return std::move(value_); } + + // Provides nondestructive access to the underlying value/reference. + // Always returns a const reference (more precisely, + // const std::add_lvalue_reference<T>::type). The behavior of calling this + // after calling Unwrap on the same object is unspecified. + const T& Peek() const { + return value_; + } + + private: + T value_; +}; + +// Specialization for lvalue reference types. See primary template +// for documentation. +template <typename T> +class ReferenceOrValueWrapper<T&> { + public: + // Workaround for debatable pass-by-reference lint warning (c-library-team + // policy precludes NOLINT in this context) + typedef T& reference; + explicit ReferenceOrValueWrapper(reference ref) + : value_ptr_(&ref) {} + T& Unwrap() { return *value_ptr_; } + const T& Peek() const { return *value_ptr_; } + + private: + T* value_ptr_; +}; + +// C++ treats the void type specially. For example, you cannot define +// a void-typed variable or pass a void value to a function. +// ActionResultHolder<T> holds a value of type T, where T must be a +// copyable type or void (T doesn't need to be default-constructable). +// It hides the syntactic difference between void and other types, and +// is used to unify the code for invoking both void-returning and +// non-void-returning mock functions. + +// Untyped base class for ActionResultHolder<T>. +class UntypedActionResultHolderBase { + public: + virtual ~UntypedActionResultHolderBase() {} + + // Prints the held value as an action's result to os. + virtual void PrintAsActionResult(::std::ostream* os) const = 0; +}; + +// This generic definition is used when T is not void. +template <typename T> +class ActionResultHolder : public UntypedActionResultHolderBase { + public: + // Returns the held value. Must not be called more than once. + T Unwrap() { + return result_.Unwrap(); + } + + // Prints the held value as an action's result to os. + void PrintAsActionResult(::std::ostream* os) const override { + *os << "\n Returns: "; + // T may be a reference type, so we don't use UniversalPrint(). + UniversalPrinter<T>::Print(result_.Peek(), os); + } + + // Performs the given mock function's default action and returns the + // result in a new-ed ActionResultHolder. + template <typename F> + static ActionResultHolder* PerformDefaultAction( + const FunctionMocker<F>* func_mocker, + typename Function<F>::ArgumentTuple&& args, + const std::string& call_description) { + return new ActionResultHolder(Wrapper(func_mocker->PerformDefaultAction( + std::move(args), call_description))); + } + + // Performs the given action and returns the result in a new-ed + // ActionResultHolder. + template <typename F> + static ActionResultHolder* PerformAction( + const Action<F>& action, typename Function<F>::ArgumentTuple&& args) { + return new ActionResultHolder( + Wrapper(action.Perform(std::move(args)))); + } + + private: + typedef ReferenceOrValueWrapper<T> Wrapper; + + explicit ActionResultHolder(Wrapper result) + : result_(std::move(result)) { + } + + Wrapper result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ActionResultHolder); +}; + +// Specialization for T = void. +template <> +class ActionResultHolder<void> : public UntypedActionResultHolderBase { + public: + void Unwrap() { } + + void PrintAsActionResult(::std::ostream* /* os */) const override {} + + // Performs the given mock function's default action and returns ownership + // of an empty ActionResultHolder*. + template <typename F> + static ActionResultHolder* PerformDefaultAction( + const FunctionMocker<F>* func_mocker, + typename Function<F>::ArgumentTuple&& args, + const std::string& call_description) { + func_mocker->PerformDefaultAction(std::move(args), call_description); + return new ActionResultHolder; + } + + // Performs the given action and returns ownership of an empty + // ActionResultHolder*. + template <typename F> + static ActionResultHolder* PerformAction( + const Action<F>& action, typename Function<F>::ArgumentTuple&& args) { + action.Perform(std::move(args)); + return new ActionResultHolder; + } + + private: + ActionResultHolder() {} + GTEST_DISALLOW_COPY_AND_ASSIGN_(ActionResultHolder); +}; + +template <typename F> +class FunctionMocker; + +template <typename R, typename... Args> +class FunctionMocker<R(Args...)> final : public UntypedFunctionMockerBase { + using F = R(Args...); + + public: + using Result = R; + using ArgumentTuple = std::tuple<Args...>; + using ArgumentMatcherTuple = std::tuple<Matcher<Args>...>; + + FunctionMocker() {} + + // There is no generally useful and implementable semantics of + // copying a mock object, so copying a mock is usually a user error. + // Thus we disallow copying function mockers. If the user really + // wants to copy a mock object, they should implement their own copy + // operation, for example: + // + // class MockFoo : public Foo { + // public: + // // Defines a copy constructor explicitly. + // MockFoo(const MockFoo& src) {} + // ... + // }; + FunctionMocker(const FunctionMocker&) = delete; + FunctionMocker& operator=(const FunctionMocker&) = delete; + + // The destructor verifies that all expectations on this mock + // function have been satisfied. If not, it will report Google Test + // non-fatal failures for the violations. + ~FunctionMocker() override GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + MutexLock l(&g_gmock_mutex); + VerifyAndClearExpectationsLocked(); + Mock::UnregisterLocked(this); + ClearDefaultActionsLocked(); + } + + // Returns the ON_CALL spec that matches this mock function with the + // given arguments; returns NULL if no matching ON_CALL is found. + // L = * + const OnCallSpec<F>* FindOnCallSpec( + const ArgumentTuple& args) const { + for (UntypedOnCallSpecs::const_reverse_iterator it + = untyped_on_call_specs_.rbegin(); + it != untyped_on_call_specs_.rend(); ++it) { + const OnCallSpec<F>* spec = static_cast<const OnCallSpec<F>*>(*it); + if (spec->Matches(args)) + return spec; + } + + return nullptr; + } + + // Performs the default action of this mock function on the given + // arguments and returns the result. Asserts (or throws if + // exceptions are enabled) with a helpful call descrption if there + // is no valid return value. This method doesn't depend on the + // mutable state of this object, and thus can be called concurrently + // without locking. + // L = * + Result PerformDefaultAction(ArgumentTuple&& args, + const std::string& call_description) const { + const OnCallSpec<F>* const spec = + this->FindOnCallSpec(args); + if (spec != nullptr) { + return spec->GetAction().Perform(std::move(args)); + } + const std::string message = + call_description + + "\n The mock function has no default action " + "set, and its return type has no default value set."; +#if GTEST_HAS_EXCEPTIONS + if (!DefaultValue<Result>::Exists()) { + throw std::runtime_error(message); + } +#else + Assert(DefaultValue<Result>::Exists(), "", -1, message); +#endif + return DefaultValue<Result>::Get(); + } + + // Performs the default action with the given arguments and returns + // the action's result. The call description string will be used in + // the error message to describe the call in the case the default + // action fails. The caller is responsible for deleting the result. + // L = * + UntypedActionResultHolderBase* UntypedPerformDefaultAction( + void* untyped_args, // must point to an ArgumentTuple + const std::string& call_description) const override { + ArgumentTuple* args = static_cast<ArgumentTuple*>(untyped_args); + return ResultHolder::PerformDefaultAction(this, std::move(*args), + call_description); + } + + // Performs the given action with the given arguments and returns + // the action's result. The caller is responsible for deleting the + // result. + // L = * + UntypedActionResultHolderBase* UntypedPerformAction( + const void* untyped_action, void* untyped_args) const override { + // Make a copy of the action before performing it, in case the + // action deletes the mock object (and thus deletes itself). + const Action<F> action = *static_cast<const Action<F>*>(untyped_action); + ArgumentTuple* args = static_cast<ArgumentTuple*>(untyped_args); + return ResultHolder::PerformAction(action, std::move(*args)); + } + + // Implements UntypedFunctionMockerBase::ClearDefaultActionsLocked(): + // clears the ON_CALL()s set on this mock function. + void ClearDefaultActionsLocked() override + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + + // Deleting our default actions may trigger other mock objects to be + // deleted, for example if an action contains a reference counted smart + // pointer to that mock object, and that is the last reference. So if we + // delete our actions within the context of the global mutex we may deadlock + // when this method is called again. Instead, make a copy of the set of + // actions to delete, clear our set within the mutex, and then delete the + // actions outside of the mutex. + UntypedOnCallSpecs specs_to_delete; + untyped_on_call_specs_.swap(specs_to_delete); + + g_gmock_mutex.Unlock(); + for (UntypedOnCallSpecs::const_iterator it = + specs_to_delete.begin(); + it != specs_to_delete.end(); ++it) { + delete static_cast<const OnCallSpec<F>*>(*it); + } + + // Lock the mutex again, since the caller expects it to be locked when we + // return. + g_gmock_mutex.Lock(); + } + + // Returns the result of invoking this mock function with the given + // arguments. This function can be safely called from multiple + // threads concurrently. + Result Invoke(Args... args) GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + ArgumentTuple tuple(std::forward<Args>(args)...); + std::unique_ptr<ResultHolder> holder(DownCast_<ResultHolder*>( + this->UntypedInvokeWith(static_cast<void*>(&tuple)))); + return holder->Unwrap(); + } + + MockSpec<F> With(Matcher<Args>... m) { + return MockSpec<F>(this, ::std::make_tuple(std::move(m)...)); + } + + protected: + template <typename Function> + friend class MockSpec; + + typedef ActionResultHolder<Result> ResultHolder; + + // Adds and returns a default action spec for this mock function. + OnCallSpec<F>& AddNewOnCallSpec( + const char* file, int line, + const ArgumentMatcherTuple& m) + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + Mock::RegisterUseByOnCallOrExpectCall(MockObject(), file, line); + OnCallSpec<F>* const on_call_spec = new OnCallSpec<F>(file, line, m); + untyped_on_call_specs_.push_back(on_call_spec); + return *on_call_spec; + } + + // Adds and returns an expectation spec for this mock function. + TypedExpectation<F>& AddNewExpectation(const char* file, int line, + const std::string& source_text, + const ArgumentMatcherTuple& m) + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + Mock::RegisterUseByOnCallOrExpectCall(MockObject(), file, line); + TypedExpectation<F>* const expectation = + new TypedExpectation<F>(this, file, line, source_text, m); + const std::shared_ptr<ExpectationBase> untyped_expectation(expectation); + // See the definition of untyped_expectations_ for why access to + // it is unprotected here. + untyped_expectations_.push_back(untyped_expectation); + + // Adds this expectation into the implicit sequence if there is one. + Sequence* const implicit_sequence = g_gmock_implicit_sequence.get(); + if (implicit_sequence != nullptr) { + implicit_sequence->AddExpectation(Expectation(untyped_expectation)); + } + + return *expectation; + } + + private: + template <typename Func> friend class TypedExpectation; + + // Some utilities needed for implementing UntypedInvokeWith(). + + // Describes what default action will be performed for the given + // arguments. + // L = * + void DescribeDefaultActionTo(const ArgumentTuple& args, + ::std::ostream* os) const { + const OnCallSpec<F>* const spec = FindOnCallSpec(args); + + if (spec == nullptr) { + *os << (std::is_void<Result>::value ? "returning directly.\n" + : "returning default value.\n"); + } else { + *os << "taking default action specified at:\n" + << FormatFileLocation(spec->file(), spec->line()) << "\n"; + } + } + + // Writes a message that the call is uninteresting (i.e. neither + // explicitly expected nor explicitly unexpected) to the given + // ostream. + void UntypedDescribeUninterestingCall(const void* untyped_args, + ::std::ostream* os) const override + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + const ArgumentTuple& args = + *static_cast<const ArgumentTuple*>(untyped_args); + *os << "Uninteresting mock function call - "; + DescribeDefaultActionTo(args, os); + *os << " Function call: " << Name(); + UniversalPrint(args, os); + } + + // Returns the expectation that matches the given function arguments + // (or NULL is there's no match); when a match is found, + // untyped_action is set to point to the action that should be + // performed (or NULL if the action is "do default"), and + // is_excessive is modified to indicate whether the call exceeds the + // expected number. + // + // Critical section: We must find the matching expectation and the + // corresponding action that needs to be taken in an ATOMIC + // transaction. Otherwise another thread may call this mock + // method in the middle and mess up the state. + // + // However, performing the action has to be left out of the critical + // section. The reason is that we have no control on what the + // action does (it can invoke an arbitrary user function or even a + // mock function) and excessive locking could cause a dead lock. + const ExpectationBase* UntypedFindMatchingExpectation( + const void* untyped_args, const void** untyped_action, bool* is_excessive, + ::std::ostream* what, ::std::ostream* why) override + GTEST_LOCK_EXCLUDED_(g_gmock_mutex) { + const ArgumentTuple& args = + *static_cast<const ArgumentTuple*>(untyped_args); + MutexLock l(&g_gmock_mutex); + TypedExpectation<F>* exp = this->FindMatchingExpectationLocked(args); + if (exp == nullptr) { // A match wasn't found. + this->FormatUnexpectedCallMessageLocked(args, what, why); + return nullptr; + } + + // This line must be done before calling GetActionForArguments(), + // which will increment the call count for *exp and thus affect + // its saturation status. + *is_excessive = exp->IsSaturated(); + const Action<F>* action = exp->GetActionForArguments(this, args, what, why); + if (action != nullptr && action->IsDoDefault()) + action = nullptr; // Normalize "do default" to NULL. + *untyped_action = action; + return exp; + } + + // Prints the given function arguments to the ostream. + void UntypedPrintArgs(const void* untyped_args, + ::std::ostream* os) const override { + const ArgumentTuple& args = + *static_cast<const ArgumentTuple*>(untyped_args); + UniversalPrint(args, os); + } + + // Returns the expectation that matches the arguments, or NULL if no + // expectation matches them. + TypedExpectation<F>* FindMatchingExpectationLocked( + const ArgumentTuple& args) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + // See the definition of untyped_expectations_ for why access to + // it is unprotected here. + for (typename UntypedExpectations::const_reverse_iterator it = + untyped_expectations_.rbegin(); + it != untyped_expectations_.rend(); ++it) { + TypedExpectation<F>* const exp = + static_cast<TypedExpectation<F>*>(it->get()); + if (exp->ShouldHandleArguments(args)) { + return exp; + } + } + return nullptr; + } + + // Returns a message that the arguments don't match any expectation. + void FormatUnexpectedCallMessageLocked( + const ArgumentTuple& args, + ::std::ostream* os, + ::std::ostream* why) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + *os << "\nUnexpected mock function call - "; + DescribeDefaultActionTo(args, os); + PrintTriedExpectationsLocked(args, why); + } + + // Prints a list of expectations that have been tried against the + // current mock function call. + void PrintTriedExpectationsLocked( + const ArgumentTuple& args, + ::std::ostream* why) const + GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) { + g_gmock_mutex.AssertHeld(); + const size_t count = untyped_expectations_.size(); + *why << "Google Mock tried the following " << count << " " + << (count == 1 ? "expectation, but it didn't match" : + "expectations, but none matched") + << ":\n"; + for (size_t i = 0; i < count; i++) { + TypedExpectation<F>* const expectation = + static_cast<TypedExpectation<F>*>(untyped_expectations_[i].get()); + *why << "\n"; + expectation->DescribeLocationTo(why); + if (count > 1) { + *why << "tried expectation #" << i << ": "; + } + *why << expectation->source_text() << "...\n"; + expectation->ExplainMatchResultTo(args, why); + expectation->DescribeCallCountTo(why); + } + } +}; // class FunctionMocker + +// Reports an uninteresting call (whose description is in msg) in the +// manner specified by 'reaction'. +void ReportUninterestingCall(CallReaction reaction, const std::string& msg); + +} // namespace internal + +namespace internal { + +template <typename F> +class MockFunction; + +template <typename R, typename... Args> +class MockFunction<R(Args...)> { + public: + MockFunction(const MockFunction&) = delete; + MockFunction& operator=(const MockFunction&) = delete; + + std::function<R(Args...)> AsStdFunction() { + return [this](Args... args) -> R { + return this->Call(std::forward<Args>(args)...); + }; + } + + // Implementation detail: the expansion of the MOCK_METHOD macro. + R Call(Args... args) { + mock_.SetOwnerAndName(this, "Call"); + return mock_.Invoke(std::forward<Args>(args)...); + } + + MockSpec<R(Args...)> gmock_Call(Matcher<Args>... m) { + mock_.RegisterOwner(this); + return mock_.With(std::move(m)...); + } + + MockSpec<R(Args...)> gmock_Call(const WithoutMatchers&, R (*)(Args...)) { + return this->gmock_Call(::testing::A<Args>()...); + } + + protected: + MockFunction() = default; + ~MockFunction() = default; + + private: + FunctionMocker<R(Args...)> mock_; +}; + +/* +The SignatureOf<F> struct is a meta-function returning function signature +corresponding to the provided F argument. + +It makes use of MockFunction easier by allowing it to accept more F arguments +than just function signatures. + +Specializations provided here cover only a signature type itself and +std::function. However, if need be it can be easily extended to cover also other +types (like for example boost::function). +*/ + +template <typename F> +struct SignatureOf; + +template <typename R, typename... Args> +struct SignatureOf<R(Args...)> { + using type = R(Args...); +}; + +template <typename F> +struct SignatureOf<std::function<F>> : SignatureOf<F> {}; + +template <typename F> +using SignatureOfT = typename SignatureOf<F>::type; + +} // namespace internal + +// A MockFunction<F> type has one mock method whose type is +// internal::SignatureOfT<F>. It is useful when you just want your +// test code to emit some messages and have Google Mock verify the +// right messages are sent (and perhaps at the right times). For +// example, if you are exercising code: +// +// Foo(1); +// Foo(2); +// Foo(3); +// +// and want to verify that Foo(1) and Foo(3) both invoke +// mock.Bar("a"), but Foo(2) doesn't invoke anything, you can write: +// +// TEST(FooTest, InvokesBarCorrectly) { +// MyMock mock; +// MockFunction<void(string check_point_name)> check; +// { +// InSequence s; +// +// EXPECT_CALL(mock, Bar("a")); +// EXPECT_CALL(check, Call("1")); +// EXPECT_CALL(check, Call("2")); +// EXPECT_CALL(mock, Bar("a")); +// } +// Foo(1); +// check.Call("1"); +// Foo(2); +// check.Call("2"); +// Foo(3); +// } +// +// The expectation spec says that the first Bar("a") must happen +// before check point "1", the second Bar("a") must happen after check +// point "2", and nothing should happen between the two check +// points. The explicit check points make it easy to tell which +// Bar("a") is called by which call to Foo(). +// +// MockFunction<F> can also be used to exercise code that accepts +// std::function<internal::SignatureOfT<F>> callbacks. To do so, use +// AsStdFunction() method to create std::function proxy forwarding to +// original object's Call. Example: +// +// TEST(FooTest, RunsCallbackWithBarArgument) { +// MockFunction<int(string)> callback; +// EXPECT_CALL(callback, Call("bar")).WillOnce(Return(1)); +// Foo(callback.AsStdFunction()); +// } +// +// The internal::SignatureOfT<F> indirection allows to use other types +// than just function signature type. This is typically useful when +// providing a mock for a predefined std::function type. Example: +// +// using FilterPredicate = std::function<bool(string)>; +// void MyFilterAlgorithm(FilterPredicate predicate); +// +// TEST(FooTest, FilterPredicateAlwaysAccepts) { +// MockFunction<FilterPredicate> predicateMock; +// EXPECT_CALL(predicateMock, Call(_)).WillRepeatedly(Return(true)); +// MyFilterAlgorithm(predicateMock.AsStdFunction()); +// } +template <typename F> +class MockFunction : public internal::MockFunction<internal::SignatureOfT<F>> { + using Base = internal::MockFunction<internal::SignatureOfT<F>>; + + public: + using Base::Base; +}; + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the MockSpec class template is +// meant to be defined in the ::testing namespace. The following line +// is just a trick for working around a bug in MSVC 8.0, which cannot +// handle it if we define MockSpec in ::testing. +using internal::MockSpec; + +// Const(x) is a convenient function for obtaining a const reference +// to x. This is useful for setting expectations on an overloaded +// const mock method, e.g. +// +// class MockFoo : public FooInterface { +// public: +// MOCK_METHOD0(Bar, int()); +// MOCK_CONST_METHOD0(Bar, int&()); +// }; +// +// MockFoo foo; +// // Expects a call to non-const MockFoo::Bar(). +// EXPECT_CALL(foo, Bar()); +// // Expects a call to const MockFoo::Bar(). +// EXPECT_CALL(Const(foo), Bar()); +template <typename T> +inline const T& Const(const T& x) { return x; } + +// Constructs an Expectation object that references and co-owns exp. +inline Expectation::Expectation(internal::ExpectationBase& exp) // NOLINT + : expectation_base_(exp.GetHandle().expectation_base()) {} + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +// Implementation for ON_CALL and EXPECT_CALL macros. A separate macro is +// required to avoid compile errors when the name of the method used in call is +// a result of macro expansion. See CompilesWithMethodNameExpandedFromMacro +// tests in internal/gmock-spec-builders_test.cc for more details. +// +// This macro supports statements both with and without parameter matchers. If +// the parameter list is omitted, gMock will accept any parameters, which allows +// tests to be written that don't need to encode the number of method +// parameter. This technique may only be used for non-overloaded methods. +// +// // These are the same: +// ON_CALL(mock, NoArgsMethod()).WillByDefault(...); +// ON_CALL(mock, NoArgsMethod).WillByDefault(...); +// +// // As are these: +// ON_CALL(mock, TwoArgsMethod(_, _)).WillByDefault(...); +// ON_CALL(mock, TwoArgsMethod).WillByDefault(...); +// +// // Can also specify args if you want, of course: +// ON_CALL(mock, TwoArgsMethod(_, 45)).WillByDefault(...); +// +// // Overloads work as long as you specify parameters: +// ON_CALL(mock, OverloadedMethod(_)).WillByDefault(...); +// ON_CALL(mock, OverloadedMethod(_, _)).WillByDefault(...); +// +// // Oops! Which overload did you want? +// ON_CALL(mock, OverloadedMethod).WillByDefault(...); +// => ERROR: call to member function 'gmock_OverloadedMethod' is ambiguous +// +// How this works: The mock class uses two overloads of the gmock_Method +// expectation setter method plus an operator() overload on the MockSpec object. +// In the matcher list form, the macro expands to: +// +// // This statement: +// ON_CALL(mock, TwoArgsMethod(_, 45))... +// +// // ...expands to: +// mock.gmock_TwoArgsMethod(_, 45)(WithoutMatchers(), nullptr)... +// |-------------v---------------||------------v-------------| +// invokes first overload swallowed by operator() +// +// // ...which is essentially: +// mock.gmock_TwoArgsMethod(_, 45)... +// +// Whereas the form without a matcher list: +// +// // This statement: +// ON_CALL(mock, TwoArgsMethod)... +// +// // ...expands to: +// mock.gmock_TwoArgsMethod(WithoutMatchers(), nullptr)... +// |-----------------------v--------------------------| +// invokes second overload +// +// // ...which is essentially: +// mock.gmock_TwoArgsMethod(_, _)... +// +// The WithoutMatchers() argument is used to disambiguate overloads and to +// block the caller from accidentally invoking the second overload directly. The +// second argument is an internal type derived from the method signature. The +// failure to disambiguate two overloads of this method in the ON_CALL statement +// is how we block callers from setting expectations on overloaded methods. +#define GMOCK_ON_CALL_IMPL_(mock_expr, Setter, call) \ + ((mock_expr).gmock_##call)(::testing::internal::GetWithoutMatchers(), \ + nullptr) \ + .Setter(__FILE__, __LINE__, #mock_expr, #call) + +#define ON_CALL(obj, call) \ + GMOCK_ON_CALL_IMPL_(obj, InternalDefaultActionSetAt, call) + +#define EXPECT_CALL(obj, call) \ + GMOCK_ON_CALL_IMPL_(obj, InternalExpectedAt, call) + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_ + +namespace testing { +namespace internal { +template <typename T> +using identity_t = T; + +template <typename Pattern> +struct ThisRefAdjuster { + template <typename T> + using AdjustT = typename std::conditional< + std::is_const<typename std::remove_reference<Pattern>::type>::value, + typename std::conditional<std::is_lvalue_reference<Pattern>::value, + const T&, const T&&>::type, + typename std::conditional<std::is_lvalue_reference<Pattern>::value, T&, + T&&>::type>::type; + + template <typename MockType> + static AdjustT<MockType> Adjust(const MockType& mock) { + return static_cast<AdjustT<MockType>>(const_cast<MockType&>(mock)); + } +}; + +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the FunctionMocker class template +// is meant to be defined in the ::testing namespace. The following +// line is just a trick for working around a bug in MSVC 8.0, which +// cannot handle it if we define FunctionMocker in ::testing. +using internal::FunctionMocker; +} // namespace testing + +#define MOCK_METHOD(...) \ + GMOCK_PP_VARIADIC_CALL(GMOCK_INTERNAL_MOCK_METHOD_ARG_, __VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_1(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_2(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_3(_Ret, _MethodName, _Args) \ + GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, ()) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, _Spec) \ + GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Args); \ + GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Spec); \ + GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \ + GMOCK_PP_NARG0 _Args, GMOCK_INTERNAL_SIGNATURE(_Ret, _Args)); \ + GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \ + GMOCK_INTERNAL_MOCK_METHOD_IMPL( \ + GMOCK_PP_NARG0 _Args, _MethodName, GMOCK_INTERNAL_HAS_CONST(_Spec), \ + GMOCK_INTERNAL_HAS_OVERRIDE(_Spec), GMOCK_INTERNAL_HAS_FINAL(_Spec), \ + GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Spec), \ + GMOCK_INTERNAL_GET_CALLTYPE(_Spec), GMOCK_INTERNAL_GET_REF_SPEC(_Spec), \ + (GMOCK_INTERNAL_SIGNATURE(_Ret, _Args))) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_5(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_6(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_7(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_WRONG_ARITY(...) \ + static_assert( \ + false, \ + "MOCK_METHOD must be called with 3 or 4 arguments. _Ret, " \ + "_MethodName, _Args and optionally _Spec. _Args and _Spec must be " \ + "enclosed in parentheses. If _Ret is a type with unprotected commas, " \ + "it must also be enclosed in parentheses.") + +#define GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Tuple) \ + static_assert( \ + GMOCK_PP_IS_ENCLOSED_PARENS(_Tuple), \ + GMOCK_PP_STRINGIZE(_Tuple) " should be enclosed in parentheses.") + +#define GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE(_N, ...) \ + static_assert( \ + std::is_function<__VA_ARGS__>::value, \ + "Signature must be a function type, maybe return type contains " \ + "unprotected comma."); \ + static_assert( \ + ::testing::tuple_size<typename ::testing::internal::Function< \ + __VA_ARGS__>::ArgumentTuple>::value == _N, \ + "This method does not take " GMOCK_PP_STRINGIZE( \ + _N) " arguments. Parenthesize all types with unprotected commas.") + +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT, ~, _Spec) + +#define GMOCK_INTERNAL_MOCK_METHOD_IMPL(_N, _MethodName, _Constness, \ + _Override, _Final, _NoexceptSpec, \ + _CallType, _RefSpec, _Signature) \ + typename ::testing::internal::Function<GMOCK_PP_REMOVE_PARENS( \ + _Signature)>::Result \ + GMOCK_INTERNAL_EXPAND(_CallType) \ + _MethodName(GMOCK_PP_REPEAT(GMOCK_INTERNAL_PARAMETER, _Signature, _N)) \ + GMOCK_PP_IF(_Constness, const, ) _RefSpec _NoexceptSpec \ + GMOCK_PP_IF(_Override, override, ) GMOCK_PP_IF(_Final, final, ) { \ + GMOCK_MOCKER_(_N, _Constness, _MethodName) \ + .SetOwnerAndName(this, #_MethodName); \ + return GMOCK_MOCKER_(_N, _Constness, _MethodName) \ + .Invoke(GMOCK_PP_REPEAT(GMOCK_INTERNAL_FORWARD_ARG, _Signature, _N)); \ + } \ + ::testing::MockSpec<GMOCK_PP_REMOVE_PARENS(_Signature)> gmock_##_MethodName( \ + GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_PARAMETER, _Signature, _N)) \ + GMOCK_PP_IF(_Constness, const, ) _RefSpec { \ + GMOCK_MOCKER_(_N, _Constness, _MethodName).RegisterOwner(this); \ + return GMOCK_MOCKER_(_N, _Constness, _MethodName) \ + .With(GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_ARGUMENT, , _N)); \ + } \ + ::testing::MockSpec<GMOCK_PP_REMOVE_PARENS(_Signature)> gmock_##_MethodName( \ + const ::testing::internal::WithoutMatchers&, \ + GMOCK_PP_IF(_Constness, const, )::testing::internal::Function< \ + GMOCK_PP_REMOVE_PARENS(_Signature)>*) const _RefSpec _NoexceptSpec { \ + return ::testing::internal::ThisRefAdjuster<GMOCK_PP_IF( \ + _Constness, const, ) int _RefSpec>::Adjust(*this) \ + .gmock_##_MethodName(GMOCK_PP_REPEAT( \ + GMOCK_INTERNAL_A_MATCHER_ARGUMENT, _Signature, _N)); \ + } \ + mutable ::testing::FunctionMocker<GMOCK_PP_REMOVE_PARENS(_Signature)> \ + GMOCK_MOCKER_(_N, _Constness, _MethodName) + +#define GMOCK_INTERNAL_EXPAND(...) __VA_ARGS__ + +// Five Valid modifiers. +#define GMOCK_INTERNAL_HAS_CONST(_Tuple) \ + GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_CONST, ~, _Tuple)) + +#define GMOCK_INTERNAL_HAS_OVERRIDE(_Tuple) \ + GMOCK_PP_HAS_COMMA( \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_OVERRIDE, ~, _Tuple)) + +#define GMOCK_INTERNAL_HAS_FINAL(_Tuple) \ + GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_FINAL, ~, _Tuple)) + +#define GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT, ~, _Tuple) + +#define GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT(_i, _, _elem) \ + GMOCK_PP_IF( \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)), \ + _elem, ) + +#define GMOCK_INTERNAL_GET_REF_SPEC(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_REF_SPEC_IF_REF, ~, _Tuple) + +#define GMOCK_INTERNAL_REF_SPEC_IF_REF(_i, _, _elem) \ + GMOCK_PP_IF(GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)), \ + GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), ) + +#define GMOCK_INTERNAL_GET_CALLTYPE(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GET_CALLTYPE_IMPL, ~, _Tuple) + +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \ + static_assert( \ + (GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)) + \ + GMOCK_INTERNAL_IS_CALLTYPE(_elem)) == 1, \ + GMOCK_PP_STRINGIZE( \ + _elem) " cannot be recognized as a valid specification modifier."); + +// Modifiers implementation. +#define GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_CONST_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_CONST_I_const , + +#define GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_OVERRIDE_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_OVERRIDE_I_override , + +#define GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_FINAL_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_FINAL_I_final , + +#define GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_NOEXCEPT_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_NOEXCEPT_I_noexcept , + +#define GMOCK_INTERNAL_DETECT_REF(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_REF_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_REF_I_ref , + +#define GMOCK_INTERNAL_UNPACK_ref(x) x + +#define GMOCK_INTERNAL_GET_CALLTYPE_IMPL(_i, _, _elem) \ + GMOCK_PP_IF(GMOCK_INTERNAL_IS_CALLTYPE(_elem), \ + GMOCK_INTERNAL_GET_VALUE_CALLTYPE, GMOCK_PP_EMPTY) \ + (_elem) + +// TODO(iserna): GMOCK_INTERNAL_IS_CALLTYPE and +// GMOCK_INTERNAL_GET_VALUE_CALLTYPE needed more expansions to work on windows +// maybe they can be simplified somehow. +#define GMOCK_INTERNAL_IS_CALLTYPE(_arg) \ + GMOCK_INTERNAL_IS_CALLTYPE_I( \ + GMOCK_PP_CAT(GMOCK_INTERNAL_IS_CALLTYPE_HELPER_, _arg)) +#define GMOCK_INTERNAL_IS_CALLTYPE_I(_arg) GMOCK_PP_IS_ENCLOSED_PARENS(_arg) + +#define GMOCK_INTERNAL_GET_VALUE_CALLTYPE(_arg) \ + GMOCK_INTERNAL_GET_VALUE_CALLTYPE_I( \ + GMOCK_PP_CAT(GMOCK_INTERNAL_IS_CALLTYPE_HELPER_, _arg)) +#define GMOCK_INTERNAL_GET_VALUE_CALLTYPE_I(_arg) \ + GMOCK_PP_IDENTITY _arg + +#define GMOCK_INTERNAL_IS_CALLTYPE_HELPER_Calltype + +// Note: The use of `identity_t` here allows _Ret to represent return types that +// would normally need to be specified in a different way. For example, a method +// returning a function pointer must be written as +// +// fn_ptr_return_t (*method(method_args_t...))(fn_ptr_args_t...) +// +// But we only support placing the return type at the beginning. To handle this, +// we wrap all calls in identity_t, so that a declaration will be expanded to +// +// identity_t<fn_ptr_return_t (*)(fn_ptr_args_t...)> method(method_args_t...) +// +// This allows us to work around the syntactic oddities of function/method +// types. +#define GMOCK_INTERNAL_SIGNATURE(_Ret, _Args) \ + ::testing::internal::identity_t<GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(_Ret), \ + GMOCK_PP_REMOVE_PARENS, \ + GMOCK_PP_IDENTITY)(_Ret)>( \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GET_TYPE, _, _Args)) + +#define GMOCK_INTERNAL_GET_TYPE(_i, _, _elem) \ + GMOCK_PP_COMMA_IF(_i) \ + GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(_elem), GMOCK_PP_REMOVE_PARENS, \ + GMOCK_PP_IDENTITY) \ + (_elem) + +#define GMOCK_INTERNAL_PARAMETER(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + GMOCK_INTERNAL_ARG_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \ + gmock_a##_i + +#define GMOCK_INTERNAL_FORWARD_ARG(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + ::std::forward<GMOCK_INTERNAL_ARG_O( \ + _i, GMOCK_PP_REMOVE_PARENS(_Signature))>(gmock_a##_i) + +#define GMOCK_INTERNAL_MATCHER_PARAMETER(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + GMOCK_INTERNAL_MATCHER_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \ + gmock_a##_i + +#define GMOCK_INTERNAL_MATCHER_ARGUMENT(_i, _1, _2) \ + GMOCK_PP_COMMA_IF(_i) \ + gmock_a##_i + +#define GMOCK_INTERNAL_A_MATCHER_ARGUMENT(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + ::testing::A<GMOCK_INTERNAL_ARG_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature))>() + +#define GMOCK_INTERNAL_ARG_O(_i, ...) \ + typename ::testing::internal::Function<__VA_ARGS__>::template Arg<_i>::type + +#define GMOCK_INTERNAL_MATCHER_O(_i, ...) \ + const ::testing::Matcher<typename ::testing::internal::Function< \ + __VA_ARGS__>::template Arg<_i>::type>& + +#define MOCK_METHOD0(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 0, __VA_ARGS__) +#define MOCK_METHOD1(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 1, __VA_ARGS__) +#define MOCK_METHOD2(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 2, __VA_ARGS__) +#define MOCK_METHOD3(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 3, __VA_ARGS__) +#define MOCK_METHOD4(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 4, __VA_ARGS__) +#define MOCK_METHOD5(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 5, __VA_ARGS__) +#define MOCK_METHOD6(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 6, __VA_ARGS__) +#define MOCK_METHOD7(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 7, __VA_ARGS__) +#define MOCK_METHOD8(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 8, __VA_ARGS__) +#define MOCK_METHOD9(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 9, __VA_ARGS__) +#define MOCK_METHOD10(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, , m, 10, __VA_ARGS__) + +#define MOCK_CONST_METHOD0(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 0, __VA_ARGS__) +#define MOCK_CONST_METHOD1(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 1, __VA_ARGS__) +#define MOCK_CONST_METHOD2(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 2, __VA_ARGS__) +#define MOCK_CONST_METHOD3(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 3, __VA_ARGS__) +#define MOCK_CONST_METHOD4(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 4, __VA_ARGS__) +#define MOCK_CONST_METHOD5(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 5, __VA_ARGS__) +#define MOCK_CONST_METHOD6(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 6, __VA_ARGS__) +#define MOCK_CONST_METHOD7(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 7, __VA_ARGS__) +#define MOCK_CONST_METHOD8(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 8, __VA_ARGS__) +#define MOCK_CONST_METHOD9(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 9, __VA_ARGS__) +#define MOCK_CONST_METHOD10(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 10, __VA_ARGS__) + +#define MOCK_METHOD0_T(m, ...) MOCK_METHOD0(m, __VA_ARGS__) +#define MOCK_METHOD1_T(m, ...) MOCK_METHOD1(m, __VA_ARGS__) +#define MOCK_METHOD2_T(m, ...) MOCK_METHOD2(m, __VA_ARGS__) +#define MOCK_METHOD3_T(m, ...) MOCK_METHOD3(m, __VA_ARGS__) +#define MOCK_METHOD4_T(m, ...) MOCK_METHOD4(m, __VA_ARGS__) +#define MOCK_METHOD5_T(m, ...) MOCK_METHOD5(m, __VA_ARGS__) +#define MOCK_METHOD6_T(m, ...) MOCK_METHOD6(m, __VA_ARGS__) +#define MOCK_METHOD7_T(m, ...) MOCK_METHOD7(m, __VA_ARGS__) +#define MOCK_METHOD8_T(m, ...) MOCK_METHOD8(m, __VA_ARGS__) +#define MOCK_METHOD9_T(m, ...) MOCK_METHOD9(m, __VA_ARGS__) +#define MOCK_METHOD10_T(m, ...) MOCK_METHOD10(m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T(m, ...) MOCK_CONST_METHOD0(m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T(m, ...) MOCK_CONST_METHOD1(m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T(m, ...) MOCK_CONST_METHOD2(m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T(m, ...) MOCK_CONST_METHOD3(m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T(m, ...) MOCK_CONST_METHOD4(m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T(m, ...) MOCK_CONST_METHOD5(m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T(m, ...) MOCK_CONST_METHOD6(m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T(m, ...) MOCK_CONST_METHOD7(m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T(m, ...) MOCK_CONST_METHOD8(m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T(m, ...) MOCK_CONST_METHOD9(m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T(m, ...) MOCK_CONST_METHOD10(m, __VA_ARGS__) + +#define MOCK_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 0, __VA_ARGS__) +#define MOCK_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 1, __VA_ARGS__) +#define MOCK_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 2, __VA_ARGS__) +#define MOCK_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 3, __VA_ARGS__) +#define MOCK_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 4, __VA_ARGS__) +#define MOCK_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 5, __VA_ARGS__) +#define MOCK_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 6, __VA_ARGS__) +#define MOCK_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 7, __VA_ARGS__) +#define MOCK_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 8, __VA_ARGS__) +#define MOCK_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 9, __VA_ARGS__) +#define MOCK_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 10, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 0, __VA_ARGS__) +#define MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 1, __VA_ARGS__) +#define MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 2, __VA_ARGS__) +#define MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 3, __VA_ARGS__) +#define MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 4, __VA_ARGS__) +#define MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 5, __VA_ARGS__) +#define MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 6, __VA_ARGS__) +#define MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 7, __VA_ARGS__) +#define MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 8, __VA_ARGS__) +#define MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 9, __VA_ARGS__) +#define MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 10, __VA_ARGS__) + +#define MOCK_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHODN(constness, ct, Method, args_num, ...) \ + GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \ + args_num, ::testing::internal::identity_t<__VA_ARGS__>); \ + GMOCK_INTERNAL_MOCK_METHOD_IMPL( \ + args_num, Method, GMOCK_PP_NARG0(constness), 0, 0, , ct, , \ + (::testing::internal::identity_t<__VA_ARGS__>)) + +#define GMOCK_MOCKER_(arity, constness, Method) \ + GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_FUNCTION_MOCKER_H_ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic actions. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_ + +#include <memory> +#include <utility> + + +// Include any custom callback actions added by the local installation. +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_ + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_ + +// Sometimes you want to give an action explicit template parameters +// that cannot be inferred from its value parameters. ACTION() and +// ACTION_P*() don't support that. ACTION_TEMPLATE() remedies that +// and can be viewed as an extension to ACTION() and ACTION_P*(). +// +// The syntax: +// +// ACTION_TEMPLATE(ActionName, +// HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m), +// AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; } +// +// defines an action template that takes m explicit template +// parameters and n value parameters. name_i is the name of the i-th +// template parameter, and kind_i specifies whether it's a typename, +// an integral constant, or a template. p_i is the name of the i-th +// value parameter. +// +// Example: +// +// // DuplicateArg<k, T>(output) converts the k-th argument of the mock +// // function to type T and copies it to *output. +// ACTION_TEMPLATE(DuplicateArg, +// HAS_2_TEMPLATE_PARAMS(int, k, typename, T), +// AND_1_VALUE_PARAMS(output)) { +// *output = T(::std::get<k>(args)); +// } +// ... +// int n; +// EXPECT_CALL(mock, Foo(_, _)) +// .WillOnce(DuplicateArg<1, unsigned char>(&n)); +// +// To create an instance of an action template, write: +// +// ActionName<t1, ..., t_m>(v1, ..., v_n) +// +// where the ts are the template arguments and the vs are the value +// arguments. The value argument types are inferred by the compiler. +// If you want to explicitly specify the value argument types, you can +// provide additional template arguments: +// +// ActionName<t1, ..., t_m, u1, ..., u_k>(v1, ..., v_n) +// +// where u_i is the desired type of v_i. +// +// ACTION_TEMPLATE and ACTION/ACTION_P* can be overloaded on the +// number of value parameters, but not on the number of template +// parameters. Without the restriction, the meaning of the following +// is unclear: +// +// OverloadedAction<int, bool>(x); +// +// Are we using a single-template-parameter action where 'bool' refers +// to the type of x, or are we using a two-template-parameter action +// where the compiler is asked to infer the type of x? +// +// Implementation notes: +// +// GMOCK_INTERNAL_*_HAS_m_TEMPLATE_PARAMS and +// GMOCK_INTERNAL_*_AND_n_VALUE_PARAMS are internal macros for +// implementing ACTION_TEMPLATE. The main trick we use is to create +// new macro invocations when expanding a macro. For example, we have +// +// #define ACTION_TEMPLATE(name, template_params, value_params) +// ... GMOCK_INTERNAL_DECL_##template_params ... +// +// which causes ACTION_TEMPLATE(..., HAS_1_TEMPLATE_PARAMS(typename, T), ...) +// to expand to +// +// ... GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(typename, T) ... +// +// Since GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS is a macro, the +// preprocessor will continue to expand it to +// +// ... typename T ... +// +// This technique conforms to the C++ standard and is portable. It +// allows us to implement action templates using O(N) code, where N is +// the maximum number of template/value parameters supported. Without +// using it, we'd have to devote O(N^2) amount of code to implement all +// combinations of m and n. + +// Declares the template parameters. +#define GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(kind0, name0) kind0 name0 +#define GMOCK_INTERNAL_DECL_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1) kind0 name0, kind1 name1 +#define GMOCK_INTERNAL_DECL_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2) kind0 name0, kind1 name1, kind2 name2 +#define GMOCK_INTERNAL_DECL_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3) kind0 name0, kind1 name1, kind2 name2, \ + kind3 name3 +#define GMOCK_INTERNAL_DECL_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4) kind0 name0, kind1 name1, \ + kind2 name2, kind3 name3, kind4 name4 +#define GMOCK_INTERNAL_DECL_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5) kind0 name0, \ + kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5 +#define GMOCK_INTERNAL_DECL_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6) kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, \ + kind5 name5, kind6 name6 +#define GMOCK_INTERNAL_DECL_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7) kind0 name0, kind1 name1, kind2 name2, kind3 name3, \ + kind4 name4, kind5 name5, kind6 name6, kind7 name7 +#define GMOCK_INTERNAL_DECL_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7, kind8, name8) kind0 name0, kind1 name1, kind2 name2, \ + kind3 name3, kind4 name4, kind5 name5, kind6 name6, kind7 name7, \ + kind8 name8 +#define GMOCK_INTERNAL_DECL_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6, kind7, name7, kind8, name8, kind9, name9) kind0 name0, \ + kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5, \ + kind6 name6, kind7 name7, kind8 name8, kind9 name9 + +// Lists the template parameters. +#define GMOCK_INTERNAL_LIST_HAS_1_TEMPLATE_PARAMS(kind0, name0) name0 +#define GMOCK_INTERNAL_LIST_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1) name0, name1 +#define GMOCK_INTERNAL_LIST_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2) name0, name1, name2 +#define GMOCK_INTERNAL_LIST_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3) name0, name1, name2, name3 +#define GMOCK_INTERNAL_LIST_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4) name0, name1, name2, name3, \ + name4 +#define GMOCK_INTERNAL_LIST_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5) name0, name1, \ + name2, name3, name4, name5 +#define GMOCK_INTERNAL_LIST_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6) name0, name1, name2, name3, name4, name5, name6 +#define GMOCK_INTERNAL_LIST_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7) name0, name1, name2, name3, name4, name5, name6, name7 +#define GMOCK_INTERNAL_LIST_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7, kind8, name8) name0, name1, name2, name3, name4, name5, \ + name6, name7, name8 +#define GMOCK_INTERNAL_LIST_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6, kind7, name7, kind8, name8, kind9, name9) name0, name1, name2, \ + name3, name4, name5, name6, name7, name8, name9 + +// Declares the types of value parameters. +#define GMOCK_INTERNAL_DECL_TYPE_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DECL_TYPE_AND_1_VALUE_PARAMS(p0) , typename p0##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_2_VALUE_PARAMS(p0, p1) , \ + typename p0##_type, typename p1##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , \ + typename p0##_type, typename p1##_type, typename p2##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type, typename p7##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type, typename p7##_type, typename p8##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9) , typename p0##_type, typename p1##_type, \ + typename p2##_type, typename p3##_type, typename p4##_type, \ + typename p5##_type, typename p6##_type, typename p7##_type, \ + typename p8##_type, typename p9##_type + +// Initializes the value parameters. +#define GMOCK_INTERNAL_INIT_AND_0_VALUE_PARAMS()\ + () +#define GMOCK_INTERNAL_INIT_AND_1_VALUE_PARAMS(p0)\ + (p0##_type gmock_p0) : p0(::std::move(gmock_p0)) +#define GMOCK_INTERNAL_INIT_AND_2_VALUE_PARAMS(p0, p1)\ + (p0##_type gmock_p0, p1##_type gmock_p1) : p0(::std::move(gmock_p0)), \ + p1(::std::move(gmock_p1)) +#define GMOCK_INTERNAL_INIT_AND_3_VALUE_PARAMS(p0, p1, p2)\ + (p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(::std::move(gmock_p0)), \ + p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)) +#define GMOCK_INTERNAL_INIT_AND_4_VALUE_PARAMS(p0, p1, p2, p3)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3) : p0(::std::move(gmock_p0)), \ + p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \ + p3(::std::move(gmock_p3)) +#define GMOCK_INTERNAL_INIT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4) : p0(::std::move(gmock_p0)), \ + p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \ + p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)) +#define GMOCK_INTERNAL_INIT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(::std::move(gmock_p0)), \ + p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \ + p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \ + p5(::std::move(gmock_p5)) +#define GMOCK_INTERNAL_INIT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6) : p0(::std::move(gmock_p0)), \ + p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \ + p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \ + p5(::std::move(gmock_p5)), p6(::std::move(gmock_p6)) +#define GMOCK_INTERNAL_INIT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7) : p0(::std::move(gmock_p0)), \ + p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \ + p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \ + p5(::std::move(gmock_p5)), p6(::std::move(gmock_p6)), \ + p7(::std::move(gmock_p7)) +#define GMOCK_INTERNAL_INIT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(::std::move(gmock_p0)), \ + p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \ + p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \ + p5(::std::move(gmock_p5)), p6(::std::move(gmock_p6)), \ + p7(::std::move(gmock_p7)), p8(::std::move(gmock_p8)) +#define GMOCK_INTERNAL_INIT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9) : p0(::std::move(gmock_p0)), \ + p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \ + p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \ + p5(::std::move(gmock_p5)), p6(::std::move(gmock_p6)), \ + p7(::std::move(gmock_p7)), p8(::std::move(gmock_p8)), \ + p9(::std::move(gmock_p9)) + +// Defines the copy constructor +#define GMOCK_INTERNAL_DEFN_COPY_AND_0_VALUE_PARAMS() \ + {} // Avoid https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82134 +#define GMOCK_INTERNAL_DEFN_COPY_AND_1_VALUE_PARAMS(...) = default; +#define GMOCK_INTERNAL_DEFN_COPY_AND_2_VALUE_PARAMS(...) = default; +#define GMOCK_INTERNAL_DEFN_COPY_AND_3_VALUE_PARAMS(...) = default; +#define GMOCK_INTERNAL_DEFN_COPY_AND_4_VALUE_PARAMS(...) = default; +#define GMOCK_INTERNAL_DEFN_COPY_AND_5_VALUE_PARAMS(...) = default; +#define GMOCK_INTERNAL_DEFN_COPY_AND_6_VALUE_PARAMS(...) = default; +#define GMOCK_INTERNAL_DEFN_COPY_AND_7_VALUE_PARAMS(...) = default; +#define GMOCK_INTERNAL_DEFN_COPY_AND_8_VALUE_PARAMS(...) = default; +#define GMOCK_INTERNAL_DEFN_COPY_AND_9_VALUE_PARAMS(...) = default; +#define GMOCK_INTERNAL_DEFN_COPY_AND_10_VALUE_PARAMS(...) = default; + +// Declares the fields for storing the value parameters. +#define GMOCK_INTERNAL_DEFN_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DEFN_AND_1_VALUE_PARAMS(p0) p0##_type p0; +#define GMOCK_INTERNAL_DEFN_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0; \ + p1##_type p1; +#define GMOCK_INTERNAL_DEFN_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0; \ + p1##_type p1; p2##_type p2; +#define GMOCK_INTERNAL_DEFN_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0; \ + p1##_type p1; p2##_type p2; p3##_type p3; +#define GMOCK_INTERNAL_DEFN_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \ + p4) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; +#define GMOCK_INTERNAL_DEFN_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \ + p5) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; +#define GMOCK_INTERNAL_DEFN_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; p6##_type p6; +#define GMOCK_INTERNAL_DEFN_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; p6##_type p6; p7##_type p7; +#define GMOCK_INTERNAL_DEFN_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \ + p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8; +#define GMOCK_INTERNAL_DEFN_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \ + p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8; \ + p9##_type p9; + +// Lists the value parameters. +#define GMOCK_INTERNAL_LIST_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_LIST_AND_1_VALUE_PARAMS(p0) p0 +#define GMOCK_INTERNAL_LIST_AND_2_VALUE_PARAMS(p0, p1) p0, p1 +#define GMOCK_INTERNAL_LIST_AND_3_VALUE_PARAMS(p0, p1, p2) p0, p1, p2 +#define GMOCK_INTERNAL_LIST_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0, p1, p2, p3 +#define GMOCK_INTERNAL_LIST_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) p0, p1, \ + p2, p3, p4 +#define GMOCK_INTERNAL_LIST_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) p0, \ + p1, p2, p3, p4, p5 +#define GMOCK_INTERNAL_LIST_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0, p1, p2, p3, p4, p5, p6 +#define GMOCK_INTERNAL_LIST_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0, p1, p2, p3, p4, p5, p6, p7 +#define GMOCK_INTERNAL_LIST_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0, p1, p2, p3, p4, p5, p6, p7, p8 +#define GMOCK_INTERNAL_LIST_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 + +// Lists the value parameter types. +#define GMOCK_INTERNAL_LIST_TYPE_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_LIST_TYPE_AND_1_VALUE_PARAMS(p0) , p0##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_2_VALUE_PARAMS(p0, p1) , p0##_type, \ + p1##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , p0##_type, \ + p1##_type, p2##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \ + p0##_type, p1##_type, p2##_type, p3##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \ + p0##_type, p1##_type, p2##_type, p3##_type, p4##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \ + p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type, \ + p6##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type, p8##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type, p8##_type, p9##_type + +// Declares the value parameters. +#define GMOCK_INTERNAL_DECL_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DECL_AND_1_VALUE_PARAMS(p0) p0##_type p0 +#define GMOCK_INTERNAL_DECL_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0, \ + p1##_type p1 +#define GMOCK_INTERNAL_DECL_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0, \ + p1##_type p1, p2##_type p2 +#define GMOCK_INTERNAL_DECL_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3 +#define GMOCK_INTERNAL_DECL_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \ + p4) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4 +#define GMOCK_INTERNAL_DECL_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \ + p5) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5 +#define GMOCK_INTERNAL_DECL_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5, p6##_type p6 +#define GMOCK_INTERNAL_DECL_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5, p6##_type p6, p7##_type p7 +#define GMOCK_INTERNAL_DECL_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8 +#define GMOCK_INTERNAL_DECL_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9 + +// The suffix of the class template implementing the action template. +#define GMOCK_INTERNAL_COUNT_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_COUNT_AND_1_VALUE_PARAMS(p0) P +#define GMOCK_INTERNAL_COUNT_AND_2_VALUE_PARAMS(p0, p1) P2 +#define GMOCK_INTERNAL_COUNT_AND_3_VALUE_PARAMS(p0, p1, p2) P3 +#define GMOCK_INTERNAL_COUNT_AND_4_VALUE_PARAMS(p0, p1, p2, p3) P4 +#define GMOCK_INTERNAL_COUNT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) P5 +#define GMOCK_INTERNAL_COUNT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) P6 +#define GMOCK_INTERNAL_COUNT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) P7 +#define GMOCK_INTERNAL_COUNT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) P8 +#define GMOCK_INTERNAL_COUNT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) P9 +#define GMOCK_INTERNAL_COUNT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) P10 + +// The name of the class template implementing the action template. +#define GMOCK_ACTION_CLASS_(name, value_params)\ + GTEST_CONCAT_TOKEN_(name##Action, GMOCK_INTERNAL_COUNT_##value_params) + +#define ACTION_TEMPLATE(name, template_params, value_params) \ + template <GMOCK_INTERNAL_DECL_##template_params \ + GMOCK_INTERNAL_DECL_TYPE_##value_params> \ + class GMOCK_ACTION_CLASS_(name, value_params) { \ + public: \ + explicit GMOCK_ACTION_CLASS_(name, value_params)( \ + GMOCK_INTERNAL_DECL_##value_params) \ + GMOCK_PP_IF(GMOCK_PP_IS_EMPTY(GMOCK_INTERNAL_COUNT_##value_params), \ + = default; , \ + : impl_(std::make_shared<gmock_Impl>( \ + GMOCK_INTERNAL_LIST_##value_params)) { }) \ + GMOCK_ACTION_CLASS_(name, value_params)( \ + const GMOCK_ACTION_CLASS_(name, value_params)&) noexcept \ + GMOCK_INTERNAL_DEFN_COPY_##value_params \ + GMOCK_ACTION_CLASS_(name, value_params)( \ + GMOCK_ACTION_CLASS_(name, value_params)&&) noexcept \ + GMOCK_INTERNAL_DEFN_COPY_##value_params \ + template <typename F> \ + operator ::testing::Action<F>() const { \ + return GMOCK_PP_IF( \ + GMOCK_PP_IS_EMPTY(GMOCK_INTERNAL_COUNT_##value_params), \ + (::testing::internal::MakeAction<F, gmock_Impl>()), \ + (::testing::internal::MakeAction<F>(impl_))); \ + } \ + private: \ + class gmock_Impl { \ + public: \ + explicit gmock_Impl GMOCK_INTERNAL_INIT_##value_params {} \ + template <typename function_type, typename return_type, \ + typename args_type, GMOCK_ACTION_TEMPLATE_ARGS_NAMES_> \ + return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \ + GMOCK_INTERNAL_DEFN_##value_params \ + }; \ + GMOCK_PP_IF(GMOCK_PP_IS_EMPTY(GMOCK_INTERNAL_COUNT_##value_params), \ + , std::shared_ptr<const gmock_Impl> impl_;) \ + }; \ + template <GMOCK_INTERNAL_DECL_##template_params \ + GMOCK_INTERNAL_DECL_TYPE_##value_params> \ + GMOCK_ACTION_CLASS_(name, value_params)< \ + GMOCK_INTERNAL_LIST_##template_params \ + GMOCK_INTERNAL_LIST_TYPE_##value_params> name( \ + GMOCK_INTERNAL_DECL_##value_params) GTEST_MUST_USE_RESULT_; \ + template <GMOCK_INTERNAL_DECL_##template_params \ + GMOCK_INTERNAL_DECL_TYPE_##value_params> \ + inline GMOCK_ACTION_CLASS_(name, value_params)< \ + GMOCK_INTERNAL_LIST_##template_params \ + GMOCK_INTERNAL_LIST_TYPE_##value_params> name( \ + GMOCK_INTERNAL_DECL_##value_params) { \ + return GMOCK_ACTION_CLASS_(name, value_params)< \ + GMOCK_INTERNAL_LIST_##template_params \ + GMOCK_INTERNAL_LIST_TYPE_##value_params>( \ + GMOCK_INTERNAL_LIST_##value_params); \ + } \ + template <GMOCK_INTERNAL_DECL_##template_params \ + GMOCK_INTERNAL_DECL_TYPE_##value_params> \ + template <typename function_type, typename return_type, typename args_type, \ + GMOCK_ACTION_TEMPLATE_ARGS_NAMES_> \ + return_type GMOCK_ACTION_CLASS_(name, value_params)< \ + GMOCK_INTERNAL_LIST_##template_params \ + GMOCK_INTERNAL_LIST_TYPE_##value_params>::gmock_Impl::gmock_PerformImpl( \ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +namespace testing { + +// The ACTION*() macros trigger warning C4100 (unreferenced formal +// parameter) in MSVC with -W4. Unfortunately they cannot be fixed in +// the macro definition, as the warnings are generated when the macro +// is expanded and macro expansion cannot contain #pragma. Therefore +// we suppress them here. +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4100) +#endif + +namespace internal { + +// internal::InvokeArgument - a helper for InvokeArgument action. +// The basic overloads are provided here for generic functors. +// Overloads for other custom-callables are provided in the +// internal/custom/gmock-generated-actions.h header. +template <typename F, typename... Args> +auto InvokeArgument(F f, Args... args) -> decltype(f(args...)) { + return f(args...); +} + +template <std::size_t index, typename... Params> +struct InvokeArgumentAction { + template <typename... Args> + auto operator()(Args&&... args) const -> decltype(internal::InvokeArgument( + std::get<index>(std::forward_as_tuple(std::forward<Args>(args)...)), + std::declval<const Params&>()...)) { + internal::FlatTuple<Args&&...> args_tuple(FlatTupleConstructTag{}, + std::forward<Args>(args)...); + return params.Apply([&](const Params&... unpacked_params) { + auto&& callable = args_tuple.template Get<index>(); + return internal::InvokeArgument( + std::forward<decltype(callable)>(callable), unpacked_params...); + }); + } + + internal::FlatTuple<Params...> params; +}; + +} // namespace internal + +// The InvokeArgument<N>(a1, a2, ..., a_k) action invokes the N-th +// (0-based) argument, which must be a k-ary callable, of the mock +// function, with arguments a1, a2, ..., a_k. +// +// Notes: +// +// 1. The arguments are passed by value by default. If you need to +// pass an argument by reference, wrap it inside std::ref(). For +// example, +// +// InvokeArgument<1>(5, string("Hello"), std::ref(foo)) +// +// passes 5 and string("Hello") by value, and passes foo by +// reference. +// +// 2. If the callable takes an argument by reference but std::ref() is +// not used, it will receive the reference to a copy of the value, +// instead of the original value. For example, when the 0-th +// argument of the mock function takes a const string&, the action +// +// InvokeArgument<0>(string("Hello")) +// +// makes a copy of the temporary string("Hello") object and passes a +// reference of the copy, instead of the original temporary object, +// to the callable. This makes it easy for a user to define an +// InvokeArgument action from temporary values and have it performed +// later. +template <std::size_t index, typename... Params> +internal::InvokeArgumentAction<index, typename std::decay<Params>::type...> +InvokeArgument(Params&&... params) { + return {internal::FlatTuple<typename std::decay<Params>::type...>( + internal::FlatTupleConstructTag{}, std::forward<Params>(params)...)}; +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +} // namespace testing + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_ +// Copyright 2013, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some matchers that depend on gmock-matchers.h. +// +// Note that tests are implemented in gmock-matchers_test.cc rather than +// gmock-more-matchers-test.cc. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_MATCHERS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_MATCHERS_H_ + + +namespace testing { + +// Silence C4100 (unreferenced formal +// parameter) for MSVC +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4100) +#if (_MSC_VER == 1900) +// and silence C4800 (C4800: 'int *const ': forcing value +// to bool 'true' or 'false') for MSVC 14 +# pragma warning(disable:4800) + #endif +#endif + +// Defines a matcher that matches an empty container. The container must +// support both size() and empty(), which all STL-like containers provide. +MATCHER(IsEmpty, negation ? "isn't empty" : "is empty") { + if (arg.empty()) { + return true; + } + *result_listener << "whose size is " << arg.size(); + return false; +} + +// Define a matcher that matches a value that evaluates in boolean +// context to true. Useful for types that define "explicit operator +// bool" operators and so can't be compared for equality with true +// and false. +MATCHER(IsTrue, negation ? "is false" : "is true") { + return static_cast<bool>(arg); +} + +// Define a matcher that matches a value that evaluates in boolean +// context to false. Useful for types that define "explicit operator +// bool" operators and so can't be compared for equality with true +// and false. +MATCHER(IsFalse, negation ? "is true" : "is false") { + return !static_cast<bool>(arg); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + + +} // namespace testing + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_MATCHERS_H_ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Implements class templates NiceMock, NaggyMock, and StrictMock. +// +// Given a mock class MockFoo that is created using Google Mock, +// NiceMock<MockFoo> is a subclass of MockFoo that allows +// uninteresting calls (i.e. calls to mock methods that have no +// EXPECT_CALL specs), NaggyMock<MockFoo> is a subclass of MockFoo +// that prints a warning when an uninteresting call occurs, and +// StrictMock<MockFoo> is a subclass of MockFoo that treats all +// uninteresting calls as errors. +// +// Currently a mock is naggy by default, so MockFoo and +// NaggyMock<MockFoo> behave like the same. However, we will soon +// switch the default behavior of mocks to be nice, as that in general +// leads to more maintainable tests. When that happens, MockFoo will +// stop behaving like NaggyMock<MockFoo> and start behaving like +// NiceMock<MockFoo>. +// +// NiceMock, NaggyMock, and StrictMock "inherit" the constructors of +// their respective base class. Therefore you can write +// NiceMock<MockFoo>(5, "a") to construct a nice mock where MockFoo +// has a constructor that accepts (int, const char*), for example. +// +// A known limitation is that NiceMock<MockFoo>, NaggyMock<MockFoo>, +// and StrictMock<MockFoo> only works for mock methods defined using +// the MOCK_METHOD* family of macros DIRECTLY in the MockFoo class. +// If a mock method is defined in a base class of MockFoo, the "nice" +// or "strict" modifier may not affect it, depending on the compiler. +// In particular, nesting NiceMock, NaggyMock, and StrictMock is NOT +// supported. + +// GOOGLETEST_CM0002 DO NOT DELETE + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_ + +#include <type_traits> + + +namespace testing { +template <class MockClass> +class NiceMock; +template <class MockClass> +class NaggyMock; +template <class MockClass> +class StrictMock; + +namespace internal { +template <typename T> +std::true_type StrictnessModifierProbe(const NiceMock<T>&); +template <typename T> +std::true_type StrictnessModifierProbe(const NaggyMock<T>&); +template <typename T> +std::true_type StrictnessModifierProbe(const StrictMock<T>&); +std::false_type StrictnessModifierProbe(...); + +template <typename T> +constexpr bool HasStrictnessModifier() { + return decltype(StrictnessModifierProbe(std::declval<const T&>()))::value; +} + +// Base classes that register and deregister with testing::Mock to alter the +// default behavior around uninteresting calls. Inheriting from one of these +// classes first and then MockClass ensures the MockClass constructor is run +// after registration, and that the MockClass destructor runs before +// deregistration. This guarantees that MockClass's constructor and destructor +// run with the same level of strictness as its instance methods. + +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW && \ + (defined(_MSC_VER) || defined(__clang__)) +// We need to mark these classes with this declspec to ensure that +// the empty base class optimization is performed. +#define GTEST_INTERNAL_EMPTY_BASE_CLASS __declspec(empty_bases) +#else +#define GTEST_INTERNAL_EMPTY_BASE_CLASS +#endif + +template <typename Base> +class NiceMockImpl { + public: + NiceMockImpl() { ::testing::Mock::AllowUninterestingCalls(this); } + + ~NiceMockImpl() { ::testing::Mock::UnregisterCallReaction(this); } +}; + +template <typename Base> +class NaggyMockImpl { + public: + NaggyMockImpl() { ::testing::Mock::WarnUninterestingCalls(this); } + + ~NaggyMockImpl() { ::testing::Mock::UnregisterCallReaction(this); } +}; + +template <typename Base> +class StrictMockImpl { + public: + StrictMockImpl() { ::testing::Mock::FailUninterestingCalls(this); } + + ~StrictMockImpl() { ::testing::Mock::UnregisterCallReaction(this); } +}; + +} // namespace internal + +template <class MockClass> +class GTEST_INTERNAL_EMPTY_BASE_CLASS NiceMock + : private internal::NiceMockImpl<MockClass>, + public MockClass { + public: + static_assert(!internal::HasStrictnessModifier<MockClass>(), + "Can't apply NiceMock to a class hierarchy that already has a " + "strictness modifier. See " + "https://google.github.io/googletest/" + "gmock_cook_book.html#NiceStrictNaggy"); + NiceMock() : MockClass() { + static_assert(sizeof(*this) == sizeof(MockClass), + "The impl subclass shouldn't introduce any padding"); + } + + // Ideally, we would inherit base class's constructors through a using + // declaration, which would preserve their visibility. However, many existing + // tests rely on the fact that current implementation reexports protected + // constructors as public. These tests would need to be cleaned up first. + + // Single argument constructor is special-cased so that it can be + // made explicit. + template <typename A> + explicit NiceMock(A&& arg) : MockClass(std::forward<A>(arg)) { + static_assert(sizeof(*this) == sizeof(MockClass), + "The impl subclass shouldn't introduce any padding"); + } + + template <typename TArg1, typename TArg2, typename... An> + NiceMock(TArg1&& arg1, TArg2&& arg2, An&&... args) + : MockClass(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2), + std::forward<An>(args)...) { + static_assert(sizeof(*this) == sizeof(MockClass), + "The impl subclass shouldn't introduce any padding"); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(NiceMock); +}; + +template <class MockClass> +class GTEST_INTERNAL_EMPTY_BASE_CLASS NaggyMock + : private internal::NaggyMockImpl<MockClass>, + public MockClass { + static_assert(!internal::HasStrictnessModifier<MockClass>(), + "Can't apply NaggyMock to a class hierarchy that already has a " + "strictness modifier. See " + "https://google.github.io/googletest/" + "gmock_cook_book.html#NiceStrictNaggy"); + + public: + NaggyMock() : MockClass() { + static_assert(sizeof(*this) == sizeof(MockClass), + "The impl subclass shouldn't introduce any padding"); + } + + // Ideally, we would inherit base class's constructors through a using + // declaration, which would preserve their visibility. However, many existing + // tests rely on the fact that current implementation reexports protected + // constructors as public. These tests would need to be cleaned up first. + + // Single argument constructor is special-cased so that it can be + // made explicit. + template <typename A> + explicit NaggyMock(A&& arg) : MockClass(std::forward<A>(arg)) { + static_assert(sizeof(*this) == sizeof(MockClass), + "The impl subclass shouldn't introduce any padding"); + } + + template <typename TArg1, typename TArg2, typename... An> + NaggyMock(TArg1&& arg1, TArg2&& arg2, An&&... args) + : MockClass(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2), + std::forward<An>(args)...) { + static_assert(sizeof(*this) == sizeof(MockClass), + "The impl subclass shouldn't introduce any padding"); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(NaggyMock); +}; + +template <class MockClass> +class GTEST_INTERNAL_EMPTY_BASE_CLASS StrictMock + : private internal::StrictMockImpl<MockClass>, + public MockClass { + public: + static_assert( + !internal::HasStrictnessModifier<MockClass>(), + "Can't apply StrictMock to a class hierarchy that already has a " + "strictness modifier. See " + "https://google.github.io/googletest/" + "gmock_cook_book.html#NiceStrictNaggy"); + StrictMock() : MockClass() { + static_assert(sizeof(*this) == sizeof(MockClass), + "The impl subclass shouldn't introduce any padding"); + } + + // Ideally, we would inherit base class's constructors through a using + // declaration, which would preserve their visibility. However, many existing + // tests rely on the fact that current implementation reexports protected + // constructors as public. These tests would need to be cleaned up first. + + // Single argument constructor is special-cased so that it can be + // made explicit. + template <typename A> + explicit StrictMock(A&& arg) : MockClass(std::forward<A>(arg)) { + static_assert(sizeof(*this) == sizeof(MockClass), + "The impl subclass shouldn't introduce any padding"); + } + + template <typename TArg1, typename TArg2, typename... An> + StrictMock(TArg1&& arg1, TArg2&& arg2, An&&... args) + : MockClass(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2), + std::forward<An>(args)...) { + static_assert(sizeof(*this) == sizeof(MockClass), + "The impl subclass shouldn't introduce any padding"); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(StrictMock); +}; + +#undef GTEST_INTERNAL_EMPTY_BASE_CLASS + +} // namespace testing + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_ + +namespace testing { + +// Declares Google Mock flags that we want a user to use programmatically. +GMOCK_DECLARE_bool_(catch_leaked_mocks); +GMOCK_DECLARE_string_(verbose); +GMOCK_DECLARE_int32_(default_mock_behavior); + +// Initializes Google Mock. This must be called before running the +// tests. In particular, it parses the command line for the flags +// that Google Mock recognizes. Whenever a Google Mock flag is seen, +// it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Mock flag variables are +// updated. +// +// Since Google Test is needed for Google Mock to work, this function +// also initializes Google Test and parses its flags, if that hasn't +// been done. +GTEST_API_ void InitGoogleMock(int* argc, char** argv); + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +GTEST_API_ void InitGoogleMock(int* argc, wchar_t** argv); + +// This overloaded version can be used on Arduino/embedded platforms where +// there is no argc/argv. +GTEST_API_ void InitGoogleMock(); + +} // namespace testing + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_H_ diff --git a/libs/fmt/test/gtest/gtest/gtest-spi.h b/libs/fmt/test/gtest/gtest/gtest-spi.h new file mode 100644 index 0000000000000000000000000000000000000000..eacef44669e8104a3d28daa054edadcdeb23c548 --- /dev/null +++ b/libs/fmt/test/gtest/gtest/gtest-spi.h @@ -0,0 +1,238 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// Utilities for testing Google Test itself and code that uses Google Test +// (e.g. frameworks built on top of Google Test). + +// GOOGLETEST_CM0004 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ + +#include "gtest/gtest.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// This helper class can be used to mock out Google Test failure reporting +// so that we can test Google Test or code that builds on Google Test. +// +// An object of this class appends a TestPartResult object to the +// TestPartResultArray object given in the constructor whenever a Google Test +// failure is reported. It can either intercept only failures that are +// generated in the same thread that created this object or it can intercept +// all generated failures. The scope of this mock object can be controlled with +// the second argument to the two arguments constructor. +class GTEST_API_ ScopedFakeTestPartResultReporter + : public TestPartResultReporterInterface { + public: + // The two possible mocking modes of this object. + enum InterceptMode { + INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures. + INTERCEPT_ALL_THREADS // Intercepts all failures. + }; + + // The c'tor sets this object as the test part result reporter used + // by Google Test. The 'result' parameter specifies where to report the + // results. This reporter will only catch failures generated in the current + // thread. DEPRECATED + explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result); + + // Same as above, but you can choose the interception scope of this object. + ScopedFakeTestPartResultReporter(InterceptMode intercept_mode, + TestPartResultArray* result); + + // The d'tor restores the previous test part result reporter. + ~ScopedFakeTestPartResultReporter() override; + + // Appends the TestPartResult object to the TestPartResultArray + // received in the constructor. + // + // This method is from the TestPartResultReporterInterface + // interface. + void ReportTestPartResult(const TestPartResult& result) override; + + private: + void Init(); + + const InterceptMode intercept_mode_; + TestPartResultReporterInterface* old_reporter_; + TestPartResultArray* const result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter); +}; + +namespace internal { + +// A helper class for implementing EXPECT_FATAL_FAILURE() and +// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +class GTEST_API_ SingleFailureChecker { + public: + // The constructor remembers the arguments. + SingleFailureChecker(const TestPartResultArray* results, + TestPartResult::Type type, const std::string& substr); + ~SingleFailureChecker(); + private: + const TestPartResultArray* const results_; + const TestPartResult::Type type_; + const std::string substr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker); +}; + +} // namespace internal + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +// A set of macros for testing Google Test assertions or code that's expected +// to generate Google Test fatal failures. It verifies that the given +// statement will cause exactly one fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_FATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - 'statement' cannot reference local non-static variables or +// non-static members of the current object. +// - 'statement' cannot return a value. +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. The AcceptsMacroThatExpandsToUnprotectedComma test in +// gtest_unittest.cc will fail to compile if we do that. +#define EXPECT_FATAL_FAILURE(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ALL_THREADS, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +// A macro for testing Google Test assertions or code that's expected to +// generate Google Test non-fatal failures. It asserts that the given +// statement will cause exactly one non-fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// 'statement' is allowed to reference local variables and members of +// the current object. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. If we do that, the code won't compile when the user gives +// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that +// expands to code containing an unprotected comma. The +// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc +// catches that. +// +// For the same reason, we have to write +// if (::testing::internal::AlwaysTrue()) { statement; } +// instead of +// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) +// to avoid an MSVC warning on unreachable code. +#define EXPECT_NONFATAL_FAILURE(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \ + >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ diff --git a/libs/fmt/test/gtest/gtest/gtest.h b/libs/fmt/test/gtest/gtest/gtest.h new file mode 100644 index 0000000000000000000000000000000000000000..55a31d0a87dc788d2d10d7db17751632a5e4f4a6 --- /dev/null +++ b/libs/fmt/test/gtest/gtest/gtest.h @@ -0,0 +1,12398 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines the public API for Google Test. It should be +// included by any test program that uses Google Test. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! +// +// Acknowledgment: Google Test borrowed the idea of automatic test +// registration from Barthelemy Dagenais' (barthelemy@prologique.com) +// easyUnit framework. + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_H_ + +#include <cstddef> +#include <limits> +#include <memory> +#include <ostream> +#include <type_traits> +#include <vector> + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file declares functions and macros used internally by +// Google Test. They are subject to change without notice. + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Low-level types and utilities for porting Google Test to various +// platforms. All macros ending with _ and symbols defined in an +// internal namespace are subject to change without notice. Code +// outside Google Test MUST NOT USE THEM DIRECTLY. Macros that don't +// end with _ are part of Google Test's public API and can be used by +// code outside Google Test. +// +// This file is fundamental to Google Test. All other Google Test source +// files are expected to #include this. Therefore, it cannot #include +// any other Google Test header. + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ + +// Environment-describing macros +// ----------------------------- +// +// Google Test can be used in many different environments. Macros in +// this section tell Google Test what kind of environment it is being +// used in, such that Google Test can provide environment-specific +// features and implementations. +// +// Google Test tries to automatically detect the properties of its +// environment, so users usually don't need to worry about these +// macros. However, the automatic detection is not perfect. +// Sometimes it's necessary for a user to define some of the following +// macros in the build script to override Google Test's decisions. +// +// If the user doesn't define a macro in the list, Google Test will +// provide a default definition. After this header is #included, all +// macros in this list will be defined to either 1 or 0. +// +// Notes to maintainers: +// - Each macro here is a user-tweakable knob; do not grow the list +// lightly. +// - Use #if to key off these macros. Don't use #ifdef or "#if +// defined(...)", which will not work as these macros are ALWAYS +// defined. +// +// GTEST_HAS_CLONE - Define it to 1/0 to indicate that clone(2) +// is/isn't available. +// GTEST_HAS_EXCEPTIONS - Define it to 1/0 to indicate that exceptions +// are enabled. +// GTEST_HAS_POSIX_RE - Define it to 1/0 to indicate that POSIX regular +// expressions are/aren't available. +// GTEST_HAS_PTHREAD - Define it to 1/0 to indicate that <pthread.h> +// is/isn't available. +// GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't +// enabled. +// GTEST_HAS_STD_WSTRING - Define it to 1/0 to indicate that +// std::wstring does/doesn't work (Google Test can +// be used where std::wstring is unavailable). +// GTEST_HAS_SEH - Define it to 1/0 to indicate whether the +// compiler supports Microsoft's "Structured +// Exception Handling". +// GTEST_HAS_STREAM_REDIRECTION +// - Define it to 1/0 to indicate whether the +// platform supports I/O stream redirection using +// dup() and dup2(). +// GTEST_LINKED_AS_SHARED_LIBRARY +// - Define to 1 when compiling tests that use +// Google Test as a shared library (known as +// DLL on Windows). +// GTEST_CREATE_SHARED_LIBRARY +// - Define to 1 when compiling Google Test itself +// as a shared library. +// GTEST_DEFAULT_DEATH_TEST_STYLE +// - The default value of --gtest_death_test_style. +// The legacy default has been "fast" in the open +// source version since 2008. The recommended value +// is "threadsafe", and can be set in +// custom/gtest-port.h. + +// Platform-indicating macros +// -------------------------- +// +// Macros indicating the platform on which Google Test is being used +// (a macro is defined to 1 if compiled on the given platform; +// otherwise UNDEFINED -- it's never defined to 0.). Google Test +// defines these macros automatically. Code outside Google Test MUST +// NOT define them. +// +// GTEST_OS_AIX - IBM AIX +// GTEST_OS_CYGWIN - Cygwin +// GTEST_OS_DRAGONFLY - DragonFlyBSD +// GTEST_OS_FREEBSD - FreeBSD +// GTEST_OS_FUCHSIA - Fuchsia +// GTEST_OS_GNU_KFREEBSD - GNU/kFreeBSD +// GTEST_OS_HAIKU - Haiku +// GTEST_OS_HPUX - HP-UX +// GTEST_OS_LINUX - Linux +// GTEST_OS_LINUX_ANDROID - Google Android +// GTEST_OS_MAC - Mac OS X +// GTEST_OS_IOS - iOS +// GTEST_OS_NACL - Google Native Client (NaCl) +// GTEST_OS_NETBSD - NetBSD +// GTEST_OS_OPENBSD - OpenBSD +// GTEST_OS_OS2 - OS/2 +// GTEST_OS_QNX - QNX +// GTEST_OS_SOLARIS - Sun Solaris +// GTEST_OS_WINDOWS - Windows (Desktop, MinGW, or Mobile) +// GTEST_OS_WINDOWS_DESKTOP - Windows Desktop +// GTEST_OS_WINDOWS_MINGW - MinGW +// GTEST_OS_WINDOWS_MOBILE - Windows Mobile +// GTEST_OS_WINDOWS_PHONE - Windows Phone +// GTEST_OS_WINDOWS_RT - Windows Store App/WinRT +// GTEST_OS_ZOS - z/OS +// +// Among the platforms, Cygwin, Linux, Mac OS X, and Windows have the +// most stable support. Since core members of the Google Test project +// don't have access to other platforms, support for them may be less +// stable. If you notice any problems on your platform, please notify +// googletestframework@googlegroups.com (patches for fixing them are +// even more welcome!). +// +// It is possible that none of the GTEST_OS_* macros are defined. + +// Feature-indicating macros +// ------------------------- +// +// Macros indicating which Google Test features are available (a macro +// is defined to 1 if the corresponding feature is supported; +// otherwise UNDEFINED -- it's never defined to 0.). Google Test +// defines these macros automatically. Code outside Google Test MUST +// NOT define them. +// +// These macros are public so that portable tests can be written. +// Such tests typically surround code using a feature with an #if +// which controls that code. For example: +// +// #if GTEST_HAS_DEATH_TEST +// EXPECT_DEATH(DoSomethingDeadly()); +// #endif +// +// GTEST_HAS_DEATH_TEST - death tests +// GTEST_HAS_TYPED_TEST - typed tests +// GTEST_HAS_TYPED_TEST_P - type-parameterized tests +// GTEST_IS_THREADSAFE - Google Test is thread-safe. +// GOOGLETEST_CM0007 DO NOT DELETE +// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. Do not confuse with +// GTEST_HAS_POSIX_RE (see above) which users can +// define themselves. +// GTEST_USES_SIMPLE_RE - our own simple regex is used; +// the above RE\b(s) are mutually exclusive. + +// Misc public macros +// ------------------ +// +// GTEST_FLAG(flag_name) - references the variable corresponding to +// the given Google Test flag. + +// Internal utilities +// ------------------ +// +// The following macros and utilities are for Google Test's INTERNAL +// use only. Code outside Google Test MUST NOT USE THEM DIRECTLY. +// +// Macros for basic C++ coding: +// GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning. +// GTEST_ATTRIBUTE_UNUSED_ - declares that a class' instances or a +// variable don't have to be used. +// GTEST_DISALLOW_ASSIGN_ - disables copy operator=. +// GTEST_DISALLOW_COPY_AND_ASSIGN_ - disables copy ctor and operator=. +// GTEST_DISALLOW_MOVE_ASSIGN_ - disables move operator=. +// GTEST_DISALLOW_MOVE_AND_ASSIGN_ - disables move ctor and operator=. +// GTEST_MUST_USE_RESULT_ - declares that a function's result must be used. +// GTEST_INTENTIONAL_CONST_COND_PUSH_ - start code section where MSVC C4127 is +// suppressed (constant conditional). +// GTEST_INTENTIONAL_CONST_COND_POP_ - finish code section where MSVC C4127 +// is suppressed. +// GTEST_INTERNAL_HAS_ANY - for enabling UniversalPrinter<std::any> or +// UniversalPrinter<absl::any> specializations. +// GTEST_INTERNAL_HAS_OPTIONAL - for enabling UniversalPrinter<std::optional> +// or +// UniversalPrinter<absl::optional> +// specializations. +// GTEST_INTERNAL_HAS_STRING_VIEW - for enabling Matcher<std::string_view> or +// Matcher<absl::string_view> +// specializations. +// GTEST_INTERNAL_HAS_VARIANT - for enabling UniversalPrinter<std::variant> or +// UniversalPrinter<absl::variant> +// specializations. +// +// Synchronization: +// Mutex, MutexLock, ThreadLocal, GetThreadCount() +// - synchronization primitives. +// +// Regular expressions: +// RE - a simple regular expression class using the POSIX +// Extended Regular Expression syntax on UNIX-like platforms +// GOOGLETEST_CM0008 DO NOT DELETE +// or a reduced regular exception syntax on other +// platforms, including Windows. +// Logging: +// GTEST_LOG_() - logs messages at the specified severity level. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. +// +// Stdout and stderr capturing: +// CaptureStdout() - starts capturing stdout. +// GetCapturedStdout() - stops capturing stdout and returns the captured +// string. +// CaptureStderr() - starts capturing stderr. +// GetCapturedStderr() - stops capturing stderr and returns the captured +// string. +// +// Integer types: +// TypeWithSize - maps an integer to a int type. +// TimeInMillis - integers of known sizes. +// BiggestInt - the biggest signed integer type. +// +// Command-line utilities: +// GTEST_DECLARE_*() - declares a flag. +// GTEST_DEFINE_*() - defines a flag. +// GetInjectableArgvs() - returns the command line as a vector of strings. +// +// Environment variable utilities: +// GetEnv() - gets the value of an environment variable. +// BoolFromGTestEnv() - parses a bool environment variable. +// Int32FromGTestEnv() - parses an int32_t environment variable. +// StringFromGTestEnv() - parses a string environment variable. +// +// Deprecation warnings: +// GTEST_INTERNAL_DEPRECATED(message) - attribute marking a function as +// deprecated; calling a marked function +// should generate a compiler warning + +#include <ctype.h> // for isspace, etc +#include <stddef.h> // for ptrdiff_t +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <cerrno> +#include <cstdint> +#include <limits> +#include <type_traits> + +#ifndef _WIN32_WCE +# include <sys/types.h> +# include <sys/stat.h> +#endif // !_WIN32_WCE + +#if defined __APPLE__ +# include <AvailabilityMacros.h> +# include <TargetConditionals.h> +#endif + +#include <iostream> // NOLINT +#include <locale> +#include <memory> +#include <string> // NOLINT +#include <tuple> +#include <vector> // NOLINT + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Injection point for custom user configurations. See README for details +// +// ** Custom implementation starts here ** + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines the GTEST_OS_* macro. +// It is separate from gtest-port.h so that custom/gtest-port.h can include it. + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ + +// Determines the platform on which Google Test is compiled. +#ifdef __CYGWIN__ +# define GTEST_OS_CYGWIN 1 +# elif defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__) +# define GTEST_OS_WINDOWS_MINGW 1 +# define GTEST_OS_WINDOWS 1 +#elif defined _WIN32 +# define GTEST_OS_WINDOWS 1 +# ifdef _WIN32_WCE +# define GTEST_OS_WINDOWS_MOBILE 1 +# elif defined(WINAPI_FAMILY) +# include <winapifamily.h> +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define GTEST_OS_WINDOWS_DESKTOP 1 +# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) +# define GTEST_OS_WINDOWS_PHONE 1 +# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) +# define GTEST_OS_WINDOWS_RT 1 +# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_TV_TITLE) +# define GTEST_OS_WINDOWS_PHONE 1 +# define GTEST_OS_WINDOWS_TV_TITLE 1 +# else + // WINAPI_FAMILY defined but no known partition matched. + // Default to desktop. +# define GTEST_OS_WINDOWS_DESKTOP 1 +# endif +# else +# define GTEST_OS_WINDOWS_DESKTOP 1 +# endif // _WIN32_WCE +#elif defined __OS2__ +# define GTEST_OS_OS2 1 +#elif defined __APPLE__ +# define GTEST_OS_MAC 1 +# include <TargetConditionals.h> +# if TARGET_OS_IPHONE +# define GTEST_OS_IOS 1 +# endif +#elif defined __DragonFly__ +# define GTEST_OS_DRAGONFLY 1 +#elif defined __FreeBSD__ +# define GTEST_OS_FREEBSD 1 +#elif defined __Fuchsia__ +# define GTEST_OS_FUCHSIA 1 +#elif defined(__GLIBC__) && defined(__FreeBSD_kernel__) +# define GTEST_OS_GNU_KFREEBSD 1 +#elif defined __linux__ +# define GTEST_OS_LINUX 1 +# if defined __ANDROID__ +# define GTEST_OS_LINUX_ANDROID 1 +# endif +#elif defined __MVS__ +# define GTEST_OS_ZOS 1 +#elif defined(__sun) && defined(__SVR4) +# define GTEST_OS_SOLARIS 1 +#elif defined(_AIX) +# define GTEST_OS_AIX 1 +#elif defined(__hpux) +# define GTEST_OS_HPUX 1 +#elif defined __native_client__ +# define GTEST_OS_NACL 1 +#elif defined __NetBSD__ +# define GTEST_OS_NETBSD 1 +#elif defined __OpenBSD__ +# define GTEST_OS_OPENBSD 1 +#elif defined __QNX__ +# define GTEST_OS_QNX 1 +#elif defined(__HAIKU__) +#define GTEST_OS_HAIKU 1 +#elif defined ESP8266 +#define GTEST_OS_ESP8266 1 +#elif defined ESP32 +#define GTEST_OS_ESP32 1 +#elif defined(__XTENSA__) +#define GTEST_OS_XTENSA 1 +#endif // __CYGWIN__ + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ + +#if !defined(GTEST_DEV_EMAIL_) +# define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com" +# define GTEST_FLAG_PREFIX_ "gtest_" +# define GTEST_FLAG_PREFIX_DASH_ "gtest-" +# define GTEST_FLAG_PREFIX_UPPER_ "GTEST_" +# define GTEST_NAME_ "Google Test" +# define GTEST_PROJECT_URL_ "https://github.com/google/googletest/" +#endif // !defined(GTEST_DEV_EMAIL_) + +#if !defined(GTEST_INIT_GOOGLE_TEST_NAME_) +# define GTEST_INIT_GOOGLE_TEST_NAME_ "testing::InitGoogleTest" +#endif // !defined(GTEST_INIT_GOOGLE_TEST_NAME_) + +// Determines the version of gcc that is used to compile this. +#ifdef __GNUC__ +// 40302 means version 4.3.2. +# define GTEST_GCC_VER_ \ + (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + +// Macros for disabling Microsoft Visual C++ warnings. +// +// GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 4385) +// /* code that triggers warnings C4800 and C4385 */ +// GTEST_DISABLE_MSC_WARNINGS_POP_() +#if defined(_MSC_VER) +# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) \ + __pragma(warning(push)) \ + __pragma(warning(disable: warnings)) +# define GTEST_DISABLE_MSC_WARNINGS_POP_() \ + __pragma(warning(pop)) +#else +// Not all compilers are MSVC +# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) +# define GTEST_DISABLE_MSC_WARNINGS_POP_() +#endif + +// Clang on Windows does not understand MSVC's pragma warning. +// We need clang-specific way to disable function deprecation warning. +#ifdef __clang__ +# define GTEST_DISABLE_MSC_DEPRECATED_PUSH_() \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \ + _Pragma("clang diagnostic ignored \"-Wdeprecated-implementations\"") +#define GTEST_DISABLE_MSC_DEPRECATED_POP_() \ + _Pragma("clang diagnostic pop") +#else +# define GTEST_DISABLE_MSC_DEPRECATED_PUSH_() \ + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4996) +# define GTEST_DISABLE_MSC_DEPRECATED_POP_() \ + GTEST_DISABLE_MSC_WARNINGS_POP_() +#endif + +// Brings in definitions for functions used in the testing::internal::posix +// namespace (read, write, close, chdir, isatty, stat). We do not currently +// use them on Windows Mobile. +#if GTEST_OS_WINDOWS +# if !GTEST_OS_WINDOWS_MOBILE +# include <direct.h> +# include <io.h> +# endif +// In order to avoid having to include <windows.h>, use forward declaration +#if GTEST_OS_WINDOWS_MINGW && !defined(__MINGW64_VERSION_MAJOR) +// MinGW defined _CRITICAL_SECTION and _RTL_CRITICAL_SECTION as two +// separate (equivalent) structs, instead of using typedef +typedef struct _CRITICAL_SECTION GTEST_CRITICAL_SECTION; +#else +// Assume CRITICAL_SECTION is a typedef of _RTL_CRITICAL_SECTION. +// This assumption is verified by +// WindowsTypesTest.CRITICAL_SECTIONIs_RTL_CRITICAL_SECTION. +typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION; +#endif +#elif GTEST_OS_XTENSA +#include <unistd.h> +// Xtensa toolchains define strcasecmp in the string.h header instead of +// strings.h. string.h is already included. +#else +// This assumes that non-Windows OSes provide unistd.h. For OSes where this +// is not the case, we need to include headers that provide the functions +// mentioned above. +# include <unistd.h> +# include <strings.h> +#endif // GTEST_OS_WINDOWS + +#if GTEST_OS_LINUX_ANDROID +// Used to define __ANDROID_API__ matching the target NDK API level. +# include <android/api-level.h> // NOLINT +#endif + +// Defines this to true if and only if Google Test can use POSIX regular +// expressions. +#ifndef GTEST_HAS_POSIX_RE +# if GTEST_OS_LINUX_ANDROID +// On Android, <regex.h> is only available starting with Gingerbread. +# define GTEST_HAS_POSIX_RE (__ANDROID_API__ >= 9) +# else +#define GTEST_HAS_POSIX_RE (!GTEST_OS_WINDOWS && !GTEST_OS_XTENSA) +# endif +#endif + +#if GTEST_USES_PCRE +// The appropriate headers have already been included. + +#elif GTEST_HAS_POSIX_RE + +// On some platforms, <regex.h> needs someone to define size_t, and +// won't compile otherwise. We can #include it here as we already +// included <stdlib.h>, which is guaranteed to define size_t through +// <stddef.h>. +# include <regex.h> // NOLINT + +# define GTEST_USES_POSIX_RE 1 + +#elif GTEST_OS_WINDOWS + +// <regex.h> is not available on Windows. Use our own simple regex +// implementation instead. +# define GTEST_USES_SIMPLE_RE 1 + +#else + +// <regex.h> may not be available on this platform. Use our own +// simple regex implementation instead. +# define GTEST_USES_SIMPLE_RE 1 + +#endif // GTEST_USES_PCRE + +#ifndef GTEST_HAS_EXCEPTIONS +// The user didn't tell us whether exceptions are enabled, so we need +// to figure it out. +# if defined(_MSC_VER) && defined(_CPPUNWIND) +// MSVC defines _CPPUNWIND to 1 if and only if exceptions are enabled. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__BORLANDC__) +// C++Builder's implementation of the STL uses the _HAS_EXCEPTIONS +// macro to enable exceptions, so we'll do the same. +// Assumes that exceptions are enabled by default. +# ifndef _HAS_EXCEPTIONS +# define _HAS_EXCEPTIONS 1 +# endif // _HAS_EXCEPTIONS +# define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS +# elif defined(__clang__) +// clang defines __EXCEPTIONS if and only if exceptions are enabled before clang +// 220714, but if and only if cleanups are enabled after that. In Obj-C++ files, +// there can be cleanups for ObjC exceptions which also need cleanups, even if +// C++ exceptions are disabled. clang has __has_feature(cxx_exceptions) which +// checks for C++ exceptions starting at clang r206352, but which checked for +// cleanups prior to that. To reliably check for C++ exception availability with +// clang, check for +// __EXCEPTIONS && __has_feature(cxx_exceptions). +# define GTEST_HAS_EXCEPTIONS (__EXCEPTIONS && __has_feature(cxx_exceptions)) +# elif defined(__GNUC__) && __EXCEPTIONS +// gcc defines __EXCEPTIONS to 1 if and only if exceptions are enabled. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__SUNPRO_CC) +// Sun Pro CC supports exceptions. However, there is no compile-time way of +// detecting whether they are enabled or not. Therefore, we assume that +// they are enabled unless the user tells us otherwise. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__IBMCPP__) && __EXCEPTIONS +// xlC defines __EXCEPTIONS to 1 if and only if exceptions are enabled. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__HP_aCC) +// Exception handling is in effect by default in HP aCC compiler. It has to +// be turned of by +noeh compiler option if desired. +# define GTEST_HAS_EXCEPTIONS 1 +# else +// For other compilers, we assume exceptions are disabled to be +// conservative. +# define GTEST_HAS_EXCEPTIONS 0 +# endif // defined(_MSC_VER) || defined(__BORLANDC__) +#endif // GTEST_HAS_EXCEPTIONS + +#ifndef GTEST_HAS_STD_WSTRING +// The user didn't tell us whether ::std::wstring is available, so we need +// to figure it out. +// Cygwin 1.7 and below doesn't support ::std::wstring. +// Solaris' libc++ doesn't support it either. Android has +// no support for it at least as recent as Froyo (2.2). +#define GTEST_HAS_STD_WSTRING \ + (!(GTEST_OS_LINUX_ANDROID || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ + GTEST_OS_HAIKU || GTEST_OS_ESP32 || GTEST_OS_ESP8266 || GTEST_OS_XTENSA)) + +#endif // GTEST_HAS_STD_WSTRING + +// Determines whether RTTI is available. +#ifndef GTEST_HAS_RTTI +// The user didn't tell us whether RTTI is enabled, so we need to +// figure it out. + +# ifdef _MSC_VER + +#ifdef _CPPRTTI // MSVC defines this macro if and only if RTTI is enabled. +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif + +// Starting with version 4.3.2, gcc defines __GXX_RTTI if and only if RTTI is +// enabled. +# elif defined(__GNUC__) + +# ifdef __GXX_RTTI +// When building against STLport with the Android NDK and with +// -frtti -fno-exceptions, the build fails at link time with undefined +// references to __cxa_bad_typeid. Note sure if STL or toolchain bug, +// so disable RTTI when detected. +# if GTEST_OS_LINUX_ANDROID && defined(_STLPORT_MAJOR) && \ + !defined(__EXCEPTIONS) +# define GTEST_HAS_RTTI 0 +# else +# define GTEST_HAS_RTTI 1 +# endif // GTEST_OS_LINUX_ANDROID && __STLPORT_MAJOR && !__EXCEPTIONS +# else +# define GTEST_HAS_RTTI 0 +# endif // __GXX_RTTI + +// Clang defines __GXX_RTTI starting with version 3.0, but its manual recommends +// using has_feature instead. has_feature(cxx_rtti) is supported since 2.7, the +// first version with C++ support. +# elif defined(__clang__) + +# define GTEST_HAS_RTTI __has_feature(cxx_rtti) + +// Starting with version 9.0 IBM Visual Age defines __RTTI_ALL__ to 1 if +// both the typeid and dynamic_cast features are present. +# elif defined(__IBMCPP__) && (__IBMCPP__ >= 900) + +# ifdef __RTTI_ALL__ +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif + +# else + +// For all other compilers, we assume RTTI is enabled. +# define GTEST_HAS_RTTI 1 + +# endif // _MSC_VER + +#endif // GTEST_HAS_RTTI + +// It's this header's responsibility to #include <typeinfo> when RTTI +// is enabled. +#if GTEST_HAS_RTTI +# include <typeinfo> +#endif + +// Determines whether Google Test can use the pthreads library. +#ifndef GTEST_HAS_PTHREAD +// The user didn't tell us explicitly, so we make reasonable assumptions about +// which platforms have pthreads support. +// +// To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0 +// to your compiler flags. +#define GTEST_HAS_PTHREAD \ + (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_HPUX || GTEST_OS_QNX || \ + GTEST_OS_FREEBSD || GTEST_OS_NACL || GTEST_OS_NETBSD || GTEST_OS_FUCHSIA || \ + GTEST_OS_DRAGONFLY || GTEST_OS_GNU_KFREEBSD || GTEST_OS_OPENBSD || \ + GTEST_OS_HAIKU) +#endif // GTEST_HAS_PTHREAD + +#if GTEST_HAS_PTHREAD +// gtest-port.h guarantees to #include <pthread.h> when GTEST_HAS_PTHREAD is +// true. +# include <pthread.h> // NOLINT + +// For timespec and nanosleep, used below. +# include <time.h> // NOLINT +#endif + +// Determines whether clone(2) is supported. +// Usually it will only be available on Linux, excluding +// Linux on the Itanium architecture. +// Also see http://linux.die.net/man/2/clone. +#ifndef GTEST_HAS_CLONE +// The user didn't tell us, so we need to figure it out. + +# if GTEST_OS_LINUX && !defined(__ia64__) +# if GTEST_OS_LINUX_ANDROID +// On Android, clone() became available at different API levels for each 32-bit +// architecture. +# if defined(__LP64__) || \ + (defined(__arm__) && __ANDROID_API__ >= 9) || \ + (defined(__mips__) && __ANDROID_API__ >= 12) || \ + (defined(__i386__) && __ANDROID_API__ >= 17) +# define GTEST_HAS_CLONE 1 +# else +# define GTEST_HAS_CLONE 0 +# endif +# else +# define GTEST_HAS_CLONE 1 +# endif +# else +# define GTEST_HAS_CLONE 0 +# endif // GTEST_OS_LINUX && !defined(__ia64__) + +#endif // GTEST_HAS_CLONE + +// Determines whether to support stream redirection. This is used to test +// output correctness and to implement death tests. +#ifndef GTEST_HAS_STREAM_REDIRECTION +// By default, we assume that stream redirection is supported on all +// platforms except known mobile ones. +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || \ + GTEST_OS_WINDOWS_RT || GTEST_OS_ESP8266 || GTEST_OS_XTENSA +# define GTEST_HAS_STREAM_REDIRECTION 0 +# else +# define GTEST_HAS_STREAM_REDIRECTION 1 +# endif // !GTEST_OS_WINDOWS_MOBILE +#endif // GTEST_HAS_STREAM_REDIRECTION + +// Determines whether to support death tests. +// pops up a dialog window that cannot be suppressed programmatically. +#if (GTEST_OS_LINUX || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ + (GTEST_OS_MAC && !GTEST_OS_IOS) || \ + (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER) || GTEST_OS_WINDOWS_MINGW || \ + GTEST_OS_AIX || GTEST_OS_HPUX || GTEST_OS_OPENBSD || GTEST_OS_QNX || \ + GTEST_OS_FREEBSD || GTEST_OS_NETBSD || GTEST_OS_FUCHSIA || \ + GTEST_OS_DRAGONFLY || GTEST_OS_GNU_KFREEBSD || GTEST_OS_HAIKU) +# define GTEST_HAS_DEATH_TEST 1 +#endif + +// Determines whether to support type-driven tests. + +// Typed tests need <typeinfo> and variadic macros, which GCC, VC++ 8.0, +// Sun Pro CC, IBM Visual Age, and HP aCC support. +#if defined(__GNUC__) || defined(_MSC_VER) || defined(__SUNPRO_CC) || \ + defined(__IBMCPP__) || defined(__HP_aCC) +# define GTEST_HAS_TYPED_TEST 1 +# define GTEST_HAS_TYPED_TEST_P 1 +#endif + +// Determines whether the system compiler uses UTF-16 for encoding wide strings. +#define GTEST_WIDE_STRING_USES_UTF16_ \ + (GTEST_OS_WINDOWS || GTEST_OS_CYGWIN || GTEST_OS_AIX || GTEST_OS_OS2) + +// Determines whether test results can be streamed to a socket. +#if GTEST_OS_LINUX || GTEST_OS_GNU_KFREEBSD || GTEST_OS_DRAGONFLY || \ + GTEST_OS_FREEBSD || GTEST_OS_NETBSD || GTEST_OS_OPENBSD +# define GTEST_CAN_STREAM_RESULTS_ 1 +#endif + +// Defines some utility macros. + +// The GNU compiler emits a warning if nested "if" statements are followed by +// an "else" statement and braces are not used to explicitly disambiguate the +// "else" binding. This leads to problems with code like: +// +// if (gate) +// ASSERT_*(condition) << "Some message"; +// +// The "switch (0) case 0:" idiom is used to suppress this. +#ifdef __INTEL_COMPILER +# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ +#else +# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: default: // NOLINT +#endif + +// Use this annotation at the end of a struct/class definition to +// prevent the compiler from optimizing away instances that are never +// used. This is useful when all interesting logic happens inside the +// c'tor and / or d'tor. Example: +// +// struct Foo { +// Foo() { ... } +// } GTEST_ATTRIBUTE_UNUSED_; +// +// Also use it after a variable or parameter declaration to tell the +// compiler the variable/parameter does not have to be used. +#if defined(__GNUC__) && !defined(COMPILER_ICC) +# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) +#elif defined(__clang__) +# if __has_attribute(unused) +# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) +# endif +#endif +#ifndef GTEST_ATTRIBUTE_UNUSED_ +# define GTEST_ATTRIBUTE_UNUSED_ +#endif + +// Use this annotation before a function that takes a printf format string. +#if (defined(__GNUC__) || defined(__clang__)) && !defined(COMPILER_ICC) +# if defined(__MINGW_PRINTF_FORMAT) +// MinGW has two different printf implementations. Ensure the format macro +// matches the selected implementation. See +// https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/. +# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \ + __attribute__((__format__(__MINGW_PRINTF_FORMAT, string_index, \ + first_to_check))) +# else +# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \ + __attribute__((__format__(__printf__, string_index, first_to_check))) +# endif +#else +# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) +#endif + + +// A macro to disallow copy operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_ASSIGN_(type) \ + type& operator=(type const &) = delete + +// A macro to disallow copy constructor and operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type) \ + type(type const&) = delete; \ + type& operator=(type const&) = delete + +// A macro to disallow move operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_MOVE_ASSIGN_(type) \ + type& operator=(type &&) noexcept = delete + +// A macro to disallow move constructor and operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_MOVE_AND_ASSIGN_(type) \ + type(type&&) noexcept = delete; \ + type& operator=(type&&) noexcept = delete + +// Tell the compiler to warn about unused return values for functions declared +// with this macro. The macro should be used on function declarations +// following the argument list: +// +// Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT_; +#if defined(__GNUC__) && !defined(COMPILER_ICC) +# define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result)) +#else +# define GTEST_MUST_USE_RESULT_ +#endif // __GNUC__ && !COMPILER_ICC + +// MS C++ compiler emits warning when a conditional expression is compile time +// constant. In some contexts this warning is false positive and needs to be +// suppressed. Use the following two macros in such cases: +// +// GTEST_INTENTIONAL_CONST_COND_PUSH_() +// while (true) { +// GTEST_INTENTIONAL_CONST_COND_POP_() +// } +# define GTEST_INTENTIONAL_CONST_COND_PUSH_() \ + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127) +# define GTEST_INTENTIONAL_CONST_COND_POP_() \ + GTEST_DISABLE_MSC_WARNINGS_POP_() + +// Determine whether the compiler supports Microsoft's Structured Exception +// Handling. This is supported by several Windows compilers but generally +// does not exist on any other system. +#ifndef GTEST_HAS_SEH +// The user didn't tell us, so we need to figure it out. + +# if defined(_MSC_VER) || defined(__BORLANDC__) +// These two compilers are known to support SEH. +# define GTEST_HAS_SEH 1 +# else +// Assume no SEH. +# define GTEST_HAS_SEH 0 +# endif + +#endif // GTEST_HAS_SEH + +#ifndef GTEST_IS_THREADSAFE + +#define GTEST_IS_THREADSAFE \ + (GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ || \ + (GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT) || \ + GTEST_HAS_PTHREAD) + +#endif // GTEST_IS_THREADSAFE + +// GTEST_API_ qualifies all symbols that must be exported. The definitions below +// are guarded by #ifndef to give embedders a chance to define GTEST_API_ in +// gtest/internal/custom/gtest-port.h +#ifndef GTEST_API_ + +#ifdef _MSC_VER +# if GTEST_LINKED_AS_SHARED_LIBRARY +# define GTEST_API_ __declspec(dllimport) +# elif GTEST_CREATE_SHARED_LIBRARY +# define GTEST_API_ __declspec(dllexport) +# endif +#elif __GNUC__ >= 4 || defined(__clang__) +# define GTEST_API_ __attribute__((visibility ("default"))) +#endif // _MSC_VER + +#endif // GTEST_API_ + +#ifndef GTEST_API_ +# define GTEST_API_ +#endif // GTEST_API_ + +#ifndef GTEST_DEFAULT_DEATH_TEST_STYLE +# define GTEST_DEFAULT_DEATH_TEST_STYLE "fast" +#endif // GTEST_DEFAULT_DEATH_TEST_STYLE + +#ifdef __GNUC__ +// Ask the compiler to never inline a given function. +# define GTEST_NO_INLINE_ __attribute__((noinline)) +#else +# define GTEST_NO_INLINE_ +#endif + +// _LIBCPP_VERSION is defined by the libc++ library from the LLVM project. +#if !defined(GTEST_HAS_CXXABI_H_) +# if defined(__GLIBCXX__) || (defined(_LIBCPP_VERSION) && !defined(_MSC_VER)) +# define GTEST_HAS_CXXABI_H_ 1 +# else +# define GTEST_HAS_CXXABI_H_ 0 +# endif +#endif + +// A function level attribute to disable checking for use of uninitialized +// memory when built with MemorySanitizer. +#if defined(__clang__) +# if __has_feature(memory_sanitizer) +# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ \ + __attribute__((no_sanitize_memory)) +# else +# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +# endif // __has_feature(memory_sanitizer) +#else +# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ +#endif // __clang__ + +// A function level attribute to disable AddressSanitizer instrumentation. +#if defined(__clang__) +# if __has_feature(address_sanitizer) +# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ \ + __attribute__((no_sanitize_address)) +# else +# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +# endif // __has_feature(address_sanitizer) +#else +# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ +#endif // __clang__ + +// A function level attribute to disable HWAddressSanitizer instrumentation. +#if defined(__clang__) +# if __has_feature(hwaddress_sanitizer) +# define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ \ + __attribute__((no_sanitize("hwaddress"))) +# else +# define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +# endif // __has_feature(hwaddress_sanitizer) +#else +# define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ +#endif // __clang__ + +// A function level attribute to disable ThreadSanitizer instrumentation. +#if defined(__clang__) +# if __has_feature(thread_sanitizer) +# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ \ + __attribute__((no_sanitize_thread)) +# else +# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ +# endif // __has_feature(thread_sanitizer) +#else +# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ +#endif // __clang__ + +namespace testing { + +class Message; + +// Legacy imports for backwards compatibility. +// New code should use std:: names directly. +using std::get; +using std::make_tuple; +using std::tuple; +using std::tuple_element; +using std::tuple_size; + +namespace internal { + +// A secret type that Google Test users don't know about. It has no +// definition on purpose. Therefore it's impossible to create a +// Secret object, which is what we want. +class Secret; + +// The GTEST_COMPILE_ASSERT_ is a legacy macro used to verify that a compile +// time expression is true (in new code, use static_assert instead). For +// example, you could use it to verify the size of a static array: +// +// GTEST_COMPILE_ASSERT_(GTEST_ARRAY_SIZE_(names) == NUM_NAMES, +// names_incorrect_size); +// +// The second argument to the macro must be a valid C++ identifier. If the +// expression is false, compiler will issue an error containing this identifier. +#define GTEST_COMPILE_ASSERT_(expr, msg) static_assert(expr, #msg) + +// A helper for suppressing warnings on constant condition. It just +// returns 'condition'. +GTEST_API_ bool IsTrue(bool condition); + +// Defines RE. + +#if GTEST_USES_PCRE +// if used, PCRE is injected by custom/gtest-port.h +#elif GTEST_USES_POSIX_RE || GTEST_USES_SIMPLE_RE + +// A simple C++ wrapper for <regex.h>. It uses the POSIX Extended +// Regular Expression syntax. +class GTEST_API_ RE { + public: + // A copy constructor is required by the Standard to initialize object + // references from r-values. + RE(const RE& other) { Init(other.pattern()); } + + // Constructs an RE from a string. + RE(const ::std::string& regex) { Init(regex.c_str()); } // NOLINT + + RE(const char* regex) { Init(regex); } // NOLINT + ~RE(); + + // Returns the string representation of the regex. + const char* pattern() const { return pattern_; } + + // FullMatch(str, re) returns true if and only if regular expression re + // matches the entire str. + // PartialMatch(str, re) returns true if and only if regular expression re + // matches a substring of str (including str itself). + static bool FullMatch(const ::std::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::std::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } + + static bool FullMatch(const char* str, const RE& re); + static bool PartialMatch(const char* str, const RE& re); + + private: + void Init(const char* regex); + const char* pattern_; + bool is_valid_; + +# if GTEST_USES_POSIX_RE + + regex_t full_regex_; // For FullMatch(). + regex_t partial_regex_; // For PartialMatch(). + +# else // GTEST_USES_SIMPLE_RE + + const char* full_pattern_; // For FullMatch(); + +# endif +}; + +#endif // GTEST_USES_PCRE + +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line); + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file, + int line); + +// Defines logging utilities: +// GTEST_LOG_(severity) - logs messages at the specified severity level. The +// message itself is streamed into the macro. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. + +enum GTestLogSeverity { + GTEST_INFO, + GTEST_WARNING, + GTEST_ERROR, + GTEST_FATAL +}; + +// Formats log entry severity, provides a stream object for streaming the +// log message, and terminates the message with a newline when going out of +// scope. +class GTEST_API_ GTestLog { + public: + GTestLog(GTestLogSeverity severity, const char* file, int line); + + // Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. + ~GTestLog(); + + ::std::ostream& GetStream() { return ::std::cerr; } + + private: + const GTestLogSeverity severity_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestLog); +}; + +#if !defined(GTEST_LOG_) + +# define GTEST_LOG_(severity) \ + ::testing::internal::GTestLog(::testing::internal::GTEST_##severity, \ + __FILE__, __LINE__).GetStream() + +inline void LogToStderr() {} +inline void FlushInfoLog() { fflush(nullptr); } + +#endif // !defined(GTEST_LOG_) + +#if !defined(GTEST_CHECK_) +// INTERNAL IMPLEMENTATION - DO NOT USE. +// +// GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition +// is not satisfied. +// Synopsys: +// GTEST_CHECK_(boolean_condition); +// or +// GTEST_CHECK_(boolean_condition) << "Additional message"; +// +// This checks the condition and if the condition is not satisfied +// it prints message about the condition violation, including the +// condition itself, plus additional message streamed into it, if any, +// and then it aborts the program. It aborts the program irrespective of +// whether it is built in the debug mode or not. +# define GTEST_CHECK_(condition) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::IsTrue(condition)) \ + ; \ + else \ + GTEST_LOG_(FATAL) << "Condition " #condition " failed. " +#endif // !defined(GTEST_CHECK_) + +// An all-mode assert to verify that the given POSIX-style function +// call returns 0 (indicating success). Known limitation: this +// doesn't expand to a balanced 'if' statement, so enclose the macro +// in {} if you need to use it as the only statement in an 'if' +// branch. +#define GTEST_CHECK_POSIX_SUCCESS_(posix_call) \ + if (const int gtest_error = (posix_call)) \ + GTEST_LOG_(FATAL) << #posix_call << "failed with error " \ + << gtest_error + +// Transforms "T" into "const T&" according to standard reference collapsing +// rules (this is only needed as a backport for C++98 compilers that do not +// support reference collapsing). Specifically, it transforms: +// +// char ==> const char& +// const char ==> const char& +// char& ==> char& +// const char& ==> const char& +// +// Note that the non-const reference will not have "const" added. This is +// standard, and necessary so that "T" can always bind to "const T&". +template <typename T> +struct ConstRef { typedef const T& type; }; +template <typename T> +struct ConstRef<T&> { typedef T& type; }; + +// The argument T must depend on some template parameters. +#define GTEST_REFERENCE_TO_CONST_(T) \ + typename ::testing::internal::ConstRef<T>::type + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Use ImplicitCast_ as a safe version of static_cast for upcasting in +// the type hierarchy (e.g. casting a Foo* to a SuperclassOfFoo* or a +// const Foo*). When you use ImplicitCast_, the compiler checks that +// the cast is safe. Such explicit ImplicitCast_s are necessary in +// surprisingly many situations where C++ demands an exact type match +// instead of an argument type convertable to a target type. +// +// The syntax for using ImplicitCast_ is the same as for static_cast: +// +// ImplicitCast_<ToType>(expr) +// +// ImplicitCast_ would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +// +// This relatively ugly name is intentional. It prevents clashes with +// similar functions users may have (e.g., implicit_cast). The internal +// namespace alone is not enough because the function can be found by ADL. +template<typename To> +inline To ImplicitCast_(To x) { return x; } + +// When you upcast (that is, cast a pointer from type Foo to type +// SuperclassOfFoo), it's fine to use ImplicitCast_<>, since upcasts +// always succeed. When you downcast (that is, cast a pointer from +// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because +// how do you know the pointer is really of type SubclassOfFoo? It +// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, +// when you downcast, you should use this macro. In debug mode, we +// use dynamic_cast<> to double-check the downcast is legal (we die +// if it's not). In normal mode, we do the efficient static_cast<> +// instead. Thus, it's important to test in debug mode to make sure +// the cast is legal! +// This is the only place in the code we should use dynamic_cast<>. +// In particular, you SHOULDN'T be using dynamic_cast<> in order to +// do RTTI (eg code like this: +// if (dynamic_cast<Subclass1>(foo)) HandleASubclass1Object(foo); +// if (dynamic_cast<Subclass2>(foo)) HandleASubclass2Object(foo); +// You should design the code some other way not to need this. +// +// This relatively ugly name is intentional. It prevents clashes with +// similar functions users may have (e.g., down_cast). The internal +// namespace alone is not enough because the function can be found by ADL. +template<typename To, typename From> // use like this: DownCast_<T*>(foo); +inline To DownCast_(From* f) { // so we only accept pointers + // Ensures that To is a sub-type of From *. This test is here only + // for compile-time type checking, and has no overhead in an + // optimized build at run-time, as it will be optimized away + // completely. + GTEST_INTENTIONAL_CONST_COND_PUSH_() + if (false) { + GTEST_INTENTIONAL_CONST_COND_POP_() + const To to = nullptr; + ::testing::internal::ImplicitCast_<From*>(to); + } + +#if GTEST_HAS_RTTI + // RTTI: debug mode only! + GTEST_CHECK_(f == nullptr || dynamic_cast<To>(f) != nullptr); +#endif + return static_cast<To>(f); +} + +// Downcasts the pointer of type Base to Derived. +// Derived must be a subclass of Base. The parameter MUST +// point to a class of type Derived, not any subclass of it. +// When RTTI is available, the function performs a runtime +// check to enforce this. +template <class Derived, class Base> +Derived* CheckedDowncastToActualType(Base* base) { +#if GTEST_HAS_RTTI + GTEST_CHECK_(typeid(*base) == typeid(Derived)); +#endif + +#if GTEST_HAS_DOWNCAST_ + return ::down_cast<Derived*>(base); +#elif GTEST_HAS_RTTI + return dynamic_cast<Derived*>(base); // NOLINT +#else + return static_cast<Derived*>(base); // Poor man's downcast. +#endif +} + +#if GTEST_HAS_STREAM_REDIRECTION + +// Defines the stderr capturer: +// CaptureStdout - starts capturing stdout. +// GetCapturedStdout - stops capturing stdout and returns the captured string. +// CaptureStderr - starts capturing stderr. +// GetCapturedStderr - stops capturing stderr and returns the captured string. +// +GTEST_API_ void CaptureStdout(); +GTEST_API_ std::string GetCapturedStdout(); +GTEST_API_ void CaptureStderr(); +GTEST_API_ std::string GetCapturedStderr(); + +#endif // GTEST_HAS_STREAM_REDIRECTION +// Returns the size (in bytes) of a file. +GTEST_API_ size_t GetFileSize(FILE* file); + +// Reads the entire content of a file as a string. +GTEST_API_ std::string ReadEntireFile(FILE* file); + +// All command line arguments. +GTEST_API_ std::vector<std::string> GetArgvs(); + +#if GTEST_HAS_DEATH_TEST + +std::vector<std::string> GetInjectableArgvs(); +// Deprecated: pass the args vector by value instead. +void SetInjectableArgvs(const std::vector<std::string>* new_argvs); +void SetInjectableArgvs(const std::vector<std::string>& new_argvs); +void ClearInjectableArgvs(); + +#endif // GTEST_HAS_DEATH_TEST + +// Defines synchronization primitives. +#if GTEST_IS_THREADSAFE +# if GTEST_HAS_PTHREAD +// Sleeps for (roughly) n milliseconds. This function is only for testing +// Google Test's own constructs. Don't use it in user tests, either +// directly or indirectly. +inline void SleepMilliseconds(int n) { + const timespec time = { + 0, // 0 seconds. + n * 1000L * 1000L, // And n ms. + }; + nanosleep(&time, nullptr); +} +# endif // GTEST_HAS_PTHREAD + +# if GTEST_HAS_NOTIFICATION_ +// Notification has already been imported into the namespace. +// Nothing to do here. + +# elif GTEST_HAS_PTHREAD +// Allows a controller thread to pause execution of newly created +// threads until notified. Instances of this class must be created +// and destroyed in the controller thread. +// +// This class is only for testing Google Test's own constructs. Do not +// use it in user tests, either directly or indirectly. +class Notification { + public: + Notification() : notified_(false) { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, nullptr)); + } + ~Notification() { + pthread_mutex_destroy(&mutex_); + } + + // Notifies all threads created with this notification to start. Must + // be called from the controller thread. + void Notify() { + pthread_mutex_lock(&mutex_); + notified_ = true; + pthread_mutex_unlock(&mutex_); + } + + // Blocks until the controller thread notifies. Must be called from a test + // thread. + void WaitForNotification() { + for (;;) { + pthread_mutex_lock(&mutex_); + const bool notified = notified_; + pthread_mutex_unlock(&mutex_); + if (notified) + break; + SleepMilliseconds(10); + } + } + + private: + pthread_mutex_t mutex_; + bool notified_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification); +}; + +# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT + +GTEST_API_ void SleepMilliseconds(int n); + +// Provides leak-safe Windows kernel handle ownership. +// Used in death tests and in threading support. +class GTEST_API_ AutoHandle { + public: + // Assume that Win32 HANDLE type is equivalent to void*. Doing so allows us to + // avoid including <windows.h> in this header file. Including <windows.h> is + // undesirable because it defines a lot of symbols and macros that tend to + // conflict with client code. This assumption is verified by + // WindowsTypesTest.HANDLEIsVoidStar. + typedef void* Handle; + AutoHandle(); + explicit AutoHandle(Handle handle); + + ~AutoHandle(); + + Handle Get() const; + void Reset(); + void Reset(Handle handle); + + private: + // Returns true if and only if the handle is a valid handle object that can be + // closed. + bool IsCloseable() const; + + Handle handle_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle); +}; + +// Allows a controller thread to pause execution of newly created +// threads until notified. Instances of this class must be created +// and destroyed in the controller thread. +// +// This class is only for testing Google Test's own constructs. Do not +// use it in user tests, either directly or indirectly. +class GTEST_API_ Notification { + public: + Notification(); + void Notify(); + void WaitForNotification(); + + private: + AutoHandle event_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification); +}; +# endif // GTEST_HAS_NOTIFICATION_ + +// On MinGW, we can have both GTEST_OS_WINDOWS and GTEST_HAS_PTHREAD +// defined, but we don't want to use MinGW's pthreads implementation, which +// has conformance problems with some versions of the POSIX standard. +# if GTEST_HAS_PTHREAD && !GTEST_OS_WINDOWS_MINGW + +// As a C-function, ThreadFuncWithCLinkage cannot be templated itself. +// Consequently, it cannot select a correct instantiation of ThreadWithParam +// in order to call its Run(). Introducing ThreadWithParamBase as a +// non-templated base class for ThreadWithParam allows us to bypass this +// problem. +class ThreadWithParamBase { + public: + virtual ~ThreadWithParamBase() {} + virtual void Run() = 0; +}; + +// pthread_create() accepts a pointer to a function type with the C linkage. +// According to the Standard (7.5/1), function types with different linkages +// are different even if they are otherwise identical. Some compilers (for +// example, SunStudio) treat them as different types. Since class methods +// cannot be defined with C-linkage we need to define a free C-function to +// pass into pthread_create(). +extern "C" inline void* ThreadFuncWithCLinkage(void* thread) { + static_cast<ThreadWithParamBase*>(thread)->Run(); + return nullptr; +} + +// Helper class for testing Google Test's multi-threading constructs. +// To use it, write: +// +// void ThreadFunc(int param) { /* Do things with param */ } +// Notification thread_can_start; +// ... +// // The thread_can_start parameter is optional; you can supply NULL. +// ThreadWithParam<int> thread(&ThreadFunc, 5, &thread_can_start); +// thread_can_start.Notify(); +// +// These classes are only for testing Google Test's own constructs. Do +// not use them in user tests, either directly or indirectly. +template <typename T> +class ThreadWithParam : public ThreadWithParamBase { + public: + typedef void UserThreadFunc(T); + + ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start) + : func_(func), + param_(param), + thread_can_start_(thread_can_start), + finished_(false) { + ThreadWithParamBase* const base = this; + // The thread can be created only after all fields except thread_ + // have been initialized. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_create(&thread_, nullptr, &ThreadFuncWithCLinkage, base)); + } + ~ThreadWithParam() override { Join(); } + + void Join() { + if (!finished_) { + GTEST_CHECK_POSIX_SUCCESS_(pthread_join(thread_, nullptr)); + finished_ = true; + } + } + + void Run() override { + if (thread_can_start_ != nullptr) thread_can_start_->WaitForNotification(); + func_(param_); + } + + private: + UserThreadFunc* const func_; // User-supplied thread function. + const T param_; // User-supplied parameter to the thread function. + // When non-NULL, used to block execution until the controller thread + // notifies. + Notification* const thread_can_start_; + bool finished_; // true if and only if we know that the thread function has + // finished. + pthread_t thread_; // The native thread object. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); +}; +# endif // !GTEST_OS_WINDOWS && GTEST_HAS_PTHREAD || + // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ + +# if GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ +// Mutex and ThreadLocal have already been imported into the namespace. +// Nothing to do here. + +# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT + +// Mutex implements mutex on Windows platforms. It is used in conjunction +// with class MutexLock: +// +// Mutex mutex; +// ... +// MutexLock lock(&mutex); // Acquires the mutex and releases it at the +// // end of the current scope. +// +// A static Mutex *must* be defined or declared using one of the following +// macros: +// GTEST_DEFINE_STATIC_MUTEX_(g_some_mutex); +// GTEST_DECLARE_STATIC_MUTEX_(g_some_mutex); +// +// (A non-static Mutex is defined/declared in the usual way). +class GTEST_API_ Mutex { + public: + enum MutexType { kStatic = 0, kDynamic = 1 }; + // We rely on kStaticMutex being 0 as it is to what the linker initializes + // type_ in static mutexes. critical_section_ will be initialized lazily + // in ThreadSafeLazyInit(). + enum StaticConstructorSelector { kStaticMutex = 0 }; + + // This constructor intentionally does nothing. It relies on type_ being + // statically initialized to 0 (effectively setting it to kStatic) and on + // ThreadSafeLazyInit() to lazily initialize the rest of the members. + explicit Mutex(StaticConstructorSelector /*dummy*/) {} + + Mutex(); + ~Mutex(); + + void Lock(); + + void Unlock(); + + // Does nothing if the current thread holds the mutex. Otherwise, crashes + // with high probability. + void AssertHeld(); + + private: + // Initializes owner_thread_id_ and critical_section_ in static mutexes. + void ThreadSafeLazyInit(); + + // Per https://blogs.msdn.microsoft.com/oldnewthing/20040223-00/?p=40503, + // we assume that 0 is an invalid value for thread IDs. + unsigned int owner_thread_id_; + + // For static mutexes, we rely on these members being initialized to zeros + // by the linker. + MutexType type_; + long critical_section_init_phase_; // NOLINT + GTEST_CRITICAL_SECTION* critical_section_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); +}; + +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::Mutex mutex + +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::Mutex mutex(::testing::internal::Mutex::kStaticMutex) + +// We cannot name this class MutexLock because the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex* mutex) + : mutex_(mutex) { mutex_->Lock(); } + + ~GTestMutexLock() { mutex_->Unlock(); } + + private: + Mutex* const mutex_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock); +}; + +typedef GTestMutexLock MutexLock; + +// Base class for ValueHolder<T>. Allows a caller to hold and delete a value +// without knowing its type. +class ThreadLocalValueHolderBase { + public: + virtual ~ThreadLocalValueHolderBase() {} +}; + +// Provides a way for a thread to send notifications to a ThreadLocal +// regardless of its parameter type. +class ThreadLocalBase { + public: + // Creates a new ValueHolder<T> object holding a default value passed to + // this ThreadLocal<T>'s constructor and returns it. It is the caller's + // responsibility not to call this when the ThreadLocal<T> instance already + // has a value on the current thread. + virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const = 0; + + protected: + ThreadLocalBase() {} + virtual ~ThreadLocalBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocalBase); +}; + +// Maps a thread to a set of ThreadLocals that have values instantiated on that +// thread and notifies them when the thread exits. A ThreadLocal instance is +// expected to persist until all threads it has values on have terminated. +class GTEST_API_ ThreadLocalRegistry { + public: + // Registers thread_local_instance as having value on the current thread. + // Returns a value that can be used to identify the thread from other threads. + static ThreadLocalValueHolderBase* GetValueOnCurrentThread( + const ThreadLocalBase* thread_local_instance); + + // Invoked when a ThreadLocal instance is destroyed. + static void OnThreadLocalDestroyed( + const ThreadLocalBase* thread_local_instance); +}; + +class GTEST_API_ ThreadWithParamBase { + public: + void Join(); + + protected: + class Runnable { + public: + virtual ~Runnable() {} + virtual void Run() = 0; + }; + + ThreadWithParamBase(Runnable *runnable, Notification* thread_can_start); + virtual ~ThreadWithParamBase(); + + private: + AutoHandle thread_; +}; + +// Helper class for testing Google Test's multi-threading constructs. +template <typename T> +class ThreadWithParam : public ThreadWithParamBase { + public: + typedef void UserThreadFunc(T); + + ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start) + : ThreadWithParamBase(new RunnableImpl(func, param), thread_can_start) { + } + virtual ~ThreadWithParam() {} + + private: + class RunnableImpl : public Runnable { + public: + RunnableImpl(UserThreadFunc* func, T param) + : func_(func), + param_(param) { + } + virtual ~RunnableImpl() {} + virtual void Run() { + func_(param_); + } + + private: + UserThreadFunc* const func_; + const T param_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(RunnableImpl); + }; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); +}; + +// Implements thread-local storage on Windows systems. +// +// // Thread 1 +// ThreadLocal<int> tl(100); // 100 is the default value for each thread. +// +// // Thread 2 +// tl.set(150); // Changes the value for thread 2 only. +// EXPECT_EQ(150, tl.get()); +// +// // Thread 1 +// EXPECT_EQ(100, tl.get()); // In thread 1, tl has the original value. +// tl.set(200); +// EXPECT_EQ(200, tl.get()); +// +// The template type argument T must have a public copy constructor. +// In addition, the default ThreadLocal constructor requires T to have +// a public default constructor. +// +// The users of a TheadLocal instance have to make sure that all but one +// threads (including the main one) using that instance have exited before +// destroying it. Otherwise, the per-thread objects managed for them by the +// ThreadLocal instance are not guaranteed to be destroyed on all platforms. +// +// Google Test only uses global ThreadLocal objects. That means they +// will die after main() has returned. Therefore, no per-thread +// object managed by Google Test will be leaked as long as all threads +// using Google Test have exited when main() returns. +template <typename T> +class ThreadLocal : public ThreadLocalBase { + public: + ThreadLocal() : default_factory_(new DefaultValueHolderFactory()) {} + explicit ThreadLocal(const T& value) + : default_factory_(new InstanceValueHolderFactory(value)) {} + + ~ThreadLocal() { ThreadLocalRegistry::OnThreadLocalDestroyed(this); } + + T* pointer() { return GetOrCreateValue(); } + const T* pointer() const { return GetOrCreateValue(); } + const T& get() const { return *pointer(); } + void set(const T& value) { *pointer() = value; } + + private: + // Holds a value of T. Can be deleted via its base class without the caller + // knowing the type of T. + class ValueHolder : public ThreadLocalValueHolderBase { + public: + ValueHolder() : value_() {} + explicit ValueHolder(const T& value) : value_(value) {} + + T* pointer() { return &value_; } + + private: + T value_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder); + }; + + + T* GetOrCreateValue() const { + return static_cast<ValueHolder*>( + ThreadLocalRegistry::GetValueOnCurrentThread(this))->pointer(); + } + + virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const { + return default_factory_->MakeNewHolder(); + } + + class ValueHolderFactory { + public: + ValueHolderFactory() {} + virtual ~ValueHolderFactory() {} + virtual ValueHolder* MakeNewHolder() const = 0; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory); + }; + + class DefaultValueHolderFactory : public ValueHolderFactory { + public: + DefaultValueHolderFactory() {} + ValueHolder* MakeNewHolder() const override { return new ValueHolder(); } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory); + }; + + class InstanceValueHolderFactory : public ValueHolderFactory { + public: + explicit InstanceValueHolderFactory(const T& value) : value_(value) {} + ValueHolder* MakeNewHolder() const override { + return new ValueHolder(value_); + } + + private: + const T value_; // The value for each thread. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory); + }; + + std::unique_ptr<ValueHolderFactory> default_factory_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); +}; + +# elif GTEST_HAS_PTHREAD + +// MutexBase and Mutex implement mutex on pthreads-based platforms. +class MutexBase { + public: + // Acquires this mutex. + void Lock() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_lock(&mutex_)); + owner_ = pthread_self(); + has_owner_ = true; + } + + // Releases this mutex. + void Unlock() { + // Since the lock is being released the owner_ field should no longer be + // considered valid. We don't protect writing to has_owner_ here, as it's + // the caller's responsibility to ensure that the current thread holds the + // mutex when this is called. + has_owner_ = false; + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&mutex_)); + } + + // Does nothing if the current thread holds the mutex. Otherwise, crashes + // with high probability. + void AssertHeld() const { + GTEST_CHECK_(has_owner_ && pthread_equal(owner_, pthread_self())) + << "The current thread is not holding the mutex @" << this; + } + + // A static mutex may be used before main() is entered. It may even + // be used before the dynamic initialization stage. Therefore we + // must be able to initialize a static mutex object at link time. + // This means MutexBase has to be a POD and its member variables + // have to be public. + public: + pthread_mutex_t mutex_; // The underlying pthread mutex. + // has_owner_ indicates whether the owner_ field below contains a valid thread + // ID and is therefore safe to inspect (e.g., to use in pthread_equal()). All + // accesses to the owner_ field should be protected by a check of this field. + // An alternative might be to memset() owner_ to all zeros, but there's no + // guarantee that a zero'd pthread_t is necessarily invalid or even different + // from pthread_self(). + bool has_owner_; + pthread_t owner_; // The thread holding the mutex. +}; + +// Forward-declares a static mutex. +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::MutexBase mutex + +// Defines and statically (i.e. at link time) initializes a static mutex. +// The initialization list here does not explicitly initialize each field, +// instead relying on default initialization for the unspecified fields. In +// particular, the owner_ field (a pthread_t) is not explicitly initialized. +// This allows initialization to work whether pthread_t is a scalar or struct. +// The flag -Wmissing-field-initializers must not be specified for this to work. +#define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::MutexBase mutex = {PTHREAD_MUTEX_INITIALIZER, false, 0} + +// The Mutex class can only be used for mutexes created at runtime. It +// shares its API with MutexBase otherwise. +class Mutex : public MutexBase { + public: + Mutex() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, nullptr)); + has_owner_ = false; + } + ~Mutex() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&mutex_)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); +}; + +// We cannot name this class MutexLock because the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(MutexBase* mutex) + : mutex_(mutex) { mutex_->Lock(); } + + ~GTestMutexLock() { mutex_->Unlock(); } + + private: + MutexBase* const mutex_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock); +}; + +typedef GTestMutexLock MutexLock; + +// Helpers for ThreadLocal. + +// pthread_key_create() requires DeleteThreadLocalValue() to have +// C-linkage. Therefore it cannot be templatized to access +// ThreadLocal<T>. Hence the need for class +// ThreadLocalValueHolderBase. +class ThreadLocalValueHolderBase { + public: + virtual ~ThreadLocalValueHolderBase() {} +}; + +// Called by pthread to delete thread-local data stored by +// pthread_setspecific(). +extern "C" inline void DeleteThreadLocalValue(void* value_holder) { + delete static_cast<ThreadLocalValueHolderBase*>(value_holder); +} + +// Implements thread-local storage on pthreads-based systems. +template <typename T> +class GTEST_API_ ThreadLocal { + public: + ThreadLocal() + : key_(CreateKey()), default_factory_(new DefaultValueHolderFactory()) {} + explicit ThreadLocal(const T& value) + : key_(CreateKey()), + default_factory_(new InstanceValueHolderFactory(value)) {} + + ~ThreadLocal() { + // Destroys the managed object for the current thread, if any. + DeleteThreadLocalValue(pthread_getspecific(key_)); + + // Releases resources associated with the key. This will *not* + // delete managed objects for other threads. + GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_)); + } + + T* pointer() { return GetOrCreateValue(); } + const T* pointer() const { return GetOrCreateValue(); } + const T& get() const { return *pointer(); } + void set(const T& value) { *pointer() = value; } + + private: + // Holds a value of type T. + class ValueHolder : public ThreadLocalValueHolderBase { + public: + ValueHolder() : value_() {} + explicit ValueHolder(const T& value) : value_(value) {} + + T* pointer() { return &value_; } + + private: + T value_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder); + }; + + static pthread_key_t CreateKey() { + pthread_key_t key; + // When a thread exits, DeleteThreadLocalValue() will be called on + // the object managed for that thread. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_key_create(&key, &DeleteThreadLocalValue)); + return key; + } + + T* GetOrCreateValue() const { + ThreadLocalValueHolderBase* const holder = + static_cast<ThreadLocalValueHolderBase*>(pthread_getspecific(key_)); + if (holder != nullptr) { + return CheckedDowncastToActualType<ValueHolder>(holder)->pointer(); + } + + ValueHolder* const new_holder = default_factory_->MakeNewHolder(); + ThreadLocalValueHolderBase* const holder_base = new_holder; + GTEST_CHECK_POSIX_SUCCESS_(pthread_setspecific(key_, holder_base)); + return new_holder->pointer(); + } + + class ValueHolderFactory { + public: + ValueHolderFactory() {} + virtual ~ValueHolderFactory() {} + virtual ValueHolder* MakeNewHolder() const = 0; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory); + }; + + class DefaultValueHolderFactory : public ValueHolderFactory { + public: + DefaultValueHolderFactory() {} + ValueHolder* MakeNewHolder() const override { return new ValueHolder(); } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory); + }; + + class InstanceValueHolderFactory : public ValueHolderFactory { + public: + explicit InstanceValueHolderFactory(const T& value) : value_(value) {} + ValueHolder* MakeNewHolder() const override { + return new ValueHolder(value_); + } + + private: + const T value_; // The value for each thread. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory); + }; + + // A key pthreads uses for looking up per-thread values. + const pthread_key_t key_; + std::unique_ptr<ValueHolderFactory> default_factory_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); +}; + +# endif // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ + +#else // GTEST_IS_THREADSAFE + +// A dummy implementation of synchronization primitives (mutex, lock, +// and thread-local variable). Necessary for compiling Google Test where +// mutex is not supported - using Google Test in multiple threads is not +// supported on such platforms. + +class Mutex { + public: + Mutex() {} + void Lock() {} + void Unlock() {} + void AssertHeld() const {} +}; + +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::Mutex mutex + +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex + +// We cannot name this class MutexLock because the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. That macro is used as a defensive measure to prevent against +// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than +// "MutexLock l(&mu)". Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex*) {} // NOLINT +}; + +typedef GTestMutexLock MutexLock; + +template <typename T> +class GTEST_API_ ThreadLocal { + public: + ThreadLocal() : value_() {} + explicit ThreadLocal(const T& value) : value_(value) {} + T* pointer() { return &value_; } + const T* pointer() const { return &value_; } + const T& get() const { return value_; } + void set(const T& value) { value_ = value; } + private: + T value_; +}; + +#endif // GTEST_IS_THREADSAFE + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +GTEST_API_ size_t GetThreadCount(); + +#if GTEST_OS_WINDOWS +# define GTEST_PATH_SEP_ "\\" +# define GTEST_HAS_ALT_PATH_SEP_ 1 +#else +# define GTEST_PATH_SEP_ "/" +# define GTEST_HAS_ALT_PATH_SEP_ 0 +#endif // GTEST_OS_WINDOWS + +// Utilities for char. + +// isspace(int ch) and friends accept an unsigned char or EOF. char +// may be signed, depending on the compiler (or compiler flags). +// Therefore we need to cast a char to unsigned char before calling +// isspace(), etc. + +inline bool IsAlpha(char ch) { + return isalpha(static_cast<unsigned char>(ch)) != 0; +} +inline bool IsAlNum(char ch) { + return isalnum(static_cast<unsigned char>(ch)) != 0; +} +inline bool IsDigit(char ch) { + return isdigit(static_cast<unsigned char>(ch)) != 0; +} +inline bool IsLower(char ch) { + return islower(static_cast<unsigned char>(ch)) != 0; +} +inline bool IsSpace(char ch) { + return isspace(static_cast<unsigned char>(ch)) != 0; +} +inline bool IsUpper(char ch) { + return isupper(static_cast<unsigned char>(ch)) != 0; +} +inline bool IsXDigit(char ch) { + return isxdigit(static_cast<unsigned char>(ch)) != 0; +} +#ifdef __cpp_char8_t +inline bool IsXDigit(char8_t ch) { + return isxdigit(static_cast<unsigned char>(ch)) != 0; +} +#endif +inline bool IsXDigit(char16_t ch) { + const unsigned char low_byte = static_cast<unsigned char>(ch); + return ch == low_byte && isxdigit(low_byte) != 0; +} +inline bool IsXDigit(char32_t ch) { + const unsigned char low_byte = static_cast<unsigned char>(ch); + return ch == low_byte && isxdigit(low_byte) != 0; +} +inline bool IsXDigit(wchar_t ch) { + const unsigned char low_byte = static_cast<unsigned char>(ch); + return ch == low_byte && isxdigit(low_byte) != 0; +} + +inline char ToLower(char ch) { + return static_cast<char>(tolower(static_cast<unsigned char>(ch))); +} +inline char ToUpper(char ch) { + return static_cast<char>(toupper(static_cast<unsigned char>(ch))); +} + +inline std::string StripTrailingSpaces(std::string str) { + std::string::iterator it = str.end(); + while (it != str.begin() && IsSpace(*--it)) + it = str.erase(it); + return str; +} + +// The testing::internal::posix namespace holds wrappers for common +// POSIX functions. These wrappers hide the differences between +// Windows/MSVC and POSIX systems. Since some compilers define these +// standard functions as macros, the wrapper cannot have the same name +// as the wrapped function. + +namespace posix { + +// Functions with a different name on Windows. + +#if GTEST_OS_WINDOWS + +typedef struct _stat StatStruct; + +# ifdef __BORLANDC__ +inline int DoIsATTY(int fd) { return isatty(fd); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return stricmp(s1, s2); +} +inline char* StrDup(const char* src) { return strdup(src); } +# else // !__BORLANDC__ +# if GTEST_OS_WINDOWS_MOBILE +inline int DoIsATTY(int /* fd */) { return 0; } +# else +inline int DoIsATTY(int fd) { return _isatty(fd); } +# endif // GTEST_OS_WINDOWS_MOBILE +inline int StrCaseCmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} +inline char* StrDup(const char* src) { return _strdup(src); } +# endif // __BORLANDC__ + +# if GTEST_OS_WINDOWS_MOBILE +inline int FileNo(FILE* file) { return reinterpret_cast<int>(_fileno(file)); } +// Stat(), RmDir(), and IsDir() are not needed on Windows CE at this +// time and thus not defined there. +# else +inline int FileNo(FILE* file) { return _fileno(file); } +inline int Stat(const char* path, StatStruct* buf) { return _stat(path, buf); } +inline int RmDir(const char* dir) { return _rmdir(dir); } +inline bool IsDir(const StatStruct& st) { + return (_S_IFDIR & st.st_mode) != 0; +} +# endif // GTEST_OS_WINDOWS_MOBILE + +#elif GTEST_OS_ESP8266 +typedef struct stat StatStruct; + +inline int FileNo(FILE* file) { return fileno(file); } +inline int DoIsATTY(int fd) { return isatty(fd); } +inline int Stat(const char* path, StatStruct* buf) { + // stat function not implemented on ESP8266 + return 0; +} +inline int StrCaseCmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} +inline char* StrDup(const char* src) { return strdup(src); } +inline int RmDir(const char* dir) { return rmdir(dir); } +inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } + +#else + +typedef struct stat StatStruct; + +inline int FileNo(FILE* file) { return fileno(file); } +inline int DoIsATTY(int fd) { return isatty(fd); } +inline int Stat(const char* path, StatStruct* buf) { return stat(path, buf); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} +inline char* StrDup(const char* src) { return strdup(src); } +inline int RmDir(const char* dir) { return rmdir(dir); } +inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } + +#endif // GTEST_OS_WINDOWS + +inline int IsATTY(int fd) { + // DoIsATTY might change errno (for example ENOTTY in case you redirect stdout + // to a file on Linux), which is unexpected, so save the previous value, and + // restore it after the call. + int savedErrno = errno; + int isAttyValue = DoIsATTY(fd); + errno = savedErrno; + + return isAttyValue; +} + +// Functions deprecated by MSVC 8.0. + +GTEST_DISABLE_MSC_DEPRECATED_PUSH_() + +// ChDir(), FReopen(), FDOpen(), Read(), Write(), Close(), and +// StrError() aren't needed on Windows CE at this time and thus not +// defined there. + +#if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && \ + !GTEST_OS_WINDOWS_RT && !GTEST_OS_ESP8266 && !GTEST_OS_XTENSA +inline int ChDir(const char* dir) { return chdir(dir); } +#endif +inline FILE* FOpen(const char* path, const char* mode) { +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW + struct wchar_codecvt : public std::codecvt<wchar_t, char, std::mbstate_t> {}; + std::wstring_convert<wchar_codecvt> converter; + std::wstring wide_path = converter.from_bytes(path); + std::wstring wide_mode = converter.from_bytes(mode); + return _wfopen(wide_path.c_str(), wide_mode.c_str()); +#else // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW + return fopen(path, mode); +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW +} +#if !GTEST_OS_WINDOWS_MOBILE +inline FILE *FReopen(const char* path, const char* mode, FILE* stream) { + return freopen(path, mode, stream); +} +inline FILE* FDOpen(int fd, const char* mode) { return fdopen(fd, mode); } +#endif +inline int FClose(FILE* fp) { return fclose(fp); } +#if !GTEST_OS_WINDOWS_MOBILE +inline int Read(int fd, void* buf, unsigned int count) { + return static_cast<int>(read(fd, buf, count)); +} +inline int Write(int fd, const void* buf, unsigned int count) { + return static_cast<int>(write(fd, buf, count)); +} +inline int Close(int fd) { return close(fd); } +inline const char* StrError(int errnum) { return strerror(errnum); } +#endif +inline const char* GetEnv(const char* name) { +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || \ + GTEST_OS_WINDOWS_RT || GTEST_OS_ESP8266 || GTEST_OS_XTENSA + // We are on an embedded platform, which has no environment variables. + static_cast<void>(name); // To prevent 'unused argument' warning. + return nullptr; +#elif defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9) + // Environment variables which we programmatically clear will be set to the + // empty string rather than unset (NULL). Handle that case. + const char* const env = getenv(name); + return (env != nullptr && env[0] != '\0') ? env : nullptr; +#else + return getenv(name); +#endif +} + +GTEST_DISABLE_MSC_DEPRECATED_POP_() + +#if GTEST_OS_WINDOWS_MOBILE +// Windows CE has no C library. The abort() function is used in +// several places in Google Test. This implementation provides a reasonable +// imitation of standard behaviour. +[[noreturn]] void Abort(); +#else +[[noreturn]] inline void Abort() { abort(); } +#endif // GTEST_OS_WINDOWS_MOBILE + +} // namespace posix + +// MSVC "deprecates" snprintf and issues warnings wherever it is used. In +// order to avoid these warnings, we need to use _snprintf or _snprintf_s on +// MSVC-based platforms. We map the GTEST_SNPRINTF_ macro to the appropriate +// function in order to achieve that. We use macro definition here because +// snprintf is a variadic function. +#if _MSC_VER && !GTEST_OS_WINDOWS_MOBILE +// MSVC 2005 and above support variadic macros. +# define GTEST_SNPRINTF_(buffer, size, format, ...) \ + _snprintf_s(buffer, size, size, format, __VA_ARGS__) +#elif defined(_MSC_VER) +// Windows CE does not define _snprintf_s +# define GTEST_SNPRINTF_ _snprintf +#else +# define GTEST_SNPRINTF_ snprintf +#endif + +// The biggest signed integer type the compiler supports. +// +// long long is guaranteed to be at least 64-bits in C++11. +using BiggestInt = long long; // NOLINT + +// The maximum number a BiggestInt can represent. +constexpr BiggestInt kMaxBiggestInt = (std::numeric_limits<BiggestInt>::max)(); + +// This template class serves as a compile-time function from size to +// type. It maps a size in bytes to a primitive type with that +// size. e.g. +// +// TypeWithSize<4>::UInt +// +// is typedef-ed to be unsigned int (unsigned integer made up of 4 +// bytes). +// +// Such functionality should belong to STL, but I cannot find it +// there. +// +// Google Test uses this class in the implementation of floating-point +// comparison. +// +// For now it only handles UInt (unsigned int) as that's all Google Test +// needs. Other types can be easily added in the future if need +// arises. +template <size_t size> +class TypeWithSize { + public: + // This prevents the user from using TypeWithSize<N> with incorrect + // values of N. + using UInt = void; +}; + +// The specialization for size 4. +template <> +class TypeWithSize<4> { + public: + using Int = std::int32_t; + using UInt = std::uint32_t; +}; + +// The specialization for size 8. +template <> +class TypeWithSize<8> { + public: + using Int = std::int64_t; + using UInt = std::uint64_t; +}; + +// Integer types of known sizes. +using TimeInMillis = int64_t; // Represents time in milliseconds. + +// Utilities for command line flags and environment variables. + +// Macro for referencing flags. +#if !defined(GTEST_FLAG) +# define GTEST_FLAG(name) FLAGS_gtest_##name +#endif // !defined(GTEST_FLAG) + +#if !defined(GTEST_USE_OWN_FLAGFILE_FLAG_) +# define GTEST_USE_OWN_FLAGFILE_FLAG_ 1 +#endif // !defined(GTEST_USE_OWN_FLAGFILE_FLAG_) + +#if !defined(GTEST_DECLARE_bool_) +# define GTEST_FLAG_SAVER_ ::testing::internal::GTestFlagSaver + +// Macros for declaring flags. +# define GTEST_DECLARE_bool_(name) GTEST_API_ extern bool GTEST_FLAG(name) +# define GTEST_DECLARE_int32_(name) \ + GTEST_API_ extern std::int32_t GTEST_FLAG(name) +# define GTEST_DECLARE_string_(name) \ + GTEST_API_ extern ::std::string GTEST_FLAG(name) + +// Macros for defining flags. +# define GTEST_DEFINE_bool_(name, default_val, doc) \ + GTEST_API_ bool GTEST_FLAG(name) = (default_val) +# define GTEST_DEFINE_int32_(name, default_val, doc) \ + GTEST_API_ std::int32_t GTEST_FLAG(name) = (default_val) +# define GTEST_DEFINE_string_(name, default_val, doc) \ + GTEST_API_ ::std::string GTEST_FLAG(name) = (default_val) + +#endif // !defined(GTEST_DECLARE_bool_) + +// Thread annotations +#if !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_) +# define GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks) +# define GTEST_LOCK_EXCLUDED_(locks) +#endif // !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_) + +// Parses 'str' for a 32-bit signed integer. If successful, writes the result +// to *value and returns true; otherwise leaves *value unchanged and returns +// false. +GTEST_API_ bool ParseInt32(const Message& src_text, const char* str, + int32_t* value); + +// Parses a bool/int32_t/string from the environment variable +// corresponding to the given Google Test flag. +bool BoolFromGTestEnv(const char* flag, bool default_val); +GTEST_API_ int32_t Int32FromGTestEnv(const char* flag, int32_t default_val); +std::string OutputFlagAlsoCheckEnvVar(); +const char* StringFromGTestEnv(const char* flag, const char* default_val); + +} // namespace internal +} // namespace testing + +#if !defined(GTEST_INTERNAL_DEPRECATED) + +// Internal Macro to mark an API deprecated, for googletest usage only +// Usage: class GTEST_INTERNAL_DEPRECATED(message) MyClass or +// GTEST_INTERNAL_DEPRECATED(message) <return_type> myFunction(); Every usage of +// a deprecated entity will trigger a warning when compiled with +// `-Wdeprecated-declarations` option (clang, gcc, any __GNUC__ compiler). +// For msvc /W3 option will need to be used +// Note that for 'other' compilers this macro evaluates to nothing to prevent +// compilations errors. +#if defined(_MSC_VER) +#define GTEST_INTERNAL_DEPRECATED(message) __declspec(deprecated(message)) +#elif defined(__GNUC__) +#define GTEST_INTERNAL_DEPRECATED(message) __attribute__((deprecated(message))) +#else +#define GTEST_INTERNAL_DEPRECATED(message) +#endif + +#endif // !defined(GTEST_INTERNAL_DEPRECATED) + +#if GTEST_HAS_ABSL +// Always use absl::any for UniversalPrinter<> specializations if googletest +// is built with absl support. +#define GTEST_INTERNAL_HAS_ANY 1 +#include "absl/types/any.h" +namespace testing { +namespace internal { +using Any = ::absl::any; +} // namespace internal +} // namespace testing +#else +#ifdef __has_include +#if __has_include(<any>) && __cplusplus >= 201703L +// Otherwise for C++17 and higher use std::any for UniversalPrinter<> +// specializations. +#define GTEST_INTERNAL_HAS_ANY 1 +#include <any> +namespace testing { +namespace internal { +using Any = ::std::any; +} // namespace internal +} // namespace testing +// The case where absl is configured NOT to alias std::any is not +// supported. +#endif // __has_include(<any>) && __cplusplus >= 201703L +#endif // __has_include +#endif // GTEST_HAS_ABSL + +#if GTEST_HAS_ABSL +// Always use absl::optional for UniversalPrinter<> specializations if +// googletest is built with absl support. +#define GTEST_INTERNAL_HAS_OPTIONAL 1 +#include "absl/types/optional.h" +namespace testing { +namespace internal { +template <typename T> +using Optional = ::absl::optional<T>; +} // namespace internal +} // namespace testing +#else +#ifdef __has_include +#if __has_include(<optional>) && __cplusplus >= 201703L +// Otherwise for C++17 and higher use std::optional for UniversalPrinter<> +// specializations. +#define GTEST_INTERNAL_HAS_OPTIONAL 1 +#include <optional> +namespace testing { +namespace internal { +template <typename T> +using Optional = ::std::optional<T>; +} // namespace internal +} // namespace testing +// The case where absl is configured NOT to alias std::optional is not +// supported. +#endif // __has_include(<optional>) && __cplusplus >= 201703L +#endif // __has_include +#endif // GTEST_HAS_ABSL + +#if GTEST_HAS_ABSL +// Always use absl::string_view for Matcher<> specializations if googletest +// is built with absl support. +# define GTEST_INTERNAL_HAS_STRING_VIEW 1 +#include "absl/strings/string_view.h" +namespace testing { +namespace internal { +using StringView = ::absl::string_view; +} // namespace internal +} // namespace testing +#else +# ifdef __has_include +# if __has_include(<string_view>) && __cplusplus >= 201703L +// Otherwise for C++17 and higher use std::string_view for Matcher<> +// specializations. +# define GTEST_INTERNAL_HAS_STRING_VIEW 1 +#include <string_view> +namespace testing { +namespace internal { +using StringView = ::std::string_view; +} // namespace internal +} // namespace testing +// The case where absl is configured NOT to alias std::string_view is not +// supported. +# endif // __has_include(<string_view>) && __cplusplus >= 201703L +# endif // __has_include +#endif // GTEST_HAS_ABSL + +#if GTEST_HAS_ABSL +// Always use absl::variant for UniversalPrinter<> specializations if googletest +// is built with absl support. +#define GTEST_INTERNAL_HAS_VARIANT 1 +#include "absl/types/variant.h" +namespace testing { +namespace internal { +template <typename... T> +using Variant = ::absl::variant<T...>; +} // namespace internal +} // namespace testing +#else +#ifdef __has_include +#if __has_include(<variant>) && __cplusplus >= 201703L +// Otherwise for C++17 and higher use std::variant for UniversalPrinter<> +// specializations. +#define GTEST_INTERNAL_HAS_VARIANT 1 +#include <variant> +namespace testing { +namespace internal { +template <typename... T> +using Variant = ::std::variant<T...>; +} // namespace internal +} // namespace testing +// The case where absl is configured NOT to alias std::variant is not supported. +#endif // __has_include(<variant>) && __cplusplus >= 201703L +#endif // __has_include +#endif // GTEST_HAS_ABSL + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ + +#if GTEST_OS_LINUX +# include <stdlib.h> +# include <sys/types.h> +# include <sys/wait.h> +# include <unistd.h> +#endif // GTEST_OS_LINUX + +#if GTEST_HAS_EXCEPTIONS +# include <stdexcept> +#endif + +#include <ctype.h> +#include <float.h> +#include <string.h> +#include <cstdint> +#include <iomanip> +#include <limits> +#include <map> +#include <set> +#include <string> +#include <type_traits> +#include <vector> + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines the Message class. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ + +#include <limits> +#include <memory> +#include <sstream> + + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// Ensures that there is at least one operator<< in the global namespace. +// See Message& operator<<(...) below for why. +void operator<<(const testing::internal::Secret&, int); + +namespace testing { + +// The Message class works like an ostream repeater. +// +// Typical usage: +// +// 1. You stream a bunch of values to a Message object. +// It will remember the text in a stringstream. +// 2. Then you stream the Message object to an ostream. +// This causes the text in the Message to be streamed +// to the ostream. +// +// For example; +// +// testing::Message foo; +// foo << 1 << " != " << 2; +// std::cout << foo; +// +// will print "1 != 2". +// +// Message is not intended to be inherited from. In particular, its +// destructor is not virtual. +// +// Note that stringstream behaves differently in gcc and in MSVC. You +// can stream a NULL char pointer to it in the former, but not in the +// latter (it causes an access violation if you do). The Message +// class hides this difference by treating a NULL char pointer as +// "(null)". +class GTEST_API_ Message { + private: + // The type of basic IO manipulators (endl, ends, and flush) for + // narrow streams. + typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&); + + public: + // Constructs an empty Message. + Message(); + + // Copy constructor. + Message(const Message& msg) : ss_(new ::std::stringstream) { // NOLINT + *ss_ << msg.GetString(); + } + + // Constructs a Message from a C-string. + explicit Message(const char* str) : ss_(new ::std::stringstream) { + *ss_ << str; + } + + // Streams a non-pointer value to this object. + template <typename T> + inline Message& operator <<(const T& val) { + // Some libraries overload << for STL containers. These + // overloads are defined in the global namespace instead of ::std. + // + // C++'s symbol lookup rule (i.e. Koenig lookup) says that these + // overloads are visible in either the std namespace or the global + // namespace, but not other namespaces, including the testing + // namespace which Google Test's Message class is in. + // + // To allow STL containers (and other types that has a << operator + // defined in the global namespace) to be used in Google Test + // assertions, testing::Message must access the custom << operator + // from the global namespace. With this using declaration, + // overloads of << defined in the global namespace and those + // visible via Koenig lookup are both exposed in this function. + using ::operator <<; + *ss_ << val; + return *this; + } + + // Streams a pointer value to this object. + // + // This function is an overload of the previous one. When you + // stream a pointer to a Message, this definition will be used as it + // is more specialized. (The C++ Standard, section + // [temp.func.order].) If you stream a non-pointer, then the + // previous definition will be used. + // + // The reason for this overload is that streaming a NULL pointer to + // ostream is undefined behavior. Depending on the compiler, you + // may get "0", "(nil)", "(null)", or an access violation. To + // ensure consistent result across compilers, we always treat NULL + // as "(null)". + template <typename T> + inline Message& operator <<(T* const& pointer) { // NOLINT + if (pointer == nullptr) { + *ss_ << "(null)"; + } else { + *ss_ << pointer; + } + return *this; + } + + // Since the basic IO manipulators are overloaded for both narrow + // and wide streams, we have to provide this specialized definition + // of operator <<, even though its body is the same as the + // templatized version above. Without this definition, streaming + // endl or other basic IO manipulators to Message will confuse the + // compiler. + Message& operator <<(BasicNarrowIoManip val) { + *ss_ << val; + return *this; + } + + // Instead of 1/0, we want to see true/false for bool values. + Message& operator <<(bool b) { + return *this << (b ? "true" : "false"); + } + + // These two overloads allow streaming a wide C string to a Message + // using the UTF-8 encoding. + Message& operator <<(const wchar_t* wide_c_str); + Message& operator <<(wchar_t* wide_c_str); + +#if GTEST_HAS_STD_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator <<(const ::std::wstring& wstr); +#endif // GTEST_HAS_STD_WSTRING + + // Gets the text streamed to this object so far as an std::string. + // Each '\0' character in the buffer is replaced with "\\0". + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + std::string GetString() const; + + private: + // We'll hold the text streamed to this object here. + const std::unique_ptr< ::std::stringstream> ss_; + + // We declare (but don't implement) this to prevent the compiler + // from implementing the assignment operator. + void operator=(const Message&); +}; + +// Streams a Message to an ostream. +inline std::ostream& operator <<(std::ostream& os, const Message& sb) { + return os << sb.GetString(); +} + +namespace internal { + +// Converts a streamable value to an std::string. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". +template <typename T> +std::string StreamableToString(const T& streamable) { + return (Message() << streamable).GetString(); +} + +} // namespace internal +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Google Test filepath utilities +// +// This header file declares classes and functions used internally by +// Google Test. They are subject to change without notice. +// +// This file is #included in gtest/internal/gtest-internal.h. +// Do not include this header file separately! + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file declares the String class and functions used internally by +// Google Test. They are subject to change without notice. They should not used +// by code external to Google Test. +// +// This header file is #included by gtest-internal.h. +// It should not be #included by other files. + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ + +#ifdef __BORLANDC__ +// string.h is not guaranteed to provide strcpy on C++ Builder. +# include <mem.h> +#endif + +#include <string.h> +#include <cstdint> +#include <string> + + +namespace testing { +namespace internal { + +// String - an abstract class holding static string utilities. +class GTEST_API_ String { + public: + // Static utility methods + + // Clones a 0-terminated C string, allocating memory using new. The + // caller is responsible for deleting the return value using + // delete[]. Returns the cloned string, or NULL if the input is + // NULL. + // + // This is different from strdup() in string.h, which allocates + // memory using malloc(). + static const char* CloneCString(const char* c_str); + +#if GTEST_OS_WINDOWS_MOBILE + // Windows CE does not have the 'ANSI' versions of Win32 APIs. To be + // able to pass strings to Win32 APIs on CE we need to convert them + // to 'Unicode', UTF-16. + + // Creates a UTF-16 wide string from the given ANSI string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the wide string, or NULL if the + // input is NULL. + // + // The wide string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static LPCWSTR AnsiToUtf16(const char* c_str); + + // Creates an ANSI string from the given wide string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the ANSI string, or NULL if the + // input is NULL. + // + // The returned string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static const char* Utf16ToAnsi(LPCWSTR utf16_str); +#endif + + // Compares two C strings. Returns true if and only if they have the same + // content. + // + // Unlike strcmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CStringEquals(const char* lhs, const char* rhs); + + // Converts a wide C string to a String using the UTF-8 encoding. + // NULL will be converted to "(null)". If an error occurred during + // the conversion, "(failed to convert from wide string)" is + // returned. + static std::string ShowWideCString(const wchar_t* wide_c_str); + + // Compares two wide C strings. Returns true if and only if they have the + // same content. + // + // Unlike wcscmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs); + + // Compares two C strings, ignoring case. Returns true if and only if + // they have the same content. + // + // Unlike strcasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CaseInsensitiveCStringEquals(const char* lhs, + const char* rhs); + + // Compares two wide C strings, ignoring case. Returns true if and only if + // they have the same content. + // + // Unlike wcscasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL wide C string, + // including the empty string. + // NB: The implementations on different platforms slightly differ. + // On windows, this method uses _wcsicmp which compares according to LC_CTYPE + // environment variable. On GNU platform this method uses wcscasecmp + // which compares according to LC_CTYPE category of the current locale. + // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the + // current locale. + static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs); + + // Returns true if and only if the given string ends with the given suffix, + // ignoring case. Any string is considered to end with an empty suffix. + static bool EndsWithCaseInsensitive( + const std::string& str, const std::string& suffix); + + // Formats an int value as "%02d". + static std::string FormatIntWidth2(int value); // "%02d" for width == 2 + + // Formats an int value to given width with leading zeros. + static std::string FormatIntWidthN(int value, int width); + + // Formats an int value as "%X". + static std::string FormatHexInt(int value); + + // Formats an int value as "%X". + static std::string FormatHexUInt32(uint32_t value); + + // Formats a byte as "%02X". + static std::string FormatByte(unsigned char value); + + private: + String(); // Not meant to be instantiated. +}; // class String + +// Gets the content of the stringstream's buffer as an std::string. Each '\0' +// character in the buffer is replaced with "\\0". +GTEST_API_ std::string StringStreamToString(::std::stringstream* stream); + +} // namespace internal +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { +namespace internal { + +// FilePath - a class for file and directory pathname manipulation which +// handles platform-specific conventions (like the pathname separator). +// Used for helper functions for naming files in a directory for xml output. +// Except for Set methods, all methods are const or static, which provides an +// "immutable value object" -- useful for peace of mind. +// A FilePath with a value ending in a path separator ("like/this/") represents +// a directory, otherwise it is assumed to represent a file. In either case, +// it may or may not represent an actual file or directory in the file system. +// Names are NOT checked for syntax correctness -- no checking for illegal +// characters, malformed paths, etc. + +class GTEST_API_ FilePath { + public: + FilePath() : pathname_("") { } + FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { } + + explicit FilePath(const std::string& pathname) : pathname_(pathname) { + Normalize(); + } + + FilePath& operator=(const FilePath& rhs) { + Set(rhs); + return *this; + } + + void Set(const FilePath& rhs) { + pathname_ = rhs.pathname_; + } + + const std::string& string() const { return pathname_; } + const char* c_str() const { return pathname_.c_str(); } + + // Returns the current working directory, or "" if unsuccessful. + static FilePath GetCurrentDir(); + + // Given directory = "dir", base_name = "test", number = 0, + // extension = "xml", returns "dir/test.xml". If number is greater + // than zero (e.g., 12), returns "dir/test_12.xml". + // On Windows platform, uses \ as the separator rather than /. + static FilePath MakeFileName(const FilePath& directory, + const FilePath& base_name, + int number, + const char* extension); + + // Given directory = "dir", relative_path = "test.xml", + // returns "dir/test.xml". + // On Windows, uses \ as the separator rather than /. + static FilePath ConcatPaths(const FilePath& directory, + const FilePath& relative_path); + + // Returns a pathname for a file that does not currently exist. The pathname + // will be directory/base_name.extension or + // directory/base_name_<number>.extension if directory/base_name.extension + // already exists. The number will be incremented until a pathname is found + // that does not already exist. + // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. + // There could be a race condition if two or more processes are calling this + // function at the same time -- they could both pick the same filename. + static FilePath GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension); + + // Returns true if and only if the path is "". + bool IsEmpty() const { return pathname_.empty(); } + + // If input name has a trailing separator character, removes it and returns + // the name, otherwise return the name string unmodified. + // On Windows platform, uses \ as the separator, other platforms use /. + FilePath RemoveTrailingPathSeparator() const; + + // Returns a copy of the FilePath with the directory part removed. + // Example: FilePath("path/to/file").RemoveDirectoryName() returns + // FilePath("file"). If there is no directory part ("just_a_file"), it returns + // the FilePath unmodified. If there is no file part ("just_a_dir/") it + // returns an empty FilePath (""). + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveDirectoryName() const; + + // RemoveFileName returns the directory path with the filename removed. + // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". + // If the FilePath is "a_file" or "/a_file", RemoveFileName returns + // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does + // not have a file, like "just/a/dir/", it returns the FilePath unmodified. + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveFileName() const; + + // Returns a copy of the FilePath with the case-insensitive extension removed. + // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns + // FilePath("dir/file"). If a case-insensitive extension is not + // found, returns a copy of the original FilePath. + FilePath RemoveExtension(const char* extension) const; + + // Creates directories so that path exists. Returns true if successful or if + // the directories already exist; returns false if unable to create + // directories for any reason. Will also return false if the FilePath does + // not represent a directory (that is, it doesn't end with a path separator). + bool CreateDirectoriesRecursively() const; + + // Create the directory so that path exists. Returns true if successful or + // if the directory already exists; returns false if unable to create the + // directory for any reason, including if the parent directory does not + // exist. Not named "CreateDirectory" because that's a macro on Windows. + bool CreateFolder() const; + + // Returns true if FilePath describes something in the file-system, + // either a file, directory, or whatever, and that something exists. + bool FileOrDirectoryExists() const; + + // Returns true if pathname describes a directory in the file-system + // that exists. + bool DirectoryExists() const; + + // Returns true if FilePath ends with a path separator, which indicates that + // it is intended to represent a directory. Returns false otherwise. + // This does NOT check that a directory (or file) actually exists. + bool IsDirectory() const; + + // Returns true if pathname describes a root directory. (Windows has one + // root directory per disk drive.) + bool IsRootDirectory() const; + + // Returns true if pathname describes an absolute path. + bool IsAbsolutePath() const; + + private: + // Replaces multiple consecutive separators with a single separator. + // For example, "bar///foo" becomes "bar/foo". Does not eliminate other + // redundancies that might be in a pathname involving "." or "..". + // + // A pathname with multiple consecutive separators may occur either through + // user error or as a result of some scripts or APIs that generate a pathname + // with a trailing separator. On other platforms the same API or script + // may NOT generate a pathname with a trailing "/". Then elsewhere that + // pathname may have another "/" and pathname components added to it, + // without checking for the separator already being there. + // The script language and operating system may allow paths like "foo//bar" + // but some of the functions in FilePath will not handle that correctly. In + // particular, RemoveTrailingPathSeparator() only removes one separator, and + // it is called in CreateDirectoriesRecursively() assuming that it will change + // a pathname from directory syntax (trailing separator) to filename syntax. + // + // On Windows this method also replaces the alternate path separator '/' with + // the primary path separator '\\', so that for example "bar\\/\\foo" becomes + // "bar\\foo". + + void Normalize(); + + // Returns a pointer to the last occurrence of a valid path separator in + // the FilePath. On Windows, for example, both '/' and '\' are valid path + // separators. Returns NULL if no path separator was found. + const char* FindLastPathSeparator() const; + + std::string pathname_; +}; // class FilePath + +} // namespace internal +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Type utilities needed for implementing typed and type-parameterized +// tests. + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + + +// #ifdef __GNUC__ is too general here. It is possible to use gcc without using +// libstdc++ (which is where cxxabi.h comes from). +# if GTEST_HAS_CXXABI_H_ +# include <cxxabi.h> +# elif defined(__HP_aCC) +# include <acxx_demangle.h> +# endif // GTEST_HASH_CXXABI_H_ + +namespace testing { +namespace internal { + +// Canonicalizes a given name with respect to the Standard C++ Library. +// This handles removing the inline namespace within `std` that is +// used by various standard libraries (e.g., `std::__1`). Names outside +// of namespace std are returned unmodified. +inline std::string CanonicalizeForStdLibVersioning(std::string s) { + static const char prefix[] = "std::__"; + if (s.compare(0, strlen(prefix), prefix) == 0) { + std::string::size_type end = s.find("::", strlen(prefix)); + if (end != s.npos) { + // Erase everything between the initial `std` and the second `::`. + s.erase(strlen("std"), end - strlen("std")); + } + } + return s; +} + +#if GTEST_HAS_RTTI +// GetTypeName(const std::type_info&) returns a human-readable name of type T. +inline std::string GetTypeName(const std::type_info& type) { + const char* const name = type.name(); +#if GTEST_HAS_CXXABI_H_ || defined(__HP_aCC) + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. +#if GTEST_HAS_CXXABI_H_ + using abi::__cxa_demangle; +#endif // GTEST_HAS_CXXABI_H_ + char* const readable_name = __cxa_demangle(name, nullptr, nullptr, &status); + const std::string name_str(status == 0 ? readable_name : name); + free(readable_name); + return CanonicalizeForStdLibVersioning(name_str); +#else + return name; +#endif // GTEST_HAS_CXXABI_H_ || __HP_aCC +} +#endif // GTEST_HAS_RTTI + +// GetTypeName<T>() returns a human-readable name of type T if and only if +// RTTI is enabled, otherwise it returns a dummy type name. +// NB: This function is also used in Google Mock, so don't move it inside of +// the typed-test-only section below. +template <typename T> +std::string GetTypeName() { +#if GTEST_HAS_RTTI + return GetTypeName(typeid(T)); +#else + return "<type>"; +#endif // GTEST_HAS_RTTI +} + +// A unique type indicating an empty node +struct None {}; + +# define GTEST_TEMPLATE_ template <typename T> class + +// The template "selector" struct TemplateSel<Tmpl> is used to +// represent Tmpl, which must be a class template with one type +// parameter, as a type. TemplateSel<Tmpl>::Bind<T>::type is defined +// as the type Tmpl<T>. This allows us to actually instantiate the +// template "selected" by TemplateSel<Tmpl>. +// +// This trick is necessary for simulating typedef for class templates, +// which C++ doesn't support directly. +template <GTEST_TEMPLATE_ Tmpl> +struct TemplateSel { + template <typename T> + struct Bind { + typedef Tmpl<T> type; + }; +}; + +# define GTEST_BIND_(TmplSel, T) \ + TmplSel::template Bind<T>::type + +template <GTEST_TEMPLATE_ Head_, GTEST_TEMPLATE_... Tail_> +struct Templates { + using Head = TemplateSel<Head_>; + using Tail = Templates<Tail_...>; +}; + +template <GTEST_TEMPLATE_ Head_> +struct Templates<Head_> { + using Head = TemplateSel<Head_>; + using Tail = None; +}; + +// Tuple-like type lists +template <typename Head_, typename... Tail_> +struct Types { + using Head = Head_; + using Tail = Types<Tail_...>; +}; + +template <typename Head_> +struct Types<Head_> { + using Head = Head_; + using Tail = None; +}; + +// Helper metafunctions to tell apart a single type from types +// generated by ::testing::Types +template <typename... Ts> +struct ProxyTypeList { + using type = Types<Ts...>; +}; + +template <typename> +struct is_proxy_type_list : std::false_type {}; + +template <typename... Ts> +struct is_proxy_type_list<ProxyTypeList<Ts...>> : std::true_type {}; + +// Generator which conditionally creates type lists. +// It recognizes if a requested type list should be created +// and prevents creating a new type list nested within another one. +template <typename T> +struct GenerateTypeList { + private: + using proxy = typename std::conditional<is_proxy_type_list<T>::value, T, + ProxyTypeList<T>>::type; + + public: + using type = typename proxy::type; +}; + +} // namespace internal + +template <typename... Ts> +using Types = internal::ProxyTypeList<Ts...>; + +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + +// Due to C++ preprocessor weirdness, we need double indirection to +// concatenate two tokens when one of them is __LINE__. Writing +// +// foo ## __LINE__ +// +// will result in the token foo__LINE__, instead of foo followed by +// the current line number. For more details, see +// http://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.6 +#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar) +#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar + +// Stringifies its argument. +// Work around a bug in visual studio which doesn't accept code like this: +// +// #define GTEST_STRINGIFY_(name) #name +// #define MACRO(a, b, c) ... GTEST_STRINGIFY_(a) ... +// MACRO(, x, y) +// +// Complaining about the argument to GTEST_STRINGIFY_ being empty. +// This is allowed by the spec. +#define GTEST_STRINGIFY_HELPER_(name, ...) #name +#define GTEST_STRINGIFY_(...) GTEST_STRINGIFY_HELPER_(__VA_ARGS__, ) + +namespace proto2 { +class MessageLite; +} + +namespace testing { + +// Forward declarations. + +class AssertionResult; // Result of an assertion. +class Message; // Represents a failure message. +class Test; // Represents a test. +class TestInfo; // Information about a test. +class TestPartResult; // Result of a test part. +class UnitTest; // A collection of test suites. + +template <typename T> +::std::string PrintToString(const T& value); + +namespace internal { + +struct TraceInfo; // Information about a trace point. +class TestInfoImpl; // Opaque implementation of TestInfo +class UnitTestImpl; // Opaque implementation of UnitTest + +// The text used in failure messages to indicate the start of the +// stack trace. +GTEST_API_ extern const char kStackTraceMarker[]; + +// An IgnoredValue object can be implicitly constructed from ANY value. +class IgnoredValue { + struct Sink {}; + public: + // This constructor template allows any value to be implicitly + // converted to IgnoredValue. The object has no data member and + // doesn't try to remember anything about the argument. We + // deliberately omit the 'explicit' keyword in order to allow the + // conversion to be implicit. + // Disable the conversion if T already has a magical conversion operator. + // Otherwise we get ambiguity. + template <typename T, + typename std::enable_if<!std::is_convertible<T, Sink>::value, + int>::type = 0> + IgnoredValue(const T& /* ignored */) {} // NOLINT(runtime/explicit) +}; + +// Appends the user-supplied message to the Google-Test-generated message. +GTEST_API_ std::string AppendUserMessage( + const std::string& gtest_msg, const Message& user_msg); + +#if GTEST_HAS_EXCEPTIONS + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4275 \ +/* an exported class was derived from a class that was not exported */) + +// This exception is thrown by (and only by) a failed Google Test +// assertion when GTEST_FLAG(throw_on_failure) is true (if exceptions +// are enabled). We derive it from std::runtime_error, which is for +// errors presumably detectable only at run time. Since +// std::runtime_error inherits from std::exception, many testing +// frameworks know how to extract and print the message inside it. +class GTEST_API_ GoogleTestFailureException : public ::std::runtime_error { + public: + explicit GoogleTestFailureException(const TestPartResult& failure); +}; + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4275 + +#endif // GTEST_HAS_EXCEPTIONS + +namespace edit_distance { +// Returns the optimal edits to go from 'left' to 'right'. +// All edits cost the same, with replace having lower priority than +// add/remove. +// Simple implementation of the Wagner-Fischer algorithm. +// See http://en.wikipedia.org/wiki/Wagner-Fischer_algorithm +enum EditType { kMatch, kAdd, kRemove, kReplace }; +GTEST_API_ std::vector<EditType> CalculateOptimalEdits( + const std::vector<size_t>& left, const std::vector<size_t>& right); + +// Same as above, but the input is represented as strings. +GTEST_API_ std::vector<EditType> CalculateOptimalEdits( + const std::vector<std::string>& left, + const std::vector<std::string>& right); + +// Create a diff of the input strings in Unified diff format. +GTEST_API_ std::string CreateUnifiedDiff(const std::vector<std::string>& left, + const std::vector<std::string>& right, + size_t context = 2); + +} // namespace edit_distance + +// Calculate the diff between 'left' and 'right' and return it in unified diff +// format. +// If not null, stores in 'total_line_count' the total number of lines found +// in left + right. +GTEST_API_ std::string DiffStrings(const std::string& left, + const std::string& right, + size_t* total_line_count); + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// expected_expression: "foo" +// actual_expression: "bar" +// expected_value: "5" +// actual_value: "6" +// +// The ignoring_case parameter is true if and only if the assertion is a +// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// be inserted into the message. +GTEST_API_ AssertionResult EqFailure(const char* expected_expression, + const char* actual_expression, + const std::string& expected_value, + const std::string& actual_value, + bool ignoring_case); + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +GTEST_API_ std::string GetBoolAssertionFailureMessage( + const AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value); + +// This template class represents an IEEE floating-point number +// (either single-precision or double-precision, depending on the +// template parameters). +// +// The purpose of this class is to do more sophisticated number +// comparison. (Due to round-off error, etc, it's very unlikely that +// two floating-points will be equal exactly. Hence a naive +// comparison by the == operation often doesn't work.) +// +// Format of IEEE floating-point: +// +// The most-significant bit being the leftmost, an IEEE +// floating-point looks like +// +// sign_bit exponent_bits fraction_bits +// +// Here, sign_bit is a single bit that designates the sign of the +// number. +// +// For float, there are 8 exponent bits and 23 fraction bits. +// +// For double, there are 11 exponent bits and 52 fraction bits. +// +// More details can be found at +// http://en.wikipedia.org/wiki/IEEE_floating-point_standard. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +template <typename RawType> +class FloatingPoint { + public: + // Defines the unsigned integer type that has the same size as the + // floating point number. + typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits; + + // Constants. + + // # of bits in a number. + static const size_t kBitCount = 8*sizeof(RawType); + + // # of fraction bits in a number. + static const size_t kFractionBitCount = + std::numeric_limits<RawType>::digits - 1; + + // # of exponent bits in a number. + static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount; + + // The mask for the sign bit. + static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1); + + // The mask for the fraction bits. + static const Bits kFractionBitMask = + ~static_cast<Bits>(0) >> (kExponentBitCount + 1); + + // The mask for the exponent bits. + static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask); + + // How many ULP's (Units in the Last Place) we want to tolerate when + // comparing two numbers. The larger the value, the more error we + // allow. A 0 value means that two numbers must be exactly the same + // to be considered equal. + // + // The maximum error of a single floating-point operation is 0.5 + // units in the last place. On Intel CPU's, all floating-point + // calculations are done with 80-bit precision, while double has 64 + // bits. Therefore, 4 should be enough for ordinary use. + // + // See the following article for more details on ULP: + // http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + static const uint32_t kMaxUlps = 4; + + // Constructs a FloatingPoint from a raw floating-point number. + // + // On an Intel CPU, passing a non-normalized NAN (Not a Number) + // around may change its bits, although the new value is guaranteed + // to be also a NAN. Therefore, don't expect this constructor to + // preserve the bits in x when x is a NAN. + explicit FloatingPoint(const RawType& x) { u_.value_ = x; } + + // Static methods + + // Reinterprets a bit pattern as a floating-point number. + // + // This function is needed to test the AlmostEquals() method. + static RawType ReinterpretBits(const Bits bits) { + FloatingPoint fp(0); + fp.u_.bits_ = bits; + return fp.u_.value_; + } + + // Returns the floating-point number that represent positive infinity. + static RawType Infinity() { + return ReinterpretBits(kExponentBitMask); + } + + // Returns the maximum representable finite floating-point number. + static RawType Max(); + + // Non-static methods + + // Returns the bits that represents this number. + const Bits &bits() const { return u_.bits_; } + + // Returns the exponent bits of this number. + Bits exponent_bits() const { return kExponentBitMask & u_.bits_; } + + // Returns the fraction bits of this number. + Bits fraction_bits() const { return kFractionBitMask & u_.bits_; } + + // Returns the sign bit of this number. + Bits sign_bit() const { return kSignBitMask & u_.bits_; } + + // Returns true if and only if this is NAN (not a number). + bool is_nan() const { + // It's a NAN if the exponent bits are all ones and the fraction + // bits are not entirely zeros. + return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0); + } + + // Returns true if and only if this number is at most kMaxUlps ULP's away + // from rhs. In particular, this function: + // + // - returns false if either number is (or both are) NAN. + // - treats really large numbers as almost equal to infinity. + // - thinks +0.0 and -0.0 are 0 DLP's apart. + bool AlmostEquals(const FloatingPoint& rhs) const { + // The IEEE standard says that any comparison operation involving + // a NAN must return false. + if (is_nan() || rhs.is_nan()) return false; + + return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_) + <= kMaxUlps; + } + + private: + // The data type used to store the actual floating-point number. + union FloatingPointUnion { + RawType value_; // The raw floating-point number. + Bits bits_; // The bits that represent the number. + }; + + // Converts an integer from the sign-and-magnitude representation to + // the biased representation. More precisely, let N be 2 to the + // power of (kBitCount - 1), an integer x is represented by the + // unsigned number x + N. + // + // For instance, + // + // -N + 1 (the most negative number representable using + // sign-and-magnitude) is represented by 1; + // 0 is represented by N; and + // N - 1 (the biggest number representable using + // sign-and-magnitude) is represented by 2N - 1. + // + // Read http://en.wikipedia.org/wiki/Signed_number_representations + // for more details on signed number representations. + static Bits SignAndMagnitudeToBiased(const Bits &sam) { + if (kSignBitMask & sam) { + // sam represents a negative number. + return ~sam + 1; + } else { + // sam represents a positive number. + return kSignBitMask | sam; + } + } + + // Given two numbers in the sign-and-magnitude representation, + // returns the distance between them as an unsigned number. + static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1, + const Bits &sam2) { + const Bits biased1 = SignAndMagnitudeToBiased(sam1); + const Bits biased2 = SignAndMagnitudeToBiased(sam2); + return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1); + } + + FloatingPointUnion u_; +}; + +// We cannot use std::numeric_limits<T>::max() as it clashes with the max() +// macro defined by <windows.h>. +template <> +inline float FloatingPoint<float>::Max() { return FLT_MAX; } +template <> +inline double FloatingPoint<double>::Max() { return DBL_MAX; } + +// Typedefs the instances of the FloatingPoint template class that we +// care to use. +typedef FloatingPoint<float> Float; +typedef FloatingPoint<double> Double; + +// In order to catch the mistake of putting tests that use different +// test fixture classes in the same test suite, we need to assign +// unique IDs to fixture classes and compare them. The TypeId type is +// used to hold such IDs. The user should treat TypeId as an opaque +// type: the only operation allowed on TypeId values is to compare +// them for equality using the == operator. +typedef const void* TypeId; + +template <typename T> +class TypeIdHelper { + public: + // dummy_ must not have a const type. Otherwise an overly eager + // compiler (e.g. MSVC 7.1 & 8.0) may try to merge + // TypeIdHelper<T>::dummy_ for different Ts as an "optimization". + static bool dummy_; +}; + +template <typename T> +bool TypeIdHelper<T>::dummy_ = false; + +// GetTypeId<T>() returns the ID of type T. Different values will be +// returned for different types. Calling the function twice with the +// same type argument is guaranteed to return the same ID. +template <typename T> +TypeId GetTypeId() { + // The compiler is required to allocate a different + // TypeIdHelper<T>::dummy_ variable for each T used to instantiate + // the template. Therefore, the address of dummy_ is guaranteed to + // be unique. + return &(TypeIdHelper<T>::dummy_); +} + +// Returns the type ID of ::testing::Test. Always call this instead +// of GetTypeId< ::testing::Test>() to get the type ID of +// ::testing::Test, as the latter may give the wrong result due to a +// suspected linker bug when compiling Google Test as a Mac OS X +// framework. +GTEST_API_ TypeId GetTestTypeId(); + +// Defines the abstract factory interface that creates instances +// of a Test object. +class TestFactoryBase { + public: + virtual ~TestFactoryBase() {} + + // Creates a test instance to run. The instance is both created and destroyed + // within TestInfoImpl::Run() + virtual Test* CreateTest() = 0; + + protected: + TestFactoryBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestFactoryBase); +}; + +// This class provides implementation of TeastFactoryBase interface. +// It is used in TEST and TEST_F macros. +template <class TestClass> +class TestFactoryImpl : public TestFactoryBase { + public: + Test* CreateTest() override { return new TestClass; } +}; + +#if GTEST_OS_WINDOWS + +// Predicate-formatters for implementing the HRESULT checking macros +// {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED} +// We pass a long instead of HRESULT to avoid causing an +// include dependency for the HRESULT type. +GTEST_API_ AssertionResult IsHRESULTSuccess(const char* expr, + long hr); // NOLINT +GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr, + long hr); // NOLINT + +#endif // GTEST_OS_WINDOWS + +// Types of SetUpTestSuite() and TearDownTestSuite() functions. +using SetUpTestSuiteFunc = void (*)(); +using TearDownTestSuiteFunc = void (*)(); + +struct CodeLocation { + CodeLocation(const std::string& a_file, int a_line) + : file(a_file), line(a_line) {} + + std::string file; + int line; +}; + +// Helper to identify which setup function for TestCase / TestSuite to call. +// Only one function is allowed, either TestCase or TestSute but not both. + +// Utility functions to help SuiteApiResolver +using SetUpTearDownSuiteFuncType = void (*)(); + +inline SetUpTearDownSuiteFuncType GetNotDefaultOrNull( + SetUpTearDownSuiteFuncType a, SetUpTearDownSuiteFuncType def) { + return a == def ? nullptr : a; +} + +template <typename T> +// Note that SuiteApiResolver inherits from T because +// SetUpTestSuite()/TearDownTestSuite() could be protected. Ths way +// SuiteApiResolver can access them. +struct SuiteApiResolver : T { + // testing::Test is only forward declared at this point. So we make it a + // dependend class for the compiler to be OK with it. + using Test = + typename std::conditional<sizeof(T) != 0, ::testing::Test, void>::type; + + static SetUpTearDownSuiteFuncType GetSetUpCaseOrSuite(const char* filename, + int line_num) { +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + SetUpTearDownSuiteFuncType test_case_fp = + GetNotDefaultOrNull(&T::SetUpTestCase, &Test::SetUpTestCase); + SetUpTearDownSuiteFuncType test_suite_fp = + GetNotDefaultOrNull(&T::SetUpTestSuite, &Test::SetUpTestSuite); + + GTEST_CHECK_(!test_case_fp || !test_suite_fp) + << "Test can not provide both SetUpTestSuite and SetUpTestCase, please " + "make sure there is only one present at " + << filename << ":" << line_num; + + return test_case_fp != nullptr ? test_case_fp : test_suite_fp; +#else + (void)(filename); + (void)(line_num); + return &T::SetUpTestSuite; +#endif + } + + static SetUpTearDownSuiteFuncType GetTearDownCaseOrSuite(const char* filename, + int line_num) { +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + SetUpTearDownSuiteFuncType test_case_fp = + GetNotDefaultOrNull(&T::TearDownTestCase, &Test::TearDownTestCase); + SetUpTearDownSuiteFuncType test_suite_fp = + GetNotDefaultOrNull(&T::TearDownTestSuite, &Test::TearDownTestSuite); + + GTEST_CHECK_(!test_case_fp || !test_suite_fp) + << "Test can not provide both TearDownTestSuite and TearDownTestCase," + " please make sure there is only one present at" + << filename << ":" << line_num; + + return test_case_fp != nullptr ? test_case_fp : test_suite_fp; +#else + (void)(filename); + (void)(line_num); + return &T::TearDownTestSuite; +#endif + } +}; + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_suite_name: name of the test suite +// name: name of the test +// type_param: the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param: text representation of the test's value parameter, +// or NULL if this is not a type-parameterized test. +// code_location: code location where the test is defined +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test suite +// tear_down_tc: pointer to the function that tears down the test suite +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +GTEST_API_ TestInfo* MakeAndRegisterTestInfo( + const char* test_suite_name, const char* name, const char* type_param, + const char* value_param, CodeLocation code_location, + TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc, + TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory); + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +GTEST_API_ bool SkipPrefix(const char* prefix, const char** pstr); + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// State of the definition of a type-parameterized test suite. +class GTEST_API_ TypedTestSuitePState { + public: + TypedTestSuitePState() : registered_(false) {} + + // Adds the given test name to defined_test_names_ and return true + // if the test suite hasn't been registered; otherwise aborts the + // program. + bool AddTestName(const char* file, int line, const char* case_name, + const char* test_name) { + if (registered_) { + fprintf(stderr, + "%s Test %s must be defined before " + "REGISTER_TYPED_TEST_SUITE_P(%s, ...).\n", + FormatFileLocation(file, line).c_str(), test_name, case_name); + fflush(stderr); + posix::Abort(); + } + registered_tests_.insert( + ::std::make_pair(test_name, CodeLocation(file, line))); + return true; + } + + bool TestExists(const std::string& test_name) const { + return registered_tests_.count(test_name) > 0; + } + + const CodeLocation& GetCodeLocation(const std::string& test_name) const { + RegisteredTestsMap::const_iterator it = registered_tests_.find(test_name); + GTEST_CHECK_(it != registered_tests_.end()); + return it->second; + } + + // Verifies that registered_tests match the test names in + // defined_test_names_; returns registered_tests if successful, or + // aborts the program otherwise. + const char* VerifyRegisteredTestNames(const char* test_suite_name, + const char* file, int line, + const char* registered_tests); + + private: + typedef ::std::map<std::string, CodeLocation> RegisteredTestsMap; + + bool registered_; + RegisteredTestsMap registered_tests_; +}; + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +using TypedTestCasePState = TypedTestSuitePState; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +// Skips to the first non-space char after the first comma in 'str'; +// returns NULL if no comma is found in 'str'. +inline const char* SkipComma(const char* str) { + const char* comma = strchr(str, ','); + if (comma == nullptr) { + return nullptr; + } + while (IsSpace(*(++comma))) {} + return comma; +} + +// Returns the prefix of 'str' before the first comma in it; returns +// the entire string if it contains no comma. +inline std::string GetPrefixUntilComma(const char* str) { + const char* comma = strchr(str, ','); + return comma == nullptr ? str : std::string(str, comma); +} + +// Splits a given string on a given delimiter, populating a given +// vector with the fields. +void SplitString(const ::std::string& str, char delimiter, + ::std::vector< ::std::string>* dest); + +// The default argument to the template below for the case when the user does +// not provide a name generator. +struct DefaultNameGenerator { + template <typename T> + static std::string GetName(int i) { + return StreamableToString(i); + } +}; + +template <typename Provided = DefaultNameGenerator> +struct NameGeneratorSelector { + typedef Provided type; +}; + +template <typename NameGenerator> +void GenerateNamesRecursively(internal::None, std::vector<std::string>*, int) {} + +template <typename NameGenerator, typename Types> +void GenerateNamesRecursively(Types, std::vector<std::string>* result, int i) { + result->push_back(NameGenerator::template GetName<typename Types::Head>(i)); + GenerateNamesRecursively<NameGenerator>(typename Types::Tail(), result, + i + 1); +} + +template <typename NameGenerator, typename Types> +std::vector<std::string> GenerateNames() { + std::vector<std::string> result; + GenerateNamesRecursively<NameGenerator>(Types(), &result, 0); + return result; +} + +// TypeParameterizedTest<Fixture, TestSel, Types>::Register() +// registers a list of type-parameterized tests with Google Test. The +// return value is insignificant - we just need to return something +// such that we can call this function in a namespace scope. +// +// Implementation note: The GTEST_TEMPLATE_ macro declares a template +// template parameter. It's defined in gtest-type-util.h. +template <GTEST_TEMPLATE_ Fixture, class TestSel, typename Types> +class TypeParameterizedTest { + public: + // 'index' is the index of the test in the type list 'Types' + // specified in INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, TestSuite, + // Types). Valid values for 'index' are [0, N - 1] where N is the + // length of Types. + static bool Register(const char* prefix, const CodeLocation& code_location, + const char* case_name, const char* test_names, int index, + const std::vector<std::string>& type_names = + GenerateNames<DefaultNameGenerator, Types>()) { + typedef typename Types::Head Type; + typedef Fixture<Type> FixtureClass; + typedef typename GTEST_BIND_(TestSel, Type) TestClass; + + // First, registers the first type-parameterized test in the type + // list. + MakeAndRegisterTestInfo( + (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name + + "/" + type_names[static_cast<size_t>(index)]) + .c_str(), + StripTrailingSpaces(GetPrefixUntilComma(test_names)).c_str(), + GetTypeName<Type>().c_str(), + nullptr, // No value parameter. + code_location, GetTypeId<FixtureClass>(), + SuiteApiResolver<TestClass>::GetSetUpCaseOrSuite( + code_location.file.c_str(), code_location.line), + SuiteApiResolver<TestClass>::GetTearDownCaseOrSuite( + code_location.file.c_str(), code_location.line), + new TestFactoryImpl<TestClass>); + + // Next, recurses (at compile time) with the tail of the type list. + return TypeParameterizedTest<Fixture, TestSel, + typename Types::Tail>::Register(prefix, + code_location, + case_name, + test_names, + index + 1, + type_names); + } +}; + +// The base case for the compile time recursion. +template <GTEST_TEMPLATE_ Fixture, class TestSel> +class TypeParameterizedTest<Fixture, TestSel, internal::None> { + public: + static bool Register(const char* /*prefix*/, const CodeLocation&, + const char* /*case_name*/, const char* /*test_names*/, + int /*index*/, + const std::vector<std::string>& = + std::vector<std::string>() /*type_names*/) { + return true; + } +}; + +GTEST_API_ void RegisterTypeParameterizedTestSuite(const char* test_suite_name, + CodeLocation code_location); +GTEST_API_ void RegisterTypeParameterizedTestSuiteInstantiation( + const char* case_name); + +// TypeParameterizedTestSuite<Fixture, Tests, Types>::Register() +// registers *all combinations* of 'Tests' and 'Types' with Google +// Test. The return value is insignificant - we just need to return +// something such that we can call this function in a namespace scope. +template <GTEST_TEMPLATE_ Fixture, typename Tests, typename Types> +class TypeParameterizedTestSuite { + public: + static bool Register(const char* prefix, CodeLocation code_location, + const TypedTestSuitePState* state, const char* case_name, + const char* test_names, + const std::vector<std::string>& type_names = + GenerateNames<DefaultNameGenerator, Types>()) { + RegisterTypeParameterizedTestSuiteInstantiation(case_name); + std::string test_name = StripTrailingSpaces( + GetPrefixUntilComma(test_names)); + if (!state->TestExists(test_name)) { + fprintf(stderr, "Failed to get code location for test %s.%s at %s.", + case_name, test_name.c_str(), + FormatFileLocation(code_location.file.c_str(), + code_location.line).c_str()); + fflush(stderr); + posix::Abort(); + } + const CodeLocation& test_location = state->GetCodeLocation(test_name); + + typedef typename Tests::Head Head; + + // First, register the first test in 'Test' for each type in 'Types'. + TypeParameterizedTest<Fixture, Head, Types>::Register( + prefix, test_location, case_name, test_names, 0, type_names); + + // Next, recurses (at compile time) with the tail of the test list. + return TypeParameterizedTestSuite<Fixture, typename Tests::Tail, + Types>::Register(prefix, code_location, + state, case_name, + SkipComma(test_names), + type_names); + } +}; + +// The base case for the compile time recursion. +template <GTEST_TEMPLATE_ Fixture, typename Types> +class TypeParameterizedTestSuite<Fixture, internal::None, Types> { + public: + static bool Register(const char* /*prefix*/, const CodeLocation&, + const TypedTestSuitePState* /*state*/, + const char* /*case_name*/, const char* /*test_names*/, + const std::vector<std::string>& = + std::vector<std::string>() /*type_names*/) { + return true; + } +}; + +// Returns the current OS stack trace as an std::string. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +GTEST_API_ std::string GetCurrentOsStackTraceExceptTop( + UnitTest* unit_test, int skip_count); + +// Helpers for suppressing warnings on unreachable code or constant +// condition. + +// Always returns true. +GTEST_API_ bool AlwaysTrue(); + +// Always returns false. +inline bool AlwaysFalse() { return !AlwaysTrue(); } + +// Helper for suppressing false warning from Clang on a const char* +// variable declared in a conditional expression always being NULL in +// the else branch. +struct GTEST_API_ ConstCharPtr { + ConstCharPtr(const char* str) : value(str) {} + operator bool() const { return true; } + const char* value; +}; + +// Helper for declaring std::string within 'if' statement +// in pre C++17 build environment. +struct TrueWithString { + TrueWithString() = default; + explicit TrueWithString(const char* str) : value(str) {} + explicit TrueWithString(const std::string& str) : value(str) {} + explicit operator bool() const { return true; } + std::string value; +}; + +// A simple Linear Congruential Generator for generating random +// numbers with a uniform distribution. Unlike rand() and srand(), it +// doesn't use global state (and therefore can't interfere with user +// code). Unlike rand_r(), it's portable. An LCG isn't very random, +// but it's good enough for our purposes. +class GTEST_API_ Random { + public: + static const uint32_t kMaxRange = 1u << 31; + + explicit Random(uint32_t seed) : state_(seed) {} + + void Reseed(uint32_t seed) { state_ = seed; } + + // Generates a random number from [0, range). Crashes if 'range' is + // 0 or greater than kMaxRange. + uint32_t Generate(uint32_t range); + + private: + uint32_t state_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(Random); +}; + +// Turns const U&, U&, const U, and U all into U. +#define GTEST_REMOVE_REFERENCE_AND_CONST_(T) \ + typename std::remove_const<typename std::remove_reference<T>::type>::type + +// HasDebugStringAndShortDebugString<T>::value is a compile-time bool constant +// that's true if and only if T has methods DebugString() and ShortDebugString() +// that return std::string. +template <typename T> +class HasDebugStringAndShortDebugString { + private: + template <typename C> + static auto CheckDebugString(C*) -> typename std::is_same< + std::string, decltype(std::declval<const C>().DebugString())>::type; + template <typename> + static std::false_type CheckDebugString(...); + + template <typename C> + static auto CheckShortDebugString(C*) -> typename std::is_same< + std::string, decltype(std::declval<const C>().ShortDebugString())>::type; + template <typename> + static std::false_type CheckShortDebugString(...); + + using HasDebugStringType = decltype(CheckDebugString<T>(nullptr)); + using HasShortDebugStringType = decltype(CheckShortDebugString<T>(nullptr)); + + public: + static constexpr bool value = + HasDebugStringType::value && HasShortDebugStringType::value; +}; + +template <typename T> +constexpr bool HasDebugStringAndShortDebugString<T>::value; + +// When the compiler sees expression IsContainerTest<C>(0), if C is an +// STL-style container class, the first overload of IsContainerTest +// will be viable (since both C::iterator* and C::const_iterator* are +// valid types and NULL can be implicitly converted to them). It will +// be picked over the second overload as 'int' is a perfect match for +// the type of argument 0. If C::iterator or C::const_iterator is not +// a valid type, the first overload is not viable, and the second +// overload will be picked. Therefore, we can determine whether C is +// a container class by checking the type of IsContainerTest<C>(0). +// The value of the expression is insignificant. +// +// In C++11 mode we check the existence of a const_iterator and that an +// iterator is properly implemented for the container. +// +// For pre-C++11 that we look for both C::iterator and C::const_iterator. +// The reason is that C++ injects the name of a class as a member of the +// class itself (e.g. you can refer to class iterator as either +// 'iterator' or 'iterator::iterator'). If we look for C::iterator +// only, for example, we would mistakenly think that a class named +// iterator is an STL container. +// +// Also note that the simpler approach of overloading +// IsContainerTest(typename C::const_iterator*) and +// IsContainerTest(...) doesn't work with Visual Age C++ and Sun C++. +typedef int IsContainer; +template <class C, + class Iterator = decltype(::std::declval<const C&>().begin()), + class = decltype(::std::declval<const C&>().end()), + class = decltype(++::std::declval<Iterator&>()), + class = decltype(*::std::declval<Iterator>()), + class = typename C::const_iterator> +IsContainer IsContainerTest(int /* dummy */) { + return 0; +} + +typedef char IsNotContainer; +template <class C> +IsNotContainer IsContainerTest(long /* dummy */) { return '\0'; } + +// Trait to detect whether a type T is a hash table. +// The heuristic used is that the type contains an inner type `hasher` and does +// not contain an inner type `reverse_iterator`. +// If the container is iterable in reverse, then order might actually matter. +template <typename T> +struct IsHashTable { + private: + template <typename U> + static char test(typename U::hasher*, typename U::reverse_iterator*); + template <typename U> + static int test(typename U::hasher*, ...); + template <typename U> + static char test(...); + + public: + static const bool value = sizeof(test<T>(nullptr, nullptr)) == sizeof(int); +}; + +template <typename T> +const bool IsHashTable<T>::value; + +template <typename C, + bool = sizeof(IsContainerTest<C>(0)) == sizeof(IsContainer)> +struct IsRecursiveContainerImpl; + +template <typename C> +struct IsRecursiveContainerImpl<C, false> : public std::false_type {}; + +// Since the IsRecursiveContainerImpl depends on the IsContainerTest we need to +// obey the same inconsistencies as the IsContainerTest, namely check if +// something is a container is relying on only const_iterator in C++11 and +// is relying on both const_iterator and iterator otherwise +template <typename C> +struct IsRecursiveContainerImpl<C, true> { + using value_type = decltype(*std::declval<typename C::const_iterator>()); + using type = + std::is_same<typename std::remove_const< + typename std::remove_reference<value_type>::type>::type, + C>; +}; + +// IsRecursiveContainer<Type> is a unary compile-time predicate that +// evaluates whether C is a recursive container type. A recursive container +// type is a container type whose value_type is equal to the container type +// itself. An example for a recursive container type is +// boost::filesystem::path, whose iterator has a value_type that is equal to +// boost::filesystem::path. +template <typename C> +struct IsRecursiveContainer : public IsRecursiveContainerImpl<C>::type {}; + +// Utilities for native arrays. + +// ArrayEq() compares two k-dimensional native arrays using the +// elements' operator==, where k can be any integer >= 0. When k is +// 0, ArrayEq() degenerates into comparing a single pair of values. + +template <typename T, typename U> +bool ArrayEq(const T* lhs, size_t size, const U* rhs); + +// This generic version is used when k is 0. +template <typename T, typename U> +inline bool ArrayEq(const T& lhs, const U& rhs) { return lhs == rhs; } + +// This overload is used when k >= 1. +template <typename T, typename U, size_t N> +inline bool ArrayEq(const T(&lhs)[N], const U(&rhs)[N]) { + return internal::ArrayEq(lhs, N, rhs); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous ArrayEq() function, arrays with different sizes would +// lead to different copies of the template code. +template <typename T, typename U> +bool ArrayEq(const T* lhs, size_t size, const U* rhs) { + for (size_t i = 0; i != size; i++) { + if (!internal::ArrayEq(lhs[i], rhs[i])) + return false; + } + return true; +} + +// Finds the first element in the iterator range [begin, end) that +// equals elem. Element may be a native array type itself. +template <typename Iter, typename Element> +Iter ArrayAwareFind(Iter begin, Iter end, const Element& elem) { + for (Iter it = begin; it != end; ++it) { + if (internal::ArrayEq(*it, elem)) + return it; + } + return end; +} + +// CopyArray() copies a k-dimensional native array using the elements' +// operator=, where k can be any integer >= 0. When k is 0, +// CopyArray() degenerates into copying a single value. + +template <typename T, typename U> +void CopyArray(const T* from, size_t size, U* to); + +// This generic version is used when k is 0. +template <typename T, typename U> +inline void CopyArray(const T& from, U* to) { *to = from; } + +// This overload is used when k >= 1. +template <typename T, typename U, size_t N> +inline void CopyArray(const T(&from)[N], U(*to)[N]) { + internal::CopyArray(from, N, *to); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous CopyArray() function, arrays with different sizes +// would lead to different copies of the template code. +template <typename T, typename U> +void CopyArray(const T* from, size_t size, U* to) { + for (size_t i = 0; i != size; i++) { + internal::CopyArray(from[i], to + i); + } +} + +// The relation between an NativeArray object (see below) and the +// native array it represents. +// We use 2 different structs to allow non-copyable types to be used, as long +// as RelationToSourceReference() is passed. +struct RelationToSourceReference {}; +struct RelationToSourceCopy {}; + +// Adapts a native array to a read-only STL-style container. Instead +// of the complete STL container concept, this adaptor only implements +// members useful for Google Mock's container matchers. New members +// should be added as needed. To simplify the implementation, we only +// support Element being a raw type (i.e. having no top-level const or +// reference modifier). It's the client's responsibility to satisfy +// this requirement. Element can be an array type itself (hence +// multi-dimensional arrays are supported). +template <typename Element> +class NativeArray { + public: + // STL-style container typedefs. + typedef Element value_type; + typedef Element* iterator; + typedef const Element* const_iterator; + + // Constructs from a native array. References the source. + NativeArray(const Element* array, size_t count, RelationToSourceReference) { + InitRef(array, count); + } + + // Constructs from a native array. Copies the source. + NativeArray(const Element* array, size_t count, RelationToSourceCopy) { + InitCopy(array, count); + } + + // Copy constructor. + NativeArray(const NativeArray& rhs) { + (this->*rhs.clone_)(rhs.array_, rhs.size_); + } + + ~NativeArray() { + if (clone_ != &NativeArray::InitRef) + delete[] array_; + } + + // STL-style container methods. + size_t size() const { return size_; } + const_iterator begin() const { return array_; } + const_iterator end() const { return array_ + size_; } + bool operator==(const NativeArray& rhs) const { + return size() == rhs.size() && + ArrayEq(begin(), size(), rhs.begin()); + } + + private: + static_assert(!std::is_const<Element>::value, "Type must not be const"); + static_assert(!std::is_reference<Element>::value, + "Type must not be a reference"); + + // Initializes this object with a copy of the input. + void InitCopy(const Element* array, size_t a_size) { + Element* const copy = new Element[a_size]; + CopyArray(array, a_size, copy); + array_ = copy; + size_ = a_size; + clone_ = &NativeArray::InitCopy; + } + + // Initializes this object with a reference of the input. + void InitRef(const Element* array, size_t a_size) { + array_ = array; + size_ = a_size; + clone_ = &NativeArray::InitRef; + } + + const Element* array_; + size_t size_; + void (NativeArray::*clone_)(const Element*, size_t); +}; + +// Backport of std::index_sequence. +template <size_t... Is> +struct IndexSequence { + using type = IndexSequence; +}; + +// Double the IndexSequence, and one if plus_one is true. +template <bool plus_one, typename T, size_t sizeofT> +struct DoubleSequence; +template <size_t... I, size_t sizeofT> +struct DoubleSequence<true, IndexSequence<I...>, sizeofT> { + using type = IndexSequence<I..., (sizeofT + I)..., 2 * sizeofT>; +}; +template <size_t... I, size_t sizeofT> +struct DoubleSequence<false, IndexSequence<I...>, sizeofT> { + using type = IndexSequence<I..., (sizeofT + I)...>; +}; + +// Backport of std::make_index_sequence. +// It uses O(ln(N)) instantiation depth. +template <size_t N> +struct MakeIndexSequenceImpl + : DoubleSequence<N % 2 == 1, typename MakeIndexSequenceImpl<N / 2>::type, + N / 2>::type {}; + +template <> +struct MakeIndexSequenceImpl<0> : IndexSequence<> {}; + +template <size_t N> +using MakeIndexSequence = typename MakeIndexSequenceImpl<N>::type; + +template <typename... T> +using IndexSequenceFor = typename MakeIndexSequence<sizeof...(T)>::type; + +template <size_t> +struct Ignore { + Ignore(...); // NOLINT +}; + +template <typename> +struct ElemFromListImpl; +template <size_t... I> +struct ElemFromListImpl<IndexSequence<I...>> { + // We make Ignore a template to solve a problem with MSVC. + // A non-template Ignore would work fine with `decltype(Ignore(I))...`, but + // MSVC doesn't understand how to deal with that pack expansion. + // Use `0 * I` to have a single instantiation of Ignore. + template <typename R> + static R Apply(Ignore<0 * I>..., R (*)(), ...); +}; + +template <size_t N, typename... T> +struct ElemFromList { + using type = + decltype(ElemFromListImpl<typename MakeIndexSequence<N>::type>::Apply( + static_cast<T (*)()>(nullptr)...)); +}; + +struct FlatTupleConstructTag {}; + +template <typename... T> +class FlatTuple; + +template <typename Derived, size_t I> +struct FlatTupleElemBase; + +template <typename... T, size_t I> +struct FlatTupleElemBase<FlatTuple<T...>, I> { + using value_type = typename ElemFromList<I, T...>::type; + FlatTupleElemBase() = default; + template <typename Arg> + explicit FlatTupleElemBase(FlatTupleConstructTag, Arg&& t) + : value(std::forward<Arg>(t)) {} + value_type value; +}; + +template <typename Derived, typename Idx> +struct FlatTupleBase; + +template <size_t... Idx, typename... T> +struct FlatTupleBase<FlatTuple<T...>, IndexSequence<Idx...>> + : FlatTupleElemBase<FlatTuple<T...>, Idx>... { + using Indices = IndexSequence<Idx...>; + FlatTupleBase() = default; + template <typename... Args> + explicit FlatTupleBase(FlatTupleConstructTag, Args&&... args) + : FlatTupleElemBase<FlatTuple<T...>, Idx>(FlatTupleConstructTag{}, + std::forward<Args>(args))... {} + + template <size_t I> + const typename ElemFromList<I, T...>::type& Get() const { + return FlatTupleElemBase<FlatTuple<T...>, I>::value; + } + + template <size_t I> + typename ElemFromList<I, T...>::type& Get() { + return FlatTupleElemBase<FlatTuple<T...>, I>::value; + } + + template <typename F> + auto Apply(F&& f) -> decltype(std::forward<F>(f)(this->Get<Idx>()...)) { + return std::forward<F>(f)(Get<Idx>()...); + } + + template <typename F> + auto Apply(F&& f) const -> decltype(std::forward<F>(f)(this->Get<Idx>()...)) { + return std::forward<F>(f)(Get<Idx>()...); + } +}; + +// Analog to std::tuple but with different tradeoffs. +// This class minimizes the template instantiation depth, thus allowing more +// elements than std::tuple would. std::tuple has been seen to require an +// instantiation depth of more than 10x the number of elements in some +// implementations. +// FlatTuple and ElemFromList are not recursive and have a fixed depth +// regardless of T... +// MakeIndexSequence, on the other hand, it is recursive but with an +// instantiation depth of O(ln(N)). +template <typename... T> +class FlatTuple + : private FlatTupleBase<FlatTuple<T...>, + typename MakeIndexSequence<sizeof...(T)>::type> { + using Indices = typename FlatTupleBase< + FlatTuple<T...>, typename MakeIndexSequence<sizeof...(T)>::type>::Indices; + + public: + FlatTuple() = default; + template <typename... Args> + explicit FlatTuple(FlatTupleConstructTag tag, Args&&... args) + : FlatTuple::FlatTupleBase(tag, std::forward<Args>(args)...) {} + + using FlatTuple::FlatTupleBase::Apply; + using FlatTuple::FlatTupleBase::Get; +}; + +// Utility functions to be called with static_assert to induce deprecation +// warnings. +GTEST_INTERNAL_DEPRECATED( + "INSTANTIATE_TEST_CASE_P is deprecated, please use " + "INSTANTIATE_TEST_SUITE_P") +constexpr bool InstantiateTestCase_P_IsDeprecated() { return true; } + +GTEST_INTERNAL_DEPRECATED( + "TYPED_TEST_CASE_P is deprecated, please use " + "TYPED_TEST_SUITE_P") +constexpr bool TypedTestCase_P_IsDeprecated() { return true; } + +GTEST_INTERNAL_DEPRECATED( + "TYPED_TEST_CASE is deprecated, please use " + "TYPED_TEST_SUITE") +constexpr bool TypedTestCaseIsDeprecated() { return true; } + +GTEST_INTERNAL_DEPRECATED( + "REGISTER_TYPED_TEST_CASE_P is deprecated, please use " + "REGISTER_TYPED_TEST_SUITE_P") +constexpr bool RegisterTypedTestCase_P_IsDeprecated() { return true; } + +GTEST_INTERNAL_DEPRECATED( + "INSTANTIATE_TYPED_TEST_CASE_P is deprecated, please use " + "INSTANTIATE_TYPED_TEST_SUITE_P") +constexpr bool InstantiateTypedTestCase_P_IsDeprecated() { return true; } + +} // namespace internal +} // namespace testing + +namespace std { +// Some standard library implementations use `struct tuple_size` and some use +// `class tuple_size`. Clang warns about the mismatch. +// https://reviews.llvm.org/D55466 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template <typename... Ts> +struct tuple_size<testing::internal::FlatTuple<Ts...>> + : std::integral_constant<size_t, sizeof...(Ts)> {}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} // namespace std + +#define GTEST_MESSAGE_AT_(file, line, message, result_type) \ + ::testing::internal::AssertHelper(result_type, file, line, message) \ + = ::testing::Message() + +#define GTEST_MESSAGE_(message, result_type) \ + GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type) + +#define GTEST_FATAL_FAILURE_(message) \ + return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure) + +#define GTEST_NONFATAL_FAILURE_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure) + +#define GTEST_SUCCESS_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess) + +#define GTEST_SKIP_(message) \ + return GTEST_MESSAGE_(message, ::testing::TestPartResult::kSkip) + +// Suppress MSVC warning 4072 (unreachable code) for the code following +// statement if it returns or throws (or doesn't return or throw in some +// situations). +// NOTE: The "else" is important to keep this expansion to prevent a top-level +// "else" from attaching to our "if". +#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \ + if (::testing::internal::AlwaysTrue()) { \ + statement; \ + } else /* NOLINT */ \ + static_assert(true, "") // User must have a semicolon after expansion. + +#if GTEST_HAS_EXCEPTIONS + +namespace testing { +namespace internal { + +class NeverThrown { + public: + const char* what() const noexcept { + return "this exception should never be thrown"; + } +}; + +} // namespace internal +} // namespace testing + +#if GTEST_HAS_RTTI + +#define GTEST_EXCEPTION_TYPE_(e) ::testing::internal::GetTypeName(typeid(e)) + +#else // GTEST_HAS_RTTI + +#define GTEST_EXCEPTION_TYPE_(e) \ + std::string { "an std::exception-derived error" } + +#endif // GTEST_HAS_RTTI + +#define GTEST_TEST_THROW_CATCH_STD_EXCEPTION_(statement, expected_exception) \ + catch (typename std::conditional< \ + std::is_same<typename std::remove_cv<typename std::remove_reference< \ + expected_exception>::type>::type, \ + std::exception>::value, \ + const ::testing::internal::NeverThrown&, const std::exception&>::type \ + e) { \ + gtest_msg.value = "Expected: " #statement \ + " throws an exception of type " #expected_exception \ + ".\n Actual: it throws "; \ + gtest_msg.value += GTEST_EXCEPTION_TYPE_(e); \ + gtest_msg.value += " with description \""; \ + gtest_msg.value += e.what(); \ + gtest_msg.value += "\"."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } + +#else // GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_THROW_CATCH_STD_EXCEPTION_(statement, expected_exception) + +#endif // GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_THROW_(statement, expected_exception, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::TrueWithString gtest_msg{}) { \ + bool gtest_caught_expected = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (expected_exception const&) { \ + gtest_caught_expected = true; \ + } \ + GTEST_TEST_THROW_CATCH_STD_EXCEPTION_(statement, expected_exception) \ + catch (...) { \ + gtest_msg.value = "Expected: " #statement \ + " throws an exception of type " #expected_exception \ + ".\n Actual: it throws a different type."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + if (!gtest_caught_expected) { \ + gtest_msg.value = "Expected: " #statement \ + " throws an exception of type " #expected_exception \ + ".\n Actual: it throws nothing."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + } else /*NOLINT*/ \ + GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__) \ + : fail(gtest_msg.value.c_str()) + +#if GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_NO_THROW_CATCH_STD_EXCEPTION_() \ + catch (std::exception const& e) { \ + gtest_msg.value = "it throws "; \ + gtest_msg.value += GTEST_EXCEPTION_TYPE_(e); \ + gtest_msg.value += " with description \""; \ + gtest_msg.value += e.what(); \ + gtest_msg.value += "\"."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ + } + +#else // GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_NO_THROW_CATCH_STD_EXCEPTION_() + +#endif // GTEST_HAS_EXCEPTIONS + +#define GTEST_TEST_NO_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::TrueWithString gtest_msg{}) { \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + GTEST_TEST_NO_THROW_CATCH_STD_EXCEPTION_() \ + catch (...) { \ + gtest_msg.value = "it throws."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \ + fail(("Expected: " #statement " doesn't throw an exception.\n" \ + " Actual: " + gtest_msg.value).c_str()) + +#define GTEST_TEST_ANY_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + bool gtest_caught_any = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (...) { \ + gtest_caught_any = true; \ + } \ + if (!gtest_caught_any) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__): \ + fail("Expected: " #statement " throws an exception.\n" \ + " Actual: it doesn't.") + + +// Implements Boolean test assertions such as EXPECT_TRUE. expression can be +// either a boolean expression or an AssertionResult. text is a textual +// representation of expression as it was passed into the EXPECT_TRUE. +#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar_ = \ + ::testing::AssertionResult(expression)) \ + ; \ + else \ + fail(::testing::internal::GetBoolAssertionFailureMessage(\ + gtest_ar_, text, #actual, #expected).c_str()) + +#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker; \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__): \ + fail("Expected: " #statement " doesn't generate new fatal " \ + "failures in the current thread.\n" \ + " Actual: it does.") + +// Expands to the name of the class that implements the given test. +#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + test_suite_name##_##test_name##_Test + +// Helper macro for defining tests. +#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id) \ + static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1, \ + "test_suite_name must not be empty"); \ + static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1, \ + "test_name must not be empty"); \ + class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + : public parent_class { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default; \ + ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default; \ + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name)); \ + GTEST_DISALLOW_MOVE_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name)); \ + \ + private: \ + void TestBody() override; \ + static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_; \ + }; \ + \ + ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name)::test_info_ = \ + ::testing::internal::MakeAndRegisterTestInfo( \ + #test_suite_name, #test_name, nullptr, nullptr, \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id), \ + ::testing::internal::SuiteApiResolver< \ + parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__), \ + ::testing::internal::SuiteApiResolver< \ + parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__), \ + new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_( \ + test_suite_name, test_name)>); \ + void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines the public API for death tests. It is +// #included by gtest.h so a user doesn't need to include this +// directly. +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This header file defines internal utilities needed for implementing +// death tests. They are subject to change without notice. +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Google C++ Testing and Mocking Framework (Google Test) +// +// This file implements just enough of the matcher interface to allow +// EXPECT_DEATH and friends to accept a matcher argument. + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_ + +#include <atomic> +#include <memory> +#include <ostream> +#include <string> +#include <type_traits> + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Google Test - The Google C++ Testing and Mocking Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr); +// +// A user can teach this function how to print a class type T by +// defining either operator<<() or PrintTo() in the namespace that +// defines T. More specifically, the FIRST defined function in the +// following list will be used (assuming T is defined in namespace +// foo): +// +// 1. foo::PrintTo(const T&, ostream*) +// 2. operator<<(ostream&, const T&) defined in either foo or the +// global namespace. +// +// However if T is an STL-style container then it is printed element-wise +// unless foo::PrintTo(const T&, ostream*) is defined. Note that +// operator<<() is ignored for container types. +// +// If none of the above is defined, it will print the debug string of +// the value if it is a protocol buffer, or print the raw bytes in the +// value otherwise. +// +// To aid debugging: when T is a reference type, the address of the +// value is also printed; when T is a (const) char pointer, both the +// pointer value and the NUL-terminated string it points to are +// printed. +// +// We also provide some convenient wrappers: +// +// // Prints a value to a string. For a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// std::string ::testing::PrintToString(const T& value); +// +// // Prints a value tersely: for a reference type, the referenced +// // value (but not the address) is printed; for a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// void ::testing::internal::UniversalTersePrint(const T& value, ostream*); +// +// // Prints value using the type inferred by the compiler. The difference +// // from UniversalTersePrint() is that this function prints both the +// // pointer and the NUL-terminated string for a (const or not) char pointer. +// void ::testing::internal::UniversalPrint(const T& value, ostream*); +// +// // Prints the fields of a tuple tersely to a string vector, one +// // element for each field. Tuple support must be enabled in +// // gtest-port.h. +// std::vector<string> UniversalTersePrintTupleFieldsToStrings( +// const Tuple& value); +// +// Known limitation: +// +// The print primitives print the elements of an STL-style container +// using the compiler-inferred type of *iter where iter is a +// const_iterator of the container. When const_iterator is an input +// iterator but not a forward iterator, this inferred type may not +// match value_type, and the print output may be incorrect. In +// practice, this is rarely a problem as for most containers +// const_iterator is a forward iterator. We'll fix this if there's an +// actual need for it. Note that this fix cannot rely on value_type +// being defined as many user-defined container types don't have +// value_type. + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ + +#include <functional> +#include <memory> +#include <ostream> // NOLINT +#include <sstream> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> +#include <vector> + + +#if GTEST_HAS_RTTI +#include <typeindex> +#include <typeinfo> +#endif // GTEST_HAS_RTTI + +namespace testing { + +// Definitions in the internal* namespaces are subject to change without notice. +// DO NOT USE THEM IN USER CODE! +namespace internal { + +template <typename T> +void UniversalPrint(const T& value, ::std::ostream* os); + +// Used to print an STL-style container when the user doesn't define +// a PrintTo() for it. +struct ContainerPrinter { + template <typename T, + typename = typename std::enable_if< + (sizeof(IsContainerTest<T>(0)) == sizeof(IsContainer)) && + !IsRecursiveContainer<T>::value>::type> + static void PrintValue(const T& container, std::ostream* os) { + const size_t kMaxCount = 32; // The maximum number of elements to print. + *os << '{'; + size_t count = 0; + for (auto&& elem : container) { + if (count > 0) { + *os << ','; + if (count == kMaxCount) { // Enough has been printed. + *os << " ..."; + break; + } + } + *os << ' '; + // We cannot call PrintTo(elem, os) here as PrintTo() doesn't + // handle `elem` being a native array. + internal::UniversalPrint(elem, os); + ++count; + } + + if (count > 0) { + *os << ' '; + } + *os << '}'; + } +}; + +// Used to print a pointer that is neither a char pointer nor a member +// pointer, when the user doesn't define PrintTo() for it. (A member +// variable pointer or member function pointer doesn't really point to +// a location in the address space. Their representation is +// implementation-defined. Therefore they will be printed as raw +// bytes.) +struct FunctionPointerPrinter { + template <typename T, typename = typename std::enable_if< + std::is_function<T>::value>::type> + static void PrintValue(T* p, ::std::ostream* os) { + if (p == nullptr) { + *os << "NULL"; + } else { + // T is a function type, so '*os << p' doesn't do what we want + // (it just prints p as bool). We want to print p as a const + // void*. + *os << reinterpret_cast<const void*>(p); + } + } +}; + +struct PointerPrinter { + template <typename T> + static void PrintValue(T* p, ::std::ostream* os) { + if (p == nullptr) { + *os << "NULL"; + } else { + // T is not a function type. We just call << to print p, + // relying on ADL to pick up user-defined << for their pointer + // types, if any. + *os << p; + } + } +}; + +namespace internal_stream_operator_without_lexical_name_lookup { + +// The presence of an operator<< here will terminate lexical scope lookup +// straight away (even though it cannot be a match because of its argument +// types). Thus, the two operator<< calls in StreamPrinter will find only ADL +// candidates. +struct LookupBlocker {}; +void operator<<(LookupBlocker, LookupBlocker); + +struct StreamPrinter { + template <typename T, + // Don't accept member pointers here. We'd print them via implicit + // conversion to bool, which isn't useful. + typename = typename std::enable_if< + !std::is_member_pointer<T>::value>::type, + // Only accept types for which we can find a streaming operator via + // ADL (possibly involving implicit conversions). + typename = decltype(std::declval<std::ostream&>() + << std::declval<const T&>())> + static void PrintValue(const T& value, ::std::ostream* os) { + // Call streaming operator found by ADL, possibly with implicit conversions + // of the arguments. + *os << value; + } +}; + +} // namespace internal_stream_operator_without_lexical_name_lookup + +struct ProtobufPrinter { + // We print a protobuf using its ShortDebugString() when the string + // doesn't exceed this many characters; otherwise we print it using + // DebugString() for better readability. + static const size_t kProtobufOneLinerMaxLength = 50; + + template <typename T, + typename = typename std::enable_if< + internal::HasDebugStringAndShortDebugString<T>::value>::type> + static void PrintValue(const T& value, ::std::ostream* os) { + std::string pretty_str = value.ShortDebugString(); + if (pretty_str.length() > kProtobufOneLinerMaxLength) { + pretty_str = "\n" + value.DebugString(); + } + *os << ("<" + pretty_str + ">"); + } +}; + +struct ConvertibleToIntegerPrinter { + // Since T has no << operator or PrintTo() but can be implicitly + // converted to BiggestInt, we print it as a BiggestInt. + // + // Most likely T is an enum type (either named or unnamed), in which + // case printing it as an integer is the desired behavior. In case + // T is not an enum, printing it as an integer is the best we can do + // given that it has no user-defined printer. + static void PrintValue(internal::BiggestInt value, ::std::ostream* os) { + *os << value; + } +}; + +struct ConvertibleToStringViewPrinter { +#if GTEST_INTERNAL_HAS_STRING_VIEW + static void PrintValue(internal::StringView value, ::std::ostream* os) { + internal::UniversalPrint(value, os); + } +#endif +}; + + +// Prints the given number of bytes in the given object to the given +// ostream. +GTEST_API_ void PrintBytesInObjectTo(const unsigned char* obj_bytes, + size_t count, + ::std::ostream* os); +struct RawBytesPrinter { + // SFINAE on `sizeof` to make sure we have a complete type. + template <typename T, size_t = sizeof(T)> + static void PrintValue(const T& value, ::std::ostream* os) { + PrintBytesInObjectTo( + static_cast<const unsigned char*>( + // Load bearing cast to void* to support iOS + reinterpret_cast<const void*>(std::addressof(value))), + sizeof(value), os); + } +}; + +struct FallbackPrinter { + template <typename T> + static void PrintValue(const T&, ::std::ostream* os) { + *os << "(incomplete type)"; + } +}; + +// Try every printer in order and return the first one that works. +template <typename T, typename E, typename Printer, typename... Printers> +struct FindFirstPrinter : FindFirstPrinter<T, E, Printers...> {}; + +template <typename T, typename Printer, typename... Printers> +struct FindFirstPrinter< + T, decltype(Printer::PrintValue(std::declval<const T&>(), nullptr)), + Printer, Printers...> { + using type = Printer; +}; + +// Select the best printer in the following order: +// - Print containers (they have begin/end/etc). +// - Print function pointers. +// - Print object pointers. +// - Use the stream operator, if available. +// - Print protocol buffers. +// - Print types convertible to BiggestInt. +// - Print types convertible to StringView, if available. +// - Fallback to printing the raw bytes of the object. +template <typename T> +void PrintWithFallback(const T& value, ::std::ostream* os) { + using Printer = typename FindFirstPrinter< + T, void, ContainerPrinter, FunctionPointerPrinter, PointerPrinter, + internal_stream_operator_without_lexical_name_lookup::StreamPrinter, + ProtobufPrinter, ConvertibleToIntegerPrinter, + ConvertibleToStringViewPrinter, RawBytesPrinter, FallbackPrinter>::type; + Printer::PrintValue(value, os); +} + +// FormatForComparison<ToPrint, OtherOperand>::Format(value) formats a +// value of type ToPrint that is an operand of a comparison assertion +// (e.g. ASSERT_EQ). OtherOperand is the type of the other operand in +// the comparison, and is used to help determine the best way to +// format the value. In particular, when the value is a C string +// (char pointer) and the other operand is an STL string object, we +// want to format the C string as a string, since we know it is +// compared by value with the string object. If the value is a char +// pointer but the other operand is not an STL string object, we don't +// know whether the pointer is supposed to point to a NUL-terminated +// string, and thus want to print it as a pointer to be safe. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// The default case. +template <typename ToPrint, typename OtherOperand> +class FormatForComparison { + public: + static ::std::string Format(const ToPrint& value) { + return ::testing::PrintToString(value); + } +}; + +// Array. +template <typename ToPrint, size_t N, typename OtherOperand> +class FormatForComparison<ToPrint[N], OtherOperand> { + public: + static ::std::string Format(const ToPrint* value) { + return FormatForComparison<const ToPrint*, OtherOperand>::Format(value); + } +}; + +// By default, print C string as pointers to be safe, as we don't know +// whether they actually point to a NUL-terminated string. + +#define GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(CharType) \ + template <typename OtherOperand> \ + class FormatForComparison<CharType*, OtherOperand> { \ + public: \ + static ::std::string Format(CharType* value) { \ + return ::testing::PrintToString(static_cast<const void*>(value)); \ + } \ + } + +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(wchar_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const wchar_t); +#ifdef __cpp_char8_t +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char8_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char8_t); +#endif +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char16_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char16_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char32_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char32_t); + +#undef GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_ + +// If a C string is compared with an STL string object, we know it's meant +// to point to a NUL-terminated string, and thus can print it as a string. + +#define GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(CharType, OtherStringType) \ + template <> \ + class FormatForComparison<CharType*, OtherStringType> { \ + public: \ + static ::std::string Format(CharType* value) { \ + return ::testing::PrintToString(value); \ + } \ + } + +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::std::string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::std::string); +#ifdef __cpp_char8_t +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char8_t, ::std::u8string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char8_t, ::std::u8string); +#endif +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char16_t, ::std::u16string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char16_t, ::std::u16string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char32_t, ::std::u32string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char32_t, ::std::u32string); + +#if GTEST_HAS_STD_WSTRING +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::std::wstring); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::std::wstring); +#endif + +#undef GTEST_IMPL_FORMAT_C_STRING_AS_STRING_ + +// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) +// operand to be used in a failure message. The type (but not value) +// of the other operand may affect the format. This allows us to +// print a char* as a raw pointer when it is compared against another +// char* or void*, and print it as a C string when it is compared +// against an std::string object, for example. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template <typename T1, typename T2> +std::string FormatForComparisonFailureMessage( + const T1& value, const T2& /* other_operand */) { + return FormatForComparison<T1, T2>::Format(value); +} + +// UniversalPrinter<T>::Print(value, ostream_ptr) prints the given +// value to the given ostream. The caller must ensure that +// 'ostream_ptr' is not NULL, or the behavior is undefined. +// +// We define UniversalPrinter as a class template (as opposed to a +// function template), as we need to partially specialize it for +// reference types, which cannot be done with function templates. +template <typename T> +class UniversalPrinter; + +// Prints the given value using the << operator if it has one; +// otherwise prints the bytes in it. This is what +// UniversalPrinter<T>::Print() does when PrintTo() is not specialized +// or overloaded for type T. +// +// A user can override this behavior for a class type Foo by defining +// an overload of PrintTo() in the namespace where Foo is defined. We +// give the user this option as sometimes defining a << operator for +// Foo is not desirable (e.g. the coding style may prevent doing it, +// or there is already a << operator but it doesn't do what the user +// wants). +template <typename T> +void PrintTo(const T& value, ::std::ostream* os) { + internal::PrintWithFallback(value, os); +} + +// The following list of PrintTo() overloads tells +// UniversalPrinter<T>::Print() how to print standard types (built-in +// types, strings, plain arrays, and pointers). + +// Overloads for various char types. +GTEST_API_ void PrintTo(unsigned char c, ::std::ostream* os); +GTEST_API_ void PrintTo(signed char c, ::std::ostream* os); +inline void PrintTo(char c, ::std::ostream* os) { + // When printing a plain char, we always treat it as unsigned. This + // way, the output won't be affected by whether the compiler thinks + // char is signed or not. + PrintTo(static_cast<unsigned char>(c), os); +} + +// Overloads for other simple built-in types. +inline void PrintTo(bool x, ::std::ostream* os) { + *os << (x ? "true" : "false"); +} + +// Overload for wchar_t type. +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its decimal code (except for L'\0'). +// The L'\0' char is printed as "L'\\0'". The decimal code is printed +// as signed integer when wchar_t is implemented by the compiler +// as a signed type and is printed as an unsigned integer when wchar_t +// is implemented as an unsigned type. +GTEST_API_ void PrintTo(wchar_t wc, ::std::ostream* os); + +GTEST_API_ void PrintTo(char32_t c, ::std::ostream* os); +inline void PrintTo(char16_t c, ::std::ostream* os) { + PrintTo(ImplicitCast_<char32_t>(c), os); +} +#ifdef __cpp_char8_t +inline void PrintTo(char8_t c, ::std::ostream* os) { + PrintTo(ImplicitCast_<char32_t>(c), os); +} +#endif + +// Overloads for C strings. +GTEST_API_ void PrintTo(const char* s, ::std::ostream* os); +inline void PrintTo(char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_<const char*>(s), os); +} + +// signed/unsigned char is often used for representing binary data, so +// we print pointers to it as void* to be safe. +inline void PrintTo(const signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_<const void*>(s), os); +} +inline void PrintTo(signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_<const void*>(s), os); +} +inline void PrintTo(const unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_<const void*>(s), os); +} +inline void PrintTo(unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_<const void*>(s), os); +} +#ifdef __cpp_char8_t +// Overloads for u8 strings. +void PrintTo(const char8_t* s, ::std::ostream* os); +inline void PrintTo(char8_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_<const char8_t*>(s), os); +} +#endif +// Overloads for u16 strings. +void PrintTo(const char16_t* s, ::std::ostream* os); +inline void PrintTo(char16_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_<const char16_t*>(s), os); +} +// Overloads for u32 strings. +void PrintTo(const char32_t* s, ::std::ostream* os); +inline void PrintTo(char32_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_<const char32_t*>(s), os); +} + +// MSVC can be configured to define wchar_t as a typedef of unsigned +// short. It defines _NATIVE_WCHAR_T_DEFINED when wchar_t is a native +// type. When wchar_t is a typedef, defining an overload for const +// wchar_t* would cause unsigned short* be printed as a wide string, +// possibly causing invalid memory accesses. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Overloads for wide C strings +GTEST_API_ void PrintTo(const wchar_t* s, ::std::ostream* os); +inline void PrintTo(wchar_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_<const wchar_t*>(s), os); +} +#endif + +// Overload for C arrays. Multi-dimensional arrays are printed +// properly. + +// Prints the given number of elements in an array, without printing +// the curly braces. +template <typename T> +void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) { + UniversalPrint(a[0], os); + for (size_t i = 1; i != count; i++) { + *os << ", "; + UniversalPrint(a[i], os); + } +} + +// Overloads for ::std::string. +GTEST_API_ void PrintStringTo(const ::std::string&s, ::std::ostream* os); +inline void PrintTo(const ::std::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} + +// Overloads for ::std::u8string +#ifdef __cpp_char8_t +GTEST_API_ void PrintU8StringTo(const ::std::u8string& s, ::std::ostream* os); +inline void PrintTo(const ::std::u8string& s, ::std::ostream* os) { + PrintU8StringTo(s, os); +} +#endif + +// Overloads for ::std::u16string +GTEST_API_ void PrintU16StringTo(const ::std::u16string& s, ::std::ostream* os); +inline void PrintTo(const ::std::u16string& s, ::std::ostream* os) { + PrintU16StringTo(s, os); +} + +// Overloads for ::std::u32string +GTEST_API_ void PrintU32StringTo(const ::std::u32string& s, ::std::ostream* os); +inline void PrintTo(const ::std::u32string& s, ::std::ostream* os) { + PrintU32StringTo(s, os); +} + +// Overloads for ::std::wstring. +#if GTEST_HAS_STD_WSTRING +GTEST_API_ void PrintWideStringTo(const ::std::wstring&s, ::std::ostream* os); +inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_INTERNAL_HAS_STRING_VIEW +// Overload for internal::StringView. +inline void PrintTo(internal::StringView sp, ::std::ostream* os) { + PrintTo(::std::string(sp), os); +} +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + +inline void PrintTo(std::nullptr_t, ::std::ostream* os) { *os << "(nullptr)"; } + +template <typename T> +void PrintTo(std::reference_wrapper<T> ref, ::std::ostream* os) { + UniversalPrinter<T&>::Print(ref.get(), os); +} + +inline const void* VoidifyPointer(const void* p) { return p; } +inline const void* VoidifyPointer(volatile const void* p) { + return const_cast<const void*>(p); +} + +template <typename T, typename Ptr> +void PrintSmartPointer(const Ptr& ptr, std::ostream* os, char) { + if (ptr == nullptr) { + *os << "(nullptr)"; + } else { + // We can't print the value. Just print the pointer.. + *os << "(" << (VoidifyPointer)(ptr.get()) << ")"; + } +} +template <typename T, typename Ptr, + typename = typename std::enable_if<!std::is_void<T>::value && + !std::is_array<T>::value>::type> +void PrintSmartPointer(const Ptr& ptr, std::ostream* os, int) { + if (ptr == nullptr) { + *os << "(nullptr)"; + } else { + *os << "(ptr = " << (VoidifyPointer)(ptr.get()) << ", value = "; + UniversalPrinter<T>::Print(*ptr, os); + *os << ")"; + } +} + +template <typename T, typename D> +void PrintTo(const std::unique_ptr<T, D>& ptr, std::ostream* os) { + (PrintSmartPointer<T>)(ptr, os, 0); +} + +template <typename T> +void PrintTo(const std::shared_ptr<T>& ptr, std::ostream* os) { + (PrintSmartPointer<T>)(ptr, os, 0); +} + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template <typename T> +void PrintTupleTo(const T&, std::integral_constant<size_t, 0>, + ::std::ostream*) {} + +template <typename T, size_t I> +void PrintTupleTo(const T& t, std::integral_constant<size_t, I>, + ::std::ostream* os) { + PrintTupleTo(t, std::integral_constant<size_t, I - 1>(), os); + GTEST_INTENTIONAL_CONST_COND_PUSH_() + if (I > 1) { + GTEST_INTENTIONAL_CONST_COND_POP_() + *os << ", "; + } + UniversalPrinter<typename std::tuple_element<I - 1, T>::type>::Print( + std::get<I - 1>(t), os); +} + +template <typename... Types> +void PrintTo(const ::std::tuple<Types...>& t, ::std::ostream* os) { + *os << "("; + PrintTupleTo(t, std::integral_constant<size_t, sizeof...(Types)>(), os); + *os << ")"; +} + +// Overload for std::pair. +template <typename T1, typename T2> +void PrintTo(const ::std::pair<T1, T2>& value, ::std::ostream* os) { + *os << '('; + // We cannot use UniversalPrint(value.first, os) here, as T1 may be + // a reference type. The same for printing value.second. + UniversalPrinter<T1>::Print(value.first, os); + *os << ", "; + UniversalPrinter<T2>::Print(value.second, os); + *os << ')'; +} + +#if GTEST_HAS_RTTI +inline void PrintTo(const ::std::type_info& value, ::std::ostream* os) { + internal::PrintTo<::std::type_info>(value, os); + *os << " (\"" << value.name() << "\")"; +} + +inline void PrintTo(const ::std::type_index& value, ::std::ostream* os) { + internal::PrintTo<::std::type_index>(value, os); + *os << " (\"" << value.name() << "\")"; +} +#endif // GTEST_HAS_RTTI + +// Implements printing a non-reference type T by letting the compiler +// pick the right overload of PrintTo() for T. +template <typename T> +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180) + + // Note: we deliberately don't call this PrintTo(), as that name + // conflicts with ::testing::internal::PrintTo in the body of the + // function. + static void Print(const T& value, ::std::ostream* os) { + // By default, ::testing::internal::PrintTo() is used for printing + // the value. + // + // Thanks to Koenig look-up, if T is a class and has its own + // PrintTo() function defined in its namespace, that function will + // be visible here. Since it is more specific than the generic ones + // in ::testing::internal, it will be picked by the compiler in the + // following statement - exactly what we want. + PrintTo(value, os); + } + + GTEST_DISABLE_MSC_WARNINGS_POP_() +}; + +// Remove any const-qualifiers before passing a type to UniversalPrinter. +template <typename T> +class UniversalPrinter<const T> : public UniversalPrinter<T> {}; + +#if GTEST_INTERNAL_HAS_ANY + +// Printer for std::any / absl::any + +template <> +class UniversalPrinter<Any> { + public: + static void Print(const Any& value, ::std::ostream* os) { + if (value.has_value()) { + *os << "value of type " << GetTypeName(value); + } else { + *os << "no value"; + } + } + + private: + static std::string GetTypeName(const Any& value) { +#if GTEST_HAS_RTTI + return internal::GetTypeName(value.type()); +#else + static_cast<void>(value); // possibly unused + return "<unknown_type>"; +#endif // GTEST_HAS_RTTI + } +}; + +#endif // GTEST_INTERNAL_HAS_ANY + +#if GTEST_INTERNAL_HAS_OPTIONAL + +// Printer for std::optional / absl::optional + +template <typename T> +class UniversalPrinter<Optional<T>> { + public: + static void Print(const Optional<T>& value, ::std::ostream* os) { + *os << '('; + if (!value) { + *os << "nullopt"; + } else { + UniversalPrint(*value, os); + } + *os << ')'; + } +}; + +#endif // GTEST_INTERNAL_HAS_OPTIONAL + +#if GTEST_INTERNAL_HAS_VARIANT + +// Printer for std::variant / absl::variant + +template <typename... T> +class UniversalPrinter<Variant<T...>> { + public: + static void Print(const Variant<T...>& value, ::std::ostream* os) { + *os << '('; +#if GTEST_HAS_ABSL + absl::visit(Visitor{os, value.index()}, value); +#else + std::visit(Visitor{os, value.index()}, value); +#endif // GTEST_HAS_ABSL + *os << ')'; + } + + private: + struct Visitor { + template <typename U> + void operator()(const U& u) const { + *os << "'" << GetTypeName<U>() << "(index = " << index + << ")' with value "; + UniversalPrint(u, os); + } + ::std::ostream* os; + std::size_t index; + }; +}; + +#endif // GTEST_INTERNAL_HAS_VARIANT + +// UniversalPrintArray(begin, len, os) prints an array of 'len' +// elements, starting at address 'begin'. +template <typename T> +void UniversalPrintArray(const T* begin, size_t len, ::std::ostream* os) { + if (len == 0) { + *os << "{}"; + } else { + *os << "{ "; + const size_t kThreshold = 18; + const size_t kChunkSize = 8; + // If the array has more than kThreshold elements, we'll have to + // omit some details by printing only the first and the last + // kChunkSize elements. + if (len <= kThreshold) { + PrintRawArrayTo(begin, len, os); + } else { + PrintRawArrayTo(begin, kChunkSize, os); + *os << ", ..., "; + PrintRawArrayTo(begin + len - kChunkSize, kChunkSize, os); + } + *os << " }"; + } +} +// This overload prints a (const) char array compactly. +GTEST_API_ void UniversalPrintArray( + const char* begin, size_t len, ::std::ostream* os); + +#ifdef __cpp_char8_t +// This overload prints a (const) char8_t array compactly. +GTEST_API_ void UniversalPrintArray(const char8_t* begin, size_t len, + ::std::ostream* os); +#endif + +// This overload prints a (const) char16_t array compactly. +GTEST_API_ void UniversalPrintArray(const char16_t* begin, size_t len, + ::std::ostream* os); + +// This overload prints a (const) char32_t array compactly. +GTEST_API_ void UniversalPrintArray(const char32_t* begin, size_t len, + ::std::ostream* os); + +// This overload prints a (const) wchar_t array compactly. +GTEST_API_ void UniversalPrintArray( + const wchar_t* begin, size_t len, ::std::ostream* os); + +// Implements printing an array type T[N]. +template <typename T, size_t N> +class UniversalPrinter<T[N]> { + public: + // Prints the given array, omitting some elements when there are too + // many. + static void Print(const T (&a)[N], ::std::ostream* os) { + UniversalPrintArray(a, N, os); + } +}; + +// Implements printing a reference type T&. +template <typename T> +class UniversalPrinter<T&> { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180) + + static void Print(const T& value, ::std::ostream* os) { + // Prints the address of the value. We use reinterpret_cast here + // as static_cast doesn't compile when T is a function type. + *os << "@" << reinterpret_cast<const void*>(&value) << " "; + + // Then prints the value itself. + UniversalPrint(value, os); + } + + GTEST_DISABLE_MSC_WARNINGS_POP_() +}; + +// Prints a value tersely: for a reference type, the referenced value +// (but not the address) is printed; for a (const) char pointer, the +// NUL-terminated string (but not the pointer) is printed. + +template <typename T> +class UniversalTersePrinter { + public: + static void Print(const T& value, ::std::ostream* os) { + UniversalPrint(value, os); + } +}; +template <typename T> +class UniversalTersePrinter<T&> { + public: + static void Print(const T& value, ::std::ostream* os) { + UniversalPrint(value, os); + } +}; +template <typename T, size_t N> +class UniversalTersePrinter<T[N]> { + public: + static void Print(const T (&value)[N], ::std::ostream* os) { + UniversalPrinter<T[N]>::Print(value, os); + } +}; +template <> +class UniversalTersePrinter<const char*> { + public: + static void Print(const char* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(std::string(str), os); + } + } +}; +template <> +class UniversalTersePrinter<char*> : public UniversalTersePrinter<const char*> { +}; + +#ifdef __cpp_char8_t +template <> +class UniversalTersePrinter<const char8_t*> { + public: + static void Print(const char8_t* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(::std::u8string(str), os); + } + } +}; +template <> +class UniversalTersePrinter<char8_t*> + : public UniversalTersePrinter<const char8_t*> {}; +#endif + +template <> +class UniversalTersePrinter<const char16_t*> { + public: + static void Print(const char16_t* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(::std::u16string(str), os); + } + } +}; +template <> +class UniversalTersePrinter<char16_t*> + : public UniversalTersePrinter<const char16_t*> {}; + +template <> +class UniversalTersePrinter<const char32_t*> { + public: + static void Print(const char32_t* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(::std::u32string(str), os); + } + } +}; +template <> +class UniversalTersePrinter<char32_t*> + : public UniversalTersePrinter<const char32_t*> {}; + +#if GTEST_HAS_STD_WSTRING +template <> +class UniversalTersePrinter<const wchar_t*> { + public: + static void Print(const wchar_t* str, ::std::ostream* os) { + if (str == nullptr) { + *os << "NULL"; + } else { + UniversalPrint(::std::wstring(str), os); + } + } +}; +#endif + +template <> +class UniversalTersePrinter<wchar_t*> { + public: + static void Print(wchar_t* str, ::std::ostream* os) { + UniversalTersePrinter<const wchar_t*>::Print(str, os); + } +}; + +template <typename T> +void UniversalTersePrint(const T& value, ::std::ostream* os) { + UniversalTersePrinter<T>::Print(value, os); +} + +// Prints a value using the type inferred by the compiler. The +// difference between this and UniversalTersePrint() is that for a +// (const) char pointer, this prints both the pointer and the +// NUL-terminated string. +template <typename T> +void UniversalPrint(const T& value, ::std::ostream* os) { + // A workarond for the bug in VC++ 7.1 that prevents us from instantiating + // UniversalPrinter with T directly. + typedef T T1; + UniversalPrinter<T1>::Print(value, os); +} + +typedef ::std::vector< ::std::string> Strings; + + // Tersely prints the first N fields of a tuple to a string vector, + // one element for each field. +template <typename Tuple> +void TersePrintPrefixToStrings(const Tuple&, std::integral_constant<size_t, 0>, + Strings*) {} +template <typename Tuple, size_t I> +void TersePrintPrefixToStrings(const Tuple& t, + std::integral_constant<size_t, I>, + Strings* strings) { + TersePrintPrefixToStrings(t, std::integral_constant<size_t, I - 1>(), + strings); + ::std::stringstream ss; + UniversalTersePrint(std::get<I - 1>(t), &ss); + strings->push_back(ss.str()); +} + +// Prints the fields of a tuple tersely to a string vector, one +// element for each field. See the comment before +// UniversalTersePrint() for how we define "tersely". +template <typename Tuple> +Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) { + Strings result; + TersePrintPrefixToStrings( + value, std::integral_constant<size_t, std::tuple_size<Tuple>::value>(), + &result); + return result; +} + +} // namespace internal + +template <typename T> +::std::string PrintToString(const T& value) { + ::std::stringstream ss; + internal::UniversalTersePrinter<T>::Print(value, &ss); + return ss.str(); +} + +} // namespace testing + +// Include any custom printer added by the local installation. +// We must include this header at the end to make sure it can use the +// declarations from this file. +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// This file provides an injection point for custom printers in a local +// installation of gTest. +// It will be included from gtest-printers.h and the overrides in this file +// will be visible to everyone. +// +// Injection point for custom user configurations. See README for details +// +// ** Custom implementation starts here ** + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ + +// MSVC warning C5046 is new as of VS2017 version 15.8. +#if defined(_MSC_VER) && _MSC_VER >= 1915 +#define GTEST_MAYBE_5046_ 5046 +#else +#define GTEST_MAYBE_5046_ +#endif + +GTEST_DISABLE_MSC_WARNINGS_PUSH_( + 4251 GTEST_MAYBE_5046_ /* class A needs to have dll-interface to be used by + clients of class B */ + /* Symbol involving type with internal linkage not defined */) + +namespace testing { + +// To implement a matcher Foo for type T, define: +// 1. a class FooMatcherMatcher that implements the matcher interface: +// using is_gtest_matcher = void; +// bool MatchAndExplain(const T&, std::ostream*); +// (MatchResultListener* can also be used instead of std::ostream*) +// void DescribeTo(std::ostream*); +// void DescribeNegationTo(std::ostream*); +// +// 2. a factory function that creates a Matcher<T> object from a +// FooMatcherMatcher. + +class MatchResultListener { + public: + // Creates a listener object with the given underlying ostream. The + // listener does not own the ostream, and does not dereference it + // in the constructor or destructor. + explicit MatchResultListener(::std::ostream* os) : stream_(os) {} + virtual ~MatchResultListener() = 0; // Makes this class abstract. + + // Streams x to the underlying ostream; does nothing if the ostream + // is NULL. + template <typename T> + MatchResultListener& operator<<(const T& x) { + if (stream_ != nullptr) *stream_ << x; + return *this; + } + + // Returns the underlying ostream. + ::std::ostream* stream() { return stream_; } + + // Returns true if and only if the listener is interested in an explanation + // of the match result. A matcher's MatchAndExplain() method can use + // this information to avoid generating the explanation when no one + // intends to hear it. + bool IsInterested() const { return stream_ != nullptr; } + + private: + ::std::ostream* const stream_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(MatchResultListener); +}; + +inline MatchResultListener::~MatchResultListener() { +} + +// An instance of a subclass of this knows how to describe itself as a +// matcher. +class MatcherDescriberInterface { + public: + virtual ~MatcherDescriberInterface() {} + + // Describes this matcher to an ostream. The function should print + // a verb phrase that describes the property a value matching this + // matcher should have. The subject of the verb phrase is the value + // being matched. For example, the DescribeTo() method of the Gt(7) + // matcher prints "is greater than 7". + virtual void DescribeTo(::std::ostream* os) const = 0; + + // Describes the negation of this matcher to an ostream. For + // example, if the description of this matcher is "is greater than + // 7", the negated description could be "is not greater than 7". + // You are not required to override this when implementing + // MatcherInterface, but it is highly advised so that your matcher + // can produce good error messages. + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "not ("; + DescribeTo(os); + *os << ")"; + } +}; + +// The implementation of a matcher. +template <typename T> +class MatcherInterface : public MatcherDescriberInterface { + public: + // Returns true if and only if the matcher matches x; also explains the + // match result to 'listener' if necessary (see the next paragraph), in + // the form of a non-restrictive relative clause ("which ...", + // "whose ...", etc) that describes x. For example, the + // MatchAndExplain() method of the Pointee(...) matcher should + // generate an explanation like "which points to ...". + // + // Implementations of MatchAndExplain() should add an explanation of + // the match result *if and only if* they can provide additional + // information that's not already present (or not obvious) in the + // print-out of x and the matcher's description. Whether the match + // succeeds is not a factor in deciding whether an explanation is + // needed, as sometimes the caller needs to print a failure message + // when the match succeeds (e.g. when the matcher is used inside + // Not()). + // + // For example, a "has at least 10 elements" matcher should explain + // what the actual element count is, regardless of the match result, + // as it is useful information to the reader; on the other hand, an + // "is empty" matcher probably only needs to explain what the actual + // size is when the match fails, as it's redundant to say that the + // size is 0 when the value is already known to be empty. + // + // You should override this method when defining a new matcher. + // + // It's the responsibility of the caller (Google Test) to guarantee + // that 'listener' is not NULL. This helps to simplify a matcher's + // implementation when it doesn't care about the performance, as it + // can talk to 'listener' without checking its validity first. + // However, in order to implement dummy listeners efficiently, + // listener->stream() may be NULL. + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0; + + // Inherits these methods from MatcherDescriberInterface: + // virtual void DescribeTo(::std::ostream* os) const = 0; + // virtual void DescribeNegationTo(::std::ostream* os) const; +}; + +namespace internal { + +struct AnyEq { + template <typename A, typename B> + bool operator()(const A& a, const B& b) const { return a == b; } +}; +struct AnyNe { + template <typename A, typename B> + bool operator()(const A& a, const B& b) const { return a != b; } +}; +struct AnyLt { + template <typename A, typename B> + bool operator()(const A& a, const B& b) const { return a < b; } +}; +struct AnyGt { + template <typename A, typename B> + bool operator()(const A& a, const B& b) const { return a > b; } +}; +struct AnyLe { + template <typename A, typename B> + bool operator()(const A& a, const B& b) const { return a <= b; } +}; +struct AnyGe { + template <typename A, typename B> + bool operator()(const A& a, const B& b) const { return a >= b; } +}; + +// A match result listener that ignores the explanation. +class DummyMatchResultListener : public MatchResultListener { + public: + DummyMatchResultListener() : MatchResultListener(nullptr) {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(DummyMatchResultListener); +}; + +// A match result listener that forwards the explanation to a given +// ostream. The difference between this and MatchResultListener is +// that the former is concrete. +class StreamMatchResultListener : public MatchResultListener { + public: + explicit StreamMatchResultListener(::std::ostream* os) + : MatchResultListener(os) {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamMatchResultListener); +}; + +struct SharedPayloadBase { + std::atomic<int> ref{1}; + void Ref() { ref.fetch_add(1, std::memory_order_relaxed); } + bool Unref() { return ref.fetch_sub(1, std::memory_order_acq_rel) == 1; } +}; + +template <typename T> +struct SharedPayload : SharedPayloadBase { + explicit SharedPayload(const T& v) : value(v) {} + explicit SharedPayload(T&& v) : value(std::move(v)) {} + + static void Destroy(SharedPayloadBase* shared) { + delete static_cast<SharedPayload*>(shared); + } + + T value; +}; + +template <typename T> +using is_trivially_copy_constructible = +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 + std::has_trivial_copy_constructor<T>; +#else + std::is_trivially_copy_constructible<T>; +#endif + +// An internal class for implementing Matcher<T>, which will derive +// from it. We put functionalities common to all Matcher<T> +// specializations here to avoid code duplication. +template <typename T> +class MatcherBase : private MatcherDescriberInterface { + public: + // Returns true if and only if the matcher matches x; also explains the + // match result to 'listener'. + bool MatchAndExplain(const T& x, MatchResultListener* listener) const { + GTEST_CHECK_(vtable_ != nullptr); + return vtable_->match_and_explain(*this, x, listener); + } + + // Returns true if and only if this matcher matches x. + bool Matches(const T& x) const { + DummyMatchResultListener dummy; + return MatchAndExplain(x, &dummy); + } + + // Describes this matcher to an ostream. + void DescribeTo(::std::ostream* os) const final { + GTEST_CHECK_(vtable_ != nullptr); + vtable_->describe(*this, os, false); + } + + // Describes the negation of this matcher to an ostream. + void DescribeNegationTo(::std::ostream* os) const final { + GTEST_CHECK_(vtable_ != nullptr); + vtable_->describe(*this, os, true); + } + + // Explains why x matches, or doesn't match, the matcher. + void ExplainMatchResultTo(const T& x, ::std::ostream* os) const { + StreamMatchResultListener listener(os); + MatchAndExplain(x, &listener); + } + + // Returns the describer for this matcher object; retains ownership + // of the describer, which is only guaranteed to be alive when + // this matcher object is alive. + const MatcherDescriberInterface* GetDescriber() const { + if (vtable_ == nullptr) return nullptr; + return vtable_->get_describer(*this); + } + + protected: + MatcherBase() : vtable_(nullptr) {} + + // Constructs a matcher from its implementation. + template <typename U> + explicit MatcherBase(const MatcherInterface<U>* impl) { + Init(impl); + } + + template <typename M, typename = typename std::remove_reference< + M>::type::is_gtest_matcher> + MatcherBase(M&& m) { // NOLINT + Init(std::forward<M>(m)); + } + + MatcherBase(const MatcherBase& other) + : vtable_(other.vtable_), buffer_(other.buffer_) { + if (IsShared()) buffer_.shared->Ref(); + } + + MatcherBase& operator=(const MatcherBase& other) { + if (this == &other) return *this; + Destroy(); + vtable_ = other.vtable_; + buffer_ = other.buffer_; + if (IsShared()) buffer_.shared->Ref(); + return *this; + } + + MatcherBase(MatcherBase&& other) + : vtable_(other.vtable_), buffer_(other.buffer_) { + other.vtable_ = nullptr; + } + + MatcherBase& operator=(MatcherBase&& other) { + if (this == &other) return *this; + Destroy(); + vtable_ = other.vtable_; + buffer_ = other.buffer_; + other.vtable_ = nullptr; + return *this; + } + + ~MatcherBase() override { Destroy(); } + + private: + struct VTable { + bool (*match_and_explain)(const MatcherBase&, const T&, + MatchResultListener*); + void (*describe)(const MatcherBase&, std::ostream*, bool negation); + // Returns the captured object if it implements the interface, otherwise + // returns the MatcherBase itself. + const MatcherDescriberInterface* (*get_describer)(const MatcherBase&); + // Called on shared instances when the reference count reaches 0. + void (*shared_destroy)(SharedPayloadBase*); + }; + + bool IsShared() const { + return vtable_ != nullptr && vtable_->shared_destroy != nullptr; + } + + // If the implementation uses a listener, call that. + template <typename P> + static auto MatchAndExplainImpl(const MatcherBase& m, const T& value, + MatchResultListener* listener) + -> decltype(P::Get(m).MatchAndExplain(value, listener->stream())) { + return P::Get(m).MatchAndExplain(value, listener->stream()); + } + + template <typename P> + static auto MatchAndExplainImpl(const MatcherBase& m, const T& value, + MatchResultListener* listener) + -> decltype(P::Get(m).MatchAndExplain(value, listener)) { + return P::Get(m).MatchAndExplain(value, listener); + } + + template <typename P> + static void DescribeImpl(const MatcherBase& m, std::ostream* os, + bool negation) { + if (negation) { + P::Get(m).DescribeNegationTo(os); + } else { + P::Get(m).DescribeTo(os); + } + } + + template <typename P> + static const MatcherDescriberInterface* GetDescriberImpl( + const MatcherBase& m) { + // If the impl is a MatcherDescriberInterface, then return it. + // Otherwise use MatcherBase itself. + // This allows us to implement the GetDescriber() function without support + // from the impl, but some users really want to get their impl back when + // they call GetDescriber(). + // We use std::get on a tuple as a workaround of not having `if constexpr`. + return std::get<( + std::is_convertible<decltype(&P::Get(m)), + const MatcherDescriberInterface*>::value + ? 1 + : 0)>(std::make_tuple(&m, &P::Get(m))); + } + + template <typename P> + const VTable* GetVTable() { + static constexpr VTable kVTable = {&MatchAndExplainImpl<P>, + &DescribeImpl<P>, &GetDescriberImpl<P>, + P::shared_destroy}; + return &kVTable; + } + + union Buffer { + // Add some types to give Buffer some common alignment/size use cases. + void* ptr; + double d; + int64_t i; + // And add one for the out-of-line cases. + SharedPayloadBase* shared; + }; + + void Destroy() { + if (IsShared() && buffer_.shared->Unref()) { + vtable_->shared_destroy(buffer_.shared); + } + } + + template <typename M> + static constexpr bool IsInlined() { + return sizeof(M) <= sizeof(Buffer) && alignof(M) <= alignof(Buffer) && + is_trivially_copy_constructible<M>::value && + std::is_trivially_destructible<M>::value; + } + + template <typename M, bool = MatcherBase::IsInlined<M>()> + struct ValuePolicy { + static const M& Get(const MatcherBase& m) { + // When inlined along with Init, need to be explicit to avoid violating + // strict aliasing rules. + const M *ptr = static_cast<const M*>( + static_cast<const void*>(&m.buffer_)); + return *ptr; + } + static void Init(MatcherBase& m, M impl) { + ::new (static_cast<void*>(&m.buffer_)) M(impl); + } + static constexpr auto shared_destroy = nullptr; + }; + + template <typename M> + struct ValuePolicy<M, false> { + using Shared = SharedPayload<M>; + static const M& Get(const MatcherBase& m) { + return static_cast<Shared*>(m.buffer_.shared)->value; + } + template <typename Arg> + static void Init(MatcherBase& m, Arg&& arg) { + m.buffer_.shared = new Shared(std::forward<Arg>(arg)); + } + static constexpr auto shared_destroy = &Shared::Destroy; + }; + + template <typename U, bool B> + struct ValuePolicy<const MatcherInterface<U>*, B> { + using M = const MatcherInterface<U>; + using Shared = SharedPayload<std::unique_ptr<M>>; + static const M& Get(const MatcherBase& m) { + return *static_cast<Shared*>(m.buffer_.shared)->value; + } + static void Init(MatcherBase& m, M* impl) { + m.buffer_.shared = new Shared(std::unique_ptr<M>(impl)); + } + + static constexpr auto shared_destroy = &Shared::Destroy; + }; + + template <typename M> + void Init(M&& m) { + using MM = typename std::decay<M>::type; + using Policy = ValuePolicy<MM>; + vtable_ = GetVTable<Policy>(); + Policy::Init(*this, std::forward<M>(m)); + } + + const VTable* vtable_; + Buffer buffer_; +}; + +} // namespace internal + +// A Matcher<T> is a copyable and IMMUTABLE (except by assignment) +// object that can check whether a value of type T matches. The +// implementation of Matcher<T> is just a std::shared_ptr to const +// MatcherInterface<T>. Don't inherit from Matcher! +template <typename T> +class Matcher : public internal::MatcherBase<T> { + public: + // Constructs a null matcher. Needed for storing Matcher objects in STL + // containers. A default-constructed matcher is not yet initialized. You + // cannot use it until a valid value has been assigned to it. + explicit Matcher() {} // NOLINT + + // Constructs a matcher from its implementation. + explicit Matcher(const MatcherInterface<const T&>* impl) + : internal::MatcherBase<T>(impl) {} + + template <typename U> + explicit Matcher( + const MatcherInterface<U>* impl, + typename std::enable_if<!std::is_same<U, const U&>::value>::type* = + nullptr) + : internal::MatcherBase<T>(impl) {} + + template <typename M, typename = typename std::remove_reference< + M>::type::is_gtest_matcher> + Matcher(M&& m) : internal::MatcherBase<T>(std::forward<M>(m)) {} // NOLINT + + // Implicit constructor here allows people to write + // EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes + Matcher(T value); // NOLINT +}; + +// The following two specializations allow the user to write str +// instead of Eq(str) and "foo" instead of Eq("foo") when a std::string +// matcher is expected. +template <> +class GTEST_API_ Matcher<const std::string&> + : public internal::MatcherBase<const std::string&> { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface<const std::string&>* impl) + : internal::MatcherBase<const std::string&>(impl) {} + + template <typename M, typename = typename std::remove_reference< + M>::type::is_gtest_matcher> + Matcher(M&& m) // NOLINT + : internal::MatcherBase<const std::string&>(std::forward<M>(m)) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a std::string object. + Matcher(const std::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +template <> +class GTEST_API_ Matcher<std::string> + : public internal::MatcherBase<std::string> { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface<const std::string&>* impl) + : internal::MatcherBase<std::string>(impl) {} + explicit Matcher(const MatcherInterface<std::string>* impl) + : internal::MatcherBase<std::string>(impl) {} + + template <typename M, typename = typename std::remove_reference< + M>::type::is_gtest_matcher> + Matcher(M&& m) // NOLINT + : internal::MatcherBase<std::string>(std::forward<M>(m)) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const std::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +#if GTEST_INTERNAL_HAS_STRING_VIEW +// The following two specializations allow the user to write str +// instead of Eq(str) and "foo" instead of Eq("foo") when a absl::string_view +// matcher is expected. +template <> +class GTEST_API_ Matcher<const internal::StringView&> + : public internal::MatcherBase<const internal::StringView&> { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface<const internal::StringView&>* impl) + : internal::MatcherBase<const internal::StringView&>(impl) {} + + template <typename M, typename = typename std::remove_reference< + M>::type::is_gtest_matcher> + Matcher(M&& m) // NOLINT + : internal::MatcherBase<const internal::StringView&>(std::forward<M>(m)) { + } + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a std::string object. + Matcher(const std::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT + + // Allows the user to pass absl::string_views or std::string_views directly. + Matcher(internal::StringView s); // NOLINT +}; + +template <> +class GTEST_API_ Matcher<internal::StringView> + : public internal::MatcherBase<internal::StringView> { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface<const internal::StringView&>* impl) + : internal::MatcherBase<internal::StringView>(impl) {} + explicit Matcher(const MatcherInterface<internal::StringView>* impl) + : internal::MatcherBase<internal::StringView>(impl) {} + + template <typename M, typename = typename std::remove_reference< + M>::type::is_gtest_matcher> + Matcher(M&& m) // NOLINT + : internal::MatcherBase<internal::StringView>(std::forward<M>(m)) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a std::string object. + Matcher(const std::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT + + // Allows the user to pass absl::string_views or std::string_views directly. + Matcher(internal::StringView s); // NOLINT +}; +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + +// Prints a matcher in a human-readable format. +template <typename T> +std::ostream& operator<<(std::ostream& os, const Matcher<T>& matcher) { + matcher.DescribeTo(&os); + return os; +} + +// The PolymorphicMatcher class template makes it easy to implement a +// polymorphic matcher (i.e. a matcher that can match values of more +// than one type, e.g. Eq(n) and NotNull()). +// +// To define a polymorphic matcher, a user should provide an Impl +// class that has a DescribeTo() method and a DescribeNegationTo() +// method, and define a member function (or member function template) +// +// bool MatchAndExplain(const Value& value, +// MatchResultListener* listener) const; +// +// See the definition of NotNull() for a complete example. +template <class Impl> +class PolymorphicMatcher { + public: + explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {} + + // Returns a mutable reference to the underlying matcher + // implementation object. + Impl& mutable_impl() { return impl_; } + + // Returns an immutable reference to the underlying matcher + // implementation object. + const Impl& impl() const { return impl_; } + + template <typename T> + operator Matcher<T>() const { + return Matcher<T>(new MonomorphicImpl<const T&>(impl_)); + } + + private: + template <typename T> + class MonomorphicImpl : public MatcherInterface<T> { + public: + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + void DescribeTo(::std::ostream* os) const override { impl_.DescribeTo(os); } + + void DescribeNegationTo(::std::ostream* os) const override { + impl_.DescribeNegationTo(os); + } + + bool MatchAndExplain(T x, MatchResultListener* listener) const override { + return impl_.MatchAndExplain(x, listener); + } + + private: + const Impl impl_; + }; + + Impl impl_; +}; + +// Creates a matcher from its implementation. +// DEPRECATED: Especially in the generic code, prefer: +// Matcher<T>(new MyMatcherImpl<const T&>(...)); +// +// MakeMatcher may create a Matcher that accepts its argument by value, which +// leads to unnecessary copies & lack of support for non-copyable types. +template <typename T> +inline Matcher<T> MakeMatcher(const MatcherInterface<T>* impl) { + return Matcher<T>(impl); +} + +// Creates a polymorphic matcher from its implementation. This is +// easier to use than the PolymorphicMatcher<Impl> constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicMatcher(foo); +// vs +// PolymorphicMatcher<TypeOfFoo>(foo); +template <class Impl> +inline PolymorphicMatcher<Impl> MakePolymorphicMatcher(const Impl& impl) { + return PolymorphicMatcher<Impl>(impl); +} + +namespace internal { +// Implements a matcher that compares a given value with a +// pre-supplied value using one of the ==, <=, <, etc, operators. The +// two values being compared don't have to have the same type. +// +// The matcher defined here is polymorphic (for example, Eq(5) can be +// used to match an int, a short, a double, etc). Therefore we use +// a template type conversion operator in the implementation. +// +// The following template definition assumes that the Rhs parameter is +// a "bare" type (i.e. neither 'const T' nor 'T&'). +template <typename D, typename Rhs, typename Op> +class ComparisonBase { + public: + explicit ComparisonBase(const Rhs& rhs) : rhs_(rhs) {} + + using is_gtest_matcher = void; + + template <typename Lhs> + bool MatchAndExplain(const Lhs& lhs, std::ostream*) const { + return Op()(lhs, Unwrap(rhs_)); + } + void DescribeTo(std::ostream* os) const { + *os << D::Desc() << " "; + UniversalPrint(Unwrap(rhs_), os); + } + void DescribeNegationTo(std::ostream* os) const { + *os << D::NegatedDesc() << " "; + UniversalPrint(Unwrap(rhs_), os); + } + + private: + template <typename T> + static const T& Unwrap(const T& v) { + return v; + } + template <typename T> + static const T& Unwrap(std::reference_wrapper<T> v) { + return v; + } + + Rhs rhs_; +}; + +template <typename Rhs> +class EqMatcher : public ComparisonBase<EqMatcher<Rhs>, Rhs, AnyEq> { + public: + explicit EqMatcher(const Rhs& rhs) + : ComparisonBase<EqMatcher<Rhs>, Rhs, AnyEq>(rhs) { } + static const char* Desc() { return "is equal to"; } + static const char* NegatedDesc() { return "isn't equal to"; } +}; +template <typename Rhs> +class NeMatcher : public ComparisonBase<NeMatcher<Rhs>, Rhs, AnyNe> { + public: + explicit NeMatcher(const Rhs& rhs) + : ComparisonBase<NeMatcher<Rhs>, Rhs, AnyNe>(rhs) { } + static const char* Desc() { return "isn't equal to"; } + static const char* NegatedDesc() { return "is equal to"; } +}; +template <typename Rhs> +class LtMatcher : public ComparisonBase<LtMatcher<Rhs>, Rhs, AnyLt> { + public: + explicit LtMatcher(const Rhs& rhs) + : ComparisonBase<LtMatcher<Rhs>, Rhs, AnyLt>(rhs) { } + static const char* Desc() { return "is <"; } + static const char* NegatedDesc() { return "isn't <"; } +}; +template <typename Rhs> +class GtMatcher : public ComparisonBase<GtMatcher<Rhs>, Rhs, AnyGt> { + public: + explicit GtMatcher(const Rhs& rhs) + : ComparisonBase<GtMatcher<Rhs>, Rhs, AnyGt>(rhs) { } + static const char* Desc() { return "is >"; } + static const char* NegatedDesc() { return "isn't >"; } +}; +template <typename Rhs> +class LeMatcher : public ComparisonBase<LeMatcher<Rhs>, Rhs, AnyLe> { + public: + explicit LeMatcher(const Rhs& rhs) + : ComparisonBase<LeMatcher<Rhs>, Rhs, AnyLe>(rhs) { } + static const char* Desc() { return "is <="; } + static const char* NegatedDesc() { return "isn't <="; } +}; +template <typename Rhs> +class GeMatcher : public ComparisonBase<GeMatcher<Rhs>, Rhs, AnyGe> { + public: + explicit GeMatcher(const Rhs& rhs) + : ComparisonBase<GeMatcher<Rhs>, Rhs, AnyGe>(rhs) { } + static const char* Desc() { return "is >="; } + static const char* NegatedDesc() { return "isn't >="; } +}; + +template <typename T, typename = typename std::enable_if< + std::is_constructible<std::string, T>::value>::type> +using StringLike = T; + +// Implements polymorphic matchers MatchesRegex(regex) and +// ContainsRegex(regex), which can be used as a Matcher<T> as long as +// T can be converted to a string. +class MatchesRegexMatcher { + public: + MatchesRegexMatcher(const RE* regex, bool full_match) + : regex_(regex), full_match_(full_match) {} + +#if GTEST_INTERNAL_HAS_STRING_VIEW + bool MatchAndExplain(const internal::StringView& s, + MatchResultListener* listener) const { + return MatchAndExplain(std::string(s), listener); + } +#endif // GTEST_INTERNAL_HAS_STRING_VIEW + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template <typename CharType> + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != nullptr && MatchAndExplain(std::string(s), listener); + } + + // Matches anything that can convert to std::string. + // + // This is a template, not just a plain function with const std::string&, + // because absl::string_view has some interfering non-explicit constructors. + template <class MatcheeStringType> + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const std::string& s2(s); + return full_match_ ? RE::FullMatch(s2, *regex_) + : RE::PartialMatch(s2, *regex_); + } + + void DescribeTo(::std::ostream* os) const { + *os << (full_match_ ? "matches" : "contains") << " regular expression "; + UniversalPrinter<std::string>::Print(regex_->pattern(), os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't " << (full_match_ ? "match" : "contain") + << " regular expression "; + UniversalPrinter<std::string>::Print(regex_->pattern(), os); + } + + private: + const std::shared_ptr<const RE> regex_; + const bool full_match_; +}; +} // namespace internal + +// Matches a string that fully matches regular expression 'regex'. +// The matcher takes ownership of 'regex'. +inline PolymorphicMatcher<internal::MatchesRegexMatcher> MatchesRegex( + const internal::RE* regex) { + return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, true)); +} +template <typename T = std::string> +PolymorphicMatcher<internal::MatchesRegexMatcher> MatchesRegex( + const internal::StringLike<T>& regex) { + return MatchesRegex(new internal::RE(std::string(regex))); +} + +// Matches a string that contains regular expression 'regex'. +// The matcher takes ownership of 'regex'. +inline PolymorphicMatcher<internal::MatchesRegexMatcher> ContainsRegex( + const internal::RE* regex) { + return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, false)); +} +template <typename T = std::string> +PolymorphicMatcher<internal::MatchesRegexMatcher> ContainsRegex( + const internal::StringLike<T>& regex) { + return ContainsRegex(new internal::RE(std::string(regex))); +} + +// Creates a polymorphic matcher that matches anything equal to x. +// Note: if the parameter of Eq() were declared as const T&, Eq("foo") +// wouldn't compile. +template <typename T> +inline internal::EqMatcher<T> Eq(T x) { return internal::EqMatcher<T>(x); } + +// Constructs a Matcher<T> from a 'value' of type T. The constructed +// matcher matches any value that's equal to 'value'. +template <typename T> +Matcher<T>::Matcher(T value) { *this = Eq(value); } + +// Creates a monomorphic matcher that matches anything with type Lhs +// and equal to rhs. A user may need to use this instead of Eq(...) +// in order to resolve an overloading ambiguity. +// +// TypedEq<T>(x) is just a convenient short-hand for Matcher<T>(Eq(x)) +// or Matcher<T>(x), but more readable than the latter. +// +// We could define similar monomorphic matchers for other comparison +// operations (e.g. TypedLt, TypedGe, and etc), but decided not to do +// it yet as those are used much less than Eq() in practice. A user +// can always write Matcher<T>(Lt(5)) to be explicit about the type, +// for example. +template <typename Lhs, typename Rhs> +inline Matcher<Lhs> TypedEq(const Rhs& rhs) { return Eq(rhs); } + +// Creates a polymorphic matcher that matches anything >= x. +template <typename Rhs> +inline internal::GeMatcher<Rhs> Ge(Rhs x) { + return internal::GeMatcher<Rhs>(x); +} + +// Creates a polymorphic matcher that matches anything > x. +template <typename Rhs> +inline internal::GtMatcher<Rhs> Gt(Rhs x) { + return internal::GtMatcher<Rhs>(x); +} + +// Creates a polymorphic matcher that matches anything <= x. +template <typename Rhs> +inline internal::LeMatcher<Rhs> Le(Rhs x) { + return internal::LeMatcher<Rhs>(x); +} + +// Creates a polymorphic matcher that matches anything < x. +template <typename Rhs> +inline internal::LtMatcher<Rhs> Lt(Rhs x) { + return internal::LtMatcher<Rhs>(x); +} + +// Creates a polymorphic matcher that matches anything != x. +template <typename Rhs> +inline internal::NeMatcher<Rhs> Ne(Rhs x) { + return internal::NeMatcher<Rhs>(x); +} +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 5046 + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_ + +#include <stdio.h> +#include <memory> + +namespace testing { +namespace internal { + +GTEST_DECLARE_string_(internal_run_death_test); + +// Names of the flags (needed for parsing Google Test flags). +const char kDeathTestStyleFlag[] = "death_test_style"; +const char kDeathTestUseFork[] = "death_test_use_fork"; +const char kInternalRunDeathTestFlag[] = "internal_run_death_test"; + +#if GTEST_HAS_DEATH_TEST + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +// DeathTest is a class that hides much of the complexity of the +// GTEST_DEATH_TEST_ macro. It is abstract; its static Create method +// returns a concrete class that depends on the prevailing death test +// style, as defined by the --gtest_death_test_style and/or +// --gtest_internal_run_death_test flags. + +// In describing the results of death tests, these terms are used with +// the corresponding definitions: +// +// exit status: The integer exit information in the format specified +// by wait(2) +// exit code: The integer code passed to exit(3), _exit(2), or +// returned from main() +class GTEST_API_ DeathTest { + public: + // Create returns false if there was an error determining the + // appropriate action to take for the current death test; for example, + // if the gtest_death_test_style flag is set to an invalid value. + // The LastMessage method will return a more detailed message in that + // case. Otherwise, the DeathTest pointer pointed to by the "test" + // argument is set. If the death test should be skipped, the pointer + // is set to NULL; otherwise, it is set to the address of a new concrete + // DeathTest object that controls the execution of the current test. + static bool Create(const char* statement, Matcher<const std::string&> matcher, + const char* file, int line, DeathTest** test); + DeathTest(); + virtual ~DeathTest() { } + + // A helper class that aborts a death test when it's deleted. + class ReturnSentinel { + public: + explicit ReturnSentinel(DeathTest* test) : test_(test) { } + ~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); } + private: + DeathTest* const test_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ReturnSentinel); + } GTEST_ATTRIBUTE_UNUSED_; + + // An enumeration of possible roles that may be taken when a death + // test is encountered. EXECUTE means that the death test logic should + // be executed immediately. OVERSEE means that the program should prepare + // the appropriate environment for a child process to execute the death + // test, then wait for it to complete. + enum TestRole { OVERSEE_TEST, EXECUTE_TEST }; + + // An enumeration of the three reasons that a test might be aborted. + enum AbortReason { + TEST_ENCOUNTERED_RETURN_STATEMENT, + TEST_THREW_EXCEPTION, + TEST_DID_NOT_DIE + }; + + // Assumes one of the above roles. + virtual TestRole AssumeRole() = 0; + + // Waits for the death test to finish and returns its status. + virtual int Wait() = 0; + + // Returns true if the death test passed; that is, the test process + // exited during the test, its exit status matches a user-supplied + // predicate, and its stderr output matches a user-supplied regular + // expression. + // The user-supplied predicate may be a macro expression rather + // than a function pointer or functor, or else Wait and Passed could + // be combined. + virtual bool Passed(bool exit_status_ok) = 0; + + // Signals that the death test did not die as expected. + virtual void Abort(AbortReason reason) = 0; + + // Returns a human-readable outcome message regarding the outcome of + // the last death test. + static const char* LastMessage(); + + static void set_last_death_test_message(const std::string& message); + + private: + // A string containing a description of the outcome of the last death test. + static std::string last_death_test_message_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DeathTest); +}; + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +// Factory interface for death tests. May be mocked out for testing. +class DeathTestFactory { + public: + virtual ~DeathTestFactory() { } + virtual bool Create(const char* statement, + Matcher<const std::string&> matcher, const char* file, + int line, DeathTest** test) = 0; +}; + +// A concrete DeathTestFactory implementation for normal use. +class DefaultDeathTestFactory : public DeathTestFactory { + public: + bool Create(const char* statement, Matcher<const std::string&> matcher, + const char* file, int line, DeathTest** test) override; +}; + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +GTEST_API_ bool ExitedUnsuccessfully(int exit_status); + +// A string passed to EXPECT_DEATH (etc.) is caught by one of these overloads +// and interpreted as a regex (rather than an Eq matcher) for legacy +// compatibility. +inline Matcher<const ::std::string&> MakeDeathTestMatcher( + ::testing::internal::RE regex) { + return ContainsRegex(regex.pattern()); +} +inline Matcher<const ::std::string&> MakeDeathTestMatcher(const char* regex) { + return ContainsRegex(regex); +} +inline Matcher<const ::std::string&> MakeDeathTestMatcher( + const ::std::string& regex) { + return ContainsRegex(regex); +} + +// If a Matcher<const ::std::string&> is passed to EXPECT_DEATH (etc.), it's +// used directly. +inline Matcher<const ::std::string&> MakeDeathTestMatcher( + Matcher<const ::std::string&> matcher) { + return matcher; +} + +// Traps C++ exceptions escaping statement and reports them as test +// failures. Note that trapping SEH exceptions is not implemented here. +# if GTEST_HAS_EXCEPTIONS +# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (const ::std::exception& gtest_exception) { \ + fprintf(\ + stderr, \ + "\n%s: Caught std::exception-derived exception escaping the " \ + "death test statement. Exception message: %s\n", \ + ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \ + gtest_exception.what()); \ + fflush(stderr); \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } catch (...) { \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } + +# else +# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) + +# endif + +// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*, +// ASSERT_EXIT*, and EXPECT_EXIT*. +#define GTEST_DEATH_TEST_(statement, predicate, regex_or_matcher, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + ::testing::internal::DeathTest* gtest_dt; \ + if (!::testing::internal::DeathTest::Create( \ + #statement, \ + ::testing::internal::MakeDeathTestMatcher(regex_or_matcher), \ + __FILE__, __LINE__, >est_dt)) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + if (gtest_dt != nullptr) { \ + std::unique_ptr< ::testing::internal::DeathTest> gtest_dt_ptr(gtest_dt); \ + switch (gtest_dt->AssumeRole()) { \ + case ::testing::internal::DeathTest::OVERSEE_TEST: \ + if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + break; \ + case ::testing::internal::DeathTest::EXECUTE_TEST: { \ + ::testing::internal::DeathTest::ReturnSentinel gtest_sentinel( \ + gtest_dt); \ + GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \ + gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \ + break; \ + } \ + default: \ + break; \ + } \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__) \ + : fail(::testing::internal::DeathTest::LastMessage()) +// The symbol "fail" here expands to something into which a message +// can be streamed. + +// This macro is for implementing ASSERT/EXPECT_DEBUG_DEATH when compiled in +// NDEBUG mode. In this case we need the statements to be executed and the macro +// must accept a streamed message even though the message is never printed. +// The regex object is not evaluated, but it is used to prevent "unused" +// warnings and to avoid an expression that doesn't compile in debug mode. +#define GTEST_EXECUTE_STATEMENT_(statement, regex_or_matcher) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } else if (!::testing::internal::AlwaysTrue()) { \ + ::testing::internal::MakeDeathTestMatcher(regex_or_matcher); \ + } else \ + ::testing::Message() + +// A class representing the parsed contents of the +// --gtest_internal_run_death_test flag, as it existed when +// RUN_ALL_TESTS was called. +class InternalRunDeathTestFlag { + public: + InternalRunDeathTestFlag(const std::string& a_file, + int a_line, + int an_index, + int a_write_fd) + : file_(a_file), line_(a_line), index_(an_index), + write_fd_(a_write_fd) {} + + ~InternalRunDeathTestFlag() { + if (write_fd_ >= 0) + posix::Close(write_fd_); + } + + const std::string& file() const { return file_; } + int line() const { return line_; } + int index() const { return index_; } + int write_fd() const { return write_fd_; } + + private: + std::string file_; + int line_; + int index_; + int write_fd_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InternalRunDeathTestFlag); +}; + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag(); + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace internal +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ + +namespace testing { + +// This flag controls the style of death tests. Valid values are "threadsafe", +// meaning that the death test child process will re-execute the test binary +// from the start, running only a single death test, or "fast", +// meaning that the child process will execute the test logic immediately +// after forking. +GTEST_DECLARE_string_(death_test_style); + +#if GTEST_HAS_DEATH_TEST + +namespace internal { + +// Returns a Boolean value indicating whether the caller is currently +// executing in the context of the death test child process. Tools such as +// Valgrind heap checkers may need this to modify their behavior in death +// tests. IMPORTANT: This is an internal utility. Using it may break the +// implementation of death tests. User code MUST NOT use it. +GTEST_API_ bool InDeathTestChild(); + +} // namespace internal + +// The following macros are useful for writing death tests. + +// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is +// executed: +// +// 1. It generates a warning if there is more than one active +// thread. This is because it's safe to fork() or clone() only +// when there is a single thread. +// +// 2. The parent process clone()s a sub-process and runs the death +// test in it; the sub-process exits with code 0 at the end of the +// death test, if it hasn't exited already. +// +// 3. The parent process waits for the sub-process to terminate. +// +// 4. The parent process checks the exit code and error message of +// the sub-process. +// +// Examples: +// +// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number"); +// for (int i = 0; i < 5; i++) { +// EXPECT_DEATH(server.ProcessRequest(i), +// "Invalid request .* in ProcessRequest()") +// << "Failed to die on request " << i; +// } +// +// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting"); +// +// bool KilledBySIGHUP(int exit_code) { +// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP; +// } +// +// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!"); +// +// On the regular expressions used in death tests: +// +// GOOGLETEST_CM0005 DO NOT DELETE +// On POSIX-compliant systems (*nix), we use the <regex.h> library, +// which uses the POSIX extended regex syntax. +// +// On other platforms (e.g. Windows or Mac), we only support a simple regex +// syntax implemented as part of Google Test. This limited +// implementation should be enough most of the time when writing +// death tests; though it lacks many features you can find in PCRE +// or POSIX extended regex syntax. For example, we don't support +// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and +// repetition count ("x{5,7}"), among others. +// +// Below is the syntax that we do support. We chose it to be a +// subset of both PCRE and POSIX extended regex, so it's easy to +// learn wherever you come from. In the following: 'A' denotes a +// literal character, period (.), or a single \\ escape sequence; +// 'x' and 'y' denote regular expressions; 'm' and 'n' are for +// natural numbers. +// +// c matches any literal character c +// \\d matches any decimal digit +// \\D matches any character that's not a decimal digit +// \\f matches \f +// \\n matches \n +// \\r matches \r +// \\s matches any ASCII whitespace, including \n +// \\S matches any character that's not a whitespace +// \\t matches \t +// \\v matches \v +// \\w matches any letter, _, or decimal digit +// \\W matches any character that \\w doesn't match +// \\c matches any literal character c, which must be a punctuation +// . matches any single character except \n +// A? matches 0 or 1 occurrences of A +// A* matches 0 or many occurrences of A +// A+ matches 1 or many occurrences of A +// ^ matches the beginning of a string (not that of each line) +// $ matches the end of a string (not that of each line) +// xy matches x followed by y +// +// If you accidentally use PCRE or POSIX extended regex features +// not implemented by us, you will get a run-time failure. In that +// case, please try to rewrite your regular expression within the +// above syntax. +// +// This implementation is *not* meant to be as highly tuned or robust +// as a compiled regex library, but should perform well enough for a +// death test, which already incurs significant overhead by launching +// a child process. +// +// Known caveats: +// +// A "threadsafe" style death test obtains the path to the test +// program from argv[0] and re-executes it in the sub-process. For +// simplicity, the current implementation doesn't search the PATH +// when launching the sub-process. This means that the user must +// invoke the test program via a path that contains at least one +// path separator (e.g. path/to/foo_test and +// /absolute/path/to/bar_test are fine, but foo_test is not). This +// is rarely a problem as people usually don't put the test binary +// directory in PATH. +// + +// Asserts that a given statement causes the program to exit, with an +// integer exit status that satisfies predicate, and emitting error output +// that matches regex. +# define ASSERT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_) + +// Like ASSERT_EXIT, but continues on to successive tests in the +// test suite, if any: +# define EXPECT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_) + +// Asserts that a given statement causes the program to exit, either by +// explicitly exiting with a nonzero exit code or being killed by a +// signal, and emitting error output that matches regex. +# define ASSERT_DEATH(statement, regex) \ + ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Like ASSERT_DEATH, but continues on to successive tests in the +// test suite, if any: +# define EXPECT_DEATH(statement, regex) \ + EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*: + +// Tests that an exit code describes a normal exit with a given exit code. +class GTEST_API_ ExitedWithCode { + public: + explicit ExitedWithCode(int exit_code); + ExitedWithCode(const ExitedWithCode&) = default; + void operator=(const ExitedWithCode& other) = delete; + bool operator()(int exit_status) const; + private: + const int exit_code_; +}; + +# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA +// Tests that an exit code describes an exit due to termination by a +// given signal. +// GOOGLETEST_CM0006 DO NOT DELETE +class GTEST_API_ KilledBySignal { + public: + explicit KilledBySignal(int signum); + bool operator()(int exit_status) const; + private: + const int signum_; +}; +# endif // !GTEST_OS_WINDOWS + +// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode. +// The death testing framework causes this to have interesting semantics, +// since the sideeffects of the call are only visible in opt mode, and not +// in debug mode. +// +// In practice, this can be used to test functions that utilize the +// LOG(DFATAL) macro using the following style: +// +// int DieInDebugOr12(int* sideeffect) { +// if (sideeffect) { +// *sideeffect = 12; +// } +// LOG(DFATAL) << "death"; +// return 12; +// } +// +// TEST(TestSuite, TestDieOr12WorksInDgbAndOpt) { +// int sideeffect = 0; +// // Only asserts in dbg. +// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death"); +// +// #ifdef NDEBUG +// // opt-mode has sideeffect visible. +// EXPECT_EQ(12, sideeffect); +// #else +// // dbg-mode no visible sideeffect. +// EXPECT_EQ(0, sideeffect); +// #endif +// } +// +// This will assert that DieInDebugReturn12InOpt() crashes in debug +// mode, usually due to a DCHECK or LOG(DFATAL), but returns the +// appropriate fallback value (12 in this case) in opt mode. If you +// need to test that a function has appropriate side-effects in opt +// mode, include assertions against the side-effects. A general +// pattern for this is: +// +// EXPECT_DEBUG_DEATH({ +// // Side-effects here will have an effect after this statement in +// // opt mode, but none in debug mode. +// EXPECT_EQ(12, DieInDebugOr12(&sideeffect)); +// }, "death"); +// +# ifdef NDEBUG + +# define EXPECT_DEBUG_DEATH(statement, regex) \ + GTEST_EXECUTE_STATEMENT_(statement, regex) + +# define ASSERT_DEBUG_DEATH(statement, regex) \ + GTEST_EXECUTE_STATEMENT_(statement, regex) + +# else + +# define EXPECT_DEBUG_DEATH(statement, regex) \ + EXPECT_DEATH(statement, regex) + +# define ASSERT_DEBUG_DEATH(statement, regex) \ + ASSERT_DEATH(statement, regex) + +# endif // NDEBUG for EXPECT_DEBUG_DEATH +#endif // GTEST_HAS_DEATH_TEST + +// This macro is used for implementing macros such as +// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where +// death tests are not supported. Those macros must compile on such systems +// if and only if EXPECT_DEATH and ASSERT_DEATH compile with the same parameters +// on systems that support death tests. This allows one to write such a macro on +// a system that does not support death tests and be sure that it will compile +// on a death-test supporting system. It is exposed publicly so that systems +// that have death-tests with stricter requirements than GTEST_HAS_DEATH_TEST +// can write their own equivalent of EXPECT_DEATH_IF_SUPPORTED and +// ASSERT_DEATH_IF_SUPPORTED. +// +// Parameters: +// statement - A statement that a macro such as EXPECT_DEATH would test +// for program termination. This macro has to make sure this +// statement is compiled but not executed, to ensure that +// EXPECT_DEATH_IF_SUPPORTED compiles with a certain +// parameter if and only if EXPECT_DEATH compiles with it. +// regex - A regex that a macro such as EXPECT_DEATH would use to test +// the output of statement. This parameter has to be +// compiled but not evaluated by this macro, to ensure that +// this macro only accepts expressions that a macro such as +// EXPECT_DEATH would accept. +// terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED +// and a return statement for ASSERT_DEATH_IF_SUPPORTED. +// This ensures that ASSERT_DEATH_IF_SUPPORTED will not +// compile inside functions where ASSERT_DEATH doesn't +// compile. +// +// The branch that has an always false condition is used to ensure that +// statement and regex are compiled (and thus syntactically correct) but +// never executed. The unreachable code macro protects the terminator +// statement from generating an 'unreachable code' warning in case +// statement unconditionally returns or throws. The Message constructor at +// the end allows the syntax of streaming additional messages into the +// macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH. +# define GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, terminator) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + GTEST_LOG_(WARNING) \ + << "Death tests are not supported on this platform.\n" \ + << "Statement '" #statement "' cannot be verified."; \ + } else if (::testing::internal::AlwaysFalse()) { \ + ::testing::internal::RE::PartialMatch(".*", (regex)); \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + terminator; \ + } else \ + ::testing::Message() + +// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and +// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if +// death tests are supported; otherwise they just issue a warning. This is +// useful when you are combining death test assertions with normal test +// assertions in one test. +#if GTEST_HAS_DEATH_TEST +# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + EXPECT_DEATH(statement, regex) +# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + ASSERT_DEATH(statement, regex) +#else +# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, ) +# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, return) +#endif + +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Macros and functions for implementing parameterized tests +// in Google C++ Testing and Mocking Framework (Google Test) +// +// GOOGLETEST_CM0001 DO NOT DELETE +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ + +// Value-parameterized tests allow you to test your code with different +// parameters without writing multiple copies of the same test. +// +// Here is how you use value-parameterized tests: + +#if 0 + +// To write value-parameterized tests, first you should define a fixture +// class. It is usually derived from testing::TestWithParam<T> (see below for +// another inheritance scheme that's sometimes useful in more complicated +// class hierarchies), where the type of your parameter values. +// TestWithParam<T> is itself derived from testing::Test. T can be any +// copyable type. If it's a raw pointer, you are responsible for managing the +// lifespan of the pointed values. + +class FooTest : public ::testing::TestWithParam<const char*> { + // You can implement all the usual class fixture members here. +}; + +// Then, use the TEST_P macro to define as many parameterized tests +// for this fixture as you want. The _P suffix is for "parameterized" +// or "pattern", whichever you prefer to think. + +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam<T> class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} + +// Finally, you can use INSTANTIATE_TEST_SUITE_P to instantiate the test +// case with any set of parameters you want. Google Test defines a number +// of functions for generating test parameters. They return what we call +// (surprise!) parameter generators. Here is a summary of them, which +// are all in the testing namespace: +// +// +// Range(begin, end [, step]) - Yields values {begin, begin+step, +// begin+step+step, ...}. The values do not +// include end. step defaults to 1. +// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. +// ValuesIn(container) - Yields values from a C-style array, an STL +// ValuesIn(begin,end) container, or an iterator range [begin, end). +// Bool() - Yields sequence {false, true}. +// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product +// for the math savvy) of the values generated +// by the N generators. +// +// For more details, see comments at the definitions of these functions below +// in this file. +// +// The following statement will instantiate tests from the FooTest test suite +// each with parameter values "meeny", "miny", and "moe". + +INSTANTIATE_TEST_SUITE_P(InstantiationName, + FooTest, + Values("meeny", "miny", "moe")); + +// To distinguish different instances of the pattern, (yes, you +// can instantiate it more than once) the first argument to the +// INSTANTIATE_TEST_SUITE_P macro is a prefix that will be added to the +// actual test suite name. Remember to pick unique prefixes for different +// instantiations. The tests from the instantiation above will have +// these names: +// +// * InstantiationName/FooTest.DoesBlah/0 for "meeny" +// * InstantiationName/FooTest.DoesBlah/1 for "miny" +// * InstantiationName/FooTest.DoesBlah/2 for "moe" +// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" +// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" +// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" +// +// You can use these names in --gtest_filter. +// +// This statement will instantiate all tests from FooTest again, each +// with parameter values "cat" and "dog": + +const char* pets[] = {"cat", "dog"}; +INSTANTIATE_TEST_SUITE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); + +// The tests from the instantiation above will have these names: +// +// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" +// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" +// +// Please note that INSTANTIATE_TEST_SUITE_P will instantiate all tests +// in the given test suite, whether their definitions come before or +// AFTER the INSTANTIATE_TEST_SUITE_P statement. +// +// Please also note that generator expressions (including parameters to the +// generators) are evaluated in InitGoogleTest(), after main() has started. +// This allows the user on one hand, to adjust generator parameters in order +// to dynamically determine a set of tests to run and on the other hand, +// give the user a chance to inspect the generated tests with Google Test +// reflection API before RUN_ALL_TESTS() is executed. +// +// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc +// for more examples. +// +// In the future, we plan to publish the API for defining new parameter +// generators. But for now this interface remains part of the internal +// implementation and is subject to change. +// +// +// A parameterized test fixture must be derived from testing::Test and from +// testing::WithParamInterface<T>, where T is the type of the parameter +// values. Inheriting from TestWithParam<T> satisfies that requirement because +// TestWithParam<T> inherits from both Test and WithParamInterface. In more +// complicated hierarchies, however, it is occasionally useful to inherit +// separately from Test and WithParamInterface. For example: + +class BaseTest : public ::testing::Test { + // You can inherit all the usual members for a non-parameterized test + // fixture here. +}; + +class DerivedTest : public BaseTest, public ::testing::WithParamInterface<int> { + // The usual test fixture members go here too. +}; + +TEST_F(BaseTest, HasFoo) { + // This is an ordinary non-parameterized test. +} + +TEST_P(DerivedTest, DoesBlah) { + // GetParam works just the same here as if you inherit from TestWithParam. + EXPECT_TRUE(foo.Blah(GetParam())); +} + +#endif // 0 + +#include <iterator> +#include <utility> + +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Type and function utilities for implementing parameterized tests. + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ +#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ + +#include <ctype.h> + +#include <cassert> +#include <iterator> +#include <memory> +#include <set> +#include <tuple> +#include <type_traits> +#include <utility> +#include <vector> + +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ + +#include <iosfwd> +#include <vector> + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// A copyable object representing the result of a test part (i.e. an +// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()). +// +// Don't inherit from TestPartResult as its destructor is not virtual. +class GTEST_API_ TestPartResult { + public: + // The possible outcomes of a test part (i.e. an assertion or an + // explicit SUCCEED(), FAIL(), or ADD_FAILURE()). + enum Type { + kSuccess, // Succeeded. + kNonFatalFailure, // Failed but the test can continue. + kFatalFailure, // Failed and the test should be terminated. + kSkip // Skipped. + }; + + // C'tor. TestPartResult does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestPartResult object. + TestPartResult(Type a_type, const char* a_file_name, int a_line_number, + const char* a_message) + : type_(a_type), + file_name_(a_file_name == nullptr ? "" : a_file_name), + line_number_(a_line_number), + summary_(ExtractSummary(a_message)), + message_(a_message) {} + + // Gets the outcome of the test part. + Type type() const { return type_; } + + // Gets the name of the source file where the test part took place, or + // NULL if it's unknown. + const char* file_name() const { + return file_name_.empty() ? nullptr : file_name_.c_str(); + } + + // Gets the line in the source file where the test part took place, + // or -1 if it's unknown. + int line_number() const { return line_number_; } + + // Gets the summary of the failure message. + const char* summary() const { return summary_.c_str(); } + + // Gets the message associated with the test part. + const char* message() const { return message_.c_str(); } + + // Returns true if and only if the test part was skipped. + bool skipped() const { return type_ == kSkip; } + + // Returns true if and only if the test part passed. + bool passed() const { return type_ == kSuccess; } + + // Returns true if and only if the test part non-fatally failed. + bool nonfatally_failed() const { return type_ == kNonFatalFailure; } + + // Returns true if and only if the test part fatally failed. + bool fatally_failed() const { return type_ == kFatalFailure; } + + // Returns true if and only if the test part failed. + bool failed() const { return fatally_failed() || nonfatally_failed(); } + + private: + Type type_; + + // Gets the summary of the failure message by omitting the stack + // trace in it. + static std::string ExtractSummary(const char* message); + + // The name of the source file where the test part took place, or + // "" if the source file is unknown. + std::string file_name_; + // The line in the source file where the test part took place, or -1 + // if the line number is unknown. + int line_number_; + std::string summary_; // The test failure summary. + std::string message_; // The test failure message. +}; + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result); + +// An array of TestPartResult objects. +// +// Don't inherit from TestPartResultArray as its destructor is not +// virtual. +class GTEST_API_ TestPartResultArray { + public: + TestPartResultArray() {} + + // Appends the given TestPartResult to the array. + void Append(const TestPartResult& result); + + // Returns the TestPartResult at the given index (0-based). + const TestPartResult& GetTestPartResult(int index) const; + + // Returns the number of TestPartResult objects in the array. + int size() const; + + private: + std::vector<TestPartResult> array_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestPartResultArray); +}; + +// This interface knows how to report a test part result. +class GTEST_API_ TestPartResultReporterInterface { + public: + virtual ~TestPartResultReporterInterface() {} + + virtual void ReportTestPartResult(const TestPartResult& result) = 0; +}; + +namespace internal { + +// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a +// statement generates new fatal failures. To do so it registers itself as the +// current test part result reporter. Besides checking if fatal failures were +// reported, it only delegates the reporting to the former result reporter. +// The original result reporter is restored in the destructor. +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +class GTEST_API_ HasNewFatalFailureHelper + : public TestPartResultReporterInterface { + public: + HasNewFatalFailureHelper(); + ~HasNewFatalFailureHelper() override; + void ReportTestPartResult(const TestPartResult& result) override; + bool has_new_fatal_failure() const { return has_new_fatal_failure_; } + private: + bool has_new_fatal_failure_; + TestPartResultReporterInterface* original_reporter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(HasNewFatalFailureHelper); +}; + +} // namespace internal + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ + +namespace testing { +// Input to a parameterized test name generator, describing a test parameter. +// Consists of the parameter value and the integer parameter index. +template <class ParamType> +struct TestParamInfo { + TestParamInfo(const ParamType& a_param, size_t an_index) : + param(a_param), + index(an_index) {} + ParamType param; + size_t index; +}; + +// A builtin parameterized test name generator which returns the result of +// testing::PrintToString. +struct PrintToStringParamName { + template <class ParamType> + std::string operator()(const TestParamInfo<ParamType>& info) const { + return PrintToString(info.param); + } +}; + +namespace internal { + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// Utility Functions + +// Outputs a message explaining invalid registration of different +// fixture class for the same test suite. This may happen when +// TEST_P macro is used to define two tests with the same name +// but in different namespaces. +GTEST_API_ void ReportInvalidTestSuiteType(const char* test_suite_name, + CodeLocation code_location); + +template <typename> class ParamGeneratorInterface; +template <typename> class ParamGenerator; + +// Interface for iterating over elements provided by an implementation +// of ParamGeneratorInterface<T>. +template <typename T> +class ParamIteratorInterface { + public: + virtual ~ParamIteratorInterface() {} + // A pointer to the base generator instance. + // Used only for the purposes of iterator comparison + // to make sure that two iterators belong to the same generator. + virtual const ParamGeneratorInterface<T>* BaseGenerator() const = 0; + // Advances iterator to point to the next element + // provided by the generator. The caller is responsible + // for not calling Advance() on an iterator equal to + // BaseGenerator()->End(). + virtual void Advance() = 0; + // Clones the iterator object. Used for implementing copy semantics + // of ParamIterator<T>. + virtual ParamIteratorInterface* Clone() const = 0; + // Dereferences the current iterator and provides (read-only) access + // to the pointed value. It is the caller's responsibility not to call + // Current() on an iterator equal to BaseGenerator()->End(). + // Used for implementing ParamGenerator<T>::operator*(). + virtual const T* Current() const = 0; + // Determines whether the given iterator and other point to the same + // element in the sequence generated by the generator. + // Used for implementing ParamGenerator<T>::operator==(). + virtual bool Equals(const ParamIteratorInterface& other) const = 0; +}; + +// Class iterating over elements provided by an implementation of +// ParamGeneratorInterface<T>. It wraps ParamIteratorInterface<T> +// and implements the const forward iterator concept. +template <typename T> +class ParamIterator { + public: + typedef T value_type; + typedef const T& reference; + typedef ptrdiff_t difference_type; + + // ParamIterator assumes ownership of the impl_ pointer. + ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {} + ParamIterator& operator=(const ParamIterator& other) { + if (this != &other) + impl_.reset(other.impl_->Clone()); + return *this; + } + + const T& operator*() const { return *impl_->Current(); } + const T* operator->() const { return impl_->Current(); } + // Prefix version of operator++. + ParamIterator& operator++() { + impl_->Advance(); + return *this; + } + // Postfix version of operator++. + ParamIterator operator++(int /*unused*/) { + ParamIteratorInterface<T>* clone = impl_->Clone(); + impl_->Advance(); + return ParamIterator(clone); + } + bool operator==(const ParamIterator& other) const { + return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_); + } + bool operator!=(const ParamIterator& other) const { + return !(*this == other); + } + + private: + friend class ParamGenerator<T>; + explicit ParamIterator(ParamIteratorInterface<T>* impl) : impl_(impl) {} + std::unique_ptr<ParamIteratorInterface<T> > impl_; +}; + +// ParamGeneratorInterface<T> is the binary interface to access generators +// defined in other translation units. +template <typename T> +class ParamGeneratorInterface { + public: + typedef T ParamType; + + virtual ~ParamGeneratorInterface() {} + + // Generator interface definition + virtual ParamIteratorInterface<T>* Begin() const = 0; + virtual ParamIteratorInterface<T>* End() const = 0; +}; + +// Wraps ParamGeneratorInterface<T> and provides general generator syntax +// compatible with the STL Container concept. +// This class implements copy initialization semantics and the contained +// ParamGeneratorInterface<T> instance is shared among all copies +// of the original object. This is possible because that instance is immutable. +template<typename T> +class ParamGenerator { + public: + typedef ParamIterator<T> iterator; + + explicit ParamGenerator(ParamGeneratorInterface<T>* impl) : impl_(impl) {} + ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {} + + ParamGenerator& operator=(const ParamGenerator& other) { + impl_ = other.impl_; + return *this; + } + + iterator begin() const { return iterator(impl_->Begin()); } + iterator end() const { return iterator(impl_->End()); } + + private: + std::shared_ptr<const ParamGeneratorInterface<T> > impl_; +}; + +// Generates values from a range of two comparable values. Can be used to +// generate sequences of user-defined types that implement operator+() and +// operator<(). +// This class is used in the Range() function. +template <typename T, typename IncrementT> +class RangeGenerator : public ParamGeneratorInterface<T> { + public: + RangeGenerator(T begin, T end, IncrementT step) + : begin_(begin), end_(end), + step_(step), end_index_(CalculateEndIndex(begin, end, step)) {} + ~RangeGenerator() override {} + + ParamIteratorInterface<T>* Begin() const override { + return new Iterator(this, begin_, 0, step_); + } + ParamIteratorInterface<T>* End() const override { + return new Iterator(this, end_, end_index_, step_); + } + + private: + class Iterator : public ParamIteratorInterface<T> { + public: + Iterator(const ParamGeneratorInterface<T>* base, T value, int index, + IncrementT step) + : base_(base), value_(value), index_(index), step_(step) {} + ~Iterator() override {} + + const ParamGeneratorInterface<T>* BaseGenerator() const override { + return base_; + } + void Advance() override { + value_ = static_cast<T>(value_ + step_); + index_++; + } + ParamIteratorInterface<T>* Clone() const override { + return new Iterator(*this); + } + const T* Current() const override { return &value_; } + bool Equals(const ParamIteratorInterface<T>& other) const override { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const int other_index = + CheckedDowncastToActualType<const Iterator>(&other)->index_; + return index_ == other_index; + } + + private: + Iterator(const Iterator& other) + : ParamIteratorInterface<T>(), + base_(other.base_), value_(other.value_), index_(other.index_), + step_(other.step_) {} + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface<T>* const base_; + T value_; + int index_; + const IncrementT step_; + }; // class RangeGenerator::Iterator + + static int CalculateEndIndex(const T& begin, + const T& end, + const IncrementT& step) { + int end_index = 0; + for (T i = begin; i < end; i = static_cast<T>(i + step)) + end_index++; + return end_index; + } + + // No implementation - assignment is unsupported. + void operator=(const RangeGenerator& other); + + const T begin_; + const T end_; + const IncrementT step_; + // The index for the end() iterator. All the elements in the generated + // sequence are indexed (0-based) to aid iterator comparison. + const int end_index_; +}; // class RangeGenerator + + +// Generates values from a pair of STL-style iterators. Used in the +// ValuesIn() function. The elements are copied from the source range +// since the source can be located on the stack, and the generator +// is likely to persist beyond that stack frame. +template <typename T> +class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface<T> { + public: + template <typename ForwardIterator> + ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end) + : container_(begin, end) {} + ~ValuesInIteratorRangeGenerator() override {} + + ParamIteratorInterface<T>* Begin() const override { + return new Iterator(this, container_.begin()); + } + ParamIteratorInterface<T>* End() const override { + return new Iterator(this, container_.end()); + } + + private: + typedef typename ::std::vector<T> ContainerType; + + class Iterator : public ParamIteratorInterface<T> { + public: + Iterator(const ParamGeneratorInterface<T>* base, + typename ContainerType::const_iterator iterator) + : base_(base), iterator_(iterator) {} + ~Iterator() override {} + + const ParamGeneratorInterface<T>* BaseGenerator() const override { + return base_; + } + void Advance() override { + ++iterator_; + value_.reset(); + } + ParamIteratorInterface<T>* Clone() const override { + return new Iterator(*this); + } + // We need to use cached value referenced by iterator_ because *iterator_ + // can return a temporary object (and of type other then T), so just + // having "return &*iterator_;" doesn't work. + // value_ is updated here and not in Advance() because Advance() + // can advance iterator_ beyond the end of the range, and we cannot + // detect that fact. The client code, on the other hand, is + // responsible for not calling Current() on an out-of-range iterator. + const T* Current() const override { + if (value_.get() == nullptr) value_.reset(new T(*iterator_)); + return value_.get(); + } + bool Equals(const ParamIteratorInterface<T>& other) const override { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + return iterator_ == + CheckedDowncastToActualType<const Iterator>(&other)->iterator_; + } + + private: + Iterator(const Iterator& other) + // The explicit constructor call suppresses a false warning + // emitted by gcc when supplied with the -Wextra option. + : ParamIteratorInterface<T>(), + base_(other.base_), + iterator_(other.iterator_) {} + + const ParamGeneratorInterface<T>* const base_; + typename ContainerType::const_iterator iterator_; + // A cached value of *iterator_. We keep it here to allow access by + // pointer in the wrapping iterator's operator->(). + // value_ needs to be mutable to be accessed in Current(). + // Use of std::unique_ptr helps manage cached value's lifetime, + // which is bound by the lifespan of the iterator itself. + mutable std::unique_ptr<const T> value_; + }; // class ValuesInIteratorRangeGenerator::Iterator + + // No implementation - assignment is unsupported. + void operator=(const ValuesInIteratorRangeGenerator& other); + + const ContainerType container_; +}; // class ValuesInIteratorRangeGenerator + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Default parameterized test name generator, returns a string containing the +// integer test parameter index. +template <class ParamType> +std::string DefaultParamName(const TestParamInfo<ParamType>& info) { + Message name_stream; + name_stream << info.index; + return name_stream.GetString(); +} + +template <typename T = int> +void TestNotEmpty() { + static_assert(sizeof(T) == 0, "Empty arguments are not allowed."); +} +template <typename T = int> +void TestNotEmpty(const T&) {} + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Stores a parameter value and later creates tests parameterized with that +// value. +template <class TestClass> +class ParameterizedTestFactory : public TestFactoryBase { + public: + typedef typename TestClass::ParamType ParamType; + explicit ParameterizedTestFactory(ParamType parameter) : + parameter_(parameter) {} + Test* CreateTest() override { + TestClass::SetParam(¶meter_); + return new TestClass(); + } + + private: + const ParamType parameter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactoryBase is a base class for meta-factories that create +// test factories for passing into MakeAndRegisterTestInfo function. +template <class ParamType> +class TestMetaFactoryBase { + public: + virtual ~TestMetaFactoryBase() {} + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0; +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactory creates test factories for passing into +// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives +// ownership of test factory pointer, same factory object cannot be passed +// into that method twice. But ParameterizedTestSuiteInfo is going to call +// it for each Test/Parameter value combination. Thus it needs meta factory +// creator class. +template <class TestSuite> +class TestMetaFactory + : public TestMetaFactoryBase<typename TestSuite::ParamType> { + public: + using ParamType = typename TestSuite::ParamType; + + TestMetaFactory() {} + + TestFactoryBase* CreateTestFactory(ParamType parameter) override { + return new ParameterizedTestFactory<TestSuite>(parameter); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestMetaFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestSuiteInfoBase is a generic interface +// to ParameterizedTestSuiteInfo classes. ParameterizedTestSuiteInfoBase +// accumulates test information provided by TEST_P macro invocations +// and generators provided by INSTANTIATE_TEST_SUITE_P macro invocations +// and uses that information to register all resulting test instances +// in RegisterTests method. The ParameterizeTestSuiteRegistry class holds +// a collection of pointers to the ParameterizedTestSuiteInfo objects +// and calls RegisterTests() on each of them when asked. +class ParameterizedTestSuiteInfoBase { + public: + virtual ~ParameterizedTestSuiteInfoBase() {} + + // Base part of test suite name for display purposes. + virtual const std::string& GetTestSuiteName() const = 0; + // Test suite id to verify identity. + virtual TypeId GetTestSuiteTypeId() const = 0; + // UnitTest class invokes this method to register tests in this + // test suite right before running them in RUN_ALL_TESTS macro. + // This method should not be called more than once on any single + // instance of a ParameterizedTestSuiteInfoBase derived class. + virtual void RegisterTests() = 0; + + protected: + ParameterizedTestSuiteInfoBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfoBase); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Report a the name of a test_suit as safe to ignore +// as the side effect of construction of this type. +struct MarkAsIgnored { + explicit MarkAsIgnored(const char* test_suite); +}; + +GTEST_API_ void InsertSyntheticTestCase(const std::string& name, + CodeLocation location, bool has_test_p); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestSuiteInfo accumulates tests obtained from TEST_P +// macro invocations for a particular test suite and generators +// obtained from INSTANTIATE_TEST_SUITE_P macro invocations for that +// test suite. It registers tests with all values generated by all +// generators when asked. +template <class TestSuite> +class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase { + public: + // ParamType and GeneratorCreationFunc are private types but are required + // for declarations of public methods AddTestPattern() and + // AddTestSuiteInstantiation(). + using ParamType = typename TestSuite::ParamType; + // A function that returns an instance of appropriate generator type. + typedef ParamGenerator<ParamType>(GeneratorCreationFunc)(); + using ParamNameGeneratorFunc = std::string(const TestParamInfo<ParamType>&); + + explicit ParameterizedTestSuiteInfo(const char* name, + CodeLocation code_location) + : test_suite_name_(name), code_location_(code_location) {} + + // Test suite base name for display purposes. + const std::string& GetTestSuiteName() const override { + return test_suite_name_; + } + // Test suite id to verify identity. + TypeId GetTestSuiteTypeId() const override { return GetTypeId<TestSuite>(); } + // TEST_P macro uses AddTestPattern() to record information + // about a single test in a LocalTestInfo structure. + // test_suite_name is the base name of the test suite (without invocation + // prefix). test_base_name is the name of an individual test without + // parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is + // test suite base name and DoBar is test base name. + void AddTestPattern(const char* test_suite_name, const char* test_base_name, + TestMetaFactoryBase<ParamType>* meta_factory, + CodeLocation code_location) { + tests_.push_back(std::shared_ptr<TestInfo>(new TestInfo( + test_suite_name, test_base_name, meta_factory, code_location))); + } + // INSTANTIATE_TEST_SUITE_P macro uses AddGenerator() to record information + // about a generator. + int AddTestSuiteInstantiation(const std::string& instantiation_name, + GeneratorCreationFunc* func, + ParamNameGeneratorFunc* name_func, + const char* file, int line) { + instantiations_.push_back( + InstantiationInfo(instantiation_name, func, name_func, file, line)); + return 0; // Return value used only to run this method in namespace scope. + } + // UnitTest class invokes this method to register tests in this test suite + // right before running tests in RUN_ALL_TESTS macro. + // This method should not be called more than once on any single + // instance of a ParameterizedTestSuiteInfoBase derived class. + // UnitTest has a guard to prevent from calling this method more than once. + void RegisterTests() override { + bool generated_instantiations = false; + + for (typename TestInfoContainer::iterator test_it = tests_.begin(); + test_it != tests_.end(); ++test_it) { + std::shared_ptr<TestInfo> test_info = *test_it; + for (typename InstantiationContainer::iterator gen_it = + instantiations_.begin(); gen_it != instantiations_.end(); + ++gen_it) { + const std::string& instantiation_name = gen_it->name; + ParamGenerator<ParamType> generator((*gen_it->generator)()); + ParamNameGeneratorFunc* name_func = gen_it->name_func; + const char* file = gen_it->file; + int line = gen_it->line; + + std::string test_suite_name; + if ( !instantiation_name.empty() ) + test_suite_name = instantiation_name + "/"; + test_suite_name += test_info->test_suite_base_name; + + size_t i = 0; + std::set<std::string> test_param_names; + for (typename ParamGenerator<ParamType>::iterator param_it = + generator.begin(); + param_it != generator.end(); ++param_it, ++i) { + generated_instantiations = true; + + Message test_name_stream; + + std::string param_name = name_func( + TestParamInfo<ParamType>(*param_it, i)); + + GTEST_CHECK_(IsValidParamName(param_name)) + << "Parameterized test name '" << param_name + << "' is invalid, in " << file + << " line " << line << std::endl; + + GTEST_CHECK_(test_param_names.count(param_name) == 0) + << "Duplicate parameterized test name '" << param_name + << "', in " << file << " line " << line << std::endl; + + test_param_names.insert(param_name); + + if (!test_info->test_base_name.empty()) { + test_name_stream << test_info->test_base_name << "/"; + } + test_name_stream << param_name; + MakeAndRegisterTestInfo( + test_suite_name.c_str(), test_name_stream.GetString().c_str(), + nullptr, // No type parameter. + PrintToString(*param_it).c_str(), test_info->code_location, + GetTestSuiteTypeId(), + SuiteApiResolver<TestSuite>::GetSetUpCaseOrSuite(file, line), + SuiteApiResolver<TestSuite>::GetTearDownCaseOrSuite(file, line), + test_info->test_meta_factory->CreateTestFactory(*param_it)); + } // for param_it + } // for gen_it + } // for test_it + + if (!generated_instantiations) { + // There are no generaotrs, or they all generate nothing ... + InsertSyntheticTestCase(GetTestSuiteName(), code_location_, + !tests_.empty()); + } + } // RegisterTests + + private: + // LocalTestInfo structure keeps information about a single test registered + // with TEST_P macro. + struct TestInfo { + TestInfo(const char* a_test_suite_base_name, const char* a_test_base_name, + TestMetaFactoryBase<ParamType>* a_test_meta_factory, + CodeLocation a_code_location) + : test_suite_base_name(a_test_suite_base_name), + test_base_name(a_test_base_name), + test_meta_factory(a_test_meta_factory), + code_location(a_code_location) {} + + const std::string test_suite_base_name; + const std::string test_base_name; + const std::unique_ptr<TestMetaFactoryBase<ParamType> > test_meta_factory; + const CodeLocation code_location; + }; + using TestInfoContainer = ::std::vector<std::shared_ptr<TestInfo> >; + // Records data received from INSTANTIATE_TEST_SUITE_P macros: + // <Instantiation name, Sequence generator creation function, + // Name generator function, Source file, Source line> + struct InstantiationInfo { + InstantiationInfo(const std::string &name_in, + GeneratorCreationFunc* generator_in, + ParamNameGeneratorFunc* name_func_in, + const char* file_in, + int line_in) + : name(name_in), + generator(generator_in), + name_func(name_func_in), + file(file_in), + line(line_in) {} + + std::string name; + GeneratorCreationFunc* generator; + ParamNameGeneratorFunc* name_func; + const char* file; + int line; + }; + typedef ::std::vector<InstantiationInfo> InstantiationContainer; + + static bool IsValidParamName(const std::string& name) { + // Check for empty string + if (name.empty()) + return false; + + // Check for invalid characters + for (std::string::size_type index = 0; index < name.size(); ++index) { + if (!isalnum(name[index]) && name[index] != '_') + return false; + } + + return true; + } + + const std::string test_suite_name_; + CodeLocation code_location_; + TestInfoContainer tests_; + InstantiationContainer instantiations_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfo); +}; // class ParameterizedTestSuiteInfo + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +template <class TestCase> +using ParameterizedTestCaseInfo = ParameterizedTestSuiteInfo<TestCase>; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestSuiteRegistry contains a map of +// ParameterizedTestSuiteInfoBase classes accessed by test suite names. TEST_P +// and INSTANTIATE_TEST_SUITE_P macros use it to locate their corresponding +// ParameterizedTestSuiteInfo descriptors. +class ParameterizedTestSuiteRegistry { + public: + ParameterizedTestSuiteRegistry() {} + ~ParameterizedTestSuiteRegistry() { + for (auto& test_suite_info : test_suite_infos_) { + delete test_suite_info; + } + } + + // Looks up or creates and returns a structure containing information about + // tests and instantiations of a particular test suite. + template <class TestSuite> + ParameterizedTestSuiteInfo<TestSuite>* GetTestSuitePatternHolder( + const char* test_suite_name, CodeLocation code_location) { + ParameterizedTestSuiteInfo<TestSuite>* typed_test_info = nullptr; + for (auto& test_suite_info : test_suite_infos_) { + if (test_suite_info->GetTestSuiteName() == test_suite_name) { + if (test_suite_info->GetTestSuiteTypeId() != GetTypeId<TestSuite>()) { + // Complain about incorrect usage of Google Test facilities + // and terminate the program since we cannot guaranty correct + // test suite setup and tear-down in this case. + ReportInvalidTestSuiteType(test_suite_name, code_location); + posix::Abort(); + } else { + // At this point we are sure that the object we found is of the same + // type we are looking for, so we downcast it to that type + // without further checks. + typed_test_info = CheckedDowncastToActualType< + ParameterizedTestSuiteInfo<TestSuite> >(test_suite_info); + } + break; + } + } + if (typed_test_info == nullptr) { + typed_test_info = new ParameterizedTestSuiteInfo<TestSuite>( + test_suite_name, code_location); + test_suite_infos_.push_back(typed_test_info); + } + return typed_test_info; + } + void RegisterTests() { + for (auto& test_suite_info : test_suite_infos_) { + test_suite_info->RegisterTests(); + } + } +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + template <class TestCase> + ParameterizedTestCaseInfo<TestCase>* GetTestCasePatternHolder( + const char* test_case_name, CodeLocation code_location) { + return GetTestSuitePatternHolder<TestCase>(test_case_name, code_location); + } + +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + private: + using TestSuiteInfoContainer = ::std::vector<ParameterizedTestSuiteInfoBase*>; + + TestSuiteInfoContainer test_suite_infos_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteRegistry); +}; + +// Keep track of what type-parameterized test suite are defined and +// where as well as which are intatiated. This allows susequently +// identifying suits that are defined but never used. +class TypeParameterizedTestSuiteRegistry { + public: + // Add a suite definition + void RegisterTestSuite(const char* test_suite_name, + CodeLocation code_location); + + // Add an instantiation of a suit. + void RegisterInstantiation(const char* test_suite_name); + + // For each suit repored as defined but not reported as instantiation, + // emit a test that reports that fact (configurably, as an error). + void CheckForInstantiations(); + + private: + struct TypeParameterizedTestSuiteInfo { + explicit TypeParameterizedTestSuiteInfo(CodeLocation c) + : code_location(c), instantiated(false) {} + + CodeLocation code_location; + bool instantiated; + }; + + std::map<std::string, TypeParameterizedTestSuiteInfo> suites_; +}; + +} // namespace internal + +// Forward declarations of ValuesIn(), which is implemented in +// include/gtest/gtest-param-test.h. +template <class Container> +internal::ParamGenerator<typename Container::value_type> ValuesIn( + const Container& container); + +namespace internal { +// Used in the Values() function to provide polymorphic capabilities. + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) +#endif + +template <typename... Ts> +class ValueArray { + public: + explicit ValueArray(Ts... v) : v_(FlatTupleConstructTag{}, std::move(v)...) {} + + template <typename T> + operator ParamGenerator<T>() const { // NOLINT + return ValuesIn(MakeVector<T>(MakeIndexSequence<sizeof...(Ts)>())); + } + + private: + template <typename T, size_t... I> + std::vector<T> MakeVector(IndexSequence<I...>) const { + return std::vector<T>{static_cast<T>(v_.template Get<I>())...}; + } + + FlatTuple<Ts...> v_; +}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +template <typename... T> +class CartesianProductGenerator + : public ParamGeneratorInterface<::std::tuple<T...>> { + public: + typedef ::std::tuple<T...> ParamType; + + CartesianProductGenerator(const std::tuple<ParamGenerator<T>...>& g) + : generators_(g) {} + ~CartesianProductGenerator() override {} + + ParamIteratorInterface<ParamType>* Begin() const override { + return new Iterator(this, generators_, false); + } + ParamIteratorInterface<ParamType>* End() const override { + return new Iterator(this, generators_, true); + } + + private: + template <class I> + class IteratorImpl; + template <size_t... I> + class IteratorImpl<IndexSequence<I...>> + : public ParamIteratorInterface<ParamType> { + public: + IteratorImpl(const ParamGeneratorInterface<ParamType>* base, + const std::tuple<ParamGenerator<T>...>& generators, bool is_end) + : base_(base), + begin_(std::get<I>(generators).begin()...), + end_(std::get<I>(generators).end()...), + current_(is_end ? end_ : begin_) { + ComputeCurrentValue(); + } + ~IteratorImpl() override {} + + const ParamGeneratorInterface<ParamType>* BaseGenerator() const override { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + void Advance() override { + assert(!AtEnd()); + // Advance the last iterator. + ++std::get<sizeof...(T) - 1>(current_); + // if that reaches end, propagate that up. + AdvanceIfEnd<sizeof...(T) - 1>(); + ComputeCurrentValue(); + } + ParamIteratorInterface<ParamType>* Clone() const override { + return new IteratorImpl(*this); + } + + const ParamType* Current() const override { return current_value_.get(); } + + bool Equals(const ParamIteratorInterface<ParamType>& other) const override { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const IteratorImpl* typed_other = + CheckedDowncastToActualType<const IteratorImpl>(&other); + + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + if (AtEnd() && typed_other->AtEnd()) return true; + + bool same = true; + bool dummy[] = { + (same = same && std::get<I>(current_) == + std::get<I>(typed_other->current_))...}; + (void)dummy; + return same; + } + + private: + template <size_t ThisI> + void AdvanceIfEnd() { + if (std::get<ThisI>(current_) != std::get<ThisI>(end_)) return; + + bool last = ThisI == 0; + if (last) { + // We are done. Nothing else to propagate. + return; + } + + constexpr size_t NextI = ThisI - (ThisI != 0); + std::get<ThisI>(current_) = std::get<ThisI>(begin_); + ++std::get<NextI>(current_); + AdvanceIfEnd<NextI>(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = std::make_shared<ParamType>(*std::get<I>(current_)...); + } + bool AtEnd() const { + bool at_end = false; + bool dummy[] = { + (at_end = at_end || std::get<I>(current_) == std::get<I>(end_))...}; + (void)dummy; + return at_end; + } + + const ParamGeneratorInterface<ParamType>* const base_; + std::tuple<typename ParamGenerator<T>::iterator...> begin_; + std::tuple<typename ParamGenerator<T>::iterator...> end_; + std::tuple<typename ParamGenerator<T>::iterator...> current_; + std::shared_ptr<ParamType> current_value_; + }; + + using Iterator = IteratorImpl<typename MakeIndexSequence<sizeof...(T)>::type>; + + std::tuple<ParamGenerator<T>...> generators_; +}; + +template <class... Gen> +class CartesianProductHolder { + public: + CartesianProductHolder(const Gen&... g) : generators_(g...) {} + template <typename... T> + operator ParamGenerator<::std::tuple<T...>>() const { + return ParamGenerator<::std::tuple<T...>>( + new CartesianProductGenerator<T...>(generators_)); + } + + private: + std::tuple<Gen...> generators_; +}; + +} // namespace internal +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ + +namespace testing { + +// Functions producing parameter generators. +// +// Google Test uses these generators to produce parameters for value- +// parameterized tests. When a parameterized test suite is instantiated +// with a particular generator, Google Test creates and runs tests +// for each element in the sequence produced by the generator. +// +// In the following sample, tests from test suite FooTest are instantiated +// each three times with parameter values 3, 5, and 8: +// +// class FooTest : public TestWithParam<int> { ... }; +// +// TEST_P(FooTest, TestThis) { +// } +// TEST_P(FooTest, TestThat) { +// } +// INSTANTIATE_TEST_SUITE_P(TestSequence, FooTest, Values(3, 5, 8)); +// + +// Range() returns generators providing sequences of values in a range. +// +// Synopsis: +// Range(start, end) +// - returns a generator producing a sequence of values {start, start+1, +// start+2, ..., }. +// Range(start, end, step) +// - returns a generator producing a sequence of values {start, start+step, +// start+step+step, ..., }. +// Notes: +// * The generated sequences never include end. For example, Range(1, 5) +// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) +// returns a generator producing {1, 3, 5, 7}. +// * start and end must have the same type. That type may be any integral or +// floating-point type or a user defined type satisfying these conditions: +// * It must be assignable (have operator=() defined). +// * It must have operator+() (operator+(int-compatible type) for +// two-operand version). +// * It must have operator<() defined. +// Elements in the resulting sequences will also have that type. +// * Condition start < end must be satisfied in order for resulting sequences +// to contain any elements. +// +template <typename T, typename IncrementT> +internal::ParamGenerator<T> Range(T start, T end, IncrementT step) { + return internal::ParamGenerator<T>( + new internal::RangeGenerator<T, IncrementT>(start, end, step)); +} + +template <typename T> +internal::ParamGenerator<T> Range(T start, T end) { + return Range(start, end, 1); +} + +// ValuesIn() function allows generation of tests with parameters coming from +// a container. +// +// Synopsis: +// ValuesIn(const T (&array)[N]) +// - returns a generator producing sequences with elements from +// a C-style array. +// ValuesIn(const Container& container) +// - returns a generator producing sequences with elements from +// an STL-style container. +// ValuesIn(Iterator begin, Iterator end) +// - returns a generator producing sequences with elements from +// a range [begin, end) defined by a pair of STL-style iterators. These +// iterators can also be plain C pointers. +// +// Please note that ValuesIn copies the values from the containers +// passed in and keeps them to generate tests in RUN_ALL_TESTS(). +// +// Examples: +// +// This instantiates tests from test suite StringTest +// each with C-string values of "foo", "bar", and "baz": +// +// const char* strings[] = {"foo", "bar", "baz"}; +// INSTANTIATE_TEST_SUITE_P(StringSequence, StringTest, ValuesIn(strings)); +// +// This instantiates tests from test suite StlStringTest +// each with STL strings with values "a" and "b": +// +// ::std::vector< ::std::string> GetParameterStrings() { +// ::std::vector< ::std::string> v; +// v.push_back("a"); +// v.push_back("b"); +// return v; +// } +// +// INSTANTIATE_TEST_SUITE_P(CharSequence, +// StlStringTest, +// ValuesIn(GetParameterStrings())); +// +// +// This will also instantiate tests from CharTest +// each with parameter values 'a' and 'b': +// +// ::std::list<char> GetParameterChars() { +// ::std::list<char> list; +// list.push_back('a'); +// list.push_back('b'); +// return list; +// } +// ::std::list<char> l = GetParameterChars(); +// INSTANTIATE_TEST_SUITE_P(CharSequence2, +// CharTest, +// ValuesIn(l.begin(), l.end())); +// +template <typename ForwardIterator> +internal::ParamGenerator< + typename std::iterator_traits<ForwardIterator>::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end) { + typedef typename std::iterator_traits<ForwardIterator>::value_type ParamType; + return internal::ParamGenerator<ParamType>( + new internal::ValuesInIteratorRangeGenerator<ParamType>(begin, end)); +} + +template <typename T, size_t N> +internal::ParamGenerator<T> ValuesIn(const T (&array)[N]) { + return ValuesIn(array, array + N); +} + +template <class Container> +internal::ParamGenerator<typename Container::value_type> ValuesIn( + const Container& container) { + return ValuesIn(container.begin(), container.end()); +} + +// Values() allows generating tests from explicitly specified list of +// parameters. +// +// Synopsis: +// Values(T v1, T v2, ..., T vN) +// - returns a generator producing sequences with elements v1, v2, ..., vN. +// +// For example, this instantiates tests from test suite BarTest each +// with values "one", "two", and "three": +// +// INSTANTIATE_TEST_SUITE_P(NumSequence, +// BarTest, +// Values("one", "two", "three")); +// +// This instantiates tests from test suite BazTest each with values 1, 2, 3.5. +// The exact type of values will depend on the type of parameter in BazTest. +// +// INSTANTIATE_TEST_SUITE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); +// +// +template <typename... T> +internal::ValueArray<T...> Values(T... v) { + return internal::ValueArray<T...>(std::move(v)...); +} + +// Bool() allows generating tests with parameters in a set of (false, true). +// +// Synopsis: +// Bool() +// - returns a generator producing sequences with elements {false, true}. +// +// It is useful when testing code that depends on Boolean flags. Combinations +// of multiple flags can be tested when several Bool()'s are combined using +// Combine() function. +// +// In the following example all tests in the test suite FlagDependentTest +// will be instantiated twice with parameters false and true. +// +// class FlagDependentTest : public testing::TestWithParam<bool> { +// virtual void SetUp() { +// external_flag = GetParam(); +// } +// } +// INSTANTIATE_TEST_SUITE_P(BoolSequence, FlagDependentTest, Bool()); +// +inline internal::ParamGenerator<bool> Bool() { + return Values(false, true); +} + +// Combine() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements. +// +// Synopsis: +// Combine(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// std::tuple<T1, T2, ..., TN> where T1, T2, ..., TN are the types +// of elements from sequences produces by gen1, gen2, ..., genN. +// +// Example: +// +// This will instantiate tests in test suite AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// class AnimalTest +// : public testing::TestWithParam<std::tuple<const char*, Color> > {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest, +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +// This will instantiate tests in FlagDependentTest with all variations of two +// Boolean flags: +// +// class FlagDependentTest +// : public testing::TestWithParam<std::tuple<bool, bool> > { +// virtual void SetUp() { +// // Assigns external_flag_1 and external_flag_2 values from the tuple. +// std::tie(external_flag_1, external_flag_2) = GetParam(); +// } +// }; +// +// TEST_P(FlagDependentTest, TestFeature1) { +// // Test your code using external_flag_1 and external_flag_2 here. +// } +// INSTANTIATE_TEST_SUITE_P(TwoBoolSequence, FlagDependentTest, +// Combine(Bool(), Bool())); +// +template <typename... Generator> +internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) { + return internal::CartesianProductHolder<Generator...>(g...); +} + +#define TEST_P(test_suite_name, test_name) \ + class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ + : public test_suite_name { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} \ + void TestBody() override; \ + \ + private: \ + static int AddToRegistry() { \ + ::testing::UnitTest::GetInstance() \ + ->parameterized_test_registry() \ + .GetTestSuitePatternHolder<test_suite_name>( \ + GTEST_STRINGIFY_(test_suite_name), \ + ::testing::internal::CodeLocation(__FILE__, __LINE__)) \ + ->AddTestPattern( \ + GTEST_STRINGIFY_(test_suite_name), GTEST_STRINGIFY_(test_name), \ + new ::testing::internal::TestMetaFactory<GTEST_TEST_CLASS_NAME_( \ + test_suite_name, test_name)>(), \ + ::testing::internal::CodeLocation(__FILE__, __LINE__)); \ + return 0; \ + } \ + static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; \ + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name)); \ + }; \ + int GTEST_TEST_CLASS_NAME_(test_suite_name, \ + test_name)::gtest_registering_dummy_ = \ + GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::AddToRegistry(); \ + void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() + +// The last argument to INSTANTIATE_TEST_SUITE_P allows the user to specify +// generator and an optional function or functor that generates custom test name +// suffixes based on the test parameters. Such a function or functor should +// accept one argument of type testing::TestParamInfo<class ParamType>, and +// return std::string. +// +// testing::PrintToStringParamName is a builtin test suffix generator that +// returns the value of testing::PrintToString(GetParam()). +// +// Note: test names must be non-empty, unique, and may only contain ASCII +// alphanumeric characters or underscore. Because PrintToString adds quotes +// to std::string and C strings, it won't work for these types. + +#define GTEST_EXPAND_(arg) arg +#define GTEST_GET_FIRST_(first, ...) first +#define GTEST_GET_SECOND_(first, second, ...) second + +#define INSTANTIATE_TEST_SUITE_P(prefix, test_suite_name, ...) \ + static ::testing::internal::ParamGenerator<test_suite_name::ParamType> \ + gtest_##prefix##test_suite_name##_EvalGenerator_() { \ + return GTEST_EXPAND_(GTEST_GET_FIRST_(__VA_ARGS__, DUMMY_PARAM_)); \ + } \ + static ::std::string gtest_##prefix##test_suite_name##_EvalGenerateName_( \ + const ::testing::TestParamInfo<test_suite_name::ParamType>& info) { \ + if (::testing::internal::AlwaysFalse()) { \ + ::testing::internal::TestNotEmpty(GTEST_EXPAND_(GTEST_GET_SECOND_( \ + __VA_ARGS__, \ + ::testing::internal::DefaultParamName<test_suite_name::ParamType>, \ + DUMMY_PARAM_))); \ + auto t = std::make_tuple(__VA_ARGS__); \ + static_assert(std::tuple_size<decltype(t)>::value <= 2, \ + "Too Many Args!"); \ + } \ + return ((GTEST_EXPAND_(GTEST_GET_SECOND_( \ + __VA_ARGS__, \ + ::testing::internal::DefaultParamName<test_suite_name::ParamType>, \ + DUMMY_PARAM_))))(info); \ + } \ + static int gtest_##prefix##test_suite_name##_dummy_ \ + GTEST_ATTRIBUTE_UNUSED_ = \ + ::testing::UnitTest::GetInstance() \ + ->parameterized_test_registry() \ + .GetTestSuitePatternHolder<test_suite_name>( \ + GTEST_STRINGIFY_(test_suite_name), \ + ::testing::internal::CodeLocation(__FILE__, __LINE__)) \ + ->AddTestSuiteInstantiation( \ + GTEST_STRINGIFY_(prefix), \ + >est_##prefix##test_suite_name##_EvalGenerator_, \ + >est_##prefix##test_suite_name##_EvalGenerateName_, \ + __FILE__, __LINE__) + + +// Allow Marking a Parameterized test class as not needing to be instantiated. +#define GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(T) \ + namespace gtest_do_not_use_outside_namespace_scope {} \ + static const ::testing::internal::MarkAsIgnored gtest_allow_ignore_##T( \ + GTEST_STRINGIFY_(T)) + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define INSTANTIATE_TEST_CASE_P \ + static_assert(::testing::internal::InstantiateTestCase_P_IsDeprecated(), \ + ""); \ + INSTANTIATE_TEST_SUITE_P +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// Google C++ Testing and Mocking Framework definitions useful in production code. +// GOOGLETEST_CM0003 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ + +// When you need to test the private or protected members of a class, +// use the FRIEND_TEST macro to declare your tests as friends of the +// class. For example: +// +// class MyClass { +// private: +// void PrivateMethod(); +// FRIEND_TEST(MyClassTest, PrivateMethodWorks); +// }; +// +// class MyClassTest : public testing::Test { +// // ... +// }; +// +// TEST_F(MyClassTest, PrivateMethodWorks) { +// // Can call MyClass::PrivateMethod() here. +// } +// +// Note: The test class must be in the same namespace as the class being tested. +// For example, putting MyClassTest in an anonymous namespace will not work. + +#define FRIEND_TEST(test_case_name, test_name)\ +friend class test_case_name##_##test_name##_Test + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ + +// This header implements typed tests and type-parameterized tests. + +// Typed (aka type-driven) tests repeat the same test for types in a +// list. You must know which types you want to test with when writing +// typed tests. Here's how you do it: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template <typename T> +class FooTest : public testing::Test { + public: + ... + typedef std::list<T> List; + static T shared_; + T value_; +}; + +// Next, associate a list of types with the test suite, which will be +// repeated for each type in the list. The typedef is necessary for +// the macro to parse correctly. +typedef testing::Types<char, int, unsigned int> MyTypes; +TYPED_TEST_SUITE(FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// TYPED_TEST_SUITE(FooTest, int); + +// Then, use TYPED_TEST() instead of TEST_F() to define as many typed +// tests for this test suite as you want. +TYPED_TEST(FooTest, DoesBlah) { + // Inside a test, refer to the special name TypeParam to get the type + // parameter. Since we are inside a derived class template, C++ requires + // us to visit the members of FooTest via 'this'. + TypeParam n = this->value_; + + // To visit static members of the fixture, add the TestFixture:: + // prefix. + n += TestFixture::shared_; + + // To refer to typedefs in the fixture, add the "typename + // TestFixture::" prefix. + typename TestFixture::List values; + values.push_back(n); + ... +} + +TYPED_TEST(FooTest, HasPropertyA) { ... } + +// TYPED_TEST_SUITE takes an optional third argument which allows to specify a +// class that generates custom test name suffixes based on the type. This should +// be a class which has a static template function GetName(int index) returning +// a string for each type. The provided integer index equals the index of the +// type in the provided type list. In many cases the index can be ignored. +// +// For example: +// class MyTypeNames { +// public: +// template <typename T> +// static std::string GetName(int) { +// if (std::is_same<T, char>()) return "char"; +// if (std::is_same<T, int>()) return "int"; +// if (std::is_same<T, unsigned int>()) return "unsignedInt"; +// } +// }; +// TYPED_TEST_SUITE(FooTest, MyTypes, MyTypeNames); + +#endif // 0 + +// Type-parameterized tests are abstract test patterns parameterized +// by a type. Compared with typed tests, type-parameterized tests +// allow you to define the test pattern without knowing what the type +// parameters are. The defined pattern can be instantiated with +// different types any number of times, in any number of translation +// units. +// +// If you are designing an interface or concept, you can define a +// suite of type-parameterized tests to verify properties that any +// valid implementation of the interface/concept should have. Then, +// each implementation can easily instantiate the test suite to verify +// that it conforms to the requirements, without having to write +// similar tests repeatedly. Here's an example: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template <typename T> +class FooTest : public testing::Test { + ... +}; + +// Next, declare that you will define a type-parameterized test suite +// (the _P suffix is for "parameterized" or "pattern", whichever you +// prefer): +TYPED_TEST_SUITE_P(FooTest); + +// Then, use TYPED_TEST_P() to define as many type-parameterized tests +// for this type-parameterized test suite as you want. +TYPED_TEST_P(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + TypeParam n = 0; + ... +} + +TYPED_TEST_P(FooTest, HasPropertyA) { ... } + +// Now the tricky part: you need to register all test patterns before +// you can instantiate them. The first argument of the macro is the +// test suite name; the rest are the names of the tests in this test +// case. +REGISTER_TYPED_TEST_SUITE_P(FooTest, + DoesBlah, HasPropertyA); + +// Finally, you are free to instantiate the pattern with the types you +// want. If you put the above code in a header file, you can #include +// it in multiple C++ source files and instantiate it multiple times. +// +// To distinguish different instances of the pattern, the first +// argument to the INSTANTIATE_* macro is a prefix that will be added +// to the actual test suite name. Remember to pick unique prefixes for +// different instances. +typedef testing::Types<char, int, unsigned int> MyTypes; +INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, int); +// +// Similar to the optional argument of TYPED_TEST_SUITE above, +// INSTANTIATE_TEST_SUITE_P takes an optional fourth argument which allows to +// generate custom names. +// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes, MyTypeNames); + +#endif // 0 + + +// Implements typed tests. + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the typedef for the type parameters of the +// given test suite. +#define GTEST_TYPE_PARAMS_(TestSuiteName) gtest_type_params_##TestSuiteName##_ + +// Expands to the name of the typedef for the NameGenerator, responsible for +// creating the suffixes of the name. +#define GTEST_NAME_GENERATOR_(TestSuiteName) \ + gtest_type_params_##TestSuiteName##_NameGenerator + +#define TYPED_TEST_SUITE(CaseName, Types, ...) \ + typedef ::testing::internal::GenerateTypeList<Types>::type \ + GTEST_TYPE_PARAMS_(CaseName); \ + typedef ::testing::internal::NameGeneratorSelector<__VA_ARGS__>::type \ + GTEST_NAME_GENERATOR_(CaseName) + +#define TYPED_TEST(CaseName, TestName) \ + static_assert(sizeof(GTEST_STRINGIFY_(TestName)) > 1, \ + "test-name must not be empty"); \ + template <typename gtest_TypeParam_> \ + class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ + : public CaseName<gtest_TypeParam_> { \ + private: \ + typedef CaseName<gtest_TypeParam_> TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + void TestBody() override; \ + }; \ + static bool gtest_##CaseName##_##TestName##_registered_ \ + GTEST_ATTRIBUTE_UNUSED_ = ::testing::internal::TypeParameterizedTest< \ + CaseName, \ + ::testing::internal::TemplateSel<GTEST_TEST_CLASS_NAME_(CaseName, \ + TestName)>, \ + GTEST_TYPE_PARAMS_( \ + CaseName)>::Register("", \ + ::testing::internal::CodeLocation( \ + __FILE__, __LINE__), \ + GTEST_STRINGIFY_(CaseName), \ + GTEST_STRINGIFY_(TestName), 0, \ + ::testing::internal::GenerateNames< \ + GTEST_NAME_GENERATOR_(CaseName), \ + GTEST_TYPE_PARAMS_(CaseName)>()); \ + template <typename gtest_TypeParam_> \ + void GTEST_TEST_CLASS_NAME_(CaseName, \ + TestName)<gtest_TypeParam_>::TestBody() + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define TYPED_TEST_CASE \ + static_assert(::testing::internal::TypedTestCaseIsDeprecated(), ""); \ + TYPED_TEST_SUITE +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +// Implements type-parameterized tests. + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the namespace name that the type-parameterized tests for +// the given type-parameterized test suite are defined in. The exact +// name of the namespace is subject to change without notice. +#define GTEST_SUITE_NAMESPACE_(TestSuiteName) gtest_suite_##TestSuiteName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the variable used to remember the names of +// the defined tests in the given test suite. +#define GTEST_TYPED_TEST_SUITE_P_STATE_(TestSuiteName) \ + gtest_typed_test_suite_p_state_##TestSuiteName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY. +// +// Expands to the name of the variable used to remember the names of +// the registered tests in the given test suite. +#define GTEST_REGISTERED_TEST_NAMES_(TestSuiteName) \ + gtest_registered_test_names_##TestSuiteName##_ + +// The variables defined in the type-parameterized test macros are +// static as typically these macros are used in a .h file that can be +// #included in multiple translation units linked together. +#define TYPED_TEST_SUITE_P(SuiteName) \ + static ::testing::internal::TypedTestSuitePState \ + GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName) + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define TYPED_TEST_CASE_P \ + static_assert(::testing::internal::TypedTestCase_P_IsDeprecated(), ""); \ + TYPED_TEST_SUITE_P +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +#define TYPED_TEST_P(SuiteName, TestName) \ + namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \ + template <typename gtest_TypeParam_> \ + class TestName : public SuiteName<gtest_TypeParam_> { \ + private: \ + typedef SuiteName<gtest_TypeParam_> TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + void TestBody() override; \ + }; \ + static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ = \ + GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTestName( \ + __FILE__, __LINE__, GTEST_STRINGIFY_(SuiteName), \ + GTEST_STRINGIFY_(TestName)); \ + } \ + template <typename gtest_TypeParam_> \ + void GTEST_SUITE_NAMESPACE_( \ + SuiteName)::TestName<gtest_TypeParam_>::TestBody() + +// Note: this won't work correctly if the trailing arguments are macros. +#define REGISTER_TYPED_TEST_SUITE_P(SuiteName, ...) \ + namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \ + typedef ::testing::internal::Templates<__VA_ARGS__> gtest_AllTests_; \ + } \ + static const char* const GTEST_REGISTERED_TEST_NAMES_( \ + SuiteName) GTEST_ATTRIBUTE_UNUSED_ = \ + GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).VerifyRegisteredTestNames( \ + GTEST_STRINGIFY_(SuiteName), __FILE__, __LINE__, #__VA_ARGS__) + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define REGISTER_TYPED_TEST_CASE_P \ + static_assert(::testing::internal::RegisterTypedTestCase_P_IsDeprecated(), \ + ""); \ + REGISTER_TYPED_TEST_SUITE_P +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +#define INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, SuiteName, Types, ...) \ + static_assert(sizeof(GTEST_STRINGIFY_(Prefix)) > 1, \ + "test-suit-prefix must not be empty"); \ + static bool gtest_##Prefix##_##SuiteName GTEST_ATTRIBUTE_UNUSED_ = \ + ::testing::internal::TypeParameterizedTestSuite< \ + SuiteName, GTEST_SUITE_NAMESPACE_(SuiteName)::gtest_AllTests_, \ + ::testing::internal::GenerateTypeList<Types>::type>:: \ + Register(GTEST_STRINGIFY_(Prefix), \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), \ + >EST_TYPED_TEST_SUITE_P_STATE_(SuiteName), \ + GTEST_STRINGIFY_(SuiteName), \ + GTEST_REGISTERED_TEST_NAMES_(SuiteName), \ + ::testing::internal::GenerateNames< \ + ::testing::internal::NameGeneratorSelector< \ + __VA_ARGS__>::type, \ + ::testing::internal::GenerateTypeList<Types>::type>()) + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +#define INSTANTIATE_TYPED_TEST_CASE_P \ + static_assert( \ + ::testing::internal::InstantiateTypedTestCase_P_IsDeprecated(), ""); \ + INSTANTIATE_TYPED_TEST_SUITE_P +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// Silence C4100 (unreferenced formal parameter) and 4805 +// unsafe mix of type 'const int' and type 'const bool' +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4805) +# pragma warning(disable:4100) +#endif + + +// Declares the flags. + +// This flag temporary enables the disabled tests. +GTEST_DECLARE_bool_(also_run_disabled_tests); + +// This flag brings the debugger on an assertion failure. +GTEST_DECLARE_bool_(break_on_failure); + +// This flag controls whether Google Test catches all test-thrown exceptions +// and logs them as failures. +GTEST_DECLARE_bool_(catch_exceptions); + +// This flag enables using colors in terminal output. Available values are +// "yes" to enable colors, "no" (disable colors), or "auto" (the default) +// to let Google Test decide. +GTEST_DECLARE_string_(color); + +// This flag controls whether the test runner should continue execution past +// first failure. +GTEST_DECLARE_bool_(fail_fast); + +// This flag sets up the filter to select by name using a glob pattern +// the tests to run. If the filter is not given all tests are executed. +GTEST_DECLARE_string_(filter); + +// This flag controls whether Google Test installs a signal handler that dumps +// debugging information when fatal signals are raised. +GTEST_DECLARE_bool_(install_failure_signal_handler); + +// This flag causes the Google Test to list tests. None of the tests listed +// are actually run if the flag is provided. +GTEST_DECLARE_bool_(list_tests); + +// This flag controls whether Google Test emits a detailed XML report to a file +// in addition to its normal textual output. +GTEST_DECLARE_string_(output); + +// This flags control whether Google Test prints only test failures. +GTEST_DECLARE_bool_(brief); + +// This flags control whether Google Test prints the elapsed time for each +// test. +GTEST_DECLARE_bool_(print_time); + +// This flags control whether Google Test prints UTF8 characters as text. +GTEST_DECLARE_bool_(print_utf8); + +// This flag specifies the random number seed. +GTEST_DECLARE_int32_(random_seed); + +// This flag sets how many times the tests are repeated. The default value +// is 1. If the value is -1 the tests are repeating forever. +GTEST_DECLARE_int32_(repeat); + +// This flag controls whether Google Test includes Google Test internal +// stack frames in failure stack traces. +GTEST_DECLARE_bool_(show_internal_stack_frames); + +// When this flag is specified, tests' order is randomized on every iteration. +GTEST_DECLARE_bool_(shuffle); + +// This flag specifies the maximum number of stack frames to be +// printed in a failure message. +GTEST_DECLARE_int32_(stack_trace_depth); + +// When this flag is specified, a failed assertion will throw an +// exception if exceptions are enabled, or exit the program with a +// non-zero code otherwise. For use with an external test framework. +GTEST_DECLARE_bool_(throw_on_failure); + +// When this flag is set with a "host:port" string, on supported +// platforms test results are streamed to the specified port on +// the specified host machine. +GTEST_DECLARE_string_(stream_result_to); + +#if GTEST_USE_OWN_FLAGFILE_FLAG_ +GTEST_DECLARE_string_(flagfile); +#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ + +// The upper limit for valid stack trace depths. +const int kMaxStackTraceDepth = 100; + +namespace internal { + +class AssertHelper; +class DefaultGlobalTestPartResultReporter; +class ExecDeathTest; +class NoExecDeathTest; +class FinalSuccessChecker; +class GTestFlagSaver; +class StreamingListenerTest; +class TestResultAccessor; +class TestEventListenersAccessor; +class TestEventRepeater; +class UnitTestRecordPropertyTestHelper; +class WindowsDeathTest; +class FuchsiaDeathTest; +class UnitTestImpl* GetUnitTestImpl(); +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const std::string& message); +std::set<std::string>* GetIgnoredParameterizedTestSuites(); + +} // namespace internal + +// The friend relationship of some of these classes is cyclic. +// If we don't forward declare them the compiler might confuse the classes +// in friendship clauses with same named classes on the scope. +class Test; +class TestSuite; + +// Old API is still available but deprecated +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ +using TestCase = TestSuite; +#endif +class TestInfo; +class UnitTest; + +// A class for indicating whether an assertion was successful. When +// the assertion wasn't successful, the AssertionResult object +// remembers a non-empty message that describes how it failed. +// +// To create an instance of this class, use one of the factory functions +// (AssertionSuccess() and AssertionFailure()). +// +// This class is useful for two purposes: +// 1. Defining predicate functions to be used with Boolean test assertions +// EXPECT_TRUE/EXPECT_FALSE and their ASSERT_ counterparts +// 2. Defining predicate-format functions to be +// used with predicate assertions (ASSERT_PRED_FORMAT*, etc). +// +// For example, if you define IsEven predicate: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then the failed expectation EXPECT_TRUE(IsEven(Fib(5))) +// will print the message +// +// Value of: IsEven(Fib(5)) +// Actual: false (5 is odd) +// Expected: true +// +// instead of a more opaque +// +// Value of: IsEven(Fib(5)) +// Actual: false +// Expected: true +// +// in case IsEven is a simple Boolean predicate. +// +// If you expect your predicate to be reused and want to support informative +// messages in EXPECT_FALSE and ASSERT_FALSE (negative assertions show up +// about half as often as positive ones in our tests), supply messages for +// both success and failure cases: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess() << n << " is even"; +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then a statement EXPECT_FALSE(IsEven(Fib(6))) will print +// +// Value of: IsEven(Fib(6)) +// Actual: true (8 is even) +// Expected: false +// +// NB: Predicates that support negative Boolean assertions have reduced +// performance in positive ones so be careful not to use them in tests +// that have lots (tens of thousands) of positive Boolean assertions. +// +// To use this class with EXPECT_PRED_FORMAT assertions such as: +// +// // Verifies that Foo() returns an even number. +// EXPECT_PRED_FORMAT1(IsEven, Foo()); +// +// you need to define: +// +// testing::AssertionResult IsEven(const char* expr, int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() +// << "Expected: " << expr << " is even\n Actual: it's " << n; +// } +// +// If Foo() returns 5, you will see the following message: +// +// Expected: Foo() is even +// Actual: it's 5 +// +class GTEST_API_ AssertionResult { + public: + // Copy constructor. + // Used in EXPECT_TRUE/FALSE(assertion_result). + AssertionResult(const AssertionResult& other); + +// C4800 is a level 3 warning in Visual Studio 2015 and earlier. +// This warning is not emitted in Visual Studio 2017. +// This warning is off by default starting in Visual Studio 2019 but can be +// enabled with command-line options. +#if defined(_MSC_VER) && (_MSC_VER < 1910 || _MSC_VER >= 1920) + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 /* forcing value to bool */) +#endif + + // Used in the EXPECT_TRUE/FALSE(bool_expression). + // + // T must be contextually convertible to bool. + // + // The second parameter prevents this overload from being considered if + // the argument is implicitly convertible to AssertionResult. In that case + // we want AssertionResult's copy constructor to be used. + template <typename T> + explicit AssertionResult( + const T& success, + typename std::enable_if< + !std::is_convertible<T, AssertionResult>::value>::type* + /*enabler*/ + = nullptr) + : success_(success) {} + +#if defined(_MSC_VER) && (_MSC_VER < 1910 || _MSC_VER >= 1920) + GTEST_DISABLE_MSC_WARNINGS_POP_() +#endif + + // Assignment operator. + AssertionResult& operator=(AssertionResult other) { + swap(other); + return *this; + } + + // Returns true if and only if the assertion succeeded. + operator bool() const { return success_; } // NOLINT + + // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. + AssertionResult operator!() const; + + // Returns the text streamed into this AssertionResult. Test assertions + // use it when they fail (i.e., the predicate's outcome doesn't match the + // assertion's expectation). When nothing has been streamed into the + // object, returns an empty string. + const char* message() const { + return message_.get() != nullptr ? message_->c_str() : ""; + } + // Deprecated; please use message() instead. + const char* failure_message() const { return message(); } + + // Streams a custom failure message into this object. + template <typename T> AssertionResult& operator<<(const T& value) { + AppendMessage(Message() << value); + return *this; + } + + // Allows streaming basic output manipulators such as endl or flush into + // this object. + AssertionResult& operator<<( + ::std::ostream& (*basic_manipulator)(::std::ostream& stream)) { + AppendMessage(Message() << basic_manipulator); + return *this; + } + + private: + // Appends the contents of message to message_. + void AppendMessage(const Message& a_message) { + if (message_.get() == nullptr) message_.reset(new ::std::string); + message_->append(a_message.GetString().c_str()); + } + + // Swap the contents of this AssertionResult with other. + void swap(AssertionResult& other); + + // Stores result of the assertion predicate. + bool success_; + // Stores the message describing the condition in case the expectation + // construct is not satisfied with the predicate's outcome. + // Referenced via a pointer to avoid taking too much stack frame space + // with test assertions. + std::unique_ptr< ::std::string> message_; +}; + +// Makes a successful assertion result. +GTEST_API_ AssertionResult AssertionSuccess(); + +// Makes a failed assertion result. +GTEST_API_ AssertionResult AssertionFailure(); + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << msg. +GTEST_API_ AssertionResult AssertionFailure(const Message& msg); + +} // namespace testing + +// Includes the auto-generated header that implements a family of generic +// predicate assertion macros. This include comes late because it relies on +// APIs declared above. +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file is AUTOMATICALLY GENERATED on 01/02/2019 by command +// 'gen_gtest_pred_impl.py 5'. DO NOT EDIT BY HAND! +// +// Implements a family of generic predicate assertion macros. +// GOOGLETEST_CM0001 DO NOT DELETE + +#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ +#define GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ + + +namespace testing { + +// This header implements a family of generic predicate assertion +// macros: +// +// ASSERT_PRED_FORMAT1(pred_format, v1) +// ASSERT_PRED_FORMAT2(pred_format, v1, v2) +// ... +// +// where pred_format is a function or functor that takes n (in the +// case of ASSERT_PRED_FORMATn) values and their source expression +// text, and returns a testing::AssertionResult. See the definition +// of ASSERT_EQ in gtest.h for an example. +// +// If you don't care about formatting, you can use the more +// restrictive version: +// +// ASSERT_PRED1(pred, v1) +// ASSERT_PRED2(pred, v1, v2) +// ... +// +// where pred is an n-ary function or functor that returns bool, +// and the values v1, v2, ..., must support the << operator for +// streaming to std::ostream. +// +// We also define the EXPECT_* variations. +// +// For now we only support predicates whose arity is at most 5. +// Please email googletestframework@googlegroups.com if you need +// support for higher arities. + +// GTEST_ASSERT_ is the basic statement to which all of the assertions +// in this file reduce. Don't use this in your code. + +#define GTEST_ASSERT_(expression, on_failure) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar = (expression)) \ + ; \ + else \ + on_failure(gtest_ar.failure_message()) + + +// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +template <typename Pred, + typename T1> +AssertionResult AssertPred1Helper(const char* pred_text, + const char* e1, + Pred pred, + const T1& v1) { + if (pred(v1)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1. +// Don't use this in your code. +#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, v1), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +#define GTEST_PRED1_(pred, v1, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, \ + #v1, \ + pred, \ + v1), on_failure) + +// Unary predicate assertion macros. +#define EXPECT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +template <typename Pred, + typename T1, + typename T2> +AssertionResult AssertPred2Helper(const char* pred_text, + const char* e1, + const char* e2, + Pred pred, + const T1& v1, + const T2& v2) { + if (pred(v1, v2)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ", " << e2 + << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" + << e2 << " evaluates to " << ::testing::PrintToString(v2); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2. +// Don't use this in your code. +#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +#define GTEST_PRED2_(pred, v1, v2, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, \ + #v1, \ + #v2, \ + pred, \ + v1, \ + v2), on_failure) + +// Binary predicate assertion macros. +#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +template <typename Pred, + typename T1, + typename T2, + typename T3> +AssertionResult AssertPred3Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3) { + if (pred(v1, v2, v3)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ", " << e2 << ", " << e3 + << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" + << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n" + << e3 << " evaluates to " << ::testing::PrintToString(v3); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3. +// Don't use this in your code. +#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +#define GTEST_PRED3_(pred, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred3Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + pred, \ + v1, \ + v2, \ + v3), on_failure) + +// Ternary predicate assertion macros. +#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +template <typename Pred, + typename T1, + typename T2, + typename T3, + typename T4> +AssertionResult AssertPred4Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4) { + if (pred(v1, v2, v3, v4)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4 + << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" + << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n" + << e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n" + << e4 << " evaluates to " << ::testing::PrintToString(v4); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4. +// Don't use this in your code. +#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4), on_failure) + +// 4-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +template <typename Pred, + typename T1, + typename T2, + typename T3, + typename T4, + typename T5> +AssertionResult AssertPred5Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + const char* e5, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4, + const T5& v5) { + if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess(); + + return AssertionFailure() + << pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4 + << ", " << e5 << ") evaluates to false, where" + << "\n" + << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" + << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n" + << e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n" + << e4 << " evaluates to " << ::testing::PrintToString(v4) << "\n" + << e5 << " evaluates to " << ::testing::PrintToString(v5); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5. +// Don't use this in your code. +#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + #v5, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4, \ + v5), on_failure) + +// 5-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) + + + +} // namespace testing + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ + +namespace testing { + +// The abstract class that all tests inherit from. +// +// In Google Test, a unit test program contains one or many TestSuites, and +// each TestSuite contains one or many Tests. +// +// When you define a test using the TEST macro, you don't need to +// explicitly derive from Test - the TEST macro automatically does +// this for you. +// +// The only time you derive from Test is when defining a test fixture +// to be used in a TEST_F. For example: +// +// class FooTest : public testing::Test { +// protected: +// void SetUp() override { ... } +// void TearDown() override { ... } +// ... +// }; +// +// TEST_F(FooTest, Bar) { ... } +// TEST_F(FooTest, Baz) { ... } +// +// Test is not copyable. +class GTEST_API_ Test { + public: + friend class TestInfo; + + // The d'tor is virtual as we intend to inherit from Test. + virtual ~Test(); + + // Sets up the stuff shared by all tests in this test suite. + // + // Google Test will call Foo::SetUpTestSuite() before running the first + // test in test suite Foo. Hence a sub-class can define its own + // SetUpTestSuite() method to shadow the one defined in the super + // class. + static void SetUpTestSuite() {} + + // Tears down the stuff shared by all tests in this test suite. + // + // Google Test will call Foo::TearDownTestSuite() after running the last + // test in test suite Foo. Hence a sub-class can define its own + // TearDownTestSuite() method to shadow the one defined in the super + // class. + static void TearDownTestSuite() {} + + // Legacy API is deprecated but still available. Use SetUpTestSuite and + // TearDownTestSuite instead. +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + static void TearDownTestCase() {} + static void SetUpTestCase() {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Returns true if and only if the current test has a fatal failure. + static bool HasFatalFailure(); + + // Returns true if and only if the current test has a non-fatal failure. + static bool HasNonfatalFailure(); + + // Returns true if and only if the current test was skipped. + static bool IsSkipped(); + + // Returns true if and only if the current test has a (either fatal or + // non-fatal) failure. + static bool HasFailure() { return HasFatalFailure() || HasNonfatalFailure(); } + + // Logs a property for the current test, test suite, or for the entire + // invocation of the test program when used outside of the context of a + // test suite. Only the last value for a given key is remembered. These + // are public static so they can be called from utility functions that are + // not members of the test fixture. Calls to RecordProperty made during + // lifespan of the test (from the moment its constructor starts to the + // moment its destructor finishes) will be output in XML as attributes of + // the <testcase> element. Properties recorded from fixture's + // SetUpTestSuite or TearDownTestSuite are logged as attributes of the + // corresponding <testsuite> element. Calls to RecordProperty made in the + // global context (before or after invocation of RUN_ALL_TESTS and from + // SetUp/TearDown method of Environment objects registered with Google + // Test) will be output as attributes of the <testsuites> element. + static void RecordProperty(const std::string& key, const std::string& value); + static void RecordProperty(const std::string& key, int value); + + protected: + // Creates a Test object. + Test(); + + // Sets up the test fixture. + virtual void SetUp(); + + // Tears down the test fixture. + virtual void TearDown(); + + private: + // Returns true if and only if the current test has the same fixture class + // as the first test in the current test suite. + static bool HasSameFixtureClass(); + + // Runs the test after the test fixture has been set up. + // + // A sub-class must implement this to define the test logic. + // + // DO NOT OVERRIDE THIS FUNCTION DIRECTLY IN A USER PROGRAM. + // Instead, use the TEST or TEST_F macro. + virtual void TestBody() = 0; + + // Sets up, executes, and tears down the test. + void Run(); + + // Deletes self. We deliberately pick an unusual name for this + // internal method to avoid clashing with names used in user TESTs. + void DeleteSelf_() { delete this; } + + const std::unique_ptr<GTEST_FLAG_SAVER_> gtest_flag_saver_; + + // Often a user misspells SetUp() as Setup() and spends a long time + // wondering why it is never called by Google Test. The declaration of + // the following method is solely for catching such an error at + // compile time: + // + // - The return type is deliberately chosen to be not void, so it + // will be a conflict if void Setup() is declared in the user's + // test fixture. + // + // - This method is private, so it will be another compiler error + // if the method is called from the user's test fixture. + // + // DO NOT OVERRIDE THIS FUNCTION. + // + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return nullptr; } + + // We disallow copying Tests. + GTEST_DISALLOW_COPY_AND_ASSIGN_(Test); +}; + +typedef internal::TimeInMillis TimeInMillis; + +// A copyable object representing a user specified test property which can be +// output as a key/value string pair. +// +// Don't inherit from TestProperty as its destructor is not virtual. +class TestProperty { + public: + // C'tor. TestProperty does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestProperty object. + TestProperty(const std::string& a_key, const std::string& a_value) : + key_(a_key), value_(a_value) { + } + + // Gets the user supplied key. + const char* key() const { + return key_.c_str(); + } + + // Gets the user supplied value. + const char* value() const { + return value_.c_str(); + } + + // Sets a new value, overriding the one supplied in the constructor. + void SetValue(const std::string& new_value) { + value_ = new_value; + } + + private: + // The key supplied by the user. + std::string key_; + // The value supplied by the user. + std::string value_; +}; + +// The result of a single Test. This includes a list of +// TestPartResults, a list of TestProperties, a count of how many +// death tests there are in the Test, and how much time it took to run +// the Test. +// +// TestResult is not copyable. +class GTEST_API_ TestResult { + public: + // Creates an empty TestResult. + TestResult(); + + // D'tor. Do not inherit from TestResult. + ~TestResult(); + + // Gets the number of all test parts. This is the sum of the number + // of successful test parts and the number of failed test parts. + int total_part_count() const; + + // Returns the number of the test properties. + int test_property_count() const; + + // Returns true if and only if the test passed (i.e. no test part failed). + bool Passed() const { return !Skipped() && !Failed(); } + + // Returns true if and only if the test was skipped. + bool Skipped() const; + + // Returns true if and only if the test failed. + bool Failed() const; + + // Returns true if and only if the test fatally failed. + bool HasFatalFailure() const; + + // Returns true if and only if the test has a non-fatal failure. + bool HasNonfatalFailure() const; + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Gets the time of the test case start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const { return start_timestamp_; } + + // Returns the i-th test part result among all the results. i can range from 0 + // to total_part_count() - 1. If i is not in that range, aborts the program. + const TestPartResult& GetTestPartResult(int i) const; + + // Returns the i-th test property. i can range from 0 to + // test_property_count() - 1. If i is not in that range, aborts the + // program. + const TestProperty& GetTestProperty(int i) const; + + private: + friend class TestInfo; + friend class TestSuite; + friend class UnitTest; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::ExecDeathTest; + friend class internal::TestResultAccessor; + friend class internal::UnitTestImpl; + friend class internal::WindowsDeathTest; + friend class internal::FuchsiaDeathTest; + + // Gets the vector of TestPartResults. + const std::vector<TestPartResult>& test_part_results() const { + return test_part_results_; + } + + // Gets the vector of TestProperties. + const std::vector<TestProperty>& test_properties() const { + return test_properties_; + } + + // Sets the start time. + void set_start_timestamp(TimeInMillis start) { start_timestamp_ = start; } + + // Sets the elapsed time. + void set_elapsed_time(TimeInMillis elapsed) { elapsed_time_ = elapsed; } + + // Adds a test property to the list. The property is validated and may add + // a non-fatal failure if invalid (e.g., if it conflicts with reserved + // key names). If a property is already recorded for the same key, the + // value will be updated, rather than storing multiple values for the same + // key. xml_element specifies the element for which the property is being + // recorded and is used for validation. + void RecordProperty(const std::string& xml_element, + const TestProperty& test_property); + + // Adds a failure if the key is a reserved attribute of Google Test + // testsuite tags. Returns true if the property is valid. + // FIXME: Validate attribute names are legal and human readable. + static bool ValidateTestProperty(const std::string& xml_element, + const TestProperty& test_property); + + // Adds a test part result to the list. + void AddTestPartResult(const TestPartResult& test_part_result); + + // Returns the death test count. + int death_test_count() const { return death_test_count_; } + + // Increments the death test count, returning the new count. + int increment_death_test_count() { return ++death_test_count_; } + + // Clears the test part results. + void ClearTestPartResults(); + + // Clears the object. + void Clear(); + + // Protects mutable state of the property vector and of owned + // properties, whose values may be updated. + internal::Mutex test_properties_mutex_; + + // The vector of TestPartResults + std::vector<TestPartResult> test_part_results_; + // The vector of TestProperties + std::vector<TestProperty> test_properties_; + // Running count of death tests. + int death_test_count_; + // The start time, in milliseconds since UNIX Epoch. + TimeInMillis start_timestamp_; + // The elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + + // We disallow copying TestResult. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestResult); +}; // class TestResult + +// A TestInfo object stores the following information about a test: +// +// Test suite name +// Test name +// Whether the test should be run +// A function pointer that creates the test object when invoked +// Test result +// +// The constructor of TestInfo registers itself with the UnitTest +// singleton such that the RUN_ALL_TESTS() macro knows which tests to +// run. +class GTEST_API_ TestInfo { + public: + // Destructs a TestInfo object. This function is not virtual, so + // don't inherit from TestInfo. + ~TestInfo(); + + // Returns the test suite name. + const char* test_suite_name() const { return test_suite_name_.c_str(); } + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + const char* test_case_name() const { return test_suite_name(); } +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Returns the test name. + const char* name() const { return name_.c_str(); } + + // Returns the name of the parameter type, or NULL if this is not a typed + // or a type-parameterized test. + const char* type_param() const { + if (type_param_.get() != nullptr) return type_param_->c_str(); + return nullptr; + } + + // Returns the text representation of the value parameter, or NULL if this + // is not a value-parameterized test. + const char* value_param() const { + if (value_param_.get() != nullptr) return value_param_->c_str(); + return nullptr; + } + + // Returns the file name where this test is defined. + const char* file() const { return location_.file.c_str(); } + + // Returns the line where this test is defined. + int line() const { return location_.line; } + + // Return true if this test should not be run because it's in another shard. + bool is_in_another_shard() const { return is_in_another_shard_; } + + // Returns true if this test should run, that is if the test is not + // disabled (or it is disabled but the also_run_disabled_tests flag has + // been specified) and its full name matches the user-specified filter. + // + // Google Test allows the user to filter the tests by their full names. + // The full name of a test Bar in test suite Foo is defined as + // "Foo.Bar". Only the tests that match the filter will run. + // + // A filter is a colon-separated list of glob (not regex) patterns, + // optionally followed by a '-' and a colon-separated list of + // negative patterns (tests to exclude). A test is run if it + // matches one of the positive patterns and does not match any of + // the negative patterns. + // + // For example, *A*:Foo.* is a filter that matches any string that + // contains the character 'A' or starts with "Foo.". + bool should_run() const { return should_run_; } + + // Returns true if and only if this test will appear in the XML report. + bool is_reportable() const { + // The XML report includes tests matching the filter, excluding those + // run in other shards. + return matches_filter_ && !is_in_another_shard_; + } + + // Returns the result of the test. + const TestResult* result() const { return &result_; } + + private: +#if GTEST_HAS_DEATH_TEST + friend class internal::DefaultDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + friend class Test; + friend class TestSuite; + friend class internal::UnitTestImpl; + friend class internal::StreamingListenerTest; + friend TestInfo* internal::MakeAndRegisterTestInfo( + const char* test_suite_name, const char* name, const char* type_param, + const char* value_param, internal::CodeLocation code_location, + internal::TypeId fixture_class_id, internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc, + internal::TestFactoryBase* factory); + + // Constructs a TestInfo object. The newly constructed instance assumes + // ownership of the factory object. + TestInfo(const std::string& test_suite_name, const std::string& name, + const char* a_type_param, // NULL if not a type-parameterized test + const char* a_value_param, // NULL if not a value-parameterized test + internal::CodeLocation a_code_location, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory); + + // Increments the number of death tests encountered in this test so + // far. + int increment_death_test_count() { + return result_.increment_death_test_count(); + } + + // Creates the test object, runs it, records its result, and then + // deletes it. + void Run(); + + // Skip and records the test result for this object. + void Skip(); + + static void ClearTestResult(TestInfo* test_info) { + test_info->result_.Clear(); + } + + // These fields are immutable properties of the test. + const std::string test_suite_name_; // test suite name + const std::string name_; // Test name + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const std::unique_ptr<const ::std::string> type_param_; + // Text representation of the value parameter, or NULL if this is not a + // value-parameterized test. + const std::unique_ptr<const ::std::string> value_param_; + internal::CodeLocation location_; + const internal::TypeId fixture_class_id_; // ID of the test fixture class + bool should_run_; // True if and only if this test should run + bool is_disabled_; // True if and only if this test is disabled + bool matches_filter_; // True if this test matches the + // user-specified filter. + bool is_in_another_shard_; // Will be run in another shard. + internal::TestFactoryBase* const factory_; // The factory that creates + // the test object + + // This field is mutable and needs to be reset before running the + // test for the second time. + TestResult result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfo); +}; + +// A test suite, which consists of a vector of TestInfos. +// +// TestSuite is not copyable. +class GTEST_API_ TestSuite { + public: + // Creates a TestSuite with the given name. + // + // TestSuite does NOT have a default constructor. Always use this + // constructor to create a TestSuite object. + // + // Arguments: + // + // name: name of the test suite + // a_type_param: the name of the test's type parameter, or NULL if + // this is not a type-parameterized test. + // set_up_tc: pointer to the function that sets up the test suite + // tear_down_tc: pointer to the function that tears down the test suite + TestSuite(const char* name, const char* a_type_param, + internal::SetUpTestSuiteFunc set_up_tc, + internal::TearDownTestSuiteFunc tear_down_tc); + + // Destructor of TestSuite. + virtual ~TestSuite(); + + // Gets the name of the TestSuite. + const char* name() const { return name_.c_str(); } + + // Returns the name of the parameter type, or NULL if this is not a + // type-parameterized test suite. + const char* type_param() const { + if (type_param_.get() != nullptr) return type_param_->c_str(); + return nullptr; + } + + // Returns true if any test in this test suite should run. + bool should_run() const { return should_run_; } + + // Gets the number of successful tests in this test suite. + int successful_test_count() const; + + // Gets the number of skipped tests in this test suite. + int skipped_test_count() const; + + // Gets the number of failed tests in this test suite. + int failed_test_count() const; + + // Gets the number of disabled tests that will be reported in the XML report. + int reportable_disabled_test_count() const; + + // Gets the number of disabled tests in this test suite. + int disabled_test_count() const; + + // Gets the number of tests to be printed in the XML report. + int reportable_test_count() const; + + // Get the number of tests in this test suite that should run. + int test_to_run_count() const; + + // Gets the number of all tests in this test suite. + int total_test_count() const; + + // Returns true if and only if the test suite passed. + bool Passed() const { return !Failed(); } + + // Returns true if and only if the test suite failed. + bool Failed() const { + return failed_test_count() > 0 || ad_hoc_test_result().Failed(); + } + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Gets the time of the test suite start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const { return start_timestamp_; } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + const TestInfo* GetTestInfo(int i) const; + + // Returns the TestResult that holds test properties recorded during + // execution of SetUpTestSuite and TearDownTestSuite. + const TestResult& ad_hoc_test_result() const { return ad_hoc_test_result_; } + + private: + friend class Test; + friend class internal::UnitTestImpl; + + // Gets the (mutable) vector of TestInfos in this TestSuite. + std::vector<TestInfo*>& test_info_list() { return test_info_list_; } + + // Gets the (immutable) vector of TestInfos in this TestSuite. + const std::vector<TestInfo*>& test_info_list() const { + return test_info_list_; + } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + TestInfo* GetMutableTestInfo(int i); + + // Sets the should_run member. + void set_should_run(bool should) { should_run_ = should; } + + // Adds a TestInfo to this test suite. Will delete the TestInfo upon + // destruction of the TestSuite object. + void AddTestInfo(TestInfo * test_info); + + // Clears the results of all tests in this test suite. + void ClearResult(); + + // Clears the results of all tests in the given test suite. + static void ClearTestSuiteResult(TestSuite* test_suite) { + test_suite->ClearResult(); + } + + // Runs every test in this TestSuite. + void Run(); + + // Skips the execution of tests under this TestSuite + void Skip(); + + // Runs SetUpTestSuite() for this TestSuite. This wrapper is needed + // for catching exceptions thrown from SetUpTestSuite(). + void RunSetUpTestSuite() { + if (set_up_tc_ != nullptr) { + (*set_up_tc_)(); + } + } + + // Runs TearDownTestSuite() for this TestSuite. This wrapper is + // needed for catching exceptions thrown from TearDownTestSuite(). + void RunTearDownTestSuite() { + if (tear_down_tc_ != nullptr) { + (*tear_down_tc_)(); + } + } + + // Returns true if and only if test passed. + static bool TestPassed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Passed(); + } + + // Returns true if and only if test skipped. + static bool TestSkipped(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Skipped(); + } + + // Returns true if and only if test failed. + static bool TestFailed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Failed(); + } + + // Returns true if and only if the test is disabled and will be reported in + // the XML report. + static bool TestReportableDisabled(const TestInfo* test_info) { + return test_info->is_reportable() && test_info->is_disabled_; + } + + // Returns true if and only if test is disabled. + static bool TestDisabled(const TestInfo* test_info) { + return test_info->is_disabled_; + } + + // Returns true if and only if this test will appear in the XML report. + static bool TestReportable(const TestInfo* test_info) { + return test_info->is_reportable(); + } + + // Returns true if the given test should run. + static bool ShouldRunTest(const TestInfo* test_info) { + return test_info->should_run(); + } + + // Shuffles the tests in this test suite. + void ShuffleTests(internal::Random* random); + + // Restores the test order to before the first shuffle. + void UnshuffleTests(); + + // Name of the test suite. + std::string name_; + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const std::unique_ptr<const ::std::string> type_param_; + // The vector of TestInfos in their original order. It owns the + // elements in the vector. + std::vector<TestInfo*> test_info_list_; + // Provides a level of indirection for the test list to allow easy + // shuffling and restoring the test order. The i-th element in this + // vector is the index of the i-th test in the shuffled test list. + std::vector<int> test_indices_; + // Pointer to the function that sets up the test suite. + internal::SetUpTestSuiteFunc set_up_tc_; + // Pointer to the function that tears down the test suite. + internal::TearDownTestSuiteFunc tear_down_tc_; + // True if and only if any test in this test suite should run. + bool should_run_; + // The start time, in milliseconds since UNIX Epoch. + TimeInMillis start_timestamp_; + // Elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + // Holds test properties recorded during execution of SetUpTestSuite and + // TearDownTestSuite. + TestResult ad_hoc_test_result_; + + // We disallow copying TestSuites. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestSuite); +}; + +// An Environment object is capable of setting up and tearing down an +// environment. You should subclass this to define your own +// environment(s). +// +// An Environment object does the set-up and tear-down in virtual +// methods SetUp() and TearDown() instead of the constructor and the +// destructor, as: +// +// 1. You cannot safely throw from a destructor. This is a problem +// as in some cases Google Test is used where exceptions are enabled, and +// we may want to implement ASSERT_* using exceptions where they are +// available. +// 2. You cannot use ASSERT_* directly in a constructor or +// destructor. +class Environment { + public: + // The d'tor is virtual as we need to subclass Environment. + virtual ~Environment() {} + + // Override this to define how to set up the environment. + virtual void SetUp() {} + + // Override this to define how to tear down the environment. + virtual void TearDown() {} + private: + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return nullptr; } +}; + +#if GTEST_HAS_EXCEPTIONS + +// Exception which can be thrown from TestEventListener::OnTestPartResult. +class GTEST_API_ AssertionException + : public internal::GoogleTestFailureException { + public: + explicit AssertionException(const TestPartResult& result) + : GoogleTestFailureException(result) {} +}; + +#endif // GTEST_HAS_EXCEPTIONS + +// The interface for tracing execution of tests. The methods are organized in +// the order the corresponding events are fired. +class TestEventListener { + public: + virtual ~TestEventListener() {} + + // Fired before any test activity starts. + virtual void OnTestProgramStart(const UnitTest& unit_test) = 0; + + // Fired before each iteration of tests starts. There may be more than + // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration + // index, starting from 0. + virtual void OnTestIterationStart(const UnitTest& unit_test, + int iteration) = 0; + + // Fired before environment set-up for each iteration of tests starts. + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0; + + // Fired after environment set-up for each iteration of tests ends. + virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0; + + // Fired before the test suite starts. + virtual void OnTestSuiteStart(const TestSuite& /*test_suite*/) {} + + // Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + virtual void OnTestCaseStart(const TestCase& /*test_case*/) {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Fired before the test starts. + virtual void OnTestStart(const TestInfo& test_info) = 0; + + // Fired after a failed assertion or a SUCCEED() invocation. + // If you want to throw an exception from this function to skip to the next + // TEST, it must be AssertionException defined above, or inherited from it. + virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0; + + // Fired after the test ends. + virtual void OnTestEnd(const TestInfo& test_info) = 0; + + // Fired after the test suite ends. + virtual void OnTestSuiteEnd(const TestSuite& /*test_suite*/) {} + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + virtual void OnTestCaseEnd(const TestCase& /*test_case*/) {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Fired before environment tear-down for each iteration of tests starts. + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0; + + // Fired after environment tear-down for each iteration of tests ends. + virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0; + + // Fired after each iteration of tests finishes. + virtual void OnTestIterationEnd(const UnitTest& unit_test, + int iteration) = 0; + + // Fired after all test activities have ended. + virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0; +}; + +// The convenience class for users who need to override just one or two +// methods and are not concerned that a possible change to a signature of +// the methods they override will not be caught during the build. For +// comments about each method please see the definition of TestEventListener +// above. +class EmptyTestEventListener : public TestEventListener { + public: + void OnTestProgramStart(const UnitTest& /*unit_test*/) override {} + void OnTestIterationStart(const UnitTest& /*unit_test*/, + int /*iteration*/) override {} + void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) override {} + void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {} + void OnTestSuiteStart(const TestSuite& /*test_suite*/) override {} +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseStart(const TestCase& /*test_case*/) override {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + void OnTestStart(const TestInfo& /*test_info*/) override {} + void OnTestPartResult(const TestPartResult& /*test_part_result*/) override {} + void OnTestEnd(const TestInfo& /*test_info*/) override {} + void OnTestSuiteEnd(const TestSuite& /*test_suite*/) override {} +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + void OnTestCaseEnd(const TestCase& /*test_case*/) override {} +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) override {} + void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {} + void OnTestIterationEnd(const UnitTest& /*unit_test*/, + int /*iteration*/) override {} + void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {} +}; + +// TestEventListeners lets users add listeners to track events in Google Test. +class GTEST_API_ TestEventListeners { + public: + TestEventListeners(); + ~TestEventListeners(); + + // Appends an event listener to the end of the list. Google Test assumes + // the ownership of the listener (i.e. it will delete the listener when + // the test program finishes). + void Append(TestEventListener* listener); + + // Removes the given event listener from the list and returns it. It then + // becomes the caller's responsibility to delete the listener. Returns + // NULL if the listener is not found in the list. + TestEventListener* Release(TestEventListener* listener); + + // Returns the standard listener responsible for the default console + // output. Can be removed from the listeners list to shut down default + // console output. Note that removing this object from the listener list + // with Release transfers its ownership to the caller and makes this + // function return NULL the next time. + TestEventListener* default_result_printer() const { + return default_result_printer_; + } + + // Returns the standard listener responsible for the default XML output + // controlled by the --gtest_output=xml flag. Can be removed from the + // listeners list by users who want to shut down the default XML output + // controlled by this flag and substitute it with custom one. Note that + // removing this object from the listener list with Release transfers its + // ownership to the caller and makes this function return NULL the next + // time. + TestEventListener* default_xml_generator() const { + return default_xml_generator_; + } + + private: + friend class TestSuite; + friend class TestInfo; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::NoExecDeathTest; + friend class internal::TestEventListenersAccessor; + friend class internal::UnitTestImpl; + + // Returns repeater that broadcasts the TestEventListener events to all + // subscribers. + TestEventListener* repeater(); + + // Sets the default_result_printer attribute to the provided listener. + // The listener is also added to the listener list and previous + // default_result_printer is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultResultPrinter(TestEventListener* listener); + + // Sets the default_xml_generator attribute to the provided listener. The + // listener is also added to the listener list and previous + // default_xml_generator is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultXmlGenerator(TestEventListener* listener); + + // Controls whether events will be forwarded by the repeater to the + // listeners in the list. + bool EventForwardingEnabled() const; + void SuppressEventForwarding(); + + // The actual list of listeners. + internal::TestEventRepeater* repeater_; + // Listener responsible for the standard result output. + TestEventListener* default_result_printer_; + // Listener responsible for the creation of the XML output file. + TestEventListener* default_xml_generator_; + + // We disallow copying TestEventListeners. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventListeners); +}; + +// A UnitTest consists of a vector of TestSuites. +// +// This is a singleton class. The only instance of UnitTest is +// created when UnitTest::GetInstance() is first called. This +// instance is never deleted. +// +// UnitTest is not copyable. +// +// This class is thread-safe as long as the methods are called +// according to their specification. +class GTEST_API_ UnitTest { + public: + // Gets the singleton UnitTest object. The first time this method + // is called, a UnitTest object is constructed and returned. + // Consecutive calls will return the same object. + static UnitTest* GetInstance(); + + // Runs all tests in this UnitTest object and prints the result. + // Returns 0 if successful, or 1 otherwise. + // + // This method can only be called from the main thread. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + int Run() GTEST_MUST_USE_RESULT_; + + // Returns the working directory when the first TEST() or TEST_F() + // was executed. The UnitTest object owns the string. + const char* original_working_dir() const; + + // Returns the TestSuite object for the test that's currently running, + // or NULL if no test is running. + const TestSuite* current_test_suite() const GTEST_LOCK_EXCLUDED_(mutex_); + +// Legacy API is still available but deprecated +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + const TestCase* current_test_case() const GTEST_LOCK_EXCLUDED_(mutex_); +#endif + + // Returns the TestInfo object for the test that's currently running, + // or NULL if no test is running. + const TestInfo* current_test_info() const + GTEST_LOCK_EXCLUDED_(mutex_); + + // Returns the random seed used at the start of the current test run. + int random_seed() const; + + // Returns the ParameterizedTestSuiteRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + internal::ParameterizedTestSuiteRegistry& parameterized_test_registry() + GTEST_LOCK_EXCLUDED_(mutex_); + + // Gets the number of successful test suites. + int successful_test_suite_count() const; + + // Gets the number of failed test suites. + int failed_test_suite_count() const; + + // Gets the number of all test suites. + int total_test_suite_count() const; + + // Gets the number of all test suites that contain at least one test + // that should run. + int test_suite_to_run_count() const; + + // Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + int successful_test_case_count() const; + int failed_test_case_count() const; + int total_test_case_count() const; + int test_case_to_run_count() const; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of skipped tests. + int skipped_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests that will be reported in the XML report. + int reportable_disabled_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of tests to be printed in the XML report. + int reportable_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the time of the test program start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const; + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const; + + // Returns true if and only if the unit test passed (i.e. all test suites + // passed). + bool Passed() const; + + // Returns true if and only if the unit test failed (i.e. some test suite + // failed or something outside of all tests failed). + bool Failed() const; + + // Gets the i-th test suite among all the test suites. i can range from 0 to + // total_test_suite_count() - 1. If i is not in that range, returns NULL. + const TestSuite* GetTestSuite(int i) const; + +// Legacy API is deprecated but still available +#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + const TestCase* GetTestCase(int i) const; +#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ + + // Returns the TestResult containing information on test failures and + // properties logged outside of individual test suites. + const TestResult& ad_hoc_test_result() const; + + // Returns the list of event listeners that can be used to track events + // inside Google Test. + TestEventListeners& listeners(); + + private: + // Registers and returns a global test environment. When a test + // program is run, all global test environments will be set-up in + // the order they were registered. After all tests in the program + // have finished, all global test environments will be torn-down in + // the *reverse* order they were registered. + // + // The UnitTest object takes ownership of the given environment. + // + // This method can only be called from the main thread. + Environment* AddEnvironment(Environment* env); + + // Adds a TestPartResult to the current TestResult object. All + // Google Test assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) + // eventually call this to report their results. The user code + // should use the assertion macros instead of calling this directly. + void AddTestPartResult(TestPartResult::Type result_type, + const char* file_name, + int line_number, + const std::string& message, + const std::string& os_stack_trace) + GTEST_LOCK_EXCLUDED_(mutex_); + + // Adds a TestProperty to the current TestResult object when invoked from + // inside a test, to current TestSuite's ad_hoc_test_result_ when invoked + // from SetUpTestSuite or TearDownTestSuite, or to the global property set + // when invoked elsewhere. If the result already contains a property with + // the same key, the value will be updated. + void RecordProperty(const std::string& key, const std::string& value); + + // Gets the i-th test suite among all the test suites. i can range from 0 to + // total_test_suite_count() - 1. If i is not in that range, returns NULL. + TestSuite* GetMutableTestSuite(int i); + + // Accessors for the implementation object. + internal::UnitTestImpl* impl() { return impl_; } + const internal::UnitTestImpl* impl() const { return impl_; } + + // These classes and functions are friends as they need to access private + // members of UnitTest. + friend class ScopedTrace; + friend class Test; + friend class internal::AssertHelper; + friend class internal::StreamingListenerTest; + friend class internal::UnitTestRecordPropertyTestHelper; + friend Environment* AddGlobalTestEnvironment(Environment* env); + friend std::set<std::string>* internal::GetIgnoredParameterizedTestSuites(); + friend internal::UnitTestImpl* internal::GetUnitTestImpl(); + friend void internal::ReportFailureInUnknownLocation( + TestPartResult::Type result_type, + const std::string& message); + + // Creates an empty UnitTest. + UnitTest(); + + // D'tor + virtual ~UnitTest(); + + // Pushes a trace defined by SCOPED_TRACE() on to the per-thread + // Google Test trace stack. + void PushGTestTrace(const internal::TraceInfo& trace) + GTEST_LOCK_EXCLUDED_(mutex_); + + // Pops a trace from the per-thread Google Test trace stack. + void PopGTestTrace() + GTEST_LOCK_EXCLUDED_(mutex_); + + // Protects mutable state in *impl_. This is mutable as some const + // methods need to lock it too. + mutable internal::Mutex mutex_; + + // Opaque implementation object. This field is never changed once + // the object is constructed. We don't mark it as const here, as + // doing so will cause a warning in the constructor of UnitTest. + // Mutable state in *impl_ is protected by mutex_. + internal::UnitTestImpl* impl_; + + // We disallow copying UnitTest. + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTest); +}; + +// A convenient wrapper for adding an environment for the test +// program. +// +// You should call this before RUN_ALL_TESTS() is called, probably in +// main(). If you use gtest_main, you need to call this before main() +// starts for it to take effect. For example, you can define a global +// variable like this: +// +// testing::Environment* const foo_env = +// testing::AddGlobalTestEnvironment(new FooEnvironment); +// +// However, we strongly recommend you to write your own main() and +// call AddGlobalTestEnvironment() there, as relying on initialization +// of global variables makes the code harder to read and may cause +// problems when you register multiple environments from different +// translation units and the environments have dependencies among them +// (remember that the compiler doesn't guarantee the order in which +// global variables from different translation units are initialized). +inline Environment* AddGlobalTestEnvironment(Environment* env) { + return UnitTest::GetInstance()->AddEnvironment(env); +} + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +GTEST_API_ void InitGoogleTest(int* argc, char** argv); + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv); + +// This overloaded version can be used on Arduino/embedded platforms where +// there is no argc/argv. +GTEST_API_ void InitGoogleTest(); + +namespace internal { + +// Separate the error generating code from the code path to reduce the stack +// frame size of CmpHelperEQ. This helps reduce the overhead of some sanitizers +// when calling EXPECT_* in a tight loop. +template <typename T1, typename T2> +AssertionResult CmpHelperEQFailure(const char* lhs_expression, + const char* rhs_expression, + const T1& lhs, const T2& rhs) { + return EqFailure(lhs_expression, + rhs_expression, + FormatForComparisonFailureMessage(lhs, rhs), + FormatForComparisonFailureMessage(rhs, lhs), + false); +} + +// This block of code defines operator==/!= +// to block lexical scope lookup. +// It prevents using invalid operator==/!= defined at namespace scope. +struct faketype {}; +inline bool operator==(faketype, faketype) { return true; } +inline bool operator!=(faketype, faketype) { return false; } + +// The helper function for {ASSERT|EXPECT}_EQ. +template <typename T1, typename T2> +AssertionResult CmpHelperEQ(const char* lhs_expression, + const char* rhs_expression, + const T1& lhs, + const T2& rhs) { + if (lhs == rhs) { + return AssertionSuccess(); + } + + return CmpHelperEQFailure(lhs_expression, rhs_expression, lhs, rhs); +} + +class EqHelper { + public: + // This templatized version is for the general case. + template < + typename T1, typename T2, + // Disable this overload for cases where one argument is a pointer + // and the other is the null pointer constant. + typename std::enable_if<!std::is_integral<T1>::value || + !std::is_pointer<T2>::value>::type* = nullptr> + static AssertionResult Compare(const char* lhs_expression, + const char* rhs_expression, const T1& lhs, + const T2& rhs) { + return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs); + } + + // With this overloaded version, we allow anonymous enums to be used + // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous + // enums can be implicitly cast to BiggestInt. + // + // Even though its body looks the same as the above version, we + // cannot merge the two, as it will make anonymous enums unhappy. + static AssertionResult Compare(const char* lhs_expression, + const char* rhs_expression, + BiggestInt lhs, + BiggestInt rhs) { + return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs); + } + + template <typename T> + static AssertionResult Compare( + const char* lhs_expression, const char* rhs_expression, + // Handle cases where '0' is used as a null pointer literal. + std::nullptr_t /* lhs */, T* rhs) { + // We already know that 'lhs' is a null pointer. + return CmpHelperEQ(lhs_expression, rhs_expression, static_cast<T*>(nullptr), + rhs); + } +}; + +// Separate the error generating code from the code path to reduce the stack +// frame size of CmpHelperOP. This helps reduce the overhead of some sanitizers +// when calling EXPECT_OP in a tight loop. +template <typename T1, typename T2> +AssertionResult CmpHelperOpFailure(const char* expr1, const char* expr2, + const T1& val1, const T2& val2, + const char* op) { + return AssertionFailure() + << "Expected: (" << expr1 << ") " << op << " (" << expr2 + << "), actual: " << FormatForComparisonFailureMessage(val1, val2) + << " vs " << FormatForComparisonFailureMessage(val2, val1); +} + +// A macro for implementing the helper functions needed to implement +// ASSERT_?? and EXPECT_??. It is here just to avoid copy-and-paste +// of similar code. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ +template <typename T1, typename T2>\ +AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + const T1& val1, const T2& val2) {\ + if (val1 op val2) {\ + return AssertionSuccess();\ + } else {\ + return CmpHelperOpFailure(expr1, expr2, val1, val2, #op);\ + }\ +} + +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// Implements the helper function for {ASSERT|EXPECT}_NE +GTEST_IMPL_CMP_HELPER_(NE, !=) +// Implements the helper function for {ASSERT|EXPECT}_LE +GTEST_IMPL_CMP_HELPER_(LE, <=) +// Implements the helper function for {ASSERT|EXPECT}_LT +GTEST_IMPL_CMP_HELPER_(LT, <) +// Implements the helper function for {ASSERT|EXPECT}_GE +GTEST_IMPL_CMP_HELPER_(GE, >=) +// Implements the helper function for {ASSERT|EXPECT}_GT +GTEST_IMPL_CMP_HELPER_(GT, >) + +#undef GTEST_IMPL_CMP_HELPER_ + +// The helper function for {ASSERT|EXPECT}_STREQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRNE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + + +// Helper function for *_STREQ on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2); + +// Helper function for *_STRNE on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2); + +} // namespace internal + +// IsSubstring() and IsNotSubstring() are intended to be used as the +// first argument to {EXPECT,ASSERT}_PRED_FORMAT2(), not by +// themselves. They check whether needle is a substring of haystack +// (NULL is considered a substring of itself only), and return an +// appropriate error message when they fail. +// +// The {needle,haystack}_expr arguments are the stringified +// expressions that generated the two real arguments. +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); + +#if GTEST_HAS_STD_WSTRING +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +// Helper template function for comparing floating-points. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template <typename RawType> +AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression, + const char* rhs_expression, + RawType lhs_value, + RawType rhs_value) { + const FloatingPoint<RawType> lhs(lhs_value), rhs(rhs_value); + + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + ::std::stringstream lhs_ss; + lhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2) + << lhs_value; + + ::std::stringstream rhs_ss; + rhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2) + << rhs_value; + + return EqFailure(lhs_expression, + rhs_expression, + StringStreamToString(&lhs_ss), + StringStreamToString(&rhs_ss), + false); +} + +// Helper function for implementing ASSERT_NEAR. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, + double val2, + double abs_error); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// A class that enables one to stream messages to assertion macros +class GTEST_API_ AssertHelper { + public: + // Constructor. + AssertHelper(TestPartResult::Type type, + const char* file, + int line, + const char* message); + ~AssertHelper(); + + // Message assignment is a semantic trick to enable assertion + // streaming; see the GTEST_MESSAGE_ macro below. + void operator=(const Message& message) const; + + private: + // We put our data in a struct so that the size of the AssertHelper class can + // be as small as possible. This is important because gcc is incapable of + // re-using stack space even for temporary variables, so every EXPECT_EQ + // reserves stack space for another AssertHelper. + struct AssertHelperData { + AssertHelperData(TestPartResult::Type t, + const char* srcfile, + int line_num, + const char* msg) + : type(t), file(srcfile), line(line_num), message(msg) { } + + TestPartResult::Type const type; + const char* const file; + int const line; + std::string const message; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelperData); + }; + + AssertHelperData* const data_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelper); +}; + +} // namespace internal + +// The pure interface class that all value-parameterized tests inherit from. +// A value-parameterized class must inherit from both ::testing::Test and +// ::testing::WithParamInterface. In most cases that just means inheriting +// from ::testing::TestWithParam, but more complicated test hierarchies +// may need to inherit from Test and WithParamInterface at different levels. +// +// This interface has support for accessing the test parameter value via +// the GetParam() method. +// +// Use it with one of the parameter generator defining functions, like Range(), +// Values(), ValuesIn(), Bool(), and Combine(). +// +// class FooTest : public ::testing::TestWithParam<int> { +// protected: +// FooTest() { +// // Can use GetParam() here. +// } +// ~FooTest() override { +// // Can use GetParam() here. +// } +// void SetUp() override { +// // Can use GetParam() here. +// } +// void TearDown override { +// // Can use GetParam() here. +// } +// }; +// TEST_P(FooTest, DoesBar) { +// // Can use GetParam() method here. +// Foo foo; +// ASSERT_TRUE(foo.DoesBar(GetParam())); +// } +// INSTANTIATE_TEST_SUITE_P(OneToTenRange, FooTest, ::testing::Range(1, 10)); + +template <typename T> +class WithParamInterface { + public: + typedef T ParamType; + virtual ~WithParamInterface() {} + + // The current parameter value. Is also available in the test fixture's + // constructor. + static const ParamType& GetParam() { + GTEST_CHECK_(parameter_ != nullptr) + << "GetParam() can only be called inside a value-parameterized test " + << "-- did you intend to write TEST_P instead of TEST_F?"; + return *parameter_; + } + + private: + // Sets parameter value. The caller is responsible for making sure the value + // remains alive and unchanged throughout the current test. + static void SetParam(const ParamType* parameter) { + parameter_ = parameter; + } + + // Static value used for accessing parameter during a test lifetime. + static const ParamType* parameter_; + + // TestClass must be a subclass of WithParamInterface<T> and Test. + template <class TestClass> friend class internal::ParameterizedTestFactory; +}; + +template <typename T> +const T* WithParamInterface<T>::parameter_ = nullptr; + +// Most value-parameterized classes can ignore the existence of +// WithParamInterface, and can just inherit from ::testing::TestWithParam. + +template <typename T> +class TestWithParam : public Test, public WithParamInterface<T> { +}; + +// Macros for indicating success/failure in test code. + +// Skips test in runtime. +// Skipping test aborts current function. +// Skipped tests are neither successful nor failed. +#define GTEST_SKIP() GTEST_SKIP_("") + +// ADD_FAILURE unconditionally adds a failure to the current test. +// SUCCEED generates a success - it doesn't automatically make the +// current test successful, as a test is only successful when it has +// no failure. +// +// EXPECT_* verifies that a certain condition is satisfied. If not, +// it behaves like ADD_FAILURE. In particular: +// +// EXPECT_TRUE verifies that a Boolean condition is true. +// EXPECT_FALSE verifies that a Boolean condition is false. +// +// FAIL and ASSERT_* are similar to ADD_FAILURE and EXPECT_*, except +// that they will also abort the current function on failure. People +// usually want the fail-fast behavior of FAIL and ASSERT_*, but those +// writing data-driven tests often find themselves using ADD_FAILURE +// and EXPECT_* more. + +// Generates a nonfatal failure with a generic message. +#define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed") + +// Generates a nonfatal failure at the given source file location with +// a generic message. +#define ADD_FAILURE_AT(file, line) \ + GTEST_MESSAGE_AT_(file, line, "Failed", \ + ::testing::TestPartResult::kNonFatalFailure) + +// Generates a fatal failure with a generic message. +#define GTEST_FAIL() GTEST_FATAL_FAILURE_("Failed") + +// Like GTEST_FAIL(), but at the given source file location. +#define GTEST_FAIL_AT(file, line) \ + GTEST_MESSAGE_AT_(file, line, "Failed", \ + ::testing::TestPartResult::kFatalFailure) + +// Define this macro to 1 to omit the definition of FAIL(), which is a +// generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_FAIL +# define FAIL() GTEST_FAIL() +#endif + +// Generates a success with a generic message. +#define GTEST_SUCCEED() GTEST_SUCCESS_("Succeeded") + +// Define this macro to 1 to omit the definition of SUCCEED(), which +// is a generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_SUCCEED +# define SUCCEED() GTEST_SUCCEED() +#endif + +// Macros for testing exceptions. +// +// * {ASSERT|EXPECT}_THROW(statement, expected_exception): +// Tests that the statement throws the expected exception. +// * {ASSERT|EXPECT}_NO_THROW(statement): +// Tests that the statement doesn't throw any exception. +// * {ASSERT|EXPECT}_ANY_THROW(statement): +// Tests that the statement throws an exception. + +#define EXPECT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_) +#define EXPECT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define EXPECT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define ASSERT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_) +#define ASSERT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_) +#define ASSERT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_) + +// Boolean assertions. Condition can be either a Boolean expression or an +// AssertionResult. For more information on how to use AssertionResult with +// these macros see comments on that class. +#define GTEST_EXPECT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_NONFATAL_FAILURE_) +#define GTEST_EXPECT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_NONFATAL_FAILURE_) +#define GTEST_ASSERT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_FATAL_FAILURE_) +#define GTEST_ASSERT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_FATAL_FAILURE_) + +// Define these macros to 1 to omit the definition of the corresponding +// EXPECT or ASSERT, which clashes with some users' own code. + +#if !GTEST_DONT_DEFINE_EXPECT_TRUE +#define EXPECT_TRUE(condition) GTEST_EXPECT_TRUE(condition) +#endif + +#if !GTEST_DONT_DEFINE_EXPECT_FALSE +#define EXPECT_FALSE(condition) GTEST_EXPECT_FALSE(condition) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_TRUE +#define ASSERT_TRUE(condition) GTEST_ASSERT_TRUE(condition) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_FALSE +#define ASSERT_FALSE(condition) GTEST_ASSERT_FALSE(condition) +#endif + +// Macros for testing equalities and inequalities. +// +// * {ASSERT|EXPECT}_EQ(v1, v2): Tests that v1 == v2 +// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2 +// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2 +// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2 +// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2 +// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2 +// +// When they are not, Google Test prints both the tested expressions and +// their actual values. The values must be compatible built-in types, +// or you will get a compiler error. By "compatible" we mean that the +// values can be compared by the respective operator. +// +// Note: +// +// 1. It is possible to make a user-defined type work with +// {ASSERT|EXPECT}_??(), but that requires overloading the +// comparison operators and is thus discouraged by the Google C++ +// Usage Guide. Therefore, you are advised to use the +// {ASSERT|EXPECT}_TRUE() macro to assert that two objects are +// equal. +// +// 2. The {ASSERT|EXPECT}_??() macros do pointer comparisons on +// pointers (in particular, C strings). Therefore, if you use it +// with two C strings, you are testing how their locations in memory +// are related, not how their content is related. To compare two C +// strings by content, use {ASSERT|EXPECT}_STR*(). +// +// 3. {ASSERT|EXPECT}_EQ(v1, v2) is preferred to +// {ASSERT|EXPECT}_TRUE(v1 == v2), as the former tells you +// what the actual value is when it fails, and similarly for the +// other comparisons. +// +// 4. Do not depend on the order in which {ASSERT|EXPECT}_??() +// evaluate their arguments, which is undefined. +// +// 5. These macros evaluate their arguments exactly once. +// +// Examples: +// +// EXPECT_NE(Foo(), 5); +// EXPECT_EQ(a_pointer, NULL); +// ASSERT_LT(i, array_size); +// ASSERT_GT(records.size(), 0) << "There is no record left."; + +#define EXPECT_EQ(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2) +#define EXPECT_NE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) +#define EXPECT_LE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define EXPECT_LT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define EXPECT_GE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define EXPECT_GT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +#define GTEST_ASSERT_EQ(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2) +#define GTEST_ASSERT_NE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) +#define GTEST_ASSERT_LE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define GTEST_ASSERT_LT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define GTEST_ASSERT_GE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define GTEST_ASSERT_GT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +// Define macro GTEST_DONT_DEFINE_ASSERT_XY to 1 to omit the definition of +// ASSERT_XY(), which clashes with some users' own code. + +#if !GTEST_DONT_DEFINE_ASSERT_EQ +# define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_NE +# define ASSERT_NE(val1, val2) GTEST_ASSERT_NE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_LE +# define ASSERT_LE(val1, val2) GTEST_ASSERT_LE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_LT +# define ASSERT_LT(val1, val2) GTEST_ASSERT_LT(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_GE +# define ASSERT_GE(val1, val2) GTEST_ASSERT_GE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_GT +# define ASSERT_GT(val1, val2) GTEST_ASSERT_GT(val1, val2) +#endif + +// C-string Comparisons. All tests treat NULL and any non-NULL string +// as different. Two NULLs are equal. +// +// * {ASSERT|EXPECT}_STREQ(s1, s2): Tests that s1 == s2 +// * {ASSERT|EXPECT}_STRNE(s1, s2): Tests that s1 != s2 +// * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case +// * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case +// +// For wide or narrow string objects, you can use the +// {ASSERT|EXPECT}_??() macros. +// +// Don't depend on the order in which the arguments are evaluated, +// which is undefined. +// +// These macros evaluate their arguments exactly once. + +#define EXPECT_STREQ(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2) +#define EXPECT_STRNE(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define EXPECT_STRCASEEQ(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2) +#define EXPECT_STRCASENE(s1, s2)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +#define ASSERT_STREQ(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2) +#define ASSERT_STRNE(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define ASSERT_STRCASEEQ(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2) +#define ASSERT_STRCASENE(s1, s2)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +// Macros for comparing floating-point numbers. +// +// * {ASSERT|EXPECT}_FLOAT_EQ(val1, val2): +// Tests that two float values are almost equal. +// * {ASSERT|EXPECT}_DOUBLE_EQ(val1, val2): +// Tests that two double values are almost equal. +// * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error): +// Tests that v1 and v2 are within the given distance to each other. +// +// Google Test uses ULP-based comparison to automatically pick a default +// error bound that is appropriate for the operands. See the +// FloatingPoint template class in gtest-internal.h if you are +// interested in the implementation details. + +#define EXPECT_FLOAT_EQ(val1, val2)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<float>, \ + val1, val2) + +#define EXPECT_DOUBLE_EQ(val1, val2)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<double>, \ + val1, val2) + +#define ASSERT_FLOAT_EQ(val1, val2)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<float>, \ + val1, val2) + +#define ASSERT_DOUBLE_EQ(val1, val2)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<double>, \ + val1, val2) + +#define EXPECT_NEAR(val1, val2, abs_error)\ + EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +#define ASSERT_NEAR(val1, val2, abs_error)\ + ASSERT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +// These predicate format functions work on floating-point values, and +// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g. +// +// EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0); + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +GTEST_API_ AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2); +GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2); + + +#if GTEST_OS_WINDOWS + +// Macros that test for HRESULT failure and success, these are only useful +// on Windows, and rely on Windows SDK macros and APIs to compile. +// +// * {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}(expr) +// +// When expr unexpectedly fails or succeeds, Google Test prints the +// expected result and the actual result with both a human-readable +// string representation of the error, if available, as well as the +// hex result code. +# define EXPECT_HRESULT_SUCCEEDED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +# define ASSERT_HRESULT_SUCCEEDED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +# define EXPECT_HRESULT_FAILED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +# define ASSERT_HRESULT_FAILED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +#endif // GTEST_OS_WINDOWS + +// Macros that execute statement and check that it doesn't generate new fatal +// failures in the current thread. +// +// * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement); +// +// Examples: +// +// EXPECT_NO_FATAL_FAILURE(Process()); +// ASSERT_NO_FATAL_FAILURE(Process()) << "Process() failed"; +// +#define ASSERT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_) +#define EXPECT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_NONFATAL_FAILURE_) + +// Causes a trace (including the given source file path and line number, +// and the given message) to be included in every test failure message generated +// by code in the scope of the lifetime of an instance of this class. The effect +// is undone with the destruction of the instance. +// +// The message argument can be anything streamable to std::ostream. +// +// Example: +// testing::ScopedTrace trace("file.cc", 123, "message"); +// +class GTEST_API_ ScopedTrace { + public: + // The c'tor pushes the given source file location and message onto + // a trace stack maintained by Google Test. + + // Template version. Uses Message() to convert the values into strings. + // Slow, but flexible. + template <typename T> + ScopedTrace(const char* file, int line, const T& message) { + PushTrace(file, line, (Message() << message).GetString()); + } + + // Optimize for some known types. + ScopedTrace(const char* file, int line, const char* message) { + PushTrace(file, line, message ? message : "(null)"); + } + + ScopedTrace(const char* file, int line, const std::string& message) { + PushTrace(file, line, message); + } + + // The d'tor pops the info pushed by the c'tor. + // + // Note that the d'tor is not virtual in order to be efficient. + // Don't inherit from ScopedTrace! + ~ScopedTrace(); + + private: + void PushTrace(const char* file, int line, std::string message); + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedTrace); +} GTEST_ATTRIBUTE_UNUSED_; // A ScopedTrace object does its job in its + // c'tor and d'tor. Therefore it doesn't + // need to be used otherwise. + +// Causes a trace (including the source file path, the current line +// number, and the given message) to be included in every test failure +// message generated by code in the current scope. The effect is +// undone when the control leaves the current scope. +// +// The message argument can be anything streamable to std::ostream. +// +// In the implementation, we include the current line number as part +// of the dummy variable name, thus allowing multiple SCOPED_TRACE()s +// to appear in the same block - as long as they are on different +// lines. +// +// Assuming that each thread maintains its own stack of traces. +// Therefore, a SCOPED_TRACE() would (correctly) only affect the +// assertions in its own thread. +#define SCOPED_TRACE(message) \ + ::testing::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)(\ + __FILE__, __LINE__, (message)) + +// Compile-time assertion for type equality. +// StaticAssertTypeEq<type1, type2>() compiles if and only if type1 and type2 +// are the same type. The value it returns is not interesting. +// +// Instead of making StaticAssertTypeEq a class template, we make it a +// function template that invokes a helper class template. This +// prevents a user from misusing StaticAssertTypeEq<T1, T2> by +// defining objects of that type. +// +// CAVEAT: +// +// When used inside a method of a class template, +// StaticAssertTypeEq<T1, T2>() is effective ONLY IF the method is +// instantiated. For example, given: +// +// template <typename T> class Foo { +// public: +// void Bar() { testing::StaticAssertTypeEq<int, T>(); } +// }; +// +// the code: +// +// void Test1() { Foo<bool> foo; } +// +// will NOT generate a compiler error, as Foo<bool>::Bar() is never +// actually instantiated. Instead, you need: +// +// void Test2() { Foo<bool> foo; foo.Bar(); } +// +// to cause a compiler error. +template <typename T1, typename T2> +constexpr bool StaticAssertTypeEq() noexcept { + static_assert(std::is_same<T1, T2>::value, "T1 and T2 are not the same type"); + return true; +} + +// Defines a test. +// +// The first parameter is the name of the test suite, and the second +// parameter is the name of the test within the test suite. +// +// The convention is to end the test suite name with "Test". For +// example, a test suite for the Foo class can be named FooTest. +// +// Test code should appear between braces after an invocation of +// this macro. Example: +// +// TEST(FooTest, InitializesCorrectly) { +// Foo foo; +// EXPECT_TRUE(foo.StatusIsOK()); +// } + +// Note that we call GetTestTypeId() instead of GetTypeId< +// ::testing::Test>() here to get the type ID of testing::Test. This +// is to work around a suspected linker bug when using Google Test as +// a framework on Mac OS X. The bug causes GetTypeId< +// ::testing::Test>() to return different values depending on whether +// the call is from the Google Test framework itself or from user test +// code. GetTestTypeId() is guaranteed to always return the same +// value, as it always calls GetTypeId<>() from the Google Test +// framework. +#define GTEST_TEST(test_suite_name, test_name) \ + GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \ + ::testing::internal::GetTestTypeId()) + +// Define this macro to 1 to omit the definition of TEST(), which +// is a generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_TEST +#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name) +#endif + +// Defines a test that uses a test fixture. +// +// The first parameter is the name of the test fixture class, which +// also doubles as the test suite name. The second parameter is the +// name of the test within the test suite. +// +// A test fixture class must be declared earlier. The user should put +// the test code between braces after using this macro. Example: +// +// class FooTest : public testing::Test { +// protected: +// void SetUp() override { b_.AddElement(3); } +// +// Foo a_; +// Foo b_; +// }; +// +// TEST_F(FooTest, InitializesCorrectly) { +// EXPECT_TRUE(a_.StatusIsOK()); +// } +// +// TEST_F(FooTest, ReturnsElementCountCorrectly) { +// EXPECT_EQ(a_.size(), 0); +// EXPECT_EQ(b_.size(), 1); +// } +// +// GOOGLETEST_CM0011 DO NOT DELETE +#if !GTEST_DONT_DEFINE_TEST +#define TEST_F(test_fixture, test_name)\ + GTEST_TEST_(test_fixture, test_name, test_fixture, \ + ::testing::internal::GetTypeId<test_fixture>()) +#endif // !GTEST_DONT_DEFINE_TEST + +// Returns a path to temporary directory. +// Tries to determine an appropriate directory for the platform. +GTEST_API_ std::string TempDir(); + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +// Dynamically registers a test with the framework. +// +// This is an advanced API only to be used when the `TEST` macros are +// insufficient. The macros should be preferred when possible, as they avoid +// most of the complexity of calling this function. +// +// The `factory` argument is a factory callable (move-constructible) object or +// function pointer that creates a new instance of the Test object. It +// handles ownership to the caller. The signature of the callable is +// `Fixture*()`, where `Fixture` is the test fixture class for the test. All +// tests registered with the same `test_suite_name` must return the same +// fixture type. This is checked at runtime. +// +// The framework will infer the fixture class from the factory and will call +// the `SetUpTestSuite` and `TearDownTestSuite` for it. +// +// Must be called before `RUN_ALL_TESTS()` is invoked, otherwise behavior is +// undefined. +// +// Use case example: +// +// class MyFixture : public ::testing::Test { +// public: +// // All of these optional, just like in regular macro usage. +// static void SetUpTestSuite() { ... } +// static void TearDownTestSuite() { ... } +// void SetUp() override { ... } +// void TearDown() override { ... } +// }; +// +// class MyTest : public MyFixture { +// public: +// explicit MyTest(int data) : data_(data) {} +// void TestBody() override { ... } +// +// private: +// int data_; +// }; +// +// void RegisterMyTests(const std::vector<int>& values) { +// for (int v : values) { +// ::testing::RegisterTest( +// "MyFixture", ("Test" + std::to_string(v)).c_str(), nullptr, +// std::to_string(v).c_str(), +// __FILE__, __LINE__, +// // Important to use the fixture type as the return type here. +// [=]() -> MyFixture* { return new MyTest(v); }); +// } +// } +// ... +// int main(int argc, char** argv) { +// std::vector<int> values_to_test = LoadValuesFromConfig(); +// RegisterMyTests(values_to_test); +// ... +// return RUN_ALL_TESTS(); +// } +// +template <int&... ExplicitParameterBarrier, typename Factory> +TestInfo* RegisterTest(const char* test_suite_name, const char* test_name, + const char* type_param, const char* value_param, + const char* file, int line, Factory factory) { + using TestT = typename std::remove_pointer<decltype(factory())>::type; + + class FactoryImpl : public internal::TestFactoryBase { + public: + explicit FactoryImpl(Factory f) : factory_(std::move(f)) {} + Test* CreateTest() override { return factory_(); } + + private: + Factory factory_; + }; + + return internal::MakeAndRegisterTestInfo( + test_suite_name, test_name, type_param, value_param, + internal::CodeLocation(file, line), internal::GetTypeId<TestT>(), + internal::SuiteApiResolver<TestT>::GetSetUpCaseOrSuite(file, line), + internal::SuiteApiResolver<TestT>::GetTearDownCaseOrSuite(file, line), + new FactoryImpl{std::move(factory)}); +} + +} // namespace testing + +// Use this function in main() to run all tests. It returns 0 if all +// tests are successful, or 1 otherwise. +// +// RUN_ALL_TESTS() should be invoked after the command line has been +// parsed by InitGoogleTest(). +// +// This function was formerly a macro; thus, it is in the global +// namespace and has an all-caps name. +int RUN_ALL_TESTS() GTEST_MUST_USE_RESULT_; + +inline int RUN_ALL_TESTS() { + return ::testing::UnitTest::GetInstance()->Run(); +} + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_H_ diff --git a/libs/fmt/test/header-only-test.cc b/libs/fmt/test/header-only-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..f0c3a7fa9ff3379f24055146526d594eb7aa93e9 --- /dev/null +++ b/libs/fmt/test/header-only-test.cc @@ -0,0 +1,7 @@ +// Header-only configuration test + +#include "fmt/core.h" + +#ifndef FMT_HEADER_ONLY +# error "Not in the header-only mode." +#endif diff --git a/libs/fmt/test/locale-test.cc b/libs/fmt/test/locale-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..0a7da95239a5460d3a63720bcdd16d661f821ea3 --- /dev/null +++ b/libs/fmt/test/locale-test.cc @@ -0,0 +1,166 @@ +// Formatting library for C++ - locale tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/locale.h" + +#include <complex> + +#include "gmock/gmock.h" + +using fmt::detail::max_value; + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template <typename Char> struct numpunct : std::numpunct<Char> { + protected: + Char do_decimal_point() const override { return '?'; } + std::string do_grouping() const override { return "\03"; } + Char do_thousands_sep() const override { return '~'; } +}; + +template <typename Char> struct no_grouping : std::numpunct<Char> { + protected: + Char do_decimal_point() const override { return '.'; } + std::string do_grouping() const override { return ""; } + Char do_thousands_sep() const override { return ','; } +}; + +template <typename Char> struct special_grouping : std::numpunct<Char> { + protected: + Char do_decimal_point() const override { return '.'; } + std::string do_grouping() const override { return "\03\02"; } + Char do_thousands_sep() const override { return ','; } +}; + +template <typename Char> struct small_grouping : std::numpunct<Char> { + protected: + Char do_decimal_point() const override { return '.'; } + std::string do_grouping() const override { return "\01"; } + Char do_thousands_sep() const override { return ','; } +}; + +TEST(locale_test, double_decimal_point) { + auto loc = std::locale(std::locale(), new numpunct<char>()); + EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23)); + EXPECT_EQ("1?230000", fmt::format(loc, "{:Lf}", 1.23)); +} + +TEST(locale_test, format) { + auto loc = std::locale(std::locale(), new numpunct<char>()); + EXPECT_EQ("1234567", fmt::format(std::locale(), "{:L}", 1234567)); + EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567)); + EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567)); + EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256)); + fmt::format_arg_store<fmt::format_context, int> as{1234567}; + EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as))); + auto s = std::string(); + fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567); + EXPECT_EQ("1~234~567", s); + + auto no_grouping_loc = std::locale(std::locale(), new no_grouping<char>()); + EXPECT_EQ("1234567", fmt::format(no_grouping_loc, "{:L}", 1234567)); + + auto special_grouping_loc = + std::locale(std::locale(), new special_grouping<char>()); + EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:L}", 12345678)); + EXPECT_EQ("12,345", fmt::format(special_grouping_loc, "{:L}", 12345)); + + auto small_grouping_loc = + std::locale(std::locale(), new small_grouping<char>()); + EXPECT_EQ("4,2,9,4,9,6,7,2,9,5", + fmt::format(small_grouping_loc, "{:L}", max_value<uint32_t>())); +} + +TEST(locale_test, format_detault_align) { + auto loc = std::locale({}, new special_grouping<char>()); + EXPECT_EQ(" 12,345", fmt::format(loc, "{:8L}", 12345)); +} + +TEST(locale_test, format_plus) { + auto loc = std::locale({}, new special_grouping<char>()); + EXPECT_EQ("+100", fmt::format(loc, "{:+L}", 100)); +} + +TEST(locale_test, wformat) { + auto loc = std::locale(std::locale(), new numpunct<wchar_t>()); + EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567)); + EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567)); + fmt::format_arg_store<fmt::wformat_context, int> as{1234567}; + EXPECT_EQ(L"1~234~567", fmt::vformat(loc, L"{:L}", fmt::wformat_args(as))); + EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567)); + + auto no_grouping_loc = std::locale(std::locale(), new no_grouping<wchar_t>()); + EXPECT_EQ(L"1234567", fmt::format(no_grouping_loc, L"{:L}", 1234567)); + + auto special_grouping_loc = + std::locale(std::locale(), new special_grouping<wchar_t>()); + EXPECT_EQ(L"1,23,45,678", + fmt::format(special_grouping_loc, L"{:L}", 12345678)); + + auto small_grouping_loc = + std::locale(std::locale(), new small_grouping<wchar_t>()); + EXPECT_EQ(L"4,2,9,4,9,6,7,2,9,5", + fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>())); +} + +TEST(locale_test, double_formatter) { + auto loc = std::locale(std::locale(), new special_grouping<char>()); + auto f = fmt::formatter<int>(); + auto parse_ctx = fmt::format_parse_context("L"); + f.parse(parse_ctx); + char buf[10] = {}; + fmt::basic_format_context<char*, char> format_ctx( + buf, {}, fmt::detail::locale_ref(loc)); + *f.format(12345, format_ctx) = 0; + EXPECT_STREQ("12,345", buf); +} + +FMT_BEGIN_NAMESPACE +template <class charT> struct formatter<std::complex<double>, charT> { + private: + detail::dynamic_format_specs<char> specs_; + + public: + FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse( + basic_format_parse_context<charT>& ctx) { + using handler_type = + detail::dynamic_specs_handler<basic_format_parse_context<charT>>; + detail::specs_checker<handler_type> handler(handler_type(specs_, ctx), + detail::type::string_type); + auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); + detail::parse_float_type_spec(specs_, ctx.error_handler()); + return it; + } + + template <class FormatContext> + typename FormatContext::iterator format(const std::complex<double>& c, + FormatContext& ctx) { + detail::handle_dynamic_spec<detail::precision_checker>( + specs_.precision, specs_.precision_ref, ctx); + auto specs = std::string(); + if (specs_.precision > 0) specs = fmt::format(".{}", specs_.precision); + if (specs_.type) specs += specs_.type; + auto real = fmt::format(ctx.locale().template get<std::locale>(), + "{:" + specs + "}", c.real()); + auto imag = fmt::format(ctx.locale().template get<std::locale>(), + "{:" + specs + "}", c.imag()); + auto fill_align_width = std::string(); + if (specs_.width > 0) fill_align_width = fmt::format(">{}", specs_.width); + return format_to( + ctx.out(), "{:" + fill_align_width + "}", + fmt::format(c.real() != 0 ? "({0}+{1}i)" : "{1}i", real, imag)); + } +}; +FMT_END_NAMESPACE + +TEST(locale_test, complex) { + std::string s = fmt::format("{}", std::complex<double>(1, 2)); + EXPECT_EQ(s, "(1+2i)"); + EXPECT_EQ(fmt::format("{:.2f}", std::complex<double>(1, 2)), "(1.00+2.00i)"); + EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), " (1+2i)"); +} + +#endif // FMT_STATIC_THOUSANDS_SEPARATOR diff --git a/libs/fmt/test/mock-allocator.h b/libs/fmt/test/mock-allocator.h new file mode 100644 index 0000000000000000000000000000000000000000..6a67e08f4f2804d6788a62e7b37206dedcbd7dfc --- /dev/null +++ b/libs/fmt/test/mock-allocator.h @@ -0,0 +1,64 @@ +// Formatting library for C++ - mock allocator +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_MOCK_ALLOCATOR_H_ +#define FMT_MOCK_ALLOCATOR_H_ + +#include <assert.h> // assert +#include <stddef.h> // size_t + +#include <memory> // std::allocator_traits + +#include "gmock/gmock.h" + +template <typename T> class mock_allocator { + public: + mock_allocator() {} + mock_allocator(const mock_allocator&) {} + using value_type = T; + MOCK_METHOD1_T(allocate, T*(size_t n)); + MOCK_METHOD2_T(deallocate, void(T* p, size_t n)); +}; + +template <typename Allocator> class allocator_ref { + private: + Allocator* alloc_; + + void move(allocator_ref& other) { + alloc_ = other.alloc_; + other.alloc_ = nullptr; + } + + public: + using value_type = typename Allocator::value_type; + + explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {} + + allocator_ref(const allocator_ref& other) : alloc_(other.alloc_) {} + allocator_ref(allocator_ref&& other) { move(other); } + + allocator_ref& operator=(allocator_ref&& other) { + assert(this != &other); + move(other); + return *this; + } + + allocator_ref& operator=(const allocator_ref& other) { + alloc_ = other.alloc_; + return *this; + } + + public: + Allocator* get() const { return alloc_; } + + value_type* allocate(size_t n) { + return std::allocator_traits<Allocator>::allocate(*alloc_, n); + } + void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); } +}; + +#endif // FMT_MOCK_ALLOCATOR_H_ diff --git a/libs/fmt/test/os-test.cc b/libs/fmt/test/os-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..91abac63e8496a24000d7332e6fe01164f65c8fa --- /dev/null +++ b/libs/fmt/test/os-test.cc @@ -0,0 +1,543 @@ +// Formatting library for C++ - tests of the OS-specific functionality +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/os.h" + +#include <cstdlib> // std::exit +#include <cstring> +#include <memory> + +#include "gtest-extra.h" +#include "util.h" + +#ifdef fileno +# undef fileno +#endif + +using fmt::buffered_file; +using fmt::error_code; + +#ifdef _WIN32 + +# include <windows.h> + +TEST(util_test, utf16_to_utf8) { + auto s = std::string("ёжик"); + fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A"); + EXPECT_EQ(s, u.str()); + EXPECT_EQ(s.size(), u.size()); +} + +TEST(util_test, utf16_to_utf8_empty_string) { + std::string s = ""; + fmt::detail::utf16_to_utf8 u(L""); + EXPECT_EQ(s, u.str()); + EXPECT_EQ(s.size(), u.size()); +} + +template <typename Converter, typename Char> +void check_utf_conversion_error( + const char* message, + fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) { + fmt::memory_buffer out; + fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message); + auto error = fmt::system_error(0, ""); + try { + (Converter)(str); + } catch (const fmt::system_error& e) { + error = e; + } + EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code()); + EXPECT_EQ(fmt::to_string(out), error.what()); +} + +TEST(util_test, utf16_to_utf8_error) { + check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>( + "cannot convert string from UTF-16 to UTF-8"); +} + +TEST(util_test, utf16_to_utf8_convert) { + fmt::detail::utf16_to_utf8 u; + EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::wstring_view(0, 1))); + EXPECT_EQ(ERROR_INVALID_PARAMETER, + u.convert(fmt::wstring_view(L"foo", INT_MAX + 1u))); +} + +TEST(os_test, format_windows_error) { + LPWSTR message = 0; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, ERROR_FILE_EXISTS, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast<LPWSTR>(&message), 0, 0); + fmt::detail::utf16_to_utf8 utf8_message(message); + LocalFree(message); + fmt::memory_buffer actual_message; + fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test"); + EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), + fmt::to_string(actual_message)); + actual_message.resize(0); + auto max_size = fmt::detail::max_value<size_t>() / 2; + fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, + fmt::string_view(nullptr, max_size)); + EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS), + fmt::to_string(actual_message)); +} + +TEST(os_test, format_long_windows_error) { + LPWSTR message = 0; + // this error code is not available on all Windows platforms and + // Windows SDKs, so do not fail the test if the error string cannot + // be retrieved. + const int provisioning_not_allowed = + 0x80284013L /*TBS_E_PROVISIONING_NOT_ALLOWED*/; + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, static_cast<DWORD>(provisioning_not_allowed), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast<LPWSTR>(&message), 0, 0) == 0) { + return; + } + fmt::detail::utf16_to_utf8 utf8_message(message); + LocalFree(message); + fmt::memory_buffer actual_message; + fmt::detail::format_windows_error(actual_message, provisioning_not_allowed, + "test"); + EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), + fmt::to_string(actual_message)); +} + +TEST(os_test, windows_error) { + auto error = fmt::system_error(0, ""); + try { + throw fmt::windows_error(ERROR_FILE_EXISTS, "test {}", "error"); + } catch (const fmt::system_error& e) { + error = e; + } + fmt::memory_buffer message; + fmt::detail::format_windows_error(message, ERROR_FILE_EXISTS, "test error"); + EXPECT_EQ(to_string(message), error.what()); + EXPECT_EQ(ERROR_FILE_EXISTS, error.error_code()); +} + +TEST(os_test, report_windows_error) { + fmt::memory_buffer out; + fmt::detail::format_windows_error(out, ERROR_FILE_EXISTS, "test error"); + out.push_back('\n'); + EXPECT_WRITE(stderr, + fmt::report_windows_error(ERROR_FILE_EXISTS, "test error"), + fmt::to_string(out)); +} + +#endif // _WIN32 + +#if FMT_USE_FCNTL + +using fmt::file; + +bool isclosed(int fd) { + char buffer; + auto result = std::streamsize(); + SUPPRESS_ASSERT(result = FMT_POSIX(read(fd, &buffer, 1))); + return result == -1 && errno == EBADF; +} + +// Opens a file for reading. +file open_file() { + file read_end, write_end; + file::pipe(read_end, write_end); + write_end.write(file_content, std::strlen(file_content)); + write_end.close(); + return read_end; +} + +// Attempts to write a string to a file. +void write(file& f, fmt::string_view s) { + size_t num_chars_left = s.size(); + const char* ptr = s.data(); + do { + size_t count = f.write(ptr, num_chars_left); + ptr += count; + // We can't write more than size_t bytes since num_chars_left + // has type size_t. + num_chars_left -= count; + } while (num_chars_left != 0); +} + +TEST(buffered_file_test, default_ctor) { + auto f = buffered_file(); + EXPECT_TRUE(f.get() == nullptr); +} + +TEST(buffered_file_test, move_ctor) { + buffered_file bf = open_buffered_file(); + FILE* fp = bf.get(); + EXPECT_TRUE(fp != nullptr); + buffered_file bf2(std::move(bf)); + EXPECT_EQ(fp, bf2.get()); + EXPECT_TRUE(bf.get() == nullptr); +} + +TEST(buffered_file_test, move_assignment) { + buffered_file bf = open_buffered_file(); + FILE* fp = bf.get(); + EXPECT_TRUE(fp != nullptr); + buffered_file bf2; + bf2 = std::move(bf); + EXPECT_EQ(fp, bf2.get()); + EXPECT_TRUE(bf.get() == nullptr); +} + +TEST(buffered_file_test, move_assignment_closes_file) { + buffered_file bf = open_buffered_file(); + buffered_file bf2 = open_buffered_file(); + int old_fd = bf2.fileno(); + bf2 = std::move(bf); + EXPECT_TRUE(isclosed(old_fd)); +} + +TEST(buffered_file_test, move_from_temporary_in_ctor) { + FILE* fp = nullptr; + buffered_file f = open_buffered_file(&fp); + EXPECT_EQ(fp, f.get()); +} + +TEST(buffered_file_test, move_from_temporary_in_assignment) { + FILE* fp = nullptr; + auto f = buffered_file(); + f = open_buffered_file(&fp); + EXPECT_EQ(fp, f.get()); +} + +TEST(buffered_file_test, move_from_temporary_in_assignment_closes_file) { + buffered_file f = open_buffered_file(); + int old_fd = f.fileno(); + f = open_buffered_file(); + EXPECT_TRUE(isclosed(old_fd)); +} + +TEST(buffered_file_test, close_file_in_dtor) { + int fd = 0; + { + buffered_file f = open_buffered_file(); + fd = f.fileno(); + } + EXPECT_TRUE(isclosed(fd)); +} + +TEST(buffered_file_test, close_error_in_dtor) { + auto f = + std::unique_ptr<buffered_file>(new buffered_file(open_buffered_file())); + EXPECT_WRITE( + stderr, + { + // The close function must be called inside EXPECT_WRITE, + // otherwise the system may recycle closed file descriptor when + // redirecting the output in EXPECT_STDERR and the second close + // will break output redirection. + FMT_POSIX(close(f->fileno())); + SUPPRESS_ASSERT(f.reset(nullptr)); + }, + format_system_error(EBADF, "cannot close file") + "\n"); +} + +TEST(buffered_file_test, close) { + buffered_file f = open_buffered_file(); + int fd = f.fileno(); + f.close(); + EXPECT_TRUE(f.get() == nullptr); + EXPECT_TRUE(isclosed(fd)); +} + +TEST(buffered_file_test, close_error) { + buffered_file f = open_buffered_file(); + FMT_POSIX(close(f.fileno())); + EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); + EXPECT_TRUE(f.get() == nullptr); +} + +TEST(buffered_file_test, fileno) { + auto f = open_buffered_file(); + EXPECT_TRUE(f.fileno() != -1); + file copy = file::dup(f.fileno()); + EXPECT_READ(copy, file_content); +} + +TEST(ostream_test, move) { + fmt::ostream out = fmt::output_file("test-file"); + fmt::ostream moved(std::move(out)); + moved.print("hello"); +} + +TEST(ostream_test, move_while_holding_data) { + { + fmt::ostream out = fmt::output_file("test-file"); + out.print("Hello, "); + fmt::ostream moved(std::move(out)); + moved.print("world!\n"); + } + { + file in("test-file", file::RDONLY); + EXPECT_READ(in, "Hello, world!\n"); + } +} + +TEST(ostream_test, print) { + fmt::ostream out = fmt::output_file("test-file"); + out.print("The answer is {}.\n", + fmt::join(std::initializer_list<int>{42}, ", ")); + out.close(); + file in("test-file", file::RDONLY); + EXPECT_READ(in, "The answer is 42.\n"); +} + +TEST(ostream_test, buffer_boundary) { + auto str = std::string(4096, 'x'); + fmt::ostream out = fmt::output_file("test-file"); + out.print("{}", str); + out.print("{}", str); + out.close(); + file in("test-file", file::RDONLY); + EXPECT_READ(in, str + str); +} + +TEST(ostream_test, buffer_size) { + fmt::ostream out = fmt::output_file("test-file", fmt::buffer_size = 1); + out.print("{}", "foo"); + out.close(); + file in("test-file", file::RDONLY); + EXPECT_READ(in, "foo"); +} + +TEST(ostream_test, truncate) { + { + fmt::ostream out = fmt::output_file("test-file"); + out.print("0123456789"); + } + { + fmt::ostream out = fmt::output_file("test-file"); + out.print("foo"); + } + file in("test-file", file::RDONLY); + EXPECT_EQ("foo", read(in, 4)); +} + +TEST(file_test, default_ctor) { + file f; + EXPECT_EQ(-1, f.descriptor()); +} + +TEST(file_test, open_buffered_file_in_ctor) { + FILE* fp = safe_fopen("test-file", "w"); + std::fputs(file_content, fp); + std::fclose(fp); + file f("test-file", file::RDONLY); + // Check if the file is open by reading one character from it. + char buffer; + bool isopen = FMT_POSIX(read(f.descriptor(), &buffer, 1)) == 1; + ASSERT_TRUE(isopen); +} + +TEST(file_test, open_buffered_file_error) { + EXPECT_SYSTEM_ERROR(file("nonexistent", file::RDONLY), ENOENT, + "cannot open file nonexistent"); +} + +TEST(file_test, move_ctor) { + file f = open_file(); + int fd = f.descriptor(); + EXPECT_NE(-1, fd); + file f2(std::move(f)); + EXPECT_EQ(fd, f2.descriptor()); + EXPECT_EQ(-1, f.descriptor()); +} + +TEST(file_test, move_assignment) { + file f = open_file(); + int fd = f.descriptor(); + EXPECT_NE(-1, fd); + file f2; + f2 = std::move(f); + EXPECT_EQ(fd, f2.descriptor()); + EXPECT_EQ(-1, f.descriptor()); +} + +TEST(file_test, move_assignment_closes_file) { + file f = open_file(); + file f2 = open_file(); + int old_fd = f2.descriptor(); + f2 = std::move(f); + EXPECT_TRUE(isclosed(old_fd)); +} + +file open_buffered_file(int& fd) { + file f = open_file(); + fd = f.descriptor(); + return f; +} + +TEST(file_test, move_from_temporary_in_ctor) { + int fd = 0xdead; + file f(open_buffered_file(fd)); + EXPECT_EQ(fd, f.descriptor()); +} + +TEST(file_test, move_from_temporary_in_assignment) { + int fd = 0xdead; + file f; + f = open_buffered_file(fd); + EXPECT_EQ(fd, f.descriptor()); +} + +TEST(file_test, move_from_temporary_in_assignment_closes_file) { + int fd = 0xdead; + file f = open_file(); + int old_fd = f.descriptor(); + f = open_buffered_file(fd); + EXPECT_TRUE(isclosed(old_fd)); +} + +TEST(file_test, close_file_in_dtor) { + int fd = 0; + { + file f = open_file(); + fd = f.descriptor(); + } + EXPECT_TRUE(isclosed(fd)); +} + +TEST(file_test, close_error_in_dtor) { + std::unique_ptr<file> f(new file(open_file())); + EXPECT_WRITE( + stderr, + { + // The close function must be called inside EXPECT_WRITE, + // otherwise the system may recycle closed file descriptor when + // redirecting the output in EXPECT_STDERR and the second close + // will break output redirection. + FMT_POSIX(close(f->descriptor())); + SUPPRESS_ASSERT(f.reset(nullptr)); + }, + format_system_error(EBADF, "cannot close file") + "\n"); +} + +TEST(file_test, close) { + file f = open_file(); + int fd = f.descriptor(); + f.close(); + EXPECT_EQ(-1, f.descriptor()); + EXPECT_TRUE(isclosed(fd)); +} + +TEST(file_test, close_error) { + file f = open_file(); + FMT_POSIX(close(f.descriptor())); + EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); + EXPECT_EQ(-1, f.descriptor()); +} + +TEST(file_test, read) { + file f = open_file(); + EXPECT_READ(f, file_content); +} + +TEST(file_test, read_error) { + file f("test-file", file::WRONLY); + char buf; + // We intentionally read from a file opened in the write-only mode to + // cause error. + EXPECT_SYSTEM_ERROR(f.read(&buf, 1), EBADF, "cannot read from file"); +} + +TEST(file_test, write) { + file read_end, write_end; + file::pipe(read_end, write_end); + write(write_end, "test"); + write_end.close(); + EXPECT_READ(read_end, "test"); +} + +TEST(file_test, write_error) { + file f("test-file", file::RDONLY); + // We intentionally write to a file opened in the read-only mode to + // cause error. + EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file"); +} + +TEST(file_test, dup) { + file f = open_file(); + file copy = file::dup(f.descriptor()); + EXPECT_NE(f.descriptor(), copy.descriptor()); + EXPECT_EQ(file_content, read(copy, std::strlen(file_content))); +} + +# ifndef __COVERITY__ +TEST(file_test, dup_error) { + int value = -1; + EXPECT_SYSTEM_ERROR_NOASSERT(file::dup(value), EBADF, + "cannot duplicate file descriptor -1"); +} +# endif + +TEST(file_test, dup2) { + file f = open_file(); + file copy = open_file(); + f.dup2(copy.descriptor()); + EXPECT_NE(f.descriptor(), copy.descriptor()); + EXPECT_READ(copy, file_content); +} + +TEST(file_test, dup2_error) { + file f = open_file(); + EXPECT_SYSTEM_ERROR_NOASSERT( + f.dup2(-1), EBADF, + fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor())); +} + +TEST(file_test, dup2_noexcept) { + file f = open_file(); + file copy = open_file(); + error_code ec; + f.dup2(copy.descriptor(), ec); + EXPECT_EQ(ec.get(), 0); + EXPECT_NE(f.descriptor(), copy.descriptor()); + EXPECT_READ(copy, file_content); +} + +TEST(file_test, dup2_noexcept_error) { + file f = open_file(); + error_code ec; + SUPPRESS_ASSERT(f.dup2(-1, ec)); + EXPECT_EQ(EBADF, ec.get()); +} + +TEST(file_test, pipe) { + file read_end, write_end; + file::pipe(read_end, write_end); + EXPECT_NE(-1, read_end.descriptor()); + EXPECT_NE(-1, write_end.descriptor()); + write(write_end, "test"); + EXPECT_READ(read_end, "test"); +} + +TEST(file_test, fdopen) { + file read_end, write_end; + file::pipe(read_end, write_end); + int read_fd = read_end.descriptor(); + EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get()))); +} + +# ifdef FMT_LOCALE +TEST(locale_test, strtod) { + fmt::locale loc; + const char *start = "4.2", *ptr = start; + EXPECT_EQ(4.2, loc.strtod(ptr)); + EXPECT_EQ(start + 3, ptr); +} +# endif +#endif // FMT_USE_FCNTL diff --git a/libs/fmt/test/ostream-test.cc b/libs/fmt/test/ostream-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..2bc2b1cb1235e5055130f9ada0a95968970aeb8a --- /dev/null +++ b/libs/fmt/test/ostream-test.cc @@ -0,0 +1,291 @@ +// Formatting library for C++ - std::ostream support tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/format.h" + +struct test {}; + +// Test that there is no issues with specializations when fmt/ostream.h is +// included after fmt/format.h. +namespace fmt { +template <> struct formatter<test> : formatter<int> { + auto format(const test&, format_context& ctx) -> decltype(ctx.out()) { + return formatter<int>::format(42, ctx); + } +}; +} // namespace fmt + +#include <sstream> + +#include "fmt/ostream.h" +#include "fmt/ranges.h" +#include "gmock/gmock.h" +#include "gtest-extra.h" +#include "util.h" + +std::ostream& operator<<(std::ostream& os, const date& d) { + os << d.year() << '-' << d.month() << '-' << d.day(); + return os; +} + +std::wostream& operator<<(std::wostream& os, const date& d) { + os << d.year() << L'-' << d.month() << L'-' << d.day(); + return os; +} + +// Make sure that overloaded comma operators do no harm to is_streamable. +struct type_with_comma_op {}; +template <typename T> void operator,(type_with_comma_op, const T&); +template <typename T> type_with_comma_op operator<<(T&, const date&); + +enum streamable_enum {}; +std::ostream& operator<<(std::ostream& os, streamable_enum) { + return os << "streamable_enum"; +} + +std::wostream& operator<<(std::wostream& os, streamable_enum) { + return os << L"streamable_enum"; +} + +enum unstreamable_enum {}; + +TEST(ostream_test, enum) { + EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum())); + EXPECT_EQ("0", fmt::format("{}", unstreamable_enum())); + EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum())); + EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum())); +} + +TEST(ostream_test, format) { + EXPECT_EQ("a string", fmt::format("{0}", test_string("a string"))); + EXPECT_EQ("The date is 2012-12-9", + fmt::format("The date is {0}", date(2012, 12, 9))); + EXPECT_EQ(L"The date is 2012-12-9", + fmt::format(L"The date is {0}", date(2012, 12, 9))); +} + +TEST(ostream_test, format_specs) { + using fmt::format_error; + EXPECT_EQ("def ", fmt::format("{0:<5}", test_string("def"))); + EXPECT_EQ(" def", fmt::format("{0:>5}", test_string("def"))); + EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def"))); + EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def"))); + EXPECT_THROW_MSG(fmt::format(+"{0:+}", test_string()), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{0:-}", test_string()), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{0: }", test_string()), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{0:#}", test_string()), format_error, + "format specifier requires numeric argument"); + EXPECT_THROW_MSG(fmt::format(+"{0:05}", test_string()), format_error, + "format specifier requires numeric argument"); + EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test"))); + EXPECT_EQ("test ", fmt::format("{0:{1}}", test_string("test"), 13)); + EXPECT_EQ("te", fmt::format("{0:.2}", test_string("test"))); + EXPECT_EQ("te", fmt::format("{0:.{1}}", test_string("test"), 2)); +} + +struct empty_test {}; +std::ostream& operator<<(std::ostream& os, empty_test) { return os << ""; } + +TEST(ostream_test, empty_custom_output) { + EXPECT_EQ("", fmt::format("{}", empty_test())); +} + +TEST(ostream_test, print) { + std::ostringstream os; + fmt::print(os, "Don't {}!", "panic"); + EXPECT_EQ("Don't panic!", os.str()); + std::wostringstream wos; + fmt::print(wos, L"Don't {}!", L"panic"); + EXPECT_EQ(L"Don't panic!", wos.str()); +} + +TEST(ostream_test, write_to_ostream) { + std::ostringstream os; + fmt::memory_buffer buffer; + const char* foo = "foo"; + buffer.append(foo, foo + std::strlen(foo)); + fmt::detail::write_buffer(os, buffer); + EXPECT_EQ("foo", os.str()); +} + +TEST(ostream_test, write_to_ostream_max_size) { + auto max_size = fmt::detail::max_value<size_t>(); + auto max_streamsize = fmt::detail::max_value<std::streamsize>(); + if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return; + + struct test_buffer final : fmt::detail::buffer<char> { + explicit test_buffer(size_t size) + : fmt::detail::buffer<char>(nullptr, size, size) {} + void grow(size_t) {} + } buffer(max_size); + + struct mock_streambuf : std::streambuf { + MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n)); + std::streamsize xsputn(const char* s, std::streamsize n) { + const void* v = s; + return xsputn(v, n); + } + } streambuf; + + struct test_ostream : std::ostream { + explicit test_ostream(mock_streambuf& output_buffer) + : std::ostream(&output_buffer) {} + } os(streambuf); + + testing::InSequence sequence; + const char* data = nullptr; + using ustreamsize = std::make_unsigned<std::streamsize>::type; + ustreamsize size = max_size; + do { + auto n = std::min(size, fmt::detail::to_unsigned(max_streamsize)); + EXPECT_CALL(streambuf, xsputn(data, static_cast<std::streamsize>(n))) + .WillOnce(testing::Return(max_streamsize)); + data += n; + size -= n; + } while (size != 0); + fmt::detail::write_buffer(os, buffer); +} + +TEST(ostream_test, join) { + int v[3] = {1, 2, 3}; + EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, v + 3, ", "))); +} + +TEST(ostream_test, join_fallback_formatter) { + auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")}; + EXPECT_EQ("foo, bar", fmt::format("{}", fmt::join(strs, ", "))); +} + +#if FMT_USE_CONSTEXPR +TEST(ostream_test, constexpr_string) { + EXPECT_EQ("42", format(FMT_STRING("{}"), std::string("42"))); + EXPECT_EQ("a string", format(FMT_STRING("{0}"), test_string("a string"))); +} +#endif + +namespace fmt_test { +struct abc {}; + +template <typename Output> Output& operator<<(Output& out, abc) { + return out << "abc"; +} +} // namespace fmt_test + +template <typename T> struct test_template {}; + +template <typename T> +std::ostream& operator<<(std::ostream& os, test_template<T>) { + return os << 1; +} + +namespace fmt { +template <typename T> struct formatter<test_template<T>> : formatter<int> { + auto format(test_template<T>, format_context& ctx) -> decltype(ctx.out()) { + return formatter<int>::format(2, ctx); + } +}; +} // namespace fmt + +TEST(ostream_test, template) { + EXPECT_EQ("2", fmt::format("{}", test_template<int>())); +} + +TEST(ostream_test, format_to_n) { + char buffer[4]; + buffer[3] = 'x'; + auto result = fmt::format_to_n(buffer, 3, "{}", fmt_test::abc()); + EXPECT_EQ(3u, result.size); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ("abcx", fmt::string_view(buffer, 4)); + result = fmt::format_to_n(buffer, 3, "x{}y", fmt_test::abc()); + EXPECT_EQ(5u, result.size); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ("xabx", fmt::string_view(buffer, 4)); +} + +template <typename T> struct convertible { + T value; + explicit convertible(const T& val) : value(val) {} + operator T() const { return value; } +}; + +TEST(ostream_test, disable_builtin_ostream_operators) { + EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42))); + EXPECT_EQ(L"42", fmt::format(L"{:d}", convertible<unsigned short>(42))); + EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo"))); +} + +struct explicitly_convertible_to_string_like { + template <typename String, + typename = typename std::enable_if<std::is_constructible< + String, const char*, size_t>::value>::type> + explicit operator String() const { + return String("foo", 3u); + } +}; + +std::ostream& operator<<(std::ostream& os, + explicitly_convertible_to_string_like) { + return os << "bar"; +} + +TEST(ostream_test, format_explicitly_convertible_to_string_like) { + EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like())); +} + +#ifdef FMT_USE_STRING_VIEW +struct explicitly_convertible_to_std_string_view { + explicit operator fmt::detail::std_string_view<char>() const { + return {"foo", 3u}; + } +}; + +std::ostream& operator<<(std::ostream& os, + explicitly_convertible_to_std_string_view) { + return os << "bar"; +} + +TEST(ostream_test, format_explicitly_convertible_to_std_string_view) { + EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like())); +} +#endif // FMT_USE_STRING_VIEW + +struct streamable_and_convertible_to_bool { + operator bool() const { return true; } +}; + +std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) { + return os << "foo"; +} + +TEST(ostream_test, format_convertible_to_bool) { + EXPECT_EQ("foo", fmt::format("{}", streamable_and_convertible_to_bool())); +} + +struct copyfmt_test {}; + +std::ostream& operator<<(std::ostream& os, copyfmt_test) { + std::ios ios(nullptr); + ios.copyfmt(os); + return os << "foo"; +} + +TEST(ostream_test, copyfmt) { + EXPECT_EQ("foo", fmt::format("{}", copyfmt_test())); +} + +TEST(ostream_test, to_string) { + EXPECT_EQ("abc", fmt::to_string(fmt_test::abc())); +} + +TEST(ostream_test, range) { + auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")}; + EXPECT_EQ("[foo, bar]", fmt::format("{}", strs)); +} diff --git a/libs/fmt/test/posix-mock-test.cc b/libs/fmt/test/posix-mock-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..dd3aaac62a7fbb9fa051a3b96b325ff7e374c359 --- /dev/null +++ b/libs/fmt/test/posix-mock-test.cc @@ -0,0 +1,563 @@ +// Tests of the C++ interface to POSIX functions that require mocks +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +// Disable bogus MSVC warnings. +#ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "posix-mock.h" + +#include <errno.h> +#include <fcntl.h> + +#include <climits> +#include <memory> + +#include "../src/os.cc" + +#ifdef _WIN32 +# include <io.h> +# undef max +#endif + +#include "gmock/gmock.h" +#include "gtest-extra.h" +#include "util.h" + +using fmt::buffered_file; +using fmt::error_code; + +using testing::_; +using testing::Return; +using testing::StrEq; + +template <typename Mock> struct scoped_mock : testing::StrictMock<Mock> { + scoped_mock() { Mock::instance = this; } + ~scoped_mock() { Mock::instance = nullptr; } +}; + +namespace { +int open_count; +int close_count; +int dup_count; +int dup2_count; +int fdopen_count; +int read_count; +int write_count; +int pipe_count; +int fopen_count; +int fclose_count; +int fileno_count; +size_t read_nbyte; +size_t write_nbyte; +bool sysconf_error; + +enum { none, max_size, error } fstat_sim; +} // namespace + +#define EMULATE_EINTR(func, error_result) \ + if (func##_count != 0) { \ + if (func##_count++ != 3) { \ + errno = EINTR; \ + return error_result; \ + } \ + } + +#ifndef _MSC_VER +int test::open(const char* path, int oflag, int mode) { + EMULATE_EINTR(open, -1); + return ::open(path, oflag, mode); +} +#else +errno_t test::sopen_s(int* pfh, const char* filename, int oflag, int shflag, + int pmode) { + EMULATE_EINTR(open, EINTR); + return _sopen_s(pfh, filename, oflag, shflag, pmode); +} +#endif + +#ifndef _WIN32 + +long test::sysconf(int name) { + long result = ::sysconf(name); + if (!sysconf_error) return result; + // Simulate an error. + errno = EINVAL; + return -1; +} + +static off_t max_file_size() { return std::numeric_limits<off_t>::max(); } + +int test::fstat(int fd, struct stat* buf) { + int result = ::fstat(fd, buf); + if (fstat_sim == max_size) buf->st_size = max_file_size(); + return result; +} + +#else + +static LONGLONG max_file_size() { return std::numeric_limits<LONGLONG>::max(); } + +DWORD test::GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) { + if (fstat_sim == error) { + SetLastError(ERROR_ACCESS_DENIED); + return INVALID_FILE_SIZE; + } + if (fstat_sim == max_size) { + DWORD max = std::numeric_limits<DWORD>::max(); + *lpFileSizeHigh = max >> 1; + return max; + } + return ::GetFileSize(hFile, lpFileSizeHigh); +} + +#endif + +int test::close(int fildes) { + // Close the file first because close shouldn't be retried. + int result = ::FMT_POSIX(close(fildes)); + EMULATE_EINTR(close, -1); + return result; +} + +int test::dup(int fildes) { + EMULATE_EINTR(dup, -1); + return ::FMT_POSIX(dup(fildes)); +} + +int test::dup2(int fildes, int fildes2) { + EMULATE_EINTR(dup2, -1); + return ::FMT_POSIX(dup2(fildes, fildes2)); +} + +FILE* test::fdopen(int fildes, const char* mode) { + EMULATE_EINTR(fdopen, nullptr); + return ::FMT_POSIX(fdopen(fildes, mode)); +} + +test::ssize_t test::read(int fildes, void* buf, test::size_t nbyte) { + read_nbyte = nbyte; + EMULATE_EINTR(read, -1); + return ::FMT_POSIX(read(fildes, buf, nbyte)); +} + +test::ssize_t test::write(int fildes, const void* buf, test::size_t nbyte) { + write_nbyte = nbyte; + EMULATE_EINTR(write, -1); + return ::FMT_POSIX(write(fildes, buf, nbyte)); +} + +#ifndef _WIN32 +int test::pipe(int fildes[2]) { + EMULATE_EINTR(pipe, -1); + return ::pipe(fildes); +} +#else +int test::pipe(int* pfds, unsigned psize, int textmode) { + EMULATE_EINTR(pipe, -1); + return _pipe(pfds, psize, textmode); +} +#endif + +FILE* test::fopen(const char* filename, const char* mode) { + EMULATE_EINTR(fopen, nullptr); + return ::fopen(filename, mode); +} + +int test::fclose(FILE* stream) { + EMULATE_EINTR(fclose, EOF); + return ::fclose(stream); +} + +int(test::fileno)(FILE* stream) { + EMULATE_EINTR(fileno, -1); +#ifdef fileno + return FMT_POSIX(fileno(stream)); +#else + return ::FMT_POSIX(fileno(stream)); +#endif +} + +#ifndef _WIN32 +# define EXPECT_RETRY(statement, func, message) \ + func##_count = 1; \ + statement; \ + EXPECT_EQ(4, func##_count); \ + func##_count = 0; +# define EXPECT_EQ_POSIX(expected, actual) EXPECT_EQ(expected, actual) +#else +# define EXPECT_RETRY(statement, func, message) \ + func##_count = 1; \ + EXPECT_SYSTEM_ERROR(statement, EINTR, message); \ + func##_count = 0; +# define EXPECT_EQ_POSIX(expected, actual) +#endif + +#if FMT_USE_FCNTL +void write_file(fmt::cstring_view filename, fmt::string_view content) { + fmt::buffered_file f(filename, "w"); + f.print("{}", content); +} + +using fmt::file; + +TEST(os_test, getpagesize) { +# ifdef _WIN32 + SYSTEM_INFO si = {}; + GetSystemInfo(&si); + EXPECT_EQ(si.dwPageSize, fmt::getpagesize()); +# else + EXPECT_EQ(sysconf(_SC_PAGESIZE), fmt::getpagesize()); + sysconf_error = true; + EXPECT_SYSTEM_ERROR(fmt::getpagesize(), EINVAL, + "cannot get memory page size"); + sysconf_error = false; +# endif +} + +TEST(file_test, open_retry) { + write_file("temp", "there must be something here"); + std::unique_ptr<file> f{nullptr}; + EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open, + "cannot open file temp"); +# ifndef _WIN32 + char c = 0; + f->read(&c, 1); +# endif +} + +TEST(file_test, close_no_retry_in_dtor) { + file read_end, write_end; + file::pipe(read_end, write_end); + std::unique_ptr<file> f(new file(std::move(read_end))); + int saved_close_count = 0; + EXPECT_WRITE( + stderr, + { + close_count = 1; + f.reset(nullptr); + saved_close_count = close_count; + close_count = 0; + }, + format_system_error(EINTR, "cannot close file") + "\n"); + EXPECT_EQ(2, saved_close_count); +} + +TEST(file_test, close_no_retry) { + file read_end, write_end; + file::pipe(read_end, write_end); + close_count = 1; + EXPECT_SYSTEM_ERROR(read_end.close(), EINTR, "cannot close file"); + EXPECT_EQ(2, close_count); + close_count = 0; +} + +TEST(file_test, size) { + std::string content = "top secret, destroy before reading"; + write_file("temp", content); + file f("temp", file::RDONLY); + EXPECT_GE(f.size(), 0); + EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size())); +# ifdef _WIN32 + fmt::memory_buffer message; + fmt::detail::format_windows_error(message, ERROR_ACCESS_DENIED, + "cannot get file size"); + fstat_sim = error; + EXPECT_THROW_MSG(f.size(), fmt::windows_error, fmt::to_string(message)); + fstat_sim = none; +# else + f.close(); + EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes"); +# endif +} + +TEST(file_test, max_size) { + write_file("temp", ""); + file f("temp", file::RDONLY); + fstat_sim = max_size; + EXPECT_GE(f.size(), 0); + EXPECT_EQ(max_file_size(), f.size()); + fstat_sim = none; +} + +TEST(file_test, read_retry) { + file read_end, write_end; + file::pipe(read_end, write_end); + enum { SIZE = 4 }; + write_end.write("test", SIZE); + write_end.close(); + char buffer[SIZE]; + size_t count = 0; + EXPECT_RETRY(count = read_end.read(buffer, SIZE), read, + "cannot read from file"); + EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count); +} + +TEST(file_test, write_retry) { + file read_end, write_end; + file::pipe(read_end, write_end); + enum { SIZE = 4 }; + size_t count = 0; + EXPECT_RETRY(count = write_end.write("test", SIZE), write, + "cannot write to file"); + write_end.close(); +# ifndef _WIN32 + EXPECT_EQ(static_cast<std::streamsize>(SIZE), count); + char buffer[SIZE + 1]; + read_end.read(buffer, SIZE); + buffer[SIZE] = '\0'; + EXPECT_STREQ("test", buffer); +# endif +} + +# ifdef _WIN32 +TEST(file_test, convert_read_count) { + file read_end, write_end; + file::pipe(read_end, write_end); + char c; + size_t size = UINT_MAX; + if (sizeof(unsigned) != sizeof(size_t)) ++size; + read_count = 1; + read_nbyte = 0; + EXPECT_THROW(read_end.read(&c, size), fmt::system_error); + read_count = 0; + EXPECT_EQ(UINT_MAX, read_nbyte); +} + +TEST(file_test, convert_write_count) { + file read_end, write_end; + file::pipe(read_end, write_end); + char c; + size_t size = UINT_MAX; + if (sizeof(unsigned) != sizeof(size_t)) ++size; + write_count = 1; + write_nbyte = 0; + EXPECT_THROW(write_end.write(&c, size), fmt::system_error); + write_count = 0; + EXPECT_EQ(UINT_MAX, write_nbyte); +} +# endif + +TEST(file_test, dup_no_retry) { + int stdout_fd = FMT_POSIX(fileno(stdout)); + dup_count = 1; + EXPECT_SYSTEM_ERROR( + file::dup(stdout_fd), EINTR, + fmt::format("cannot duplicate file descriptor {}", stdout_fd)); + dup_count = 0; +} + +TEST(file_test, dup2_retry) { + int stdout_fd = FMT_POSIX(fileno(stdout)); + file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd); + EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2, + fmt::format("cannot duplicate file descriptor {} to {}", + f1.descriptor(), f2.descriptor())); +} + +TEST(file_test, dup2_no_except_retry) { + int stdout_fd = FMT_POSIX(fileno(stdout)); + file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd); + error_code ec; + dup2_count = 1; + f1.dup2(f2.descriptor(), ec); +# ifndef _WIN32 + EXPECT_EQ(4, dup2_count); +# else + EXPECT_EQ(EINTR, ec.get()); +# endif + dup2_count = 0; +} + +TEST(file_test, pipe_no_retry) { + file read_end, write_end; + pipe_count = 1; + EXPECT_SYSTEM_ERROR(file::pipe(read_end, write_end), EINTR, + "cannot create pipe"); + pipe_count = 0; +} + +TEST(file_test, fdopen_no_retry) { + file read_end, write_end; + file::pipe(read_end, write_end); + fdopen_count = 1; + EXPECT_SYSTEM_ERROR(read_end.fdopen("r"), EINTR, + "cannot associate stream with file descriptor"); + fdopen_count = 0; +} + +TEST(buffered_file_test, open_retry) { + write_file("temp", "there must be something here"); + std::unique_ptr<buffered_file> f{nullptr}; + EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen, + "cannot open file temp"); +# ifndef _WIN32 + char c = 0; + if (fread(&c, 1, 1, f->get()) < 1) + throw fmt::system_error(errno, "fread failed"); +# endif +} + +TEST(buffered_file_test, close_no_retry_in_dtor) { + file read_end, write_end; + file::pipe(read_end, write_end); + std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r"))); + int saved_fclose_count = 0; + EXPECT_WRITE( + stderr, + { + fclose_count = 1; + f.reset(nullptr); + saved_fclose_count = fclose_count; + fclose_count = 0; + }, + format_system_error(EINTR, "cannot close file") + "\n"); + EXPECT_EQ(2, saved_fclose_count); +} + +TEST(buffered_file_test, close_no_retry) { + file read_end, write_end; + file::pipe(read_end, write_end); + buffered_file f = read_end.fdopen("r"); + fclose_count = 1; + EXPECT_SYSTEM_ERROR(f.close(), EINTR, "cannot close file"); + EXPECT_EQ(2, fclose_count); + fclose_count = 0; +} + +TEST(buffered_file_test, fileno_no_retry) { + file read_end, write_end; + file::pipe(read_end, write_end); + buffered_file f = read_end.fdopen("r"); + fileno_count = 1; + EXPECT_SYSTEM_ERROR((f.fileno)(), EINTR, "cannot get file descriptor"); + EXPECT_EQ(2, fileno_count); + fileno_count = 0; +} +#endif // FMT_USE_FCNTL + +struct test_mock { + static test_mock* instance; +} * test_mock::instance; + +TEST(scoped_mock, scope) { + { + scoped_mock<test_mock> mock; + EXPECT_EQ(&mock, test_mock::instance); + test_mock& copy = mock; + static_cast<void>(copy); + } + EXPECT_EQ(nullptr, test_mock::instance); +} + +#ifdef FMT_LOCALE + +using locale_type = fmt::locale::type; + +struct locale_mock { + static locale_mock* instance; + MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale, + locale_type base)); + MOCK_METHOD1(freelocale, void(locale_type locale)); + + MOCK_METHOD3(strtod_l, + double(const char* nptr, char** endptr, locale_type locale)); +} * locale_mock::instance; + +# ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4273) +# ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Winconsistent-dllimport" +# endif + +_locale_t _create_locale(int category, const char* locale) { + return locale_mock::instance->newlocale(category, locale, 0); +} + +void _free_locale(_locale_t locale) { + locale_mock::instance->freelocale(locale); +} + +double _strtod_l(const char* nptr, char** endptr, _locale_t locale) { + return locale_mock::instance->strtod_l(nptr, endptr, locale); +} +# ifdef __clang__ +# pragma clang diagnostic pop +# endif +# pragma warning(pop) +# endif + +# if defined(__THROW) && \ + ((FMT_GCC_VERSION > 0 && FMT_GCC_VERSION <= 408) || defined(__e2k__)) +# define FMT_LOCALE_THROW __THROW +# else +# define FMT_LOCALE_THROW +# endif + +# if defined(__APPLE__) || \ + (defined(__FreeBSD__) && __FreeBSD_version < 1200002) +typedef int FreeLocaleResult; +# else +typedef void FreeLocaleResult; +# endif + +FreeLocaleResult freelocale(locale_type locale) FMT_LOCALE_THROW { + locale_mock::instance->freelocale(locale); + return FreeLocaleResult(); +} + +double strtod_l(const char* nptr, char** endptr, + locale_type locale) FMT_LOCALE_THROW { + return locale_mock::instance->strtod_l(nptr, endptr, locale); +} + +# undef FMT_LOCALE_THROW + +# ifndef _WIN32 +locale_t test::newlocale(int category_mask, const char* locale, locale_t base) { + return locale_mock::instance->newlocale(category_mask, locale, base); +} + +TEST(locale_test, locale_mock) { + scoped_mock<locale_mock> mock; + auto locale = reinterpret_cast<locale_type>(11); + EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale)); + FMT_SYSTEM(newlocale(222, "foo", locale)); +} +# endif + +TEST(locale_test, locale) { +# ifndef LC_NUMERIC_MASK + enum { LC_NUMERIC_MASK = LC_NUMERIC }; +# endif + scoped_mock<locale_mock> mock; + auto impl = reinterpret_cast<locale_type>(42); + EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr)) + .WillOnce(Return(impl)); + EXPECT_CALL(mock, freelocale(impl)); + fmt::locale loc; + EXPECT_EQ(impl, loc.get()); +} + +TEST(locale_test, strtod) { + scoped_mock<locale_mock> mock; + EXPECT_CALL(mock, newlocale(_, _, _)) + .WillOnce(Return(reinterpret_cast<locale_type>(42))); + EXPECT_CALL(mock, freelocale(_)); + fmt::locale loc; + const char* str = "4.2"; + char end = 'x'; + EXPECT_CALL(mock, strtod_l(str, _, loc.get())) + .WillOnce(testing::DoAll(testing::SetArgPointee<1>(&end), Return(777))); + EXPECT_EQ(777, loc.strtod(str)); + EXPECT_EQ(&end, str); +} + +#endif // FMT_LOCALE diff --git a/libs/fmt/test/posix-mock.h b/libs/fmt/test/posix-mock.h new file mode 100644 index 0000000000000000000000000000000000000000..e76af70022403c8d3a8e7a54fef5f9b728785238 --- /dev/null +++ b/libs/fmt/test/posix-mock.h @@ -0,0 +1,77 @@ +// Formatting library for C++ - mocks of POSIX functions +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_POSIX_TEST_H +#define FMT_POSIX_TEST_H + +#include <errno.h> +#include <locale.h> +#include <stdio.h> +#ifdef __APPLE__ +# include <xlocale.h> +#endif + +#ifdef _WIN32 +# include <windows.h> +#else +# include <sys/param.h> // for FreeBSD version +# include <sys/types.h> // for ssize_t +#endif + +#ifndef _MSC_VER +struct stat; +#endif + +namespace test { + +#ifndef _MSC_VER +// Size type for read and write. +typedef size_t size_t; +typedef ssize_t ssize_t; +int open(const char* path, int oflag, int mode); +int fstat(int fd, struct stat* buf); +#else +typedef unsigned size_t; +typedef int ssize_t; +errno_t sopen_s(int* pfh, const char* filename, int oflag, int shflag, + int pmode); +#endif + +#ifndef _WIN32 +long sysconf(int name); +#else +DWORD GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh); +#endif + +int close(int fildes); + +int dup(int fildes); +int dup2(int fildes, int fildes2); + +FILE* fdopen(int fildes, const char* mode); + +ssize_t read(int fildes, void* buf, size_t nbyte); +ssize_t write(int fildes, const void* buf, size_t nbyte); + +#ifndef _WIN32 +int pipe(int fildes[2]); +#else +int pipe(int* pfds, unsigned psize, int textmode); +#endif + +FILE* fopen(const char* filename, const char* mode); +int fclose(FILE* stream); +int(fileno)(FILE* stream); + +#if defined(FMT_LOCALE) && !defined(_WIN32) +locale_t newlocale(int category_mask, const char* locale, locale_t base); +#endif +} // namespace test + +#define FMT_SYSTEM(call) test::call + +#endif // FMT_POSIX_TEST_H diff --git a/libs/fmt/test/printf-test.cc b/libs/fmt/test/printf-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..f8db99aed5e9af7a07826e39bfd9c58b25f52fdc --- /dev/null +++ b/libs/fmt/test/printf-test.cc @@ -0,0 +1,629 @@ +// Formatting library for C++ - printf tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/printf.h" + +#include <cctype> +#include <climits> +#include <cstring> + +#include "fmt/core.h" +#include "fmt/ostream.h" +#include "gtest-extra.h" +#include "util.h" + +using fmt::format; +using fmt::format_error; +using fmt::detail::max_value; + +const unsigned big_num = INT_MAX + 1u; + +// Makes format string argument positional. +static std::string make_positional(fmt::string_view format) { + std::string s(format.data(), format.size()); + s.replace(s.find('%'), 1, "%1$"); + return s; +} + +static std::wstring make_positional(fmt::wstring_view format) { + std::wstring s(format.data(), format.size()); + s.replace(s.find(L'%'), 1, L"%1$"); + return s; +} + +// A wrapper around fmt::sprintf to workaround bogus warnings about invalid +// format strings in MSVC. +template <typename... Args> +std::string test_sprintf(fmt::string_view format, const Args&... args) { + return fmt::sprintf(format, args...); +} +template <typename... Args> +std::wstring test_sprintf(fmt::wstring_view format, const Args&... args) { + return fmt::sprintf(format, args...); +} + +#define EXPECT_PRINTF(expected_output, format, arg) \ + EXPECT_EQ(expected_output, test_sprintf(format, arg)) \ + << "format: " << format; \ + EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg)) + +template <typename T> struct value_extractor { + T operator()(T value) { return value; } + + template <typename U> FMT_NORETURN T operator()(U) { + throw std::runtime_error(fmt::format("invalid type {}", typeid(U).name())); + } + +#if FMT_USE_INT128 + // Apple Clang does not define typeid for __int128_t and __uint128_t. + FMT_NORETURN T operator()(fmt::detail::int128_t) { + throw std::runtime_error("invalid type __int128_t"); + } + + FMT_NORETURN T operator()(fmt::detail::uint128_t) { + throw std::runtime_error("invalid type __uint128_t"); + } +#endif +}; + +TEST(printf_test, arg_converter) { + long long value = max_value<long long>(); + auto arg = fmt::detail::make_arg<fmt::format_context>(value); + fmt::visit_format_arg( + fmt::detail::arg_converter<long long, fmt::format_context>(arg, 'd'), + arg); + EXPECT_EQ(value, fmt::visit_format_arg(value_extractor<long long>(), arg)); +} + +TEST(printf_test, no_args) { + EXPECT_EQ("test", test_sprintf("test")); + EXPECT_EQ(L"test", fmt::sprintf(L"test")); +} + +TEST(printf_test, escape) { + EXPECT_EQ("%", test_sprintf("%%")); + EXPECT_EQ("before %", test_sprintf("before %%")); + EXPECT_EQ("% after", test_sprintf("%% after")); + EXPECT_EQ("before % after", test_sprintf("before %% after")); + EXPECT_EQ("%s", test_sprintf("%%s")); + EXPECT_EQ(L"%", fmt::sprintf(L"%%")); + EXPECT_EQ(L"before %", fmt::sprintf(L"before %%")); + EXPECT_EQ(L"% after", fmt::sprintf(L"%% after")); + EXPECT_EQ(L"before % after", fmt::sprintf(L"before %% after")); + EXPECT_EQ(L"%s", fmt::sprintf(L"%%s")); +} + +TEST(printf_test, positional_args) { + EXPECT_EQ("42", test_sprintf("%1$d", 42)); + EXPECT_EQ("before 42", test_sprintf("before %1$d", 42)); + EXPECT_EQ("42 after", test_sprintf("%1$d after", 42)); + EXPECT_EQ("before 42 after", test_sprintf("before %1$d after", 42)); + EXPECT_EQ("answer = 42", test_sprintf("%1$s = %2$d", "answer", 42)); + EXPECT_EQ("42 is the answer", test_sprintf("%2$d is the %1$s", "answer", 42)); + EXPECT_EQ("abracadabra", test_sprintf("%1$s%2$s%1$s", "abra", "cad")); +} + +TEST(printf_test, automatic_arg_indexing) { + EXPECT_EQ("abc", test_sprintf("%c%c%c", 'a', 'b', 'c')); +} + +TEST(printf_test, number_is_too_big_in_arg_index) { + EXPECT_THROW_MSG(test_sprintf(format("%{}$", big_num)), format_error, + "number is too big"); + EXPECT_THROW_MSG(test_sprintf(format("%{}$d", big_num)), format_error, + "number is too big"); +} + +TEST(printf_test, switch_arg_indexing) { + EXPECT_THROW_MSG(test_sprintf("%1$d%", 1, 2), format_error, + "cannot switch from manual to automatic argument indexing"); + EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", big_num), 1, 2), + format_error, "number is too big"); + EXPECT_THROW_MSG(test_sprintf("%1$d%d", 1, 2), format_error, + "cannot switch from manual to automatic argument indexing"); + + EXPECT_THROW_MSG(test_sprintf("%d%1$", 1, 2), format_error, + "cannot switch from automatic to manual argument indexing"); + EXPECT_THROW_MSG(test_sprintf(format("%d%{}$d", big_num), 1, 2), format_error, + "number is too big"); + EXPECT_THROW_MSG(test_sprintf("%d%1$d", 1, 2), format_error, + "cannot switch from automatic to manual argument indexing"); + + // Indexing errors override width errors. + EXPECT_THROW_MSG(test_sprintf(format("%d%1${}d", big_num), 1, 2), + format_error, "number is too big"); + EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", big_num), 1, 2), + format_error, "number is too big"); +} + +TEST(printf_test, invalid_arg_index) { + EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error, + "argument not found"); + EXPECT_THROW_MSG(test_sprintf("%2$d", 42), format_error, + "argument not found"); + EXPECT_THROW_MSG(test_sprintf(format("%{}$d", INT_MAX), 42), format_error, + "argument not found"); + + EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error, "argument not found"); + EXPECT_THROW_MSG(test_sprintf(format("%{}$d", big_num), 42), format_error, + "number is too big"); +} + +TEST(printf_test, default_align_right) { + EXPECT_PRINTF(" 42", "%5d", 42); + EXPECT_PRINTF(" abc", "%5s", "abc"); +} + +TEST(printf_test, zero_flag) { + EXPECT_PRINTF("00042", "%05d", 42); + EXPECT_PRINTF("-0042", "%05d", -42); + + EXPECT_PRINTF("00042", "%05d", 42); + EXPECT_PRINTF("-0042", "%05d", -42); + EXPECT_PRINTF("-004.2", "%06g", -4.2); + + EXPECT_PRINTF("+00042", "%00+6d", 42); + + EXPECT_PRINTF(" 42", "%05.d", 42); + EXPECT_PRINTF(" 0042", "%05.4d", 42); + + // '0' flag is ignored for non-numeric types. + EXPECT_PRINTF(" x", "%05c", 'x'); +} + +TEST(printf_test, plus_flag) { + EXPECT_PRINTF("+42", "%+d", 42); + EXPECT_PRINTF("-42", "%+d", -42); + EXPECT_PRINTF("+0042", "%+05d", 42); + EXPECT_PRINTF("+0042", "%0++5d", 42); + + // '+' flag is ignored for non-numeric types. + EXPECT_PRINTF("x", "%+c", 'x'); + + // '+' flag wins over space flag + EXPECT_PRINTF("+42", "%+ d", 42); + EXPECT_PRINTF("-42", "%+ d", -42); + EXPECT_PRINTF("+42", "% +d", 42); + EXPECT_PRINTF("-42", "% +d", -42); + EXPECT_PRINTF("+0042", "% +05d", 42); + EXPECT_PRINTF("+0042", "%0+ 5d", 42); + + // '+' flag and space flag are both ignored for non-numeric types. + EXPECT_PRINTF("x", "%+ c", 'x'); + EXPECT_PRINTF("x", "% +c", 'x'); +} + +TEST(printf_test, minus_flag) { + EXPECT_PRINTF("abc ", "%-5s", "abc"); + EXPECT_PRINTF("abc ", "%0--5s", "abc"); + + EXPECT_PRINTF("7 ", "%-5d", 7); + EXPECT_PRINTF("97 ", "%-5hhi", 'a'); + EXPECT_PRINTF("a ", "%-5c", 'a'); + + // '0' flag is ignored if '-' flag is given + EXPECT_PRINTF("7 ", "%-05d", 7); + EXPECT_PRINTF("7 ", "%0-5d", 7); + EXPECT_PRINTF("a ", "%-05c", 'a'); + EXPECT_PRINTF("a ", "%0-5c", 'a'); + EXPECT_PRINTF("97 ", "%-05hhi", 'a'); + EXPECT_PRINTF("97 ", "%0-5hhi", 'a'); + + // '-' and space flag don't interfere + EXPECT_PRINTF(" 42", "%- d", 42); +} + +TEST(printf_test, space_flag) { + EXPECT_PRINTF(" 42", "% d", 42); + EXPECT_PRINTF("-42", "% d", -42); + EXPECT_PRINTF(" 0042", "% 05d", 42); + EXPECT_PRINTF(" 0042", "%0 5d", 42); + + // ' ' flag is ignored for non-numeric types. + EXPECT_PRINTF("x", "% c", 'x'); +} + +TEST(printf_test, hash_flag) { + EXPECT_PRINTF("042", "%#o", 042); + EXPECT_PRINTF(fmt::format("0{:o}", static_cast<unsigned>(-042)), "%#o", -042); + EXPECT_PRINTF("0", "%#o", 0); + + EXPECT_PRINTF("0x42", "%#x", 0x42); + EXPECT_PRINTF("0X42", "%#X", 0x42); + EXPECT_PRINTF(fmt::format("0x{:x}", static_cast<unsigned>(-0x42)), "%#x", + -0x42); + EXPECT_PRINTF("0", "%#x", 0); + + EXPECT_PRINTF("0x0042", "%#06x", 0x42); + EXPECT_PRINTF("0x0042", "%0##6x", 0x42); + + EXPECT_PRINTF("-42.000000", "%#f", -42.0); + EXPECT_PRINTF("-42.000000", "%#F", -42.0); + + char buffer[256]; + safe_sprintf(buffer, "%#e", -42.0); + EXPECT_PRINTF(buffer, "%#e", -42.0); + safe_sprintf(buffer, "%#E", -42.0); + EXPECT_PRINTF(buffer, "%#E", -42.0); + + EXPECT_PRINTF("-42.0000", "%#g", -42.0); + EXPECT_PRINTF("-42.0000", "%#G", -42.0); + + safe_sprintf(buffer, "%#a", 16.0); + EXPECT_PRINTF(buffer, "%#a", 16.0); + safe_sprintf(buffer, "%#A", 16.0); + EXPECT_PRINTF(buffer, "%#A", 16.0); + + // '#' flag is ignored for non-numeric types. + EXPECT_PRINTF("x", "%#c", 'x'); +} + +TEST(printf_test, width) { + EXPECT_PRINTF(" abc", "%5s", "abc"); + + // Width cannot be specified twice. + EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error, + "invalid type specifier"); + + EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error, + "number is too big"); + EXPECT_THROW_MSG(test_sprintf(format("%1${}d", big_num), 42), format_error, + "number is too big"); +} + +TEST(printf_test, dynamic_width) { + EXPECT_EQ(" 42", test_sprintf("%*d", 5, 42)); + EXPECT_EQ("42 ", test_sprintf("%*d", -5, 42)); + EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error, + "width is not integer"); + EXPECT_THROW_MSG(test_sprintf("%*d"), format_error, "argument not found"); + EXPECT_THROW_MSG(test_sprintf("%*d", big_num, 42), format_error, + "number is too big"); +} + +TEST(printf_test, int_precision) { + EXPECT_PRINTF("00042", "%.5d", 42); + EXPECT_PRINTF("-00042", "%.5d", -42); + EXPECT_PRINTF("00042", "%.5x", 0x42); + EXPECT_PRINTF("0x00042", "%#.5x", 0x42); + EXPECT_PRINTF("00042", "%.5o", 042); + EXPECT_PRINTF("00042", "%#.5o", 042); + + EXPECT_PRINTF(" 00042", "%7.5d", 42); + EXPECT_PRINTF(" 00042", "%7.5x", 0x42); + EXPECT_PRINTF(" 0x00042", "%#10.5x", 0x42); + EXPECT_PRINTF(" 00042", "%7.5o", 042); + EXPECT_PRINTF(" 00042", "%#10.5o", 042); + + EXPECT_PRINTF("00042 ", "%-7.5d", 42); + EXPECT_PRINTF("00042 ", "%-7.5x", 0x42); + EXPECT_PRINTF("0x00042 ", "%-#10.5x", 0x42); + EXPECT_PRINTF("00042 ", "%-7.5o", 042); + EXPECT_PRINTF("00042 ", "%-#10.5o", 042); +} + +TEST(printf_test, float_precision) { + char buffer[256]; + safe_sprintf(buffer, "%.3e", 1234.5678); + EXPECT_PRINTF(buffer, "%.3e", 1234.5678); + EXPECT_PRINTF("1234.568", "%.3f", 1234.5678); + EXPECT_PRINTF("1.23e+03", "%.3g", 1234.5678); + safe_sprintf(buffer, "%.3a", 1234.5678); + EXPECT_PRINTF(buffer, "%.3a", 1234.5678); +} + +TEST(printf_test, string_precision) { + char test[] = {'H', 'e', 'l', 'l', 'o'}; + EXPECT_EQ(fmt::sprintf("%.4s", test), "Hell"); +} + +TEST(printf_test, ignore_precision_for_non_numeric_arg) { + EXPECT_PRINTF("abc", "%.5s", "abc"); +} + +TEST(printf_test, dynamic_precision) { + EXPECT_EQ("00042", test_sprintf("%.*d", 5, 42)); + EXPECT_EQ("42", test_sprintf("%.*d", -5, 42)); + EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error, + "precision is not integer"); + EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error, "argument not found"); + EXPECT_THROW_MSG(test_sprintf("%.*d", big_num, 42), format_error, + "number is too big"); + if (sizeof(long long) != sizeof(int)) { + long long prec = static_cast<long long>(INT_MIN) - 1; + EXPECT_THROW_MSG(test_sprintf("%.*d", prec, 42), format_error, + "number is too big"); + } +} + +template <typename T> struct make_signed { typedef T type; }; + +#define SPECIALIZE_MAKE_SIGNED(T, S) \ + template <> struct make_signed<T> { typedef S type; } + +SPECIALIZE_MAKE_SIGNED(char, signed char); +SPECIALIZE_MAKE_SIGNED(unsigned char, signed char); +SPECIALIZE_MAKE_SIGNED(unsigned short, short); +SPECIALIZE_MAKE_SIGNED(unsigned, int); +SPECIALIZE_MAKE_SIGNED(unsigned long, long); +SPECIALIZE_MAKE_SIGNED(unsigned long long, long long); + +// Test length format specifier ``length_spec``. +template <typename T, typename U> +void test_length(const char* length_spec, U value) { + long long signed_value = 0; + unsigned long long unsigned_value = 0; + // Apply integer promotion to the argument. + unsigned long long max = max_value<U>(); + using fmt::detail::const_check; + if (const_check(max <= static_cast<unsigned>(max_value<int>()))) { + signed_value = static_cast<int>(value); + unsigned_value = static_cast<unsigned long long>(value); + } else if (const_check(max <= max_value<unsigned>())) { + signed_value = static_cast<unsigned>(value); + unsigned_value = static_cast<unsigned long long>(value); + } + if (sizeof(U) <= sizeof(int) && sizeof(int) < sizeof(T)) { + signed_value = static_cast<long long>(value); + unsigned_value = static_cast<unsigned long long>( + static_cast<typename std::make_unsigned<unsigned>::type>(value)); + } else { + signed_value = static_cast<typename make_signed<T>::type>(value); + unsigned_value = static_cast<typename std::make_unsigned<T>::type>(value); + } + std::ostringstream os; + os << signed_value; + EXPECT_PRINTF(os.str(), fmt::format("%{}d", length_spec), value); + EXPECT_PRINTF(os.str(), fmt::format("%{}i", length_spec), value); + os.str(""); + os << unsigned_value; + EXPECT_PRINTF(os.str(), fmt::format("%{}u", length_spec), value); + os.str(""); + os << std::oct << unsigned_value; + EXPECT_PRINTF(os.str(), fmt::format("%{}o", length_spec), value); + os.str(""); + os << std::hex << unsigned_value; + EXPECT_PRINTF(os.str(), fmt::format("%{}x", length_spec), value); + os.str(""); + os << std::hex << std::uppercase << unsigned_value; + EXPECT_PRINTF(os.str(), fmt::format("%{}X", length_spec), value); +} + +template <typename T> void test_length(const char* length_spec) { + T min = std::numeric_limits<T>::min(), max = max_value<T>(); + test_length<T>(length_spec, 42); + test_length<T>(length_spec, -42); + test_length<T>(length_spec, min); + test_length<T>(length_spec, max); + long long long_long_min = std::numeric_limits<long long>::min(); + if (static_cast<long long>(min) > long_long_min) + test_length<T>(length_spec, static_cast<long long>(min) - 1); + unsigned long long long_long_max = max_value<long long>(); + if (static_cast<unsigned long long>(max) < long_long_max) + test_length<T>(length_spec, static_cast<long long>(max) + 1); + test_length<T>(length_spec, std::numeric_limits<short>::min()); + test_length<T>(length_spec, max_value<unsigned short>()); + test_length<T>(length_spec, std::numeric_limits<int>::min()); + test_length<T>(length_spec, max_value<int>()); + test_length<T>(length_spec, std::numeric_limits<unsigned>::min()); + test_length<T>(length_spec, max_value<unsigned>()); + test_length<T>(length_spec, std::numeric_limits<long long>::min()); + test_length<T>(length_spec, max_value<long long>()); + test_length<T>(length_spec, std::numeric_limits<unsigned long long>::min()); + test_length<T>(length_spec, max_value<unsigned long long>()); +} + +TEST(printf_test, length) { + test_length<char>("hh"); + test_length<signed char>("hh"); + test_length<unsigned char>("hh"); + test_length<short>("h"); + test_length<unsigned short>("h"); + test_length<long>("l"); + test_length<unsigned long>("l"); + test_length<long long>("ll"); + test_length<unsigned long long>("ll"); + test_length<intmax_t>("j"); + test_length<size_t>("z"); + test_length<std::ptrdiff_t>("t"); + long double max = max_value<long double>(); + EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max); + EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max); +} + +TEST(printf_test, bool) { + EXPECT_PRINTF("1", "%d", true); + EXPECT_PRINTF("true", "%s", true); +} + +TEST(printf_test, int) { + EXPECT_PRINTF("-42", "%d", -42); + EXPECT_PRINTF("-42", "%i", -42); + unsigned u = 0 - 42u; + EXPECT_PRINTF(fmt::format("{}", u), "%u", -42); + EXPECT_PRINTF(fmt::format("{:o}", u), "%o", -42); + EXPECT_PRINTF(fmt::format("{:x}", u), "%x", -42); + EXPECT_PRINTF(fmt::format("{:X}", u), "%X", -42); +} + +TEST(printf_test, long_long) { + // fmt::printf allows passing long long arguments to %d without length + // specifiers. + long long max = max_value<long long>(); + EXPECT_PRINTF(fmt::format("{}", max), "%d", max); +} + +TEST(printf_test, float) { + EXPECT_PRINTF("392.650000", "%f", 392.65); + EXPECT_PRINTF("392.65", "%.2f", 392.65); + EXPECT_PRINTF("392.6", "%.1f", 392.65); + EXPECT_PRINTF("393", "%.f", 392.65); + EXPECT_PRINTF("392.650000", "%F", 392.65); + char buffer[256]; + safe_sprintf(buffer, "%e", 392.65); + EXPECT_PRINTF(buffer, "%e", 392.65); + safe_sprintf(buffer, "%E", 392.65); + EXPECT_PRINTF(buffer, "%E", 392.65); + EXPECT_PRINTF("392.65", "%g", 392.65); + EXPECT_PRINTF("392.65", "%G", 392.65); + EXPECT_PRINTF("392", "%g", 392.0); + EXPECT_PRINTF("392", "%G", 392.0); + EXPECT_PRINTF("4.56e-07", "%g", 0.000000456); + safe_sprintf(buffer, "%a", -392.65); + EXPECT_EQ(buffer, format("{:a}", -392.65)); + safe_sprintf(buffer, "%A", -392.65); + EXPECT_EQ(buffer, format("{:A}", -392.65)); +} + +TEST(printf_test, inf) { + double inf = std::numeric_limits<double>::infinity(); + for (const char* type = "fega"; *type; ++type) { + EXPECT_PRINTF("inf", fmt::format("%{}", *type), inf); + char upper = static_cast<char>(std::toupper(*type)); + EXPECT_PRINTF("INF", fmt::format("%{}", upper), inf); + } +} + +TEST(printf_test, char) { + EXPECT_PRINTF("x", "%c", 'x'); + int max = max_value<int>(); + EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max); + // EXPECT_PRINTF("x", "%lc", L'x'); + EXPECT_PRINTF(L"x", L"%c", L'x'); + EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max); +} + +TEST(printf_test, string) { + EXPECT_PRINTF("abc", "%s", "abc"); + const char* null_str = nullptr; + EXPECT_PRINTF("(null)", "%s", null_str); + EXPECT_PRINTF(" (null)", "%10s", null_str); + EXPECT_PRINTF(L"abc", L"%s", L"abc"); + const wchar_t* null_wstr = nullptr; + EXPECT_PRINTF(L"(null)", L"%s", null_wstr); + EXPECT_PRINTF(L" (null)", L"%10s", null_wstr); +} + +TEST(printf_test, uchar_string) { + unsigned char str[] = "test"; + unsigned char* pstr = str; + EXPECT_EQ("test", fmt::sprintf("%s", pstr)); +} + +TEST(printf_test, pointer) { + int n; + void* p = &n; + EXPECT_PRINTF(fmt::format("{}", p), "%p", p); + p = nullptr; + EXPECT_PRINTF("(nil)", "%p", p); + EXPECT_PRINTF(" (nil)", "%10p", p); + const char* s = "test"; + EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s); + const char* null_str = nullptr; + EXPECT_PRINTF("(nil)", "%p", null_str); + + p = &n; + EXPECT_PRINTF(fmt::format(L"{}", p), L"%p", p); + p = nullptr; + EXPECT_PRINTF(L"(nil)", L"%p", p); + EXPECT_PRINTF(L" (nil)", L"%10p", p); + const wchar_t* w = L"test"; + EXPECT_PRINTF(fmt::format(L"{:p}", w), L"%p", w); + const wchar_t* null_wstr = nullptr; + EXPECT_PRINTF(L"(nil)", L"%p", null_wstr); +} + +enum test_enum { answer = 42 }; + +TEST(printf_test, enum) { + EXPECT_PRINTF("42", "%d", answer); + volatile test_enum volatile_enum = answer; + EXPECT_PRINTF("42", "%d", volatile_enum); +} + +#if FMT_USE_FCNTL +TEST(printf_test, examples) { + const char* weekday = "Thursday"; + const char* month = "August"; + int day = 21; + EXPECT_WRITE(stdout, fmt::printf("%1$s, %3$d %2$s", weekday, month, day), + "Thursday, 21 August"); +} + +TEST(printf_test, printf_error) { + fmt::file read_end, write_end; + fmt::file::pipe(read_end, write_end); + int result = fmt::fprintf(read_end.fdopen("r").get(), "test"); + EXPECT_LT(result, 0); +} +#endif + +TEST(printf_test, wide_string) { + EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc")); +} + +TEST(printf_test, printf_custom) { + EXPECT_EQ("abc", test_sprintf("%s", test_string("abc"))); +} + +TEST(printf_test, ostream) { + std::ostringstream os; + int ret = fmt::fprintf(os, "Don't %s!", "panic"); + EXPECT_EQ("Don't panic!", os.str()); + EXPECT_EQ(12, ret); +} + +TEST(printf_test, vprintf) { + fmt::format_arg_store<fmt::printf_context, int> as{42}; + fmt::basic_format_args<fmt::printf_context> args(as); + EXPECT_EQ(fmt::vsprintf("%d", args), "42"); + EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42"); + EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42"); + EXPECT_WRITE(stdout, fmt::vfprintf(std::cout, "%d", args), "42"); +} + +template <typename... Args> +void check_format_string_regression(fmt::string_view s, const Args&... args) { + fmt::sprintf(s, args...); +} + +TEST(printf_test, check_format_string_regression) { + check_format_string_regression("%c%s", 'x', ""); +} + +TEST(printf_test, fixed_large_exponent) { + EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21)); +} + +TEST(printf_test, vsprintf_make_args_example) { + fmt::format_arg_store<fmt::printf_context, int, const char*> as{42, + "something"}; + fmt::basic_format_args<fmt::printf_context> args(as); + EXPECT_EQ("[42] something happened", fmt::vsprintf("[%d] %s happened", args)); + auto as2 = fmt::make_printf_args(42, "something"); + fmt::basic_format_args<fmt::printf_context> args2(as2); + EXPECT_EQ("[42] something happened", + fmt::vsprintf("[%d] %s happened", args2)); + EXPECT_EQ("[42] something happened", + fmt::vsprintf("[%d] %s happened", + {fmt::make_printf_args(42, "something")})); +} + +TEST(printf_test, vsprintf_make_wargs_example) { + fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{ + 42, L"something"}; + fmt::basic_format_args<fmt::wprintf_context> args(as); + EXPECT_EQ(L"[42] something happened", + fmt::vsprintf(L"[%d] %s happened", args)); + auto as2 = fmt::make_wprintf_args(42, L"something"); + fmt::basic_format_args<fmt::wprintf_context> args2(as2); + EXPECT_EQ(L"[42] something happened", + fmt::vsprintf(L"[%d] %s happened", args2)); + EXPECT_EQ(L"[42] something happened", + fmt::vsprintf(L"[%d] %s happened", + {fmt::make_wprintf_args(42, L"something")})); +} diff --git a/libs/fmt/test/ranges-test.cc b/libs/fmt/test/ranges-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..64f65e463465b5c93f6a9d397fb3aa894b7262f3 --- /dev/null +++ b/libs/fmt/test/ranges-test.cc @@ -0,0 +1,269 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for ranges, containers and types tuple interface. + +#include "fmt/ranges.h" + +#include <map> +#include <string> +#include <vector> + +#include "gtest/gtest.h" + +#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 601 +# define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY +#endif + +#if !FMT_MSC_VER || FMT_MSC_VER > 1910 +# define FMT_RANGES_TEST_ENABLE_JOIN +# define FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT +#endif + +#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY +TEST(ranges_test, format_array) { + int arr[] = {1, 2, 3, 5, 7, 11}; + EXPECT_EQ(fmt::format("{}", arr), "[1, 2, 3, 5, 7, 11]"); +} + +TEST(ranges_test, format_2d_array) { + int arr[][2] = {{1, 2}, {3, 5}, {7, 11}}; + EXPECT_EQ(fmt::format("{}", arr), "[[1, 2], [3, 5], [7, 11]]"); +} + +TEST(ranges_test, format_array_of_literals) { + const char* arr[] = {"1234", "abcd"}; + EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]"); +} +#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY + +TEST(ranges_test, format_vector) { + auto v = std::vector<int>{1, 2, 3, 5, 7, 11}; + EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]"); +} + +TEST(ranges_test, format_vector2) { + auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}}; + EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]"); +} + +TEST(ranges_test, format_map) { + auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}}; + EXPECT_EQ(fmt::format("{}", m), "[(\"one\", 1), (\"two\", 2)]"); +} + +TEST(ranges_test, format_pair) { + auto p = std::pair<int, float>(42, 1.5f); + EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)"); +} + +TEST(ranges_test, format_tuple) { + auto t = + std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i'); + EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')"); + EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()"); +} + +#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT +struct tuple_like { + int i; + std::string str; + + template <size_t N> fmt::enable_if_t<N == 0, int> get() const noexcept { + return i; + } + template <size_t N> + fmt::enable_if_t<N == 1, fmt::string_view> get() const noexcept { + return str; + } +}; + +template <size_t N> +auto get(const tuple_like& t) noexcept -> decltype(t.get<N>()) { + return t.get<N>(); +} + +namespace std { +template <> +struct tuple_size<tuple_like> : std::integral_constant<size_t, 2> {}; + +template <size_t N> struct tuple_element<N, tuple_like> { + using type = decltype(std::declval<tuple_like>().get<N>()); +}; +} // namespace std + +TEST(ranges_test, format_struct) { + auto t = tuple_like{42, "foo"}; + EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")"); +} +#endif // FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT + +TEST(ranges_test, format_to) { + char buf[10]; + auto end = fmt::format_to(buf, "{}", std::vector<int>{1, 2, 3}); + *end = '\0'; + EXPECT_STREQ(buf, "[1, 2, 3]"); +} + +struct path_like { + const path_like* begin() const; + const path_like* end() const; + + operator std::string() const; +}; + +TEST(ranges_test, path_like) { + EXPECT_FALSE((fmt::is_range<path_like, char>::value)); +} + +#ifdef FMT_USE_STRING_VIEW +struct string_like { + const char* begin(); + const char* end(); + explicit operator fmt::string_view() const { return "foo"; } + explicit operator std::string_view() const { return "foo"; } +}; + +TEST(ranges_test, format_string_like) { + EXPECT_EQ(fmt::format("{}", string_like()), "foo"); +} +#endif // FMT_USE_STRING_VIEW + +// A range that provides non-const only begin()/end() to test fmt::join handles +// that. +// +// Some ranges (e.g. those produced by range-v3's views::filter()) can cache +// information during iteration so they only provide non-const begin()/end(). +template <typename T> class non_const_only_range { + private: + std::vector<T> vec; + + public: + using const_iterator = typename ::std::vector<T>::const_iterator; + + template <typename... Args> + explicit non_const_only_range(Args&&... args) + : vec(std::forward<Args>(args)...) {} + + const_iterator begin() { return vec.begin(); } + const_iterator end() { return vec.end(); } +}; + +template <typename T> class noncopyable_range { + private: + std::vector<T> vec; + + public: + using const_iterator = typename ::std::vector<T>::const_iterator; + + template <typename... Args> + explicit noncopyable_range(Args&&... args) + : vec(std::forward<Args>(args)...) {} + + noncopyable_range(noncopyable_range const&) = delete; + noncopyable_range(noncopyable_range&) = delete; + + const_iterator begin() const { return vec.begin(); } + const_iterator end() const { return vec.end(); } +}; + +TEST(ranges_test, range) { + noncopyable_range<int> w(3u, 0); + EXPECT_EQ(fmt::format("{}", w), "[0, 0, 0]"); + EXPECT_EQ(fmt::format("{}", noncopyable_range<int>(3u, 0)), "[0, 0, 0]"); + + non_const_only_range<int> x(3u, 0); + EXPECT_EQ(fmt::format("{}", x), "[0, 0, 0]"); + EXPECT_EQ(fmt::format("{}", non_const_only_range<int>(3u, 0)), "[0, 0, 0]"); + + auto y = std::vector<int>(3u, 0); + EXPECT_EQ(fmt::format("{}", y), "[0, 0, 0]"); + EXPECT_EQ(fmt::format("{}", std::vector<int>(3u, 0)), "[0, 0, 0]"); + + const auto z = std::vector<int>(3u, 0); + EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]"); +} + +#if !FMT_MSC_VER || FMT_MSC_VER >= 1927 +struct unformattable {}; + +TEST(ranges_test, unformattable_range) { + EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>, + fmt::format_context>::value)); +} +#endif + +#ifdef FMT_RANGES_TEST_ENABLE_JOIN +TEST(ranges_test, join_tuple) { + // Value tuple args. + auto t1 = std::tuple<char, int, float>('a', 1, 2.0f); + EXPECT_EQ(fmt::format("({})", fmt::join(t1, ", ")), "(a, 1, 2)"); + + // Testing lvalue tuple args. + int x = 4; + auto t2 = std::tuple<char, int&>('b', x); + EXPECT_EQ(fmt::format("{}", fmt::join(t2, " + ")), "b + 4"); + + // Empty tuple. + auto t3 = std::tuple<>(); + EXPECT_EQ(fmt::format("{}", fmt::join(t3, "|")), ""); + + // Single element tuple. + auto t4 = std::tuple<float>(4.0f); + EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4"); +} + +TEST(ranges_test, wide_string_join_tuple) { + auto t = std::tuple<wchar_t, int, float>('a', 1, 2.0f); + EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)"); +} + +TEST(ranges_test, join_initializer_list) { + EXPECT_EQ(fmt::format("{}", fmt::join({1, 2, 3}, ", ")), "1, 2, 3"); + EXPECT_EQ(fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " ")), + "fmt rocks !"); +} + +struct zstring_sentinel {}; + +bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; } +bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; } + +struct zstring { + const char* p; + const char* begin() const { return p; } + zstring_sentinel end() const { return {}; } +}; + +TEST(ranges_test, join_sentinel) { + auto hello = zstring{"hello"}; + EXPECT_EQ(fmt::format("{}", hello), "['h', 'e', 'l', 'l', 'o']"); + EXPECT_EQ(fmt::format("{}", fmt::join(hello, "_")), "h_e_l_l_o"); +} + +TEST(ranges_test, join_range) { + noncopyable_range<int> w(3u, 0); + EXPECT_EQ(fmt::format("{}", fmt::join(w, ",")), "0,0,0"); + EXPECT_EQ(fmt::format("{}", fmt::join(noncopyable_range<int>(3u, 0), ",")), + "0,0,0"); + + non_const_only_range<int> x(3u, 0); + EXPECT_EQ(fmt::format("{}", fmt::join(x, ",")), "0,0,0"); + EXPECT_EQ(fmt::format("{}", fmt::join(non_const_only_range<int>(3u, 0), ",")), + "0,0,0"); + + auto y = std::vector<int>(3u, 0); + EXPECT_EQ(fmt::format("{}", fmt::join(y, ",")), "0,0,0"); + EXPECT_EQ(fmt::format("{}", fmt::join(std::vector<int>(3u, 0), ",")), + "0,0,0"); + + const auto z = std::vector<int>(3u, 0); + EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0"); +} +#endif // FMT_RANGES_TEST_ENABLE_JOIN diff --git a/libs/fmt/test/scan-test.cc b/libs/fmt/test/scan-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..7e327ea3f5fdcc25829f0bdca81971b08e04c60b --- /dev/null +++ b/libs/fmt/test/scan-test.cc @@ -0,0 +1,116 @@ +// Formatting library for C++ - scanning API test +// +// Copyright (c) 2019 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "scan.h" + +#include <time.h> + +#include <climits> + +#include "gmock/gmock.h" +#include "gtest-extra.h" + +TEST(scan_test, read_text) { + auto s = fmt::string_view("foo"); + auto end = fmt::scan(s, "foo"); + EXPECT_EQ(end, s.end()); + EXPECT_THROW_MSG(fmt::scan("fob", "foo"), fmt::format_error, "invalid input"); +} + +TEST(scan_test, read_int) { + auto n = int(); + fmt::scan("42", "{}", n); + EXPECT_EQ(n, 42); + fmt::scan("-42", "{}", n); + EXPECT_EQ(n, -42); +} + +TEST(scan_test, read_longlong) { + long long n = 0; + fmt::scan("42", "{}", n); + EXPECT_EQ(n, 42); + fmt::scan("-42", "{}", n); + EXPECT_EQ(n, -42); +} + +TEST(scan_test, read_uint) { + auto n = unsigned(); + fmt::scan("42", "{}", n); + EXPECT_EQ(n, 42); + EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error, + "invalid input"); +} + +TEST(scan_test, read_ulonglong) { + unsigned long long n = 0; + fmt::scan("42", "{}", n); + EXPECT_EQ(n, 42); + EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error, + "invalid input"); +} + +TEST(scan_test, read_string) { + auto s = std::string(); + fmt::scan("foo", "{}", s); + EXPECT_EQ(s, "foo"); +} + +TEST(scan_test, read_string_view) { + auto s = fmt::string_view(); + fmt::scan("foo", "{}", s); + EXPECT_EQ(s, "foo"); +} + +#ifndef _WIN32 +namespace fmt { +template <> struct scanner<tm> { + std::string format; + + scan_parse_context::iterator parse(scan_parse_context& ctx) { + auto it = ctx.begin(); + if (it != ctx.end() && *it == ':') ++it; + auto end = it; + while (end != ctx.end() && *end != '}') ++end; + format.reserve(detail::to_unsigned(end - it + 1)); + format.append(it, end); + format.push_back('\0'); + return end; + } + + template <class ScanContext> + typename ScanContext::iterator scan(tm& t, ScanContext& ctx) { + auto result = strptime(ctx.begin(), format.c_str(), &t); + if (!result) throw format_error("failed to parse time"); + return result; + } +}; +} // namespace fmt + +TEST(scan_test, read_custom) { + auto input = "Date: 1985-10-25"; + auto t = tm(); + fmt::scan(input, "Date: {0:%Y-%m-%d}", t); + EXPECT_EQ(t.tm_year, 85); + EXPECT_EQ(t.tm_mon, 9); + EXPECT_EQ(t.tm_mday, 25); +} +#endif + +TEST(scan_test, invalid_format) { + EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error, + "argument index out of range"); + EXPECT_THROW_MSG(fmt::scan("", "{"), fmt::format_error, + "invalid format string"); +} + +TEST(scan_test, example) { + auto key = std::string(); + auto value = int(); + fmt::scan("answer = 42", "{} = {}", key, value); + EXPECT_EQ(key, "answer"); + EXPECT_EQ(value, 42); +} diff --git a/libs/fmt/test/scan.h b/libs/fmt/test/scan.h new file mode 100644 index 0000000000000000000000000000000000000000..de82067a499577b6ff97c792b882ba2fde8ec3b1 --- /dev/null +++ b/libs/fmt/test/scan.h @@ -0,0 +1,238 @@ +// Formatting library for C++ - scanning API proof of concept +// +// Copyright (c) 2019 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include <array> +#include <cassert> +#include <climits> + +#include "fmt/format.h" + +FMT_BEGIN_NAMESPACE +template <typename T, typename Char = char> struct scanner { + // A deleted default constructor indicates a disabled scanner. + scanner() = delete; +}; + +class scan_parse_context { + private: + string_view format_; + + public: + using iterator = string_view::iterator; + + explicit FMT_CONSTEXPR scan_parse_context(string_view format) + : format_(format) {} + + FMT_CONSTEXPR iterator begin() const { return format_.begin(); } + FMT_CONSTEXPR iterator end() const { return format_.end(); } + + void advance_to(iterator it) { + format_.remove_prefix(detail::to_unsigned(it - begin())); + } +}; + +struct scan_context { + private: + string_view input_; + + public: + using iterator = const char*; + + explicit scan_context(string_view input) : input_(input) {} + + iterator begin() const { return input_.data(); } + iterator end() const { return begin() + input_.size(); } + + void advance_to(iterator it) { + input_.remove_prefix(detail::to_unsigned(it - begin())); + } +}; + +namespace detail { +enum class scan_type { + none_type, + int_type, + uint_type, + long_long_type, + ulong_long_type, + string_type, + string_view_type, + custom_type +}; + +struct custom_scan_arg { + void* value; + void (*scan)(void* arg, scan_parse_context& parse_ctx, scan_context& ctx); +}; + +class scan_arg { + public: + scan_type type; + union { + int* int_value; + unsigned* uint_value; + long long* long_long_value; + unsigned long long* ulong_long_value; + std::string* string; + fmt::string_view* string_view; + custom_scan_arg custom; + // TODO: more types + }; + + scan_arg() : type(scan_type::none_type) {} + scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {} + scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {} + scan_arg(long long& value) + : type(scan_type::long_long_type), long_long_value(&value) {} + scan_arg(unsigned long long& value) + : type(scan_type::ulong_long_type), ulong_long_value(&value) {} + scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {} + scan_arg(fmt::string_view& value) + : type(scan_type::string_view_type), string_view(&value) {} + template <typename T> scan_arg(T& value) : type(scan_type::custom_type) { + custom.value = &value; + custom.scan = scan_custom_arg<T>; + } + + private: + template <typename T> + static void scan_custom_arg(void* arg, scan_parse_context& parse_ctx, + scan_context& ctx) { + scanner<T> s; + parse_ctx.advance_to(s.parse(parse_ctx)); + ctx.advance_to(s.scan(*static_cast<T*>(arg), ctx)); + } +}; +} // namespace detail + +struct scan_args { + int size; + const detail::scan_arg* data; + + template <size_t N> + scan_args(const std::array<detail::scan_arg, N>& store) + : size(N), data(store.data()) { + static_assert(N < INT_MAX, "too many arguments"); + } +}; + +namespace detail { + +struct scan_handler : error_handler { + private: + scan_parse_context parse_ctx_; + scan_context scan_ctx_; + scan_args args_; + int next_arg_id_; + scan_arg arg_; + + template <typename T = unsigned> T read_uint() { + T value = 0; + auto it = scan_ctx_.begin(), end = scan_ctx_.end(); + while (it != end) { + char c = *it++; + if (c < '0' || c > '9') on_error("invalid input"); + // TODO: check overflow + value = value * 10 + static_cast<unsigned>(c - '0'); + } + scan_ctx_.advance_to(it); + return value; + } + + template <typename T = int> T read_int() { + auto it = scan_ctx_.begin(), end = scan_ctx_.end(); + bool negative = it != end && *it == '-'; + if (negative) ++it; + scan_ctx_.advance_to(it); + const auto value = read_uint<typename std::make_unsigned<T>::type>(); + if (negative) return -static_cast<T>(value); + return static_cast<T>(value); + } + + public: + scan_handler(string_view format, string_view input, scan_args args) + : parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {} + + const char* pos() const { return scan_ctx_.begin(); } + + void on_text(const char* begin, const char* end) { + auto size = to_unsigned(end - begin); + auto it = scan_ctx_.begin(); + if (it + size > scan_ctx_.end() || + !std::equal(begin, end, make_checked(it, size))) { + on_error("invalid input"); + } + scan_ctx_.advance_to(it + size); + } + + int on_arg_id() { return on_arg_id(next_arg_id_++); } + int on_arg_id(int id) { + if (id >= args_.size) on_error("argument index out of range"); + arg_ = args_.data[id]; + return id; + } + int on_arg_id(string_view) { return on_error("invalid format"), 0; } + + void on_replacement_field(int, const char*) { + auto it = scan_ctx_.begin(), end = scan_ctx_.end(); + switch (arg_.type) { + case scan_type::int_type: + *arg_.int_value = read_int(); + break; + case scan_type::uint_type: + *arg_.uint_value = read_uint(); + break; + case scan_type::long_long_type: + *arg_.long_long_value = read_int<long long>(); + break; + case scan_type::ulong_long_type: + *arg_.ulong_long_value = read_uint<unsigned long long>(); + break; + case scan_type::string_type: + while (it != end && *it != ' ') arg_.string->push_back(*it++); + scan_ctx_.advance_to(it); + break; + case scan_type::string_view_type: { + auto s = it; + while (it != end && *it != ' ') ++it; + *arg_.string_view = fmt::string_view(s, to_unsigned(it - s)); + scan_ctx_.advance_to(it); + break; + } + case scan_type::none_type: + case scan_type::custom_type: + assert(false); + } + } + + const char* on_format_specs(int, const char* begin, const char*) { + if (arg_.type != scan_type::custom_type) return begin; + parse_ctx_.advance_to(begin); + arg_.custom.scan(arg_.custom.value, parse_ctx_, scan_ctx_); + return parse_ctx_.begin(); + } +}; +} // namespace detail + +template <typename... Args> +std::array<detail::scan_arg, sizeof...(Args)> make_scan_args(Args&... args) { + return {{args...}}; +} + +string_view::iterator vscan(string_view input, string_view format_str, + scan_args args) { + detail::scan_handler h(format_str, input, args); + detail::parse_format_string<false>(format_str, h); + return input.begin() + (h.pos() - &*input.begin()); +} + +template <typename... Args> +string_view::iterator scan(string_view input, string_view format_str, + Args&... args) { + return vscan(input, format_str, make_scan_args(args...)); +} +FMT_END_NAMESPACE diff --git a/libs/fmt/test/std-format-test.cc b/libs/fmt/test/std-format-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..c67a2a0309230478d2f6246c103507d01f95cace --- /dev/null +++ b/libs/fmt/test/std-format-test.cc @@ -0,0 +1,161 @@ +#include <format> + +#include "gtest/gtest.h" + +TEST(std_format_test, escaping) { + using namespace std; + string s = format("{0}-{{", 8); // s == "8-{" + EXPECT_EQ(s, "8-{"); +} + +TEST(std_format_test, indexing) { + using namespace std; + string s0 = format("{} to {}", "a", "b"); // OK: automatic indexing + string s1 = format("{1} to {0}", "a", "b"); // OK: manual indexing + EXPECT_EQ(s0, "a to b"); + EXPECT_EQ(s1, "b to a"); + // Error: mixing automatic and manual indexing + EXPECT_THROW(string s2 = format("{0} to {}", "a", "b"), std::format_error); + // Error: mixing automatic and manual indexing + EXPECT_THROW(string s3 = format("{} to {1}", "a", "b"), std::format_error); +} + +TEST(std_format_test, alignment) { + using namespace std; + char c = 120; + string s0 = format("{:6}", 42); // s0 == " 42" + string s1 = format("{:6}", 'x'); // s1 == "x " + string s2 = format("{:*<6}", 'x'); // s2 == "x*****" + string s3 = format("{:*>6}", 'x'); // s3 == "*****x" + string s4 = format("{:*^6}", 'x'); // s4 == "**x***" + // Error: '=' with charT and no integer presentation type + EXPECT_THROW(string s5 = format("{:=6}", 'x'), std::format_error); + string s6 = format("{:6d}", c); // s6 == " 120" + string s7 = format("{:6}", true); // s9 == "true " + EXPECT_EQ(s0, " 42"); + EXPECT_EQ(s1, "x "); + EXPECT_EQ(s2, "x*****"); + EXPECT_EQ(s3, "*****x"); + EXPECT_EQ(s4, "**x***"); + EXPECT_EQ(s6, " 120"); + EXPECT_EQ(s7, "true "); +} + +TEST(std_format_test, float) { + using namespace std; + double inf = numeric_limits<double>::infinity(); + double nan = numeric_limits<double>::quiet_NaN(); + string s0 = format("{0:} {0:+} {0:-} {0: }", 1); // s0 == "1 +1 1 1" + string s1 = format("{0:} {0:+} {0:-} {0: }", -1); // s1 == "-1 -1 -1 -1" + string s2 = + format("{0:} {0:+} {0:-} {0: }", inf); // s2 == "inf +inf inf inf" + string s3 = + format("{0:} {0:+} {0:-} {0: }", nan); // s3 == "nan +nan nan nan" + EXPECT_EQ(s0, "1 +1 1 1"); + EXPECT_EQ(s1, "-1 -1 -1 -1"); + EXPECT_EQ(s2, "inf +inf inf inf"); + EXPECT_EQ(s3, "nan +nan nan nan"); +} + +TEST(std_format_test, int) { + using namespace std; + string s0 = format("{}", 42); // s0 == "42" + string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a" + string s2 = format("{0:#x} {0:#X}", 42); // s2 == "0x2a 0X2A" + string s3 = format("{:L}", 1234); // s3 == "1234" (depends on the locale) + EXPECT_EQ(s0, "42"); + EXPECT_EQ(s1, "101010 42 52 2a"); + EXPECT_EQ(s2, "0x2a 0X2A"); + EXPECT_EQ(s3, "1234"); +} + +#include <format> + +enum color { red, green, blue }; + +const char* color_names[] = {"red", "green", "blue"}; + +template <> struct std::formatter<color> : std::formatter<const char*> { + auto format(color c, format_context& ctx) { + return formatter<const char*>::format(color_names[c], ctx); + } +}; + +struct err {}; + +TEST(std_format_test, formatter) { + std::string s0 = std::format("{}", 42); // OK: library-provided formatter + // std::string s1 = std::format("{}", L"foo"); // Ill-formed: disabled + // formatter + std::string s2 = std::format("{}", red); // OK: user-provided formatter + // std::string s3 = std::format("{}", err{}); // Ill-formed: disabled + // formatter + EXPECT_EQ(s0, "42"); + EXPECT_EQ(s2, "red"); +} + +struct S { + int value; +}; + +template <> struct std::formatter<S> { + size_t width_arg_id = 0; + + // Parses a width argument id in the format { <digit> }. + constexpr auto parse(format_parse_context& ctx) { + constexpr auto is_ascii_digit = [](const char c) { + return c >= '0' && c <= '9'; + }; + + auto iter = ctx.begin(); + // auto get_char = [&]() { return iter != ctx.end() ? *iter : 0; }; + auto get_char = [&]() { return iter != ctx.end() ? *iter : '\0'; }; + if (get_char() != '{') return iter; + ++iter; + char c = get_char(); + if (!is_ascii_digit(c) || (++iter, get_char()) != '}') + throw format_error("invalid format"); + width_arg_id = fmt::detail::to_unsigned(c - '0'); + ctx.check_arg_id(width_arg_id); + return ++iter; + } + + // Formats S with width given by the argument width_arg_id. + auto format(S s, format_context& ctx) { + int width = visit_format_arg( + [](auto value) -> int { + using type = decltype(value); + if constexpr (!is_integral_v<type> || is_same_v<type, bool>) + throw format_error("width is not integral"); + // else if (value < 0 || value > numeric_limits<int>::max()) + else if (fmt::detail::is_negative(value) || + value > numeric_limits<int>::max()) + throw format_error("invalid width"); + else + return static_cast<int>(value); + }, + ctx.arg(width_arg_id)); + return format_to(ctx.out(), "{0:{1}}", s.value, width); + } +}; + +TEST(std_format_test, parsing) { + std::string s = std::format("{0:{1}}", S{42}, 10); // s == " 42" + EXPECT_EQ(s, " 42"); +} + +#if FMT_USE_INT128 +template <> struct std::formatter<__int128_t> : std::formatter<long long> { + auto format(__int128_t n, format_context& ctx) { + // Format as a long long since we only want to check if it is possible to + // specialize formatter for __int128_t. + return formatter<long long>::format(static_cast<long long>(n), ctx); + } +}; + +TEST(std_format_test, int128) { + __int128_t n = 42; + auto s = std::format("{}", n); + EXPECT_EQ(s, "42"); +} +#endif // FMT_USE_INT128 diff --git a/libs/fmt/test/test-assert.h b/libs/fmt/test/test-assert.h new file mode 100644 index 0000000000000000000000000000000000000000..cc7920dfe398f63e8aa60b05bf00ef020bc7f871 --- /dev/null +++ b/libs/fmt/test/test-assert.h @@ -0,0 +1,40 @@ +// Formatting library for C++ - test version of FMT_ASSERT +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_TEST_ASSERT_H_ +#define FMT_TEST_ASSERT_H_ + +#include <stdexcept> + +void throw_assertion_failure(const char* message); +#define FMT_ASSERT(condition, message) \ + if (!(condition)) throw_assertion_failure(message); + +#include "gtest-extra.h" +#include "gtest/gtest.h" + +class assertion_failure : public std::logic_error { + public: + explicit assertion_failure(const char* message) : std::logic_error(message) {} + + private: + virtual void avoid_weak_vtable(); +}; + +void assertion_failure::avoid_weak_vtable() {} + +// We use a separate function (rather than throw directly from FMT_ASSERT) to +// avoid GCC's -Wterminate warning when FMT_ASSERT is used in a destructor. +inline void throw_assertion_failure(const char* message) { + throw assertion_failure(message); +} + +// Expects an assertion failure. +#define EXPECT_ASSERT(stmt, message) \ + FMT_TEST_THROW_(stmt, assertion_failure, message, GTEST_NONFATAL_FAILURE_) + +#endif // FMT_TEST_ASSERT_H_ diff --git a/libs/fmt/test/test-main.cc b/libs/fmt/test/test-main.cc new file mode 100644 index 0000000000000000000000000000000000000000..b69cc5002c358b4d759ec8c9446cf7485deda009 --- /dev/null +++ b/libs/fmt/test/test-main.cc @@ -0,0 +1,43 @@ +// Formatting library for C++ - test main function. +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include <cstdlib> + +#include "gtest/gtest.h" + +#ifdef _WIN32 +# include <windows.h> +#endif + +#ifdef _MSC_VER +# include <crtdbg.h> +#else +# define _CrtSetReportFile(a, b) +# define _CrtSetReportMode(a, b) +#endif + +int main(int argc, char** argv) { +#ifdef _WIN32 + // Don't display any error dialogs. This also suppresses message boxes + // on assertion failures in MinGW where _set_error_mode/CrtSetReportMode + // doesn't help. + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | + SEM_NOOPENFILEERRORBOX); +#endif + // Disable message boxes on assertion failures. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + try { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); + } catch (...) { + // Catch all exceptions to make Coverity happy. + } + return EXIT_FAILURE; +} diff --git a/libs/fmt/test/util.cc b/libs/fmt/test/util.cc new file mode 100644 index 0000000000000000000000000000000000000000..6bdbe6137ceac447ea576d4bb6c5a6d99aba3b79 --- /dev/null +++ b/libs/fmt/test/util.cc @@ -0,0 +1,40 @@ +// Formatting library for C++ - test utilities +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "util.h" + +#include <cstring> + +std::string get_system_error(int error_code) { +#if defined(__MINGW32__) || !defined(_WIN32) + return strerror(error_code); +#else + enum { buffer_size = 200 }; + char buffer[buffer_size]; + if (strerror_s(buffer, buffer_size, error_code)) + throw std::exception("strerror_s failed"); + return buffer; +#endif +} + +const char* const file_content = "Don't panic!"; + +fmt::buffered_file open_buffered_file(FILE** fp) { +#if FMT_USE_FCNTL + fmt::file read_end, write_end; + fmt::file::pipe(read_end, write_end); + write_end.write(file_content, std::strlen(file_content)); + write_end.close(); + fmt::buffered_file f = read_end.fdopen("r"); + if (fp) *fp = f.get(); +#else + fmt::buffered_file f("test-file", "w"); + fputs(file_content, f.get()); + if (fp) *fp = f.get(); +#endif + return f; +} diff --git a/libs/fmt/test/util.h b/libs/fmt/test/util.h new file mode 100644 index 0000000000000000000000000000000000000000..92871cbd107e018a841001a47e01366fde2fa7e0 --- /dev/null +++ b/libs/fmt/test/util.h @@ -0,0 +1,79 @@ +// Formatting library for C++ - test utilities +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include <cstdarg> +#include <cstdio> +#include <string> + +#include "fmt/os.h" + +#ifdef _MSC_VER +# define FMT_VSNPRINTF vsprintf_s +#else +# define FMT_VSNPRINTF vsnprintf +#endif + +template <size_t SIZE> +void safe_sprintf(char (&buffer)[SIZE], const char* format, ...) { + std::va_list args; + va_start(args, format); + FMT_VSNPRINTF(buffer, SIZE, format, args); + va_end(args); +} + +std::string get_system_error(int error_code); + +extern const char* const file_content; + +// Opens a buffered file for reading. +fmt::buffered_file open_buffered_file(FILE** fp = nullptr); + +inline FILE* safe_fopen(const char* filename, const char* mode) { +#if defined(_WIN32) && !defined(__MINGW32__) + // Fix MSVC warning about "unsafe" fopen. + FILE* f = 0; + errno = fopen_s(&f, filename, mode); + return f; +#else + return std::fopen(filename, mode); +#endif +} + +template <typename Char> class basic_test_string { + private: + std::basic_string<Char> value_; + + static const Char empty[]; + + public: + explicit basic_test_string(const Char* value = empty) : value_(value) {} + + const std::basic_string<Char>& value() const { return value_; } +}; + +template <typename Char> const Char basic_test_string<Char>::empty[] = {0}; + +typedef basic_test_string<char> test_string; +typedef basic_test_string<wchar_t> test_wstring; + +template <typename Char> +std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& os, + const basic_test_string<Char>& s) { + os << s.value(); + return os; +} + +class date { + int year_, month_, day_; + + public: + date(int year, int month, int day) : year_(year), month_(month), day_(day) {} + + int year() const { return year_; } + int month() const { return month_; } + int day() const { return day_; } +}; diff --git a/libs/mavlink_skyward_lib b/libs/mavlink_skyward_lib index 00a22f01347688d08464eafcbb86998a0690a8f8..d7c4c0179f0cde99b3346181b51c6cd1a95747a8 160000 --- a/libs/mavlink_skyward_lib +++ b/libs/mavlink_skyward_lib @@ -1 +1 @@ -Subproject commit 00a22f01347688d08464eafcbb86998a0690a8f8 +Subproject commit d7c4c0179f0cde99b3346181b51c6cd1a95747a8 diff --git a/libs/miosix-kernel b/libs/miosix-kernel index a34d3827097db99973d5119c5a0c817c2d7b306c..a87c0b76db9d11e099e8ebf040b405b24ff52e63 160000 --- a/libs/miosix-kernel +++ b/libs/miosix-kernel @@ -1 +1 @@ -Subproject commit a34d3827097db99973d5119c5a0c817c2d7b306c +Subproject commit a87c0b76db9d11e099e8ebf040b405b24ff52e63 diff --git a/libs/mxgui b/libs/mxgui new file mode 160000 index 0000000000000000000000000000000000000000..cab17f6185da95502d20dd31b17fef67e06ca6ef --- /dev/null +++ b/libs/mxgui @@ -0,0 +1 @@ +Subproject commit cab17f6185da95502d20dd31b17fef67e06ca6ef diff --git a/sbs b/sbs index d159ba3a51bb38c2280f2f0853a1aa75da16d187..2e3243ea1fd30d96522a5ff5087ecee367c2846e 100755 --- a/sbs +++ b/sbs @@ -17,7 +17,7 @@ # # 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 +# 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 @@ -48,9 +48,8 @@ except ImportError: # Global variables # srcfiles={} +extlibs={} mainfiles={} -tests={} -entrypoints={} colors=False # Map for the project configuration, filled in when parsing sbs.conf @@ -154,27 +153,31 @@ def parseConf(path): projconf[i] = conf.get('SBS_PROJECT', i) elif stype == 'srcfiles': srcfiles[i] = [x.strip() for x in conf.get(i, 'Files').split("\n")] - elif stype == 'board': - entrypoints[i] = { 'id': conf.get(i, 'BoardId'), - 'bin': conf.get(i, 'BinName'), - 'defines': conf.get(i, 'Defines'), - 'files': [entrypoint_mask % conf.get(i, 'Main').strip()] + - [x.strip() for x in conf.get(i, 'Include').split(' ')] - } - elif stype == 'test': - tests[i] = { 'id': conf.get(i, 'BoardId'), + elif stype == 'library': + extlibs[i] = {'subdir': conf.get(i, 'Subdir'), + 'libs': [x.strip() for x in conf.get(i, 'Libs').split()], + 'defines': conf.get(i, 'Defines').strip(), + 'includes': [x.strip() for x in conf.get(i, 'Includes').split()]} + elif stype == 'board' or stype == 'test': + if stype == 'test': + mask = test_mask + add_def = " -I" + projconf['TESTS_PATH'] + else: + mask = entrypoint_mask + add_def = "" + + mainfiles[i] = {'type': stype, + 'id': conf.get(i, 'BoardId').strip(), 'bin': conf.get(i, 'BinName'), - 'defines': conf.get(i, 'Defines') + " -I" + projconf['TESTS_PATH'], - 'files': [test_mask % conf.get(i, 'Main').strip()] + + 'defines': conf.get(i, 'Defines') + add_def, + 'libs': [x.strip() for x in conf.get(i, 'Libs', fallback="").split()], + 'files': [mask % conf.get(i, 'Main').strip()] + [x.strip() for x in conf.get(i, 'Include').split(' ')] } else: print('[SBS] Syntax error in %s: type %s not implemented' % (path, stype)) exit(1) - # Join tests and entrypoints in mainfiles - mainfiles.update(entrypoints) - mainfiles.update(tests) # Exit if nothing was found if (len(mainfiles) == 0 ): @@ -184,6 +187,7 @@ def parseConf(path): # Import also srcfiles from boardcore if executing from another folders if projconf['SBS_BASE'] != '.': importSrcFiles(projconf['SBS_BASE'] + "/sbs.conf") + importLibs(projconf['SBS_BASE'] + "/sbs.conf") # Substitute includes for i in mainfiles: @@ -194,6 +198,13 @@ def parseConf(path): else: files += [j] mainfiles[i]['files'] = files + + # Substitute libs + for i in mainfiles: + lib_dicts = {} + for j in mainfiles[i]['libs']: + lib_dicts[j] = extlibs[j] + mainfiles[i]['libs'] = lib_dicts # # Create a Makefile for a specific board starting from Makefile.template @@ -208,7 +219,11 @@ def build_makefile(template, board, bname): "MAP_FILE": "export MAIN_MAP_FILE := bin/%s.map\n" % board['bin'], "SOURCE_FILES": "%s\n" % (" ".join(board['files'])), "CUSTOM_DEFINES": board['defines'], - "BIN_NAME": board['bin'] + "BIN_NAME": board['bin'], + "LIB_SUBDIR": " ".join([x['subdir'] for _,x in board['libs'].items()]), + "LIB_LIBS": " ".join([" ".join(x['libs']) for _,x in board['libs'].items()]), + "LIB_INCLUDES": " ".join([" ".join([ "-I" + s for s in x['includes']]) for _,x in board['libs'].items()]), + "LIB_DEFINES": " ".join(x['defines'] for _,x in board['libs'].items()) } rmap["PROJECT_INCLUDES"] = "-I" + projconf["SRC_PATH"].strip() + " " @@ -255,7 +270,25 @@ def importSrcFiles(path): else: print('[SBS] Syntax error in %s: a srcfiles group named %s is already defined. Terminating' % (path, i)) exit(1) +# +# Import libraries groups from another .conf file. +# Libraries are added only if there's not a group already with the same name. +# +def importLibs(path): + conf = cp.RawConfigParser() + conf.read(path) + for i in conf.sections(): + stype = conf.get(i, 'Type') + if stype == 'library': + if i not in extlibs: + extlibs[i] = {'subdir': '' if conf.get(i, 'Subdir') == '' else projconf['SBS_BASE'] + "/" + conf.get(i, 'Subdir'), + 'libs': [projconf['SBS_BASE'] + "/" + x.strip() for x in conf.get(i, 'Libs').split()], + 'defines': conf.get(i, 'Defines').strip(), + 'includes': [projconf['SBS_BASE'] + "/" + x.strip() for x in conf.get(i, 'Includes').split()]} + else: + print('[SBS] Syntax error in %s: a library group named %s is already defined. Terminating' % (path, i)) + exit(1) ##------------------------------------------------------------- ## MAIN @@ -297,14 +330,17 @@ if options.lint == True: # List if options.list == True: print('[SBS] List of available entrypoints:') - for entrypoint in entrypoints: - print(entrypoint) + for main in mainfiles: + if(mainfiles[main]['type'] == 'board'): + print(main) print('\n[SBS] List of available tests:') - for test in tests: - print(test) + for main in mainfiles: + if(mainfiles[main]['type'] == 'test'): + print(main) exit(0) # Set how many main files to build: +files = list(mainfiles.keys()).copy() if options.board != None: # just one try: good = mainfiles[options.board] @@ -313,9 +349,13 @@ if options.board != None: # just one print('[SBS] No entrypoint or test named %s. Terminating.' % (options.board)) sys.exit(2) elif options.all_entry == True: # all entrypoints - mainfiles = entrypoints + for main in files: + if(mainfiles[main]['type'] == 'test'): + del mainfiles[main] elif options.all_tests == True: # all tests - mainfiles = tests + for main in files: + if(mainfiles[main]['type'] == 'board'): + del mainfiles[main] # Set action diff --git a/sbs.conf b/sbs.conf index 7d3af427a1afe1322773e14d5bfc34159341539d..cfaf4af5ed85e8b0ca9d53c8b400f91693663463 100644 --- a/sbs.conf +++ b/sbs.conf @@ -15,7 +15,7 @@ # # 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 +# 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 @@ -81,39 +81,90 @@ PROJECT_INCLUDES: PROJECT_SUBDIRS: PROJECT_LIBS: +#----------------------------------------------# +# List of External Libraries # +#----------------------------------------------# +# [name] -> can be included using 'name' +# Type: library +# Subdir: Subdirectory containing the library makefile +# Libs: Static libraries to link against (whitespace-separated) +# Includes: List of additional include directories (whitespace-separated) +[libmxgui] +Type: library +Subdir: libs/mxgui +Libs: libs/mxgui/libmxgui.a +Defines: +Includes: libs/mxgui + +[eigen] +Type: library +Subdir: +Libs: +Defines: -DEIGEN_MAX_ALIGN_BYTES=0 +Includes: libs/eigen + +[fmt] +Type: library +Subdir: libs/fmt/ +Libs: libs/fmt/libfmt.a +Defines: -DFMT_STATIC_THOUSANDS_SEPARATOR -D_GLIBCXX_USE_WCHAR_T +Includes: libs/fmt/include + +[fmt-header-only] +Type: library +Subdir: +Libs: +Defines: +Includes: libs/fmt/include + #-----------------------------------# # List of Sources # #-----------------------------------# # [name] -> can be included using '%name' # Type: srcfiles -# Files: a '/n'-separated list of f - -[canbus] -Type: srcfiles -Files: src/shared/drivers/canbus/CanManager.cpp - src/shared/drivers/canbus/CanBus.cpp - src/shared/drivers/canbus/CanInterrupt.cpp - -[piksi] -Type: srcfiles -Files: src/shared/drivers/piksi/piksi.cpp +# Files: a '/n'-separated list of source files [shared] Type: srcfiles Files: src/shared/scheduler/TaskScheduler.cpp - src/shared/drivers/spi/SensorSpi.cpp src/shared/diagnostic/CpuMeter.cpp src/shared/events/EventBroker.cpp src/shared/math/Stats.cpp - src/shared/math/Matrix.cpp src/shared/drivers/interrupt/external_interrupts.cpp src/shared/utils/aero/AeroUtils.cpp src/shared/Debug.cpp + src/shared/TimestampTimer.cpp + src/shared/diagnostic/PrintLogger.cpp + src/shared/logger/Logger.cpp + libs/tscpp/buffer.cpp + libs/tscpp/stream.cpp + +[ubloxgps] +Type: srcfiles +Files: src/shared/drivers/gps/ublox/UbloxGPS.cpp + +[canbus] +Type: srcfiles +Files: src/shared/drivers/canbus/CanManager.cpp + src/shared/drivers/canbus/CanBus.cpp + src/shared/drivers/canbus/CanInterrupt.cpp + +[calibration] +Type: srcfiles +Files: src/shared/sensors/calibration/SensorDataExtra.cpp + +[piksi] +Type: srcfiles +Files: src/shared/drivers/old_examples/piksi/piksi.cpp [pwm] Type: srcfiles Files: src/shared/drivers/pwm/pwm.cpp +[spi] +Type: srcfiles +Files: src/shared/drivers/spi/SPITransaction.cpp + [i2c] Type: srcfiles Files: src/shared/drivers/i2c/stm32f2_f4_i2c.cpp @@ -131,11 +182,15 @@ Type: srcfiles Files: src/shared/boards/AnakinBoard.cpp src/shared/drivers/Leds.cpp -[logger] +[sensormanager] Type: srcfiles -Files: src/shared/logger/Logger.cpp - libs/tscpp/buffer.cpp - libs/tscpp/stream.cpp +Files: src/shared/sensors/SensorManager.cpp + src/shared/sensors/SensorSampler.cpp + +[math] +Type: srcfiles +Files: src/shared/math/SkyQuaternion.cpp + src/shared/math/Matrix.cpp [gamma868] Type: srcfiles @@ -143,7 +198,8 @@ Files: src/shared/drivers/gamma868/Gamma868.cpp [xbee] Type: srcfiles -Files: src/shared/drivers/Xbee/Xbee.cpp +Files: src/shared/drivers/Xbee/APIFrameParser.cpp + src/shared/drivers/Xbee/Xbee.cpp [events] Type: srcfiles @@ -153,18 +209,57 @@ Files: src/shared/events/EventBroker.cpp Type: srcfiles Files: src/shared/drivers/servo/servo.cpp +[hbridge] +Type: srcfiles +Files: src/shared/drivers/hbridge/HBridge.cpp + [test-utils] Type: srcfiles Files: src/shared/utils/testutils/TestHelper.cpp -[tests] +[tests-boardcore] Type: srcfiles -Files: src/tests/catch/test-eventbroker.cpp - src/tests/catch/test-circularbuffer.cpp - src/tests/catch/test-aero.cpp +Files: src/tests/catch/test-aero.cpp src/tests/catch/test-buttonhandler.cpp + src/tests/catch/test-circularbuffer.cpp + src/tests/catch/test-eventbroker.cpp + src/tests/catch/test-sensormanager-catch.cpp + src/tests/catch/test-hardwaretimer.cpp + src/tests/catch/test-kalman.cpp + #src/tests/catch/test-kalman-eigen.cpp + src/tests/catch/test-matrix.cpp src/tests/catch/test-packetqueue.cpp + src/tests/catch/spidriver/test-spidriver + src/tests/catch/xbee/test-xbee-parser.cpp + src/tests/catch/xbee/test-xbee-driver.cpp + +[internal-adc] +Type: srcfiles +Files: src/shared/drivers/adc/InternalADC/InternalADC.cpp +[ads1118] +Type: srcfiles +Files: src/shared/drivers/adc/ADS1118/ADS1118.cpp + +[mpu9250] +Type: srcfiles +Files: src/shared/sensors/MPU9250/MPU9250.cpp + +[bme280] +Type: srcfiles +Files: src/shared/sensors/BME280/BME280.cpp + +[bmx160] +Type: srcfiles +Files: src/shared/sensors/BMX160/BMX160.cpp + +[bmx160withcorrection] +Type: srcfiles +Files: src/shared/sensors/BMX160/BMX160WithCorrection.cpp + +[ms5803] +Type: srcfiles +Files: src/shared/sensors/MS5803/MS5803.cpp #-------------------------------# # Entrypoints # @@ -180,8 +275,9 @@ Files: src/tests/catch/test-eventbroker.cpp Type: board BoardId: stm32f429zi_stm32f4discovery BinName: dsgamma-config -Include: %gamma868 +Include: %shared %gamma868 Defines: +Libs: fmt Main: config-dsgamma [kernel-testsuite] @@ -192,6 +288,32 @@ Include: Defines: Main: kernel-testsuite +[mxgui-helloworld] +Type: board +BoardId: stm32f429zi_stm32f4discovery +BinName: mxgui-helloworld +Include: +Defines: -DDEBUG +Libs: libmxgui +Main: examples/mxgui-helloworld + +[sdcard-benchmark] +Type: board +BoardId: stm32f429zi_stm32f4discovery +BinName: sdcard-benchmark +Include: %shared +Defines: +Libs: fmt +Main: sdcard-benchmark + +[bmx160-calibration-entry] +Type: board +BoardId: stm32f429zi_skyward_death_stack_x +BinName: bmx160-calibration-entry +Libs: eigen fmt +Include: %shared %spi %bmx160 %bmx160withcorrection %calibration +Defines: +Main: bmx160-calibration-entry #--------------------------# # Tests # @@ -207,59 +329,110 @@ Main: kernel-testsuite [tests-catch] Type: test -BoardId: stm32f429zi_skyward_homeone +BoardId: stm32f429zi_stm32f4discovery BinName: tests-catch -Include: %tests %shared %test-utils -Defines: +Include: %tests-boardcore %shared %test-utils %spi %sensormanager %xbee +Defines: -DUSE_MOCK_PERIPHERALS +Libs: fmt eigen Main: catch/catch-tests-entry +# [test-kalman] +# Type: test +# BoardId: stm32f429zi_stm32f4discovery +# BinName: test-kalman +# Include: %shared +# Defines: -DDEBUG -DSTANDALONE_CATCH1_TEST +# Libs: fmt +# Main: catch/test-kalman + +# [test-kalman-eigen] +# Type: test +# BoardId: stm32f429zi_stm32f4discovery +# BinName: test-kalman-eigen +# Include: %shared +# Defines: -DDEBUG -DSTANDALONE_CATCH1_TEST +# Libs: eigen fmt +# Main: catch/test-kalman-eigen + [test-taskscheduler] Type: test -BoardId: stm32f429zi_stm32f4discovery +BoardId: stm32f429zi_skyward_death_stack_x BinName: test-taskscheduler Include: %shared Defines: +Libs: fmt Main: test-taskscheduler [test-serial] Type: test -BoardId: stm32f429zi_stm32f4discovery +BoardId: stm32f429zi_skyward_death_stack_x BinName: test-serial -Include: -Defines: +Include: %shared +Defines: +Libs: fmt Main: test-serial +[test-trace-logger] +Type: test +BoardId: stm32f429zi_stm32f4discovery +BinName: test-trace-logger +Include: %shared +Defines: -DDEBUG +Libs: fmt +Main: test-trace-logger + [test-hsm] Type: test BoardId: stm32f429zi_stm32f4discovery BinName: test-hsm Include: %shared Defines: -DDEBUG +Libs: fmt Main: test-hsm [test-interrupt] Type: test BoardId: stm32f429zi_stm32f4discovery BinName: test-interrupt -Include: +Include: %shared Defines: -DDEBUG +Libs: fmt Main: test-interrupt +[test-timestamp-timer] +Type: test +BoardId: stm32f429zi_stm32f4discovery +BinName: test-timestamp-timer +Include: %shared +Defines: -DDEBUG +Libs: fmt +Main: test-timestamp-timer + [test-logger] Type: test BoardId: stm32f429zi_stm32f4discovery BinName: test-logger -Include: %shared %logger +Include: %shared Defines: +Libs: fmt Main: logger/test-logger -[test-kalman-benchmark] -Type: test -BoardId: stm32f429zi_stm32f4discovery -BinName: test-kalman-benchmark -Include: -Defines: -Main: kalman/test-kalman-benchmark +# [test-kalman-benchmark] +# Type: test +# BoardId: stm32f429zi_stm32f4discovery +# BinName: test-kalman-benchmark +# Include: +# Defines: +# Main: kalman/test-kalman-benchmark + +# [test-kalman-eigen-benchmark] +# Type: test +# BoardId: stm32f429zi_stm32f4discovery +# BinName: test-kalman-eigen-benchmark +# Include: %shared +# Defines: +# Libs: eigen fmt +# Main: kalman/test-kalman-eigen-benchmark [test-pinobserver] Type: test @@ -267,48 +440,72 @@ BoardId: stm32f429zi_stm32f4discovery BinName: test-pinobserver Include: %shared Defines: +Libs: fmt Main: test-pinobserver -## Drivers +# [test-sensormanager-catch] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack_x +# BinName: test-sensormanager-catch +# Include: %shared %sensormanager +# Defines: -DSTANDALONE_CATCH1_TEST +# Libs: fmt +# Main: catch/test-sensormanager-catch -[test-dsgamma] +[test-sensormanager] Type: test -BoardId: stm32f429zi_stm32f4discovery -BinName: test-dsgamma -Include: %gamma868 -Defines: -Main: drivers/test-dsgamma +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-sensormanager +Include: %shared %sensormanager +Defines: -DDEBUG +Libs: fmt +Main: test-sensormanager -[test-imu-adis] +[test-calibration] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: test-imu-adis -Include: %shared -Defines: -Main: drivers/test-imu-adis +BoardId: stm32f407vg_stm32f4discovery +BinName: test-calibration +Include: %shared %spi %calibration +Defines: -DDEBUG +Libs: eigen fmt +Main: calibration/test-calibration + +# [test-calibration-benchmark] +# Type: test +# BoardId: stm32f407vg_stm32f4discovery +# BinName: test-calibration-benchmark +# Include: %shared %spi %calibration +# Defines: -DDEBUG +# Libs: eigen fmt +# Main: calibration/test-calibration-benchmark + +# [test-calibration-stats] +# Type: test +# BoardId: stm32f407vg_stm32f4discovery +# BinName: test-calibration-stats +# Include: %shared %spi %calibration +# Defines: -DDEBUG +# Libs: eigen fmt +# Main: calibration/test-calibration-stats -[test-mpu9250] -Type: test -BoardId: stm32f429zi_skyward_homeone -BinName: test-mpu9250 -Include: %shared -Defines: -Main: drivers/test-mpu9250 +## Drivers -[test-timer] +[test-dsgamma] Type: test BoardId: stm32f429zi_stm32f4discovery -BinName: test-timer -Include: %shared -Defines: -DDEBUG -Main: drivers/test-timer +BinName: test-dsgamma +Include: %shared %gamma868 +Defines: +Libs: fmt +Main: drivers/test-dsgamma [test-canbus] Type: test -BoardId: stm32f429zi_stm32f4discovery +BoardId: stm32f429zi_skyward_death_stack_x BinName: test-canbus Include: %shared %canbus Defines: -DDEBUG +Libs: fmt Main: drivers/test-canbus [test-pwm] @@ -317,31 +514,17 @@ BoardId: stm32f429zi_stm32f4discovery BinName: test-pwm Include: %shared %pwm Defines: -DDEBUG +Libs: fmt Main: drivers/test-pwm -[test-piksi] +[test-lis3mdl] Type: test -BoardId: stm32f429zi_skyward_homeone -BinName: test-piksi -Include: %shared %piksi +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-lis3mdl +Include: %shared %spi Defines: -DDEBUG -Main: drivers/test-piksi - -[test-IMU] -Type: test -BoardId: stm32f429zi_stm32f4discovery -BinName: test-IMU -Include: %shared -Defines: -Main: drivers/test-IMU - -#[test-mavlink-multi] -#Type: test -#BoardId: stm32f429zi_skyward_homeone -#BinName: test-mavlink-multi -#Include: %shared %gamma868 -#Defines: -DDEBUG -#Main: drivers/test-mavlink-multi +Libs: fmt +Main: drivers/test-lis3mdl [test-mavlink] Type: test @@ -349,124 +532,353 @@ BoardId: stm32f429zi_skyward_homeone BinName: test-mavlink Include: %shared %gamma868 Defines: -DDEBUG +Libs: fmt Main: drivers/test-mavlink -[xbee-bitrate] +# [xbee-bitrate] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: xbee-bitrate +# Include: %shared %xbee %spi +# Defines: -DDEBUG -DSDRAM_ISSI +# Main: misc/xbee-bitrate + +# [xbee-send-rcv] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: xbee-send-rcv +# Include: %shared %xbee %spi +# Defines: -DDEBUG +# Main: misc/xbee-send-rcv + +# [xbee-time-to-send] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: xbee-time-to-send +# Include: %shared %xbee %spi +# Defines: -DDEBUG +# Main: misc/xbee-time-to-send + +# [test-circularbuffer] +# Type: test +# BoardId: stm32f429zi_stm32f4discovery +# BinName: test-circularbuffer +# Include: %shared %test-utils +# Defines: -DSTANDALONE_CATCH1_TEST +# Libs: fmt +# Main: catch/test-circularbuffer + +# [test-eventbroker] +# Type: test +# BoardId: stm32f429zi_stm32f4discovery +# BinName: test-eventbroker +# Include: %shared %test-utils +# Defines: -DSTANDALONE_CATCH1_TEST +# Libs: fmt +# Main: catch/test-eventbroker + +[test-servo] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: xbee-bitrate -Include: %shared %xbee -Defines: -DDEBUG -DSDRAM_ISSI -Main: misc/xbee-bitrate +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-servo +Include: %shared %servo %pwm +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-servo -[xbee-send-rcv] +[test-hbridge] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: xbee-send-rcv -Include: %shared %xbee +BoardId: stm32f429zi_stm32f4discovery +BinName: test-hbridge +Include: %shared %hbridge %pwm +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-hbridge + +[test-ms5803] +Type: test +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-ms5803 +Include: %shared %spi %sensormanager %ms5803 Defines: -DDEBUG -Main: misc/xbee-send-rcv +Libs: fmt +Main: drivers/test-ms5803 -[xbee-time-to-send] +[test-ublox] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: xbee-time-to-send -Include: %shared %xbee +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-ublox +Include: %ubloxgps %shared Defines: -DDEBUG -Main: misc/xbee-time-to-send +Libs: fmt +Main: drivers/test-ublox + +[test-l3gd20] +Type: test +BoardId: stm32f429zi_stm32f4discovery +BinName: test-l3gd20 +Include: %shared %spi +Defines: +Libs: fmt +Main: drivers/test-l3gd20 + +[test-l3gd20-fifo] +Type: test +BoardId: stm32f429zi_stm32f4discovery +BinName: test-l3gd20-fifo +Include: %shared %spi +Defines: +Libs: fmt +Main: drivers/test-l3gd20-fifo -[test-ad7994] +[test-rls] Type: test BoardId: stm32f429zi_skyward_death_stack -BinName: test-ad7994 +BinName: test-rls Include: %shared Defines: -Main: drivers/test-ad7994 +Libs: fmt +Main: test-rls -[test-matrix] +[test-lis3dsh] +Type: test +BoardId: stm32f407vg_stm32f4discovery +BinName: test-lis3dsh +Include: %shared %spi +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-lis3dsh + +[test-bmx160] +Type: test +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-bmx160 +Include: %shared %spi %bmx160 +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-bmx160 + +[test-bmx160-with-correction] +Type: test +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-bmx160-with-correction +Libs: eigen fmt +Include: %shared %spi %bmx160 %bmx160withcorrection %calibration +Defines: -DDEBUG +Main: drivers/test-bmx160-with-correction + +[test-ads1118] +Type: test +BoardId: stm32f407vg_stm32f4discovery +BinName: test-ads1118 +Include: %shared %spi %ads1118 +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-ads1118 + +[test-xbee-snd] Type: test BoardId: stm32f429zi_stm32f4discovery -BinName: test-matrix -Include: %shared -Defines: -DSTANDALONE_CATCH1_TEST -Main: catch/test-matrix +BinName: test-xbee-snd +Include: %shared %spi %xbee +Defines: -DDEBUG +Libs: fmt +Main: drivers/xbee/test-xbee-snd -[test-kalman] +[test-xbee-rcv] Type: test BoardId: stm32f429zi_stm32f4discovery -BinName: test-kalman -Include: %shared -Defines: -DSTANDALONE_CATCH1_TEST -Main: catch/test-kalman +BinName: test-xbee-rcv +Include: %shared %spi %xbee +Defines: -DDEBUG +Libs: fmt +Main: drivers/xbee/test-xbee-rcv -#[test-eventbroker] -#Type: test -#BoardId: stm32f429zi_stm32f4discovery -#BinName: test-eventbroker -#Include: %shared %events -#Defines: -DSTANDALONE_CATCH1_TEST -#Main: catch/test-eventbroker +[test-xbee-bidir] +Type: test +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-xbee-bidir +Include: %shared %spi %xbee +Defines: -DDEBUG +Libs: fmt +Main: drivers/xbee/test-xbee-bidir -[test-circularbuffer] +[test-xbee-gui] Type: test BoardId: stm32f429zi_stm32f4discovery -BinName: test-circularbuffer -Include: %shared -Defines: -DSTANDALONE_CATCH1_TEST -Main: catch/test-circularbuffer +BinName: test-xbee-gui +Include: %shared %spi %xbee src/tests/drivers/xbee/gui/res/respect.cpp +Defines: -DDEBUG +Libs: libmxgui fmt +Main: drivers/xbee/test-xbee-gui -[test-tempSensor] +[test-bme280] Type: test -BoardId: stm32f429zi_skyward_homeone -BinName: test-tempSensor -Include: %shared +BoardId: stm32f407vg_stm32f4discovery +BinName: test-bme280 +Include: %shared %spi %bme280 Defines: -DDEBUG -Main: drivers/test-tempSensor +Libs: fmt +Main: drivers/test-bme280 -[test-spi2] +[test-internal-adc] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: test-spi2 -Include: %shared -Defines: -DDEBUG -DSDRAM_ISSI -Main: drivers/test-spi2 +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-internal-adc +Include: %shared %internal-adc +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-internal-adc -[test-i2c-mpu9255] +[test-internal-adc-dma] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: test-i2c-mpu9255 -Include: %shared -Defines: -DDEBUG -DSDRAM_ISSI -Main: drivers/test-i2c-mpu9255 +BoardId: stm32f429zi_stm32f4discovery +BinName: test-internal-adc-dma +Include: %shared %internal-adc +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-internal-adc-dma -[test-ad7994-bare] +[test-analog-pressure-sensors] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: test-ad7994-bare -Include: %shared -Defines: -DDEBUG -DSDRAM_ISSI -Main: drivers/test-ad7994-bare +BoardId: stm32f429zi_stm32f4discovery +BinName: test-analog-pressure-sensors +Include: %shared %spi %ads1118 +Defines: -DDEBUG +Libs: fmt +Main: drivers/analog/test-analog-pressure-sensors -[test-servo] +[test-battery-voltage] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: test-servo -Include: %shared %servo %pwm -Defines: -DDEBUG -Main: drivers/test-servo +BoardId: stm32f429zi_stm32f4discovery +BinName: test-battery-voltage +Include: %shared %internal-adc +Defines: -DDEBUG +Libs: fmt +Main: drivers/analog/test-battery-voltage -[calibrate-mpu9250] +[test-current-sensor] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: calibrate-mpu9250 -Include: %shared -Defines: -Main: drivers/calibrate-mpu9250 +BoardId: stm32f429zi_stm32f4discovery +BinName: test-current-sensor +Include: %shared %internal-adc +Defines: -DDEBUG +Libs: fmt +Main: drivers/analog/test-current-sensor -[test-ms5803] +[test-spi] Type: test -BoardId: stm32f429zi_skyward_death_stack -BinName: test-ms5803 +BoardId: stm32f429zi_stm32f4discovery +BinName: test-spi +Include: %shared %spi +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-spi + +[test-hwtimer-chain] +Type: test +BoardId: stm32f407vg_stm32f4discovery +BinName: test-hwtimer-chain Include: %shared -Defines: -Main: drivers/test-ms5803 +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-hwtimer-chain + +[test-mpu9250] +Type: test +BoardId: stm32f429zi_skyward_death_stack_x +BinName: test-mpu9250 +Include: %shared %spi %mpu9250 +Defines: -DDEBUG +Libs: fmt +Main: drivers/test-mpu9250 + +## Older drivers (not up-to-date) + +# [test-piksi] +# Type: test +# BoardId: stm32f429zi_skyward_homeone +# BinName: test-piksi +# Include: %shared %piksi +# Defines: -DDEBUG +# Main: drivers/test-piksi + +# [test-imu-adis] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: test-imu-adis +# Include: %shared %sensormanager +# Defines: +# Main: drivers/old_examples/test-imu-adis + +# [test-lsm] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: test-lsm +# Include: %shared %sensormanager +# Defines: +# Main: drivers/old_examples/test-lsm + +# [test-IMU] +# Type: test +# BoardId: stm32f429zi_stm32f4discovery +# BinName: test-IMU +# Include: %shared +# Defines: +# Main: drivers/old_examples/test-IMU + +# [test-mavlink-multi] +# Type: test +# BoardId: stm32f429zi_skyward_homeone +# BinName: test-mavlink-multi +# Include: %shared %gamma868 +# Defines: -DDEBUG +# Main: drivers/test-mavlink-multi + +# [test-ad7994] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: test-ad7994 +# Include: %shared +# Defines: +# Main: drivers/old_examples/test-ad7994 + +# [test-tempSensor] +# Type: test +# BoardId: stm32f429zi_skyward_homeone +# BinName: test-tempSensor +# Include: %shared +# Defines: -DDEBUG +# Main: drivers/old_examples/test-tempSensor + +# [test-spi2] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: test-spi2 +# Include: %shared +# Defines: -DDEBUG -DSDRAM_ISSI +# Main: drivers/old_examples/test-spi2 + +# [test-i2c-mpu9255] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: test-i2c-mpu9255 +# Include: %shared +# Defines: -DDEBUG -DSDRAM_ISSI +# Main: drivers/test-i2c-mpu9255 + +# [test-ad7994-bare] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: test-ad7994-bare +# Include: %shared +# Defines: -DDEBUG -DSDRAM_ISSI +# Main: drivers/test-ad7994-bare + +# [calibrate-mpu9250] +# Type: test +# BoardId: stm32f429zi_skyward_death_stack +# BinName: calibrate-mpu9250 +# Include: %shared %sensormanager +# Defines: +# Main: drivers/calibrate-mpu9250 diff --git a/scripts/anakin.sh b/scripts/anakin.sh deleted file mode 100755 index 52d32b6107f171f011925e0da0f9f215ff9caf98..0000000000000000000000000000000000000000 --- a/scripts/anakin.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -if [ $# -ne 1 ]; then - echo "Usage: $0 <filename.bin>" - exit -1 -fi; - -stm32flash -b 500000 -w $1 -v /dev/ttyUSB0 diff --git a/scripts/anakin_client_curses/.gitignore b/scripts/anakin_client_curses/.gitignore deleted file mode 100644 index b051c6c57fa8b6cc588a3ebad860005ec6fcf7cc..0000000000000000000000000000000000000000 --- a/scripts/anakin_client_curses/.gitignore +++ /dev/null @@ -1 +0,0 @@ -client diff --git a/scripts/anakin_client_curses/Makefile b/scripts/anakin_client_curses/Makefile deleted file mode 100644 index f97ef085b53c809ca0363276bbae66fbf22e6ded..0000000000000000000000000000000000000000 --- a/scripts/anakin_client_curses/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -all: - gcc -o client client.c -lcurses diff --git a/scripts/anakin_client_curses/client.c b/scripts/anakin_client_curses/client.c deleted file mode 100644 index 5c45227bddf8b8aa5ced0341a9cb56e0ce624d63..0000000000000000000000000000000000000000 --- a/scripts/anakin_client_curses/client.c +++ /dev/null @@ -1,319 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <fcntl.h> -#include <ncurses.h> -#include <signal.h> -#include <stdint.h> -#include <termios.h> -#include <unistd.h> - -volatile int stop = 0; -uint8_t buf[8192] = {0}; -uint16_t rptr = 0, wptr = 0; - -#pragma pack(1) -struct vec3_t { - float x; - float y; - float z; -}; -#pragma pack() - -enum DataType -{ - DATA_VEC3 = 0, - DATA_QUAT = 1, - DATA_FLOAT = 2, - DATA_INT = 3, - DATA_STRING= 4, - DATA_LIMITED_INT = 5, - DATA_UINT32 = 6 -}; - -const char* sensor_names[] = -{ - "ACCEL_MPU9250", - "ACCEL_INEMO", - "ACCEL_MAX21105", - - "GYRO_MPU9250", - "GYRO_INEMO", - "GYRO_FXAS21002", - "GYRO_MAX21105", - - "COMPASS_MPU9250", - "COMPASS_INEMO", - - "TEMP_MPU9250", - "TEMP_INEMO", - "TEMP_LPS331AP", - "TEMP_MAX21105", - "TEMP_MS580", - - "PRESS_LPS331AP", - "PRESS_MS580", - - "UNUSED_16", - "DMA_TX_FIFOSZ", - "DMA_RX_FIFOSZ", - "DMA_FIFO_ERR", - "CPU%", - "LOG_QUEUE_SZ" -}; - -uint8_t sensor_type[] = -{ - DATA_VEC3, - DATA_VEC3, - DATA_VEC3, - - DATA_VEC3, - DATA_VEC3, - DATA_VEC3, - DATA_VEC3, - - DATA_VEC3, - DATA_VEC3, - - DATA_FLOAT, - DATA_FLOAT, - DATA_FLOAT, - DATA_FLOAT, - DATA_FLOAT, - - DATA_FLOAT, - DATA_FLOAT, - - DATA_INT, - DATA_LIMITED_INT, - DATA_LIMITED_INT, - DATA_UINT32, - DATA_UINT32, - DATA_LIMITED_INT -}; - -#define NUM_SENSORS ((int)((sizeof(sensor_names) / sizeof(sensor_names[0])))) - - -void on_signal(int s) -{ - if(!stop) - { - signal(s, on_signal); - stop = 1; - } -} - -int logline = 0; -void log_string(const char* data, int len) -{ - if(len <= 0) - return; - - move(NUM_SENSORS + 2 + logline, 10); - attron(COLOR_PAIR(1)); - printw(data); - if(len < 30) - for(int i=len;i<30;i++) - printw(" "); - refresh(); - logline = (logline+1)%20; -} - -void draw_sensor(int id, int type, const void* data) -{ - char tmp[256] = {0}; - - if(id >= (int)NUM_SENSORS) - { - sprintf(tmp, "Got packet for invalid sensor %d\n", id); - log_string(tmp, strlen(tmp)); - return; - } - - if(type < 0 || type > 6 || type == 4) - { - sprintf(tmp, "Got packet for invalid type %d\n", type); - log_string(tmp, strlen(tmp)); - return; - } - - move(1+id, 10); - sprintf(tmp, "%20s : ", sensor_names[id]); - - uint8_t color = (type != 5) ? (type + 1) : 2; - attron(COLOR_PAIR(color)); - printw(tmp); - attroff(COLOR_PAIR(color)); - - int all_null = 1; - for(int i=0;i<NUM_SENSORS && all_null == 1;i++) - if(((const char *)data)[i] != 0) - all_null = 0; - if((data == NULL || all_null == 1) && type != 5) - { - attron(COLOR_PAIR(5)); - attron(A_BOLD); - printw("[NULL] "); - attroff(A_BOLD); - attroff(COLOR_PAIR(5)); - return; - } - - const uint8_t* i = (const uint8_t*) data; - const struct vec3_t* v = (const struct vec3_t*) data; - const float* f = (const float*) data; - const uint8_t* ui = (const uint8_t*) data; - const uint32_t* ui32 = (const uint32_t*) data; - - switch(type) - { - case DATA_FLOAT: - sprintf(tmp, "%7.4f ", *f); - printw(tmp); - break; - case DATA_INT: - sprintf(tmp, "%d ", *i); - printw(tmp); - break; - case DATA_UINT32: - sprintf(tmp, "%d ", *ui32); - printw(tmp); - break; - case DATA_VEC3: - sprintf(tmp, "(%7.4f, %7.4f, %7.4f)", v->x, v->y, v->z); - printw(tmp); - break; - case DATA_LIMITED_INT: - { - uint32_t vv = (*ui * 27) / 256; - for(uint32_t i=0;i<27;i++) - { - if(i < vv) - printw("#"); - else - printw("."); - } - break; - } - default: - printw("[NOT IMPLEMENTED]"); - break; - } -} - -int sfd; -int init_serial(const char *device) -{ - sfd = open(device, O_RDWR | O_NOCTTY | O_SYNC); - if(sfd < 0) - { - printf("Cannot open ttyUSB0\n"); - return 1; - } - - struct termios tty; - memset(&tty, 0, sizeof(tty)); - if(tcgetattr(sfd, &tty) != 0) - { - printf("Cannot get attributes\n"); - return 1; - } - cfmakeraw(&tty); - cfsetospeed(&tty, B115200); - cfsetispeed(&tty, B115200); - tcsetattr(sfd, TCSANOW, &tty); - - return 0; -} - -void parse(int len) -{ - if(len == 0) - return; - - uint8_t type = buf[0]; - if(type == DATA_STRING) - log_string((const char *)&buf[1], len-1); - else - { - uint8_t id = buf[1]; - draw_sensor(id, type, &buf[2]); - } -} - -int fromHex(char l) -{ - if(l >= '0' && l <= '9') - return l - '0'; - if(l >= 'a' && l <= 'f') - return 10 + l - 'a'; - if(l >= 'A' && l <= 'F') - return 10 + l - 'A'; - return 0; -} - -int read_and_draw() -{ - memset(buf, 0, sizeof(buf)); - int bpos = 0; - char b; - char tmp = 0; - do { - read(sfd, &b, 1); - if(b == '\n' || b == '\r') - break; - else { - if(tmp == 0) - tmp = b; - else { - buf[bpos++] = (fromHex(tmp) << 4) | fromHex(b); - tmp = 0; - } - } - } while(1); - parse(bpos); - return 0; -} - -int main(int argc, char *argv[]) -{ - if(argc < 2) - { - printf("Usage: %s <PORT>\n", argv[0]); - return -1; - } - if(init_serial(argv[1])) - return -1; - - signal(SIGINT, on_signal); - signal(SIGTERM, on_signal); - - initscr(); - start_color(); - init_pair(1, COLOR_BLUE, COLOR_BLACK); - init_pair(2, COLOR_GREEN, COLOR_BLACK); - init_pair(3, COLOR_CYAN, COLOR_BLACK); - init_pair(4, COLOR_WHITE, COLOR_BLACK); - init_pair(5, COLOR_RED, COLOR_BLACK); - init_pair(7, COLOR_MAGENTA, COLOR_BLACK); - erase(); - refresh(); - - log_string("Ready...", 8); - while(!stop) - { - if(read_and_draw() == 1) - stop = 1; - refresh(); - } - - endwin(); - - signal(SIGINT, 0); - signal(SIGTERM, 0); - - return 0; -} diff --git a/scripts/copyrightchecker.py b/scripts/copyrightchecker.py new file mode 100755 index 0000000000000000000000000000000000000000..6c39c3f208259009c21dfd14cdbc0532819fd007 --- /dev/null +++ b/scripts/copyrightchecker.py @@ -0,0 +1,140 @@ +#!/usr/bin/python3 + +# Copyright (c) 2021 Skyward Experimental Rocketry +# Authors: Alberto Nidasio +# +# 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. + +import re +from argparse import ArgumentParser +from os import walk +from os.path import join +from pprint import pprint + +# Copyright template for C++ code files and headers +CPP_TEMPLATE = r'\/\* Copyright \(c\) 20\d\d(?:-20\d\d)? Skyward Experimental Rocketry\n' + \ +r' \* (Authors?): (.+)\n' + \ +r' \*\n' + \ +r' \* Permission is hereby granted, free of charge, to any person obtaining a copy\n' + \ +r' \* of this software and associated documentation files \(the "Software"\), to deal\n' + \ +r' \* in the Software without restriction, including without limitation the rights\n' + \ +r' \* to use, copy, modify, merge, publish, distribute, sublicense, and\/or sell\n' + \ +r' \* copies of the Software, and to permit persons to whom the Software is\n' + \ +r' \* furnished to do so, subject to the following conditions:\n' + \ +r' \*\n' + \ +r' \* The above copyright notice and this permission notice shall be included in\n' + \ +r' \* all copies or substantial portions of the Software.\n' + \ +r' \*\n' + \ +r' \* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n' + \ +r' \* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n' + \ +r' \* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n' + \ +r' \* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n' + \ +r' \* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n' + \ +r' \* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n' + \ +r' \* THE SOFTWARE.\n' + \ +r' \*\/' +AUTHOR_DELIMITER = ',' + +def config_cmd_parser(): + parser = ArgumentParser( + description='The Copyright Checker script verifies that all files in the specified directory contains the correct copyright notice.\nSupport cpp code and header files.') + parser.add_argument('directory', nargs='?', + help='Directory where to search files') + return parser + +def print_banner(): + # Font: Ivrit + print("+------------------------------------------------+") + print("| ___ _ _ _ |") + print("| / ___|___ _ __ _ _ _ __(_) __ _| |__ | |_ |") + print("| | | / _ \\| '_ \\| | | | '__| |/ _` | '_ \\| __||") + print("| | |__| (_) | |_) | |_| | | | | (_| | | | | |_ |") + print("| \\____\\___/| .__/ \\__, |_| |_|\\__, |_| |_|\\__||") + print("| ____|_| |___/ _ |___/ |") + print("| / ___| |__ ___ ___| | _____ _ __ |") + print("| | | | '_ \\ / _ \\/ __| |/ / _ \\ '__| |") + print("| | |___| | | | __/ (__| < __/ | |") + print("| \\____|_| |_|\\___|\\___|_|\\_\\___|_| |") + print("+------------------------------------------------+") + +# ------------------------------------------------------------- +# MAIN +# ------------------------------------------------------------- + + +parser = config_cmd_parser() +args = parser.parse_args() +totalCheckdFilesCounter = 0 +filesWithErrorsCounter = 0 + +# Statistics +authors = {} +averageAuthorsPerFile = 0 + +if(not args.directory): + print('[chopyrightchecker] No directory specified') + print('') + parser.print_help() + exit(-1) + +# Walk throgh the directory and check each file +for dirpath, dirnames, filenames in walk(args.directory): + for filename in [f for f in filenames if f.endswith(('.cpp', '.h'))]: + totalCheckdFilesCounter += 1 + + # Prepare the complete filepath + currentFilepath = join(dirpath, filename) + + # Check the current file + with open(currentFilepath) as file: + match = re.search(CPP_TEMPLATE, file.read()) + if not match: + filesWithErrorsCounter += 1 + + # The file's copyright notice does not match the template! + print('[chopyrightchecker] Wrong copyright notice in file {0}'.format(currentFilepath)) + else: + fileAuthors = [a.strip() for a in match.group(2).split(AUTHOR_DELIMITER)] + + # Check the number of authors against 'Author' or `Authors` + if len(fileAuthors) == 1 and match.group(1)[-1] == 's': + print('[chopyrightchecker] \'Authors\' should to be changed to \'Author\' in {0}'.format(currentFilepath)) + if len(fileAuthors) > 1 and match.group(1)[-1] != 's': + print('[chopyrightchecker] \'Author\' should to be changed to \'Authors\' in {0}'.format(currentFilepath)) + + # Save statistics on authors + for author in fileAuthors: + if author in authors: + authors[author] += 1 + else: + authors[author] = 1 + averageAuthorsPerFile += len(fileAuthors) +averageAuthorsPerFile /= totalCheckdFilesCounter + + +print('[chopyrightchecker] Checked {} files'.format(totalCheckdFilesCounter)) +if filesWithErrorsCounter == 0: + print('[chopyrightchecker] All the files have the correct copyright notice') +else: + print('[chopyrightchecker] {} files do not match with the copyright template'.format(filesWithErrorsCounter)) + +print('[chopyrightchecker] {:.2} authors per file'.format(averageAuthorsPerFile)) +print('[chopyrightchecker] Number of mentrions per author:') +for author in authors: + print('[chopyrightchecker] {:3} - {}'.format(authors[author], author)) \ No newline at end of file diff --git a/scripts/gen_fault_headers.py b/scripts/gen_fault_headers.py deleted file mode 100755 index 0a5badbdaac96f3de9d501d912fbe39c1f3f4ef2..0000000000000000000000000000000000000000 --- a/scripts/gen_fault_headers.py +++ /dev/null @@ -1,239 +0,0 @@ -#!/usr/bin/python - -# Copyright (c) 2017 Skyward Experimental Rocketry -# Authors: Alain Carlucci -# -# 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. - - -import csv,string,hashlib,datetime,sys - -if len(sys.argv) != 3: - print("Usage: %s <CSV File> <Output header file>" % (sys.argv[0])) - exit(-1) - -headerLine = None - -newLines = [] - -lineCtr = 0 -curCatId = 0 -categories={} -enums={} - -import hashlib - -def fileHash(filename): - h = hashlib.sha1() - with open(filename, 'rb', buffering=0) as f: - for b in iter(lambda : f.read(128*1024), b''): - h.update(b) - return h.hexdigest() - -def genEnum(name, s, inc): - a = ('''enum class %s -{ -''') % (name,); - cnt = 0 - for i in s: - if inc: - a += " " + i + " = " + str(cnt) + ",\n" - else: - a += " " + i + " = " + str(s[i]) + ",\n" - cnt += 1 - a += "};\n" - a += "const std::size_t %s_SIZE = %d;\n" %(name, cnt) - return a - -def genHeaderFile(fileName,csvFile): - print("[*] Generating header file (%s)" %(fileName,)) - content = ('''/* Copyright (c) 2017 Skyward Experimental Rocketry - * Authors: Alain Carlucci - * - * 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. - */ - -/* - ****************************************************************************** - * THIS FILE IS AUTOGENERATED. DO NOT EDIT. * - ****************************************************************************** - */ - -// CSV File: %s -// SHA1 of CSV File: %s -// Autogen date: %s - -#include <cstdint> - -#ifndef SKYWARD_FAULT_CTRL_LIST_H -#define SKYWARD_FAULT_CTRL_LIST_H - -''') % (csvFile, fileHash(csvFile), datetime.datetime.now()) - content += genEnum("Fault", enums, True) - content += "\nnamespace FaultCounterData\n{\n\n" - content += genEnum("FaultCategory", categories, False) - content += ''' - -// Usage: categoryID = FaultCounter::FaultToCategory[faultID]; -const uint32_t FaultToCategory[] = -{ - ''' - colCnt = 0 - for i in enums: - content += "%d, " % (categories[enums[i]], ) - colCnt += 1 - if colCnt == 20: - content += "\n " - colCnt = 0 - content += ''' -}; /* CategoryMapping */ - -} /* FaultCounterData */ - -#endif /* SKYWARD_FAULT_CTRL_LIST_H */ - -''' - try: - with open(fileName, "w") as f: - f.write(content) - print("[+] Written %d lines in %s" % (len(content.split("\n")), fileName)) - except: - print("[!] Cannot open %s for writing.", fileName) - exit(-1) - - -def checkForGoodName(n): - if len(n) == 0: - return False - - if n[0] not in string.ascii_uppercase: - return False - - for x in n: - if x not in string.ascii_uppercase + string.digits + '_': - return False - - if n[-1] not in string.ascii_uppercase + string.digits: - return False - - return True - -def genEnumName(cat,name): - ctr = 2 - - genBName = "F_" + cat + "_" + name - genName = genBName - - while genName in enums: - genName = genBName + "_" + str(ctr) - ctr += 1 - - return genName - -# return False: csv file is ok -# return True: csv file must be updated -def handleLine(r): - global lineCtr,categories,newLines,enums,curCatId - - if len(r) != 4: - print("[!] Parsing error at line %d. I'm expecting 4 fields" % (lineCtr,)) - exit(-1) - - curCat = r[0].strip().upper() - curName = r[1].strip().upper() - curDesc = r[2].strip() - curEnum = r[3].strip().upper() - - if checkForGoodName(curCat) == False: - print("[!] Invalid category at line %d." % (lineCtr,)) - exit(-1) - - if checkForGoodName(curName) == False: - print("[!] Invalid name at line %d." % (lineCtr,)) - exit(-1) - - if len(curEnum) > 0 and (checkForGoodName(curEnum) == False or curEnum in enums): - print("[!] Invalid generated name at line %d." % (lineCtr,)) - exit(-1) - - retVal = False - if len(curEnum) == 0: - curEnum = genEnumName(curCat, curName) - retVal = True - - if curCat not in categories: - print("[+] Added category '%s'" % (curCat,)) - categories[curCat] = curCatId - curCatId += 1 - - print("[+] Added %s in category %s" %(curEnum,curCat)) - enums[curEnum] = curCat - - newLines.append([curCat, curName, curDesc, curEnum]) - return retVal - -mustUpdate = False -print("[*] Opening %s..." % (sys.argv[1],)) -try: - with open(sys.argv[1], "r") as csvfile: - r = csv.reader(csvfile, delimiter=',', quotechar='"') - for row in r: - lineCtr += 1 - if headerLine is None: - headerLine = row - continue - if handleLine(row): - mustUpdate = True -except Exception as e: - print("[!] Cannot open %s for reading: %s!" %(sys.argv[1],str(e))) - exit(-1) - -if not mustUpdate: - print("[+] No need to update the csv file.") -else: - print("[*] Updating %s..." % (sys.argv[1],)) - - try: - with open(sys.argv[1], "w") as csvfile: - r = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL) - r.writerow(headerLine) - for row in newLines: - r.writerow(row) - print("[+] Successfully updated csv file") - except: - print("[!] Cannot open %s for writing!" %(sys.argv[1],)) - exit(-1) - -genHeaderFile(sys.argv[2], sys.argv[1]) diff --git a/scripts/generators/README.md b/scripts/generators/README.md new file mode 100644 index 0000000000000000000000000000000000000000..dfa4189d688093436b7c79293db6ad612aa7513a --- /dev/null +++ b/scripts/generators/README.md @@ -0,0 +1,38 @@ +# Generators scripts + +The scripts contained in this directory are used to generate cpp code from scxml files, they accept a directory or a list of files. + +## Eventgen + +The Eventgen program generates the files `Events.h`, `Topics.h` and `EventString.cpp` gathering information from all scxml files specified. + +```shell +usage: eventgen.py [-h] [-q] [-f [F [F ...]]] [directory] + +positional arguments: + directory Directory where to search files + +optional arguments: + -h, --help show this help message and exit + -q, --quiet Output only essential messages + -f [F [F ...]], --files [F [F ...]] +``` + +## FSMGen + +The FSMGen program generates cpp files (`FSMController.h`, `FSMController.cpp`, `FSMData.h` and `test-FMS.cpp`) for each scxml file specified. + +```shell +usage: fsmgen.py [-h] [-q] [-a AUTHORS] [-n MAIN_NAMESPACE] [-f [F [F ...]]] [directory] + +positional arguments: + directory Directory where to search files + +optional arguments: + -h, --help show this help message and exit + -q, --quiet Output only essential messages + -a AUTHORS, --authors AUTHORS + -n MAIN_NAMESPACE, --main_namespace MAIN_NAMESPACE + DeathStackBoard as default + -f [F [F ...]], --files [F [F ...]] +``` diff --git a/scripts/generators/eventgen.py b/scripts/generators/eventgen.py new file mode 100755 index 0000000000000000000000000000000000000000..dedf167d77171b073d56623f605f9139855b4010 --- /dev/null +++ b/scripts/generators/eventgen.py @@ -0,0 +1,215 @@ +#!/usr/bin/python3 + +# Copyright (c) 2018-2021 Skyward Experimental Rocketry +# Authors: Luca Erbetta, Alvise de' Faveri Tron, Alberto Nidasio +# +# 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. + +import sys +import datetime +from os import walk +from os.path import join, dirname +from argparse import ArgumentParser +from genutils import parse_scxml, read_file, write_file + +current_directory = dirname(sys.argv[0]) +TEMPLATE_FOLDER = 'templates' +OUTPUT_FOLDER = 'generated' + +# Templates file names +EVENTS_H_TEMPLATE_FILE = current_directory + \ + ('/' if current_directory != '' else '') + \ + TEMPLATE_FOLDER + '/Events.h.template' +TOPICS_H_TEMPLATE_FILE = current_directory + \ + ('/' if current_directory != '' else '') + \ + TEMPLATE_FOLDER + '/Topics.h.template' +EVENT_STRINGS_CPP_TEMPLATE_FILE = current_directory + \ + ('/' if current_directory != '' else '') + \ + TEMPLATE_FOLDER + '/EventStrings.cpp.template' + + +def config_cmd_parser(): + parser = ArgumentParser( + description='The Eventgen program generates the files Events.h, Topics.hand EventString.cpp gathering information from all scxml files specified.') + parser.add_argument('-q', '--quiet', dest='quiet', + action='store_true', help='Output only essential messages') + parser.add_argument('-f', '--files', dest='files', metavar='F', nargs='*') + parser.add_argument('directory', nargs='?', + help='Directory where to search files') + return parser + + +def print_banner(): + # Font: Ivrit + print("+-----------------------------------------------------+") + print("| ____ _ _ |") + print("| / ___|| | ___ ___ ____ _ _ __ __| | |") + print("| \\___ \\| |/ / | | \\ \\ /\\ / / _` | '__/ _` | |") + print("| ___) | <| |_| |\\ V V / (_| | | | (_| | |") + print("| |____/|_|\\_\\\\__, | \\_/\\_/ \\__,_|_| \\__,_| |") + print("| _____|___/ _ |") + print("| | ____|_ _____ _ __ | |_ __ _ ___ _ __ |") + print("| | _| \\ \\ / / _ \\ '_ \\| __/ _` |/ _ \\ '_ \\ |") + print("| | |___ \\ V / __/ | | | || (_| | __/ | | | |") + print("| |_____| \\_/ \\___|_| |_|\\__\\__, |\\___|_| |_| |") + print("| |___/ |") + print("+-----------------------------------------------------+") + + +def extract_events(fsmData): + # Get all possible events + events = [] + for state in fsmData[3]: + for event in state[3]: + events.append(event[0]) + + # Remove doubles + events = list(dict.fromkeys(events)) + + return events + + +def generate_events_h(events, date): + template = read_file(EVENTS_H_TEMPLATE_FILE) + + if not events or len(events) == 0: + return + + # Prepare the event list + enum_event_list = list(events) + enum_event_list[0] += ' = EV_FIRST_SIGNAL' + enum_event_list = '\n'.join( + [' '*4 + event + ',' for event in enum_event_list]) + vector_event_list = '\n'.join([' '*4 + event + ',' for event in events]) + + # Insert information into template file + content = template.format( + year=date.year, date=date, enum_event_list=enum_event_list, + vector_event_list=vector_event_list + ) + + # Save the file + return write_file(OUTPUT_FOLDER + '/events', 'Events.h', content) + + +def generate_topics_h(topics, date): + template = read_file(TOPICS_H_TEMPLATE_FILE) + + if not topics or len(topics) == 0: + return + + # Prepare the topic list + topic_list = '\n'.join([' '*4 + topic + ',' for topic in topics]) + + # Insert information into template file + content = template.format(year=date.year, date=date, topic_list=topic_list) + + # Save the file + return write_file(OUTPUT_FOLDER + '/events', 'Topics.h', content) + + +def generate_event_string_cpp(events, topics, date): + template = read_file(EVENT_STRINGS_CPP_TEMPLATE_FILE) + + if not events or len(events) == 0 or not topics or len(topics) == 0: + return + + # Prepare the event and topic lists + event_list = '\n'.join( + [' '*8 + '{' + event + ', "' + event + '"},' for event in events]) + topic_list = '\n'.join( + [' '*8 + '{' + topic + ', "' + topic + '"},' for topic in topics]) + + # Insert information into template file + content = template.format( + year=date.year, date=date, + event_list=event_list, topic_list=topic_list + ) + + # Save the file + return write_file(OUTPUT_FOLDER + '/events', 'EventStrings.cpp', content) + + +# ------------------------------------------------------------- +# MAIN +# ------------------------------------------------------------- + + +parser = config_cmd_parser() +args = parser.parse_args() +files = [] + +if(not args.files and not args.directory): + print('[eventgen] No scxml files specified nor a directory') + print('') + parser.print_help() + exit(-1) + +# Fetch files to parse +if args.files: + files.extend(args.files) +if args.directory: + for dirpath, dirnames, filenames in walk(args.directory): + for filename in [f for f in filenames if f.endswith(".scxml")]: + if not 'template' in filename: + files.append(join(dirpath, filename)) + +if(not args.quiet): + print_banner() + print('') + print('[eventgen] Welcome to the eventgen script!') + print('') + +date = datetime.datetime.now() +events = [] +topics = [] + +for file in files: + print('[eventgen] Now parsing ' + file) + + fsmData = parse_scxml(file) + + # Extract events + events.extend(extract_events(fsmData)) + + # Save topic + topics.extend(['TOPIC_' + topic for topic in fsmData[5]]) + +# Remove duplicates +events = list(dict.fromkeys(events)) +topics = list(dict.fromkeys(topics)) + +# Sort +events.sort() +topics.sort() + +# Generate Events.h +file_name = generate_events_h(events, date) +if file_name: + print('[eventgen] Generated ' + file_name) + +# Generate Topics.h +file_name = generate_topics_h(topics, date) +if file_name: + print('[eventgen] Generated ' + file_name) + +# Generate EventString.cpp +file_name = generate_event_string_cpp(events, topics, date) +if file_name: + print('[eventgen] Generated ' + file_name) diff --git a/scripts/generators/fsmgen.py b/scripts/generators/fsmgen.py new file mode 100755 index 0000000000000000000000000000000000000000..874a15a32a561595b0d6a1a01ed8a3c6e45af7b1 --- /dev/null +++ b/scripts/generators/fsmgen.py @@ -0,0 +1,388 @@ +#!/usr/bin/python3 + +# Copyright (c) 2021 Skyward Experimental Rocketry +# Authors: Alberto Nidasio +# +# 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. + +import os +import sys +import datetime +from os import walk +from os.path import join, dirname +from argparse import ArgumentParser +from genutils import parse_scxml, read_file, write_file + +current_directory = dirname(sys.argv[0]) +TEMPLATE_FOLDER = 'templates' +OUTPUT_FOLDER = 'generated' +DEFAULT_NAMESPACE = 'DeathStackBoard' + +# Templates file names +FSM_CONTROLLER_H_TEMPLATE_FILE = current_directory + \ + ('/' if current_directory != '' else '') + \ + TEMPLATE_FOLDER + '/FSMController.h.template' +FSM_CONTROLLER_CPP_TEMPLATE_FILE = current_directory + \ + ('/' if current_directory != '' else '') + \ + TEMPLATE_FOLDER + '/FSMController.cpp.template' +FSM_DATA_H_TEMPLATE_FILE = current_directory + \ + ('/' if current_directory != '' else '') + \ + TEMPLATE_FOLDER + '/FSMData.h.template' +STATE_FUNCTION_DEFINITION_TEMPLATE_FILE = current_directory + \ + ('/' if current_directory != '' else '') + \ + TEMPLATE_FOLDER + '/state_function_definition.template' +FSM_TEST_CPP_TEMPLATE_FILE = current_directory + \ + ('/' if current_directory != '' else '') + \ + TEMPLATE_FOLDER + '/test.cpp.template' + +# Templates strings +STATE_FUNCTION_DECLARATION_TEMPLATE = 'void state_{state_name}(const Event& ev);' +STATE_FUNCTION_CASE_TEMPLATE = ' '*8 + 'case {event}:\n' + \ + ' '*8 + '{{\n' + \ + '{actions}\n' + \ + ' '*12 + 'break;\n' + \ + ' '*8 + '}}' +UTILITY_FUNCTION_DECLARATION_TEMPLATE = 'void {function_name}();' +TOPICS_SUBSCIPTION_TEMPLATE = ' '*4 + 'sEventBroker->subscribe(this, {topic});' +UTILITY_FUNCTION_DEFINITION_TEMPLATE = 'void {state_machine_name}Controller::{function_name}()\n{{\n // ...\n}}' +STATE_FUNCTION_CASE_TRANSITION_TO_TARGET_TEMPLATE = 'transition(&{state_machine_name}Controller::state_{state_name});' +TEST_CASE_METHOD_TEMPLATE = 'TEST_CASE_METHOD({state_machine_name}ControllerFixture, "Testing transitions from {state}")\n' + \ + '{{\n' + \ + ' '*4 + 'controller->transition(&{state_machine_name}Controller::state_{start_state});\n\n' + \ + '{test_case_sections}' + \ + '\n}}' +TEST_CASE_SECTION_TEMPLATE = ' '*4 + 'SECTION("{event} -> {target_state_upper}")\n' + \ + ' '*4 + '{{\n' + \ + ' '*8 + 'REQUIRE(testFSMTransition(*controller, Event{{{event}}}, &{state_machine_name}Controller::state_{target_state}));\n' + \ + ' '*4 + '}}' +README_TEMPLATE = '```mermaid\nstateDiagram-v2\n{mermaid}\n```' + + +def config_cmd_parser(): + parser = ArgumentParser( + description='The FSMGen program generates cpp files (FSMController.h, FSMController.cpp, FSMData.h and test-FMS.cpp) for each scxml file specified.') + parser.add_argument('-q', '--quiet', help='Output only essential messages', dest='quiet', + action='store_true') + parser.add_argument('-a', '--authors', dest='authors') + parser.add_argument('-n', '--main_namespace', + help=DEFAULT_NAMESPACE + ' as default', dest='main_namespace') + parser.add_argument('-f', '--files', dest='files', metavar='F', nargs='*') + parser.add_argument('directory', nargs='?', + help='Directory where to search files') + return parser + + +def print_banner(): + # Font: Ivrit + print("+----------------------------------------------------------------------------+") + print("| ____ _ _ |") + print("| / ___|| | ___ ___ ____ _ _ __ __| | |") + print("| \\___ \\| |/ / | | \\ \\ /\\ / / _` | '__/ _` | |") + print( + "| ___) | <| |_| |\\ V V / (_| | | | (_| | |") + print("| |____/|_|\\_\\\\__, | \\_/\\_/ \\__,_|_| \\__,_| |") + print("| _____|___/ __ __ ____ _ |") + print("| | ___/ ___|| \\/ | / ___| ___ _ __ ___ _ __ __ _| |_ ___ _ __ |") + print("| | |_ \___ \\| |\\/| | | | _ / _ \\ '_ \\ / _ \\ '__/ _` | __/ _ \\| '__| |") + print("| | _| ___) | | | | | |_| | __/ | | | __/ | | (_| | || (_) | | |") + print("| |_| |____/|_| |_| \\____|\\___|_| |_|\\___|_| \\__,_|\\__\___/|_| |") + print("| |") + print("+----------------------------------------------------------------------------+") + + +def generate_fsm_controller_h(fsmData, year, authors, main_namespace): + state_machine_name = fsmData[0] + + # Open template file + template = read_file(FSM_CONTROLLER_H_TEMPLATE_FILE) + + # Prepare the state functions declarations + states_funcions_declarations = [ + STATE_FUNCTION_DECLARATION_TEMPLATE.format(state_name=state[0]) + for state in fsmData[3]] + states_funcions_declarations = '\n'.join( + [' '*4 + declaration for declaration in states_funcions_declarations]) + + # Preapare utility functions declarations + utility_functions_declarations = [ + UTILITY_FUNCTION_DECLARATION_TEMPLATE.format( + function_name=function.replace('()', '')) + for function in fsmData[4]] + utility_functions_declarations = '\n'.join( + [' '*4 + declaration for declaration in utility_functions_declarations] + ) + + # Insert information into template file + content = template.format( + year=year, authors=authors, main_namespace=main_namespace, + state_machine_name=state_machine_name, + states_funcions_declarations=states_funcions_declarations, + start_state=fsmData[2] + '_' + fsmData[1].upper(), + utility_functions_declarations=utility_functions_declarations + ) + + # Save the file + return write_file(OUTPUT_FOLDER + '/' + state_machine_name, + state_machine_name + 'Controller.h', content) + + +def generate_fsm_controller_cpp(fsmData, year, authors, main_namespace): + state_machine_name = fsmData[0] + + # Open template files + template = read_file(FSM_CONTROLLER_CPP_TEMPLATE_FILE) + functon_definition_template = read_file( + STATE_FUNCTION_DEFINITION_TEMPLATE_FILE) + + # Prepare the state functions definitions + states_functions_definitions = [] + for state in fsmData[3]: + event_cases = [] + + # Prepare onentry and onexit actions + onentry_actions = '\n'.join( + [' '*12 + action + ';' for action in state[1]]) + onexit_actions = '\n'.join( + [' '*12 + action + ';' for action in state[2]]) + if onentry_actions == '': + onentry_actions = ' '*12 + '// ...' + if onexit_actions == '': + onexit_actions = ' '*12 + '// ...' + + # Prepare the cases + for event in state[3]: + actions = [action + ';' for action in event[2]] + if event[1] and event[1] != state[0]: + actions.append(STATE_FUNCTION_CASE_TRANSITION_TO_TARGET_TEMPLATE.format( + state_machine_name=state_machine_name, state_name=event[1])) + actions = '\n'.join([' '*12 + action for action in actions]) + if actions == '': + actions = ' '*12 + '// ...' + event_cases.append(STATE_FUNCTION_CASE_TEMPLATE.format( + event=event[0], actions=actions + )) + event_cases = '\n'.join(event_cases) + + states_functions_definitions.append( + functon_definition_template.format( + state_machine_name=state_machine_name, + state_name=state[0].lower(), + current_state=state[0].upper(), + onentry_actions=onentry_actions, onexit_actions=onexit_actions, + event_cases=event_cases + ) + ) + states_functions_definitions = '\n\n'.join(states_functions_definitions) + + # Preapare utility functions declarations + utility_functions_definiton = [] + for function in fsmData[4]: + utility_functions_definiton.append( + UTILITY_FUNCTION_DEFINITION_TEMPLATE.format( + state_machine_name=state_machine_name, + function_name=function.replace('()', '') + ) + ) + utility_functions_definiton = '\n\n'.join(utility_functions_definiton) + + # Prepare topics subscriptions + topics_subscriptions = [] + for topic in fsmData[5]: + topics_subscriptions.append(TOPICS_SUBSCIPTION_TEMPLATE.format( + topic='TOPIC_' + topic + )) + topics_subscriptions = '\n'.join(topics_subscriptions) + + # Insert information into template file + content = template.format( + year=year, authors=authors, state_machine_name=state_machine_name, + initial_state=fsmData[1], topics_subscriptions=topics_subscriptions, + topic='TOPIC_' + fsmData[2], main_namespace=main_namespace, + states_functions_definitions=states_functions_definitions, + utility_functions_definiton=utility_functions_definiton + ) + + # Save the file + return write_file(OUTPUT_FOLDER + '/' + state_machine_name, + state_machine_name + 'Controller.cpp', content) + + +def generate_fsm_data_h(fsmData, year, authors, main_namespace): + state_machine_name = fsmData[0] + + # Open template file + template = read_file(FSM_DATA_H_TEMPLATE_FILE) + + # Prepare the states list + states = [state[0].upper() for state in fsmData[3]] + states[0] += ' = 0' + states = '\n'.join([' '*4 + state + ',' for state in states]) + + # Insert information into template file + content = template.format( + year=year, authors=authors, main_namespace=main_namespace, + state_machine_name=state_machine_name, states=states + ) + + # Save the file + return write_file(OUTPUT_FOLDER + '/' + state_machine_name, + state_machine_name + 'Data.h', content) + + +def generate_fsm_test_cpp(fsmData, year, authors): + state_machine_name = fsmData[0] + + # Open template file + template = read_file(FSM_TEST_CPP_TEMPLATE_FILE) + + # Prepare the test cases list + test_cases = [] + for state in fsmData[3]: + # Prepare test case sections + test_case_sections = [] + for event in state[3]: + test_case_sections.append(TEST_CASE_SECTION_TEMPLATE.format( + target_state_upper=event[1].upper(), target_state=event[1], + state_upper=state[0].upper(), event=event[0], + state_machine_name=state_machine_name + )) + test_case_sections = '\n\n'.join(test_case_sections) + + test_cases.append(TEST_CASE_METHOD_TEMPLATE.format( + state_machine_name=state_machine_name, state=state[0], + start_state=state[0], + test_case_sections=test_case_sections + )) + test_cases = '\n\n'.join(test_cases) + + # Insert information into template file + content = template.format( + year=year, authors=authors, state_machine_name=state_machine_name, + test_cases=test_cases + ) + + # Save the file + return write_file(OUTPUT_FOLDER + '/' + 'tests', 'test-' + fsmData[0].lower() + '.cpp', content) + + +def generate_readme_with_mermaid(fsmData): + state_machine_name = fsmData[0] + lines = [] + + # Initial state + lines.append('[*] --> ' + fsmData[1]) + + # Most commented code is because gitlab does not support some features of mermaid + + # Generate descriptions and transitons + for state in fsmData[3]: + # Name of the state + # lines.append(state[0] + ' : ' + state[0]) + + # Description with onentry, onexit and other actions + # for action in state[1]: + # lines.append(state[0] + ' : onentry - ' + action) + # for action in state[2]: + # lines.append(state[0] + ' : onexit - ' + action) + for transiton in state[3]: + # if transiton[1] == state[0]: + # lines.append(state[0] + ' : ' + transiton[0] + ( + # ' - ' + ''.join(transiton[2]) + # if len(transiton[2]) > 0 else '')) + # Transitions + # else: + if not transiton[1] == state[0]: + lines.append(state[0] + ' --> ' + + transiton[1] + ' : ' + transiton[0]) + # + ('\\n' + ''.join(transiton[2]) if len(transiton[2]) > 0 else '') + + mermaid = '\n'.join([' '*4 + line for line in lines]) + mermaid = README_TEMPLATE.format(mermaid=mermaid) + + # Save the file + return write_file(OUTPUT_FOLDER + '/' + state_machine_name, 'readme.md', mermaid) + + +# ------------------------------------------------------------- +# MAIN +# ------------------------------------------------------------- + + +parser = config_cmd_parser() +args = parser.parse_args() +files = [] + +if(not args.files and not args.directory): + print('[eventgen] No scxml files specified nor a directory') + print('') + parser.print_help() + exit(-1) + +# Fetch files to parse +if args.files: + files.extend(args.files) +if args.directory: + for dirpath, dirnames, filenames in walk(args.directory): + for filename in [f for f in filenames if f.endswith(".scxml")]: + if not 'template' in filename: + files.append(join(dirpath, filename)) + +if(not args.quiet): + print_banner() + print('') + print('[fsmgen] Welcome to the FSMgen script!') + print('') + +if(not args.authors): + print('[fsmgen] WARNING missing authors parameter') + args.authors = 'Someone' + +if(not args.main_namespace): + print('[fsmgen] WARNING missing namespace parameter') + args.main_namespace = DEFAULT_NAMESPACE + +date = datetime.datetime.now() + +for file in files: + print('[fsmgen] Now parsing ' + file) + + fsmData = parse_scxml(file) + + # Generate FSMController.h + file_name = generate_fsm_controller_h( + fsmData, date.year, args.authors, args.main_namespace) + print('[fsmgen] Generated ' + file_name) + + # Generate FSMController.cpp + file_name = generate_fsm_controller_cpp( + fsmData, date.year, args.authors, args.main_namespace) + print('[fsmgen] Generated ' + file_name) + + # Generate FSMData.h + file_name = generate_fsm_data_h( + fsmData, date.year, args.authors, args.main_namespace) + print('[fsmgen] Generated ' + file_name) + + # Generate test-FMS.cpp + file_name = generate_fsm_test_cpp(fsmData, date.year, args.authors) + print('[fsmgen] Generated ' + file_name) + + # Generate readme.md + file_name = generate_readme_with_mermaid(fsmData) + print('[fsmgen] Generated ' + file_name) diff --git a/scripts/generators/genutils.py b/scripts/generators/genutils.py new file mode 100644 index 0000000000000000000000000000000000000000..6a2d0fee71df89f9edd44309f71c07c1a3188e04 --- /dev/null +++ b/scripts/generators/genutils.py @@ -0,0 +1,142 @@ +# Copyright (c) 2021 Skyward Experimental Rocketry +# Authors: Alberto Nidasio +# +# 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. + +from re import sub +from os import makedirs +from os.path import join, basename +from xml.etree.ElementTree import parse + + +def camel_case_to_underscores(string): + return sub(r'((?<=[a-z])[A-Z]|(?<!\A)[A-Z](?=[a-z]))', r'_\1', string) + + +def parse_scxml(file): + name = basename(file).split('.')[0] + + # Parse the scxml file + tree = parse(str(file)) + root = tree.getroot() + initial_state = root.attrib['initial'] + + # Extract states and actions + topics = [] + states_list = [] + actions_list = [] + for state in root: + state_id = camel_case_to_underscores(state.attrib['id']).lower() + + # Extract on entry and on exit actions + onentry_actions = [] + onexit_actions = [] + for onentry in state.iter('{http://www.w3.org/2005/07/scxml}onentry'): + if 'postD' in onentry.text or 'removeD' in onentry.text: + onentry_actions.append(onentry.text) + else: + onentry_actions.append( + camel_case_to_underscores(onentry.text).lower()) + for onexit in state.iter('{http://www.w3.org/2005/07/scxml}onexit'): + if 'postD' in onexit.text or 'removeD' in onexit.text: + onexit_actions.append(onexit.text) + else: + onexit_actions.append( + camel_case_to_underscores(onexit.text).lower()) + + # Extract transitions and topics + transitions_list = [] + for transition in state.iter('{http://www.w3.org/2005/07/scxml}transition'): + try: + if '.' in transition.attrib['event']: + (topic, event) = camel_case_to_underscores( + transition.attrib['event']).split('.') + topics.append(topic) + else: + event = camel_case_to_underscores( + transition.attrib['event']) + except: + print( + '[genutils] Transition is missing event parameter in file ' + file) + exit(-1) + try: + target = camel_case_to_underscores( + transition.attrib['target']).lower() + except: + target = None + + # Divide each line of the transition text into a single action + try: + transition_actions = transition.text.splitlines() + for i, line in enumerate(transition_actions): + transition_actions[i] = line.lstrip().rstrip() + transition_actions = list( + filter(lambda a: a != '', transition_actions)) + transition_actions = [ + camel_case_to_underscores(action).lower() + if transition.text and not 'postD' in transition.text + and not 'removeD' in transition.text else action + for action in transition_actions] + except: + transition_actions = [] + + transitions_list.append((event, target, transition_actions)) + actions_list.extend(transition_actions) + + states_list.append( + (state_id, onentry_actions, onexit_actions, transitions_list)) + + # Remove from the actions list all the duplicates, postD and removeD + actions_list.extend(onentry_actions) + actions_list.extend(onexit_actions) + actions_list = list(dict.fromkeys(actions_list)) + actions_list = list( + filter(lambda a: not 'postD' in a and not 'removeD' in a, actions_list)) + actions_list.sort() + + state_machine_topic = max(set(topics), key=topics.count) + topics = list(dict.fromkeys(topics)) + + return (name, initial_state, state_machine_topic, states_list, actions_list, topics) + + +def read_file(file_name): + try: + with open(file_name, 'r') as r_file: + return r_file.read() + except: + print('Could not open template file "' + file_name + '"') + exit(-1) + + +def write_file(file_path, file_name, content): + file_path = file_path + + try: + # Ensures the path exists + makedirs(file_path, exist_ok=True) + + with open(join(file_path, file_name), 'w') as w_file: + w_file.write(content) + + return file_name + except: + print('Could not create and/or write file "' + + file_path + '/' + file_name + '"') + exit(-1) diff --git a/scripts/homeone/event_header_generator/EventFunctions.cpp.template b/scripts/generators/templates/EventStrings.cpp.template similarity index 80% rename from scripts/homeone/event_header_generator/EventFunctions.cpp.template rename to scripts/generators/templates/EventStrings.cpp.template index 18f7690704c127e2bd5eb516097a08868e505565..81fc1be2a016fc4f2e471915eec57a24c7a8f80b 100644 --- a/scripts/homeone/event_header_generator/EventFunctions.cpp.template +++ b/scripts/generators/templates/EventStrings.cpp.template @@ -1,5 +1,5 @@ -/* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2018-2020 Skyward Experimental Rocketry + * Authors: Luca Erbetta, Alvise de' Faveri Tron * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -26,34 +26,28 @@ ****************************************************************************** */ -// Generated from: {sheet_link} -// Autogen date: {date} +// Autogen date: {date} +#include <map> +using std::map; #include "Events.h" #include "Topics.h" -#include <map> - -namespace HomeoneBoard -{{ - string getEventString(uint8_t event) {{ - static const map<uint8_t, string> event_string_map {{ -{event_map_data} + static const map<uint8_t, string> event_string_map{{ +{event_list} }}; - auto it = event_string_map.find(event); + auto it = event_string_map.find(event); return it == event_string_map.end() ? "EV_UNKNOWN" : it->second; }} string getTopicString(uint8_t topic) {{ static const map<uint8_t, string> topic_string_map{{ -{topic_map_data} +{topic_list} }}; auto it = topic_string_map.find(topic); - return it == topic_string_map.end() ? "TOPIC_UNKNOWN" : it->second; -}} - + return it == topic_string_map.end() ? "TOPIC_UNKNOWN" : it->second; }} \ No newline at end of file diff --git a/scripts/homeone/event_header_generator/Events.h.template b/scripts/generators/templates/Events.h.template similarity index 70% rename from scripts/homeone/event_header_generator/Events.h.template rename to scripts/generators/templates/Events.h.template index 2c4937a1fdd509e3307baf666120b77fdd067fec..7273a2c74fafc83ba7cc103300c71a5f0639b3d4 100644 --- a/scripts/homeone/event_header_generator/Events.h.template +++ b/scripts/generators/templates/Events.h.template @@ -1,5 +1,5 @@ -/* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2018-{year} Skyward Experimental Rocketry + * Authors: Luca Erbetta, Alvise de' Faveri Tron * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -26,42 +26,37 @@ ****************************************************************************** */ -// Generated from: {sheet_link} -// Autogen date: {date} +// Autogen date: {date} -#ifndef SRC_SHARED_BOARDS_HOMEONE_EVENTS_H -#define SRC_SHARED_BOARDS_HOMEONE_EVENTS_H +#pragma once #include <cstdint> #include <string> +#include <vector> +#include "Topics.h" #include "events/Event.h" #include "events/EventBroker.h" -#include "EventClasses.h" -#include "Topics.h" using std::string; -using std::map; -namespace HomeoneBoard -{{ /** - * Definition of all events in the Homeone Board software - * Refer to section 5.1.1 of the Software Design Document. + * Definition of all events in the Board software. + * Refer to the Software Design Document. */ enum Events : uint8_t {{ -{enum_data} +{enum_event_list} +}}; + +const std::vector<uint8_t> EVENT_LIST{{ +{vector_event_list} }}; /** * @brief Returns the name of the provided event - * - * @param event - * @return string + * + * @param event + * @return string */ -string getEventString(uint8_t event); - -}} - -#endif /* SRC_SHARED_BOARDS_HOMEONE_EVENTS_H */ +string getEventString(uint8_t event); \ No newline at end of file diff --git a/scripts/generators/templates/FSMController.cpp.template b/scripts/generators/templates/FSMController.cpp.template new file mode 100644 index 0000000000000000000000000000000000000000..4107e696a81e098e64998a6eb4814efe6c59a04d --- /dev/null +++ b/scripts/generators/templates/FSMController.cpp.template @@ -0,0 +1,66 @@ +/* Copyright (c) {year} Skyward Experimental Rocketry + * Authors: {authors} + * + * 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. + */ + +#include "{state_machine_name}Controller.h" + +#include <miosix.h> + +#include "configs/{state_machine_name}Config.h" +#include "events/EventBroker.h" +#include "events/Events.h" + +namespace {main_namespace} +{{ + +using namespace {state_machine_name}Config; + +{state_machine_name}Controller::{state_machine_name}Controller() : FSM(&{state_machine_name}Controller::state_{initial_state}) +{{ + memset(&status, 0, sizeof({state_machine_name}ControllerStatus)); +{topics_subscriptions} +}} + +{state_machine_name}Controller::~{state_machine_name}Controller() +{{ + sEventBroker->unsubscribe(this); +}} + +{states_functions_definitions} + +{utility_functions_definiton} + +/* --- LOGGER --- */ + +void {state_machine_name}Controller::logStatus({state_machine_name}ControllerState state) +{{ + status.state = state; + logStatus(); +}} + +void {state_machine_name}Controller::logStatus() +{{ + status.timestamp = miosix::getTick(); + logger.log(status); + StackLogger::getInstance()->updateStack(THID); +}} + +}} // namespace {main_namespace} \ No newline at end of file diff --git a/scripts/generators/templates/FSMController.h.template b/scripts/generators/templates/FSMController.h.template new file mode 100644 index 0000000000000000000000000000000000000000..99b4dd6b5dbf34d27d3977439b5c5b636d5121a0 --- /dev/null +++ b/scripts/generators/templates/FSMController.h.template @@ -0,0 +1,55 @@ +/* Copyright (c) {year} Skyward Experimental Rocketry + * Authors: {authors} + * + * 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. + */ + +#pragma once + +#include "{state_machine_name}Data.h" +#include "events/FSM.h" +#include "LoggerService/LoggerService.h" + +namespace {main_namespace} +{{ + +/** + * @brief {state_machine_name} state machine + */ +class {state_machine_name}Controller : public FSM<{state_machine_name}Controller> +{{ +public: + {state_machine_name}Controller(); + ~{state_machine_name}Controller(); + +{states_funcions_declarations} + +private: + {state_machine_name}ControllerStatus status; + LoggerService& logger = *(LoggerService::getInstance()); + +{utility_functions_declarations} + + /* --- LOGGER --- */ + + void logStatus({state_machine_name}ControllerState state); + void logStatus(); +}}; + +}} // namespace {main_namespace} \ No newline at end of file diff --git a/scripts/generators/templates/FSMData.h.template b/scripts/generators/templates/FSMData.h.template new file mode 100644 index 0000000000000000000000000000000000000000..b2bb851febc395f5f2b633241b83a55c4c5f3167 --- /dev/null +++ b/scripts/generators/templates/FSMData.h.template @@ -0,0 +1,60 @@ +/* Copyright (c) {year} Skyward Experimental Rocketry + * Authors: {authors} + * + * 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. + */ + +#pragma once + +#include <stdint.h> + +#include <iostream> +#include <string> + +namespace {main_namespace} +{{ + +/** + * Enum defining the possibile FSM states. + */ +enum {state_machine_name}ControllerState : uint8_t +{{ +{states} +}}; + +/** + * Structure defining the overall controller status. + */ +struct {state_machine_name}ControllerStatus +{{ + long long timestamp; + {state_machine_name}ControllerState state; + + static std::string header() + {{ + return "timestamp,state\n"; + }} + + void print(std::ostream& os) const + {{ + os << timestamp << "," << (int)state << "\n"; + }} +}}; + +}} // namespace {main_namespace} diff --git a/scripts/homeone/event_header_generator/Topics.h.template b/scripts/generators/templates/Topics.h.template similarity index 75% rename from scripts/homeone/event_header_generator/Topics.h.template rename to scripts/generators/templates/Topics.h.template index 43e72f7302673cf61bbb3a6605681feb08cf7236..97c6448c83a044411e79dec4ba8f52a460384300 100644 --- a/scripts/homeone/event_header_generator/Topics.h.template +++ b/scripts/generators/templates/Topics.h.template @@ -1,5 +1,5 @@ -/* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2018-{year} Skyward Experimental Rocketry + * Authors: Luca Erbetta, Alvise de' Faveri Tron * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -26,26 +26,26 @@ ****************************************************************************** */ -// Generated from: {sheet_link} -// Autogen date: {date} +// Autogen date: {date} -#ifndef SRC_SHARED_BOARDS_HOMEONE_TOPICS_H -#define SRC_SHARED_BOARDS_HOMEONE_TOPICS_H +#pragma once -#include <stdint.h> +#include <cstdint> #include <string> +#include <vector> -using std::map; using std::string; -namespace HomeoneBoard -{{ /** * Definition of various event topics to use in the EventBroker */ enum Topics : uint8_t {{ -{enum_data} +{topic_list} +}}; + +const std::vector<uint8_t> TOPIC_LIST{{ +{topic_list} }}; /** @@ -54,8 +54,4 @@ enum Topics : uint8_t * @param event * @return string */ -string getTopicString(uint8_t topic); - -}} // namespace HomeoneBoard - -#endif /* SRC_SHARED_BOARDS_HOMEONE_TOPICS_H_ */ +string getTopicString(uint8_t topic); \ No newline at end of file diff --git a/scripts/generators/templates/state_function_definition.template b/scripts/generators/templates/state_function_definition.template new file mode 100644 index 0000000000000000000000000000000000000000..80522fb70a5ffa66eaf361b3332521f1bf90b363 --- /dev/null +++ b/scripts/generators/templates/state_function_definition.template @@ -0,0 +1,27 @@ +void {state_machine_name}Controller::state_{state_name}(const Event& ev) +{{ + switch (ev.sig) + {{ + case EV_ENTRY: + {{ +{onentry_actions} + + logStatus({current_state}); + + TRACE("[{state_machine_name}] entering state {state_name}\n"); + break; + }} + case EV_EXIT: + {{ +{onexit_actions} + + TRACE("[{state_machine_name}] exiting state {state_name}\n"); + break; + }} +{event_cases} + default: + {{ + break; + }} + }} +}} \ No newline at end of file diff --git a/scripts/generators/templates/test.cpp.template b/scripts/generators/templates/test.cpp.template new file mode 100644 index 0000000000000000000000000000000000000000..4fc96e72bcd7c57462c6899f04c0681916330a0a --- /dev/null +++ b/scripts/generators/templates/test.cpp.template @@ -0,0 +1,66 @@ +/* Copyright (c) {year} Skyward Experimental Rocketry + * Authors: {authors} + * + * 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. + */ + +#ifdef STANDALONE_CATCH1_TEST +#include "catch/catch-tests-entry.cpp" +#endif + +// We need access to the handleEvent(...) function in state machines in order to +// test them synchronously +#define protected public + +#include <miosix.h> + +#include <utils/testutils/catch.hpp> + +#include "{state_machine_name}Controller/{state_machine_name}Controller.h" +#include "events/Events.h" +#include "utils/testutils/TestHelper.h" + +using miosix::Thread; +using namespace DeathStackBoard; + +class {state_machine_name}ControllerFixture +{{ +public: + // This is called at the beginning of each test / section + {state_machine_name}ControllerFixture() + {{ + controller = new {state_machine_name}Controller(); + sEventBroker->start(); + controller->start(); + }} + + // This is called at the end of each test / section + ~{state_machine_name}ControllerFixture() + {{ + controller->stop(); + sEventBroker->unsubscribe(controller); + sEventBroker->clearDelayedEvents(); + delete controller; + }} + +protected: + {state_machine_name}Controller* controller; +}}; + +{test_cases} \ No newline at end of file diff --git a/scripts/homeone/event_header_generator/README.md b/scripts/homeone/event_header_generator/README.md deleted file mode 100644 index f87e46c67ebcec406b02c58e10dbe91b00a0c8d3..0000000000000000000000000000000000000000 --- a/scripts/homeone/event_header_generator/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Events Generator Script - -This script generates the Events.h and Topics.h heander file from a GoogleSheets document on Google Drive. - -To execute the script: - -``` -pip install --upgrade google-api-python-client -pip install oauth2client -python scripts/homeone/event_header_generator/event_generator.py -``` - -The first time a browser window should open asking for your credentials (use the Skyward ones). -If it doesn't and only gives you an error, try executing it by clicking on the icon. \ No newline at end of file diff --git a/scripts/homeone/event_header_generator/credentials.json b/scripts/homeone/event_header_generator/credentials.json deleted file mode 100644 index dae318afc5661116b82fe75f80f4316e538e670a..0000000000000000000000000000000000000000 --- a/scripts/homeone/event_header_generator/credentials.json +++ /dev/null @@ -1 +0,0 @@ -{"installed":{"client_id":"1025168905991-tv31etsgm3lecodc5c798shqciekad40.apps.googleusercontent.com","project_id":"homeone-event-ge-1540834105298","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://www.googleapis.com/oauth2/v3/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"Yhaf67HuHR4DyXZKNXWu2lre","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}} \ No newline at end of file diff --git a/scripts/homeone/event_header_generator/event_generator.py b/scripts/homeone/event_header_generator/event_generator.py deleted file mode 100755 index e895e6fc1bf6ee8615103c51250e5e85ae4a241a..0000000000000000000000000000000000000000 --- a/scripts/homeone/event_header_generator/event_generator.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/python3 - -# Copyright (c) 2018 Skyward Experimental Rocketry -# Authors: Luca Erbetta -# -# 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. -# - -from googleapiclient.discovery import build -from httplib2 import Http -from oauth2client import file, client, tools - -import re -import datetime -import sys -from os.path import join -import os - -OUTPUT_FOLDER = "generated" -SCOPES = 'https://www.googleapis.com/auth/spreadsheets.readonly' - -service = None - -SPREADSHEET_ID = '12TecOmDd7Uot-MvXkCbhDJRU48-XO6s5ChKDlr4AOvI' -EVENTS_RANGE_NAME = 'EventList!A2:A' -TOPICS_RANGE_NAME = 'Topics!B3:B' - - -def auth(): - try: - store = file.Storage('store.json') - creds = store.get() - if not creds or creds.invalid: - flow = client.flow_from_clientsecrets('credentials.json', SCOPES) - creds = tools.run_flow(flow, store) - - global service - service = build('sheets', 'v4', http=creds.authorize(Http())) - return True - except: - print("Authentication error:", sys.exc_info()[0]) - return False - - -def load_events(): - result = service.spreadsheets().values().get(spreadsheetId=SPREADSHEET_ID, - range=EVENTS_RANGE_NAME).execute() - lines = result.get('values', []) - - # Event names start with EV_ and contains only uppercase letters or underscores - re_event = re.compile(r'(?P<ev>^EV_([A-Z_]+)+)') - - # Only return lines with valid events, remove additional data - events = [] - for l in lines: - m = re_event.match(l[0]) - if m is not None: - events.append(m.group('ev')) - else: - print("Skipped line containing invalid event: {}".format(l)) - - return events - - -def load_topics(): - result = service.spreadsheets().values().get(spreadsheetId=SPREADSHEET_ID, - range=TOPICS_RANGE_NAME).execute() - lines = result.get('values', []) - re_topics = re.compile(r'(?P<topic>^TOPIC_([A-Z_]+)+)') - - # Only return lines with valid topics, remove additional data - topics = [] - for l in lines: - m = re_topics.match(l[0]) - if m is not None: - topics.append(m.group('topic')) - else: - print("Skipped line containing invalid topics: {}".format(l)) - - return topics - - -def has_duplicates(lst): - if len(lst) != len(set(lst)): - return True - return False - - - -print("Homeone on-board software event header generator v0.2") -print("Google sheets API auth in progress...") - -if auth(): - print("Auth successfull.") -else: - exit() - -#directory = os.path.dirname(OUTPUT_FOLDER) -if not os.path.exists(OUTPUT_FOLDER): - os.mkdir(OUTPUT_FOLDER) - -print("Reading from: https://docs.google.com/spreadsheets/d/12TecOmDd7Uot-MvXkCbhDJRU48-XO6s5ChKDlr4AOvI") - -events = load_events() -topics = load_topics() - -# Check duplicates -if has_duplicates(events): - print("Duplicate events found! Terminating.") - exit() - -if has_duplicates(topics): - print("Duplicate topics found! Terminating.") - exit() - -print("{} events loaded.".format(len(events))) -print("{} topics loaded.".format(len(topics))) - -enum_str = "" -event_map_str = "" - -date = datetime.datetime.now() -link = "https://docs.google.com/spreadsheets/d/{id}".format(id=SPREADSHEET_ID) - - -# Events.h generation - -print("Generating Events.h...") - -for e in events: - endl = ",\n" if e != events[-1] else "" - enum_str += " " + e + \ - (" = EV_FIRST_SIGNAL" if e == events[0] else "") + endl - event_map_str += " {{ {event}, {string} }}{endl}".format( - event=e, string='"' + e + '"', endl=endl) - -with open('Events.h.template', 'r') as template_file: - template = template_file.read() - -template = template.format(sheet_link=link, date=date, - enum_data=enum_str) - -with open(join(OUTPUT_FOLDER, 'Events.h'), 'w') as header_file: - header_file.write(template) - -print("Events.h successfully generated.") -# Topics.h generation - -print("Generating Topics.h...") -enum_str = "" -topic_map_str = "" - -for t in topics: - endl = ",\n" if t != topics[-1] else "" - enum_str += " " + t + endl - topic_map_str += " {{ {topics}, {string} }}{endl}".format( - topics=t, string='"' + t + '"', endl=endl) - -with open('Topics.h.template', 'r') as template_file: - template = template_file.read() - -template = template.format(sheet_link=link, date=date, - enum_data=enum_str) - -with open(join(OUTPUT_FOLDER, 'Topics.h'), 'w') as header_file: - header_file.write(template) - -with open('EventFunctions.cpp.template', 'r') as cpp_template_file: - cpp = cpp_template_file.read() - -cpp = cpp.format(sheet_link=link, date=date, - event_map_data=event_map_str, topic_map_data=topic_map_str) - -with open(join(OUTPUT_FOLDER, 'EventFunctions.cpp'), 'w') as cpp_file: - cpp_file.write(cpp) - -print("Topics.h successfully generated.") - -print() -print("All files successfully generated. Please move Events.h, Topics.h, EventFunction.cpp into the Homeone board folder.") -print(".... Done.") diff --git a/scripts/linter.sh b/scripts/linter.sh index 70dd0adb8d12a393cc67d72cc2051353b27c2b22..ae9e1e73a401e5debf1f3377e8676e5fc95f29a4 100755 --- a/scripts/linter.sh +++ b/scripts/linter.sh @@ -1,18 +1,83 @@ #!/bin/bash +# +# Usage: grepcheck <FOLDER_TO_CHECK> <STRING_TO_FIND> <EGREP_ARGS> +# +function grepcheck { + + FILES=$(grep -rl "$2" $1 | egrep "$3") + + for f in $FILES + do + # Print name of the file in red + echo -ne '\033\x5b\x33\x33\x6d' + + echo $f + echo -ne '\033\x5b\x30\x6d' + + if [[ $verbose -eq 1 ]]; then + grep --color=always -r "$2" $f -C3 + echo '' + fi + done + + echo '' + + # If VERBOSE is defined then also print the context in the file +} + +# Horrifying way of checking if -v or --verbose is enabled +verbose=0 + +case "$1" in +-v|--verbose) + verbose=1 + shift ;; +esac + +case "$2" in +-v|--verbose) + verbose=1 +esac + +# Execute checks +echo '--------------------------- CPPCHECK' echo '[Linter] Executing cppcheck' -cppcheck --template=gcc -q --std=c++11 --enable=all \ +echo '' +cppcheck -U "RCC_APB1ENR_CAN2EN" --template=gcc -q --std=c++11 --enable=all \ --suppress=unusedFunction --suppress=missingInclude --suppress=noExplicitConstructor \ - "$@" 2>&1 | awk ' + "$1" 2>&1 | awk ' function color(c,s) { printf("\033[%dm%s\033[0m\n",30+c,s) } - /warning/ {color(1,$0);next} + /error/ {color(1,$0);next} + /warning/ {color(3,$0);next} /style/ {color(2,$0);next} - /performance/ {color(3,$0);next} + /performance/ {color(6,$0);next} /information/ {color(4,$0);next} /portability/ {color(5,$0);next} {print} ' +echo '' + +echo '--------------------------- USING NAMESPACE' +echo '[Linter] Checking for using namespace in .h(pp) files' + +grepcheck $1 'using namespace' '.h(pp)?$' + +echo '--------------------------- GREP PRINTF' +echo '[Linter] Checking for printf instead of TRACE' + +grepcheck $1 "printf" '.*' + +echo '--------------------------- GREP ASSERT' +echo '[Linter] Checking if there are asserts in the code' + +grepcheck $1 "assert" '.*' + +echo '--------------------------- GREP THROW' +echo '[Linter] Checking if there are exceptions in the code' + +grepcheck $1 "throw" '~*catch.hpp' #exit 0 diff --git a/scripts/homeone/mavlink_client/Makefile b/scripts/mavlink_client/Makefile similarity index 100% rename from scripts/homeone/mavlink_client/Makefile rename to scripts/mavlink_client/Makefile diff --git a/scripts/mavlink_client/README.md b/scripts/mavlink_client/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e8cd75de7fbd87bf075a2506218e1de6d4be6a3c --- /dev/null +++ b/scripts/mavlink_client/README.md @@ -0,0 +1,14 @@ +# Mavlink Client + +These scripts can be used to transmit and receive mavlink data from a PC, either +connected via serial port to a transceiver or directly connected to one of the boards. + +Use `make` to compile the 3 `.c` files: + +* continuous_tx: sends a message of raw bytes with a given length at a certain +frequency. This was used to test the LoRa modules. +* mavlink_demo_tx: sends a message whenever it receives a char on stdin +* mavlink_demo_rx: receives and prints packets, eventually sending back an ACK. + +`plot.py`: reads value on stdin and plots them. Can be used piped with the output +of mavlink_demo_rx. \ No newline at end of file diff --git a/scripts/homeone/mavlink_client/continuous_tx.c b/scripts/mavlink_client/continuous_tx.c similarity index 97% rename from scripts/homeone/mavlink_client/continuous_tx.c rename to scripts/mavlink_client/continuous_tx.c index bf97e2f0b1ba01dfd5bca65087a44d4ca2aafe17..e477fb533d055d9d18f6a324c80158156428fe93 100644 --- a/scripts/homeone/mavlink_client/continuous_tx.c +++ b/scripts/mavlink_client/continuous_tx.c @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/scripts/homeone/mavlink_client/mavlink_demo_rx.c b/scripts/mavlink_client/mavlink_demo_rx.c similarity index 98% rename from scripts/homeone/mavlink_client/mavlink_demo_rx.c rename to scripts/mavlink_client/mavlink_demo_rx.c index e33145dfb2ba3eb00163ba5c0329326d3e5bdfc5..95c49aa44d440abf09822dd0889435438775b51b 100644 --- a/scripts/homeone/mavlink_client/mavlink_demo_rx.c +++ b/scripts/mavlink_client/mavlink_demo_rx.c @@ -1,6 +1,6 @@ /************************************************** -Ssimple demo that receives mavlink +Simple demo that receives mavlink messages on the serial port and prints them on the screen. **************************************************/ diff --git a/scripts/homeone/mavlink_client/mavlink_demo_tx.c b/scripts/mavlink_client/mavlink_demo_tx.c similarity index 100% rename from scripts/homeone/mavlink_client/mavlink_demo_tx.c rename to scripts/mavlink_client/mavlink_demo_tx.c diff --git a/scripts/homeone/mavlink_client/plot.py b/scripts/mavlink_client/plot.py similarity index 100% rename from scripts/homeone/mavlink_client/plot.py rename to scripts/mavlink_client/plot.py diff --git a/scripts/homeone/mavlink_client/rs232.c b/scripts/mavlink_client/rs232.c similarity index 100% rename from scripts/homeone/mavlink_client/rs232.c rename to scripts/mavlink_client/rs232.c diff --git a/scripts/homeone/mavlink_client/rs232.h b/scripts/mavlink_client/rs232.h similarity index 100% rename from scripts/homeone/mavlink_client/rs232.h rename to scripts/mavlink_client/rs232.h diff --git a/scripts/openocd-flash.sh b/scripts/openocd/openocd-flash.sh similarity index 100% rename from scripts/openocd-flash.sh rename to scripts/openocd/openocd-flash.sh diff --git a/scripts/start_openocd.sh b/scripts/openocd/start_openocd.sh similarity index 100% rename from scripts/start_openocd.sh rename to scripts/openocd/start_openocd.sh diff --git a/scripts/scxml2plant/scxml2plant.sh b/scripts/scxml2plant/scxml2plant.sh new file mode 100755 index 0000000000000000000000000000000000000000..dbd1f04907a38ead88b5e27d8ae037cf95b88bac --- /dev/null +++ b/scripts/scxml2plant/scxml2plant.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Input checks +if [ "$#" -ne 1 ]; then + echo "Usage: scxml2plant <FOLDER_TO_SEARCH>" + exit 1 +fi + +# Find this folder when calling the script from another folder +PATH_TO_XALAN=$(dirname $0) + +# Select all scxml files +for file in $(find $1 -name "*.scxml") +do + # generate plantuml + INPUT_XML=$file + OUTPUT_NAME=$(dirname $file)/$(basename -s ".scxml" $file).plantuml + echo "Input file: " $INPUT_XML " Output basename: " $OUTPUT_NAME + + java -cp $PATH_TO_XALAN/xalan/xalan.jar \ + -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XIncludeParserConfiguration org.apache.xalan.xslt.Process \ + -IN $INPUT_XML -XSL $PATH_TO_XALAN/xslt/scxml2plantuml.xslt -OUT $OUTPUT_NAME + + # generate README + README_FILE=$(dirname $file)/README.md + + touch $README_FILE + echo "\`\`\`plantuml" > $README_FILE + cat $OUTPUT_NAME >> $README_FILE + echo "\`\`\`" >> $README_FILE + rm $OUTPUT_NAME +done diff --git a/scripts/scxml2plant/xalan/LICENSE.txt b/scripts/scxml2plant/xalan/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..47ad566a4592992bb8c6cfe6bda017487dda8cf5 --- /dev/null +++ b/scripts/scxml2plant/xalan/LICENSE.txt @@ -0,0 +1,684 @@ +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +The license above applies to this Apache Xalan release of: + Xalan-Java 2 - XSLT Processor + Xalan-Java 2 - Serializer + +The license above also applies to the jar files +xalan.jar and xsltc.jar - Xalan-Java 2 - XSLT Processor from +Source: http://xalan.apache.org/ + +The license above also applies to the jar file +serializer.jar - Xalan-Java 2 - Serializer +Source: http://xalan.apache.org/ +Used by: Xalan-Java 2 and Xerces-Java 2 + +The license above also applies to the jar file +xercesImpl.jar - Xerces-Java 2 XML Parser. +Source: http://xerces.apache.org/ +Used by: Xalan-Java 2 + +The license above also applies to the jar file +xml-apis.jar - Xerces-Java 2 XML Parser. +Source: http://xerces.apache.org/ +Used by: Xalan-Java 2 and release copy of Xerces-Java 2 + + + + + + + + +The following license applies to the included files: + tools/ant.jar + tools/antRun + tools/antRun.bat +Source: http://ant.apache.org/ +Used By: Xalan's build process: java/build.xml and test/build.xml + +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +/* + * ============================================================================ + * The Apache Software License, Version 1.1 + * ============================================================================ + * + * Copyright (C) 1999 The Apache Software Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The end-user documentation included with the redistribution, if any, must + * include the following acknowledgment: "This product includes software + * developed by the Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, if + * and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Ant" and "Apache Software Foundation" must not be used to + * endorse or promote products derived from this software without prior + * written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", nor may + * "Apache" appear in their name, without prior written permission of the + * Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- + * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * on behalf of the Apache Software Foundation. For more information on the + * Apache Software Foundation, please see <http://www.apache.org/>. + * + */ +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + + + + + +The following license, Apache Software License, Version 1.1, +applies to the included BCEL.jar from Apache Jakarta +(Byte Code Engineering Library). +Source: http://jakarta.apache.org/bcel +Used By: XSLTC component of xml-xalan/java + +The following license, Apache Software License, Version 1.1, +also applies to the included regexp.jar, +jakarta-regexp-1.2.jar from Apache Jakarta. +Source: http://jakarta.apache.org/regexp +Used By: BCEL.jar which is used by XSLTC component of xml-xalan/java + +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +/* + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache BCEL" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache BCEL", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + + + + + +The following license applies to the DOM documentation +for the org.w3c.dom.* packages: + +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +W3C® DOCUMENT LICENSE +http://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231 +Public documents on the W3C site are provided by the copyright holders +under the following license. By using and/or copying this document, +or the W3C document from which this statement is linked, you (the licensee) +agree that you have read, understood, and will comply with the following +terms and conditions: + +Permission to copy, and distribute the contents of this document, or the +W3C document from which this statement is linked, in any medium for any +purpose and without fee or royalty is hereby granted, provided that you include +the following on ALL copies of the document, or portions thereof, that you use: + +1. A link or URL to the original W3C document. +2. The pre-existing copyright notice of the original author, or if it + doesn't exist, a notice (hypertext is preferred, but a textual representation + is permitted) of the form: "Copyright © [$date-of-document] World Wide Web + Consortium, (Massachusetts Institute of Technology, European Research + Consortium for Informatics and Mathematics, Keio University). All Rights + Reserved. http://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231" +3. If it exists, the STATUS of the W3C document. + +When space permits, inclusion of the full text of this NOTICE should be provided. +We request that authorship attribution be provided in any software, documents, +or other items or products that you create pursuant to the implementation of the +contents of this document, or any portion thereof. + +No right to create modifications or derivatives of W3C documents is granted pursuant +to this license. However, if additional requirements (documented in the Copyright FAQ) +are satisfied, the right to create modifications or derivatives is sometimes granted +by the W3C to individuals complying with those requirements. + +THIS DOCUMENT IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS +OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; +THAT THE CONTENTS OF THE DOCUMENT ARE SUITABLE FOR ANY PURPOSE; NOR THAT THE +IMPLEMENTATION OF SUCH CONTENTS WILL NOT INFRINGE ANY THIRD PARTY PATENTS, +COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. + +COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE DOCUMENT OR THE PERFORMANCE +OR IMPLEMENTATION OF THE CONTENTS THEREOF. + +The name and trademarks of copyright holders may NOT be used in advertising +or publicity pertaining to this document or its contents without specific, +written prior permission. Title to copyright in this document will at all +times remain with copyright holders. + + +---------------------------------------------------------------------------- + +This formulation of W3C's notice and license became active on December 31 2002. +This version removes the copyright ownership notice such that this license +can be used with materials other than those owned by the W3C, moves information +on style sheets, DTDs, and schemas to the Copyright FAQ, reflects that ERCIM +is now a host of the W3C, includes references to this specific dated version +of the license, and removes the ambiguous grant of "use". See the older +formulation for the policy prior to this date. Please see our Copyright FAQ for +common questions about using materials from our site, such as the translating +or annotating specifications. Other questions about this notice can be directed +to site-policy@w3.org. + + +Joseph Reagle <mailto:site-policy@w3.org +Last revised by Reagle $Date: 2005-07-19 12:33:09 -0400 (Tue, 19 Jul 2005) $ +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + + + + + + +The following license applies to the DOM software, +for the org.w3c.dom.* packages in jar file xml-apis.jar: + +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +W3C® SOFTWARE NOTICE AND LICENSE +http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 +This work (and included software, documentation such as READMEs, +or other related items) is being provided by the copyright holders +under the following license. By obtaining, using and/or copying this +work, you (the licensee) agree that you have read, understood, and will +comply with the following terms and conditions. + +Permission to copy, modify, and distribute this software and its +documentation, with or without modification, for any purpose and +without fee or royalty is hereby granted, provided that you include +the following on ALL copies of the software and documentation or +portions thereof, including modifications: + +1. The full text of this NOTICE in a location viewable to users of the + redistributed or derivative work. +2. Any pre-existing intellectual property disclaimers, notices, + or terms and conditions. If none exist, the W3C Software Short Notice + should be included (hypertext is preferred, text is permitted) within + the body of any redistributed or derivative code. +3. Notice of any changes or modifications to the files, including the + date changes were made. (We recommend you provide URIs to the location + from which the code is derived.) + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS +MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR +PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE +ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. + +COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. + +The name and trademarks of copyright holders may NOT be used in advertising +or publicity pertaining to the software without specific, written prior +permission. Title to copyright in this software and any associated documentation +will at all times remain with copyright holders. + + +____________________________________ + +This formulation of W3C's notice and license became active on December 31 2002. +This version removes the copyright ownership notice such that this license can +be used with materials other than those owned by the W3C, reflects that ERCIM +is now a host of the W3C, includes references to this specific dated version +of the license, and removes the ambiguous grant of "use". Otherwise, this +version is the same as the previous version and is written so as to preserve +the Free Software Foundation's assessment of GPL compatibility and OSI's +certification under the Open Source Definition. Please see our Copyright FAQ +for common questions about using materials from our site, including specific +terms and conditions for packages like libwww, Amaya, and Jigsaw. Other +questions about this notice can be directed to site-policy@w3.org. + + +Joseph Reagle <mailto:site-policy@w3.org +Last revised by Reagle $Date: 2005-07-19 12:33:09 -0400 (Tue, 19 Jul 2005) $ +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + + + + + +The following license applies to the SAX software, +for the org.xml.sax.* packages in jar file xml-apis.jar: + +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +This module, both source code and documentation, is in the Public Domain, +and comes with NO WARRANTY. See http://www.saxproject.org for further information. +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + + + + + +The following license applies to the jar file +java_cup.jar - LALR Parser Generator for Java(TM). +Source: http://www.cs.princeton.edu/~appel/modern/java/CUP +Used By: XSLTC component of xml-xalan/java + +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +CUP Parser Generator Copyright Notice, License, and Disclaimer + +Copyright 1996-1999 by Scott Hudson, Frank Flannery, C. Scott Ananian + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both +the copyright notice and this permission notice and warranty disclaimer +appear in supporting documentation, and that the names of the authors +or their employers not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior permission. + +The authors and their employers disclaim all warranties with regard to +this software, including all implied warranties of merchantability +and fitness. In no event shall the authors or their employers be liable +for any special, 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. +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + + + + + +The following license applies to the jar file runtime.jar - Component +of JavaCup: LALR Parser Generator for Java(TM). +Source: http://www.cs.princeton.edu/~appel/modern/java/CUP +Used By: XSLTC component of xml-xalan/java + +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +CUP Parser Generator Copyright Notice, License, and Disclaimer +(runtime.jar component) + +Copyright 1996-1999 by Scott Hudson, Frank Flannery, C. Scott Ananian + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both +the copyright notice and this permission notice and warranty disclaimer +appear in supporting documentation, and that the names of the authors +or their employers not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior permission. + +The authors and their employers disclaim all warranties with regard to +this software, including all implied warranties of merchantability +and fitness. In no event shall the authors or their employers be liable +for any special, 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. +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + + + + + +The following license applies to the JLEX jar file +JLex.jar - A Lexical Analyzer Generator for Java(TM). +Source: http://www.cs.princeton.edu/~appel/modern/java/JLex +Used By: XSLTC component of xml-xalan/java + +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +JLEX COPYRIGHT NOTICE, LICENSE AND DISCLAIMER. + +Copyright 1996-2000 by Elliot Joel Berk and C. Scott Ananian + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and +that both the copyright notice and this permission notice and +warranty disclaimer appear in supporting documentation, and that the +name of the authors or their employers not be used in advertising or +publicity pertaining to distribution of the software without specific, +written prior permission. + +The authors and their employers disclaim all warranties with regard +to this software, including all implied warranties of merchantability and +fitness. In no event shall the authors or their employers be liable for any +special, 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. + +Java is a trademark of Sun Microsystems, Inc. References to the Java +programming language in relation to JLex are not meant to imply that +Sun endorses this product. +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + + + + + +The following license applies to the jar file +stylebook-1.0-b3_xalan-2.jar - Tool for generating Xalan documentation. +Integrated with Xalan-Java 2 and Xerces 2. +Source: http://svn.apache.org/viewvc/xml/stylebook/ +Used by: Xalan-Java 2, Xalan-C++ + +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 1999 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan", "Xerces", and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 1999, International + * Business Machines, Inc., http://www.apache.org. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> diff --git a/scripts/scxml2plant/xalan/NOTICE.txt b/scripts/scxml2plant/xalan/NOTICE.txt new file mode 100644 index 0000000000000000000000000000000000000000..132f8bccd53579fb004f2b5d095588d3ff9e832a --- /dev/null +++ b/scripts/scxml2plant/xalan/NOTICE.txt @@ -0,0 +1,80 @@ + ========================================================================= + == NOTICE file corresponding to section 4(d) of the Apache License, == + == Version 2.0, in this case for the Apache Xalan Java distribution. == + ========================================================================= + + Apache Xalan (Xalan XSLT processor) + Copyright 1999-2014 The Apache Software Foundation + + Apache Xalan (Xalan serializer) + Copyright 1999-2012 The Apache Software Foundation + + This product includes software developed at + The Apache Software Foundation (http://www.apache.org/). + + ========================================================================= + Portions of this software was originally based on the following: + - software copyright (c) 1999-2002, Lotus Development Corporation., + http://www.lotus.com. + - software copyright (c) 2001-2002, Sun Microsystems., + http://www.sun.com. + - software copyright (c) 2003, IBM Corporation., + http://www.ibm.com. + + ========================================================================= + The binary distribution package (ie. jars, samples and documentation) of + this product includes software developed by the following: + + - The Apache Software Foundation + - Xerces Java - see LICENSE.txt + - JAXP 1.3 APIs - see LICENSE.txt + - Bytecode Engineering Library - see LICENSE.txt + - Regular Expression - see LICENSE.txt + + - Scott Hudson, Frank Flannery, C. Scott Ananian + - CUP Parser Generator runtime (javacup\runtime) - see LICENSE.txt + + ========================================================================= + The source distribution package (ie. all source and tools required to build + Xalan Java) of this product includes software developed by the following: + + - The Apache Software Foundation + - Xerces Java - see LICENSE.txt + - JAXP 1.3 APIs - see LICENSE.txt + - Bytecode Engineering Library - see LICENSE.txt + - Regular Expression - see LICENSE.txt + - Ant - see LICENSE.txt + - Stylebook doc tool - see LICENSE.txt + + - Elliot Joel Berk and C. Scott Ananian + - Lexical Analyzer Generator (JLex) - see LICENSE.txt + + ========================================================================= + Apache Xerces Java + Copyright 1999-2006 The Apache Software Foundation + + This product includes software developed at + The Apache Software Foundation (http://www.apache.org/). + + Portions of Apache Xerces Java in xercesImpl.jar and xml-apis.jar + were originally based on the following: + - software copyright (c) 1999, IBM Corporation., http://www.ibm.com. + - software copyright (c) 1999, Sun Microsystems., http://www.sun.com. + - voluntary contributions made by Paul Eng on behalf of the + Apache Software Foundation that were originally developed at iClick, Inc., + software copyright (c) 1999. + + ========================================================================= + Apache xml-commons xml-apis (redistribution of xml-apis.jar) + + Apache XML Commons + Copyright 2001-2003,2006 The Apache Software Foundation. + + This product includes software developed at + The Apache Software Foundation (http://www.apache.org/). + + Portions of this software were originally based on the following: + - software copyright (c) 1999, IBM Corporation., http://www.ibm.com. + - software copyright (c) 1999, Sun Microsystems., http://www.sun.com. + - software copyright (c) 2000 World Wide Web Consortium, http://www.w3.org + diff --git a/scripts/scxml2plant/xalan/readme.html b/scripts/scxml2plant/xalan/readme.html new file mode 100644 index 0000000000000000000000000000000000000000..538f4286b0308b84b3c4dcfbeb6937ab2d3d56a4 --- /dev/null +++ b/scripts/scxml2plant/xalan/readme.html @@ -0,0 +1,39 @@ +<!-- + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> + +<html> +<head> +<title> +</title> +</head> +<body> +<meta content="0; URL=docs/whatsnew.html" http-equiv="Refresh"> + Redirecting to <a href="docs/whatsnew.html">What's New in Xalan_Java 2</a> +</body> +</html> + + + + + + + + + + + + diff --git a/scripts/scxml2plant/xalan/serializer.jar b/scripts/scxml2plant/xalan/serializer.jar new file mode 100644 index 0000000000000000000000000000000000000000..175126880ff58fab0922f3e0cd25ecff765989ba Binary files /dev/null and b/scripts/scxml2plant/xalan/serializer.jar differ diff --git a/scripts/scxml2plant/xalan/xalan.jar b/scripts/scxml2plant/xalan/xalan.jar new file mode 100644 index 0000000000000000000000000000000000000000..5c6c6f947071ef42c8d984f13ace5884e2bc7f03 Binary files /dev/null and b/scripts/scxml2plant/xalan/xalan.jar differ diff --git a/scripts/scxml2plant/xalan/xercesImpl.jar b/scripts/scxml2plant/xalan/xercesImpl.jar new file mode 100644 index 0000000000000000000000000000000000000000..0aaa990f3ecadf60d28b5395dc87bbe49da0cdd7 Binary files /dev/null and b/scripts/scxml2plant/xalan/xercesImpl.jar differ diff --git a/scripts/scxml2plant/xalan/xml-apis.jar b/scripts/scxml2plant/xalan/xml-apis.jar new file mode 100644 index 0000000000000000000000000000000000000000..46733464fc746776c331ecc51061f3a05e662fd1 Binary files /dev/null and b/scripts/scxml2plant/xalan/xml-apis.jar differ diff --git a/scripts/scxml2plant/xalan/xsltc.jar b/scripts/scxml2plant/xalan/xsltc.jar new file mode 100644 index 0000000000000000000000000000000000000000..e11dbeca0aa04a138ea1a5fb29ed331ed02191f6 Binary files /dev/null and b/scripts/scxml2plant/xalan/xsltc.jar differ diff --git a/scripts/scxml2plant/xslt/scxml2plantuml.xslt b/scripts/scxml2plant/xslt/scxml2plantuml.xslt new file mode 100644 index 0000000000000000000000000000000000000000..8468b2c3f684ab52e06678ff41d747407b5751a3 --- /dev/null +++ b/scripts/scxml2plant/xslt/scxml2plantuml.xslt @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="utf-8"?> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:sc="http://www.w3.org/2005/07/scxml"> + + <xsl:output method="text" encoding="utf-8"/> + <xsl:strip-space elements="*" /> + <xsl:template match="text()|@*"/> + <xsl:template match="text()|@*" mode="target"/> + <xsl:template match="text()|@*" mode="id"/> + + <xsl:template match="/sc:scxml"> + <xsl:text>@startuml
</xsl:text> + <xsl:apply-templates select="@initial"/> + <xsl:apply-templates select="*"/> + <xsl:text>@enduml
</xsl:text> + </xsl:template> + + <xsl:template match="@initial"> + <xsl:text>[*] --> </xsl:text> + <xsl:call-template name="gettargetname"> + <xsl:with-param name="id" select="."/> + </xsl:call-template> + <xsl:text>
</xsl:text> + </xsl:template> + + <xsl:template match="sc:initial"> + <xsl:apply-templates select="*"/> + </xsl:template> + + <xsl:template match="sc:final[@id]"> + <xsl:value-of select="concat(@id, ' : <<final>>
')"/> + </xsl:template> + <xsl:template match="sc:final"> + <xsl:text>unnamed : <<final>>
</xsl:text> + </xsl:template> + + <xsl:template match="sc:state"> + <xsl:text>state </xsl:text> + <xsl:apply-templates select="." mode="id"/> + <xsl:text> {
</xsl:text> + + <xsl:apply-templates select="@initial"/> + <xsl:apply-templates select="*"/> + + <xsl:text>}
</xsl:text> + </xsl:template> + + <xsl:template match="sc:history"> + <xsl:text>state </xsl:text> + <xsl:apply-templates select="." mode="id"/> + <xsl:text> {
</xsl:text> + + <xsl:apply-templates select="*"/> + + <xsl:apply-templates select="." mode="id"/> + <xsl:value-of select="concat(' : <<', @type, ' history>>
')"/> + + <xsl:text>}
</xsl:text> + </xsl:template> + + <xsl:template match="sc:parallel"> + <xsl:text>state </xsl:text> + <xsl:apply-templates select="." mode="id"/> + <xsl:text> {
</xsl:text> + + <xsl:for-each select="sc:state | sc:parallel | sc:history"> + <xsl:apply-templates select="."/> + <xsl:if test="position() != last()"> + <xsl:text>||
</xsl:text> + </xsl:if> + </xsl:for-each> + + <xsl:apply-templates select="sc:transition"/> + + <xsl:text>}
</xsl:text> + </xsl:template> + + + <!--get id of the state - special case for initial state--> + <xsl:template match="sc:state[@id] | sc:history[@id] | sc:parallel[@id] | sc:final[@id]" mode="id"> + <xsl:value-of select="@id"/> + </xsl:template> + <xsl:template match="sc:state | sc:history | sc:parallel" mode="id"> + <xsl:text>unnamed</xsl:text> + </xsl:template> + <xsl:template match="sc:initial" mode="id"> + <xsl:text>[*]</xsl:text> + </xsl:template> + + <xsl:template name="gettargetname"> + <xsl:param name="id"/> + <xsl:apply-templates select="//*[@id = $id][1]" mode="id"/> + </xsl:template> + + <!--target can be specified explicitely as some other state--> + <xsl:template match="sc:transition[@target]" mode="target"> + <xsl:call-template name="gettargetname"> + <xsl:with-param name="id" select="@target"/> + </xsl:call-template> + </xsl:template> + <!--or self-transiton when @target not specified--> + <xsl:template match="sc:transition" mode="target"> + <xsl:apply-templates select=".." mode="id"/> + </xsl:template> + + <!--if 'cond' attribute exists it shall be sourrounded with brackets--> + <xsl:template match="sc:transition/@cond"> + <xsl:value-of select="concat('[', ., ']')"/> + </xsl:template> + + <xsl:template match="sc:transition"> + <xsl:apply-templates select=".." mode="id"/> + <xsl:text> --> </xsl:text> + <xsl:apply-templates select="." mode="target"/> + <xsl:if test="@event or @cond"> + <xsl:text> : </xsl:text> + </xsl:if> + <xsl:value-of select="@event"/> + <xsl:apply-templates select="@cond"/> + <xsl:text>
</xsl:text> + </xsl:template> + +</xsl:stylesheet> diff --git a/scripts/scxml2plant/xslt/scxml2plantuml_finaldot.xslt b/scripts/scxml2plant/xslt/scxml2plantuml_finaldot.xslt new file mode 100644 index 0000000000000000000000000000000000000000..f9050a05ddb1f253be2cea26a355830028cbe939 --- /dev/null +++ b/scripts/scxml2plant/xslt/scxml2plantuml_finaldot.xslt @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="utf-8"?> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:sc="http://www.w3.org/2005/07/scxml"> + + <xsl:output method="text" encoding="utf-8"/> + <xsl:strip-space elements="*" /> + <xsl:template match="text()|@*"/> + <xsl:template match="text()|@*" mode="target"/> + <xsl:template match="text()|@*" mode="id"/> + + <xsl:template match="/sc:scxml"> + <xsl:text>@startuml
</xsl:text> + <xsl:apply-templates select="@initial"/> + <xsl:apply-templates select="*"/> + <xsl:text>@enduml
</xsl:text> + </xsl:template> + + <xsl:template match="@initial"> + <xsl:text>[*] --> </xsl:text> + <xsl:call-template name="gettargetname"> + <xsl:with-param name="id" select="."/> + </xsl:call-template> + <xsl:text>
</xsl:text> + </xsl:template> + + <xsl:template match="sc:initial"> + <xsl:apply-templates select="*"/> + </xsl:template> + + <xsl:template match="sc:state"> + <xsl:text>state </xsl:text> + <xsl:apply-templates select="." mode="id"/> + <xsl:text> {
</xsl:text> + + <xsl:apply-templates select="@initial"/> + <xsl:apply-templates select="*"/> + + <xsl:text>}
</xsl:text> + </xsl:template> + + <xsl:template match="sc:history"> + <xsl:text>state </xsl:text> + <xsl:apply-templates select="." mode="id"/> + <xsl:text> {
</xsl:text> + + <xsl:apply-templates select="*"/> + + <xsl:apply-templates select="." mode="id"/> + <xsl:value-of select="concat(' : <<', @type, ' history>>
')"/> + + <xsl:text>}
</xsl:text> + </xsl:template> + + <xsl:template match="sc:parallel"> + <xsl:text>state </xsl:text> + <xsl:apply-templates select="." mode="id"/> + <xsl:text> {
</xsl:text> + + <xsl:for-each select="sc:state | sc:parallel | sc:history"> + <xsl:apply-templates select="."/> + <xsl:if test="position() != last()"> + <xsl:text>||
</xsl:text> + </xsl:if> + </xsl:for-each> + + <xsl:apply-templates select="sc:transition"/> + + <xsl:text>}
</xsl:text> + </xsl:template> + + + <!--get id of the state - special case for initial state--> + <xsl:template match="sc:state[@id] | sc:history[@id] | sc:parallel[@id]" mode="id"> + <xsl:value-of select="@id"/> + </xsl:template> + <xsl:template match="sc:state | sc:history | sc:parallel" mode="id"> + <xsl:text>unnamed</xsl:text> + </xsl:template> + <xsl:template match="sc:initial | sc:final" mode="id"> + <xsl:text>[*]</xsl:text> + </xsl:template> + + <xsl:template name="gettargetname"> + <xsl:param name="id"/> + <xsl:apply-templates select="//*[@id = $id][1]" mode="id"/> + </xsl:template> + + <!--target can be specified explicitely as some other state--> + <xsl:template match="sc:transition[@target]" mode="target"> + <xsl:call-template name="gettargetname"> + <xsl:with-param name="id" select="@target"/> + </xsl:call-template> + </xsl:template> + <!--or self-transiton when @target not specified--> + <xsl:template match="sc:transition" mode="target"> + <xsl:apply-templates select=".." mode="id"/> + </xsl:template> + + <!--if 'cond' attribute exists it shall be sourrounded with brackets--> + <xsl:template match="sc:transition/@cond"> + <xsl:value-of select="concat('[', ., ']')"/> + </xsl:template> + + <xsl:template match="sc:transition"> + <xsl:apply-templates select=".." mode="id"/> + <xsl:text> --> </xsl:text> + <xsl:apply-templates select="." mode="target"/> + <xsl:if test="@event or @cond"> + <xsl:text> : </xsl:text> + </xsl:if> + <xsl:value-of select="@event"/> + <xsl:apply-templates select="@cond"/> + <xsl:text>
</xsl:text> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/entrypoints/anakin/anakin-accel.cpp b/src/entrypoints/anakin/anakin-accel.cpp index 01bc91ec214a122d97d17fc079d7455cee7aa3bd..9d22fa1e89177b093c2aa30dad997e8e5e53e191 100644 --- a/src/entrypoints/anakin/anakin-accel.cpp +++ b/src/entrypoints/anakin/anakin-accel.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci, Matteo Michele Piazzolla + * Authors: Alain Carlucci, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/anakin-demo-board.cpp b/src/entrypoints/anakin/anakin-demo-board.cpp index 2129e7dccd9e84a4ab0a897afa5fc731fff7456e..a51bc42a4f8e26ba524224d0399ae31054a08e44 100644 --- a/src/entrypoints/anakin/anakin-demo-board.cpp +++ b/src/entrypoints/anakin/anakin-demo-board.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/anakin-test-canbus.cpp b/src/entrypoints/anakin/anakin-test-canbus.cpp index 37c67c435d82ffde7102eab25fd2ca2c65d06aa2..63f6884c626a04a731e6305e66f2d2835eb7ff9b 100644 --- a/src/entrypoints/anakin/anakin-test-canbus.cpp +++ b/src/entrypoints/anakin/anakin-test-canbus.cpp @@ -1,7 +1,5 @@ -/* Canbus Test ROM - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci, Matteo Michele Piazzolla +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Alain Carlucci, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/anakin-test-dma.cpp b/src/entrypoints/anakin/anakin-test-dma.cpp index b4d8e5272156c0cf871b7d3810a0de21d42fa425..03815abc7f272b3fea0c471fe82b00508d6650a4 100644 --- a/src/entrypoints/anakin/anakin-test-dma.cpp +++ b/src/entrypoints/anakin/anakin-test-dma.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2017 Skyward Experimental Rocketry - * Authors: Alain Carlucci + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/anakin-test-suite.cpp b/src/entrypoints/anakin/anakin-test-suite.cpp index 5de88b6e37b95f61518121d84e22edcf790c0a5e..3499646a6d6eb3706432ce5ddd517343fa0dcd60 100644 --- a/src/entrypoints/anakin/anakin-test-suite.cpp +++ b/src/entrypoints/anakin/anakin-test-suite.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci, Matteo Michele Piazzolla + * Authors: Alain Carlucci, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/canbus-test.cpp b/src/entrypoints/anakin/canbus-test.cpp index 3e8bfc25ad963545b02ee32db66d307546eac31f..a52cde1fa5b67700250305cd1e1ac9669da609ba 100644 --- a/src/entrypoints/anakin/canbus-test.cpp +++ b/src/entrypoints/anakin/canbus-test.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci, Matteo Michele Piazzolla + * Authors: Alain Carlucci, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/dma-lowlevel-test.cpp b/src/entrypoints/anakin/dma-lowlevel-test.cpp index caf412e38b260189b6b4b6aaf57a6d8de2601536..7e50325fc32eb15d588d1c11bd32e4c3cfb65ea3 100644 --- a/src/entrypoints/anakin/dma-lowlevel-test.cpp +++ b/src/entrypoints/anakin/dma-lowlevel-test.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2017 Skyward Experimental Rocketry - * Authors: Federico Terraneo + * Author: Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/test-busfault.cpp b/src/entrypoints/anakin/test-busfault.cpp index ee8ce6062380819afaf6c9e7820ab4482d3056ea..db743d9c73afd3b1234f576965bb78167a3b7af3 100644 --- a/src/entrypoints/anakin/test-busfault.cpp +++ b/src/entrypoints/anakin/test-busfault.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2017 Skyward Experimental Rocketry - * Authors: Alain Carlucci + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/test-scheduler.cpp b/src/entrypoints/anakin/test-scheduler.cpp index b072c2f650af919d4f9ebebd529bfcdfd6a4e488..24e828dee7b83fd608d55786c143502ae7128ad3 100644 --- a/src/entrypoints/anakin/test-scheduler.cpp +++ b/src/entrypoints/anakin/test-scheduler.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Federico Terraneo + * Authors: Matteo Piazzolla, Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/test_ethernet.cpp b/src/entrypoints/anakin/test_ethernet.cpp index d8d464e1373be3994afbd3d200066ac529f3856e..7b57358d03c086907859076082a52301298bfeb1 100644 --- a/src/entrypoints/anakin/test_ethernet.cpp +++ b/src/entrypoints/anakin/test_ethernet.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Silvano Seva + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/anakin/test_i2c.cpp b/src/entrypoints/anakin/test_i2c.cpp index 434de5dc3e0b163e09ea7c42cb6c2ef1674bf33a..dc9dcdccdee9073a61746f2a8c3879c488856efc 100644 --- a/src/entrypoints/anakin/test_i2c.cpp +++ b/src/entrypoints/anakin/test_i2c.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Silvano Seva + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/bmx160-calibration-entry.cpp b/src/entrypoints/bmx160-calibration-entry.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e0378a4a7694cd31b1b8eba8d82c93cc848bddd5 --- /dev/null +++ b/src/entrypoints/bmx160-calibration-entry.cpp @@ -0,0 +1,461 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Riccardo Musso, Alberto Nidasio + * + * 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. + */ + +#include <Common.h> +#include <drivers/HardwareTimer.h> +#include <drivers/interrupt/external_interrupts.h> +#include <sensors/BMX160/BMX160.h> +#include <sensors/BMX160/BMX160WithCorrection.h> +#include <sensors/calibration/SoftIronCalibration.h> + +#include <fstream> +#include <iostream> + +constexpr const char* CORRECTION_PARAMETER_FILE = "/sd/bmx160_params.csv"; +constexpr const char* MAG_CALIBRATION_DATA_FILE = + "/sd/bmx160_mag_calibration_data.csv"; +constexpr int ACC_CALIBRATION_SAMPLES = 200; +constexpr int ACC_CALIBRATION_SLEEP_TIME = 25; // [us] +constexpr int ACC_CALIBRATION_N_ORIENTATIONS = 6; + +constexpr int MAG_CALIBRATION_SAMPLES = 500; +constexpr int MAG_CALIBRATION_DURAITON = 60; // [s] +constexpr int MAG_CALIBRATION_SLEEP_TIME = + MAG_CALIBRATION_DURAITON * 1000 / MAG_CALIBRATION_SAMPLES; // [us] + +BMX160* bmx160; + +/** + * Orientations for accelerometer calibration + * + * The BMX160 reference frame view facing the death stack x is: + * z x + * ^ ^ + * | / + * | / + * y <----/ + * + * Each AxisOrthoOrientation values indicates how to change x and y + */ +AxisOrthoOrientation orientations[ACC_CALIBRATION_N_ORIENTATIONS]{ + {Direction::POSITIVE_X, Direction::POSITIVE_Y}, // Z up + {Direction::POSITIVE_Y, Direction::POSITIVE_Z}, // X up + {Direction::POSITIVE_Z, Direction::POSITIVE_X}, // Y up + {Direction::POSITIVE_Y, Direction::POSITIVE_X}, // Z down + {Direction::POSITIVE_Z, Direction::POSITIVE_Y}, // X down + {Direction::POSITIVE_X, Direction::POSITIVE_Z}, // Y down +}; + +constexpr const char* testHumanFriendlyDirection[]{ + "Z up", "X up", "Y up", "Z down", "X down", "Y down", +}; + +// BMX160 Watermark interrupt +void __attribute__((used)) EXTI5_IRQHandlerImpl() +{ + if (bmx160) + { + bmx160->IRQupdateTimestamp(TimestampTimer::getTimestamp()); + } +} + +int menu(); +bool askToContinue(); +void waitForInput(); + +BMX160CorrectionParameters calibrateAccelerometer( + BMX160CorrectionParameters correctionParameters); +BMX160CorrectionParameters calibrateMagnetometer( + BMX160CorrectionParameters correctionParameters); +BMX160CorrectionParameters changeMinGyroCorrectionSamples( + BMX160CorrectionParameters correctionParameters); + +int main() +{ + TimestampTimer::enableTimestampTimer(); + + // Enable interrupt from BMX pin + enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE); + + // Greet the user + printf("\nWelcome to the calibration procedure!\n"); + + // Read the current correciton parameters + BMX160CorrectionParameters correctionParameters; + correctionParameters = + BMX160WithCorrection::readCorrectionParametersFromFile( + CORRECTION_PARAMETER_FILE); + + // Make the user choose what to do + switch (menu()) + { + case 1: + correctionParameters = calibrateAccelerometer(correctionParameters); + break; + case 2: + correctionParameters = calibrateMagnetometer(correctionParameters); + break; + case 3: + correctionParameters = + changeMinGyroCorrectionSamples(correctionParameters); + break; + + default: + break; + } + + // Save the correction parameters in the file + { + std::ofstream correctionParametersFile(CORRECTION_PARAMETER_FILE); + correctionParametersFile << BMX160CorrectionParameters::header() + << std::endl; + correctionParameters.print(correctionParametersFile); + } + + return 0; +} + +int menu() +{ + int choice; + + printf("\nWhat do you want to do?\n"); + printf("1. Calibrate accelerometer\n"); + printf("2. Calibrate magnetometer\n"); + printf("3. Set minimum gyroscope samples for calibration\n"); + printf("\n>> "); + scanf("%d", &choice); + + return choice; +} + +bool askToContinue() +{ + char choice; + + printf("Press [c] to continue, otherwise stop..."); + scanf("%s", &choice); + + return choice == 'c'; +} + +void waitForInput() +{ + printf("Press [c] to continue..."); + while (getchar() != 'c') + ; +} + +/** + * The accelerometer calibration consists in finding the bias and coefficient + * against target 9.8m/s^2 on the three axis positioning the sensor in all the 6 + * positions (+x, -x, +y, -y, +z, -z). + */ +BMX160CorrectionParameters calibrateAccelerometer( + BMX160CorrectionParameters correctionParameters) +{ + Matrix<float, 3, 2> m; + auto* calibrationModel = + new SixParameterCalibration<AccelerometerData, + ACC_CALIBRATION_SAMPLES * + ACC_CALIBRATION_N_ORIENTATIONS>; + + SPIBus bus(SPI1); + + BMX160Config bmx_config; + bmx_config.fifo_mode = BMX160Config::FifoMode::HEADER; + bmx_config.fifo_watermark = 100; + bmx_config.fifo_int = BMX160Config::FifoInterruptPin::PIN_INT1; + + bmx_config.temp_divider = 0; + + bmx_config.acc_range = BMX160Config::AccelerometerRange::G_16; + bmx_config.gyr_range = BMX160Config::GyroscopeRange::DEG_2000; + + bmx_config.acc_odr = BMX160Config::OutputDataRate::HZ_1600; + bmx_config.gyr_odr = BMX160Config::OutputDataRate::HZ_1600; + bmx_config.mag_odr = BMX160Config::OutputDataRate::HZ_50; + + bmx_config.gyr_unit = BMX160Config::GyroscopeMeasureUnit::RAD; + + bmx160 = new BMX160(bus, miosix::sensors::bmx160::cs::getPin(), bmx_config); + + TRACE("Initializing BMX160...\n"); + + if (!bmx160->init()) + { + TRACE("Init failed! (code: %d)\n", bmx160->getLastError()); + return correctionParameters; + } + + TRACE("Performing self-test...\n"); + + if (!bmx160->selfTest()) + { + TRACE("Self-test failed! (code: %d)\n", bmx160->getLastError()); + return correctionParameters; + } + + TRACE("Initialization and self-test completed!\n"); + + Vector3f avgAccel, vec; + int fifoAccSampleCount; + uint64_t accelTimestamp = 0; + + // Set reference vector, 1g on third axis + calibrationModel->setReferenceVector({0, 0, 9.8}); + + // Show the user the current correction values + printf("\nCurrent bias vector\n"); + printf("b = [ % 2.5f % 2.5f % 2.5f ]\n\n", + correctionParameters.accelParams(0, 1), + correctionParameters.accelParams(1, 1), + correctionParameters.accelParams(2, 1)); + printf("Matrix to be multiplied to the input vector\n"); + printf(" | % 2.5f % 2.5f % 2.5f |\n", + correctionParameters.accelParams(0, 0), 0.f, 0.f); + printf("M = | % 2.5f % 2.5f % 2.5f |\n", 0.f, + correctionParameters.accelParams(1, 0), 0.f); + printf(" | % 2.5f % 2.5f % 2.5f |\n", 0.f, 0.f, + correctionParameters.accelParams(2, 0)); + + if (!askToContinue()) + { + return correctionParameters; + } + + printf( + "Please note that the BMX axis, viewed facing the death stack x, " + "are as follows:\n"); + printf(" z x\n"); + printf(" ^ ^\n"); + printf(" | /\n"); + printf(" | /\n"); + printf(" y <----/\n"); + + for (unsigned i = 0; i < ACC_CALIBRATION_N_ORIENTATIONS; i++) + { + printf( + "Step n.%d/%d, please rotate the death stack x so that the " + "sensor " + "is %s\n", + i + 1, ACC_CALIBRATION_N_ORIENTATIONS, + testHumanFriendlyDirection[i]); + + waitForInput(); + + printf("Reding data and feeding the model...\n"); + + // Sample the sensor and feed the data to the calibration model + int count = 0; + while (count < ACC_CALIBRATION_SAMPLES) + { + bmx160->sample(); + avgAccel = {0, 0, 0}; + + // Read all the data contained in the fifo + uint8_t size = bmx160->getLastFifoSize(); + fifoAccSampleCount = 0; + for (int i = 0; i < size && count < ACC_CALIBRATION_SAMPLES; i++) + { + BMX160Data fifoElement = bmx160->getFifoElement(i); + + // Read acceleration data + if (fifoElement.accel_timestamp > accelTimestamp) + { + static_cast<AccelerometerData>(fifoElement) >> vec; + avgAccel += vec; + + accelTimestamp = fifoElement.accel_timestamp; + fifoAccSampleCount++; + } + } + avgAccel /= fifoAccSampleCount; + + AccelerometerData data; + data << avgAccel; + + miosix::Thread::sleep(ACC_CALIBRATION_SLEEP_TIME); + + calibrationModel->feed(data, orientations[i]); + count++; + } + + printf("Orientation calibration completed\n"); + } + + printf("Computing the result...."); + SixParameterCorrector<AccelerometerData> corrector = + calibrationModel->computeResult(); + + corrector >> m; + corrector >> correctionParameters.accelParams; + + printf("Bias vector\n"); + printf("b = [ % 2.5f % 2.5f % 2.5f ]\n\n", m(0, 1), m(1, 1), + m(2, 1)); + printf("Matrix to be multiplied to the input vector\n"); + printf(" | % 2.5f % 2.5f % 2.5f |\n", m(0, 0), 0.f, 0.f); + printf("M = | % 2.5f % 2.5f % 2.5f |\n", 0.f, m(1, 0), 0.f); + printf(" | % 2.5f % 2.5f % 2.5f |\n", 0.f, 0.f, m(2, 0)); + + return correctionParameters; +} + +BMX160CorrectionParameters calibrateMagnetometer( + BMX160CorrectionParameters correctionParameters) +{ + Matrix<float, 3, 2> m; + auto* calibrationModel = new SoftIronCalibration<MAG_CALIBRATION_SAMPLES>; + Vector3f avgMag{0, 0, 0}, vec; + + SPIBus bus(SPI1); + + BMX160Config bmx_config; + bmx_config.fifo_mode = BMX160Config::FifoMode::DISABLED; + + bmx_config.temp_divider = 0; + + bmx_config.acc_range = BMX160Config::AccelerometerRange::G_16; + bmx_config.gyr_range = BMX160Config::GyroscopeRange::DEG_2000; + + bmx_config.acc_odr = BMX160Config::OutputDataRate::HZ_1600; + bmx_config.gyr_odr = BMX160Config::OutputDataRate::HZ_1600; + bmx_config.mag_odr = BMX160Config::OutputDataRate::HZ_50; + + bmx_config.gyr_unit = BMX160Config::GyroscopeMeasureUnit::RAD; + + bmx160 = new BMX160(bus, miosix::sensors::bmx160::cs::getPin(), bmx_config); + + TRACE("Initializing BMX160...\n"); + + if (!bmx160->init()) + { + TRACE("Init failed! (code: %d)\n", bmx160->getLastError()); + return correctionParameters; + } + + TRACE("Performing self-test...\n"); + + if (!bmx160->selfTest()) + { + TRACE("Self-test failed! (code: %d)\n", bmx160->getLastError()); + return correctionParameters; + } + + TRACE("Initialization and self-test completed!\n"); + + // Show the user the current correction values + printf("Current bias vector\n"); + printf("b = [ % 2.5f % 2.5f % 2.5f ]\n\n", + correctionParameters.magnetoParams(0, 1), + correctionParameters.magnetoParams(1, 1), + correctionParameters.magnetoParams(2, 1)); + printf("Matrix to be multiplied to the input vector\n"); + printf(" | % 2.5f % 2.5f % 2.5f |\n", + correctionParameters.magnetoParams(0, 0), 0.f, 0.f); + printf("M = | % 2.5f % 2.5f % 2.5f |\n", 0.f, + correctionParameters.magnetoParams(1, 0), 0.f); + printf(" | % 2.5f % 2.5f % 2.5f |\n", 0.f, 0.f, + correctionParameters.magnetoParams(2, 0)); + + printf("Now I will calibrate the magnetometer\n"); + printf( + "Please, after starting the calibration, rotate the gyroscope in " + "the " + "most different directions.\n"); + printf("The calibration will run for %d seconds\n", + MAG_CALIBRATION_DURAITON); + + if (!askToContinue()) + { + return correctionParameters; + } + + // Sample the sensor and feed the data to the calibration model + int count = 0; + while (count < MAG_CALIBRATION_SAMPLES) + { + bmx160->sample(); + + BMX160Data data = bmx160->getLastSample(); + calibrationModel->feed(data); + count++; + + miosix::Thread::sleep(MAG_CALIBRATION_SLEEP_TIME); + } + + printf("Computing the result...."); + auto corrector = calibrationModel->computeResult(); + + // Save the calibration data in the sd card + // Save the correction parameters in the file + { + std::ofstream magnetometerCalibrationDataFile( + MAG_CALIBRATION_DATA_FILE); + + auto samples = calibrationModel->getSamples(); + int numberOfSamples = samples.rows(); + + for (int i = 0; i < numberOfSamples; i++) + { + auto row = samples.row(i); + for (int j = 0; j < row.cols(); j++) + { + magnetometerCalibrationDataFile << samples.row(i)(j) << ","; + } + magnetometerCalibrationDataFile << "\n"; + } + } + + corrector >> m; + corrector >> correctionParameters.magnetoParams; + + printf("b: the bias vector\n"); + printf("b = [ % 2.5f % 2.5f % 2.5f ]\n\n", m(0, 1), m(1, 1), + m(2, 1)); + printf("M: the matrix to be multiplied to the input vector\n"); + printf(" | % 2.5f % 2.5f % 2.5f |\n", m(0, 0), 0.f, 0.f); + printf("M = | % 2.5f % 2.5f % 2.5f |\n", 0.f, m(1, 0), 0.f); + printf(" | % 2.5f % 2.5f % 2.5f |\n", 0.f, 0.f, m(2, 0)); + + return correctionParameters; +} + +BMX160CorrectionParameters changeMinGyroCorrectionSamples( + BMX160CorrectionParameters correctionParameters) +{ + // Show the user the current parameter + printf( + "The current minimun number of gyroscope samples for calibration " + "is " + "%d\n", + correctionParameters.minGyroSamplesForCalibration); + + if (askToContinue()) + { + printf("Insert the new value: "); + scanf("%d", &correctionParameters.minGyroSamplesForCalibration); + } + + return correctionParameters; +} \ No newline at end of file diff --git a/src/entrypoints/config-dsgamma.cpp b/src/entrypoints/config-dsgamma.cpp index 1df4eb831c7ca65eb596a22f62e168104f759787..389a91ad12187b2cdad0334254c267503787da0b 100644 --- a/src/entrypoints/config-dsgamma.cpp +++ b/src/entrypoints/config-dsgamma.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -22,6 +22,7 @@ #include <stdio.h> #include <string.h> + #include "miosix.h" using namespace std; @@ -61,10 +62,10 @@ int main() newConf.dest_addr[0] = 126; newConf.dest_addr[1] = 126; newConf.dest_addr[2] = 126; - newConf.lora_sf = SF6; // SF6 - newConf.lora_power = dbm20; //+20dbm + newConf.lora_sf = SF6; // SF6 + newConf.lora_power = dbm20; //+20dbm newConf.handshake = false; // No handshake - newConf.baudrate = B_9600; // 9600 baud + newConf.baudrate = B_9600; // 9600 baud // printf("LOCAL ADDRESS (3 bytes, 0-127 each):\n"); // scanf("%d %d %d", &newConf.local_addr[0], &newConf.local_addr[1], @@ -82,10 +83,12 @@ int main() // scanf("%d", &newConf.baudrate); bool configStatus = gamma.configure(newConf); - if(configStatus == true){ + if (configStatus == true) + { printf("New configuration set!\n"); } - else{ + else + { printf("Failed to set new configuration!\n"); } Thread::sleep(500); @@ -94,7 +97,6 @@ int main() return 0; } - /* * Waits for the discovery user button to be pressed (blocking). */ diff --git a/src/entrypoints/examples/example-fsm.cpp b/src/entrypoints/examples/example-fsm.cpp index 43b4e48695299f83592c1539b9fdd3884e51823f..8e1ee5399247bb2808e77f18e0ebcae49c576387 100644 --- a/src/entrypoints/examples/example-fsm.cpp +++ b/src/entrypoints/examples/example-fsm.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/entrypoints/examples/example-fsm.h b/src/entrypoints/examples/example-fsm.h index d9faaf3d887ffb1e03bf79c649a97e7ae0b41a4c..a30ec5433614af04eaf4764e8a5ddf20dc51fead 100644 --- a/src/entrypoints/examples/example-fsm.h +++ b/src/entrypoints/examples/example-fsm.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/drivers/test-timer.cpp b/src/entrypoints/examples/mxgui-helloworld.cpp similarity index 73% rename from src/tests/drivers/test-timer.cpp rename to src/entrypoints/examples/mxgui-helloworld.cpp index 5ef955e78440eba187000bb61ad56d60a4b22de8..0595df2aa0b8dcc1bd735bc470d56f101990e88a 100644 --- a/src/tests/drivers/test-timer.cpp +++ b/src/entrypoints/examples/mxgui-helloworld.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,24 +20,24 @@ * THE SOFTWARE. */ -#include <miosix.h> #include <cstdio> -#include "drivers/HardwareTimer.h" +#include <miosix.h> -using miosix::Thread; +#include <mxgui/entry.h> +#include <mxgui/display.h> + +using namespace mxgui; +using namespace miosix; int main() { - HardwareTimer<uint32_t, 2>& timer2 = HardwareTimer<uint32_t, 2>::instance(); - - timer2.start(); - - while (true) { - uint32_t tick = timer2.tick(); - printf("%lu\t\t(%.3f)\n", tick, timer2.toMilliSeconds(tick)); - - usleep(100000); + DrawingContext dc(DisplayManager::instance().getDisplay()); + dc.write(Point(0,0),"Hello world"); + } + for(;;) + { + printf("Hello world! Look at the screen!\n"); + Thread::sleep(1000); } - return 0; } \ No newline at end of file diff --git a/src/entrypoints/kernel-testsuite.cpp b/src/entrypoints/kernel-testsuite.cpp index ce71b9421b1a6a3e247a195cfecdd4f3926f9846..f6751fe45d81d3679d37a338a9fc2826ea424145 100644 --- a/src/entrypoints/kernel-testsuite.cpp +++ b/src/entrypoints/kernel-testsuite.cpp @@ -1,34 +1,29 @@ -/*************************************************************************** - * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Terraneo Federico * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * As a special exception, if other files instantiate templates or use * - * macros or inline functions from this file, or you compile this file * - * and link it with other works to produce a work based on this file, * - * this file does not by itself cause the resulting work to be covered * - * by the GNU General Public License. However the source code for this * - * file must still be made available in accordance with the GNU General * - * Public License. This exception does not invalidate any other reasons * - * why a work based on this file might be covered by the GNU General * - * Public License. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, see <http://www.gnu.org/licenses/> * - ***************************************************************************/ - -/************************************************************************ -* Part of the Miosix Embedded OS. This is the testsuite used during Miosix -* development to check for regressions. -*************************************************************************/ +/* Copyright (c) 2008-2013 Skyward Experimental Rocketry + * Author: Federico Terraneo + * + * 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. + */ + + +/* Part of the Miosix Embedded OS. This is the testsuite used during Miosix + * development to check for regressions. +*/ #include <cstdio> #include <cstdlib> @@ -190,6 +185,7 @@ int main() } //For testing mpu unsigned int *m; + (void)m; // Disable unused warning switch(c) { case 't': @@ -347,6 +343,7 @@ int main() case 's': iprintf("Shutting down\n"); shutdown(); + break; default: iprintf("Unrecognized option\n"); } @@ -609,6 +606,7 @@ static volatile bool t1_v1; static void t1_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -701,6 +699,8 @@ static Thread *t2_p_v1; static void t2_p1(void *argv) { + (void)argv; + //This is to fix a race condition: the immediately after the thread //creation a yield occurs, t2_p_v1 is not yet assigned so the check fails Thread::sleep(5); @@ -718,6 +718,7 @@ static void t2_p1(void *argv) static void t2_p2(void *argv) { + (void)argv; while(Thread::testTerminate()==false) t2_v1=true; } @@ -782,6 +783,7 @@ also tests creation of multiple instances of the same thread static void t3_p1(void *argv) { + (void)argv; const int SLEEP_TIME=100; for(;;) { @@ -799,6 +801,7 @@ static volatile bool t3_deleted;//Set when an instance of t3_p2 is deleted static void t3_p2(void *argv) { + (void)argv; const int SLEEP_TIME=15; for(;;) { @@ -893,6 +896,7 @@ static volatile bool t4_v1; static void t4_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1035,6 +1039,7 @@ static volatile bool t5_v2;//False=testing Thread::wait() else Thread::IRQwait() static void t5_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1153,6 +1158,7 @@ static FastMutex t6_m1a; static void t6_p1(void *argv) { + (void)argv; t6_m1.lock(); seq.add('1'); Thread::sleep(100); @@ -1165,6 +1171,7 @@ static void t6_p1(void *argv) static void t6_p2(void *argv) { + (void)argv; t6_m1.lock(); seq.add('2'); Thread::sleep(100); @@ -1176,6 +1183,7 @@ static void t6_p2(void *argv) static void t6_p3(void *argv) { + (void)argv; t6_m1.lock(); seq.add('3'); Thread::sleep(100); @@ -1187,6 +1195,7 @@ static void t6_p3(void *argv) static void t6_p4(void *argv) { + (void)argv; t6_m1.lock(); for(;;) { @@ -1205,6 +1214,7 @@ static void t6_p4(void *argv) static void t6_p1a(void *argv) { + (void)argv; t6_m1a.lock(); seq.add('1'); Thread::sleep(100); @@ -1213,6 +1223,7 @@ static void t6_p1a(void *argv) static void t6_p2a(void *argv) { + (void)argv; t6_m1a.lock(); seq.add('2'); Thread::sleep(100); @@ -1221,6 +1232,7 @@ static void t6_p2a(void *argv) static void t6_p3a(void *argv) { + (void)argv; t6_m1a.lock(); seq.add('3'); Thread::sleep(100); @@ -1229,6 +1241,7 @@ static void t6_p3a(void *argv) static void t6_p4a(void *argv) { + (void)argv; t6_m1a.lock(); for(;;) { @@ -1251,6 +1264,7 @@ static FastMutex t6_m2a; static void t6_p5(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1266,6 +1280,7 @@ static void t6_p5(void *argv) static void t6_p5a(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1305,6 +1320,7 @@ static FastMutex t6_m5a(FastMutex::RECURSIVE); static void *t6_p7(void *argv) { + (void)argv; if(t6_m5.tryLock()==false) return reinterpret_cast<void*>(1); // 1 = locked t6_m5.unlock(); return reinterpret_cast<void*>(0); // 0 = unlocked @@ -1320,6 +1336,7 @@ bool checkIft6_m5IsLocked() static void *t6_p7a(void *argv) { + (void)argv; if(t6_m5a.tryLock()==false) return reinterpret_cast<void*>(1); // 1 = locked t6_m5a.unlock(); return reinterpret_cast<void*>(0); // 0 = unlocked @@ -1741,6 +1758,7 @@ static Queue<char,4> t8_q2; static void t8_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; @@ -1985,6 +2003,7 @@ void t10_f2() void t10_p1(void *argv) { + (void)argv; t10_f2(); fail("Exception not thrown"); } @@ -2011,6 +2030,7 @@ static volatile unsigned int t11_v1;//Free heap after spawning thread void t11_p1(void *argv) { + (void)argv; if(MemoryProfiling::getStackSize()!=STACK_SMALL) fail("getStackSize (2)"); //Check that getCurrentFreeHeap returns the same value from different //threads if no heap allocation happened in between @@ -2032,7 +2052,8 @@ void test_11() fail("getAbsoluteFreeStack (1)"); if(MemoryProfiling::getAbsoluteFreeStack()>curFreeStack-4) fail("getAbsoluteFreeStack (2)"); - unsigned int heapSize=MemoryProfiling::getHeapSize(); + + unsigned int heapSize=MemoryProfiling::getHeapSize(); if(MemoryProfiling::getCurrentFreeHeap()>heapSize) fail("getCurrentFreeHeap"); if(MemoryProfiling::getAbsoluteFreeHeap()>heapSize) @@ -2057,12 +2078,14 @@ Mutex t12_m2; void t12_p1(void *argv) { + (void)argv; Lock<Mutex> l1(t12_m1); Lock<Mutex> l2(t12_m2); } void t12_p2(void *argv) { + (void)argv; Lock<Mutex> l(t12_m1); } @@ -2265,6 +2288,7 @@ static Mutex t15_m1; void t15_p1(void *argv) { + (void)argv; for(int i=0;i<10;i++) { Lock<Mutex> l(t15_m1); @@ -2276,6 +2300,7 @@ void t15_p1(void *argv) void t15_p2(void *argv) { + (void)argv; for(int i=0;i<10;i++) { Lock<Mutex> l(t15_m1); @@ -2395,6 +2420,7 @@ pthread_cond_t t16_c1=PTHREAD_COND_INITIALIZER; void *t16_p3(void *argv) { + (void)argv; Thread::sleep(30); if(pthread_mutex_trylock(&t16_m1)!=0) fail("cond_wait did not release mutex"); //<--- @@ -2408,6 +2434,7 @@ pthread_cond_t t16_c2; void *t16_p4(void *argv) { + (void)argv; Thread::sleep(30); if(pthread_mutex_trylock(&t16_m1)!=0) fail("cond_wait did not release mutex (2)"); //<--- @@ -2791,6 +2818,7 @@ static void be() static void t19_p1(void *argv) { + (void)argv; Thread::sleep(50); { FastInterruptDisableLock dLock; @@ -3232,6 +3260,7 @@ struct t22_s1 static void t22_t2(void *argv) { + (void)argv; while(Thread::testTerminate()==false) { t22_v5=true; @@ -3958,6 +3987,7 @@ static volatile bool fs_1_error; static void fs_t1_p1(void *argv) { + (void)argv; FILE *f; if((f=fopen("/sd/testdir/file_1.txt","w"))==NULL) { @@ -3990,6 +4020,7 @@ static void fs_t1_p1(void *argv) static void fs_t1_p2(void *argv) { + (void)argv; FILE *f; if((f=fopen("/sd/testdir/file_2.txt","w"))==NULL) { @@ -4022,6 +4053,7 @@ static void fs_t1_p2(void *argv) static void fs_t1_p3(void *argv) { + (void)argv; FILE *f; if((f=fopen("/sd/testdir/file_3.txt","w"))==NULL) { @@ -4558,6 +4590,7 @@ context switch speed static void b2_p1(void *argv) { + (void)argv; for(;;) { if(Thread::testTerminate()) break; diff --git a/src/entrypoints/sdcard-benchmark.cpp b/src/entrypoints/sdcard-benchmark.cpp new file mode 100644 index 0000000000000000000000000000000000000000..57b745d233b209cb7793932ba96a2ae9f807ef30 --- /dev/null +++ b/src/entrypoints/sdcard-benchmark.cpp @@ -0,0 +1,141 @@ +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + + +// #include "drivers/HardwareTimer.h" +#include <array> +// #include <chrono> +#include <cstdio> +#include <ctime> +#include <iostream> +// #include <thread> +#include <vector> +#include "math/Stats.h" +#include <miosix.h> + +using namespace miosix; +using namespace std; + +const unsigned int NUM_WRITES = 5000; + +vector<unsigned int> BUF_SIZES{128, 256, 512, 1024, 2048, + 4096, 1024 * 8}; + +// Fills a buffer with random bytes +void rndFill(uint8_t* buf, unsigned int size) +{ + for (unsigned int i = 0; i < size; i++) + { + *buf = rand() % 256; + ++buf; + } +} + +bool benchmark(unsigned int buf_size, array<float, NUM_WRITES>& results) +{ + bool success = false; + + uint8_t* buf = new uint8_t[buf_size]; + + FILE* f = fopen("/sd/buf.dat", "w"); + + if (f == NULL) + { + printf("Error opening file!\n"); + goto clean; + } + + for (unsigned int i = 0; i < NUM_WRITES; i++) + { + rndFill(buf, buf_size); + long long t = getTick(); + int w = fwrite(buf, 1, buf_size, f); + t = getTick() - t; + + if (w != (int)buf_size) + { + printf("fwrite error: %d\n", ferror(f)); + goto clean; + } + results[i] = t; + } + + success = true; + +clean: + // Cleanup + fclose(f); + remove("buf.dat"); + delete[] buf; + + return success; +} + +void printResults(unsigned int buf_size, array<float, NUM_WRITES>& results) +{ + Stats s; + for (float f : results) + { + s.add(f); + } + StatsResult res = s.getStats(); + + cout << "***BUF SIZE: " << buf_size << "\n"; + cout << "Times: \n"; + cout << "- mean: " << res.mean << " ms \n"; + cout << "- stddev: " << res.stdev << " ms \n"; + cout << "- min: " << res.minValue << " ms \n"; + cout << "- max: " << res.maxValue << " ms \n"; + + cout << "Speeds: \n"; + cout << "- mean: " << buf_size / (res.mean * 1024) << " KiB/s \n"; + cout << "- min: " << buf_size / (res.maxValue * 1024) << " KiB/s \n"; + cout << "- max: " << buf_size / (res.minValue * 1024) << " KiB/s \n"; + + cout << "\n\n"; +} + +array<float, NUM_WRITES> data; + +int main() +{ + srand(time(NULL)); + + for (unsigned int s : BUF_SIZES) + { + if (benchmark(s, data)) + { + printResults(s, data); + } + else + { + cout << "Error (buf_size: " << s << ").\nAborting.\n"; + } + } + for(;;) + { + Thread::sleep(60000); + cout << "Aborted!\n"; + } + + return 0; +} \ No newline at end of file diff --git a/src/shared/ActiveObject.h b/src/shared/ActiveObject.h index f3cd2a415a3e608fa5c29c21b9b14b288a934470..574373009200c431aebd8c0d19e324898a62e18a 100644 --- a/src/shared/ActiveObject.h +++ b/src/shared/ActiveObject.h @@ -1,7 +1,5 @@ -/* Event scheduler - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Author: Federico Terraneo, Matteo Michele Piazzolla +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Federico Terraneo, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 @@ -26,6 +24,7 @@ #define SRC_SHARED_ACTIVEOBJECT_H #include <Common.h> + #include "diagnostic/SkywardStack.h" /** @@ -84,9 +83,12 @@ public: */ virtual void stop() { - should_stop = true; - thread->join(); - stopped = true; + if (isRunning()) + { + should_stop = true; + thread->join(); + stopped = true; + } } bool isStarted() { return started; } diff --git a/src/shared/Common.h b/src/shared/Common.h index eee2fe4f0c60c8b985fc7d56fabf101ca0f2ac55..3d2b0ce8212d71fe557ee124a61800657a4f3dfc 100644 --- a/src/shared/Common.h +++ b/src/shared/Common.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -42,6 +42,8 @@ #include "Constants.h" +#include "TimestampTimer.h" + #include "Debug.h" #define UNUSED(x) (void)(x) diff --git a/src/shared/Constants.h b/src/shared/Constants.h index 0d3bbd309d7e8d6e109299fc13023c20ff2f8dd0..fbc8d55c5c144e0d9f5e43cedb5303628fa9333f 100644 --- a/src/shared/Constants.h +++ b/src/shared/Constants.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -25,7 +25,11 @@ static constexpr const float PI = 3.14159265f; static constexpr const float EARTH_GRAVITY = 9.80665f; +static constexpr const float EARTH_RADIUS = 6371.0f * 1000.0f; // [m] static constexpr const float DEGREES_TO_RADIANS = PI / 180.0f; static constexpr const float RADIANS_TO_DEGREES = 180.0f / PI; +static constexpr const float KNOTS_TO_MPS = 0.514444; +static constexpr const float MSL_PRESSURE = 101325.0f; // [Pa] +static constexpr const float MSL_TEMPERATURE = 288.15f; // [Kelvin] #endif diff --git a/src/shared/Debug.cpp b/src/shared/Debug.cpp index 483b54b7598c33ffb343d408c35fe86b070c24c2..9f9824a9b04dbc4ee68d5c670a1135ca61745521 100644 --- a/src/shared/Debug.cpp +++ b/src/shared/Debug.cpp @@ -1,20 +1,19 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta - * +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * * 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 + * 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 diff --git a/src/shared/Debug.h b/src/shared/Debug.h index 5121949f24a5c15e2edf084e616cf21f021bd830..4df4929db0610334a104fa26d0e69f9710f89dd9 100644 --- a/src/shared/Debug.h +++ b/src/shared/Debug.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/Singleton.h b/src/shared/Singleton.h index 715babfc8879dfd1e3ace64f339eb0b8b65820fd..b49b098af147ea22c9a1fce73ebc6925da1a7f5e 100644 --- a/src/shared/Singleton.h +++ b/src/shared/Singleton.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Illya Dudchenko, Matteo Michele Piazzolla, Federico Terraneo + * Authors: Illya Dudchenko, Matteo Piazzolla, Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/TimestampTimer.cpp b/src/shared/TimestampTimer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..080e81c325a8d0f93c61cdb70433323f903e7379 --- /dev/null +++ b/src/shared/TimestampTimer.cpp @@ -0,0 +1,70 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Authors: Luca Conterio, Davide Mor + * + * 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. + */ + +#include "TimestampTimer.h" + +#include <Debug.h> + +namespace TimestampTimer +{ + +#ifdef _ARCH_CORTEXM3_STM32 +HardwareTimer<uint32_t, TimerMode::Chain> initHardwareTimer() +{ + // VERY IMPORTANT! ALWAYS ENABLE CLOCKS BEFORE CONFIGURING THE TIMERS!! + { + miosix::FastInterruptDisableLock dLock; + // Enable TIM2 + TIM3 peripheral clock + RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN; + } + + // chain two 16-bits timers + return HardwareTimer<uint32_t, TimerMode::Chain>( + TIM2, TIM3, TimerTrigger::ITR1, + TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1)); +} + +HardwareTimer<uint32_t, TimerMode::Chain> timestamp_timer = initHardwareTimer(); +#else +HardwareTimer<uint32_t, TimerMode::Single> initHardwareTimer() +{ + { + miosix::FastInterruptDisableLock dLock; + // Enable TIM2 peripheral clock + RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; + } + + return HardwareTimer<uint32_t, TimerMode::Single>( + TIM2, + TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1)); +} + +HardwareTimer<uint32_t, TimerMode::Single> timestamp_timer = initHardwareTimer(); +#endif + +void enableTimestampTimer(uint8_t prescaler) +{ + timestamp_timer.setPrescaler(prescaler); + timestamp_timer.start(); +} + +} // namespace TimestampTimer \ No newline at end of file diff --git a/src/shared/TimestampTimer.h b/src/shared/TimestampTimer.h new file mode 100644 index 0000000000000000000000000000000000000000..c280ac5cab19d1216333a4064b96cfd2b5d00a36 --- /dev/null +++ b/src/shared/TimestampTimer.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Authors: Luca Conterio, Davide Mor + * + * 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. + */ + +#pragma once + +#include <math.h> + +#include <Debug.h> +#include "drivers/HardwareTimer.h" + +namespace TimestampTimer +{ + +/** + * For timer resolution and duration refer to : + * https://docs.google.com/spreadsheets/d/1B44bN6m2vnldQx9XVxZaBP8bDHqoLPREyHoaLh8s0UA/edit#gid=0 + * + */ +static const uint8_t PRESCALER_VALUE = 255; + +#ifdef _ARCH_CORTEXM3_STM32 +extern HardwareTimer<uint32_t, TimerMode::Chain> timestamp_timer; +#else +extern HardwareTimer<uint32_t, TimerMode::Single> timestamp_timer; +#endif + +/** + * @brief Enables and starts the timer peripheral, which can be + * accessed via the timestamp_timer object. + */ +void enableTimestampTimer(uint8_t prescaler = PRESCALER_VALUE); + +/** + * @return the current timestamp in microseconds + */ +inline uint64_t getTimestamp() +{ + return timestamp_timer.toIntMicroSeconds(timestamp_timer.tick()); +} + +} // namespace TimestampTimer \ No newline at end of file diff --git a/src/shared/diagnostic/CpuMeter.cpp b/src/shared/diagnostic/CpuMeter.cpp index e81c45d6d271849b20df3f24165b19c0b9a0cb25..b448cf8aa94397245230e8ea6ceb28baf97bbcf4 100644 --- a/src/shared/diagnostic/CpuMeter.cpp +++ b/src/shared/diagnostic/CpuMeter.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/diagnostic/CpuMeter.h b/src/shared/diagnostic/CpuMeter.h index 9b470c6186637dbc0c646ffca137d78985974c3c..8b577251d426e6a868b12759ef15e0f13bae2aa2 100644 --- a/src/shared/diagnostic/CpuMeter.h +++ b/src/shared/diagnostic/CpuMeter.h @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/diagnostic/FaultCounter.h b/src/shared/diagnostic/FaultCounter.h index 7d49d31481b77d0b2cc53180b0dc99c95b9ff86a..fd597022aaf2ccd239d4b0e1ba8bc8f2537250df 100644 --- a/src/shared/diagnostic/FaultCounter.h +++ b/src/shared/diagnostic/FaultCounter.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2017 Skyward Experimental Rocketry - * Authors: Alain Carlucci + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/diagnostic/FaultCounterData.h b/src/shared/diagnostic/FaultCounterData.h index 5b63d1c605918770d7683c58113d2d8b5f91cc9f..ef73f4e7fda3b3d2b4169c6fffe72ed2aed2651a 100644 --- a/src/shared/diagnostic/FaultCounterData.h +++ b/src/shared/diagnostic/FaultCounterData.h @@ -1,5 +1,5 @@ /* Copyright (c) 2017 Skyward Experimental Rocketry - * Authors: Alain Carlucci + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/diagnostic/Log.h b/src/shared/diagnostic/Log.h deleted file mode 100644 index 061053657263f7f0b14bf76e3a40944d26f48b69..0000000000000000000000000000000000000000 --- a/src/shared/diagnostic/Log.h +++ /dev/null @@ -1,178 +0,0 @@ -/* Copyright (c) 2015-2017 Skyward Experimental Rocketry - * Authors: Alain Carlucci - * - * 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. - */ - -#ifndef SHARED_LOG_H -#define SHARED_LOG_H - -#include <ActiveObject.h> -#include <Common.h> -#include <Singleton.h> -#include <drivers/Leds.h> -#include <fcntl.h> -#include <math/Vec3.h> -#include <termios.h> -#include <unistd.h> -#include <queue> - -class Log : public Singleton<Log>, public ActiveObject -{ - enum DataType - { - DATA_VEC3 = 0, - DATA_QUAT = 1, - DATA_FLOAT = 2, - DATA_INT = 3, - DATA_STRING = 4, - DATA_LIMITED_INT = 5, - DATA_UINT32 = 6, - }; - - friend class Singleton<Log>; - -public: - ~Log() {} - - void logString(const std::string& str) - { - std::vector<uint8_t> buf(1 + str.length()); - buf[0] = DATA_STRING; - for (size_t i = 0; i < str.length(); i++) - buf[1 + i] = str[i]; - - queue(std::move(buf)); - } - - void logSensorFloat(uint8_t id, float data) - { - std::vector<uint8_t> buf(2 + sizeof(float)); - buf[0] = DATA_FLOAT; - buf[1] = id; - *((float*)&buf[2]) = data; - - queue(std::move(buf)); - } - - void logUInt32(uint8_t id, uint32_t data) - { - std::vector<uint8_t> buf(2 + sizeof(uint32_t)); - buf[0] = DATA_UINT32; - buf[1] = id; - memcpy(&buf[2], &data, sizeof(uint32_t)); - - queue(std::move(buf)); - } - - void logLimitedInt(uint8_t id, int min, int max, int data) - { - assert(max > min); - uint32_t v = (data - min) * 256 / (max - min); - - std::vector<uint8_t> buf(2 + 1); - buf[0] = DATA_LIMITED_INT; - buf[1] = id; - buf[2] = v & 0xff; - - queue(std::move(buf)); - } - - void logSensorVec3(uint8_t id, const Vec3& data) - { - float tmp; - std::vector<uint8_t> buf(2 + 3 * sizeof(float)); - buf[0] = DATA_VEC3; - buf[1] = id; - - tmp = data.getX(); - *((float*)&buf[2]) = tmp; - tmp = data.getY(); - *((float*)&buf[6]) = tmp; - tmp = data.getZ(); - *((float*)&buf[10]) = tmp; - - queue(std::move(buf)); - } - - uint32_t getLogQueueSize() const { return mQueue.size(); } - -protected: - void run() override - { - miosix::Lock<miosix::FastMutex> lock(mMutex); - while (1) - { - Leds::set(4, 1); - while (mQueue.empty()) - mCondVar.wait(mMutex); - - Leds::set(4, 0); - while (!mQueue.empty()) - { - const std::vector<uint8_t>& el = mQueue.front(); - write(el); - mQueue.pop(); - } - mWaitVar.broadcast(); - } - } - -private: - std::queue<std::vector<uint8_t>> mQueue; - miosix::FastMutex mMutex; - miosix::ConditionVariable mCondVar, mWaitVar; - - Log() : ActiveObject() - { - /* - struct termios t; - tcgetattr(STDIN_FILENO, &t); - t.c_lflag &= ~(ISIG | ICANON | ECHO); - tcsetattr(STDIN_FILENO,TCSANOW, &t); - */ - } - - void write(const std::vector<uint8_t>& data) - { - static const char map[] = "0123456789abcdef"; - for (size_t i = 0; i < data.size(); i++) - { - const char& di = data[i]; - char out[2]; - out[0] = map[(di >> 4) & 0x0f]; - out[1] = map[di & 0x0f]; - ::write(1, out, 2); - } - ::write(1, "\r\n", 2); - } - - void queue(std::vector<uint8_t>&& data) - { - miosix::Lock<miosix::FastMutex> lock(mMutex); - while (mQueue.size() > 100) - mWaitVar.wait(lock); - mQueue.push(std::move(data)); - mCondVar.signal(); - } -}; - -#define sLog Log::getInstance() - -#endif /* ifndef SHARED_LOG_H */ diff --git a/src/shared/diagnostic/LogSink.h b/src/shared/diagnostic/LogSink.h new file mode 100644 index 0000000000000000000000000000000000000000..531af475540fb23202b708586f5ed76ef24f34be --- /dev/null +++ b/src/shared/diagnostic/LogSink.h @@ -0,0 +1,97 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta, Luca Conterio + * + * 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. + */ + +#pragma once + +#include <logger/Logger.h> +#include <miosix.h> + +#include "PrintLoggerData.h" + +using miosix::FastMutex; + +class LogSink +{ +public: + LogSink() {} + LogSink(const LogSink&) = delete; + LogSink& operator=(const LogSink&) = delete; + + void log(const LogRecord& record); + + void enable() { enabled = true; } + + void disable() { enabled = false; } + + bool isEnabled() { return enabled; } + + void setLevel(uint8_t level) { min_level = level; } + + int getLevel() { return min_level; } + + void setFormatString(std::string format) { this->format = format; } + +protected: + virtual void logImpl(std::string l) = 0; + +private: + bool enabled = true; // enabled by the default when created + uint8_t min_level = LOGL_NOTSET; + std::string format = "{ts} {file}:{line} {fun} {lvl} [{name}] {msg}\n"; +}; + +/** + * This class directly outputs the log to file. + */ +class FileLogSink : public LogSink +{ +public: + FileLogSink(FILE* f) : f(f) {} + + FileLogSink() {} + + void setFile(FILE* f_) { f = f_; } + +protected: + void logImpl(std::string l); + + FILE* f; + FastMutex mutex; +}; + +/** + * This class uses the skyward-boardcore logger in order to output + * strings to filesystem. It can be used for example to log error strings + * during flight, but use it carefully. + */ +class FileLogSinkBuffered : public FileLogSink +{ +public: + FileLogSinkBuffered() : logger(Logger::instance()) {} + +protected: + void logImpl(std::string l); + +private: + Logger& logger; + FastMutex mutex; +}; \ No newline at end of file diff --git a/src/shared/diagnostic/PrintLogger.cpp b/src/shared/diagnostic/PrintLogger.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c76b8a9d865ed56fd34bc7230c0a4db6c91b4040 --- /dev/null +++ b/src/shared/diagnostic/PrintLogger.cpp @@ -0,0 +1,158 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#include "PrintLogger.h" + +using miosix::Lock; + +static string getLevelString(uint8_t level) +{ + switch (level) + { + case LOGL_DEBUG: + return "DEBUG"; + case LOGL_INFO: + return "INFO"; + case LOGL_WARNING: + return "WARNING"; + case LOGL_ERROR: + return "ERROR"; + case LOGL_CRITICAL: + return "CRITICAL"; + default: + return std::to_string(level); + } +} +void LogSink::log(const LogRecord& record) +{ + using namespace fmt::literals; + if (record.level >= min_level) + { + float ts = miosix::getTick() / 1000.0f; + int min = ts / 60; + string ts_str = fmt::format("{:02d}:{:06.3f}", min, (ts - min * 60)); + logImpl(fmt::format(format, "ts"_a = ts_str, "file"_a = record.file, + "line"_a = record.line, "fun"_a = record.function, + "lvl"_a = getLevelString(record.level), + "name"_a = record.name, "msg"_a = record.message)); + } +} + +void FileLogSink::logImpl(string l) +{ + Lock<FastMutex> lock(mutex); + fwrite(l.c_str(), sizeof(char), l.length(), f); +} + +void FileLogSinkBuffered::logImpl(string l) +{ + Lock<FastMutex> lock(mutex); + LoggingString s; + strncpy(s.log_string, l.c_str(), MAX_LOG_STRING_SIZE-1); + logger.log(s); +} + +PrintLogger PrintLogger::getChild(string name) +{ + return PrintLogger(parent, this->name + "." + name); +} + +LogRecord PrintLogger::buildLogRecord(uint8_t level, string function, + string file, int line, + fmt::string_view format, + fmt::format_args args) +{ + LogRecord record; + record.level = level; + record.function = function; + record.file = file; + record.line = line; + record.name = name; + try + { + record.message = fmt::vformat(format, args); + } + catch (const std::exception& e) + { + level = ERROR; + record.message = "FMT Formatting error! " + string(e.what()); + } + + return record; +} + +void PrintLogger::vlog(uint8_t level, string function, string file, int line, + fmt::string_view format, fmt::format_args args) +{ + parent.log(buildLogRecord(level, function, file, line, format, args)); +} + +void PrintLogger::vlogAsync(uint8_t level, string function, string file, + int line, fmt::string_view format, + fmt::format_args args) +{ + parent.logAsync(buildLogRecord(level, function, file, line, format, args)); +} + +void Logging::log(const LogRecord& record) +{ + for (auto& s : sinks) + { + if (s->isEnabled()) + { + s->log(record); + } + } +} + +void Logging::logAsync(const LogRecord& record) { async_log.log(record); } + +Logging::AsyncLogger::AsyncLogger(Logging& parent) : parent(parent) {} + +void Logging::AsyncLogger::log(const LogRecord& record) +{ + { + Lock<FastMutex> l(mutex); + records.put(record); + } + + cv.signal(); +} + +void Logging::AsyncLogger::run() +{ + while (!shouldStop()) + { + LogRecord rec; + { + Lock<FastMutex> l(mutex); + while (records.isEmpty()) + { + cv.wait(mutex); + } + + rec = records.pop(); + } + + parent.log(rec); + } +} \ No newline at end of file diff --git a/src/shared/diagnostic/PrintLogger.h b/src/shared/diagnostic/PrintLogger.h new file mode 100644 index 0000000000000000000000000000000000000000..1b8b06ff02b4f072f311341f7c3a062f80f4574e --- /dev/null +++ b/src/shared/diagnostic/PrintLogger.h @@ -0,0 +1,170 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <fmt/format.h> +#include <miosix.h> + +#include <memory> +#include <string> +#include <vector> + +#include "ActiveObject.h" +#include "LogSink.h" +#include "PrintLoggerData.h" +#include "Singleton.h" +#include "logger/Logger.h" +#include "utils/collections/CircularBuffer.h" + +using std::string; +using std::unique_ptr; +using std::vector; + +using miosix::ConditionVariable; +using miosix::FastMutex; + +#ifndef DEFAULT_STDOUT_LOG_LEVEL +#define DEFAULT_STDOUT_LOG_LEVEL 0 +#endif + +static constexpr unsigned int ASYNC_LOG_BUFFER_SIZE = 100; + +class Logging; + +class PrintLogger +{ +public: + PrintLogger(Logging& logging, string name) : parent(logging), name(name) {} + + PrintLogger getChild(string name); + + template <typename... Args> + void log(uint8_t level, string function, string file, int line, + string format, Args&&... args) + { + vlog(level, function, file, line, format, + fmt::make_args_checked<Args...>(format, args...)); + } + + template <typename... Args> + void logAsync(uint8_t level, string function, string file, int line, + string format, Args&&... args) + { + vlogAsync(level, function, file, line, format, + fmt::make_args_checked<Args...>(format, args...)); + } + +private: + void vlog(uint8_t level, string function, string file, int line, + fmt::string_view format, fmt::format_args args); + void vlogAsync(uint8_t level, string function, string file, int line, + fmt::string_view format, fmt::format_args args); + + LogRecord buildLogRecord(uint8_t level, string function, string file, + int line, fmt::string_view format, + fmt::format_args args); + + Logging& parent; + string name; +}; + +class Logging : public Singleton<Logging> +{ + friend class Singleton<Logging>; + friend class PrintLogger; + +public: + static PrintLogger getLogger(string name) + { + return PrintLogger(*getInstance(), name); + } + + static void addLogSink(unique_ptr<LogSink>& sink) + { + getInstance()->sinks.push_back(std::move(sink)); + } + + static LogSink& getStdOutLogSink() { return *getInstance()->sinks.at(0); } + + static void startAsyncLogger() { getInstance()->async_log.start(); } + +private: + void log(const LogRecord& record); + void logAsync(const LogRecord& record); + + class AsyncLogger : public ActiveObject + { + public: + AsyncLogger(Logging& parent); + void log(const LogRecord& record); + + protected: + void run() override; + + private: + Logging& parent; + CircularBuffer<LogRecord, ASYNC_LOG_BUFFER_SIZE> records; + FastMutex mutex; + ConditionVariable cv; + }; + + Logging() : async_log(*this) + { + unique_ptr<FileLogSink> serial = std::make_unique<FileLogSink>(stdout); + serial->setLevel(DEFAULT_STDOUT_LOG_LEVEL); +#ifndef DEBUG // do not output to serial if not in DEBUG mode + serial->disable(); +#endif + sinks.push_back(std::move(serial)); + } + + AsyncLogger async_log; + vector<unique_ptr<LogSink>> sinks; +}; + +#define LOG(logger, level, ...) \ + logger.log(level, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__) + +#define LOG_DEBUG(logger, ...) LOG(logger, LOGL_DEBUG, __VA_ARGS__) + +#define LOG_INFO(logger, ...) LOG(logger, LOGL_INFO, __VA_ARGS__) + +#define LOG_WARN(logger, ...) LOG(logger, LOGL_WARNING, __VA_ARGS__) + +#define LOG_ERR(logger, ...) LOG(logger, LOGL_ERROR, __VA_ARGS__) + +#define LOG_CRIT(logger, ...) LOG(logger, LOGL_CRITICAL, __VA_ARGS__) + +#define LOG_ASYNC(logger, level, ...) \ + logger.logAsync(level, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__) + +#define LOG_DEBUG_ASYNC(logger, ...) LOG_ASYNC(logger, LOGL_DEBUG, __VA_ARGS__) + +#define LOG_INFO_ASYNC(logger, ...) LOG_ASYNC(logger, LOGL_INFO, __VA_ARGS__) + +#define LOG_WARN_ASYNC(logger, ...) LOG_ASYNC(logger, LOGL_WARNING, __VA_ARGS__) + +#define LOG_ERR_ASYNC(logger, ...) LOG_ASYNC(logger, LOGL_ERROR, __VA_ARGS__) + +#define LOG_CRIT_ASYNC(logger, ...) \ + LOG_ASYNC(logger, LOGL_CRITICAL, __VA_ARGS__) diff --git a/src/shared/diagnostic/PrintLoggerData.h b/src/shared/diagnostic/PrintLoggerData.h new file mode 100644 index 0000000000000000000000000000000000000000..03ad85bf4245016fa3d5eb9db945ce6b62ce9d0b --- /dev/null +++ b/src/shared/diagnostic/PrintLoggerData.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta, Luca Conterio + * + * 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. + */ + +#pragma once + +#include <string> + +static constexpr unsigned int MAX_LOG_STRING_SIZE = + 250; /**< Max length of messages to be logged through the buffered logger */ + +struct LogRecord +{ + int level; + std::string function; + std::string file; + int line; + std::string name; + std::string message; +}; + +enum LogLevel : uint8_t +{ + LOGL_NOTSET = 0, + LOGL_DEBUG = 10, + LOGL_INFO = 20, + LOGL_WARNING = 30, + LOGL_ERROR = 40, + LOGL_CRITICAL = 50 +}; + +/** + * This class is used to output to filesystem (SD card) log messages + * through the buffered logger (src/shared/logger/Logger.h). + */ +struct LoggingString +{ + char log_string[MAX_LOG_STRING_SIZE]; + + static std::string header() { return "log_string\n"; } + + void print(std::ostream& os) const { os << log_string << "\n"; } +}; diff --git a/src/shared/diagnostic/SkywardStack.h b/src/shared/diagnostic/SkywardStack.h index 11134a161cde691dc48204e07af230199b919c73..38fa133cb0aae41323b8169dcb9224e91bf7b91d 100644 --- a/src/shared/diagnostic/SkywardStack.h +++ b/src/shared/diagnostic/SkywardStack.h @@ -1,20 +1,19 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Federico Terraneo - * +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Federico Terraneo + * * 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 + * 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 diff --git a/src/shared/diagnostic/StackData.h b/src/shared/diagnostic/StackData.h index c3f93b86c4269dfce2039e61a2f7fcb66fb7724e..65bb636e9c8c09717bedb38cc1744a40acea0c72 100644 --- a/src/shared/diagnostic/StackData.h +++ b/src/shared/diagnostic/StackData.h @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 @@ -31,7 +30,7 @@ enum ThreadId : uint8_t THID_MAV_RECEIVER, THID_MAV_SENDER, THID_XBEE, - THID_PIKSI, + THID_GPS, THID_EVT_BROKER, THID_LOGGER_PACK, THID_LOGGER_WRITE, diff --git a/src/shared/diagnostic/StackLogger.h b/src/shared/diagnostic/StackLogger.h index bd2cbdf46510cc985e643d39b680e3edaade1683..fe2797db32a7906b5d4945468c999dd0c30bab92 100644 --- a/src/shared/diagnostic/StackLogger.h +++ b/src/shared/diagnostic/StackLogger.h @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/diagnostic/logger.hpp b/src/shared/diagnostic/logger.hpp deleted file mode 100644 index e71c70ea9f0c3b5a993432458ba3d3adef54ba8b..0000000000000000000000000000000000000000 --- a/src/shared/diagnostic/logger.hpp +++ /dev/null @@ -1,104 +0,0 @@ -/* Logger - * Inspired by http://www.drdobbs.com/cpp/a-lightweight-logger-for-c/240147505. - * - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Gabriele Farina - * - * 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. - */ - -#ifndef LOGGER_HPP -#define LOGGER_HPP - -#include <iostream> -#include <string> -#include <sstream> -#include <ctime> -#include <iomanip> - -namespace logging { - -class LogLevel { - public: - LogLevel(int numeric_value, const std::string& string_value) : - _numeric_value(numeric_value), _string_value(string_value) {} - - int getNumericValue() const { - return _numeric_value; - } - - const std::string& getStringValue() const { - return _string_value; - } - - bool operator<(const LogLevel& ref) const { - return _numeric_value < ref._numeric_value; - } - - private: - int _numeric_value; - std::string _string_value; -}; - -// Define some sugary constants -auto INFO = LogLevel(1, "INFO"); -auto WARNING = LogLevel(2, "WARN"); -auto ERROR = LogLevel(3, "ERR!"); -auto CRITICAL = LogLevel(4, "CRIT"); - -class Logger { - public: - void setLogLevel(const LogLevel& new_level) { - _log_level = &new_level; - } - void setOstream(std::ostream* new_ostream) { - _out_stream = new_ostream; - } - - // Print function. Use it like this: - // .print<logging::WARNING>(arg1, arg2, arg3) - template <const LogLevel& level, typename... Args> - void log(Args...args) { - if (level < *_log_level) - return; - - std::stringstream stream; - - stream << "[" << level.getStringValue() << "] "; - _printOnSStream(&stream, args...); - *_out_stream << stream.str() << std::endl; - } - - private: - void _printOnSStream(std::stringstream*) {} - - template <typename FirstArg, typename... Args> - void _printOnSStream(std::stringstream* stream, - const FirstArg& arg, Args...args) { - *stream << arg; - _printOnSStream(stream, args...); - } - - std::ostream* _out_stream = &std::cout; - const LogLevel* _log_level = &INFO; -}; - -} // namespace logging - -#endif // LOGGER_HPP diff --git a/src/shared/diagnostic/logger_example.cpp b/src/shared/diagnostic/logger_example.cpp deleted file mode 100644 index 351c2841b34305233a8d93d9351aa4dc894e3a59..0000000000000000000000000000000000000000 --- a/src/shared/diagnostic/logger_example.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Gabriele Farina - * - * 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. - */ - -#include "logger.hpp" - -int main() -{ - int i = 42; - auto Logger = logging::Logger(); - - Logger.log<logging::INFO>("Some warning using variable i (=", i, ")"); - Logger.log<logging::WARNING>("Some warning using variable i (=", i, ")"); - Logger.log<logging::ERROR>("Some warning using variable i (=", i, ")"); - Logger.log<logging::CRITICAL>("Some warning using variable i (=", i, ")"); - - // Set severity to >= WARNING. - Logger.setLogLevel(logging::WARNING); - Logger.log<logging::INFO>("Some warning using variable i (=", i, ")"); - Logger.log<logging::WARNING>("Some warning using variable i (=", i, ")"); - Logger.log<logging::ERROR>("Some warning using variable i (=", i, ")"); - Logger.log<logging::CRITICAL>("Some warning using variable i (=", i, ")"); - - // Set severity to >= ERROR. - Logger.setLogLevel(logging::ERROR); - Logger.log<logging::INFO>("Some warning using variable i (=", i, ")"); - Logger.log<logging::WARNING>("Some warning using variable i (=", i, ")"); - Logger.log<logging::ERROR>("Some warning using variable i (=", i, ")"); - Logger.log<logging::CRITICAL>("Some warning using variable i (=", i, ")"); -} diff --git a/src/shared/drivers/BusTemplate.h b/src/shared/drivers/BusTemplate.h index 0652a840bf017fa009fbc6883e85947ac5454318..a7df169be307effc63ba74b4726ce22121d8c6f9 100644 --- a/src/shared/drivers/BusTemplate.h +++ b/src/shared/drivers/BusTemplate.h @@ -1,8 +1,5 @@ -/* Bus base class - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Illya Dudchenko, Matteo Michele Piazzolla, Silvano Seva, - * Alain Carlucci +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Illya Dudchenko, Matteo Piazzolla, Silvano Seva, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -16,7 +13,7 @@ * * 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 + * 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 @@ -110,6 +107,9 @@ private: inline void _write(uint8_t byte) const { + // Wait until the peripheral is ready to transmit + while ((getSPIAddr(N)->SR & SPI_SR_TXE) == 0) + ; getSPIAddr(N)->DR = byte; while ((getSPIAddr(N)->SR & SPI_SR_RXNE) == 0) ; @@ -127,6 +127,9 @@ private: inline uint8_t _read() const { + // Wait until the peripheral is ready to transmit + while ((getSPIAddr(N)->SR & SPI_SR_TXE) == 0) + ; getSPIAddr(N)->DR = 0; while ((getSPIAddr(N)->SR & SPI_SR_RXNE) == 0) ; @@ -157,6 +160,9 @@ private: */ inline uint8_t _transfer(uint8_t* byte) { + // Wait until the peripheral is ready to transmit + while ((getSPIAddr(N)->SR & SPI_SR_TXE) == 0) + ; getSPIAddr(N)->DR = *byte; while ((getSPIAddr(N)->SR & SPI_SR_RXNE) == 0) ; @@ -210,7 +216,7 @@ private: return n_spi == 1 || n_spi == 2 ? 5 : 6; } - constexpr SPI_TypeDef* getSPIAddr(unsigned n) + constexpr SPI_TypeDef* getSPIAddr(unsigned n) const { return n == 1 ? SPI1 : n == 2 ? SPI2 : SPI3; } diff --git a/src/shared/drivers/HardwareTimer.h b/src/shared/drivers/HardwareTimer.h index f983b78a4d7245b062aa6281a5f497bbd4c72d63..11cc6d5fb10151e38c80eb9ede984e4ef5cc07f9 100644 --- a/src/shared/drivers/HardwareTimer.h +++ b/src/shared/drivers/HardwareTimer.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2018-2021 Skyward Experimental Rocketry + * Authors: Luca Erbetta, Davide Mor * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,19 +20,11 @@ * THE SOFTWARE. */ -#ifndef SRC_SHARED_DRIVERS_HARDWARETIMER_H -#define SRC_SHARED_DRIVERS_HARDWARETIMER_H +#pragma once -#include <kernel/scheduler/scheduler.h> #include <miosix.h> -#include <cassert> -#include "Debug.h" -#if !defined _ARCH_CORTEXM4_STM32F4 -#error "Unsupported architecture!" -#endif - -using miosix::FastInterruptDisableLock; +#include <type_traits> class TimerUtils { @@ -48,22 +39,22 @@ public: * @brief returns the timer clock frequency before the prescaler. * Function borrowed from the SyncronizedServo class in Miosix. * - * @return unsigned int Prescaler input frequency + * @return Prescaler input frequency */ - static unsigned int getPrescalerInputFrequency(InputClock input_clock) + static uint32_t getPrescalerInputFrequency(InputClock input_clock) { // The global variable SystemCoreClock from ARM's CMSIS allows to // know // the CPU frequency. - unsigned int freq = SystemCoreClock; + uint32_t freq = SystemCoreClock; // The position of the PPRE1 bit in RCC->CFGR is different in some stm32 #ifdef _ARCH_CORTEXM3_STM32 - const unsigned int ppre1 = 8; -#error "Architecture not currently supported" + const uint32_t ppre1 = 8; + const uint32_t ppre2 = 11; #else // stm32f2 and f4 - const unsigned int ppre1 = 10; - const unsigned int ppre2 = 13; + const uint32_t ppre1 = 10; + const uint32_t ppre2 = 13; #endif // The timer frequency may however be a submultiple of the CPU // frequency, @@ -89,59 +80,181 @@ public: return freq; } -}; -template <typename T, unsigned Tim> -class HardwareTimer -{ - using TimerType = HardwareTimer<T, Tim>; + template <typename Type> + static float toMicroSeconds(Type &tim, typename Type::Output ticks) + { + return (1.0f * ticks * 1000000 * (1 + tim.prescaler)) / + tim.prescaler_freq; + } - static_assert(std::is_same<T, uint32_t>::value || - std::is_same<T, uint16_t>::value, - "Timer output type must be either uint16_t or uint32_t"); + template <typename Type> + static uint64_t toIntMicroSeconds(Type &tim, typename Type::Output ticks) + { + return ((uint64_t)ticks * 1000000 * (uint64_t)(1 + tim.prescaler)) / + (uint64_t)tim.prescaler_freq; + } - static_assert(!((Tim == 2 || Tim == 5) && std::is_same<T, uint16_t>::value), - "Tim2 and Tim5 are 32 bit timers!"); + template <typename Type> + static float toMilliSeconds(Type &tim, typename Type::Output ticks) + { + return (1.0f * ticks * 1000 * (1 + tim.prescaler)) / tim.prescaler_freq; + } -public: - inline static TimerType& instance() + template <typename Type> + static float toSeconds(Type &tim, typename Type::Output ticks) { - static TimerType timer; - return timer; + return (1.0f * ticks * (1 + tim.prescaler)) / tim.prescaler_freq; } - inline T start() + template <typename Type> + static float getResolution(Type &tim) { - if (!ticking) - { - ticking = true; - TIM->CNT = 0; // Reset the counter + // Resolution in us = number microseconds in one tick + return toMicroSeconds(tim, 1); + } - TIM->CR1 |= TIM_CR1_CEN; - return 0; - } - else - { - return tick(); - } + template <typename Type> + static float getMaxDuration(Type &tim) + { + // Maximum duration = number of seconds to count to the auto_reload + // (reset) value. + return toSeconds(tim, tim.auto_reload); } +}; - inline T tick() { return TIM->CNT; } +/** + * @brief Selects to operating mode of the timer. + */ +enum class TimerMode +{ + Single, //< Use a single timer as a source of time. + Chain //< Chain two timers as a source of time. +}; - inline T stop() - { - if (ticking) - { - T tick = TIM->CNT; - TIM->CR1 &= ~TIM_CR1_CEN; - ticking = false; - return tick; - } +/** + * @brief Which trigger connection to use. + * + * This dictates the trigger for the slave timer, + * this must be choosen depending on the master slave. + * + * Here you can see a table with all the possible combinations + * taken directly from the STM32 datasheet, on the top row you can + * see the trigger pin, while on the left there is the choosen slave timer, + * by looking at the intersection you can find the connected master timer. + * + * PS: The datasheet where this comes from is for: + * - STM32F101xx + * - STM32F102xx + * - STM32F103xx + * - STM32F105xx + * - STM32F107xx + * + * ``` + * ITR0 ITR1 ITR2 ITR3 + * TIM1 TIM5 TIM2 TIM3 TIM4 + * TIM2 TIM1 TIM8 TIM3 TIM4 + * TIM3 TIM1 TIM2 TIM5 TIM4 + * TIM4 TIM1 TIM2 TIM3 TIM8 + * TIM5 TIM2 TIM3 TIM4 TIM8 + * TIM6 ---- ---- ---- ---- + * TIM7 ---- ---- ---- ---- + * TIM8 TIM1 TIM2 TIM4 TIM5 + * TIM9 TIM2 TIM3 TIM10 TIM11 + * TIM10 ---- ---- ---- ---- + * TIM11 ---- ---- ---- ---- + * TIM12 TIM4 TIM5 TIM13 TIM14 + * TIM13 ---- ---- ---- ---- + * TIM14 ---- ---- ---- ---- + * ``` + * + */ +enum class TimerTrigger +{ + ITR0 = 0, + ITR1 = TIM_SMCR_TS_0, + ITR2 = TIM_SMCR_TS_1, + ITR3 = TIM_SMCR_TS_0 | TIM_SMCR_TS_1 +}; - return 0; +/** + * @brief Class for handling Hardware Timers and perform time unit conversions + * + * @tparam Type type to use to access timer register (uint32_t if 32 bit timer, + * uint16_t if 16 bit timer) + * @tparam Operating mode of the timer (see TimerMode) + */ +template <typename Type, TimerMode Mode = TimerMode::Single> +class HardwareTimer; + +template <typename Type> +class HardwareTimer<Type, TimerMode::Single> +{ + friend class TimerUtils; + + static_assert(std::is_same<Type, uint32_t>::value || + std::is_same<Type, uint16_t>::value, + "Type must be either uint32_t or uint16_t."); + +public: + using Output = Type; + + /** + * @brief Creates a new HardwareTimer instance + * + * @param timer The timer to use (pointer to timer registers struct) + * @param psc_input_freq Input frequency of the timer's prescaler, see + * TimerUtils::getPrescalerInputFrequency() + */ + HardwareTimer(TIM_TypeDef *timer, uint32_t psc_input_freq) + : tim(timer), prescaler_freq(psc_input_freq) + { } - bool isTicking() { return ticking; } + /** + * @brief Starts the timer returns the current tick + * + * @return Current tick + */ + Type start(); + + /** + * @brief Stops the timer if already started + * + * @return Current tick or 0 if already stopped + */ + Type stop(); + + /** + * @brief Returns current tick + */ + Type tick(); + + /** + * @brief Sets the prescaler value. + * The tick frequency is defined as the clock frequency value of the timer + * divided by the prescaler. See the datasheet for further information. + */ + void setPrescaler(uint16_t prescaler); + + /** + * @brief Set the auto reload value. + * The auto reload value is the maximum value the timer can count to. + * After this value is reached, the timers counts back from 0. + */ + void setAutoReload(Type auto_reload); + + /** + * @brief Converts from ticks to microseconds, using the current prescaler + * setting + * + * @param ticks + * @return uint64_t + */ + uint64_t toIntMicroSeconds(Type ticks) + { + return TimerUtils::toIntMicroSeconds(*this, ticks); + } /** * @brief Converts from ticks to microseconds, using the current prescaler @@ -150,10 +263,9 @@ public: * @param ticks * @return float */ - float toMicroSeconds(T ticks) + float toMicroSeconds(Type ticks) { - return (1.0f * ticks * 1000000 * (1 + prescaler)) / - (float)TimerUtils::getPrescalerInputFrequency(clk); + return TimerUtils::toMicroSeconds(*this, ticks); } /** @@ -163,10 +275,9 @@ public: * @param ticks * @return float */ - float toMilliSeconds(T ticks) + float toMilliSeconds(Type ticks) { - return (1.0f * ticks * 1000 * (1 + prescaler)) / - (float)TimerUtils::getPrescalerInputFrequency(clk); + return TimerUtils::toMilliSeconds(*this, ticks); } /** @@ -176,154 +287,231 @@ public: * @param ticks * @return float */ - float toSeconds(T ticks) - { - return (1.0f * ticks * (1 + prescaler)) / - (float)TimerUtils::getPrescalerInputFrequency(clk); - } + float toSeconds(Type ticks) { return TimerUtils::toSeconds(*this, ticks); } - /*static float toSeconds(T tick) {} - static float toMilliseconds(T tick) {} - static float toMicroSeconds(T tick) {}*/ + /** + * @brief Returns the current resolution of the timer in microseconds. + * @return Resolution in microseconds + */ + float getResolution() { return TimerUtils::getResolution(*this); } /** - * @brief Sets the prescaler value. - * The tick frequency is defined as the clock frequency value of the timer - * divided by the prescaler. See the datasheet for further information. + * @brief Returns the maximum time the timer can measure before restarting + * from 0, in seconds. + * + * @return Maximum timer duration in seconds. */ - void setPrescaler(uint16_t prescaler) + float getMaxDuration() { return TimerUtils::getMaxDuration(*this); } + +private: + TIM_TypeDef *tim; + uint32_t prescaler_freq; + + bool ticking = false; + uint16_t prescaler = 0; + Type auto_reload = + static_cast<Type>(-1); // Max value of Type (Type is unsigned) +}; + +template <typename Type> +inline Type HardwareTimer<Type>::start() +{ + if (!ticking) + { + ticking = true; + tim->CNT = 0; // Reset the counter + + tim->CR1 |= TIM_CR1_CEN; + return 0; + } + else { - // reset(); - TIM->PSC = prescaler; - TIM->EGR = - TIM_EGR_UG; // Send an update event to load the new prescaler - // value - this->prescaler = prescaler; + return tick(); } +} + +template <typename Type> +inline Type HardwareTimer<Type>::stop() +{ + if (ticking) + { + tim->CR1 &= ~TIM_CR1_CEN; + ticking = false; + return tim->CNT; + } + + return 0; +} + +template <typename Type> +inline Type HardwareTimer<Type>::tick() +{ + return tim->CNT; +} + +template <typename Type> +void HardwareTimer<Type>::setPrescaler(uint16_t prescaler) +{ + this->prescaler = prescaler; + tim->PSC = prescaler; + tim->EGR = TIM_EGR_UG; // Send an update event to load the new prescaler + // value +} + +template <typename Type> +void HardwareTimer<Type>::setAutoReload(Type auto_reload) +{ + this->auto_reload = auto_reload; + tim->ARR = auto_reload; + tim->EGR = TIM_EGR_UG; // Send an update event to load the new auto-reload +} + +template <typename Type> +class HardwareTimer<Type, TimerMode::Chain> +{ + friend class TimerUtils; + + // TODO: Does this still work with two 32bit timers? + static_assert(std::is_same<Type, uint32_t>::value, + "Type must be uint32_t."); + +public: + using Output = Type; /** - * @brief Set the auto reload value. - * The auto reload value is the maximum value the timer can count to. - * After this value is reached, the timers counts back from 0. + * @brief Creates a new HardwareTimer instance + * + * @param tim The timer to use as master (pointer to timer registers + * struct) + * @param slave The timer to use as slave (pointer to timer registers + * struct) + * @param trigger The slave input trigger, should be choosen according to + * master and slave (see TimerTrigger) + * @param prescaler_freq Input frequency of the timer's prescaler, see + * TimerUtils::getPrescalerInputFrequency() */ - void setAutoReload(T auto_reload) + HardwareTimer(TIM_TypeDef *tim, TIM_TypeDef *slave, TimerTrigger trigger, + uint32_t prescaler_freq); + + Type start(); + + Type stop(); + + Type tick(); + + void setPrescaler(uint16_t prescaler); + + void setAutoReload(Type auto_reload); + + uint64_t toIntMicroSeconds(Type ticks) { - if (ticking) - { - stop(); - } - this->auto_reload = auto_reload; - TIM->ARR = auto_reload; + return TimerUtils::toIntMicroSeconds(*this, ticks); + } - TIM->EGR = - TIM_EGR_UG; // Send an update event to load the new auto-reload + float toMicroSeconds(Type ticks) + { + return TimerUtils::toMicroSeconds(*this, ticks); } -private: - HardwareTimer() + float toMilliSeconds(Type ticks) { - switch (Tim) - { - case 1: - TIM_EN = RCC_APB2ENR_TIM1EN; - TIM = TIM1; - break; - default: // Use TIM2 as default - TRACE("Wrong timer selected. Using TIM2.\n"); - case 2: - TIM_EN = RCC_APB1ENR_TIM2EN; - TIM = TIM2; - break; - case 3: - TIM_EN = RCC_APB1ENR_TIM3EN; - TIM = TIM3; - break; - case 4: - TIM_EN = RCC_APB1ENR_TIM4EN; - TIM = TIM4; - break; - case 5: - TIM_EN = RCC_APB1ENR_TIM5EN; - TIM = TIM5; - break; - case 6: - TIM_EN = RCC_APB1ENR_TIM6EN; - TIM = TIM6; - break; - case 7: - TIM_EN = RCC_APB1ENR_TIM7EN; - TIM = TIM7; - break; - case 8: - TIM_EN = RCC_APB2ENR_TIM8EN; - TIM = TIM8; - break; - case 9: - TIM_EN = RCC_APB2ENR_TIM9EN; - TIM = TIM9; - break; - case 10: - TIM_EN = RCC_APB2ENR_TIM10EN; - TIM = TIM10; - break; - case 11: - TIM_EN = RCC_APB2ENR_TIM11EN; - TIM = TIM11; - break; - case 12: - TIM_EN = RCC_APB1ENR_TIM12EN; - TIM = TIM12; - break; - case 13: - TIM_EN = RCC_APB1ENR_TIM13EN; - TIM = TIM13; - break; - case 14: - TIM_EN = RCC_APB1ENR_TIM14EN; - TIM = TIM14; - break; - } + return TimerUtils::toMilliSeconds(*this, ticks); + } - if (Tim == 1 || (Tim >= 8 && Tim <= 11)) - { - clk = TimerUtils::InputClock::APB2; + float toSeconds(Type ticks) { return TimerUtils::toSeconds(*this, ticks); } - FastInterruptDisableLock dLock; - RCC->APB2ENR |= TIM_EN; - RCC_SYNC(); - } - else - { - clk = TimerUtils::InputClock::APB1; + float getResolution() { return toMicroSeconds(1); } - FastInterruptDisableLock dLock; - RCC->APB1ENR |= TIM_EN; - RCC_SYNC(); - } + float getMaxDuration() { return toSeconds(auto_reload); } - // Reset control registers - TIM->CR1 = 0; - TIM->CR2 = 0; +private: + TIM_TypeDef *tim, *slave; + uint32_t prescaler_freq; + + bool ticking = false; + uint16_t prescaler = 0; + Type auto_reload = + static_cast<Type>(-1); // Max value of Type (Type is unsigned) +}; - auto_reload = static_cast<T>(-1); // Max value of T (unsigned int) - TIM->ARR = auto_reload; +template <typename Type> +inline HardwareTimer<Type, TimerMode::Chain>::HardwareTimer( + TIM_TypeDef *tim, TIM_TypeDef *slave, TimerTrigger trigger, + uint32_t prescaler_freq) + : tim(tim), slave(slave), prescaler_freq(prescaler_freq) +{ + // Trigger selection from parameter + const uint8_t ts = static_cast<uint8_t>(trigger); + // Slave mode selection 0b111 -> External Clock mode 1 + const uint8_t sms = TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_2; + // Master mode selection 0b010 -> Update + const uint8_t mms = TIM_CR2_MMS_1; + + // Configure timer1 as master and timer2 as slave + tim->CR2 &= ~TIM_CR2_MMS; + tim->CR2 |= mms; + + slave->SMCR &= ~(TIM_SMCR_TS | TIM_SMCR_SMS); + slave->SMCR |= ts | sms; +} + +template <typename Type> +inline Type HardwareTimer<Type, TimerMode::Chain>::start() +{ + if (!ticking) + { + ticking = true; - prescaler = 0; - TIM->PSC = prescaler; + slave->CNT = tim->CNT = 0; - TIM->EGR = - TIM_EGR_UG; // Send an update event to load the new prescaler and - // auto reload values + slave->CR1 |= TIM_CR1_CEN; + tim->CR1 |= TIM_CR1_CEN; + return 0; } + else + { + return tick(); + } +} - TIM_TypeDef* TIM; - uint32_t TIM_EN; - TimerUtils::InputClock clk; +template <typename Type> +inline Type HardwareTimer<Type, TimerMode::Chain>::stop() +{ + if (ticking) + { + slave->CR1 &= ~TIM_CR1_CEN; + tim->CR1 &= ~TIM_CR1_CEN; - T auto_reload; - uint16_t prescaler; + ticking = false; + return tick(); + } + else + { + return 0; + } +} - bool ticking = false; -}; +template <typename Type> +inline Type HardwareTimer<Type, TimerMode::Chain>::tick() +{ + return (Type)tim->CNT | ((Type)slave->CNT << (sizeof(Type) * 8 / 2)); +} -#endif /* SRC_SHARED_DRIVERS_HARDWARETIMER_H */ +template <typename Type> +void HardwareTimer<Type, TimerMode::Chain>::setPrescaler(uint16_t prescaler) +{ + this->prescaler = prescaler; + tim->PSC = prescaler; + tim->EGR = TIM_EGR_UG; // Send an update event to load the new prescaler + // value +} + +template <typename Type> +void HardwareTimer<Type, TimerMode::Chain>::setAutoReload(Type auto_reload) +{ + this->auto_reload = auto_reload; + // We only care about the higher bits + slave->ARR = auto_reload >> (sizeof(Type) * 8 / 2); + slave->EGR = TIM_EGR_UG; // Send an update event to load the new + // auto-reload +} \ No newline at end of file diff --git a/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.cpp b/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.cpp index 271b50f53b6d74fd2bf05b95bfb8f186c040a9e1..fd10b0a2c5233929b1ad65e119991bf914b591eb 100644 --- a/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.cpp +++ b/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.cpp @@ -1,23 +1,23 @@ -/* - * Inter Stormtrooper Boards communication protocol through STM32's - * USART2 interface - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2018 Skyward Experimental Rocketry * Author: Silvano Seva - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * + * 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. */ #include "IsbProtocol_serial2.h" diff --git a/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.h b/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.h index c031fe1402c1815b936ddd45504152414a39846d..261a42fa1fc7d01345f5e00a0cf0e71b05760f37 100644 --- a/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.h +++ b/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.h @@ -1,23 +1,23 @@ -/* - * Inter Stormtrooper Boards communication protocol through STM32's - * USART2 interface - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * + * 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. */ #ifndef ISBPROTOCOL_SERIAL2_H @@ -39,7 +39,7 @@ * response (if expected). Owing to this model, if two stormtrooper slaves need * to exchange data they have to rely on the stormtrooper master acting as a * router between them. - * + * * The packet structure is this: * * +------+-----+------+-----+ diff --git a/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.cpp b/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.cpp index ffe44e566ab9d0f8606653fce6e315e4966dedca..4222b8496705ad6adf0c624beb0374b9ab5ae6a5 100644 --- a/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.cpp +++ b/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.cpp @@ -20,6 +20,28 @@ * */ +/* Copyright (c) 2017 Skyward Experimental Rocketry + * Author: Silvano Seva + * + * 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. + */ + #include "IsbProtocol_serial3.h" using namespace std; diff --git a/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.h b/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.h index 04738688b5f2237fb54dbcd24b54196aa12265c3..c0d850ce08590a8afad64fae432ec25c7f865187 100644 --- a/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.h +++ b/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.h @@ -1,23 +1,23 @@ -/* - * Inter Stormtrooper Boards communication protocol through STM32's - * USART3 interface - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * + * 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. */ #ifndef ISBPROTOCOL_SERIAL3_H @@ -39,7 +39,7 @@ * response (if expected). Owing to this model, if two stormtrooper slaves need * to exchange data they have to rely on the stormtrooper master acting as a * router between them. - * + * * The packet structure is this: * * +------+-----+------+-----+ diff --git a/src/shared/drivers/Leds.cpp b/src/shared/drivers/Leds.cpp index 08bab0f88f4777210cc58e023eb72e3e538d945d..2ec4677a10026890df5834c84723b3a37fb45455 100644 --- a/src/shared/drivers/Leds.cpp +++ b/src/shared/drivers/Leds.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/Leds.h b/src/shared/drivers/Leds.h index a1c1522a02a6d325ae9930560bd3e137d6c18cdc..0cef4fa338b3ebe6a693107b4701a4c1815fecab 100644 --- a/src/shared/drivers/Leds.h +++ b/src/shared/drivers/Leds.h @@ -1,5 +1,5 @@ /* Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci, Matteo Michele Piazzolla, Federico Terraneo + * Authors: Alain Carlucci, Matteo Piazzolla, Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/SoftwareI2CAdapter.h b/src/shared/drivers/SoftwareI2CAdapter.h index 60931e79dbd181150a1124ec8d3950c412d3622e..219aad3a727c40d1cdbcf2eefcb295ff94e40478 100644 --- a/src/shared/drivers/SoftwareI2CAdapter.h +++ b/src/shared/drivers/SoftwareI2CAdapter.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/Transceiver.h b/src/shared/drivers/Transceiver.h index 3bfd631a8ee7e878f3ef8293af771ffc3005caaf..f1a3456d8f11bc0f630a73c286d538fc601841d5 100644 --- a/src/shared/drivers/Transceiver.h +++ b/src/shared/drivers/Transceiver.h @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Nuno Barcellos + * Author: Nuno Barcellos * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -23,8 +23,6 @@ #ifndef TRANSCEIVER_H #define TRANSCEIVER_H -#include <Common.h> - class Transceiver { public: diff --git a/src/shared/drivers/Xbee/APIFrameParser.cpp b/src/shared/drivers/Xbee/APIFrameParser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..44751d263e81bc2aa6841114782830bfdd592329 --- /dev/null +++ b/src/shared/drivers/Xbee/APIFrameParser.cpp @@ -0,0 +1,110 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#include "APIFrameParser.h" + +#include "Debug.h" + +namespace Xbee +{ + +APIFrameParser::APIFrameParser() {} + +APIFrameParser::ParseResult APIFrameParser::parse(uint8_t byte, APIFrame* frame) +{ + switch (parser_state) + { + // Look for the start of frame delimiter + case ParserState::FIND_START: + + if (byte == START_DELIMITER) + { + parser_state = ParserState::READ_LENGTH_1; + } + break; + // Read most significant byte of the length + case ParserState::READ_LENGTH_1: + frame->length = byte; + parser_state = ParserState::READ_LENGTH_2; + + break; + // Read least significant byte of the length + case ParserState::READ_LENGTH_2: + frame->length |= ((uint16_t)byte << 8) & 0xFF00; + // At least two frame data bytes (frame_type and a payload) + if (swapBytes16(frame->length) < 2) + { + parser_state = ParserState::FIND_START; + return ParseResult::FAIL; + } + + if (frame->getFrameDataLength() > FRAME_DATA_SIZE) + { + parser_state = ParserState::FIND_START; + return ParseResult::FAIL; + } + + parser_state = ParserState::READ_FRAME_TYPE; + break; + // Read frame type + case ParserState::READ_FRAME_TYPE: + frame->frame_type = byte; + parser_state = ParserState::READ_FRAME_DATA; + break; + // Read the data frame + case ParserState::READ_FRAME_DATA: + frame->frame_data[current_frame_data_index++] = byte; + + if (current_frame_data_index == frame->getFrameDataLength()) + { + current_frame_data_index = 0; + parser_state = ParserState::READ_CHECKSUM; + } + break; + // Read & verify checksum + case ParserState::READ_CHECKSUM: + frame->checksum = byte; + parser_state = ParserState::FIND_START; + + if (frame->verifyChecksum()) + { + return ParseResult::SUCCESS; + } + else + { + LOG_ERR(logger, "Wrong packet checksum!"); + return ParseResult::FAIL; + } + break; + } + + if (parser_state != ParserState::FIND_START) + { + return ParseResult::PARSING; + } + else + { + return ParseResult::IDLE; + } +} + +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/APIFrameParser.h b/src/shared/drivers/Xbee/APIFrameParser.h new file mode 100644 index 0000000000000000000000000000000000000000..4a1f5abc2dde7d6ac62544e5199ddc58d9226be6 --- /dev/null +++ b/src/shared/drivers/Xbee/APIFrameParser.h @@ -0,0 +1,90 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <diagnostic/PrintLogger.h> + +#include <cstdint> + +#include "APIFrames.h" + +namespace Xbee +{ + +/** + * @brief Parses a byte sequence into an Xbee APIFrame + */ +class APIFrameParser +{ +public: + /** + * @brief Current state of the parser internal state machine + */ + enum class ParserState + { + FIND_START, + READ_LENGTH_1, + READ_LENGTH_2, + READ_FRAME_TYPE, + READ_FRAME_DATA, + READ_CHECKSUM + }; + + /** + * @brief Result of the last parse operation + * + */ + enum class ParseResult : uint8_t + { + IDLE = 0, // No frame has been found yet + PARSING, // Currently paring a frame + SUCCESS, // A frame has been parsed successfully + FAIL // The parsed frame was invalid (eg wrong length, wrong checksum) + }; + + APIFrameParser(); + + /** + * @brief Parses a single byte. When this function returns + * ParseResult:SUCESS, @p frame contains a valid APIFrame + * + * @param byte Byte to parse + * @param frame Frame to be constructed from the parsed data. + * @return ParseResult If SUCCESS is returned, then the provided frame is + * valid and can be used + */ + ParseResult parse(uint8_t byte, APIFrame* frame); + + /** + * @brief Returns the current state of the parser + */ + ParserState getParserState() { return parser_state; } + +private: + ParserState parser_state = ParserState::FIND_START; + uint16_t current_frame_data_index = 0; + + PrintLogger logger = Logging::getLogger("apiframeparser"); +}; + +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/APIFrames.h b/src/shared/drivers/Xbee/APIFrames.h new file mode 100644 index 0000000000000000000000000000000000000000..ca7cc9209869b13480be4767af23c3c7aed972aa --- /dev/null +++ b/src/shared/drivers/Xbee/APIFrames.h @@ -0,0 +1,474 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <algorithm> +#include <cassert> +#include <cstdint> +#include <cstring> +#include <ostream> +#include <string> +#include <type_traits> +#include <utility> + +#ifndef COMPILE_FOR_X86 +#include <interfaces/endianness.h> +#elif defined(__GNUC__) + +#define swapBytes64 __builtin_bswap64 + +static uint16_t swapBytes16(uint16_t x) +{ + return (x & 0xFF00) >> 8 | (x & 0x00FF) << 8; +} +#endif + +using std::min; +using std::string; + +namespace Xbee +{ +/// Maximum length for a TX/RX frame payload. User-configurable using the "NP" +/// AT command or the Xbee configuration software (see Xbee datasheet) +static constexpr size_t MIN_RX_PACKET_FRAME_SIZE = 11; +static constexpr size_t MIN_TX_REQUEST_FRAME_SIZE = 13; +static constexpr size_t MIN_AT_COMMAND_FRAME_SIZE = 3; +static constexpr size_t MIN_AT_RESPONSE_FRAME_SIZE = 4; + +static constexpr size_t TX_STATUS_FRAME_SIZE = 6; +static constexpr size_t MODEM_STATUS_FRAME_SIZE = 1; + +static constexpr uint16_t MAX_PACKET_PAYLOAD_LENGTH = 256; +static constexpr uint16_t MAX_AT_COMMAND_PARAMS_LENGTH = 20; +static constexpr uint16_t MAX_AT_COMMAND_RESPONSE_LENGTH = 30; + +static constexpr size_t FRAME_DATA_SIZE = + MAX_PACKET_PAYLOAD_LENGTH + MIN_TX_REQUEST_FRAME_SIZE; + +static constexpr size_t MAX_API_FRAME_SIZE = FRAME_DATA_SIZE + 5; +static constexpr size_t MIN_API_FRAME_SIZE = 5; + +static constexpr uint64_t ADDRESS_BROADCAST = 0xFFFF; +static constexpr uint8_t START_DELIMITER = 0x7E; + +enum FrameType : uint8_t +{ + FTYPE_AT_COMMAND = 0x08, + FTYPE_AT_COMMAND_QUEUE = 0x09, + FTYPE_TX_REQUEST = 0x10, + FTYPE_AT_COMMAND_RESPONSE = 0x88, + FTYPE_MODEM_STATUS = 0x8A, + FTYPE_TX_STATUS = 0x8B, + FTYPE_RX_PACKET_FRAME = 0x90, +}; + +enum TransmitOptionsBitfield : uint8_t +{ + TO_DISABLE_ACK = 0x01, + TO_DISABLE_RD = 0x02, + TO_NACK = 0x04, + TO_TRACE_ROUTE = 0x08, + + TO_DM_POINT_MULTIPOINT = 0x40, + TO_DM_REPEATER_MODE = 0x80, + TO_DM_DIGIMESH = 0xC0 +}; + +enum CommandStatusBitfield : uint8_t +{ + CS_OK = 0x00, + CS_ERROR = 0x01, + CS_INVALID_COMMAND = 0x02, + CS_INVALID_PARAMETER = 0x03, + CS_RSSI_INVALID = 0x40, + CS_IS_REMOTE_COMMAND = 0x80 +}; + +enum ModemStatus : uint8_t +{ + MS_HARDWARE_RESET = 0x00, + MS_WATCHDOG_TIMER_RESET = 0x01, + MS_NETWORK_WOKE_UP = 0x0B, + MS_NETWORK_WENT_SLEEP = 0x0C, +}; + +enum DeliveryStatus : uint8_t +{ + DELS_SUCCESS = 0x00, + DELS_MAC_ACK_FAILURE = 0x01, + DELS_COLL_AVOID_FAILURE = 0x02, + DELS_NO_SPECTRUM_AVAILABLE = 0x03, + DELS_NET_ACK_FAILURE = 0x21, + DELS_ROUTE_NOT_FOUND = 0x25, + DELS_INT_RESOURCE_ERR = 0x31, + DELS_INTERNAL_ERROR = 0x32, + DELS_PAYLOAD_TOO_LARGE = 0x74, + DELS_INDIRECT_MSG_REQ = 0x75, +}; + +enum DiscoveryStatus : uint8_t +{ + DISCS_NO_DISC_OVERHEAD = 0x00, + DISCS_ROUTE_DISCOVERY = 0x02 +}; + +enum ReceiveOptions : uint8_t +{ + RO_PACKET_ACK = 0x00, + RO_PACKET_IS_BROADCAST = 0x01, + RO_POINT_MULTIPOINT = 0x40, + RO_REPEATER_MODE = 0x80, + RO_DIGIMESH = 0xC0, +}; + +#pragma pack(1) +struct APIFrame +{ + uint8_t start_del = 0x7E; + uint16_t length = 0; + uint8_t frame_type = 0; + uint8_t frame_data[FRAME_DATA_SIZE]; + + uint8_t checksum = 0; + // Used for logging, not part of the standard Xbee API Frame + long long timestamp = 0; + + APIFrame() { memset(frame_data, 0, FRAME_DATA_SIZE); } + + uint16_t getFrameDataLength() const + { + size_t len = swapBytes16(length) - 1; + assert(len <= FRAME_DATA_SIZE); + + return min(len, FRAME_DATA_SIZE); + } + + void setFrameDataLength(uint16_t len) + { + assert(len <= FRAME_DATA_SIZE); + length = swapBytes16(min((size_t)(len + 1), FRAME_DATA_SIZE + 1)); + } + + bool verifyChecksum() const + { + assert(getFrameDataLength() <= FRAME_DATA_SIZE); + // Sum all the bytes including checksum and frame type. + // The sum can be stored in a uint8_t since we only care about the least + // significant byte. + uint8_t sum = checksum + frame_type; + for (uint16_t i = 0; i < getFrameDataLength(); ++i) + { + sum += frame_data[i]; + } + return sum == 0xFF; + } + + void calcChecksum() + { + assert(getFrameDataLength() <= FRAME_DATA_SIZE); + checksum = frame_type; + for (uint16_t i = 0; i < getFrameDataLength(); ++i) + { + checksum += frame_data[i]; + } + + checksum = 0xFF - checksum; + } + + template <typename FrameType> + FrameType* toFrameType() + { + static_assert( + std::is_base_of<APIFrame, FrameType>::value || + std::is_same<APIFrame, FrameType>::value, + "FrameType must be derived from APIFrame or be an APIFrame"); + + return reinterpret_cast<FrameType*>(this); + } + + size_t toBytes(uint8_t* bytes) + { + memcpy(bytes, this, 4 + getFrameDataLength()); + + bytes[4 + getFrameDataLength()] = checksum; + return 5 + getFrameDataLength(); + } + + static bool fromBytes(uint8_t* bytes, size_t size, APIFrame* f) + { + if (size >= MIN_API_FRAME_SIZE && size <= MAX_API_FRAME_SIZE) + { + memcpy(f, bytes, size - 1); + f->checksum = bytes[size - 1]; + + return true; + } + return false; + } +}; +#pragma pack() +struct ATCommandFrame : public APIFrame +{ + ATCommandFrame() : APIFrame() + { + frame_type = FTYPE_AT_COMMAND; + setFrameDataLength(MIN_AT_COMMAND_FRAME_SIZE); + } + + uint8_t getFrameID() const { return frame_data[0]; } + + void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; } + + const char* getATCommand() const + { + return reinterpret_cast<const char*>(&frame_data[1]); + } + + void setATCommand(const char* at) + { + frame_data[1] = at[0]; + frame_data[2] = at[1]; + } + + uint8_t* getCommandDataPointer() { return &frame_data[3]; } + + uint16_t getCommandDataLength() const + { + return getFrameDataLength() - MIN_AT_COMMAND_FRAME_SIZE; + } + + void setParameterSize(uint16_t size) + { + assert(size <= FRAME_DATA_SIZE - MIN_AT_COMMAND_FRAME_SIZE); + size = min((size_t)size, FRAME_DATA_SIZE - MIN_AT_COMMAND_FRAME_SIZE); + + setFrameDataLength(MIN_AT_COMMAND_FRAME_SIZE + size); + } +}; +static_assert(sizeof(ATCommandFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct ATCommandResponseFrame : public APIFrame +{ + ATCommandResponseFrame() : APIFrame() + { + frame_type = FTYPE_AT_COMMAND_RESPONSE; + setFrameDataLength(MIN_AT_RESPONSE_FRAME_SIZE); + } + + uint8_t getFrameID() const { return frame_data[0]; } + + void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; } + + const char* getATCommand() const + { + return reinterpret_cast<const char*>(&frame_data[1]); + } + + void setATCommand(const char* at) + { + frame_data[1] = at[0]; + frame_data[2] = at[1]; + } + + uint8_t getCommandStatus() const { return frame_data[3]; } + + void setCommandStatus(uint8_t cs) { frame_data[3] = cs; } + + uint8_t* getCommandDataPointer() { return &frame_data[4]; } + + uint16_t getCommandDataLength() const + { + return getFrameDataLength() - MIN_AT_RESPONSE_FRAME_SIZE; + } + + void setCommandDataSize(uint16_t size) + { + assert(size <= FRAME_DATA_SIZE - MIN_AT_RESPONSE_FRAME_SIZE); + size = min((size_t)size, FRAME_DATA_SIZE - MIN_AT_RESPONSE_FRAME_SIZE); + + setFrameDataLength(MIN_AT_RESPONSE_FRAME_SIZE + size); + } +}; +static_assert(sizeof(ATCommandFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct TXRequestFrame : public APIFrame +{ + TXRequestFrame() : APIFrame() + { + frame_type = FTYPE_TX_REQUEST; + setFrameDataLength(MIN_TX_REQUEST_FRAME_SIZE); + + // Reserved bytes + frame_data[9] = 0xFF; + frame_data[10] = 0xFE; + } + + uint8_t getFrameID() const { return frame_data[0]; } + + void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; } + + uint64_t getDestAddress() const + { + uint64_t addr; + memcpy(&addr, &frame_data[1], sizeof(uint64_t)); + return swapBytes64(addr); + } + + void setDestAddress(uint64_t address) + { + address = swapBytes64(address); + memcpy(&frame_data[1], &address, 8); + } + + uint8_t getBroadcastRadius() const { return frame_data[11]; } + + void setBroadcastRadius(uint8_t br) { frame_data[11] = br; } + + uint8_t getTrasmitOptions() const { return frame_data[12]; } + + void setTransmitOptions(uint8_t br) { frame_data[12] = br; } + + uint8_t* getRFDataPointer() { return &frame_data[13]; } + + uint16_t getRFDataLength() const + { + return min((size_t)(getFrameDataLength() - MIN_TX_REQUEST_FRAME_SIZE), + (size_t)MAX_PACKET_PAYLOAD_LENGTH); + } + + void setRFDataLength(uint16_t size) + { + assert(size <= MAX_PACKET_PAYLOAD_LENGTH); + size = min(size, MAX_PACKET_PAYLOAD_LENGTH); + + setFrameDataLength(MIN_TX_REQUEST_FRAME_SIZE + size); + } +}; +static_assert(sizeof(TXRequestFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct ModemStatusFrame : public APIFrame +{ + ModemStatusFrame() : APIFrame() + { + frame_type = FTYPE_MODEM_STATUS; + setFrameDataLength(MODEM_STATUS_FRAME_SIZE); + } + + uint8_t getStatus() const { return frame_data[0]; } + + void setStatus(uint8_t status) { frame_data[0] = status; } +}; +static_assert(sizeof(ModemStatusFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct TXStatusFrame : public APIFrame +{ + TXStatusFrame() : APIFrame() + { + frame_type = FTYPE_TX_STATUS; + setFrameDataLength(TX_STATUS_FRAME_SIZE); + + // Reserved bytes + frame_data[1] = 0xFF; + frame_data[2] = 0xFE; + } + + uint8_t getFrameID() const { return frame_data[0]; } + + void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; } + + uint8_t getTransmitRetryCount() const { return frame_data[3]; } + + void setTransmitRetryCount(uint8_t trc) { frame_data[3] = trc; } + + uint8_t getDeliveryStatus() const { return frame_data[4]; } + + void setDeliveryStatus(uint8_t ds) { frame_data[4] = ds; } + + uint8_t getDiscoveryStatus() const { return frame_data[5]; } + + void setDiscoveryStatus(uint8_t ds) { frame_data[5] = ds; } +}; + +static_assert(sizeof(TXStatusFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); + +struct RXPacketFrame : public APIFrame +{ + RXPacketFrame() : APIFrame() + { + frame_type = FTYPE_RX_PACKET_FRAME; + setFrameDataLength(MIN_RX_PACKET_FRAME_SIZE); + + frame_data[8] = 0xFF; + frame_data[9] = 0xFE; + } + + uint64_t getSourceAddress() const + { + uint64_t addr; + memcpy(&addr, &frame_data[0], sizeof(uint64_t)); + return swapBytes64(addr); + } + + void setSourceAddress(uint64_t address) + { + address = swapBytes64(address); + uint8_t* addr = reinterpret_cast<uint8_t*>(&address); + + memcpy(&frame_data[0], addr, 8); + } + + uint8_t getReceiveOptions() const { return frame_data[10]; } + + void setReceiveOptions(uint8_t ro) { frame_data[10] = ro; } + + uint8_t* getRXDataPointer() { return &frame_data[11]; } + + uint16_t getRXDataLength() const + { + return min((size_t)(getFrameDataLength() - MIN_RX_PACKET_FRAME_SIZE), + (size_t)MAX_PACKET_PAYLOAD_LENGTH); + } + + void setRXDataLength(uint16_t size) + { + assert(size <= MAX_PACKET_PAYLOAD_LENGTH); + size = min(size, MAX_PACKET_PAYLOAD_LENGTH); + + setFrameDataLength(MIN_RX_PACKET_FRAME_SIZE + size); + } +}; + +static_assert(sizeof(RXPacketFrame) == sizeof(APIFrame), + "Size of derived classes must be the same as APIFrame class (no " + "additional members & no virtual functions)"); +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/APIFramesLog.h b/src/shared/drivers/Xbee/APIFramesLog.h new file mode 100644 index 0000000000000000000000000000000000000000..f7a86ca1ea64b4db813608edb7b9334bd59c1344 --- /dev/null +++ b/src/shared/drivers/Xbee/APIFramesLog.h @@ -0,0 +1,393 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <algorithm> +#include <cstdint> +#include <ostream> +#include <string> +#include <utility> + +#include "APIFrames.h" + +using std::min; + +/** + * Classes used if you want to log an APIFrame, but do not want to log the base + * APIFrame class, which is quite heavy on memory + */ + +namespace Xbee +{ + +struct APIFrameLog +{ + long long timestamp; + uint8_t frame_type; + uint16_t frame_data_length; + uint8_t frame_data[FRAME_DATA_SIZE]; + + static bool fromAPIFrame(APIFrame& api, APIFrameLog* dest) + { + dest->timestamp = api.timestamp; + dest->frame_type = api.frame_type; + dest->frame_data_length = api.getFrameDataLength(); + + memcpy(dest->frame_data, api.frame_data, dest->frame_data_length); + + return true; + } + + static string header() { return "timestamp,length,frame_type\n"; } + + void print(std::ostream& os) const + { + os << timestamp << "," << frame_data_length << "," << frame_type + << "\n"; + } +}; + +struct ATCommandFrameLog +{ + long long timestamp; + uint8_t frame_id = 0; + char at_command[2]; + + uint8_t command_data[MAX_AT_COMMAND_PARAMS_LENGTH]; + uint16_t command_data_length = 0; + + static bool toFrameType(APIFrame& api, ATCommandFrameLog* dest) + { + if (api.frame_type != FTYPE_AT_COMMAND) + { + return false; + } + + if (api.getFrameDataLength() < MIN_AT_COMMAND_FRAME_SIZE || + api.getFrameDataLength() > + MIN_AT_COMMAND_FRAME_SIZE + MAX_AT_COMMAND_PARAMS_LENGTH) + { + return false; + } + ATCommandFrame* at = api.toFrameType<ATCommandFrame>(); + + dest->timestamp = at->timestamp; + dest->frame_id = at->getFrameID(); + + memcpy(dest->at_command, at->getATCommand(), 2); + + dest->command_data_length = at->getCommandDataLength(); + memcpy(dest->command_data, at->getCommandDataPointer(), + dest->command_data_length); + + return true; + } + + static string header() { return "timestamp,id,cmd,param_size,params\n"; } + + void print(std::ostream& os) const + { + char cmd[3]; + + strncpy(cmd, at_command, 2); + cmd[2] = '\0'; + + os << timestamp << "," << (int)frame_id << "," << cmd << "," + << command_data_length << ","; + + if (command_data_length > 0) + { + for (uint16_t i = 0; i < command_data_length; i++) + { + os << (int)command_data[i] << " "; + } + } + else + { + os << "-"; + } + + os << "\n"; + } +}; + +struct TXRequestFrameLog +{ + long long timestamp = 0; + uint8_t frame_id = 0; + + uint64_t dest_address = 0; + + uint8_t broadcast_radius = 0; + uint8_t transmit_options = 0; + + uint8_t rf_data[MAX_PACKET_PAYLOAD_LENGTH]; + uint16_t rf_data_length = 0; + + static bool toFrameType(APIFrame& api, TXRequestFrameLog* dest) + { + if (api.frame_type != FTYPE_TX_REQUEST) + { + return false; + } + + if (api.getFrameDataLength() < MIN_TX_REQUEST_FRAME_SIZE || + api.getFrameDataLength() > + MIN_TX_REQUEST_FRAME_SIZE + MAX_PACKET_PAYLOAD_LENGTH) + { + return false; + } + + TXRequestFrame* tx = api.toFrameType<TXRequestFrame>(); + + dest->timestamp = api.timestamp; + dest->frame_id = tx->getFrameID(); + + dest->dest_address = tx->getDestAddress(); + + dest->broadcast_radius = tx->getBroadcastRadius(); + dest->transmit_options = tx->getTrasmitOptions(); + + dest->rf_data_length = tx->getRFDataLength(); + memcpy(dest->rf_data, tx->getRFDataPointer(), dest->rf_data_length); + + return true; + } + + static string header() + { + return "timestamp,id,dest_addr,broadcast_radius,tx_options,rf_data_" + "len,first_uint32\n"; + } + + void print(std::ostream& os) const + { + uint32_t d; + memcpy(&d, rf_data, sizeof(d)); + os << timestamp << "," << (int)frame_id << "," << dest_address << "," + << (int)broadcast_radius << "," << (int)transmit_options << "," + << rf_data_length << "," << d << "\n"; + } +}; + +struct ATCommandResponseFrameLog +{ + long long timestamp = 0; + uint8_t frame_id = 0; + char at_command[2]; + uint8_t command_status = 0; + + uint8_t command_data[MAX_AT_COMMAND_RESPONSE_LENGTH]; + uint16_t command_data_length = 0; + + static bool toFrameType(APIFrame& api, ATCommandResponseFrameLog* dest) + { + if (api.frame_type != FTYPE_AT_COMMAND_RESPONSE) + { + return false; + } + + if (api.getFrameDataLength() < MIN_AT_COMMAND_FRAME_SIZE || + api.getFrameDataLength() > + MIN_AT_COMMAND_FRAME_SIZE + MAX_AT_COMMAND_RESPONSE_LENGTH) + { + return false; + } + + ATCommandResponseFrame* at = api.toFrameType<ATCommandResponseFrame>(); + + dest->timestamp = api.timestamp; + dest->frame_id = at->getFrameID(); + memcpy(dest->at_command, at->getATCommand(), 2); + + dest->command_status = at->getCommandStatus(); + + dest->command_data_length = at->getCommandDataLength(); + memcpy(dest->command_data, at->getCommandDataPointer(), + dest->command_data_length); + + return true; + } + + static string header() + { + return "timestamp,id,cmd,status,param_size,params\n"; + } + + void print(std::ostream& os) const + { + char cmd[3]; + + strncpy(cmd, at_command, 2); + cmd[2] = '\0'; + + os << timestamp << "," << (int)frame_id << "," << cmd << "," + << (int)command_status << "," << command_data_length << ","; + + if (command_data_length > 0) + { + for (uint16_t i = 0; i < command_data_length; i++) + { + os << (int)command_data[i] << " "; + } + } + else + { + os << "-"; + } + + os << "\n"; + } +}; + +struct ModemStatusFrameLog +{ + long long timestamp = 0; + uint8_t modem_status = 0; + + static bool toFrameType(APIFrame& api, ModemStatusFrameLog* dest) + { + if (api.frame_type != FTYPE_MODEM_STATUS) + { + return false; + } + + if (api.getFrameDataLength() != MODEM_STATUS_FRAME_SIZE) + { + return false; + } + + ModemStatusFrame* modem = api.toFrameType<ModemStatusFrame>(); + + dest->timestamp = api.timestamp; + dest->modem_status = modem->getStatus(); + + return true; + } + + static string header() { return "timestamp,status\n"; } + + void print(std::ostream& os) const + { + os << timestamp << "," << (int)modem_status << "\n"; + } +}; + +struct TXStatusFrameLog +{ + long long timestamp = 0; + uint8_t frame_id = 0; + uint8_t tx_retry_count = 0; + uint8_t delivery_status = 0; + uint8_t discovery_status = 0; + + static bool toFrameType(APIFrame& api, TXStatusFrameLog* dest) + { + if (api.frame_type != FTYPE_TX_STATUS) + { + return false; + } + + if (api.getFrameDataLength() != TX_STATUS_FRAME_SIZE) + { + return false; + } + + TXStatusFrame* tx = api.toFrameType<TXStatusFrame>(); + + dest->timestamp = api.timestamp; + dest->frame_id = tx->getFrameID(); + + dest->tx_retry_count = tx->getTransmitRetryCount(); + dest->delivery_status = tx->getDeliveryStatus(); + dest->discovery_status = tx->getDiscoveryStatus(); + + return true; + } + + static string header() + { + return "timestamp,id,tx_retries,delivery_status,discovery_status\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << (int)frame_id << "," << (int)tx_retry_count + << "," << (int)delivery_status << "," << (int)discovery_status + << "\n"; + } +}; + +struct RXPacketFrameLog +{ + long long timestamp = 0; + + uint64_t source_address = 0; + + uint8_t receive_options = 0; + + uint8_t rx_data[MAX_PACKET_PAYLOAD_LENGTH]; + uint16_t rx_data_length = 0; + + static bool toFrameType(APIFrame& api, RXPacketFrameLog* dest) + { + if (api.frame_type != FTYPE_RX_PACKET_FRAME) + { + return false; + } + + if (api.getFrameDataLength() < MIN_RX_PACKET_FRAME_SIZE || + api.getFrameDataLength() > + MIN_RX_PACKET_FRAME_SIZE + MAX_PACKET_PAYLOAD_LENGTH) + { + return false; + } + + RXPacketFrame* rx = api.toFrameType<RXPacketFrame>(); + + dest->timestamp = api.timestamp; + dest->source_address = rx->getSourceAddress(); + + dest->receive_options = rx->getReceiveOptions(); + + dest->rx_data_length = rx->getRXDataLength(); + memcpy(dest->rx_data, rx->getRXDataPointer(), dest->rx_data_length); + + return true; + } + + static string header() + { + return "timestamp,src_addr,rx_options,rx_data_size,payload32_0,payload32_1\n"; + } + + void print(std::ostream& os) const + { + uint32_t data[2]; + memcpy(&data, rx_data, min(sizeof(uint32_t)*2, (size_t)rx_data_length)); + os << timestamp << "," << source_address << "," << (int)receive_options + << "," << rx_data_length << "," << data[0] << "," << data[1] << "\n"; + } +}; + +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/ATCommands.h b/src/shared/drivers/Xbee/ATCommands.h new file mode 100644 index 0000000000000000000000000000000000000000..6db0c915b7dcef0a022530f1a78f9c8ae4d649e2 --- /dev/null +++ b/src/shared/drivers/Xbee/ATCommands.h @@ -0,0 +1,154 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include "APIFrames.h" +#include "Xbee.h" + +namespace Xbee +{ + +/** + * @brief Enables or disable the specified channels. Channel 9 and 24 are always + * disabled due to regulatory limitations. + * See datasheet for the list of channel frequencies. + * + * @param xbee Reference to an xbee object + * @param channels channel mask + * @return True if the command was executed successfully + */ +bool setChannelMask(Xbee& xbee, uint32_t mask, unsigned int timeout = 1000) +{ + ATCommandResponseFrame response; + + mask = swapBytes32(mask); + + return xbee.sendATCommand("CM", &response, + reinterpret_cast<uint8_t*>(&mask), 4, timeout); +} + +/** + * @brief Enables or disable the specified channels. Channel 9 and 24 are always + * disabled due to regulatory limitations. + * See datasheet for the list of channel frequencies. + * + * @param xbee Reference to an xbee object + * @param channels channel mask array + * + * @return True if the command was executed successfully + */ +bool setChannelMask(Xbee& xbee, bool channels[30], unsigned int timeout = 1000) +{ + uint32_t val = 0; + + for (int i = 0; i < 30; i++) + { + val &= ((uint32_t)channels[i]) << i; + } + + return setChannelMask(xbee, val, timeout); +} + +/** + * @brief Enable communication on all available channels. Equivalent to calling + * setChannelMask(xbee, 0x3EFFFDFF). + * + * @param xbee Reference to an xbee object + * @return True if the command was executed successfully + */ +bool enableAllChannels(Xbee& xbee, unsigned int timeout = 1000) +{ + return setChannelMask(xbee, 0x3EFFFDFF, timeout); +} + +/** + * @brief Disables frequency hopping by allowing communication only on channel + * #29. Equivalent to calling setChannelMask(xbee, 0x20000000). + * + * @param xbee Reference to an xbee object + */ +bool disableFrequencyHopping(Xbee& xbee, unsigned int timeout = 1000) +{ + return setChannelMask(xbee, 0x20000000, timeout); +} + +/** + * @brief Configures the desired xbee data rate. + * + * @param xbee Reference to an xbee object + * @param data_rate_80kbps true for 80kbps, false for 10kbps + * @return True if the command was executed successfully + */ +bool setDataRate(Xbee& xbee, bool data_rate_80kbps, unsigned int timeout = 1000) +{ + uint8_t param = (uint8_t)data_rate_80kbps; + ATCommandResponseFrame response; + + return xbee.sendATCommand("BR", &response, ¶m, 1, timeout); +} + +/** + * @brief Writes parameter values to non-volatile memory so that parameter + * modifications persist through subsequent resets. + * + * @param xbee Reference to an xbee object + * @return True if the command was executed successfully + */ +bool writeToMemory(Xbee& xbee, unsigned int timeout = 1000) +{ + ATCommandResponseFrame response; + return xbee.sendATCommand("WR", &response, nullptr, 0, timeout); +} + +/** + * @brief Performs an energy detect scan on all channels + * + * @param xbee Reference to an xbee object + * @param energy_detect_data Pointer to a 30-bytes buffer where energy levels + * will be stored. Energy levels are in -dBm units. + * @param duration Scan duration in ms + * @return True if the command was executed successfully and data stored in + * energy_detect_data is valid + */ +bool energyDetect(Xbee& xbee, int* energy_detect_data, uint8_t duration, + unsigned int timeout = 1000) +{ + ATCommandResponseFrame response; + if (xbee.sendATCommand("ED", &response, &duration, 1, timeout)) + { + uint16_t resp_len = response.getCommandDataLength(); + if (resp_len == 30) + { + for (int i = 0; i < 30; i++) + { + energy_detect_data[i] = + (int)(*(response.getCommandDataPointer() + i)); + } + + return true; + } + } + return false; +} + +} // namespace Xbee diff --git a/src/shared/drivers/Xbee/Xbee.cpp b/src/shared/drivers/Xbee/Xbee.cpp index 4cc53fb6b2ba165fd28aa69da0a2877b28f2f986..a361e3894bc63cdbd5e2ac9e40fce44f0e42947a 100644 --- a/src/shared/drivers/Xbee/Xbee.cpp +++ b/src/shared/drivers/Xbee/Xbee.cpp @@ -1,20 +1,19 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta - * +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * * 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 + * 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 @@ -23,7 +22,512 @@ #include "Xbee.h" +#include <Debug.h> +#include <kernel/scheduler/scheduler.h> +#include <miosix.h> +#include <diagnostic/StackLogger.h> +#include <algorithm> + +using miosix::FastMutex; +using miosix::Lock; +using miosix::Unlock; +using std::min; + namespace Xbee { -miosix::Thread* waiting = nullptr; -} \ No newline at end of file + +Xbee::Xbee(SPIBusInterface& bus, GpioType cs, GpioType attn, GpioType rst, + long long tx_timeout) + : Xbee(bus, {}, cs, attn, rst, tx_timeout) +{ + spi_xbee.config.clock_div = SPIClockDivider::DIV128; +} + +Xbee::Xbee(SPIBusInterface& bus, SPIBusConfig config, GpioType cs, + GpioType attn, GpioType rst, long long tx_timeout) + : spi_xbee(bus, cs, config), attn(attn), rst(rst), tx_timeout(tx_timeout) +{ + reset(); +} + +Xbee::~Xbee() { wakeReceiver(true); } + +bool Xbee::send(uint8_t* pkt, size_t pkt_len) +{ + if (pkt_len > MAX_PACKET_PAYLOAD_LENGTH || pkt_len == 0) + { + LOG_ERR(logger, "Invalid packet length (0< {} <= {})", pkt_len, + MAX_PACKET_PAYLOAD_LENGTH); + return false; + } + long long start_tick = miosix::getTick(); + + TXRequestFrame tx_req; + uint8_t tx_frame_id = buildTXRequestFrame(tx_req, pkt, pkt_len); + bool success = false; + bool status_timeout = true; + { + Lock<FastMutex> l(mutex_xbee_comm); + writeFrame(tx_req); + + // Wait for a TX Status frame + long long timeout_tick = miosix::getTick() + tx_timeout; + + while (waitForFrame(FTYPE_TX_STATUS, FRAME_POLL_INTERVAL, timeout_tick)) + { + TXStatusFrame* f = parsing_api_frame.toFrameType<TXStatusFrame>(); + + if (f->getFrameID() == tx_frame_id) + { + success = f->getDeliveryStatus() == DELS_SUCCESS; + status_timeout = false; + break; + } + else + { + LOG_ERR(logger, "Wrong tx_status ID"); + } + } + + // Ak for total transmitted byte count for logging purposes + // sendATCommandInternal("BC"); + } + + // If there is more data to receive, wake the receive() thread + if (attn.value() == 0) + { + wakeReceiver(); + } + if (status_timeout) + { + ++status.tx_timeout_count; + LOG_ERR(logger, "TX_STATUS timeout"); + } + time_to_send_stats.add(miosix::getTick() - start_tick); + + StackLogger::getInstance()->updateStack(THID_XBEE); + + return success; +} + +ssize_t Xbee::receive(uint8_t* buf, size_t buf_max_size) +{ + while (true) + { + // We have data in the buffer pending to be returned + if (!rx_frames_buf.isEmpty() || curr_rx_payload_ptr >= 0) + { + return fillReceiveBuf(buf, buf_max_size); + } + // No data in the buffer, but the xbee has data to return via SPI + else if (attn.value() == 0) + { + Lock<FastMutex> l(mutex_xbee_comm); + if (readRXFrame()) + { + sendATCommandInternal("DB"); // Query last packet RSSI + sendATCommandInternal("ER"); // Query receive error count + } + } + // No data available at all: sleep until we have something to do + else + { + { + miosix::FastInterruptDisableLock dLock; + receive_thread = miosix::Thread::getCurrentThread(); + + while (receive_thread != 0) // Avoid spurious wakeups + { + receive_thread->IRQwait(); + { + miosix::FastInterruptEnableLock eLock(dLock); + miosix::Thread::yield(); + } + } + } + + // Forcefully return without receiving anything + if (force_rcv_return) + { + force_rcv_return = false; + return -1; + } + } + } +} + +XbeeStatus Xbee::getStatus() +{ + status.timestamp = miosix::getTick(); + status.time_to_send_stats = time_to_send_stats.getStats(); + return status; +} + +void Xbee::reset() +{ + Lock<FastMutex> l(mutex_xbee_comm); + { + miosix::FastInterruptDisableLock dLock(); + rst.mode(miosix::Mode::OPEN_DRAIN); + } + rst.low(); + miosix::delayUs(50); + rst.high(); + + // When the xbee is ready, we should assert SSEL to tell it to use + // SPI, and it should provide us with a modem status frame + long long timeout = miosix::getTick() + 1000; + do + { + // Assert SSEL on every iteration as we don't exactly know when the + // xbee will be ready. + { + SPIAcquireLock acq(spi_xbee); + spi_xbee.cs.low(); + miosix::delayUs(10); + spi_xbee.cs.high(); + } + + miosix::delayUs(50); + + if (attn.value() == 0 && readOneFrame() == ParseResult::SUCCESS) + { + handleFrame(parsing_api_frame); + + if (parsing_api_frame.frame_type == FTYPE_MODEM_STATUS) + { + break; + } + } + miosix::Thread::sleep(5); + } while (miosix::getTick() < timeout); +} + +void Xbee::wakeReceiver(bool force_return) +{ + force_rcv_return = force_return; + miosix::FastInterruptDisableLock dLock; + + if (receive_thread) + { + receive_thread->IRQwakeup(); + receive_thread = 0; + } +} + +void Xbee::handleATTNInterrupt() +{ + if (receive_thread) + { + receive_thread->IRQwakeup(); + if (receive_thread->IRQgetPriority() > + miosix::Thread::IRQgetCurrentThread()->IRQgetPriority()) + { + miosix::Scheduler::IRQfindNextThread(); + } + receive_thread = 0; + } +} + +size_t Xbee::fillReceiveBuf(uint8_t* buf, size_t buf_max_size) +{ + if (!rx_frames_buf.isEmpty() && curr_rx_payload_ptr == -1) + { + Lock<FastMutex> l(mutex_rx_frames); + curr_rx_frame = rx_frames_buf.pop(); + curr_rx_payload_ptr = 0; + } + + if (curr_rx_payload_ptr >= 0) + { + size_t len_remaining_data = + curr_rx_frame.getRXDataLength() - curr_rx_payload_ptr; + + // The buffer may be smaller than the data we need to return + size_t len_to_copy = min(buf_max_size, len_remaining_data); + + memcpy(buf, curr_rx_frame.getRXDataPointer() + curr_rx_payload_ptr, + len_to_copy); + + curr_rx_payload_ptr += len_to_copy; + + if (curr_rx_payload_ptr == curr_rx_frame.getRXDataLength()) + { + // We've emptied the current frame + curr_rx_payload_ptr = -1; + } + + return len_to_copy; + } + + return 0; +} + +ParseResult Xbee::readOneFrame() +{ + SPIAcquireLock acq(spi_xbee); + SPISelectLock sel(spi_xbee); + + ParseResult result = ParseResult::IDLE; + do + { + result = parser.parse(spi_xbee.bus.read(), &parsing_api_frame); + } while (attn.value() == 0 && result == ParseResult::PARSING); + + return result; +} + +bool Xbee::readRXFrame() +{ + while (attn.value() == 0) + { + if (readOneFrame() == ParseResult::SUCCESS) + { + handleFrame(parsing_api_frame); + + if (parsing_api_frame.frame_type == FTYPE_RX_PACKET_FRAME) + { + return true; + } + } + } + return false; +} + +void Xbee::writeFrame(APIFrame& frame) +{ + frame.timestamp = miosix::getTick(); // Only for logging purposes + + // Serialize the frame + uint8_t tx_buf[MAX_API_FRAME_SIZE]; + size_t tx_buf_size = frame.toBytes(tx_buf); + + { + SPITransaction spi(spi_xbee); + spi.transfer(tx_buf, tx_buf_size); + } + + // Pass the frame we just sent to the listener + if (frame_listener) + { + frame_listener(frame); + } + + // Full duplex spi transfer: we may have received valid data while we + // were sending the frame. + for (unsigned int i = 0; i < tx_buf_size; i++) + { + ParseResult res = parser.parse(tx_buf[i], &parsing_api_frame); + if (res == ParseResult::SUCCESS) + { + handleFrame(parsing_api_frame); + } + } +} + +bool Xbee::waitForFrame(uint8_t frame_type, unsigned int poll_interval, + long long timeout_tick) +{ + do + { + if (attn.value() == 0) + { + if (readOneFrame() == ParseResult::SUCCESS) + { + handleFrame(parsing_api_frame); + + if (parsing_api_frame.frame_type == frame_type) + { + return true; + } + } + } + else + { + miosix::Thread::sleep(poll_interval); + } + } while (miosix::getTick() < timeout_tick); + + return false; +} + +uint8_t Xbee::buildTXRequestFrame(TXRequestFrame& tx_req, uint8_t* pkt, + size_t pkt_len) +{ + memcpy(tx_req.getRFDataPointer(), pkt, pkt_len); + tx_req.setRFDataLength(pkt_len); + + uint8_t tx_frame_id = getNewFrameID(); + + tx_req.setFrameID(tx_frame_id); + + tx_req.setDestAddress(ADDRESS_BROADCAST); + tx_req.setBroadcastRadius(0); // 0 = max hops, but we don't really care as + // it does not apply to point-multipoint mode + // Point-multipoint mode, disable ack, disable route discovery + tx_req.setTransmitOptions(TO_DM_POINT_MULTIPOINT | TO_DISABLE_ACK | + TO_DISABLE_RD); + tx_req.calcChecksum(); + + return tx_frame_id; +} + +void Xbee::sendATCommand(const char* cmd, uint8_t* params, size_t params_len) +{ + Lock<FastMutex> l(mutex_xbee_comm); + + sendATCommandInternal(0, cmd, params, params_len); +} + +bool Xbee::sendATCommand(const char* cmd, ATCommandResponseFrame* response, + uint8_t* params, size_t params_len, + unsigned int timeout) +{ + Lock<FastMutex> l(mutex_xbee_comm); + + uint8_t tx_frame_id = sendATCommandInternal(cmd, params, params_len); + + bool success = false; + + long long timeout_tick = miosix::getTick() + timeout; + + while (waitForFrame(FTYPE_AT_COMMAND_RESPONSE, FRAME_POLL_INTERVAL, + timeout_tick)) + { + ATCommandResponseFrame* f = + parsing_api_frame.toFrameType<ATCommandResponseFrame>(); + + if (f->getFrameID() == tx_frame_id && + strncmp(cmd, f->getATCommand(), 2) == 0) + { + memcpy(response, f, sizeof(ATCommandResponseFrame)); + success = true; + break; + } + } + + return success; +} + +uint8_t Xbee::sendATCommandInternal(const char* cmd, uint8_t* params, + size_t params_len) +{ + return sendATCommandInternal(getNewFrameID(), cmd, params, params_len); +} + +uint8_t Xbee::sendATCommandInternal(uint8_t tx_frame_id, const char* cmd, + uint8_t* params, size_t params_len) +{ + // Build the AT command + ATCommandFrame at; + at.setATCommand(cmd); + at.setFrameID(tx_frame_id); + if (params_len > 0) + { + if (params_len > MAX_AT_COMMAND_PARAMS_LENGTH) + { + LOG_ERR(logger, "AT Command payload too large, it was truncated"); + params_len = MAX_AT_COMMAND_PARAMS_LENGTH; + } + at.setParameterSize(params_len); + memcpy(at.getCommandDataPointer(), params, params_len); + } + + at.calcChecksum(); + + // Send it + writeFrame(at); + + return tx_frame_id; +} + +void Xbee::handleFrame(APIFrame& frame) +{ + // Set the timestamp to the frame + frame.timestamp = miosix::getTick(); + + switch (frame.frame_type) + { + case FTYPE_RX_PACKET_FRAME: + { + RXPacketFrame* pf = frame.toFrameType<RXPacketFrame>(); + { + Lock<FastMutex> l(mutex_rx_frames); + + if (rx_frames_buf.isFull()) + { + ++status.rx_dropped_buffers; + } + rx_frames_buf.put(*pf); + if (rx_frames_buf.count() > status.frame_buf_max_length) + { + status.frame_buf_max_length = rx_frames_buf.count(); + } + } + wakeReceiver(); + break; + } + case FTYPE_MODEM_STATUS: + { + ModemStatusFrame* ms = frame.toFrameType<ModemStatusFrame>(); + + switch (ms->getStatus()) + { + case MS_HARDWARE_RESET: + LOG_DEBUG(logger, "Modem status: Hardware reset"); + break; + case MS_WATCHDOG_TIMER_RESET: + LOG_DEBUG(logger, "Modem status: Watchdog timer reset"); + break; + } + break; + } + case FTYPE_TX_STATUS: + { + TXStatusFrame* ts = frame.toFrameType<TXStatusFrame>(); + status.last_tx_status = ts->getDeliveryStatus(); + if (status.last_tx_status != DELS_SUCCESS) + { + status.last_tx_status_error = status.last_tx_status; + } + switch (status.last_tx_status) + { + case DELS_SUCCESS: + break; + default: + LOG_ERR( + logger, "TX Status Error: {} (retries: {}, RD: {})", + status.last_tx_status, ts->getTransmitRetryCount(), + ts->getDiscoveryStatus() == 2 ? "Enabled" : "Disabled"); + break; + } + break; + } + default: + break; + } + + // Pass the frame to the listener + if (frame_listener) + { + frame_listener(frame); + } +} + +void Xbee::setOnFrameReceivedListener(OnFrameReceivedListener listener) +{ + frame_listener = listener; +} + +uint8_t Xbee::getNewFrameID() +{ + uint8_t tx_frame_id = frame_id_counter++; + + // Any value != 0 implies that we DO want a tx status response + if (frame_id_counter == 0) + frame_id_counter = 1; + + return tx_frame_id; +} + +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/Xbee.h b/src/shared/drivers/Xbee/Xbee.h index 163089b0b7745512d289c034ce17ecca519c0f5e..b84f062eec5f683971e028cc1f65046c7ce117c9 100644 --- a/src/shared/drivers/Xbee/Xbee.h +++ b/src/shared/drivers/Xbee/Xbee.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2015-2019 Skyward Experimental Rocketry - * Authors: Andrea Milluzzo, Artem Glukhov, Alvise de Faveri Tron, Luca Erbetta +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -22,594 +22,262 @@ #pragma once -#include <Common.h> -#include <drivers/BusTemplate.h> -#include <drivers/Transceiver.h> -#include <algorithm> -#include <vector> - +#include <diagnostic/PrintLogger.h> #include <miosix.h> -#include "ActiveObject.h" -#include "XbeeStatus.h" -#include "diagnostic/StackLogger.h" -using miosix::FastInterruptDisableLock; -using miosix::FastInterruptEnableLock; -using miosix::Thread; +#include <functional> + +#include "XbeeStatus.h" +#include "drivers/Transceiver.h" +#include "drivers/Xbee/APIFrameParser.h" +#include "drivers/spi/SPIDriver.h" +#include "utils/collections/CircularBuffer.h" -using miosix::ConditionVariable; using miosix::FastMutex; -using miosix::Lock; -using miosix::Unlock; -using std::vector; +#ifndef USE_MOCK_PERIPHERALS +using GpioType = GpioPin; +#else +#include "utils/testutils/MockGpioPin.h" +using GpioType = MockGpioPin; +#endif namespace Xbee { -// Constants - -// How often should we check if we received a transmit status response. -static constexpr unsigned int SEND_STATUS_POLL_INTERVAL = 10; // ms - -static constexpr uint16_t MAX_PAYLOAD_LEN = 0xFFFF; - -static constexpr uint8_t START_DELIMITER = 0x7E; -static constexpr uint64_t BROADCAST_ADDR = 0xFFFF; -static constexpr uint8_t ID_ACK_DISABLE = 0x00; -static constexpr uint8_t MAX_BROADCAST_HOPS = 0x00; -static constexpr uint8_t TX_STATUS_DELIVERY_SUCCESS = 0x00; - -// NOTE: 0x43 = Point-Multipoint, No Ack, No route discovery. See transmit -// request frame bit options -static constexpr uint8_t TRANSMIT_OPTIONS = 0x43; - -// Frame Sizes -static constexpr uint8_t API_HEADER_SIZE = 3; - -static constexpr uint8_t TX_FRAME_HEADER_SIZE = 14; -static constexpr uint8_t RX_FRAME_HEADER_SIZE = 12; -static constexpr uint8_t CHECKSUM_SIZE = 1; -static constexpr uint8_t TX_STATUS_FRAME_SIZE = 7; -static constexpr uint8_t MODEM_STATUS_FRAME_SIZE = 2; - -// Bit definitions for status frame -static constexpr uint8_t BIT_STATUS_RETRY_COUNT = 4; -static constexpr uint8_t BIT_STATUS_DELIVERY = 5; -static constexpr uint8_t BIT_STATUS_DISCOVERY = 6; - -// Waiting thread to be woken when something has been received or must be sent. -// Defined extern so it can be accessed from multiple translation units -extern miosix::Thread* waiting; - -/** - * @brief Handler for the ATTN interrupt: wakes up the thread. - * The ATTN (ATTention Needed) pin is raised when the transceiver has - * received something. - * Call this function from inside an IRQHandler, after you have - * enabled the corresponding interrupt in the NVIC vector. - */ -static void __attribute__((used)) handleATTNInterrupt() -{ - if (waiting) - { - // Wake - waiting->IRQwakeup(); - - if (waiting->IRQgetPriority() > - miosix::Thread::IRQgetCurrentThread()->IRQgetPriority()) - { - miosix::Scheduler::IRQfindNextThread(); - } - - waiting = nullptr; - } -} - -/** - * WARNING: An IRQ linked with the ATTN pin of the Xbee module must be enabled - * before using this class. See test/misc/xbee-bitrate for an example. - */ -template <typename Bus, class CS, class ATTN, class RST> -class Xbee : public Transceiver, public ActiveObject +using ParseResult = APIFrameParser::ParseResult; + +static constexpr unsigned int FRAME_POLL_INTERVAL = 10; // ms +static constexpr unsigned int DEFAULT_TX_TIMEOUT = 5000; // ms + +// Size (in frames) of the receive circular buffer +static constexpr unsigned int RX_FRAMES_BUF_SIZE = 3; + +class Xbee : public Transceiver { public: - Xbee(unsigned int send_timeout) : send_timeout(send_timeout) - { - reset(); - miosix::Thread::sleep(10); - - CS::low(); - miosix::Thread::sleep(1); - CS::high(); - miosix::Thread::sleep(1); - } - - Xbee() : Xbee(1000) {} - /* - * Send a message through the XBee - * Blocks until the message is sent (successfully or not) + using OnFrameReceivedListener = std::function<void(APIFrame& frame)>; + + /** + * @brief Constructs a new instance of the Xbee driver. * - * @param msg Message to be sent - * @param pkt_len Lenght of the message - * @return true The message was sent correctly. - * @return false There was an error sending the message. + * @param bus The SPI bus where the xbee is connected + * @param cs Xbee SPI Chip Select Pin + * @param attn Xbee ATTN Pin + * @param rst Xbee RST PIN + * @param tx_timeout How long to wait to for a TX Status */ - bool send(uint8_t* msg, size_t msg_len) override - { - if (msg == nullptr || msg_len == 0) - { - return false; - } - - // Create a TX API packet - vector<uint8_t> tx_pkt; - buildTxPacket(tx_pkt, msg, msg_len); - - // Send the packet - setTxBuf(tx_pkt.data(), tx_pkt.size()); - - // Wake the runner thread in order to send the data - wakeThread(); - - // Wait until the XBee sends the message or a timeout expires - // It would be cool to have condition variables with timeout to avoid - // polling... - unsigned int timeout = 0; - while (!received_tx_status) - { - if (timeout > send_timeout) - { - // Timeout. Return error - TRACE("[Xbee] Send Timeout!\n"); - ++status.tx_timeout_count; - return false; - } - Thread::sleep(SEND_STATUS_POLL_INTERVAL); - timeout += SEND_STATUS_POLL_INTERVAL; - } - - received_tx_status = false; - if (status.tx_delivery_status != TX_STATUS_DELIVERY_SUCCESS) - { - TRACE("[Xbee] Error: %02X\n", status.tx_delivery_status); - } - return status.tx_delivery_status == TX_STATUS_DELIVERY_SUCCESS; - } - - /* - * Wait for a new message to be received by the XBee - * @param rcv_buf Buffer to store the received message - * @param rcv_buf_len Maximum length of the message to be received - * @return true A message was successfully received - * @return false Received invalid message + Xbee(SPIBusInterface& bus, GpioType cs, GpioType attn, GpioType rst, + long long tx_timeout = DEFAULT_TX_TIMEOUT); + + /** + * @brief Constructs a new instance of the Xbee driver. + * + * @param bus The SPI bus where the xbee is connected + * @param config Custom SPI bus configuration + * @param cs Xbee SPI Chip Select Pin + * @param attn Xbee ATTN Pin + * @param rst Xbee RST PIN + * @param tx_timeout How long to wait to for a TX Status + * + */ + Xbee(SPIBusInterface& bus, SPIBusConfig config, GpioType cs, GpioType attn, + GpioType rst, long long tx_timeout = DEFAULT_TX_TIMEOUT); + + ~Xbee(); + + /** + * @brief Sends a packet. + * The function blocks until the packet is sent to the peripheral, but does + * not wait for an ACK or send confirmation from the Xbee. Thus, it always + * returns true. + * + * @param pkt Pointer to the packet (needs to be at least pkt_len + * bytes). + * @param pkt_len Lenght of the packet to be sent. + * @return Always true */ - ssize_t receive(uint8_t* rcv_buf, size_t rcv_buf_len) override - { - Lock<FastMutex> l(rx_mutex); - - while (rx_frame.size() == 0) - { - rx_cond.wait(l); - } - - if (rx_frame.size() > rcv_buf_len) - { - return -1; - } - else - { - memcpy(rcv_buf, rx_frame.data(), rx_frame.size()); - rcv_buf_len = rx_frame.size(); - - rx_frame.clear(); - - return rcv_buf_len; - } - } - - void stop() override - { - should_stop = true; - wakeThread(); - ActiveObject::stop(); - } - - XbeeStatus getStatus() { return status; } - -protected: + bool send(uint8_t* pkt, size_t pkt_len) override; + /** - * Sends and receives data, puts the thread to sleep if nothing to - * send/receive. + * @brief Waits until a new packet is received. + * + * @param buf Buffer to store the received packet into. + * @param buf_max_size Maximum length of the received data. + * @return Size of the data received or -1 if failure */ - void run() override - { - while (!shouldStop()) - { - // Wait for RX or TX request - { - miosix::FastInterruptDisableLock dLock; - - // Check if we have data to send (tx_buf.size > 0) or receive - // (attn == 0) with disabled interrupts to avoid race conditions - if (ATTN::value() != 0 && tx_buf.size() == 0) - { - // If we have nothing to receive or send, wait. - waiting = miosix::Thread::getCurrentThread(); - - while (waiting != 0) - { - waiting->IRQwait(); - { - miosix::FastInterruptEnableLock eLock(dLock); - miosix::Thread::yield(); - } - } - } - - StackLogger::getInstance()->updateStack(THID_XBEE); - } - // Transfer any data on the tx buffer and receive any incoming data - transferData(); - } - } + ssize_t receive(uint8_t* buf, size_t buf_max_size) override; -private: - enum class ParseResult : uint8_t - { - IDLE, - PARSING, - SUCCESS, - FAIL - }; - - enum class ParserState - { - FIND_START, - READ_LENGTH_1, - READ_LENGTH_2, - READ_FRAME, - READ_CHECKSUM - }; - - enum FrameType : uint8_t - { - FRAMETYPE_AT_COMMAND = 0x08, - FRAMETYPE_AT_QUEUE_PARAM_VALUE = 0x09, - FRAMETYPE_TX_REQUEST = 0x10, - FRAMETYPE_EXPICIT_TX_REQUEST = 0x11, - FRAMETYPE_REMOTE_AT_REQUEST = 0x17, - FRAMETYPE_AT_COMMAND_RESPONSE = 0x88, - FRAMETYPE_MODEM_STATUS = 0x8A, - FRAMETYPE_TRANSMIT_STATUS = 0x8B, - FRAMETYPE_ROUTE_INFO_PACKET = 0x8D, - FRAMETYPE_AGGREGATE_ADRESSING_UPDATE = 0x8E, - FRAMETYPE_RX_PACKET = 0x90, - FRAMETYPE_EXPLICIT_RX_PACKET = 0x91, - FRAMETYPE_DATA_SAMPLE_RX_INDICATOR = 0x92, - FRAMETYPE_NODE_IDENTIFICATION_INDICATOR = 0x95, - FRAMETYPE_REMOTE_COMMAND_RESPONSE = 0x97 - }; - - void reset() - { - RST::mode(miosix::Mode::OPEN_DRAIN); - RST::low(); - miosix::delayUs(500); - RST::high(); - } + /** + * @brief Hardware resets the Xbee. + */ + void reset(); /** - * Wake the AO thread from another thread. - * Cannot be called with disabled interrupts. + * @brief Signals the receive() function that there is new data available. + * Call this from the ATTN pin interrupt servirce routine, and nowhere else + * plese. + * */ - void wakeThread() - { - FastInterruptDisableLock dLock; + void handleATTNInterrupt(); - if (waiting) - { - waiting->IRQwakeup(); - waiting = nullptr; - } - } + /** + * @brief Wakes the receive function without needing an interrupt + * + * @param force_return Wether receive(..) should return even if it has not + * received any data + */ + void wakeReceiver(bool force_return = false); /** - * Transfer data to/from the Xbee. - * Performs a full duplex transaction. + * @brief Sends an AT Command to the Xbee (see datasheet) without waiting + * for a response + * + * @param cmd Two character string with the name of the command (eg "DB") + * @param params Optional command parameters + * @param params_len Length in bytes of the params array */ - void transferData() - { - ParseResult result = ParseResult::IDLE; - - CS::low(); - vector<uint8_t> data; - - { - // Make a local copy of the TX buffer - FastInterruptDisableLock dLock; - data = tx_buf; - - // Clear the tx buffer - tx_buf.clear(); - } - - // If there is data to send - if (data.size() > 0) - { - // Full duplex transfer, the data vector is replaced with received - // data, if any. - Bus::transfer(data.data(), data.size()); - - // Parse the received data - for (uint8_t rx : data) - { - result = parse(rx); - - // We received something - if (result == ParseResult::SUCCESS) - { - handleFrame(parser_buf); - } - } - - // If there's nothing more to parse, return - if (result != ParseResult::PARSING) - { - CS::high(); - return; - } - - // If there is more data to be received, continue below. - } - - // Read until we have received a packet (or no packet is found) - do - { - result = parse(Bus::read()); - } while (result == ParseResult::PARSING); - - if (result == ParseResult::SUCCESS) - { - handleFrame(parser_buf); - } - else if (result == ParseResult::FAIL) - { - TRACE("[Xbee] Read failed. Parser result: %d\n", (int)result); - } - - CS::high(); - } + void sendATCommand(const char* cmd, uint8_t* params = nullptr, + size_t params_len = 0); /** - * Parse received data one byte at a time, storing it in the parser_buf. - * When a full packet is received, returns ParseResult::SUCCESS. - * Returns ParseResult::FAIL if a full packet is received but checksum - * verification fails. - * In both cases, the frame is stored in parser_buf. + * @brief Sends an AT Command to the Xbee and wait for a response (see + * datasheet) * - * Returns ParseResult::IDLE if no frame start delimiter has been found yet. - * Returns ParseResult::PARSING if a start delimiter was found and a packet - * is being parsed. + * @param cmd Two character string with the name of the command (eg "DB") + * @param params Optional command parameters + * @param params_len Length in bytes of the params array + * @param response Where to store the response + * @param timeout Maximum time to wait for the response + * @return true if response received before the timeout, false otherwise + */ + bool sendATCommand(const char* cmd, ATCommandResponseFrame* response, + uint8_t* params = nullptr, size_t params_len = 0, + unsigned int timeout = 1000); + + /** + * @brief Set the frame received listener, called each time a new APIFrame + * is received from the device * - * @param byte byte to be parsed - * @return + * @param listener The listener */ - ParseResult parse(uint8_t byte) - { - switch (parser_state) - { - // Look for the start of frame delimiter - case ParserState::FIND_START: - if (byte == START_DELIMITER) - { - parser_state = ParserState::READ_LENGTH_1; - - // Frame start found, clear old rx buf - parser_buf.clear(); - parser_packet_length = 0; - } - else - { - return ParseResult::IDLE; - } - break; - // Read most significant byte of the length - case ParserState::READ_LENGTH_1: - parser_packet_length = byte << 8; - parser_state = ParserState::READ_LENGTH_2; - break; - // Read least significant byte of the length - case ParserState::READ_LENGTH_2: - parser_packet_length += byte; - - parser_state = ParserState::READ_FRAME; - - // Now that we know how long the packet is, reserve memory to - // store it - parser_buf.reserve(parser_packet_length); - break; - // Read the data frame - case ParserState::READ_FRAME: - parser_buf.push_back(byte); - - if (parser_buf.size() == parser_packet_length) - { - parser_state = ParserState::READ_CHECKSUM; - } - break; - // Read & verify checksum - case ParserState::READ_CHECKSUM: - parser_state = ParserState::FIND_START; - - if (verifyChecksum(parser_buf, byte)) - { - return ParseResult::SUCCESS; - } - else - { - ++status.rx_wrong_checksum; - TRACE("[Xbee] Rx checksum verification failed\n"); - return ParseResult::FAIL; - } - break; - } - - return ParseResult::PARSING; - } - - void handleFrame(const vector<uint8_t>& frame) - { - if (frame.size() == 0) - { - return; - } - // The first byte indicates the frame type - switch (frame[0]) - { - case FRAMETYPE_RX_PACKET: - { - size_t payload_size = frame.size() - RX_FRAME_HEADER_SIZE; - - if (payload_size > 0) - { - { - Lock<FastMutex> l(rx_mutex); - rx_frame.clear(); - rx_frame.insert(rx_frame.end(), - frame.begin() + RX_FRAME_HEADER_SIZE, - frame.end()); - } - rx_cond.signal(); - } - break; - } - case FRAMETYPE_TRANSMIT_STATUS: - { - if (frame.size() == TX_STATUS_FRAME_SIZE) - { - status.tx_retry_count = frame[BIT_STATUS_RETRY_COUNT]; - status.tx_delivery_status = frame[BIT_STATUS_DELIVERY]; - status.tx_discovery_status = frame[BIT_STATUS_DISCOVERY]; - - received_tx_status = true; - } - else - { - TRACE("[Xbee] Wrong TX status frame size.\n"); - } - break; - } - case FRAMETYPE_MODEM_STATUS: - { - if (frame.size() == MODEM_STATUS_FRAME_SIZE) - { - TRACE("[Xbee] Modem status: %d\n", frame[1]); - } - else - { - TRACE("[Xbee] Wrong MODEM status frame size.\n"); - } - break; - } - default: - break; - } - } - - void buildTxPacket(vector<uint8_t>& tx_pkt, uint8_t* msg, size_t msg_len) - { - tx_pkt.reserve(API_HEADER_SIZE + TX_FRAME_HEADER_SIZE + msg_len + - CHECKSUM_SIZE); - - tx_pkt.push_back(START_DELIMITER); - - uint16_t frame_len = msg_len + TX_FRAME_HEADER_SIZE; - // invert pkt_len bytes order - tx_pkt.push_back((frame_len & 0xff00) >> 8); - tx_pkt.push_back(frame_len & 0xff); - - tx_pkt.push_back(FRAMETYPE_TX_REQUEST); - - // Any value != 0 indicates that we want a tx status response - tx_pkt.push_back(0x01); - - // Split the address in bytes & add to the buffer - uint8_t addr_buffer[8]; - memcpy(addr_buffer, &BROADCAST_ADDR, 8); - - for (int i = 7; i >= 0; i--) - { - tx_pkt.push_back(addr_buffer[i]); - } - - // Reserved bytes - tx_pkt.push_back(0xff); - tx_pkt.push_back(0xfe); - - tx_pkt.push_back(MAX_BROADCAST_HOPS); - - tx_pkt.push_back(TRANSMIT_OPTIONS); - - // payload - for (uint32_t i = 0; i < msg_len; i++) - { - tx_pkt.push_back(*(msg + i)); - } - - // Calculate & add checksum - uint32_t checksum = 0; - for (uint32_t i = 3; i < tx_pkt.size(); i++) - { - checksum += tx_pkt.at(i); - } - - tx_pkt.push_back(0xff - (checksum & 0xff)); - } + void setOnFrameReceivedListener(OnFrameReceivedListener listener); + XbeeStatus getStatus(); + +private: /** - * Set the tx buffer with the provided data. + * @brief Handles an API Frame + * @attention mutex_xbee_comm must be locked before calling this function. */ - void setTxBuf(uint8_t* buf, size_t size) - { - // We need to disable interrupts (instead of using a mutex) becouse we - // need to synchronize the access to the tx buf in the waiting thread - // before putting it to sleep (which requires disabling interrupts) - FastInterruptDisableLock dLock; - - // Copy buf into tx_buf, removing old content - tx_buf.clear(); - tx_buf.insert(tx_buf.end(), buf, buf + size); - } - - bool verifyChecksum(vector<uint8_t> frame, uint8_t checksum) - { - // Sum all the bytes including checksum. - // The sum can be stored in a uint8_t since we only care about the least - // significant bits. - uint8_t sum = checksum; - for (size_t i = 0; i < frame.size(); ++i) - { - sum += frame[i]; - } - return sum == 0xFF; - } - - // How long to wait for a transmit status response. - unsigned int send_timeout; - - vector<uint8_t> tx_buf; - - bool received_tx_status = false; - - FastMutex rx_mutex; - vector<uint8_t> rx_frame; - ConditionVariable rx_cond; - - ParserState parser_state = ParserState::FIND_START; - size_t parser_packet_length = 0; - vector<uint8_t> parser_buf; + void handleFrame(APIFrame& frame); + /** + * @brief Sends an AT command to the devices + * @attention mutex_xbee_comm must be locked before calling this function. + */ + uint8_t sendATCommandInternal(const char* cmd, uint8_t* params = nullptr, + size_t params_len = 0); + + /** + * @brief Sends an AT command to the devices + * @attention mutex_xbee_comm must be locked before calling this function. + */ + uint8_t sendATCommandInternal(uint8_t frame_id, const char* cmd, + uint8_t* params = nullptr, + size_t params_len = 0); + + uint8_t buildTXRequestFrame(TXRequestFrame& tx_req, uint8_t* pkt, + size_t pkt_len); + + /** + * @brief Fills the provided buffer with data contained in rx_frames_buf + */ + size_t fillReceiveBuf(uint8_t* buf, size_t buf_max_size); + + /** + * @brief Reads a single frame from the SPI bus. + * @attention mutex_xbee_comm must be locked before calling this function. + */ + ParseResult readOneFrame(); + + /** + * @brief Reads the SPI bus until an RXPacketFrame is encountered or no more + * data is available + * @attention mutex_xbee_comm must be locked before calling this function. + * + * @return true RXPacketFrame received + * @return false No more data available on SPI and RXPacketFrame not + * received + */ + bool readRXFrame(); + + /** + * @brief Writes a frame from the SPI bus. + * @attention mutex_xbee_comm must be locked before calling this function. + */ + void writeFrame(APIFrame& frame); + + uint8_t getNewFrameID(); + + /** + * @brief Waits until an APIFrame with frame type \p frame_type is received + * on the SPI or until the timeouts expires. The frame is then stored in the + * parsing_api_frame local class varibale. Any frame received by this + * function is automatically passed to the handleFrame() function + * @attention mutex_xbee_comm must be locked before calling this function. + * + * @param poll_interval Polling interval for the attn pin in milliseconds + * @param timeout_tick Tick after which the function should stop waiting for + * frames + * @return true If a frame with type equal to \p frame_type is received + * @return false If the timeout_tick reached + */ + bool waitForFrame(uint8_t frame_type, unsigned int poll_interval, + long long timeout_tick); + + // Synchronizes all communications to the xbee, as they may happen on + // multiple threads + FastMutex mutex_xbee_comm; + + APIFrameParser parser; + // Frame being parsed. Only valid if last call to + // parser.parse() returned ParseResult::SUCCESS + APIFrame parsing_api_frame; + + // Temporary storage for RX packet frames, waiting to be returned by + // receive() + CircularBuffer<RXPacketFrame, RX_FRAMES_BUF_SIZE> rx_frames_buf; + FastMutex mutex_rx_frames; + // RX Packet currently being returned by receive() + RXPacketFrame curr_rx_frame; + // Index of the first byte of the payload of curr_rx_frame that will be + // returned on the next call to receive() + int curr_rx_payload_ptr = -1; + + // SPI defs + SPISlave spi_xbee; + GpioType attn; + GpioType rst; + + // How long to wait for a TX Status + long long tx_timeout; + + OnFrameReceivedListener frame_listener; + + // Used to generate a unique frame id + uint8_t frame_id_counter = 1; + + // Forces receive() to return even if there is no new data + bool force_rcv_return = false; + + // Status structs XbeeStatus status; -}; // namespace Xbee + Stats time_to_send_stats; + + // Waiting thread to be woken when something has been received. + miosix::Thread* receive_thread = 0; + + PrintLogger logger = Logging::getLogger("xbee"); +}; } // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/Xbee/XbeeStatus.h b/src/shared/drivers/Xbee/XbeeStatus.h index a3bfcaa44f8e38d951da2aa1bd1b48b2cb296d71..8a246debcd1cb8dad6217e8baebdfd3638c5ca08 100644 --- a/src/shared/drivers/Xbee/XbeeStatus.h +++ b/src/shared/drivers/Xbee/XbeeStatus.h @@ -1,20 +1,19 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta - * +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * * 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 + * 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 @@ -25,27 +24,43 @@ #include <cstdint> #include <cstdio> +#include <ostream> +#include <string> +#include "math/Stats.h" namespace Xbee { - struct XbeeStatus +struct XbeeStatus +{ + long long timestamp = 0LL; + + uint8_t last_tx_status_error = 0; + uint8_t last_tx_status = 0; + + StatsResult time_to_send_stats; + + unsigned int tx_timeout_count = 0; + + unsigned int rx_dropped_buffers = 0; + + unsigned int frame_buf_max_length = 0; + + static std::string header() + { + return "timestamp,last_tx_status_error,last_tx_status,tts_stats.min," + "tts_stats.max,tts_stats.mean,tts_stats.stddev,tts_stats.n_" + "samples,tx_timeout_count,rx_dropped_buffers,frame_buf_max_" + "length\n"; + } + + void print(std::ostream& os) const { - uint8_t tx_retry_count = 0; - uint8_t tx_delivery_status = 0; - uint8_t tx_discovery_status = 0; - unsigned int tx_timeout_count = 0; - - unsigned int rx_dropped_buffers = 0; - unsigned int rx_wrong_checksum = 0; - - void print() - { - TRACE("+++XBEE STATUS+++"); - TRACE("TX: Timeouts: %d\n", tx_timeout_count); - TRACE("TXSTATUS: Retries: %d, Delivery: %02X, Discovery: %02X\n", - tx_retry_count, tx_delivery_status, tx_discovery_status); - TRACE("RX: Dropped: %d, Checksum: %d\n", rx_dropped_buffers, rx_wrong_checksum); - - } - }; -} \ No newline at end of file + os << timestamp << "," << (int)last_tx_status_error << "," + << (int)last_tx_status << "," << time_to_send_stats.minValue << "," + << time_to_send_stats.maxValue << "," << time_to_send_stats.mean + << "," << time_to_send_stats.stdev << "," + << time_to_send_stats.nSamples << "," << tx_timeout_count << "," + << rx_dropped_buffers << "," << frame_buf_max_length << "\n"; + } +}; +} // namespace Xbee \ No newline at end of file diff --git a/src/shared/drivers/adc/AD7994Data.h b/src/shared/drivers/adc/AD7994Data.h deleted file mode 100644 index d921ed9196609ad4a5c7abc2414671e935d72afa..0000000000000000000000000000000000000000 --- a/src/shared/drivers/adc/AD7994Data.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SRC_SHARED_DRIVERS_ADC_AD7994DATA_H -#define SRC_SHARED_DRIVERS_ADC_AD7994DATA_H - -#include <cstdint> -#include <ostream> -#include <string> - -struct AD7994Sample -{ - long long timestamp; - - uint8_t channel_id; // [1-4] - bool alert_flag; - uint16_t value; - - // Functions used to deserialize the binary logs into csv files - - static std::string header() { return "timestamp,ch_id,value,alert_flag\n"; } - - void print(std::ostream& os) const - { - os << timestamp << "," << channel_id << "," << value << "," - << (int)alert_flag << "\n"; - } -}; - -#endif \ No newline at end of file diff --git a/src/shared/drivers/adc/ADC.h b/src/shared/drivers/adc/ADC.h deleted file mode 100644 index 4eaa76818c779336ac117475f8555c5b9add9fc8..0000000000000000000000000000000000000000 --- a/src/shared/drivers/adc/ADC.h +++ /dev/null @@ -1,171 +0,0 @@ -/* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Luca Erbetta - * - * 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. - */ -#pragma once -#include "Common.h" - -/** - * @brief Driver for stm32 internal ADC. - * Allows for conversion on multiple channels, one at a time (no SCAN mode), - * with per-channel sample time. - * This class is not thread-safe. All conversions must be executed on the same - * thread. - * @tparam ADC_num Which ADC to use: [1,2,3] on stm32f429zi - */ -template <unsigned ADC_num> -class SensorADC -{ -public: - /** - * @brief One ADC channel - */ - enum class Channel : uint8_t - { - CH1 = 1, - CH2, - CH3, - CH4, - CH5, - CH6, - CH7, - CH8, - CH9, - CH10, - CH11, - CH12, - CH13, - CH14, - CH15, - CH16, - CH17, - CH18, - CH19 - }; - - /** - * @brief Conversion sample time. See datasheet. - * - */ - enum class SampleTime : uint8_t - { - CYCLES_3 = 0x0, - CYCLES_15 = 0x1, - CYCLES_28 = 0x2, - CYCLES_56 = 0x3, - CYCLES_84 = 0x4, - CYCLES_112 = 0x5, - CYCLES_144 = 0x6, - CYCLES_480 = 0x7 - }; - - SensorADC() - { - adc = getADC(ADC_num); - enableADC(); - - adc->SQR1 = 0x0; // One conversion - - adc->CR2 = ADC_CR2_ADON; - // CCR pre scaler - ADC->CCR = 0x3 << 16; - } - - ~SensorADC() { disableADC(); } - - /** - * @brief Converts the specified channel - * - * @param channel The channel to convert - * @param sample_time Conversion sample time - * @return uint16_t Measured value (12 bits) - */ - uint16_t convertChannel(Channel channel, - SampleTime sample_time = SampleTime::CYCLES_15) - { - // Select the channel - adc->SQR3 = static_cast<uint32_t>(channel); - // Set sample time - adc->SMPR2 = static_cast<uint32_t>(sample_time); - - // Start conversion - adc->CR2 |= ADC_CR2_SWSTART; - - // Wait for conversion to finish - const int TIMEOUT = 50; // Make sure the loop terminates eventually - int i = 0; - while ((adc->SR & ADC_SR_EOC) == 0 && i < TIMEOUT) - { - ++i; - } - adc->SR = 0; // Clear the status register - - // Conversion terminated, read result - return static_cast<uint16_t>(adc->DR & 0x0000FFFF); - } - -private: - void enableADC() - { - miosix::FastInterruptDisableLock dLock; - - if (adc == ADC1) - { - RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; - } - else if (adc == ADC2) - { - RCC->APB2ENR |= RCC_APB2ENR_ADC2EN; - } - else if (adc == ADC3) - { - RCC->APB2ENR |= RCC_APB2ENR_ADC3EN; - } - - RCC_SYNC(); - } - - void disableADC() - { - miosix::FastInterruptDisableLock dLock; - - if (adc == ADC1) - { - RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN; - } - else if (adc == ADC2) - { - RCC->APB2ENR &= ~RCC_APB2ENR_ADC2EN; - } - else if (adc == ADC3) - { - RCC->APB2ENR &= ~RCC_APB2ENR_ADC3EN; - } - - RCC_SYNC(); - } - - constexpr ADC_TypeDef* getADC(unsigned n) - { - return n == 1 ? ADC1 : n == 2 ? ADC2 : ADC3; - } - - ADC_TypeDef* adc; -}; diff --git a/src/shared/drivers/adc/ADS1118/ADS1118.cpp b/src/shared/drivers/adc/ADS1118/ADS1118.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f19aeb1cbb548ef943a68b39bb1adb8516f5e45d --- /dev/null +++ b/src/shared/drivers/adc/ADS1118/ADS1118.cpp @@ -0,0 +1,318 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#include "ADS1118.h" + +#include <interfaces/endianness.h> + +#include "TimestampTimer.h" + +const ADS1118::ADS1118Config ADS1118::ADS1118_DEFAULT_CONFIG = { + SINGLE_SHOT_MODE, FSR_2_048, MUX_AIN0_AIN1, 0, 0, + VALID_OPERATION, PULL_UP_EN, ADC_MODE, DR_128}; + +ADS1118::ADS1118(SPIBusInterface &bus, GpioPin cs, ADS1118Config config_, + SPIBusConfig spiConfig) + : ADS1118(SPISlave(bus, cs, spiConfig), config_, false) +{ +} + +ADS1118::ADS1118(SPISlave spiSlave_, ADS1118Config config_, bool busyWait_, + int16_t tempDivider_) + : spiSlave(spiSlave_), baseConfig(config_), busyWait(busyWait_), + tempDivider(tempDivider_) + +{ + // Initialize to 0 all the channels + for (auto i = 0; i < NUM_OF_CHANNELS; i++) + { + channelsConfig[i].word = 0; + + // Set channel id for each ADS1118Data object in values array + values[i].channel_id = i; + } + + // Reset the last written config value + lastConfig.word = 0; + lastConfigIndex = 0; +} + +SPIBusConfig ADS1118::getDefaultSPIConfig() +{ + SPIBusConfig spiConfig{}; + spiConfig.clock_div = SPIClockDivider::DIV32; + spiConfig.mode = SPIMode::MODE1; + return spiConfig; +} + +bool ADS1118::init() { return true; } + +void ADS1118::enableInput(ADS1118Mux mux) +{ + enableInput(mux, baseConfig.bits.rate, baseConfig.bits.pga); +} + +void ADS1118::enableInput(ADS1118Mux mux, ADS1118DataRate rate, ADS1118Pga pga) +{ + channelsConfig[mux].bits.mode = baseConfig.bits.mode; + channelsConfig[mux].bits.pga = pga; + channelsConfig[mux].bits.mux = mux; + channelsConfig[mux].bits.singleShot = 1; + channelsConfig[mux].bits.noOp = VALID_OPERATION; + channelsConfig[mux].bits.pullUp = baseConfig.bits.pullUp; + channelsConfig[mux].bits.tempMode = ADC_MODE; + channelsConfig[mux].bits.rate = rate; + + // Decrement the sample counter in order to read the temperature earlier on + sampleCounter--; +} + +void ADS1118::disableInput(ADS1118Mux mux) { channelsConfig[mux].word = 0; } + +void ADS1118::disableAllInputs() +{ + for (auto i = 0; i < NUM_OF_CHANNELS; i++) + { + channelsConfig[i].word = 0; + } +} + +void ADS1118::enableTemperature() +{ + channelsConfig[TEMP_CHANNEL].word = TEMP_CONFIG; +} + +void ADS1118::disableTemperature() { channelsConfig[TEMP_CHANNEL].word = 0; } + +void ADS1118::enablePullUpResistor() { baseConfig.bits.pullUp = PULL_UP_EN; } + +void ADS1118::disablePullUpResistor() { baseConfig.bits.pullUp = PULL_UP_DIS; } + +void ADS1118::enableConfigCheck() { configCheck = true; } + +void ADS1118::disableConfigCheck() { configCheck = false; } + +ADS1118Data ADS1118::readInputAndWait(ADS1118Mux mux) +{ + readChannel(mux); + return getVoltage(mux); +} + +TemperatureData ADS1118::readTemperatureAndWait() +{ + readChannel(TEMP_CHANNEL); + return getTemperature(); +} + +ADS1118Data ADS1118::getVoltage(ADS1118Mux mux) { return values[mux]; } + +TemperatureData ADS1118::getTemperature() +{ + return TemperatureData{values[TEMP_CHANNEL].adc_timestamp, + values[TEMP_CHANNEL].voltage}; +} + +int ADS1118::getConversionTime(int8_t channel) +{ + if (channel >= 0 && channel <= TEMP_CHANNEL) + { + return CONV_TIME[channelsConfig[channel].bits.rate]; + } + else + { + return 0; + } +} + +bool ADS1118::selfTest() +{ + // Save the current configuration + bool prevConfigCheck = configCheck; + uint16_t prevTempConfig = channelsConfig[TEMP_CHANNEL].word; + + // Enable temperature config in case it is not, just in case no other + // configuration is enabled + channelsConfig[TEMP_CHANNEL].word = TEMP_CONFIG; + + // Enable configuration check + configCheck = true; + + // Check the communication by reading the temperature channel + readChannel(TEMP_CHANNEL, INVALID_CHANNEL); + + // Restore the configuration + configCheck = prevConfigCheck; + channelsConfig[TEMP_CHANNEL].word = prevTempConfig; + + // Return false if an error occurred + return last_error == 0; // The sensor class misses a constant for no error +} + +/** + * The sampling of all the enabled inputs is performed in sequence starting from + * the input with the lowest mux value. + * + * The first configuration is written and then, at the next call, read back the + * result while transmitting the next configuration + */ +ADS1118Data ADS1118::sampleImpl() +{ + int8_t i = findNextEnabledChannel(lastConfigIndex + 1); + + // Write the next config and read the value (only if lastConfig is valid) + readChannel(i, lastConfig.word != 0 ? lastConfigIndex : INVALID_CHANNEL); + + // Save index and config for the next read + lastConfig.word = channelsConfig[i].word; + lastConfigIndex = i; + + // Increment the sample counter + sampleCounter++; + + // Regardless of the readChannel result, return the value stored + return values[lastConfigIndex]; +} + +void ADS1118::readChannel(int8_t nextChannel, int8_t prevChannel) +{ + int16_t rawValue; + uint32_t writeData, transferData; + + last_error = NO_ERRORS; + + // Prepare the next configuration data + if (nextChannel > INVALID_CHANNEL && nextChannel < NUM_OF_CHANNELS) + { + writeData = channelsConfig[nextChannel].word; + } + else + { + // A valid configuration will always be not equal to 0 since the valid + // operation bits must be 0b01 + writeData = 0x0; + } + + // Write next configuration and read previous value if necessary + transferData = writeData; + { + SPITransaction transaction(spiSlave); + transaction.transfer((uint8_t *)&transferData, configCheck ? 4 : 2); + } + + // If enabled and a valid configuration has just been written, check the + // read back configuration + if (configCheck && writeData) + { + // Compare the configuration with the second 16 bit word read + if ((channelsConfig[nextChannel].word & CONFIG_MASK) != + (transferData >> 16 & CONFIG_MASK)) + { + // Save the error + last_error = BUS_FAULT; + + // Disable next value conversion + lastConfig.word = 0; + } + } + + // Convert and save the value if last written configuration is valid + if (prevChannel >= 0) + { + rawValue = swapBytes16(transferData); + + // TODO: the timestamp should be taken when the configuration is + // written, now we could be reading the value after some time! + values[prevChannel].adc_timestamp = TimestampTimer::getTimestamp(); + + if (prevChannel != 8) // Voltage value + { + values[prevChannel].voltage = + rawValue * PGA_LSB_SIZE[channelsConfig[prevChannel].bits.pga] / + 1000; + } + else // Temperature value + { + values[TEMP_CHANNEL].voltage = (rawValue / 4) * TEMP_LSB_SIZE; + } + } +} + +/** + * To read an input we'll first write the appropriate configuration, wait for + * the sample accordingly to the channel data rate and then read back the result + */ +void ADS1118::readChannel(int8_t channel) +{ + readChannel(channel, INVALID_CHANNEL); + + if (busyWait) + { + // Use a busy wait loop to be as precise as possible + miosix::delayUs(getConversionTime(channel)); + } + else + { + // Converto to milliseconds and increment by one to prevent premature + // readings + miosix::Thread::sleep(getConversionTime(channel) / 1000 + 1); + } + + readChannel(INVALID_CHANNEL, channel); +} + +/** + * Search the next enabled channel starting from the specified channel and + * returns it's index. If no channels are enabled return INVALID_CHANNEL + */ +int8_t ADS1118::findNextEnabledChannel(int8_t startChannel) +{ + int8_t &channel = startChannel; // Just a change of name + + for (auto i = 0; i < 2; i++) + { + // Go to the first channel if channel is too big + if (channel >= NUM_OF_CHANNELS) + { + channel = 0; + } + + // Find next enabled mux config + for (; channelsConfig[channel].word == 0 && channel < NUM_OF_CHANNELS; + channel++) + ; + + // Check if the channel is valid and, for the temperature channel, if we + // have to read it based on sampleCounter and tempDivider + // If invalid try to search again starting from the first channel + if (channel == TEMP_CHANNEL && sampleCounter % tempDivider != 0) + { + continue; + } + if (channel < NUM_OF_CHANNELS) + { + return channel; + } + } + + // If no valid channel has been fount return an invalid channel + return INVALID_CHANNEL; +} \ No newline at end of file diff --git a/src/shared/drivers/adc/ADS1118/ADS1118.h b/src/shared/drivers/adc/ADS1118/ADS1118.h new file mode 100644 index 0000000000000000000000000000000000000000..25c600e0c53f1adc66cc31b20aad6b50d98d8d02 --- /dev/null +++ b/src/shared/drivers/adc/ADS1118/ADS1118.h @@ -0,0 +1,368 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +/** + * @brief Driver for ADS1118 adc + * + * The ADS1118 is a 16-bit delta-sigma analog-to-digital converter. + * It measures the difference of two inputs configured by a MUX. The device + * allows also to read each of the four pins in reference to GND. A temperature + * sensor is also included. + * + * The communication uses the spi protocol and the maximum allowed frequency is + * 4MHz. Configuration is applied by writing a 16bit value to the device. + * Sampled voltages or temperature readings are obtained also by reading a 16bit + * value while writing the configuration. + * + * Data rate between 8Hz and 860Hz can be programmed and an internal + * programmable gain aplifier can be set with a sensitivity range from ±0.256V + * to ±6.144V (note that the inputs must remain between VCC or GND). + * + * The data rate should be choosen as low as possible to allow the delta-sigma + * adc to average the input voltage (this allows a less noisy reading). + * + * The device can work in two modes: + * - CONTIN_CONV_MODE: Continuosly read the last configured channel, when you + * make a read you'll obtain the lates reading + * - SINGLE_SHOT_MODE: A single conversion is performed when the configuration + * is written + * + * The ADS1118 is a simple device, it has a single data register where it can + * store the reading, therefore it can sample a single input at a time. sample() + * cylces through the enabled channels one at a time and writes it's + * configuration while reading the value of the previous written one. + * + * As an example if you need to read 4 inputs at 50Hz you should set all the + * data rates at 250Hz (50Hz x * 4 = 200Hz) and call sample() at a rate of + * 200Hz. + * + * An example for how to use the driver can be found in the test code + * (src/tests/drivers/test-ads1118.cpp) + */ + +#pragma once + +#include "ADS1118Data.h" +#include "drivers/spi/SPIDriver.h" +#include "sensors/Sensor.h" + +/** + * @brief Driver class for ADS1118 adc + * + * This class allows to mange the device configuration. + */ +class ADS1118 : public Sensor<ADS1118Data> +{ +public: + enum ADS1118Mux + { + MUX_AIN0_AIN1 = 0x0, ///< AINp is AIN0 and AINn is AIN1 (default) + MUX_AIN0_AIN3 = 0x1, ///< AINp is AIN0 and AINn is AIN3 + MUX_AIN1_AIN3 = 0x2, ///< AINp is AIN1 and AINn is AIN3 + MUX_AIN2_AIN3 = 0x3, ///< AINp is AIN2 and AINn is AIN3 + MUX_AIN0_GND = 0x4, ///< AINp is AIN0 and AINn is GND + MUX_AIN1_GND = 0x5, ///< AINp is AIN1 and AINn is GND + MUX_AIN2_GND = 0x6, ///< AINp is AIN2 and AINn is GND + MUX_AIN3_GND = 0x7 ///< AINp is AIN3 and AINn is GND + }; ///< Multiplexer values + + enum ADS1118Pga + { + FSR_6_144 = 0x0, ///< FSR is ±6.144 V + FSR_4_096 = 0x1, ///< FSR is ±4.096 V + FSR_2_048 = 0x2, ///< FSR is ±2.048 V (default) + FSR_1_024 = 0x3, ///< FSR is ±1.024 V + FSR_0_512 = 0x4, ///< FSR is ±0.512 V + FSR_0_256 = 0x5 ///< FSR is ±0.256 V + }; ///< Programmable gain amplifier values + + enum ADS1118Mode + { + CONTIN_CONV_MODE = 0x0, ///< Continuous-conversion mode + SINGLE_SHOT_MODE = 0x1 ///< Power-down and single-shot mode (default) + }; ///< Conversione mode values + + enum ADS1118DataRate + { + DR_8 = 0x0, ///< 8 SPS + DR_16 = 0x1, ///< 16 SPS + DR_32 = 0x2, ///< 32 SPS + DR_64 = 0x3, ///< 64 SPS + DR_128 = 0x4, ///< 128 SPS (default) + DR_250 = 0x5, ///< 250 SPS + DR_475 = 0x6, ///< 475 SPS + DR_860 = 0x7 ///< 860 SPS + }; ///< Data rate configuration values + + enum ADS1118TempMode + { + ADC_MODE = 0x0, ///< ADC mode (default) + TEMP_SENSOR_MODE = 0x1 ///< Temperature sensor mode + }; ///< Temeprature or ADC mode values + + enum ADS1118PullUp + { + PULL_UP_DIS = 0x0, ///< Pullup resistor disabled on DOUT pin + PULL_UP_EN = 0x1 ///< Pullup resistor enabled on DOUT pin (default) + }; ///< Pull up enable or disable values + + union ADS1118Config + { + struct + { + ADS1118Mode mode : 1; ///< Device operating mode + ADS1118Pga pga : 3; ///< Programmable gain amplifier configuration + ADS1118Mux mux : 3; ///< Input multiplexer configuration + uint8_t singleShot : 1; ///< Single-shot conversion start + uint8_t reserved : 1; ///< Reserved, doesn't matter + uint8_t noOp : 2; ///< No operation + ADS1118PullUp pullUp : 1; ///< Pullup enable + ADS1118TempMode tempMode : 1; ///< Temperature sensor mode + ADS1118DataRate rate : 3; ///< Data rate + } bits; ///< Includes all the configuration bits + struct + { + uint8_t msb; ///< Byte MSB + uint8_t lsb; ///< Byte LSB + } byte; ///< Includes the msb and lsb bytes + uint16_t word; ///< Representation in word (16-bits) format + }; ///< Structure of configuration word + + struct ADS1118InputConfig + { + ADS1118Mux mux = MUX_AIN0_AIN1; ///< Input's mux configuration + ADS1118DataRate rate = DR_128; ///< Input's data rate configuration + }; ///< Driver's input config + + static constexpr uint8_t VALID_OPERATION = + 0x1; ///< Indicates a valid configuration + + static const ADS1118Config + ADS1118_DEFAULT_CONFIG; ///< Default configuration + + static constexpr int8_t TEMP_CHANNEL = 8; ///< Temperature channel number + + static constexpr int8_t NUM_OF_CHANNELS = 9; + + static constexpr int8_t INVALID_CHANNEL = -1; + + /** + * @brief Construct a new ADS1118 object specifing spi bus, spi config and + * cs pin as well as device configuration + */ + ADS1118(SPIBusInterface &bus, GpioPin cs, ADS1118Config config_, + SPIBusConfig spiConfig = getDefaultSPIConfig()); + + /** + * @brief Construct a new ADS1118 object + * + * @param spiSlave_ Spi slave configured with spi interface, spi config and + * ss pin + * @param config_ Device main configuration used as default while enabling + * channels + * @param busyWait_ Enable busy wait instead normal wait (uses `delayUs` + * instead of `sleep`), only useful when sampling at close to the maximum + * frequency! + * @param tempDivider_ Specify how many onSimpleUpdate calls between each + * temperature reading + */ + ADS1118(SPISlave spiSlave_, ADS1118Config config_ = ADS1118_DEFAULT_CONFIG, + bool busyWait_ = false, int16_t tempDivider_ = 100); + + /** + * Constructs the default config for SPI Bus. + * + * @returns the default SPIBusConfig + */ + static SPIBusConfig getDefaultSPIConfig(); + + /** + * @brief Initialize the configuration + * + * It resets all the channels thus you must call init() before enabling any + * channel + */ + bool init() override; + + /** + * @brief Enables the sapling of a specific mux configuration with the main + * configuration specified in the constructor + * + * @param mux Mux configuration to enable + */ + void enableInput(ADS1118Mux mux); + + /** + * @brief Enables the sapling of a specific mux configuration + * + * @param mux Mux configuration to enable + * @param rate Data rate for this specific channel + * @param pga Gain for this specific channel + */ + void enableInput(ADS1118Mux mux, ADS1118DataRate rate, ADS1118Pga pga); + + /** + * @brief Disables the specified mux configuration + * + * @param mux Mux configuration to disable + */ + void disableInput(ADS1118Mux mux); + + /** + * @brief Disables all the inputs + */ + void disableAllInputs(); + + /** + * @brief Enables temperature readings + */ + void enableTemperature(); + + /** + * @brief Disables temperature readings + */ + void disableTemperature(); + + /** + * @brief Enables the pull up resistor on dout/miso + */ + void enablePullUpResistor(); + + /** + * @brief Disables the pull up resistor on dout/miso + */ + void disablePullUpResistor(); + + /** + * @brief Enables the configuration check after it's writing to the device + */ + void enableConfigCheck(); + + /** + * @brief Disables the configuration check after it's writing to the device + */ + void disableConfigCheck(); + + /** + * @brief Reads on the fly the specified input + * + * @param mux Mux configuration to read + * @return Voltage value sampled from the channel in mV + */ + ADS1118Data readInputAndWait(ADS1118Mux mux); + + /** + * @brief Reads on the fly the temperature + * + * @return Temperature in degree + */ + TemperatureData readTemperatureAndWait(); + + /** + * @brief Returns the last read voltage value for the specified channel + */ + ADS1118Data getVoltage(ADS1118Mux mux); + + /** + * @brief Returns the last temperature value + */ + TemperatureData getTemperature(); + + /** + * @brief Returns the conversion time in us for the specified channel + */ + int getConversionTime(int8_t channel); + + /** + * @brief Writes the temperature configuration and check if it is read back + * correctly + * + * @return True if everything ok + */ + bool selfTest() override; + +private: + /** + * @brief Reads the previously configured channel while writing the next + * enabled configuration. + * + * Multiple calls are needed to read all the enabled channels. + */ + ADS1118Data sampleImpl() override; + + /** + * @brief Writes the configuration specified, reads the previous written + * configuration's value and stores it. If enabled checks also that the + * configuration has been written correctly + * + * @param nextChannel Channel number to write the configuration of + * @param prevChannel Channel number to read the configuration of + * @return True if everything ok + */ + void readChannel(int8_t nextChannel, int8_t prevChannel); + + /** + * @brief Reads on the fly the speficied channel + * + * @param channel + */ + void readChannel(int8_t channel); + + int8_t findNextEnabledChannel(int8_t startChannel); + + const SPISlave spiSlave; + ADS1118Config baseConfig; + + ///< Read the written configuration on each transaction and checks it + bool configCheck = false; + + ///< Use `delayUs` instead of `sleep` + const bool busyWait; + + ADS1118Config channelsConfig[NUM_OF_CHANNELS]; ///< Channels configuration + ADS1118Data values[NUM_OF_CHANNELS]; ///< Voltage values in mV + + ADS1118Config lastConfig; ///< Last written configuration + uint8_t lastConfigIndex = 0; ///< Last written configuration's index + + ///< Rate of sample calls on which the temperature read + const uint16_t tempDivider; + + uint16_t sampleCounter = 0; ///< Counts the number of samples made + + ///< Digit value in mV for each pga configurations + const float PGA_LSB_SIZE[6] = {0.187, 0.125, 0.0625, + 0.03125, 0.015625, 0.0078125}; + + ///< Conversion times in us + 100us + const int CONV_TIME[8] = {125000, 62500, 31250, 15625, + 7813, 4000, 2106, 1163}; + + static constexpr float TEMP_LSB_SIZE = 0.03125; + static constexpr uint16_t TEMP_CONFIG = 0xF281; + + /** + * This masks excludes 2 bits from the configuration, the reserved bit and + * the sigle shot bit + */ + static constexpr uint16_t CONFIG_MASK = 0xFE7F; +}; \ No newline at end of file diff --git a/src/shared/drivers/adc/ADS1118/ADS1118Data.h b/src/shared/drivers/adc/ADS1118/ADS1118Data.h new file mode 100644 index 0000000000000000000000000000000000000000..3bbc933f589ab47ebf2b07ac66d58333aadcffe4 --- /dev/null +++ b/src/shared/drivers/adc/ADS1118/ADS1118Data.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct ADS1118Data : public ADCData +{ + ADS1118Data() : ADCData{0, 0, 0.0} {} + + ADS1118Data(uint64_t t, uint8_t channel_id, float voltage) + : ADCData{t, channel_id, voltage} + + { + } + + static std::string header() { return "adc_timestamp,channel_id,voltage\n"; } + + void print(std::ostream& os) const + { + os << adc_timestamp << "," << (int)channel_id << "," << voltage << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/drivers/adc/InternalADC.h b/src/shared/drivers/adc/InternalADC.h deleted file mode 100644 index 9d0558b4a1ff8ddd824646a55c507f63c604cc3d..0000000000000000000000000000000000000000 --- a/src/shared/drivers/adc/InternalADC.h +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (c) 2015-2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta, Matteo Michele Piazzolla - * - * 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. - */ - -#pragma once - -#include <utility> -#include <vector> -#include "Common.h" -#include "Debug.h" -#include "InternalADCData.h" - -using std::pair; -using std::vector; - -/** - * +++ THIS CLASS IS NOT FINISHED. THIS WILL NOT WORK +++ - * @brief SensorADC class to sample data from multiple stm32 adc channels. - * Supports converting multiple channels with per-channel sample time. - * All channels are converted togheter, so the sampling frequency is the same - * for all channels. - * - * DMA STREAMS for stm32f429zi: - * ADC1: DMA2_Stream0, DMA2_Stream4 - * ADC2: DMA2_Stream2, DMA2_Stream3 - * ADC3: DMA2_Stream0, DMA2_Stream1 - * - * @tparam ADC_n Which adc to use (1,2,3 for stm32f429zi) - * @tparam DMA_Stream Which dma stream to use (see above) - */ -template <unsigned ADC_n, unsigned DMA_Stream> -class SensorADC -{ -public: - enum class Channel : uint8_t - { - CH1 = 1, - CH2, - CH3, - CH4, - CH5, - CH6, - CH7, - CH8, - CH9, - CH10, - CH11, - CH12, - CH13, - CH14, - CH15, - CH16, - CH17, - CH18, - CH19 - }; - - enum class SampleTime : uint8_t - { - CYCLES_3 = 0x0, - CYCLES_15 = 0x1, - CYCLES_28 = 0x2, - CYCLES_56 = 0x3, - CYCLES_84 = 0x4, - CYCLES_112 = 0x5, - CYCLES_144 = 0x6, - CYCLES_480 = 0x7 - }; - - SensorADC() : adc(getADC(ADC_n)) - { - - { - miosix::FastInterruptDisableLock dLock; - - if (adc == ADC1) - { - RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; - } - else if (adc == ADC2) - { - RCC->APB2ENR |= RCC_APB2ENR_ADC2EN; - } - else if (adc == ADC3) - { - RCC->APB2ENR |= RCC_APB2ENR_ADC3EN; - } - - RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; - - RCC_SYNC(); - } - adc->CR2 = ADC_CR2_ADON; - // CCR pre scaler - ADC->CCR = 0x3 << 16; - - // DMA Setup - setupDMA(); - } - - ~SensorADC() { disableADC(); } - - bool enableChannel(Channel ch, SampleTime sample_time) - { - if (channels.size() == 16) - { - return false; - } - - for (auto& i : channels) - { - if (i.first == ch) - { - // channel already enabled - return false; - } - } - - channels.push_back({ch, sample_time}); - - updateChannels(); - - return true; - } - - void disableChannel(Channel ch) - { - for (auto it = channels.begin(); it < channels.end(); ++it) - { - if ((*it).first == ch) - { - channels.erase(it); - - updateChannels(); - return; - } - } - } - - void update() - { - adc->CR2 |= ADC_CR2_SWSTART; - int i = 0; - while (adc->SR & ADC_SR_EOC == 0) - { - ++i; - } - adc->SR = 0; - printf("Waited %d cycles. ", i); - } - - uint16_t get() { return static_cast<uint16_t>(adc->DR & 0x0000FFFF); } - -private: - constexpr ADC_TypeDef* getADC(unsigned n) - { - return n == 1 ? ADC1 : n == 2 ? ADC2 : ADC3; - } - - void enableADC() {} - - void disableADC() - { - miosix::FastInterruptDisableLock dLock; - - if (adc == ADC1) - { - RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN; - } - else if (adc == ADC2) - { - RCC->APB2ENR &= ~RCC_APB2ENR_ADC2EN; - } - else if (adc == ADC3) - { - RCC->APB2ENR &= ~RCC_APB2ENR_ADC3EN; - } - - RCC_SYNC(); - } - - void setupDMA() {} - - void updateChannels() - { - // Clear the registers - adc->SQR1 = 0; - adc->SQR2 = 0; - adc->SQR3 = 0; - adc->SMPR1 = 0; - adc->SMPR2 = 0; - - if (channels.size() == 0) - { - // If no channel is enabled, clear the registers and return. - return; - } - - // Store the channel sequence in the SQRx registers - uint8_t L = channels.size() - 1; - - volatile uint32_t* sqr_ptr = - &(adc->SQR3); // Pointer to the SQR3 register, defining the first 6 - // channels in the sequence. If more channels are to - // be converted, this pointer will be updated to - // point the register that stores the sequence for - // subsequent channels. - - unsigned int offset = 0; - for (unsigned int i = 0; i < channels.size(); i++) - { - if (i == 6) - { - sqr_ptr = &(adc->SQR2); - offset = 6; - } - else if (i == 12) - { - sqr_ptr = &(adc->SQR1); - offset = 12; - } - - *sqr_ptr |= static_cast<uint8_t>(channels[i].first) - << ((i - offset) * 5); - } - - adc->SQR1 |= L << 20; - - // Now update sample times for each channel - - volatile uint32_t* SMPR; - for (auto& i : channels) - { - uint8_t ch = static_cast<uint8_t>(i.first) - 1; // CH1 --> 0 - uint8_t sample_time = static_cast<uint8_t>(i.second); - if (ch >= 10) - { - SMPR = &(adc->SMPR1); - offset = 10; - } - else - { - SMPR = &(adc->SMPR1); - offset = 0; - } - - *SMPR |= static_cast<uint32_t>(sample_time) << ((ch - offset) * 3); - } - } - - ADC_TypeDef* adc; - // Channels to convert, in order of conversion. size() must not exceed 16. - vector<pair<Channel, SampleTime>> channels; -}; \ No newline at end of file diff --git a/src/shared/drivers/adc/InternalADC/InternalADC.cpp b/src/shared/drivers/adc/InternalADC/InternalADC.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5432a14eaa64d7c85aeafb9aeaeec270f1d2b20b --- /dev/null +++ b/src/shared/drivers/adc/InternalADC/InternalADC.cpp @@ -0,0 +1,386 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#include "InternalADC.h" + +#include "TimestampTimer.h" + +InternalADC::InternalADC(ADC_TypeDef& ADCx_, const float V_SUPPLY_, + const bool isUsingDMA_, + DMA_Stream_TypeDef* DMAx_Streamx_) + : ADCx(ADCx_), V_SUPPLY(V_SUPPLY_), isUsingDMA(isUsingDMA_), + DMAx_Streamx(DMAx_Streamx_) +{ + resetRegisters(); + enableADCClock(); + + // Init indexMap + for (auto i = 0; i < CH_NUM; i++) + { + indexMap[i] = -1; + } +} + +InternalADC::~InternalADC() +{ + resetRegisters(); + disableADCClock(); +} + +bool InternalADC::init() +{ + // Turn on the ADC + ADCx.CR2 |= ADC_CR2_ADON; + + // Set single conversion mode + ADCx.CR2 &= ~ADC_CR2_CONT; + + // Set scan mode + ADCx.CR1 |= ADC_CR1_SCAN; + + // Data alignment + ADCx.CR2 &= ~ADC_CR2_ALIGN; // right + + if (isUsingDMA) + { + // Set the DMA peripheral address + DMAx_Streamx->PAR = (uint32_t) & (ADCx.DR); + + // Set the DMA memory address + DMAx_Streamx->M0AR = (uint32_t)values; + + // Enable DMA on ADC + ADCx.CR2 |= ADC_CR2_DMA; + + // Enable DMA stream + DMAx_Streamx->CR |= DMA_SxCR_EN; + + // Check if we are using the DMA2 controller, otherwise it's DMA1 + if (((uint32_t)&DMAx_Streamx & ~0xFF) == (uint32_t)DMA2_BASE) + { + DMAx = DMA2; + } + + // Find the DMA stream number + streamNum = ((((uint32_t)&DMAx_Streamx) & 0xFF) - 0x10) / 0x18; + + // Check which registers to use + statusReg = (streamNum < 4 ? &(DMAx->LISR) : &(DMAx->HISR)); + clearFlagReg = (streamNum < 4 ? &(DMAx->LIFCR) : &(DMAx->HIFCR)); + + // Create the masks for the status bits (this will do for both high and + // low registers as well as status and reset status registers) + switch (streamNum % 4) + { + case 0: + transferCompleteMask = DMA_LISR_TCIF0; + transferErrorMask = DMA_LISR_TEIF0; + break; + case 1: + transferCompleteMask = DMA_LISR_TCIF1; + transferErrorMask = DMA_LISR_TEIF1; + break; + case 2: + transferCompleteMask = DMA_LISR_TCIF2; + transferErrorMask = DMA_LISR_TEIF2; + break; + case 3: + transferCompleteMask = DMA_LISR_TCIF3; + transferErrorMask = DMA_LISR_TEIF3; + break; + } + + // All this is because the status bits are not stored in the + // DMAx_Streamx registers but in the main DMA controller status + // register. We could not care about the transfer status but the + // timestamp would not match the values read after calling sample(). + + // We calculate this values to avoid doing it every time in + // sampleImpl(). + } + + return true; +} + +bool InternalADC::enableChannel(Channel channel) +{ + return enableChannel(channel, CYCLES_3); +} + +bool InternalADC::enableChannel(Channel channel, SampleTime sampleTime) +{ + // Check channel number + if (channel < CH0 || channel >= CH_NUM) + { + return false; + } + + // Add channel to the sequence + if (!isUsingDMA) + { + if (!addInjectedChannel(channel)) + { + return false; + } + } + else + { + if (!addRegularChannel(channel)) + { + return false; + } + + // Update the DMA number of data + DMAx_Streamx->NDTR = activeChannels; + } + + // Set channel's sample time + setChannelSampleTime(channel, sampleTime); + + return true; +} + +ADCData InternalADC::getVoltage(Channel channel) +{ + float voltage = 0; + + if (indexMap[channel] >= 0 && indexMap[channel] < CH_NUM) + { + voltage = values[indexMap[channel]] * V_SUPPLY / RESOLUTION; + } + + return ADCData{timestamp, (uint8_t)channel, voltage}; +} + +bool InternalADC::selfTest() +{ + // Try a single sample and check for error + sample(); + + if (last_error != NO_ERRORS) + { + return false; + } + + return true; +} + +ADCData InternalADC::sampleImpl() +{ + if (!isUsingDMA) + { + startInjectedConversion(); + + // Wait for end of conversion + while (!(ADCx.SR & ADC_SR_JEOC)) + ; + + // Read all 4 channels (faster than read only the enabled ones) + values[0] = ADCx.JDR1; + values[1] = ADCx.JDR2; + values[2] = ADCx.JDR3; + values[3] = ADCx.JDR4; + } + else + { + // Rewrite the DMA bit in ADC CR2 (reference manual chapter 13.8.1) + ADCx.CR2 &= ~ADC_CR2_DMA; + ADCx.CR2 |= ADC_CR2_DMA; + + startRegularConversion(); + + // This should trigger the DMA stream for each channel's conversion + + // Wait for tranfer end + while (!(*statusReg & (transferCompleteMask | transferErrorMask))) + ; + + // Clear the transfer complete flag + *clearFlagReg |= transferCompleteMask; + + // If and error has occurred (probaly due to a higher priority stream) + // don't update the timestamp, the values should not have been updated + if (*statusReg & transferErrorMask) + { + // Clear the transfer error flag + *clearFlagReg |= transferErrorMask; + + // Signal an error + last_error = DMA_ERROR; + return last_sample; + } + } + + timestamp = TimestampTimer::getTimestamp(); + + return last_sample; +} + +inline void InternalADC::resetRegisters() +{ + // Reset the ADC configuration + ADCx.CR1 = 0; + ADCx.CR2 = 0; + ADCx.SMPR1 = 0; + ADCx.SMPR2 = 0; + ADCx.JOFR1 = 0; + ADCx.JOFR2 = 0; + ADCx.JOFR3 = 0; + ADCx.JOFR4 = 0; + ADCx.HTR = 0; + ADCx.LTR = 0; + ADCx.SQR1 = 0; + ADCx.SQR2 = 0; + ADCx.SQR3 = 0; + ADCx.JSQR = 0; +} + +inline void InternalADC::startInjectedConversion() +{ + ADCx.CR2 |= ADC_CR2_JSWSTART; +} + +inline void InternalADC::startRegularConversion() +{ + ADCx.CR2 |= ADC_CR2_SWSTART; +} + +inline bool InternalADC::addInjectedChannel(Channel channel) +{ + // Check active channels number + if (activeChannels >= 4) + { + return false; + } + + // Add the channel to the sequence, starting from position 4 + ADCx.JSQR |= channel << (15 - activeChannels * 5); + + // Update the channels number in the register + ADCx.JSQR &= 0x000FFFFF; + ADCx.JSQR |= activeChannels << 20; + + // Increment the index of all enabled channels + for (auto i = 0; i < CH_NUM; i++) + { + if (indexMap[i] >= 0) + { + indexMap[i]++; + } + } + + // Set this channel index to 0 + indexMap[channel] = 0; + + // Update the counter + activeChannels++; + + return true; +} + +inline bool InternalADC::addRegularChannel(Channel channel) +{ + // Check active channels number + if (activeChannels >= 16) + { + return false; + } + + // Add the channel to the sequence + volatile uint32_t* sqrPtr; + switch (activeChannels % 6) + { + case 1: + sqrPtr = &(ADCx.SQR3); + break; + case 2: + sqrPtr = &(ADCx.SQR2); + break; + default: + sqrPtr = &(ADCx.SQR1); + } + *sqrPtr = channel << ((activeChannels % 6) * 5); + + // Update the channels number in the register + ADCx.SQR1 |= activeChannels << 20; + + // Save the index of the channel in the ADC's regular sequence + indexMap[channel] = activeChannels; + + // Update the counter + activeChannels++; + + return true; +} + +inline void InternalADC::setChannelSampleTime(Channel channel, + SampleTime sampleTime) +{ + volatile uint32_t* smprPtr; + if (channel <= 9) + { + smprPtr = &(ADCx.SMPR2); + } + else + { + smprPtr = &(ADCx.SMPR1); + } + *smprPtr = sampleTime << (channel * 3); +} + +inline void InternalADC::enableADCClock() +{ + miosix::FastInterruptDisableLock dLock; + + if (&ADCx == ADC1) + { + RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; + } + else if (&ADCx == ADC2) + { + RCC->APB2ENR |= RCC_APB2ENR_ADC2EN; + } + else if (&ADCx == ADC3) + { + RCC->APB2ENR |= RCC_APB2ENR_ADC3EN; + } + + RCC_SYNC(); +} + +inline void InternalADC::disableADCClock() +{ + miosix::FastInterruptDisableLock dLock; + + if (&ADCx == ADC1) + { + RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN; + } + else if (&ADCx == ADC2) + { + RCC->APB2ENR &= ~RCC_APB2ENR_ADC2EN; + } + else if (&ADCx == ADC3) + { + RCC->APB2ENR &= ~RCC_APB2ENR_ADC3EN; + } +} \ No newline at end of file diff --git a/src/shared/drivers/adc/InternalADC/InternalADC.h b/src/shared/drivers/adc/InternalADC/InternalADC.h new file mode 100644 index 0000000000000000000000000000000000000000..0343176c5254240de174ca3ac1345768c38c053d --- /dev/null +++ b/src/shared/drivers/adc/InternalADC/InternalADC.h @@ -0,0 +1,181 @@ +/* Copyright (c) 2015-2020 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Luca Erbetta, Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include <miosix.h> + +#include "InternalADCData.h" +#include "sensors/Sensor.h" + +/** + * @brief Driver for stm32 internal ADC + * + * Allows conversions on multiple channels with per-channel sample time. + * + * The driver configures the ADC by itself, including the clock. + * + * This driver implements the following ADC's features: + * - injected channels: a list of up to 4 channels that can be converted + * - scan mode with regular channels: up to 16 channels converted in the same + * sequence as they are enabled + * - DMA: Used in scan mode to transfer data from the peripheral data register + * + * When you don't use the DMA, the driver works with the injected channels. + * When a conversion is started the values are stored in 4 specific data + * registes, whithout the need to use DMA. + * + * If you need more than 4 channels you must use DMA. When the regular group + * of channels is converted, the data will be stored sequentially in a single + * data register and DMA is needed to save the value between each conversion in + * another memory location. + * + * When using DMA the driver will configure only the memory addresses, you must + * configure the stream properly! + * + * Examples of how to use the internal ADC driver and set up the DMA stream + * can be found in the test files (test-internal-adc*). + * + * The ADC has also other features as value offsets for injected channels, + * interrupts (such as dma transfer and watchdog), conversion triggering with + * timers and multi adc mode. + * This features are not implemented to keep the driver simple. + */ +class InternalADC : public Sensor<ADCData> +{ +public: + /** + * @brief ADC channels enumeration + */ + enum Channel : uint8_t + { + CH0 = 0, + CH1, + CH2, + CH3, + CH4, + CH5, + CH6, + CH7, + CH8, + CH9, + CH10, + CH11, + CH12, + CH13, + CH14, + CH15, + CH_NUM + }; + + /** + * @brief Conversion sample time. See reference manual. + */ + enum SampleTime : uint8_t + { + CYCLES_3 = 0x0, + CYCLES_15 = 0x1, + CYCLES_28 = 0x2, + CYCLES_56 = 0x3, + CYCLES_84 = 0x4, + CYCLES_112 = 0x5, + CYCLES_144 = 0x6, + CYCLES_480 = 0x7 + }; + + InternalADC(ADC_TypeDef& ADCx_, const float V_SUPPLY_ = 5.0, + const bool isUsingDMA_ = false, + DMA_Stream_TypeDef* DMAx_ = DMA1_Stream0); + + ~InternalADC(); + + /** + * @brief ADC Initialization + * + * The ADC clock must be set before hand as well as GPIO configuration + * and DMA if used. Also the clock for the analog circuitry should be set + * accordingly to the device datasheet. + */ + bool init() override; + + bool enableChannel(Channel channel); + + bool enableChannel(Channel channel, SampleTime sampleTime); + + ADCData getVoltage(Channel channel); + + bool selfTest() override; + + ADCData sampleImpl() override; + +private: + inline void resetRegisters(); + + inline void startInjectedConversion(); + + inline void startRegularConversion(); + + inline bool addInjectedChannel(Channel channel); + + inline bool addRegularChannel(Channel channel); + + inline void setChannelSampleTime(Channel channel, SampleTime sampleTime); + + inline void enableADCClock(); + + inline void disableADCClock(); + + ADC_TypeDef& ADCx; + const float V_SUPPLY = 5.0; + + uint8_t activeChannels = 0; + uint64_t timestamp; + + // Raw value used by DMA + uint16_t values[CH_NUM] = {}; + + // Maps the channel number with the index in the ADC's regular sequence + int8_t indexMap[CH_NUM]; + + /** + * @brief Determines whether to use regular channels or injected channels + * + * We'll use up to 4 injected channel by dafult and up to 16 channels when + * using DMA. + * + * The differantiation is necessary because whitout DMA it is much more + * simplier to use injected channel for multichannel readings. Otherwise we + * would need to handle each channel's end of conversion interrupt or go + * through + */ + const bool isUsingDMA; + DMA_Stream_TypeDef* DMAx_Streamx; + DMA_TypeDef* DMAx = DMA1; + uint8_t streamNum = 0; + uint32_t transferCompleteMask; + uint32_t transferErrorMask; + volatile uint32_t* statusReg; + volatile uint32_t* clearFlagReg; + + static constexpr int INJECTED_CHANNEL_N = 4; + static constexpr int RESOLUTION = 4096; ///< 12 bits +}; \ No newline at end of file diff --git a/src/shared/drivers/adc/InternalADC/InternalADCData.h b/src/shared/drivers/adc/InternalADC/InternalADCData.h new file mode 100644 index 0000000000000000000000000000000000000000..5aa751b3bf11801e6b27670471fea339337aa9e6 --- /dev/null +++ b/src/shared/drivers/adc/InternalADC/InternalADCData.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct InternalADCData : public ADCData +{ + InternalADCData() : ADCData{0, 0, 0.0} {} + + InternalADCData(uint64_t t, uint8_t channel_id, float voltage) + : ADCData{t, channel_id, voltage} + { + } + + static std::string header() { return "adc_timestamp,channel_id,voltage\n"; } + + void print(std::ostream& os) const + { + os << adc_timestamp << "," << (int)channel_id << "," << voltage << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/drivers/canbus/CanBus.cpp b/src/shared/drivers/canbus/CanBus.cpp index a57cfec668c55c4444c061ab9398992b09621fbc..d6c6bd89ecd8dfb6d05bd6aab440fb12cc7fa100 100644 --- a/src/shared/drivers/canbus/CanBus.cpp +++ b/src/shared/drivers/canbus/CanBus.cpp @@ -1,7 +1,5 @@ -/* CAN-Bus Driver - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Alain Carlucci +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 @@ -32,6 +30,7 @@ TODO: */ #include "CanBus.h" + #include "CanManager.h" #include "CanUtils.h" @@ -41,7 +40,8 @@ using std::set; // Transmit mailbox request #define TMIDxR_TXRQ ((uint32_t)0x00000001) -CanBus::CanBus(CAN_TypeDef *bus, CanManager *manager, const int can_id, CanDispatcher dispatcher) +CanBus::CanBus(CAN_TypeDef *bus, CanManager *manager, const int can_id, + CanDispatcher dispatcher) : CANx(bus), manager(manager), id(can_id), dispatchMessage(dispatcher) { this->canSetup(); @@ -49,8 +49,8 @@ CanBus::CanBus(CAN_TypeDef *bus, CanManager *manager, const int can_id, CanDispa } /** -* Funzione eseguita dall'ActiveObject per ricevere messaggi -*/ + * Funzione eseguita dall'ActiveObject per ricevere messaggi + */ void CanBus::rcvFunction() { while (!should_stop) @@ -60,7 +60,7 @@ void CanBus::rcvFunction() rcvQueue.waitUntilNotEmpty(); // blocks rcvQueue.get(message); - TRACE("[CanBus] message received\n"); + LOG_DEBUG(logger, "Message received"); /* Check message */ uint32_t filter_id; @@ -72,7 +72,7 @@ void CanBus::rcvFunction() if (filter_id >= (uint32_t)CanManager::filter_max_id) { - TRACE("[CanBus] Unsupported message\n"); + LOG_ERR(logger, "Unsupported message"); continue; } @@ -84,7 +84,7 @@ void CanBus::rcvFunction() // Update status Lock<FastMutex> l(statusMutex); status.n_rcv++; - status.last_rcv = (uint8_t)filter_id; + status.last_rcv = (uint8_t)filter_id; status.last_rcv_ts = miosix::getTick(); } @@ -92,7 +92,7 @@ void CanBus::rcvFunction() dispatchMessage(message, status); } - TRACE("Canbus thread exit\n"); + LOG_DEBUG(logger, "Thread exit"); } /* @@ -169,7 +169,7 @@ bool CanBus::send(uint16_t id, const uint8_t *message, uint8_t len) // Update status Lock<FastMutex> l(statusMutex); status.n_sent++; - status.last_sent = (uint8_t)id; + status.last_sent = (uint8_t)id; status.last_sent_ts = miosix::getTick(); } @@ -231,8 +231,8 @@ void CanBus::canSetup() * => 42M / (14 * 6) = 500kBit/s */ // Synchronization with jump (CAN_BRT SJW) - uint8_t CAN_SJW = CAN_SJW_1tq; - // Bit Segment 1 = sample point (CAN_BRT TS1) + uint8_t CAN_SJW = CAN_SJW_1tq; + // Bit Segment 1 = sample point (CAN_BRT TS1) uint32_t CAN_BS1 = CAN_BS1_6tq; // Bit Segment 2 = transmit point (CAN_BRT TS2) uint32_t CAN_BS2 = CAN_BS2_7tq; diff --git a/src/shared/drivers/canbus/CanBus.h b/src/shared/drivers/canbus/CanBus.h index 6e281166d31249c786f7301b0b65d85a2edf0f13..7d2e71911fb284ea75607285ffb6ea3fb6ce2269 100644 --- a/src/shared/drivers/canbus/CanBus.h +++ b/src/shared/drivers/canbus/CanBus.h @@ -1,7 +1,5 @@ -/* CAN-Bus Driver - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Alain Carlucci +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 @@ -27,10 +25,12 @@ #include <ActiveObject.h> #include <Common.h> +#include <diagnostic/PrintLogger.h> + #include "CanUtils.h" /* Alias for the function to be executed on receive */ -using CanDispatcher = std::function<void(CanMsg,CanStatus)>; +using CanDispatcher = std::function<void(CanMsg, CanStatus)>; class CanManager; @@ -45,15 +45,16 @@ public: * @param bus the physical pins to be used * @param manager who created this bus object * @param can_id the id of the currently created canbus (CAN1, CAN2...) - * @param dispatcher function to be executed on receive + * @param dispatcher function to be executed on receive */ - CanBus(CAN_TypeDef* bus, CanManager* manager, const int can_id, CanDispatcher dispatcher); + CanBus(CAN_TypeDef* bus, CanManager* manager, const int can_id, + CanDispatcher dispatcher); ~CanBus() {} /* Receiving function executed by the ActiveObject */ void rcvFunction(); - /* Sender function + /* Sender function * @param id Id of the message (aka topic) * @para message message as byte array * @param len length of the message (max 8 bytes, truncated if grater) @@ -69,7 +70,7 @@ public: { should_stop = true; - //Wake the thread posting an empty can msg + // Wake the thread posting an empty can msg CanMsg empty_msg; empty_msg.StdId = 0xFFFFFFFF; empty_msg.ExtId = 0xFFFFFFFF; @@ -78,8 +79,6 @@ public: ActiveObject::stop(); } - - /* Rule of five */ CanBus(const CanBus&) = delete; CanBus(const CanBus&&) = delete; @@ -90,9 +89,7 @@ public: protected: /* Inherited from ActiveObject: executes the receiving function */ - void run() override { - rcvFunction(); - } + void run() override { rcvFunction(); } private: void canSetup(); @@ -106,6 +103,8 @@ private: CanDispatcher dispatchMessage; CanStatus status; + + PrintLogger logger = Logging::getLogger("canbus"); }; #endif /* CANBUS_H */ diff --git a/src/shared/drivers/canbus/CanInterrupt.cpp b/src/shared/drivers/canbus/CanInterrupt.cpp index aac8ecd04b8bbeba217d40537915134d534ace91..3e9773393615a9db9d538c2cc397214e8969a8dc 100644 --- a/src/shared/drivers/canbus/CanInterrupt.cpp +++ b/src/shared/drivers/canbus/CanInterrupt.cpp @@ -1,7 +1,5 @@ -/* CAN-Bus Driver - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Alain Carlucci +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/canbus/CanManager.cpp b/src/shared/drivers/canbus/CanManager.cpp index c2627a9ae587afd1d7bfc2dfda0d9048b77d82d0..6278ea231997f9f60b7076bbe1a152fdbfb14f0b 100644 --- a/src/shared/drivers/canbus/CanManager.cpp +++ b/src/shared/drivers/canbus/CanManager.cpp @@ -1,7 +1,5 @@ -/* CAN-Bus Driver - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Alain Carlucci +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/canbus/CanManager.h b/src/shared/drivers/canbus/CanManager.h index 6aa9c980c33d4949d2284e36f8ae4167fc5cf665..4b6fc0a01f507384a640a3917c1d5cff4668ea7d 100644 --- a/src/shared/drivers/canbus/CanManager.h +++ b/src/shared/drivers/canbus/CanManager.h @@ -1,7 +1,5 @@ -/* CAN-Bus Driver - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Alain Carlucci +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 @@ -125,10 +123,18 @@ public: { // Disable interrupts // TODO: Only disable the interrupts that we enabled +#ifdef CAN1_RX0_IRQn NVIC_DisableIRQ(CAN1_RX0_IRQn); +#endif +#ifdef CAN1_RX1_IRQn NVIC_DisableIRQ(CAN1_RX1_IRQn); +#endif +#ifdef CAN2_RX0_IRQn NVIC_DisableIRQ(CAN2_RX0_IRQn); +#endif +#ifdef CAN2_RX1_IRQn NVIC_DisableIRQ(CAN2_RX1_IRQn); +#endif global_bus_ptr[0] = NULL; global_bus_ptr[1] = NULL; diff --git a/src/shared/drivers/canbus/CanUtils.h b/src/shared/drivers/canbus/CanUtils.h index a55c52459c489141c9eae06a534950a4ec208ad1..ebdefec449976aa27b7f3119f3ca2e403b81e239 100644 --- a/src/shared/drivers/canbus/CanUtils.h +++ b/src/shared/drivers/canbus/CanUtils.h @@ -1,7 +1,5 @@ -/* CAN-Bus Driver - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Alain Carlucci +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/PacketBuffer.cpp b/src/shared/drivers/ethernet/PacketBuffer.cpp index 9766d9d3e0bd6e46189f05dff2af0f1e1b8271f0..aad856f35b0eabcf06e80ffe30b6f230729428fe 100644 --- a/src/shared/drivers/ethernet/PacketBuffer.cpp +++ b/src/shared/drivers/ethernet/PacketBuffer.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/PacketBuffer.h b/src/shared/drivers/ethernet/PacketBuffer.h index 92a36823c81864ed6235da84789e4fb7fa454ac1..738573708633d9e46e3e0c060a46b5e5d38382a3 100644 --- a/src/shared/drivers/ethernet/PacketBuffer.h +++ b/src/shared/drivers/ethernet/PacketBuffer.h @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/UdpManager.cpp b/src/shared/drivers/ethernet/UdpManager.cpp index f4751d6b0f2e52c0b6f13a5f9a98b27b765dab73..2f562144d936a773bc177bd9c71145be4fe477bb 100644 --- a/src/shared/drivers/ethernet/UdpManager.cpp +++ b/src/shared/drivers/ethernet/UdpManager.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/UdpManager.h b/src/shared/drivers/ethernet/UdpManager.h index 3a2c790ae73f786254545a3f0b6bb6038d5a80f6..f3207bd62ab7e390d25c90b364c4bde8ac0d7d8c 100644 --- a/src/shared/drivers/ethernet/UdpManager.h +++ b/src/shared/drivers/ethernet/UdpManager.h @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/W5200/spi_impl.cpp b/src/shared/drivers/ethernet/W5200/spi_impl.cpp index 42be4428ae27c94b089d943eaf3b9b80fb06284a..5585500554449111b801ff6bcf40b0541961a42d 100644 --- a/src/shared/drivers/ethernet/W5200/spi_impl.cpp +++ b/src/shared/drivers/ethernet/W5200/spi_impl.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Silvano Seva + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/W5200/spi_impl.h b/src/shared/drivers/ethernet/W5200/spi_impl.h index fa21a9fc8b2fd85d9924a9721c003af0ce919d45..d82d961c24d0190e535aacf5bf4b353d6ff91a64 100644 --- a/src/shared/drivers/ethernet/W5200/spi_impl.h +++ b/src/shared/drivers/ethernet/W5200/spi_impl.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Silvano Seva + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/W5200/w5200.cpp b/src/shared/drivers/ethernet/W5200/w5200.cpp index fedcbf88f17ca919c16a3dc8fe20ba7ebf0b2f3e..e9328c0cf5bf8c167298d0d6648cee0a52576578 100644 --- a/src/shared/drivers/ethernet/W5200/w5200.cpp +++ b/src/shared/drivers/ethernet/W5200/w5200.cpp @@ -1,7 +1,5 @@ -/* Driver class for Wiznet W5200 chip - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Silvano Seva +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/W5200/w5200.h b/src/shared/drivers/ethernet/W5200/w5200.h index bb01a77bab32f1461b088573e2add225472ace5c..d7e024941abd1468b8a3c6599cf8ed9ee1f9c4e0 100644 --- a/src/shared/drivers/ethernet/W5200/w5200.h +++ b/src/shared/drivers/ethernet/W5200/w5200.h @@ -1,7 +1,5 @@ -/* Driver class for Wiznet W5200 chip - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Silvano Seva +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/W5200/w5200_defs.h b/src/shared/drivers/ethernet/W5200/w5200_defs.h index d07638d69d5296d54cffe93634bd1720c69fedbb..3455191efff0cee0afe0a977db3255e182527e7b 100644 --- a/src/shared/drivers/ethernet/W5200/w5200_defs.h +++ b/src/shared/drivers/ethernet/W5200/w5200_defs.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Silvano Seva + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/WatchdogTimer.cpp b/src/shared/drivers/ethernet/WatchdogTimer.cpp index 2074be2ec5123d7e67cb74c5bbb66e907f252741..3868ac03bf142c5462207ef720509bec0d8b5476 100644 --- a/src/shared/drivers/ethernet/WatchdogTimer.cpp +++ b/src/shared/drivers/ethernet/WatchdogTimer.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/WatchdogTimer.h b/src/shared/drivers/ethernet/WatchdogTimer.h index 19fb921f8b587bfacb1711941930005dfa8c9ead..f170a92fec0991941f01e4fa44a22adb2d1908d8 100644 --- a/src/shared/drivers/ethernet/WatchdogTimer.h +++ b/src/shared/drivers/ethernet/WatchdogTimer.h @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/ethernet/packet.h b/src/shared/drivers/ethernet/packet.h index aca1bfc8b2ecdf0673d84231c29b9bac1539a76e..9a7479848db468d2041fbc5f8e942eb4d81cfe08 100644 --- a/src/shared/drivers/ethernet/packet.h +++ b/src/shared/drivers/ethernet/packet.h @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/gamma868/Gamma868.cpp b/src/shared/drivers/gamma868/Gamma868.cpp index e3cf3cdec90d324485a6ba9144c483bbfaa2c2c5..30476976ccbaed5a16d758a686f7c30448a5336b 100644 --- a/src/shared/drivers/gamma868/Gamma868.cpp +++ b/src/shared/drivers/gamma868/Gamma868.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -28,15 +28,15 @@ using namespace miosix; * to be passed to the object in order to communicate with the device. * */ -Gamma868::Gamma868(const char *serialPath, const uint16_t multiplier) - : send_timeout_multiplier(multiplier), conf{}, gammaSwitch{} +Gamma868::Gamma868(const char* serialPath, const uint16_t multiplier) + : send_timeout_multiplier(multiplier), conf{}, gammaSwitch{} { conf_enabled = false; - fd = open(serialPath, O_RDWR); + fd = open(serialPath, O_RDWR); if (fd < 0) - TRACE("[Gamma868] Cannot open %s\n", serialPath); - // TODO: Signal an error if we fail here? + LOG_ERR(logger, "Cannot open {}", serialPath); + // TODO: Signal an error if we fail here? } /* @@ -44,20 +44,18 @@ Gamma868::Gamma868(const char *serialPath, const uint16_t multiplier) * to be passed to the object in order to communicate with the device. * */ -Gamma868::Gamma868(const char *serialPath, GpioPin* lrn_pin, const uint16_t multiplier) - : Gamma868(serialPath, multiplier) +Gamma868::Gamma868(const char* serialPath, GpioPin* lrn_pin, + const uint16_t multiplier) + : Gamma868(serialPath, multiplier) { - gammaSwitch = lrn_pin; + gammaSwitch = lrn_pin; conf_enabled = true; gammaSwitch->mode(Mode::OUTPUT); gammaSwitch->high(); } -Gamma868::~Gamma868() -{ - close(fd); -} +Gamma868::~Gamma868() { close(fd); } /* * Immediately sends command (blocking). @@ -74,7 +72,7 @@ bool Gamma868::send(uint8_t* pkt, size_t pkt_len) */ ssize_t Gamma868::receive(uint8_t* pkt, size_t pkt_len) { - if(pkt_len > 0) + if (pkt_len > 0) return read(fd, pkt, 1); else return 0; @@ -85,13 +83,16 @@ ssize_t Gamma868::receive(uint8_t* pkt, size_t pkt_len) */ GammaConf Gamma868::readConfig() { - if(!conf_enabled) { + if (!conf_enabled) + { conf.is_valid = false; - } else { + } + else + { enterLearnMode(); bool ok = updateConfig(); - if(!ok) + if (!ok) conf.is_valid = false; exitLearnMode(); @@ -108,7 +109,7 @@ bool Gamma868::configure(const GammaConf& newConf) { bool retValue; - if(!conf_enabled) + if (!conf_enabled) return false; enterLearnMode(); @@ -117,19 +118,21 @@ bool Gamma868::configure(const GammaConf& newConf) memcpy(&oldConf, &conf, sizeof(GammaConf)); // Write the new configuration - TRACE("[Gamma868] Writing new configuration.. \n"); + LOG_DEBUG(logger, "Writing new configuration..."); writeConfig(newConf); // Check the current configuration bool ok = updateConfig(); - if(ok && conf == newConf){ - TRACE("[Gamma868] Config Ok\n"); + if (ok && conf == newConf) + { + LOG_DEBUG(logger, "Config ok"); retValue = true; } - else{ - TRACE("[Gamma868] Config error\n"); - memcpy(&conf, &oldConf, sizeof(GammaConf)); // rollback + else + { + LOG_ERR(logger, "Config error"); + memcpy(&conf, &oldConf, sizeof(GammaConf)); // rollback retValue = false; } @@ -146,7 +149,7 @@ void Gamma868::enterLearnMode() // TODO: switch baudrate to 9600 // Enter learn mode - TRACE("[Gamma868] Entering learn mode ... \n"); + LOG_DEBUG(logger, "Entering learn mode..."); gammaSwitch->low(); // Wait 5 seconds @@ -162,7 +165,7 @@ void Gamma868::exitLearnMode() { // TODO: switch baudrate according to configuration - TRACE("[Gamma868] Exiting learn mode. \n"); + LOG_DEBUG(logger, "Exiting learn mode"); write(fd, "#Q", 2); } @@ -203,7 +206,7 @@ void Gamma868::writeConfig(const GammaConf& newConf) */ bool Gamma868::updateConfig() { - if(!conf_enabled) + if (!conf_enabled) return false; gamma_msg msg; @@ -213,9 +216,8 @@ bool Gamma868::updateConfig() read(fd, &(msg.buf), sizeof(gamma_msg)); // Check values validity - if( msg.conf.lora_mode >= LAST_SF - || msg.conf.lora_power >= LAST_POWER - || msg.conf.baudrate >= LAST_BAUDRATE ) + if (msg.conf.lora_mode >= LAST_SF || msg.conf.lora_power >= LAST_POWER || + msg.conf.baudrate >= LAST_BAUDRATE) { return false; } @@ -225,7 +227,7 @@ bool Gamma868::updateConfig() // Addresses memcpy(&conf.local_addr, msg.conf.local_addr, 3); - memcpy(&conf.dest_addr, msg.conf.dest_addr, 3); + memcpy(&conf.dest_addr, msg.conf.dest_addr, 3); // LoRa values conf.lora_sf = static_cast<GammaSF>(msg.conf.lora_mode); @@ -244,6 +246,6 @@ void Gamma868::waitForOk() { char reply[3]; read(fd, reply, 3); - TRACE("[Gamma868] device replied: %s\n", reply); + LOG_DEBUG(logger, "Device replied: {}", reply); Thread::sleep(100); } \ No newline at end of file diff --git a/src/shared/drivers/gamma868/Gamma868.h b/src/shared/drivers/gamma868/Gamma868.h index e04ea39d4609d5f0bd59538ed9a389872b2b391c..40db04a673c96f5c72b307352a3e000f84a050c4 100644 --- a/src/shared/drivers/gamma868/Gamma868.h +++ b/src/shared/drivers/gamma868/Gamma868.h @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -24,10 +24,11 @@ #define GAMMA868_H #include <Common.h> +#include <diagnostic/PrintLogger.h> +#include <drivers/Transceiver.h> #include <fcntl.h> #include "GammaTypes.h" -#include <drivers/Transceiver.h> class Gamma868 : public Transceiver { @@ -37,7 +38,8 @@ public: * Create a Gamma868 object using the given path as the serial port to use. * @param serialPath Name of the serial port (es. /dev/tty) * @param multiplier If defined, after each send the send function - * will block for multiplier*n_bytes_sent milliseconds. + * will block for multiplier*n_bytes_sent + * milliseconds. */ explicit Gamma868(const char* serialPath, const uint16_t multiplier = 0); @@ -46,53 +48,56 @@ public: * @param serialPath Name of the serial port (es. /dev/tty) * @param lrn_pin pin connected to the Learn Mode pin of the Gamma * @param multiplier If defined, after each send the send() function - * will block for multiplier*n_bytes_sent milliseconds. + * will block for multiplier*n_bytes_sent + * milliseconds. */ - Gamma868(const char* serialPath, miosix::GpioPin* lrn_pin, const uint16_t multiplier = 0); + Gamma868(const char* serialPath, miosix::GpioPin* lrn_pin, + const uint16_t multiplier = 0); ~Gamma868(); /* * Send a message through the serial port to the gamma868 module (blocking). - * @param pkt Pointer to the packet (needs to be at least pkt_len bytes). + * @param pkt Pointer to the packet (needs to be at least + * pkt_len bytes). * @param pkt_len Lenght of the packet to be sent. * @return True if the message was sent correctly. */ bool send(uint8_t* pkt, size_t pkt_len) override; /* - * Receive a message through the serial port to the gamma868 module (blocking). - * @param pkt Pointer to the buffer (needs to be at least pkt_len bytes). + * Receive a message through the serial port to the gamma868 module + * (blocking). + * @param pkt Pointer to the buffer (needs to be at least + * pkt_len bytes). * @param pkt_len Maximum lenght of the packet to be received. * @return Size of the data received or -1 if failure */ ssize_t receive(uint8_t* pkt, size_t pkt_len) override; /* - * Set a new configuration to the gamma868 module. Can be done only if the + * Set a new configuration to the gamma868 module. Can be done only if the * learn pin has been specified in the constructor. * @return True if the configuration was set correctly. */ - bool configure(const GammaConf& newConf); + bool configure(const GammaConf& newConf); /* - * Reads the configuration from the device, updates the internal configuration - * variable and returns it. - * Meaningful only if the learn pin has been specified in the constructor. + * Reads the configuration from the device, updates the internal + * configuration variable and returns it. Meaningful only if the learn pin + * has been specified in the constructor. * @return The current configuration. */ GammaConf readConfig(); /* - * Immediately returns the value of the configuration variable, without reading it from - * the device. + * Immediately returns the value of the configuration variable, without + * reading it from the device. * @return The current configuration. - * @warning check the is_valid bit to see if the returned configuration is meaningful. + * @warning check the is_valid bit to see if the returned configuration + * is meaningful. */ - GammaConf getConfig() - { - return conf; - } + GammaConf getConfig() { return conf; } private: int fd; @@ -108,6 +113,8 @@ private: void writeConfig(const GammaConf& conf); bool updateConfig(); void waitForOk(); + + PrintLogger logger = Logging::getLogger("gamma868"); }; #endif /* GAMMA868_H */ \ No newline at end of file diff --git a/src/shared/drivers/gamma868/GammaTypes.h b/src/shared/drivers/gamma868/GammaTypes.h index e5a63ac5c915f2e8bf36db360da64185584b5d1e..af07d445734e72820b8cf86ec67c23bfd6de77c3 100644 --- a/src/shared/drivers/gamma868/GammaTypes.h +++ b/src/shared/drivers/gamma868/GammaTypes.h @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/gps/nmea/nmea.c b/src/shared/drivers/gps/nmea/nmea.c new file mode 100644 index 0000000000000000000000000000000000000000..68d659f5f85a3ff4a6644140db615a5fd3684f03 --- /dev/null +++ b/src/shared/drivers/gps/nmea/nmea.c @@ -0,0 +1,613 @@ +/* Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com> + * This program is free software. It comes without any warranty, to the extent + * permitted by applicable law. You can redistribute it and/or modify it under + * the terms of the Do What The Fuck You Want To Public License, Version 2, as + * published by Sam Hocevar. See the COPYING file for more details. + */ + +#include <drivers/gps/nmea/nmea.h> + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <stdarg.h> +#include <time.h> + +#define boolstr(s) ((s) ? "true" : "false") + +static int hex2int(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; +} + +uint8_t minmea_checksum(const char *sentence) +{ + // Support senteces with or without the starting dollar sign. + if (*sentence == '$') + sentence++; + + uint8_t checksum = 0x00; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while (*sentence && *sentence != '*') + checksum ^= *sentence++; + + return checksum; +} + +bool minmea_check(const char *sentence, bool strict) +{ + uint8_t checksum = 0x00; + + // Sequence length is limited. + if (strlen(sentence) > MINMEA_MAX_LENGTH + 3) + return false; + + // A valid sentence starts with "$". + if (*sentence++ != '$') + return false; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while (*sentence && *sentence != '*' && isprint((unsigned char) *sentence)) + checksum ^= *sentence++; + + // If checksum is present... + if (*sentence == '*') { + // Extract checksum. + sentence++; + int upper = hex2int(*sentence++); + if (upper == -1) + return false; + int lower = hex2int(*sentence++); + if (lower == -1) + return false; + int expected = upper << 4 | lower; + + // Check for checksum mismatch. + if (checksum != expected) + return false; + } else if (strict) { + // Discard non-checksummed frames in strict mode. + return false; + } + + // The only stuff allowed at this point is a newline. + if (*sentence && strcmp(sentence, "\n") && strcmp(sentence, "\r\n")) + return false; + + return true; +} + +static inline bool minmea_isfield(char c) { + return isprint((unsigned char) c) && c != ',' && c != '*'; +} + +bool minmea_scan(const char *sentence, const char *format, ...) +{ + bool result = false; + bool optional = false; + va_list ap; + va_start(ap, format); + + const char *field = sentence; +#define next_field() \ + do { \ + /* Progress to the next field. */ \ + while (minmea_isfield(*sentence)) \ + sentence++; \ + /* Make sure there is a field there. */ \ + if (*sentence == ',') { \ + sentence++; \ + field = sentence; \ + } else { \ + field = NULL; \ + } \ + } while (0) + + while (*format) { + char type = *format++; + + if (type == ';') { + // All further fields are optional. + optional = true; + continue; + } + + if (!field && !optional) { + // Field requested but we ran out if input. Bail out. + goto parse_error; + } + + switch (type) { + case 'c': { // Single character field (char). + char value = '\0'; + + if (field && minmea_isfield(*field)) + value = *field; + + *va_arg(ap, char *) = value; + } break; + + case 'd': { // Single character direction field (int). + int value = 0; + + if (field && minmea_isfield(*field)) { + switch (*field) { + case 'N': + case 'E': + value = 1; + break; + case 'S': + case 'W': + value = -1; + break; + default: + goto parse_error; + } + } + + *va_arg(ap, int *) = value; + } break; + + case 'f': { // Fractional value with scale (struct minmea_float). + int sign = 0; + int_least32_t value = -1; + int_least32_t scale = 0; + + if (field) { + while (minmea_isfield(*field)) { + if (*field == '+' && !sign && value == -1) { + sign = 1; + } else if (*field == '-' && !sign && value == -1) { + sign = -1; + } else if (isdigit((unsigned char) *field)) { + int digit = *field - '0'; + if (value == -1) + value = 0; + if (value > (INT_LEAST32_MAX-digit) / 10) { + /* we ran out of bits, what do we do? */ + if (scale) { + /* truncate extra precision */ + break; + } else { + /* integer overflow. bail out. */ + goto parse_error; + } + } + value = (10 * value) + digit; + if (scale) + scale *= 10; + } else if (*field == '.' && scale == 0) { + scale = 1; + } else if (*field == ' ') { + /* Allow spaces at the start of the field. Not NMEA + * conformant, but some modules do this. */ + if (sign != 0 || value != -1 || scale != 0) + goto parse_error; + } else { + goto parse_error; + } + field++; + } + } + + if ((sign || scale) && value == -1) + goto parse_error; + + if (value == -1) { + /* No digits were scanned. */ + value = 0; + scale = 0; + } else if (scale == 0) { + /* No decimal point. */ + scale = 1; + } + if (sign) + value *= sign; + + *va_arg(ap, struct minmea_float *) = (struct minmea_float) {value, scale}; + } break; + + case 'i': { // Integer value, default 0 (int). + int value = 0; + + if (field) { + char *endptr; + value = strtol(field, &endptr, 10); + if (minmea_isfield(*endptr)) + goto parse_error; + } + + *va_arg(ap, int *) = value; + } break; + + case 's': { // String value (char *). + char *buf = va_arg(ap, char *); + + if (field) { + while (minmea_isfield(*field)) + *buf++ = *field++; + } + + *buf = '\0'; + } break; + + case 't': { // NMEA talker+sentence identifier (char *). + // This field is always mandatory. + if (!field) + goto parse_error; + + if (field[0] != '$') + goto parse_error; + int f=0; + for (f=0; f<5; f++) + if (!minmea_isfield(field[1+f])) + goto parse_error; + + char *buf = va_arg(ap, char *); + memcpy(buf, field+1, 5); + buf[5] = '\0'; + } break; + + case 'D': { // Date (int, int, int), -1 if empty. + struct minmea_date *date = va_arg(ap, struct minmea_date *); + + int d = -1, m = -1, y = -1; + + if (field && minmea_isfield(*field)) { + // Always six digits. + int f=0; + for (f=0; f<6; f++) + if (!isdigit((unsigned char) field[f])) + goto parse_error; + + char dArr[] = {field[0], field[1], '\0'}; + char mArr[] = {field[2], field[3], '\0'}; + char yArr[] = {field[4], field[5], '\0'}; + d = strtol(dArr, NULL, 10); + m = strtol(mArr, NULL, 10); + y = strtol(yArr, NULL, 10); + } + + date->day = d; + date->month = m; + date->year = y; + } break; + + case 'T': { // Time (int, int, int, int), -1 if empty. + struct minmea_time *time_ = va_arg(ap, struct minmea_time *); + + int h = -1, i = -1, s = -1, u = -1; + + if (field && minmea_isfield(*field)) { + // Minimum required: integer time. + int f=0; + for (f=0; f<6; f++) + if (!isdigit((unsigned char) field[f])) + goto parse_error; + + char hArr[] = {field[0], field[1], '\0'}; + char iArr[] = {field[2], field[3], '\0'}; + char sArr[] = {field[4], field[5], '\0'}; + h = strtol(hArr, NULL, 10); + i = strtol(iArr, NULL, 10); + s = strtol(sArr, NULL, 10); + field += 6; + + // Extra: fractional time. Saved as microseconds. + if (*field++ == '.') { + int value = 0; + int scale = 1000000; + while (isdigit((unsigned char) *field) && scale > 1) { + value = (value * 10) + (*field++ - '0'); + scale /= 10; + } + u = value * scale; + } else { + u = 0; + } + } + + time_->hours = h; + time_->minutes = i; + time_->seconds = s; + time_->microseconds = u; + } break; + + case '_': { // Ignore the field. + } break; + + default: { // Unknown. + goto parse_error; + } break; + } + + next_field(); + } + + result = true; + +parse_error: + va_end(ap); + return result; +} + +bool minmea_talker_id(char talker[3], const char *sentence) +{ + char type[6]; + if (!minmea_scan(sentence, "t", type)) + return false; + + talker[0] = type[0]; + talker[1] = type[1]; + talker[2] = '\0'; + + return true; +} + +enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict) +{ + if (!minmea_check(sentence, strict)) + return MINMEA_INVALID; + + char type[6]; + if (!minmea_scan(sentence, "t", type)) + return MINMEA_INVALID; + + if (!strcmp(type+2, "RMC")) + return MINMEA_SENTENCE_RMC; + if (!strcmp(type+2, "GGA")) + return MINMEA_SENTENCE_GGA; + if (!strcmp(type+2, "GSA")) + return MINMEA_SENTENCE_GSA; + if (!strcmp(type+2, "GLL")) + return MINMEA_SENTENCE_GLL; + if (!strcmp(type+2, "GST")) + return MINMEA_SENTENCE_GST; + if (!strcmp(type+2, "GSV")) + return MINMEA_SENTENCE_GSV; + if (!strcmp(type+2, "VTG")) + return MINMEA_SENTENCE_VTG; + + return MINMEA_UNKNOWN; +} + +bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence) +{ + // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 + char type[6]; + char validity; + int latitude_direction; + int longitude_direction; + int variation_direction; + if (!minmea_scan(sentence, "tTcfdfdffDfd", + type, + &frame->time, + &validity, + &frame->latitude, &latitude_direction, + &frame->longitude, &longitude_direction, + &frame->speed, + &frame->course, + &frame->date, + &frame->variation, &variation_direction)) + return false; + if (strcmp(type+2, "RMC")) + return false; + + frame->valid = (validity == 'A'); + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + frame->variation.value *= variation_direction; + + return true; +} + +bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence) +{ + // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 + char type[6]; + int latitude_direction; + int longitude_direction; + + if (!minmea_scan(sentence, "tTfdfdiiffcfci_", + type, + &frame->time, + &frame->latitude, &latitude_direction, + &frame->longitude, &longitude_direction, + &frame->fix_quality, + &frame->satellites_tracked, + &frame->hdop, + &frame->altitude, &frame->altitude_units, + &frame->height, &frame->height_units, + &frame->dgps_age)) + return false; + if (strcmp(type+2, "GGA")) + return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence) +{ + // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 + char type[6]; + + if (!minmea_scan(sentence, "tciiiiiiiiiiiiifff", + type, + &frame->mode, + &frame->fix_type, + &frame->sats[0], + &frame->sats[1], + &frame->sats[2], + &frame->sats[3], + &frame->sats[4], + &frame->sats[5], + &frame->sats[6], + &frame->sats[7], + &frame->sats[8], + &frame->sats[9], + &frame->sats[10], + &frame->sats[11], + &frame->pdop, + &frame->hdop, + &frame->vdop)) + return false; + if (strcmp(type+2, "GSA")) + return false; + + return true; +} + +bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence) +{ + // $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$; + char type[6]; + int latitude_direction; + int longitude_direction; + + if (!minmea_scan(sentence, "tfdfdTc;c", + type, + &frame->latitude, &latitude_direction, + &frame->longitude, &longitude_direction, + &frame->time, + &frame->status, + &frame->mode)) + return false; + if (strcmp(type+2, "GLL")) + return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence) +{ + // $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58 + char type[6]; + + if (!minmea_scan(sentence, "tTfffffff", + type, + &frame->time, + &frame->rms_deviation, + &frame->semi_major_deviation, + &frame->semi_minor_deviation, + &frame->semi_major_orientation, + &frame->latitude_error_deviation, + &frame->longitude_error_deviation, + &frame->altitude_error_deviation)) + return false; + if (strcmp(type+2, "GST")) + return false; + + return true; +} + +bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence) +{ + // $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74 + // $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D + // $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75 + // $GPGSV,4,4,13,39,31,170,27*40 + // $GPGSV,4,4,13*7B + char type[6]; + + if (!minmea_scan(sentence, "tiii;iiiiiiiiiiiiiiii", + type, + &frame->total_msgs, + &frame->msg_nr, + &frame->total_sats, + &frame->sats[0].nr, + &frame->sats[0].elevation, + &frame->sats[0].azimuth, + &frame->sats[0].snr, + &frame->sats[1].nr, + &frame->sats[1].elevation, + &frame->sats[1].azimuth, + &frame->sats[1].snr, + &frame->sats[2].nr, + &frame->sats[2].elevation, + &frame->sats[2].azimuth, + &frame->sats[2].snr, + &frame->sats[3].nr, + &frame->sats[3].elevation, + &frame->sats[3].azimuth, + &frame->sats[3].snr + )) { + return false; + } + if (strcmp(type+2, "GSV")) + return false; + + return true; +} + +bool minmea_parse_vtg(struct minmea_sentence_vtg *frame, const char *sentence) +{ + // $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48 + // $GPVTG,156.1,T,140.9,M,0.0,N,0.0,K*41 + // $GPVTG,096.5,T,083.5,M,0.0,N,0.0,K,D*22 + // $GPVTG,188.36,T,,M,0.820,N,1.519,K,A*3F + char type[6]; + char c_true, c_magnetic, c_knots, c_kph, c_faa_mode; + + if (!minmea_scan(sentence, "tfcfcfcfc;c", + type, + &frame->true_track_degrees, + &c_true, + &frame->magnetic_track_degrees, + &c_magnetic, + &frame->speed_knots, + &c_knots, + &frame->speed_kph, + &c_kph, + &c_faa_mode)) + return false; + if (strcmp(type+2, "VTG")) + return false; + // check chars + if (c_true != 'T' || + c_magnetic != 'M' || + c_knots != 'N' || + c_kph != 'K') + return false; + //frame->faa_mode = c_faa_mode; + + return true; +} +/* +int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_) +{ + if (date->year == -1 || time_->hours == -1) + return -1; + + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_year = 2000 + date->year - 1900; + tm.tm_mon = date->month - 1; + tm.tm_mday = date->day; + tm.tm_hour = time_->hours; + tm.tm_min = time_->minutes; + tm.tm_sec = time_->seconds; + + time_t timestamp = timegm(&tm); //See README.md if your system lacks timegm(). + if (timestamp != -1) { + ts->tv_sec = timestamp; + ts->tv_nsec = time_->microseconds * 1000; + return 0; + } else { + return -1; + } +} +*/ +/* vim: set ts=4 sw=4 et: */ diff --git a/src/shared/drivers/gps/nmea/nmea.h b/src/shared/drivers/gps/nmea/nmea.h new file mode 100644 index 0000000000000000000000000000000000000000..afa6aa77c9c97638c824df550db764a898a144ca --- /dev/null +++ b/src/shared/drivers/gps/nmea/nmea.h @@ -0,0 +1,263 @@ +/* Copyright (c) 2014 Skyward Experimental Rocketry + * Author: Kosma Moczek + * + * 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. + */ + +#ifndef MINMEA_H +#define MINMEA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <time.h> +#include <math.h> + +#define MINMEA_MAX_LENGTH 80 + +enum minmea_sentence_id { + MINMEA_INVALID = -1, + MINMEA_UNKNOWN = 0, + MINMEA_SENTENCE_RMC, + MINMEA_SENTENCE_GGA, + MINMEA_SENTENCE_GSA, + MINMEA_SENTENCE_GLL, + MINMEA_SENTENCE_GST, + MINMEA_SENTENCE_GSV, + MINMEA_SENTENCE_VTG, +}; + +struct minmea_float { + int_least32_t value; + int_least32_t scale; +}; + +struct minmea_date { + int day; + int month; + int year; +}; + +struct minmea_time { + int hours; + int minutes; + int seconds; + int microseconds; +}; + +struct minmea_sentence_rmc { + struct minmea_time time; + bool valid; + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_float speed; + struct minmea_float course; + struct minmea_date date; + struct minmea_float variation; +}; + +struct minmea_sentence_gga { + struct minmea_time time; + struct minmea_float latitude; + struct minmea_float longitude; + int fix_quality; + int satellites_tracked; + struct minmea_float hdop; + struct minmea_float altitude; char altitude_units; + struct minmea_float height; char height_units; + int dgps_age; +}; + +enum minmea_gll_status { + MINMEA_GLL_STATUS_DATA_VALID = 'A', + MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V', +}; + +// FAA mode added to some fields in NMEA 2.3. +enum minmea_faa_mode { + MINMEA_FAA_MODE_AUTONOMOUS = 'A', + MINMEA_FAA_MODE_DIFFERENTIAL = 'D', + MINMEA_FAA_MODE_ESTIMATED = 'E', + MINMEA_FAA_MODE_MANUAL = 'M', + MINMEA_FAA_MODE_SIMULATED = 'S', + MINMEA_FAA_MODE_NOT_VALID = 'N', + MINMEA_FAA_MODE_PRECISE = 'P', +}; + +struct minmea_sentence_gll { + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_time time; + char status; + char mode; +}; + +struct minmea_sentence_gst { + struct minmea_time time; + struct minmea_float rms_deviation; + struct minmea_float semi_major_deviation; + struct minmea_float semi_minor_deviation; + struct minmea_float semi_major_orientation; + struct minmea_float latitude_error_deviation; + struct minmea_float longitude_error_deviation; + struct minmea_float altitude_error_deviation; +}; + +enum minmea_gsa_mode { + MINMEA_GPGSA_MODE_AUTO = 'A', + MINMEA_GPGSA_MODE_FORCED = 'M', +}; + +enum minmea_gsa_fix_type { + MINMEA_GPGSA_FIX_NONE = 1, + MINMEA_GPGSA_FIX_2D = 2, + MINMEA_GPGSA_FIX_3D = 3, +}; + +struct minmea_sentence_gsa { + char mode; + int fix_type; + int sats[12]; + struct minmea_float pdop; + struct minmea_float hdop; + struct minmea_float vdop; +}; + +struct minmea_sat_info { + int nr; + int elevation; + int azimuth; + int snr; +}; + +struct minmea_sentence_gsv { + int total_msgs; + int msg_nr; + int total_sats; + struct minmea_sat_info sats[4]; +}; + +struct minmea_sentence_vtg { + struct minmea_float true_track_degrees; + struct minmea_float magnetic_track_degrees; + struct minmea_float speed_knots; + struct minmea_float speed_kph; + enum minmea_faa_mode faa_mode; +}; + +/** + * Calculate raw sentence checksum. Does not check sentence integrity. + */ +uint8_t minmea_checksum(const char *sentence); + +/** + * Check sentence validity and checksum. Returns true for valid sentences. + */ +bool minmea_check(const char *sentence, bool strict); + +/** + * Determine talker identifier. + */ +bool minmea_talker_id(char talker[3], const char *sentence); + +/** + * Determine sentence identifier. + */ +enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict); + +/** + * Scanf-like processor for NMEA sentences. Supports the following formats: + * c - single character (char *) + * d - direction, returned as 1/-1, default 0 (int *) + * f - fractional, returned as value + scale (int *, int *) + * i - decimal, default zero (int *) + * s - string (char *) + * t - talker identifier and type (char *) + * T - date/time stamp (int *, int *, int *) + * Returns true on success. See library source code for details. + */ +bool minmea_scan(const char *sentence, const char *format, ...); + +/* + * Parse a specific type of sentence. Return true on success. + */ +bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence); +bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence); +bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence); +bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence); +bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence); +bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence); +bool minmea_parse_vtg(struct minmea_sentence_vtg *frame, const char *sentence); + +/** + * Convert GPS UTC date/time representation to a UNIX timestamp. + */ +int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_); + +/** + * Rescale a fixed-point value to a different scale. Rounds towards zero. + */ +static inline int_least32_t minmea_rescale(struct minmea_float *f, int_least32_t new_scale) +{ + if (f->scale == 0) + return 0; + if (f->scale == new_scale) + return f->value; + if (f->scale > new_scale) + return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale/new_scale/2) / (f->scale/new_scale); + else + return f->value * (new_scale/f->scale); +} + +/** + * Convert a fixed-point value to a floating-point value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tofloat(struct minmea_float *f) +{ + if (f->scale == 0) + return NAN; + return (float) f->value / (float) f->scale; +} + +/** + * Convert a raw coordinate to a floating point DD.DDD... value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tocoord(struct minmea_float *f) +{ + if (f->scale == 0) + return NAN; + int_least32_t degrees = f->value / (f->scale * 100); + int_least32_t minutes = f->value % (f->scale * 100); + return (float) degrees + (float) minutes / (60 * f->scale); +} + +#ifdef __cplusplus +} +#endif + +#endif /* MINMEA_H */ + +/* vim: set ts=4 sw=4 et: */ diff --git a/src/shared/drivers/gps/ublox/UbloxGPS.cpp b/src/shared/drivers/gps/ublox/UbloxGPS.cpp new file mode 100644 index 0000000000000000000000000000000000000000..392be263e908aba8bb618febc96929b262a48171 --- /dev/null +++ b/src/shared/drivers/gps/ublox/UbloxGPS.cpp @@ -0,0 +1,532 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio + * + * 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. + */ + +#include "UbloxGPS.h" + +#include <fcntl.h> + +#include "drivers/serial.h" +#include "filesystem/file_access.h" + +#include "diagnostic/StackLogger.h" + +using namespace miosix; + +UbloxGPS::UbloxGPS(int baudrate_, uint8_t sampleRate_, int serialPortNum_, + const char* serialPortName_, int defaultBaudrate_) + : baudrate(baudrate_), sampleRate(sampleRate_), + serialPortNumber(serialPortNum_), serialPortName(serialPortName_), + defaultBaudrate(defaultBaudrate_) +{ + // Prepare the gps file path with the specified name + strcpy(gpsFilePath, "/dev/"); + strcat(gpsFilePath, serialPortName); +} + +bool UbloxGPS::init() +{ + // Change the baud rate from the default value + if (!serialCommuinicationSetup()) + { + return false; + } + + Thread::sleep(10); + + // Reset configuration to default + // TODO: maybe move this on serial communication setup + if (!resetConfiguration()) + { + return false; + } + + // Disable NMEA messages + if (!disableNMEAMessages()) + { + return false; + } + + Thread::sleep(100); + + // Set GNSS configuration + if (!setGNSSConfiguration()) + { + return false; + } + + Thread::sleep(100); + + // Enable UBX messages + if (!enableUBXMessages()) + { + return false; + } + + Thread::sleep(100); + + // Set rate + if (!setRate()) + { + return false; + } + + return true; +} + +bool UbloxGPS::selfTest() { return true; } + +UbloxGPSData UbloxGPS::sampleImpl() +{ + Lock<FastMutex> l(mutex); + return threadSample; +} + +void UbloxGPS::run() +{ + /** + * UBX message structure: + * - 2B: Preamble + * - 1B: Message class + * - 1B: Message id + * - 2B: Payload length + * - lB: Payload + * - 2B: Checksum + */ + uint8_t message[6 + UBX_MAX_PAYLOAD_LENGTH + 2]; + uint16_t payloadLength; + + while (!shouldStop()) + { + StackLogger::getInstance()->updateStack(THID_GPS); + + // Try to read the message + if (!readUBXMessage(message, payloadLength)) + { + LOG_ERR(logger, "Unable to read a UBX message"); + continue; + } + + // Parse the message + if (!parseUBXMessage(message)) + { + LOG_ERR(logger, + "UBX message not recognized (class:0x{02x}, id: 0x{02x})", + message[2], message[3]); + } + } +} + +void UbloxGPS::ubxChecksum(uint8_t* msg, int len) +{ + uint8_t ck_a = 0, ck_b = 0; + + // The length must be valid, at least 8 bytes (preamble, msg, length, + // checksum) + if (len <= 8) + { + return; + } + + // Checksum calculation from byte 2 to end of payload + for (int i = 2; i < len - 2; i++) + { + ck_a = ck_a + msg[i]; + ck_b = ck_b + ck_a; + } + + msg[len - 2] = ck_a; + msg[len - 1] = ck_b; +} + +bool UbloxGPS::writeUBXMessage(uint8_t* message, int length) +{ + // Compute the checksum + ubxChecksum(message, length); + + // Write configuration + if (write(gpsFile, message, length) < 0) + { + LOG_ERR(logger, + "Failed to write ubx message (class:0x{02x}, id: 0x{02x})", + message[2], message[3]); + return false; + } + + return true; +} + +bool UbloxGPS::serialCommuinicationSetup() +{ + intrusive_ref_ptr<DevFs> devFs = FilesystemManager::instance().getDevFs(); + + // Change the baudrate only if it is different than the default + if (baudrate != defaultBaudrate) + { + // Close the gps file if already opened + devFs->remove(serialPortName); + + // Open the serial port device with the default boudrate + if (!devFs->addDevice(serialPortName, + intrusive_ref_ptr<Device>(new STM32Serial( + serialPortNumber, defaultBaudrate)))) + { + LOG_ERR(logger, + "[gps] Faild to open serial port {0} with baudrate {1} as " + "file {2}", + serialPortNumber, defaultBaudrate, serialPortName); + return false; + } + + // Open the gps file + if ((gpsFile = open(gpsFilePath, O_RDWR)) < 0) + { + LOG_ERR(logger, "Failed to open gps file {}", gpsFilePath); + return false; + } + + // Change boudrate + if (!setBaudrate()) + { + return false; + }; + + // Close the gps file + if (close(gpsFile) < 0) + { + LOG_ERR(logger, "Failed to close gps file {}", gpsFilePath); + return false; + } + + // Close the serial port + if (!devFs->remove(serialPortName)) + { + LOG_ERR(logger, "Failed to close serial port {} as file {}", + serialPortNumber, serialPortName); + return false; + } + } + + // Reopen the serial port with the configured boudrate + if (!devFs->addDevice(serialPortName, + intrusive_ref_ptr<Device>( + new STM32Serial(serialPortNumber, baudrate)))) + { + LOG_ERR(logger, + "Faild to open serial port {} with baudrate {} as file {}\n", + serialPortNumber, defaultBaudrate, serialPortName); + return false; + } + + // Reopen the gps file + if ((gpsFile = open(gpsFilePath, O_RDWR)) < 0) + { + LOG_ERR(logger, "Failed to open gps file {}", gpsFilePath); + return false; + } + + return true; +} + +bool UbloxGPS::resetConfiguration() +{ + uint8_t ubx_cfg_prt[RESET_CONFIG_MSG_LEN] = { + 0Xb5, 0x62, // Preamble + 0x06, 0x04, // Message UBX-CFG-RST + 0x04, 0x00, // Length + 0x00, 0x00, // navBbrMask (Hot start) + 0x00, // Hardware reset immediately + 0x00, // Reserved + 0xff, 0xff // Checksum + }; + + return writeUBXMessage(ubx_cfg_prt, RESET_CONFIG_MSG_LEN); +} + +bool UbloxGPS::setBaudrate() +{ + uint8_t ubx_cfg_prt[SET_BAUDRATE_MSG_LEN] = { + 0Xb5, 0x62, // Preamble + 0x06, 0x8a, // Message UBX-CFG-VALSET + 0x0c, 0x00, // Length + 0x00, // Version + 0xff, // All layers + 0x00, 0x00, // Reserved + 0x01, 0x00, 0x52, 0x40, // Configuration item key ID + 0xff, 0xff, 0xff, 0xff, // Value + 0xff, 0xff // Checksum + }; + + // Prepare boud rate + ubx_cfg_prt[14] = baudrate; + ubx_cfg_prt[15] = baudrate >> 8; + ubx_cfg_prt[16] = baudrate >> 16; + ubx_cfg_prt[17] = baudrate >> 24; + + return writeUBXMessage(ubx_cfg_prt, SET_BAUDRATE_MSG_LEN); +} + +bool UbloxGPS::disableNMEAMessages() +{ + uint8_t ubx_cfg_valset[DISABLE_NMEA_MESSAGES_MSG_LEN] = { + 0Xb5, 0x62, // Preamble + 0x06, 0x8a, // Message UBX-CFG-VALSET + 0x22, 0x00, // Length + 0x00, // Version + 0xff, // All layers + 0x00, 0x00, // Reserved + 0xbb, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_GGA_UART1 key ID + 0x00, // CFG-MSGOUT-NMEA_ID_GGA_UART1 value + 0xca, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_GLL_UART1 key ID + 0x00, // CFG-MSGOUT-NMEA_ID_GLL_UART1 value + 0xc0, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_GSA_UART1 key ID + 0x00, // CFG-MSGOUT-NMEA_ID_GSA_UART1 value + 0xc5, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_GSV_UART1 key ID + 0x00, // CFG-MSGOUT-NMEA_ID_GSV_UART1 value + 0xac, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_RMC_UART1 key ID + 0x00, // CFG-MSGOUT-NMEA_ID_RMC_UART1 value + 0xb1, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_VTG_UART1 key ID + 0x00, // CFG-MSGOUT-NMEA_ID_VTG_UART1 value + 0xff, 0xff // Checksum + }; + + return writeUBXMessage(ubx_cfg_valset, DISABLE_NMEA_MESSAGES_MSG_LEN); +} + +bool UbloxGPS::setGNSSConfiguration() +{ + uint8_t ubx_cfg_valset[SET_GNSS_CONF_LEN] = { + 0Xb5, 0x62, // Preamble + 0x06, 0x8a, // Message UBX-CFG-VALSET + 0x09, 0x00, // Length + 0x00, // Version + 0x07, // All layers + 0x00, 0x00, // Reserved + 0x21, 0x00, 0x11, 0x20, // CFG-NAVSPG-DYNMODEL key ID + 0x08, // CFG-NAVSPG-DYNMODEL value + 0xff, 0xff // Checksum + }; + + return writeUBXMessage(ubx_cfg_valset, SET_GNSS_CONF_LEN); +} + +bool UbloxGPS::enableUBXMessages() +{ + uint8_t ubx_cfg_valset[ENABLE_UBX_MESSAGES_MSG_LEN] = { + 0Xb5, 0x62, // Preamble + 0x06, 0x8a, // Message UBX-CFG-VALSET + 0x09, 0x00, // Length + 0x00, // Version + 0xff, // All layers + 0x00, 0x00, // Reserved + 0x07, 0x00, 0x91, 0x20, // CFG-MSGOUT-UBX_NAV_PVT_UART1 key ID + 0x01, // CFG-MSGOUT-UBX_NAV_PVT_UART1 value + 0xff, 0xff // Checksum + }; + + return writeUBXMessage(ubx_cfg_valset, ENABLE_UBX_MESSAGES_MSG_LEN); +} + +bool UbloxGPS::setRate() +{ + uint16_t rate = 1000 / sampleRate; + LOG_DEBUG(logger, "Rate: {}", rate); + + uint8_t ubx_cfg_valset[SET_RATE_MSG_LEN] = { + 0Xb5, 0x62, // Preamble + 0x06, 0x8a, // Message UBX-CFG-VALSET + 0x0a, 0x00, // Length + 0x00, // Version + 0x07, // All layers + 0x00, 0x00, // Reserved + 0x01, 0x00, 0x21, 0x30, // CFG-RATE-MEAS key ID + 0xff, 0xff, // CFG-RATE-MEAS value + 0xff, 0xff // Checksum + }; + + // Prepare rate + ubx_cfg_valset[14] = rate; + ubx_cfg_valset[15] = rate >> 8; + + return writeUBXMessage(ubx_cfg_valset, SET_RATE_MSG_LEN); +} + +bool UbloxGPS::readUBXMessage(uint8_t* message, uint16_t& payloadLength) +{ + // Read preamble + do + { + // Read util the first byte of the preamble + do + { + if (read(gpsFile, &message[0], 1) <= 0) // No more data available + { + return false; + } + } while (message[0] != PREAMBLE[0]); + + // Read the next byte + if (read(gpsFile, &message[1], 1) <= 0) // No more data available + { + return false; + } + } while (message[1] != PREAMBLE[1]); // Continue + + // Read message class and ID + if (read(gpsFile, &message[2], 1) <= 0) + { + return false; + } + if (read(gpsFile, &message[3], 1) <= 0) + { + return false; + } + + // Read length + if (read(gpsFile, &message[4], 2) <= 0) + { + return false; + } + payloadLength = message[4] | (message[5] << 8); + if (payloadLength > UBX_MAX_PAYLOAD_LENGTH) + { + return false; + } + + // Read paylaod and checksum + for (auto i = 0; i < payloadLength + 2; i++) + { + if (read(gpsFile, &message[6 + i], 1) <= 0) + { + return false; + } + } + + // Verify the checksum + uint8_t msgChecksum1 = message[6 + payloadLength]; + uint8_t msgChecksum2 = message[6 + payloadLength + 1]; + ubxChecksum(message, 6 + payloadLength + 2); + if (msgChecksum1 != message[6 + payloadLength] || + msgChecksum2 != message[6 + payloadLength + 1]) + { + LOG_ERR(logger, "Message checksum verification failed"); + return false; + } + + return true; +} + +bool UbloxGPS::parseUBXMessage(uint8_t* message) +{ + switch (message[2]) // Message class + { + case 0x01: // UBX-NAV + return parseUBXNAVMessage(message); + case 0x05: // UBX-ACK + return parseUBXACKMessage(message); + } + return false; +} + +bool UbloxGPS::parseUBXNAVMessage(uint8_t* message) +{ + switch (message[3]) // Message id + { + case 0x07: // UBX-NAV-PVT + // Lock the threadSample variable + Lock<FastMutex> l(mutex); + + // Latitude + int32_t rawLatitude = message[6 + 28] | message[6 + 29] << 8 | + message[6 + 30] << 16 | message[6 + 31] << 24; + threadSample.latitude = (float)rawLatitude / 1e7; + + // Longitude + int32_t rawLongitude = message[6 + 24] | message[6 + 25] << 8 | + message[6 + 26] << 16 | + message[6 + 27] << 24; + threadSample.longitude = (float)rawLongitude / 1e7; + + // Height + int32_t rawHeight = message[6 + 32] | message[6 + 33] << 8 | + message[6 + 34] << 16 | message[6 + 35] << 24; + threadSample.height = (float)rawHeight / 1e3; + + // Velocity north + int32_t rawVelocityNorth = message[6 + 48] | message[6 + 49] << 8 | + message[6 + 50] << 16 | + message[6 + 51] << 24; + threadSample.velocity_north = (float)rawVelocityNorth / 1e3; + + // Velocity east + int32_t rawVelocityEast = message[6 + 52] | message[6 + 53] << 8 | + message[6 + 54] << 16 | + message[6 + 55] << 24; + threadSample.velocity_east = (float)rawVelocityEast / 1e3; + + // Velocity down + int32_t rawVelocityDown = message[6 + 56] | message[6 + 57] << 8 | + message[6 + 58] << 16 | + message[6 + 59] << 24; + threadSample.velocity_down = (float)rawVelocityDown / 1e3; + + // Speed + int32_t rawSpeed = message[6 + 60] | message[6 + 61] << 8 | + message[6 + 62] << 16 | message[6 + 63] << 24; + threadSample.speed = (float)rawSpeed / 1e3; + + // Track (heading of motion) + int32_t rawTrack = message[6 + 64] | message[6 + 65] << 8 | + message[6 + 66] << 16 | message[6 + 67] << 24; + threadSample.track = (float)rawTrack / 1e5; + + // Number of satellite + threadSample.num_satellites = (uint8_t)message[6 + 23]; + + // Fix (every type of fix accepted) + threadSample.fix = message[6 + 20] != 0; + + // Timestamp + threadSample.gps_timestamp = TimestampTimer::getTimestamp(); + + return true; + } + + return false; +} + +bool UbloxGPS::parseUBXACKMessage(uint8_t* message) +{ + switch (message[3]) // Message id + { + case 0x00: // UBX-ACK-NAC + LOG_DEBUG(logger, + "Received NAC for message (class:0x{02x}, id: 0x{02x})", + message[6], message[7]); + return true; + case 0x01: // UBX-ACK-ACK + LOG_DEBUG(logger, + "Received ACK for message (class:0x{02x}, id: 0x{02x})", + message[6], message[7]); + return true; + } + return false; +} diff --git a/src/shared/drivers/gps/ublox/UbloxGPS.h b/src/shared/drivers/gps/ublox/UbloxGPS.h new file mode 100644 index 0000000000000000000000000000000000000000..a50e19fd8417324e8aa924dc051a27e8f966a422 --- /dev/null +++ b/src/shared/drivers/gps/ublox/UbloxGPS.h @@ -0,0 +1,146 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include <diagnostic/PrintLogger.h> +#include <miosix.h> + +#include "ActiveObject.h" +#include "UbloxGPSData.h" +#include "sensors/Sensor.h" + +/** + * @brief Driver for Ublox GPSs + * + * This driver handles communication and setup with Ublox GPSs. It uses the + * binary UBX protocol to retrieve and parse navigation data quicker than using + * the string based NMEA. + * + * At initialization it configures the device with the specified baudrate, reset + * the configuration and sets up UBX messages and GNSS parameters. + * + * Communication with the device is performed through a file, the driver opens + * the serial port under the filepath /dev/<serialPortName>. + * There is no need for the file to be setted up beforhand. + * + * This driver was written for a NEO-M9N gps with the latest version of UBX. + */ +class UbloxGPS : public Sensor<UbloxGPSData>, public ActiveObject +{ +public: + /** + * @brief Construct a new UbloxGPS object + * + * @param boudrate_ Baudrate to communicate with the device (max: 921600, + * min: 4800 for NEO-M9N) + * @param sampleRate_ GPS sample rate (max: 25 for NEO-M9N) + * @param serialPortName_ Name of the file for the gps device + * @param defaultBaudrate_ Startup baudrate (38400 for NEO-M9N) + */ + UbloxGPS(int boudrate_ = 921600, uint8_t sampleRate_ = 10, + int serialPortNumber_ = 2, const char *serialPortName_ = "gps", + int defaultBaudrate_ = 38400); + + /** + * @brief Sets up the serial port baudrate, disables the NMEA messages, + * configures GNSS options and enables UBX-PVT message + * + * @return True if the operation succeeded + */ + bool init() override; + + /** + * @brief Read a single message form the GPS, waits 2 sample cycle + * + * @return True if a message was sampled + */ + bool selfTest() override; + +private: + UbloxGPSData sampleImpl() override; + + void run() override; + + void ubxChecksum(uint8_t *msg, int len); + + /** + * @brief Compute the checksum and write the message to the device + */ + bool writeUBXMessage(uint8_t *message, int length); + + /** + * @brief Sets up the serial port with the correct baudrate + * + * Opens the serial port with the defaul baudrate and changes it to + * the value specified in the constructor, then it reopens the serial port. + * If the device is already using the correct baudrate this won't have + * effect. However if the gps is using a different baudrate the diver won't + * be able to communicate. + */ + bool serialCommuinicationSetup(); + + bool resetConfiguration(); + + bool setBaudrate(); + + bool disableNMEAMessages(); + + bool setGNSSConfiguration(); + + bool enableUBXMessages(); + + bool setRate(); + + bool readUBXMessage(uint8_t *message, uint16_t &payloadLength); + + bool parseUBXMessage(uint8_t *message); + + bool parseUBXNAVMessage(uint8_t *message); + + bool parseUBXACKMessage(uint8_t *message); + + const int baudrate; // [baud] + const uint8_t sampleRate; // [Hz] + const int serialPortNumber; + const char *serialPortName; + const int defaultBaudrate; // [baud] + + char gpsFilePath[16]; ///< Allows for a filename of up to 10 characters + int gpsFile; + + mutable miosix::FastMutex mutex; + UbloxGPSData threadSample{}; + + static constexpr int SET_BAUDRATE_MSG_LEN = 20; + static constexpr int RESET_CONFIG_MSG_LEN = 12; + static constexpr int DISABLE_NMEA_MESSAGES_MSG_LEN = 42; + static constexpr int SET_GNSS_CONF_LEN = 17; + static constexpr int ENABLE_UBX_MESSAGES_MSG_LEN = 17; + static constexpr int SET_RATE_MSG_LEN = 18; + + static constexpr uint8_t PREAMBLE[] = {0xb5, 0x62}; + + static constexpr int UBX_MAX_PAYLOAD_LENGTH = 92; + + PrintLogger logger = Logging::getLogger("ubloxgps"); +}; diff --git a/src/shared/drivers/gps/ublox/UbloxGPSData.h b/src/shared/drivers/gps/ublox/UbloxGPSData.h new file mode 100644 index 0000000000000000000000000000000000000000..86272a6ba38ac0ccc27af41775869de4bc3d726f --- /dev/null +++ b/src/shared/drivers/gps/ublox/UbloxGPSData.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Davide Bonomini + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct UbloxGPSData : public GPSData +{ + static std::string header() + { + return "gps_timestamp,latitude,longitude,height,velocity_north," + "velocity_east,velocity_down,speed,track,num_satellites,fix\n"; + } + + void print(std::ostream &os) const + { + os << gps_timestamp << "," << latitude << "," << longitude << "," + << height << "," << velocity_north << "," << velocity_east << "," + << velocity_down << "," << speed << "," << track << "," + << (int)num_satellites << "," << (int)fix << "\n"; + } +}; diff --git a/src/shared/drivers/hbridge/HBridge.cpp b/src/shared/drivers/hbridge/HBridge.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fc279273d68952f7a113143fb1d39b899462bb52 --- /dev/null +++ b/src/shared/drivers/hbridge/HBridge.cpp @@ -0,0 +1,86 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#include "HBridge.h" + +HBridge::HBridge(miosix::GpioPin inhibit, PWM::Timer timer, PWMChannel channel, + uint32_t frequency, float duty_cycle, + uint16_t disable_delay_ms) + : pin_inh(inhibit), pwm(timer, frequency), channel(channel), + duty_cycle(duty_cycle), disable_delay_ms(disable_delay_ms) +{ + pin_inh.low(); + + status.timestamp = TimestampTimer::getTimestamp(); + status.state = HBridgeState::DISABLED; + + // Start PWM with 0 duty cycle to keep IN pins low + pwm.enableChannel(channel, 0.0f); + + pwm.start(); +} + +HBridge::~HBridge() +{ + disable(); + + pwm.stop(); +} + +void HBridge::disable() +{ + if (status.state == HBridgeState::ENABLED) + { + pwm.setDutyCycle(channel, 0.0f); // Set duty cycle to 0 to leave the IN + // pin of the h-bridge low + + Thread::sleep(disable_delay_ms); // Wait a short delay + + pin_inh.low(); // Disable h-bridge + + status.timestamp = TimestampTimer::getTimestamp(); + status.state = HBridgeState::DISABLED; + } +} + +void HBridge::enable() { enableHBridge(channel, pin_inh, duty_cycle); } + +void HBridge::enableTest(float test_duty_cycle) +{ + enableHBridge(channel, pin_inh, test_duty_cycle); +} + +HBridgeStatus HBridge::getStatus() { return status; } + +void HBridge::enableHBridge(PWMChannel channel, GpioPin& inh, float duty_cycle) +{ + if (status.state == HBridgeState::DISABLED) + { + // Enable PWM Generation + pwm.setDutyCycle(channel, duty_cycle); + // enable h-bridge + inh.high(); + + status.timestamp = TimestampTimer::getTimestamp(); + status.state = HBridgeState::ENABLED; + } +} diff --git a/src/shared/drivers/hbridge/HBridge.h b/src/shared/drivers/hbridge/HBridge.h new file mode 100644 index 0000000000000000000000000000000000000000..d21fbb3f80dc9266e917d66101e136dccc772da0 --- /dev/null +++ b/src/shared/drivers/hbridge/HBridge.h @@ -0,0 +1,106 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#pragma once + +#include <miosix.h> + +#include "HBridgeData.h" +#include "drivers/pwm/pwm.h" +#include "TimestampTimer.h" + +using miosix::GpioPin; +using miosix::Thread; + +/** + * @brief Interface class to operate an h-bridge. + * + * Provides the ability to enable an h-bridge using a "nominal" duty cycle or + * using a "test" duty cycle. + */ +class HBridge +{ +public: + /** + * @brief Create a new HBridge + * + * @param inhibit Inhibit pin of the h-bridge + * @param timer Timer peripheral used for the PWM signal + * @param channel PWM channel + * @param frequency Frequency of the PWM driving the h-bridge + * @param duty_cycle Duty cycle of the PWM (from 0.0 to 1.0) + * @param disable_delay_ms Period of time where the IN must be kept + * low before bringing ENA/INH low + */ + HBridge(GpioPin inhibit, PWM::Timer timer, PWMChannel channel, + uint32_t frequency, float duty_cycle, + uint16_t disable_delay_ms = 50); + + ~HBridge(); + + /** + * @brief Deactivates the h-bridge + */ + void disable(); + + /** + * @brief Enables the h-bridge + * + * call disable() to stop + */ + void enable(); + + /** + * @brief Enables the h-bridge using the "test" duty cycle. + * Call disable() to stop + * + * @param test_duty_cycle Duty cycle to be used when testing the + * h-bridge for continuity + */ + void enableTest(float test_duty_cycle); + + /** + * @return the HBridge object status + */ + HBridgeStatus getStatus(); + +private: + /** + * @brief Enables the h-bridge. + * + * @param channel the h-bridge PWM channel + * @param ena_pin enable pin of the h-bridge + * @param duty_cycle the duty cycle to be used + */ + void enableHBridge(PWMChannel channel, GpioPin& ena_pin, float duty_cycle); + + HBridge(const HBridge& hb) = delete; + + GpioPin pin_inh; + PWM pwm; + PWMChannel channel; + + float duty_cycle; + uint16_t disable_delay_ms; + + HBridgeStatus status; +}; diff --git a/src/shared/drivers/hbridge/HBridgeData.h b/src/shared/drivers/hbridge/HBridgeData.h new file mode 100644 index 0000000000000000000000000000000000000000..bc78294aaabb02383c0738ae5565001554311302 --- /dev/null +++ b/src/shared/drivers/hbridge/HBridgeData.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Authors: Luca Erbetta, Luca Conterio + * + * 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. + */ + +#pragma once + +#include <ostream> +#include <string> + +enum class HBridgeState : uint8_t +{ + DISABLED = 0, + ENABLED = 1 +}; + +struct HBridgeStatus +{ + uint64_t timestamp; + HBridgeState state = HBridgeState::DISABLED; + + static std::string header() { return "timestamp,state\n"; } + + void print(std::ostream& os) const + { + os << timestamp << "," << (uint8_t)state << "\n"; + } +}; diff --git a/src/shared/drivers/i2c/stm32f2_f4_i2c.cpp b/src/shared/drivers/i2c/stm32f2_f4_i2c.cpp index e2c97970da71ad2f3b0dd7edc5ef62d29f82f6ec..f33431c79b0fc72bbeb3e7a4ba326c5ac58a333b 100644 --- a/src/shared/drivers/i2c/stm32f2_f4_i2c.cpp +++ b/src/shared/drivers/i2c/stm32f2_f4_i2c.cpp @@ -1,29 +1,25 @@ -/*************************************************************************** - * Copyright (C) 2013 by Terraneo Federico and Silvano Seva * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * As a special exception, if other files instantiate templates or use * - * macros or inline functions from this file, or you compile this file * - * and link it with other works to produce a work based on this file, * - * this file does not by itself cause the resulting work to be covered * - * by the GNU General Public License. However the source code for this * - * file must still be made available in accordance with the GNU General * - * Public License. This exception does not invalidate any other reasons * - * why a work based on this file might be covered by the GNU General * - * Public License. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, see <http://www.gnu.org/licenses/> * - ***************************************************************************/ +/* Copyright (c) 2013 Skyward Experimental Rocketry + * Author: Federico Terraneo + * + * 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. + */ + #include "stm32f2_f4_i2c.h" #include <kernel/scheduler/scheduler.h> diff --git a/src/shared/drivers/i2c/stm32f2_f4_i2c.h b/src/shared/drivers/i2c/stm32f2_f4_i2c.h index c493ef935b5589d8349b6f7d185562d2a5828d1d..80882b524c17f0bad804a71b1eed21879314af33 100644 --- a/src/shared/drivers/i2c/stm32f2_f4_i2c.h +++ b/src/shared/drivers/i2c/stm32f2_f4_i2c.h @@ -1,29 +1,25 @@ -/*************************************************************************** - * Copyright (C) 2013 by Terraneo Federico and Silvano Seva * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * As a special exception, if other files instantiate templates or use * - * macros or inline functions from this file, or you compile this file * - * and link it with other works to produce a work based on this file, * - * this file does not by itself cause the resulting work to be covered * - * by the GNU General Public License. However the source code for this * - * file must still be made available in accordance with the GNU General * - * Public License. This exception does not invalidate any other reasons * - * why a work based on this file might be covered by the GNU General * - * Public License. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, see <http://www.gnu.org/licenses/> * - ***************************************************************************/ +/* Copyright (c) 2013 Skyward Experimental Rocketry + * Author: Federico Terraneo + * + * 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. + */ + #ifndef STM32F2_I2C_H #define STM32F2_I2C_H diff --git a/src/shared/drivers/interrupt/InterruptManager.cpp b/src/shared/drivers/interrupt/InterruptManager.cpp index 8b5f22c26169a354eba73c6a6144db24b1f2c081..261db7df50e3fd5fae482f5115763eaf05acbf9e 100755 --- a/src/shared/drivers/interrupt/InterruptManager.cpp +++ b/src/shared/drivers/interrupt/InterruptManager.cpp @@ -1,3 +1,25 @@ +/* Copyright (c) 2016 Skyward Experimental Rocketry + * Author: Matteo Piazzolla + * + * 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. + */ + #include "InterruptManager.h" #include "miosix.h" #include <e20/e20.h> diff --git a/src/shared/drivers/interrupt/InterruptManager.h b/src/shared/drivers/interrupt/InterruptManager.h index 87e956bee598f813063b10f3256eaadfc724d89a..322a005c54a3ced5c489676f876352b4f4c60c02 100755 --- a/src/shared/drivers/interrupt/InterruptManager.h +++ b/src/shared/drivers/interrupt/InterruptManager.h @@ -1,3 +1,25 @@ +/* Copyright (c) 2016-2018 Skyward Experimental Rocketry + * Author: Matteo Piazzolla + * + * 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. + */ + #ifndef INTERRUPTMANAGER_H #define INTERRUPTMANAGER_H diff --git a/src/shared/drivers/interrupt/external_interrupts.cpp b/src/shared/drivers/interrupt/external_interrupts.cpp index 7b58d7c3d19e8538713881fb2526df62b1bfc588..69592402a4d8a5bd2bd3bb64a1dfd298ff4a677c 100644 --- a/src/shared/drivers/interrupt/external_interrupts.cpp +++ b/src/shared/drivers/interrupt/external_interrupts.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Alvise de' Faveri Tron +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Alvise de'Faveri Tron * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,13 +13,14 @@ * * 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 + * 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. */ +#include "external_interrupts.h" #include <miosix.h> using namespace miosix; @@ -31,7 +31,7 @@ using namespace miosix; * * In particular, an EXTIxx_IRQHandlerImpl() function is declared for every * interrupt from 5 to 15, and defined in a similar fashion as miosix's - * 'stage_1_boot', i.e. handlers are "weak" and, if not overridden, will be + * 'stage_1_boot', i.e. handlers are "weak" and, if not overridden, will be * assigned to a default handler. * * NOTE: you will still have to enable the corresponding interrupt if you @@ -61,33 +61,40 @@ using namespace miosix; // All unused interrupts call this function. extern void unexpectedInterrupt(); -extern "C" void Default_EXTI_Handler() -{ - unexpectedInterrupt(); -} +extern "C" void Default_EXTI_Handler() { unexpectedInterrupt(); } /** * Declaration of a separate IRQHandler for each external * interrupt. If no further implementatio is provided, the * Default_Handler will be called. */ -void __attribute__((weak)) EXTI5_IRQHandlerImpl(); -void __attribute__((weak)) EXTI6_IRQHandlerImpl(); -void __attribute__((weak)) EXTI7_IRQHandlerImpl(); -void __attribute__((weak)) EXTI8_IRQHandlerImpl(); -void __attribute__((weak)) EXTI9_IRQHandlerImpl(); -void __attribute__((weak)) EXTI10_IRQHandlerImpl(); -void __attribute__((weak)) EXTI11_IRQHandlerImpl(); -void __attribute__((weak)) EXTI12_IRQHandlerImpl(); -void __attribute__((weak)) EXTI13_IRQHandlerImpl(); -void __attribute__((weak)) EXTI14_IRQHandlerImpl(); -void __attribute__((weak)) EXTI15_IRQHandlerImpl(); - -#pragma weak EXTI5_IRQHandlerImpl = Default_EXTI_Handler -#pragma weak EXTI6_IRQHandlerImpl = Default_EXTI_Handler -#pragma weak EXTI7_IRQHandlerImpl = Default_EXTI_Handler -#pragma weak EXTI8_IRQHandlerImpl = Default_EXTI_Handler -#pragma weak EXTI9_IRQHandlerImpl = Default_EXTI_Handler +void __attribute__((weak)) EXTI0_IRQHandlerImpl(); +void __attribute__((weak)) EXTI1_IRQHandlerImpl(); +void __attribute__((weak)) EXTI2_IRQHandlerImpl(); +void __attribute__((weak)) EXTI3_IRQHandlerImpl(); +void __attribute__((weak)) EXTI4_IRQHandlerImpl(); +void __attribute__((weak)) EXTI5_IRQHandlerImpl(); +void __attribute__((weak)) EXTI6_IRQHandlerImpl(); +void __attribute__((weak)) EXTI7_IRQHandlerImpl(); +void __attribute__((weak)) EXTI8_IRQHandlerImpl(); +void __attribute__((weak)) EXTI9_IRQHandlerImpl(); +void __attribute__((weak)) EXTI10_IRQHandlerImpl(); +void __attribute__((weak)) EXTI11_IRQHandlerImpl(); +void __attribute__((weak)) EXTI12_IRQHandlerImpl(); +void __attribute__((weak)) EXTI13_IRQHandlerImpl(); +void __attribute__((weak)) EXTI14_IRQHandlerImpl(); +void __attribute__((weak)) EXTI15_IRQHandlerImpl(); + +#pragma weak EXTI0_IRQHandlerImpl = Default_EXTI_Handler +#pragma weak EXTI1_IRQHandlerImpl = Default_EXTI_Handler +#pragma weak EXTI2_IRQHandlerImpl = Default_EXTI_Handler +#pragma weak EXTI3_IRQHandlerImpl = Default_EXTI_Handler +#pragma weak EXTI4_IRQHandlerImpl = Default_EXTI_Handler +#pragma weak EXTI5_IRQHandlerImpl = Default_EXTI_Handler +#pragma weak EXTI6_IRQHandlerImpl = Default_EXTI_Handler +#pragma weak EXTI7_IRQHandlerImpl = Default_EXTI_Handler +#pragma weak EXTI8_IRQHandlerImpl = Default_EXTI_Handler +#pragma weak EXTI9_IRQHandlerImpl = Default_EXTI_Handler #pragma weak EXTI10_IRQHandlerImpl = Default_EXTI_Handler #pragma weak EXTI11_IRQHandlerImpl = Default_EXTI_Handler #pragma weak EXTI12_IRQHandlerImpl = Default_EXTI_Handler @@ -97,7 +104,67 @@ void __attribute__((weak)) EXTI15_IRQHandlerImpl(); /** * Implementation of the IRQHandler that is triggered when - * any external interrupt between 5 and 9 is raised. + * external interrupt 0 is raised. + */ +void __attribute__((naked)) EXTI0_IRQHandler() +{ + saveContext(); + EXTI->PR = EXTI_PR_PR0; + asm volatile("bl _Z20EXTI0_IRQHandlerImplv"); + restoreContext(); +} + +/** + * Implementation of the IRQHandler that is triggered when + * external interrupt 1 is raised. + */ +void __attribute__((naked)) EXTI1_IRQHandler() +{ + saveContext(); + EXTI->PR = EXTI_PR_PR1; + asm volatile("bl _Z20EXTI1_IRQHandlerImplv"); + restoreContext(); +} + +/** + * Implementation of the IRQHandler that is triggered when + * external interrupt 2 is raised. + */ +void __attribute__((naked)) EXTI2_IRQHandler() +{ + saveContext(); + EXTI->PR = EXTI_PR_PR2; + asm volatile("bl _Z20EXTI2_IRQHandlerImplv"); + restoreContext(); +} + +/** + * Implementation of the IRQHandler that is triggered when + * external interrupt 3 is raised. + */ +void __attribute__((naked)) EXTI3_IRQHandler() +{ + saveContext(); + EXTI->PR = EXTI_PR_PR3; + asm volatile("bl _Z20EXTI3_IRQHandlerImplv"); + restoreContext(); +} + +/** + * Implementation of the IRQHandler that is triggered when + * external interrupt 4 is raised. + */ +void __attribute__((naked)) EXTI4_IRQHandler() +{ + saveContext(); + EXTI->PR = EXTI_PR_PR4; + asm volatile("bl _Z20EXTI4_IRQHandlerImplv"); + restoreContext(); +} + +/** + * Implementation of the IRQHandler that is triggered when + * any external interrupt between 5 and 9 is raised. */ void __attribute__((naked)) EXTI9_5_IRQHandler() { @@ -108,7 +175,7 @@ void __attribute__((naked)) EXTI9_5_IRQHandler() /** * Implementation of the IRQHandler that is triggered when - * any external interrupt between 10 and 15 is raised. + * any external interrupt between 10 and 15 is raised. */ void __attribute__((naked)) EXTI15_10_IRQHandler() { @@ -118,78 +185,148 @@ void __attribute__((naked)) EXTI15_10_IRQHandler() } /** - * Read from the PR register which interrupt is pending + * Read from the PR register which interrupt is pending * and call the corresponding IRQHandler. - * If no flag in the range covered by this IRQ is set, + * If no flag in the range covered by this IRQ is set, * call default handler. */ void __attribute__((used)) EXTI9_5_IRQHandlerImpl() { - if(EXTI->PR & EXTI_PR_PR5) + if (EXTI->PR & EXTI_PR_PR5) { - EXTI->PR |= EXTI_PR_PR5; // Clear pending flag + EXTI->PR = EXTI_PR_PR5; // Clear pending flag EXTI5_IRQHandlerImpl(); } - else if(EXTI->PR & EXTI_PR_PR6) + else if (EXTI->PR & EXTI_PR_PR6) { - EXTI->PR |= EXTI_PR_PR6; + EXTI->PR = EXTI_PR_PR6; EXTI6_IRQHandlerImpl(); } - else if(EXTI->PR & EXTI_PR_PR7) + else if (EXTI->PR & EXTI_PR_PR7) { - EXTI->PR |= EXTI_PR_PR7; + EXTI->PR = EXTI_PR_PR7; EXTI7_IRQHandlerImpl(); } - else if(EXTI->PR & EXTI_PR_PR8) + else if (EXTI->PR & EXTI_PR_PR8) { - EXTI->PR |= EXTI_PR_PR8; + EXTI->PR = EXTI_PR_PR8; EXTI8_IRQHandlerImpl(); } - else if(EXTI->PR & EXTI_PR_PR9) + else if (EXTI->PR & EXTI_PR_PR9) { - EXTI->PR |= EXTI_PR_PR9; + EXTI->PR = EXTI_PR_PR9; EXTI9_IRQHandlerImpl(); } else { +#ifndef FLIGHT Default_EXTI_Handler(); +#endif } } void __attribute__((used)) EXTI15_10_IRQHandlerImpl() { - if(EXTI->PR & EXTI_PR_PR10) + if (EXTI->PR & EXTI_PR_PR10) { - EXTI->PR |= EXTI_PR_PR10; // Clear pending flag + EXTI->PR = EXTI_PR_PR10; // Clear pending flag EXTI10_IRQHandlerImpl(); } - else if(EXTI->PR & EXTI_PR_PR11) + else if (EXTI->PR & EXTI_PR_PR11) { - EXTI->PR |= EXTI_PR_PR11; + EXTI->PR = EXTI_PR_PR11; EXTI11_IRQHandlerImpl(); } - else if(EXTI->PR & EXTI_PR_PR12) + else if (EXTI->PR & EXTI_PR_PR12) { - EXTI->PR |= EXTI_PR_PR12; + EXTI->PR = EXTI_PR_PR12; EXTI12_IRQHandlerImpl(); } - else if(EXTI->PR & EXTI_PR_PR13) + else if (EXTI->PR & EXTI_PR_PR13) { - EXTI->PR |= EXTI_PR_PR13; + EXTI->PR = EXTI_PR_PR13; EXTI13_IRQHandlerImpl(); } - else if(EXTI->PR & EXTI_PR_PR14) + else if (EXTI->PR & EXTI_PR_PR14) { - EXTI->PR |= EXTI_PR_PR14; + EXTI->PR = EXTI_PR_PR14; EXTI14_IRQHandlerImpl(); } - else if(EXTI->PR & EXTI_PR_PR15) + else if (EXTI->PR & EXTI_PR_PR15) { - EXTI->PR |= EXTI_PR_PR15; + EXTI->PR = EXTI_PR_PR15; EXTI15_IRQHandlerImpl(); } else { +#ifndef FLIGHT Default_EXTI_Handler(); +#endif } } + +constexpr unsigned ConvertGPIO_BASEtoUnsiged(unsigned P) +{ + // clang-format off + return P == GPIOA_BASE? 0 : + P == GPIOB_BASE? 1 : + P == GPIOC_BASE? 2 : + P == GPIOD_BASE? 3 : + P == GPIOE_BASE? 4 : + P == GPIOF_BASE? 5 : + P == GPIOG_BASE? 6 : + P == GPIOH_BASE? 7 : + P == GPIOI_BASE? 8 : +#if (defined (STM32F427xx) || defined (STM32F437xx) || defined (STM32F429xx) || defined (STM32F439xx)) + P == GPIOJ_BASE? 9 : + P == GPIOK_BASE? 10 : +#endif + 0; + // clang-format on +} + +constexpr unsigned GetEXTI_IRQn(unsigned N) +{ + // clang-format off + + return N==0? EXTI0_IRQn : + N==1? EXTI1_IRQn : + N==2? EXTI2_IRQn : + N==3? EXTI3_IRQn : + N==4? EXTI4_IRQn : + (N>=5&&N<=9)? EXTI9_5_IRQn : + EXTI15_10_IRQn; + + // clang-format on +} + +constexpr unsigned GetEXTICR_register_value(unsigned P, unsigned N) +{ + return (ConvertGPIO_BASEtoUnsiged(P) << ((N % 4) * 4)); +} + +void enableExternalInterrupt(unsigned int gpio_port, unsigned int gpio_num, + InterruptTrigger trigger, unsigned int priority) +{ + auto exitcr_reg_value = GetEXTICR_register_value(gpio_port, gpio_num); + + { + FastInterruptDisableLock dLock; + + RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; + SYSCFG->EXTICR[int(gpio_num / 4)] |= exitcr_reg_value; + } + + EXTI->IMR |= 1 << gpio_num; + + if (trigger == InterruptTrigger::RISING_EDGE || + trigger == InterruptTrigger::RISING_FALLING_EDGE) + EXTI->RTSR |= 1 << gpio_num; + + if (trigger == InterruptTrigger::FALLING_EDGE || + trigger == InterruptTrigger::RISING_FALLING_EDGE) + EXTI->FTSR |= 1 << gpio_num; + + NVIC_EnableIRQ(static_cast<IRQn_Type>(GetEXTI_IRQn(gpio_num))); + NVIC_SetPriority(static_cast<IRQn_Type>(GetEXTI_IRQn(gpio_num)), priority); +} \ No newline at end of file diff --git a/src/shared/drivers/interrupt/external_interrupts.h b/src/shared/drivers/interrupt/external_interrupts.h new file mode 100644 index 0000000000000000000000000000000000000000..02dce4f69a48f013614762ef6a7eda67834c79fb --- /dev/null +++ b/src/shared/drivers/interrupt/external_interrupts.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +enum class InterruptTrigger +{ + RISING_EDGE, + FALLING_EDGE, + RISING_FALLING_EDGE +}; + +/** + * @brief Enables external interrupts on the provided pin. + * Remember to set the GPIO to input mode! + * + * @param gpio_port Port of the pin (eg: GPIOC_BASE) + * @param gpio_num Pin number (eg: 4 for PC4) + * @param trigger Interrupt detection trigger (rising edge, falling or both) + * @param priority Interrupt priority [0-15], 0 = Highest priority + */ +void enableExternalInterrupt(unsigned int gpio_port, unsigned int gpio_num, + InterruptTrigger trigger, unsigned int priority = 15); \ No newline at end of file diff --git a/src/shared/drivers/mavlink/MavlinkDriver.h b/src/shared/drivers/mavlink/MavlinkDriver.h index e89ebd3e1f4fe8a65fc11ed349144e00c7de5f51..e2c025f735c5187b76f4a6d22f4a8389fb1aa31a 100644 --- a/src/shared/drivers/mavlink/MavlinkDriver.h +++ b/src/shared/drivers/mavlink/MavlinkDriver.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2018-2019 Skyward Experimental Rocketry - * Authors: Alvise de'Faveri Tron +/* Copyright (c) 2018-2019 Skyward Experimental Rocketry + * Author: Alvise de'Faveri Tron * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 @@ -24,6 +23,8 @@ #pragma once #include <Common.h> +#include <diagnostic/PrintLogger.h> + #include <vector> /** @@ -38,10 +39,10 @@ implementation before including MavlinkDriver.h" #endif -#include <mavlink_skyward_lib/mavlink_lib/mavlink_types.h> - #include <drivers/Transceiver.h> +#include <mavlink_skyward_lib/mavlink_lib/mavlink_types.h> #include <utils/collections/SyncPacketQueue.h> + #include "MavlinkStatus.h" #include "diagnostic/SkywardStack.h" #include "diagnostic/StackLogger.h" @@ -165,6 +166,8 @@ private: miosix::Thread* sndThread = nullptr; miosix::Thread* rcvThread = nullptr; + + PrintLogger logger = Logging::getLogger("mavlinkdriver"); }; /********************************************************************** @@ -199,7 +202,7 @@ bool MavlinkDriver<pkt_len, out_queue_size>::start() } else { - TRACE("[MAV] Could not start sender!\n"); + LOG_ERR(logger, "Could not start sender!"); } } @@ -216,13 +219,13 @@ bool MavlinkDriver<pkt_len, out_queue_size>::start() } else { - TRACE("[MAV] Could not start receiver!\n"); + LOG_ERR(logger, "Could not start receiver!"); } } if (sndStarted && rcvStarted) { - TRACE("[MAV] Start ok (sender and receiver)\n"); + LOG_DEBUG(logger, "Start ok (sender and receiver)\n"); } return (sndStarted && rcvStarted); @@ -262,7 +265,7 @@ void MavlinkDriver<pkt_len, out_queue_size>::updateQueueStats(int dropped) if (dropped != 0) { - TRACE("[MAV] Buffer full. The oldest message has been discarded.\n"); + LOG_ERR(logger, "Buffer full, the oldest message has been discarded"); status.n_dropped_packets++; } @@ -308,11 +311,10 @@ void MavlinkDriver<pkt_len, out_queue_size>::runReceiver() // what could happen. miosix::Unlock<miosix::FastMutex> unlock(l); - TRACE( - "[MAV] Received message with ID %d, sequence: %d " - "from " - "component %d of system %d\n", - msg.msgid, msg.seq, msg.compid, msg.sysid); + LOG_DEBUG(logger, + "Received message with ID {}, sequence: {} from " + "component {} of system {}", + msg.msgid, msg.seq, msg.compid, msg.sysid); // ... handle the command onRcv(this, msg); @@ -327,7 +329,7 @@ void MavlinkDriver<pkt_len, out_queue_size>::runReceiver() template <unsigned int pkt_len, unsigned int out_queue_size> void MavlinkDriver<pkt_len, out_queue_size>::runSender() { - TRACE("[MAV] Sender is running\n"); + LOG_DEBUG(logger, "Sender is running"); Packet<pkt_len> pkt; while (!stop_flag) @@ -345,8 +347,8 @@ void MavlinkDriver<pkt_len, out_queue_size>::runSender() { out_queue.pop(); // remove from queue - // TRACE("[MAV] Sending packet. Size: %d (age: %d)\n", pkt.size(), - // (int)age); + // LOG_DEBUG(logger, "Sending packet. Size: {} (age: {})", + // pkt.size(), age); bool sent = device->send(pkt.content.data(), pkt.size()); updateSenderStats(pkt.msgCount(), sent); @@ -373,7 +375,7 @@ void MavlinkDriver<pkt_len, out_queue_size>::updateSenderStats(size_t msgCount, if (!sent) { status.n_send_errors++; - TRACE("[MAV] Error: could not send message\n"); + LOG_ERR(logger, "Could not send message"); } } @@ -384,6 +386,7 @@ template <unsigned int pkt_len, unsigned int out_queue_size> MavlinkStatus MavlinkDriver<pkt_len, out_queue_size>::getStatus() { miosix::Lock<miosix::FastMutex> l(mtx_status); + status.timestamp = miosix::getTick(); return status; } diff --git a/src/shared/drivers/mavlink/MavlinkStatus.h b/src/shared/drivers/mavlink/MavlinkStatus.h index a3b327cc1339da66afe975f84e12cb9dcc09f5f3..ed69cd1ef56b48eaa380fd083a769f03014ba04d 100644 --- a/src/shared/drivers/mavlink/MavlinkStatus.h +++ b/src/shared/drivers/mavlink/MavlinkStatus.h @@ -1,5 +1,5 @@ /* Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Alvise de'Faveri Tron + * Author: Alvise de'Faveri Tron * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/memory/FlashController.h b/src/shared/drivers/memory/FlashController.h new file mode 100644 index 0000000000000000000000000000000000000000..7d442a742e164b6d3cf1bc65e5371f09b9a1e208 --- /dev/null +++ b/src/shared/drivers/memory/FlashController.h @@ -0,0 +1,670 @@ +/* Copyright (c) 2017 Skyward Experimental Rocketry + * Author: Matteo Piazzolla + * + * 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. + */ + +#ifndef FLASHCONTROLLER_H +#define FLASHCONTROLLER_H + +#include <drivers/memory/FlashDriverInclude.h> + +#include <miosix.h> +#include <Singleton.h> +#include <diagnostic/FaultCounter.h> +#include <diagnostic/NewLogger.h> + +using logging::logger; + +//Forward declaration +namespace testing +{ +namespace flashmemorytests +{ +template<typename > +class FlashTest; +} +} + +namespace flashmemory +{ + +/* + 16 pagine da 256 byte + 1000000 pagine in un banco + 62500 sottosettori + */ +static const uint16_t CONTROLLER_VERSION = 1; +static const uint16_t BOARD_ID = 0xAB; //TBD + +//BLOCK_SIZE deve essere un divisore di SUBSECTOR_SIZE +static const uint32_t BLOCK_SIZE = PAGE_SIZE; + +/** Number of flash data blocks that fit in each sector. + * First block of each sector is reserved for the header. + * In the first sector, the entire first subsector is reserved for the header. + */ +static const uint32_t BLOCKS_PER_SECTOR = SECTOR_SIZE / BLOCK_SIZE; +static const uint32_t BLOCKS_NUM = BLOCKS_PER_SECTOR * SECTORS_NUM; + +static const uint32_t FIRST_BOOT_KEY = 0xF135B007; +static const uint32_t READ_ONLY_KEY = 0x3EAD0271; + +//How many sectors should we attempt to write in before we give up? +static const int MAX_SECTOR_ITERATIONS = 50; + +struct FlashHeader +{ + const uint16_t version = CONTROLLER_VERSION; + const uint16_t board_id = BOARD_ID; + + uint32_t first_boot = FIRST_BOOT_KEY; + uint32_t start_index = 0; + uint32_t read_only = 0; + + bool operator==(const FlashHeader& other) const + { + return memcmp(this, &other, sizeof(FlashHeader)) == 0; + } + + bool operator!=(const FlashHeader& other) const + { + return memcmp(this, &other, sizeof(FlashHeader)) != 0; + } +}; + +static const uint8_t SECTOR_EMPTY = 0x55; +static const uint8_t SECTOR_WRITTEN = 0x44; +static const uint8_t SECTOR_CORRUPT = 0x00; + +struct SectorHeader +{ + uint8_t content = SECTOR_CORRUPT; //Either one of SECTOR_EMPTY, + //SECTOR_WRITTEN, SECTOR_CORRUPT + bool operator==(const SectorHeader& other) const + { + return memcmp(this, &other, sizeof(SectorHeader)) == 0; + } +}; + +static const uint32_t FLASH_BUFFER_SIZE = (BLOCK_SIZE) + - (sizeof(uint32_t) * 2 + sizeof(uint16_t)); + +struct FlashDataBlock +{ + uint32_t id; + uint32_t timestamp; + uint8_t buffer[FLASH_BUFFER_SIZE]; + uint16_t crc; + + bool operator==(const FlashDataBlock& other) const + { + return memcmp(this, &other, sizeof(FlashDataBlock)) == 0; + } + + bool operator!=(const FlashDataBlock& other) const + { + return memcmp(this, &other, sizeof(FlashDataBlock)) != 0; + } +}; + +template<typename MemoryBus> +class FlashController : Singleton<FlashController<MemoryBus>> +{ + friend class Singleton<FlashController<MemoryBus>>; + + template<typename > + friend class testing::flashmemorytests::FlashTest; + + typedef FlashController<MemoryBus> FlashControllerType; + typedef FlashDriver<MemoryBus> FlashDriverType; + + public: + enum class Status + { + WRITE_READY = 0, + READ_ONLY = 1, + INITIALIZATION_ERROR = 2, + WRITE_ERROR = 3 + }; + + void init() + { + status_ = Status::INITIALIZATION_ERROR; + block_index_ = SUBSECTOR_SIZE / BLOCK_SIZE; + + FlashHeader header; + + if (!readFlashHeader(&header)) + { + logger.critical(logtag(), + "Cannot read flash header."); + sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT); + return; + } + + if (header.read_only == READ_ONLY_KEY) + { + flash_->setReadOnly(); + status_ = Status::READ_ONLY; + } else + { + if (header.first_boot == FIRST_BOOT_KEY) + { + //Update the first boot key + header.first_boot = 0; + + if (!writeFlashHeader(header, false)) + { + logger.critical(logtag(), + "Could not update first boot value."); + sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT); + return; + } + + if (header.start_index >= block_index_) + { + block_index_ = header.start_index; + } + status_ = Status::WRITE_READY; + } else + { + if (recoverFromReboot()) + { + status_ = Status::WRITE_READY; + } else + { + logger.critical(logtag(), "Couldn't find sector to write in."); + sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT); + } + } + } + } + + FlashController::Status getStatus() + { + return status_; + } + + uint32_t getBlockIndex() + { + return block_index_; + } + + /** + * @brief Writes a block onto the flash memory. + * + * @param block The block to be written on the flash + */ + bool writeDataBlock(FlashDataBlock* block_buf) + { + + if (status_ != Status::WRITE_READY) + { + logger.error(logtag(), "Writing is not enabled."); + return false; + } + + if (block_index_ >= BLOCKS_NUM) + { + logger.critical(logtag(), "Memory is full."); + sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT); + status_ = Status::WRITE_ERROR; + return false; + } + + //Enforce size <= BLOCK_SIZE + size_t size = sizeof(FlashDataBlock) < BLOCK_SIZE ? + sizeof(FlashDataBlock) : BLOCK_SIZE; + + //Beginning of a new sector. Check if we can write in it and update its header + if (block_index_ % BLOCKS_PER_SECTOR == 0) + { + if(!gotoNewSector()){ + logger.critical(logtag(), "Could not find sector to write in."); + status_ = Status::WRITE_ERROR; + sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT); + return false; + } + } + + uint8_t result; + flash_->write(&result, block_index_ * BLOCK_SIZE, + reinterpret_cast<uint8_t*>(block_buf), size); + block_index_++; + + return result == RESULT_OK; + } + + static bool readFlashHeader(FlashHeader* header) + { + uint8_t result; + FlashDriverType* flash = Singleton<FlashDriverType>::getInstance(); + flash->read(&result, 0, (uint8_t*) header, sizeof(FlashHeader)); + return result == RESULT_OK; + } + + /** + * Saves (and overwrites) the header in the first address on the memory. + * @param header + */ + static bool writeFlashHeader(const FlashHeader& header, bool erase = true) + { + uint8_t result; + FlashDriverType* flash = Singleton<FlashDriverType>::getInstance(); + if (erase) + { + flash->enableErase(); + flash->eraseSubsector(&result, 0); + if (result != OpResultFlags::RESULT_OK) + { + return false; + } + } + uint32_t size = + sizeof(FlashHeader) > SUBSECTOR_SIZE ? SUBSECTOR_SIZE : + sizeof(FlashHeader); + + flash->writeAndCheck(&result, 0, (uint8_t*) (&header), size); + return result == RESULT_OK; + } + + static bool readBlock(uint32_t block_index, uint8_t* buffer) + { + uint8_t result; + FlashDriverType* flash = Singleton<FlashDriverType>::getInstance(); + + flash->read(&result, block_index * BLOCK_SIZE, buffer, BLOCK_SIZE); + return result == RESULT_OK; + } + + static bool readDataBlock(uint32_t block_index, FlashDataBlock* block) + { + //Flash header & sector headers are not *data* blocks. + if(block_index % BLOCKS_PER_SECTOR == 0 + || block_index < SUBSECTOR_SIZE / BLOCK_SIZE) + { + return false; + } + + readBlock(block_index, reinterpret_cast<uint8_t*>(block)); + return true; + } + + /** + * Saves (and overwrites) the header in the first address on the memory. + * @param header + */ + static bool writeSectorHeader(uint32_t sector, const SectorHeader& header) + { + uint8_t result; + FlashDriverType* flash = Singleton<FlashDriverType>::getInstance(); + + uint32_t size = + sizeof(SectorHeader) > BLOCK_SIZE ? BLOCK_SIZE : sizeof(SectorHeader); + + flash->writeAndCheck(&result, sector * SECTOR_SIZE, (uint8_t*) (&header), + size); + + return result == RESULT_OK; + } + + static bool readSectorHeader(uint32_t sector, SectorHeader* header) + { + return readSectorHeader(sector, (uint8_t*) header); + } + + static bool readSectorHeader(uint32_t sector, uint8_t* header_buff) + { + uint8_t result; + FlashDriverType* flash = Singleton<FlashDriverType>::getInstance(); + flash->read(&result, SECTOR_SIZE * sector, header_buff, + sizeof(SectorHeader)); + return result == RESULT_OK; + } + + static bool format() + { + uint8_t result; + logger.warning(logtag(), "THE MEMORY WILL BE FORMATTED IN 5 SECONDS"); + //Thread::sleep(5000); + logger.info(logtag(), "Erasing..."); + //TODO: Flash leds for a few seconds as a warning + eraseMemory(&result); + if (result != OpResultFlags::RESULT_OK) + { + logger.error(logtag(), "Error: result=", logging::hex(result)); + return false; + } + logger.info(logtag(), "Writing flash header..."); + FlashHeader header = { }; + header.first_boot = FIRST_BOOT_KEY; + //header.magic_word = MAGIC_WORD; + + if (!writeFlashHeader(header, false)) + { + logger.error(logtag(), "Error: result=", logging::hex(result)); + return false; + } + logger.info(logtag(), "Writing sector headers..."); + SectorHeader sheader; + sheader.content = SECTOR_EMPTY; + + for (uint32_t sector = 1; sector < SECTORS_NUM; sector++) + { + if (!writeSectorHeader(sector, sheader)) + { + logger.error(logtag(), "Error: result=", logging::hex(result), + " sector: ", sector); + return false; + } + } + logger.info(logtag(), "Success!"); + + return true; + } + + /** + * @brief Formats the memory. + * Use this only if the filesystem is in a good state: this fuction + * reads every sector header and erases only sector where + * header content != SECTOR_EMPTY + * + * @warning Use this only for testing! + */ + static bool fastFormat() + { + logger.warning(logtag(), "!!!THE MEMORY IS ABOUT TO BE FORMATTED!!!\n"); + + logger.info(logtag(),"Formatting...\n"); + //TODO: Flash leds for a few seconds as a warning + //Always erase the first sector + if(!erase(0, SUBSECTORS_PER_SECTOR)){ + logger.error(logtag(), "Couldn't erase sector 0"); + return false; + } + + //Then erase only the written/broken sectors + uint32_t s; + for (s = 1; s < SECTORS_NUM; s++) + { + SectorHeader header; + bool read_result = readSectorHeader(s, &header); + if (read_result && header.content != SECTOR_EMPTY) + { + //Erase data in this sector + if(!erase(s * SUBSECTORS_PER_SECTOR, (s + 1) * SUBSECTORS_PER_SECTOR)){ + logger.error(logtag(), "Couldn't erase sector ", s); + return false; + } + + //Rewrite the header + SectorHeader sheader; + sheader.content = SECTOR_EMPTY; + if (!writeSectorHeader(s, sheader)) + { + logger.error("Error writing sector header in sector ", s); + return false; + } + }else if(!read_result) + { + logger.error(logtag(), "Error reading sector header in sector ", s); + return false; + } + } + + //Format the memory + FlashHeader header { }; + header.first_boot = FIRST_BOOT_KEY; + + if(!writeFlashHeader(header, false)){ + logger.error(logtag(),"Couldn't write flash header."); + return false; + } + + return true; + } + + static bool eraseMemory(uint8_t* result) + { + return erase(0, SUBSECTORS_NUM); + } + + /** + * Erases the memory between the given subsectors + * Only erases a subsector if data has been written into it, + * to avoid wasting erase cycles. + * + * @brief Erases The memory between the given bounds. + * @param result Erase successful or not + * @param from Address belonging to the first subsector to be erased + * @param to Address belonging to the last subsector to be erased + * @return + */ + static bool erase(uint32_t from_subsector, uint32_t to_subsector) + { + uint8_t result; + FlashDriverType* flash = Singleton<FlashDriverType>::getInstance(); + uint8_t* buffer = new uint8_t[PAGE_SIZE]; + + std::stringstream stream; //To save strings to write in a single log line + stream << "Erasing subsectors: "; + + //Erase from the start of the subsector corresponding to "from" + uint32_t from_addr = from_subsector * SUBSECTOR_SIZE; + + //To the end of the subsector corresponding to "to". + uint32_t to_addr = to_subsector * SUBSECTOR_SIZE; + + //Read memory in blocks of PAGE_SIZE, if a byte is != 0xFF, erase the + //subsector and start reading again from the next one. + while (from_addr < to_addr) + { + flash->read(&result, from_addr, buffer, PAGE_SIZE); + + if (result == OpResultFlags::RESULT_OK) + { + bool erase = false; + for (uint32_t i = 0; i < PAGE_SIZE; i++) + { + erase = buffer[i] != 0xFF; + + if (erase) + break; + } + + if (erase) + { + stream << from_addr / SUBSECTOR_SIZE << ", "; + + flash->enableErase(); + flash->eraseSubsector(&result, from_addr); + if (result == OpResultFlags::RESULT_OK) + { + //Move to the beginning of the next subsector + from_addr = (from_addr / SUBSECTOR_SIZE + 1) * SUBSECTOR_SIZE; + } else + { + logger.error(logtag(), "Error erasing subsector: result=0x", + logging::hex(result)); + break; + } + + } else + { + from_addr = from_addr + PAGE_SIZE; + } + } else + { + logger.error(logtag(), "Error reading memory: result=0x", + logging::hex(result)); + break; + } + } + + stream << "--"; + + logger.info(logtag(), stream.str()); + delete buffer; + + return from_addr >= to_addr; + } + + static bool isMemoryClean(uint8_t* result) + { + FlashDriverType* flash = Singleton<FlashDriverType>::getInstance(); + uint8_t buffer[BLOCK_SIZE]; + + //Check from the start of the subsector corresponding to "from" + uint32_t addr = SUBSECTOR_SIZE; + + bool clean = true; + while (addr < MEMORY_SIZE) + { + if (addr % SECTOR_SIZE == 0) + { + SectorHeader header; + bool read_result = readSectorHeader(addr / SECTOR_SIZE, &header); + if (!read_result || header.content != SECTOR_EMPTY) + { + logger.warning(logtag(), "Wrong sector header @", addr); + clean = false; + } + } else + { + flash->read(result, addr, buffer, BLOCK_SIZE); + if (*result == OpResultFlags::RESULT_OK) + { + for (uint32_t i = 0; i < BLOCK_SIZE; i++) + { + if (buffer[i] != 0xFF) + { + logger.warning(logtag(), "Dirty block @", addr); + clean = false; + break; + } + } + } else + { + logger.warning(logtag(), "Error reading at %d\n", addr); + break; + } + } + + addr = addr + BLOCK_SIZE; + } + + if (clean) + { + logger.info(logtag(), "Memory is clean."); + } + return clean; + } + + private: + + FlashController() + { + flash_ = Singleton<FlashDriverType>::getInstance(); + } + + /** + * @brief Restores the state after a reboot. + * + * Restores the state after a reboot. + * Sequentially search for the first empty sector and update block_index_ + * accordingly + * + * @return Empty sector found or not + */ + bool recoverFromReboot() + { + //Start from the second sector + uint32_t sector = 1; + SectorHeader header; + + do + { + bool r = readSectorHeader(sector, &header); + if (r && header.content == SECTOR_EMPTY) + { + block_index_ = sector * BLOCKS_PER_SECTOR; + return true; + } + } while (++sector < SECTORS_NUM); + + return false; + } + + /** + * Updates block_index_ to point to the next available sector. + * + * @return True if a new sector is found + */ + bool gotoNewSector() + { + uint32_t sector = block_index_ / BLOCKS_PER_SECTOR; + SectorHeader readHeader, writeHeader; + + //Look for an empty sector + bool read_result; + int i = 0; + do + { + read_result = readSectorHeader(sector, &readHeader); + + } while (((read_result && readHeader.content != SECTOR_EMPTY) || !read_result) + && ++sector < SECTORS_NUM && ++i < MAX_SECTOR_ITERATIONS); + + block_index_ = sector * BLOCKS_PER_SECTOR; + + if (read_result && readHeader.content == SECTOR_EMPTY) + { + writeHeader.content = SECTOR_WRITTEN; + + if (!writeSectorHeader(sector, writeHeader)) + { + logger.error(logtag(), "Error writing sector header."); + return false; + } + block_index_++; + } else + { + logger.error(logtag(), "Error: could not find empty sector to write in."); + return false; + } + + return true; + } + + static std::string logtag() + { + return "FlashCTRL"; + } + + FlashDriverType* flash_; + uint32_t block_index_ = 0; + //Value will be changed if initialization is successful + Status status_ = Status::INITIALIZATION_ERROR; + +}; + +} //namespace flashmemory + +#endif /* FLASHCONTROLLER_H */ diff --git a/src/shared/drivers/memory/FlashDriver.h b/src/shared/drivers/memory/FlashDriver.h new file mode 100644 index 0000000000000000000000000000000000000000..5f94ae7419afa544701531c64e0f326574239e1a --- /dev/null +++ b/src/shared/drivers/memory/FlashDriver.h @@ -0,0 +1,615 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + + +#ifndef FLASHDRIVER_H +#define FLASHDRIVER_H + +#include <miosix.h> +#include <Singleton.h> +#include <diagnostic/NewLogger.h> +#include <string> + +using miosix::Thread; +using miosix::Mode; + +using logging::logger; + +namespace flashmemory { + +static const uint32_t PAGES_PER_SUBSECTOR = 16U; +static const uint32_t PAGES_PER_SECTOR = 256U; +static const uint32_t SUBSECTORS_PER_SECTOR = 16U; + +static const uint32_t SUBSECTORS_NUM = 16384U; +static const uint32_t SECTORS_NUM = 1024U; +static const uint32_t PAGE_SIZE = 256U; + +static const uint32_t SUBSECTOR_SIZE = PAGE_SIZE*PAGES_PER_SUBSECTOR; +static const uint32_t SECTOR_SIZE = PAGE_SIZE*PAGES_PER_SECTOR; + +static const uint32_t BANK_SIZE = SECTOR_SIZE*SECTORS_NUM/2; +static const uint32_t MEMORY_SIZE = SECTOR_SIZE*SECTORS_NUM; + + +/** + * @brief Definitions for results of various flash operations. + * + * Definitions for result flags of various flash operations. + */ +enum OpResultFlags +{ + RESULT_OK = 0x00, + + + RESULT_F_OUT_OF_MEMORY = 0x01, + RESULT_F_CHECK_FAIL = 0x04, + + + //Bits reserved for future use + RESULT_F_UNUSED_1 = 0x08, + RESULT_F_UNUSED_2 = 0x40, + RESULT_F_UNUSED_3 = 0x80, + + + /** + * These bits are in the same positions of the corresponding bits in the + * FLAG STATUS REGISTER + */ + RESULT_F_PROTECTION_ERROR = 0x02, + RESULT_F_PROGRAM_ERROR = 0x10, + RESULT_F_ERASE_ERROR = 0x20 +}; + + +/* + * TODO: + * -Controlla significato bit Vpp nel Flag status register + * -Casi in cui eseguire _write_disable() + * -Velocizzare il clock software per il factory reset + * -Controlla se bisogna davvero leggere due volte il flag_status_register per + * vedere se una scrittura ad un registro e' stata completata + * + */ + +template<typename Bus> +class FlashDriver : Singleton<FlashDriver<Bus>> +{ + typedef Singleton<FlashDriver<Bus>> SingletonType; + + friend class Singleton<FlashDriver<Bus>>; + + template<typename> + friend class FlashDriverTest; + +public: + + /** + * @brief Read n bytes into a buffer, starting from the specified address + * + * @param result + * @param address Starting address + * @param buf Buffer to read data into + * @param size Number of bytes to read + */ + void read(uint8_t *result, uint32_t address, uint8_t* buf, uint32_t size) + { + if(address + size > MEMORY_SIZE){ + *result = RESULT_F_OUT_OF_MEMORY; + return; + } + + uint32_t rsize; + if(address < BANK_SIZE) + rsize = std::min(size, BANK_SIZE - address); + else + rsize = size; + + uint8_t addr_buf[4]; + addrToBuf(addr_buf, address); + + Bus::read(READ, addr_buf, buf, 4, rsize); + + size -= rsize; + + //This means that we reached the end of the first die but we still need + //to read some data. + if(size > 0) + { + address += rsize; + buf += rsize; + + addrToBuf(addr_buf, address); + + //Read the remaining bytes + Bus::read(READ, addr_buf, buf, 4, size); + } + + *result = RESULT_OK; + } + + /** + * @brief Writes data on the flash starting from the specified address. + * + * @param address + * @param data + * @param size + */ + void write(uint8_t *result, uint32_t address, uint8_t* data, uint32_t size) + { + //Do not write outside of the memory + if(address + size > MEMORY_SIZE) + { + *result = RESULT_F_OUT_OF_MEMORY; + return; + } + + uint8_t addr_buf[4], status; + + uint32_t next_page, wsize; + + do { + next_page = (address / 256)*256 + 256; + + wsize = std::min(next_page - address, size); + + addrToBuf(addr_buf, address); + writeEnable(); + Bus::write(PROGRAM_PAGE, addr_buf, data, 4, wsize); + + do { + status = readFlagStatusReg(); + }while((status & FSR_PRG_ERS_CTRL) == 0); + + data += wsize; + size -= wsize; + address = next_page; + + *result = status & (FSR_PROGRAM | FSR_PROTECTION); + }while(size > 0 && *result == RESULT_OK); + + //Clear flag status register if an error occurred + if(*result != RESULT_OK) + { + clearFlagStatusReg(); + + //Manually reset write_enable latch + if((*result & RESULT_F_PROTECTION_ERROR) > 0) + { + writeDisable(); + } + } + } + + /** + * @brief Writes data on the flash starting from the specified address, then + * checks if everything was written correctly. + * + * @param address + * @param data + * @param size + */ + void writeAndCheck(uint8_t *result, uint32_t address, uint8_t* data, + uint32_t size){ //TODO: size_t + write(result,address,data,size); + if(*result == RESULT_OK){ + uint8_t *check = new uint8_t[size]; + read(result, address, check, size); + for(uint32_t i = 0; i < size; i++){ + if(*check++ != *data++){ + //Something was not written/read correctly. + *result = RESULT_F_CHECK_FAIL; + break; + } + } + } + } + + /** + * @brief Writes data on the flash starting from the specified address, only + * if the provided data fits in a single page. + * + * Writes data on the flash starting from the specified address, only + * if the provided data fits in a single page. If too much data is provided + * nothing will be written and result will be set to RESULT_F_OUT_OF_MEMORY + * + * @param result + * @param address + * @param data + * @param size + */ + void programPage(uint8_t *result, uint32_t address, uint8_t* data, uint32_t size) + { + //Do now wrap around a page and do not try to write outside of the memory + uint32_t next_page = (address / 256)*256 + 256; + if(address + size > next_page || address > MEMORY_SIZE) + { + *result = RESULT_F_OUT_OF_MEMORY; + return; + } + + uint8_t addr_buf[4], status; + addrToBuf(addr_buf, address); + + writeEnable(); + Bus::write(PROGRAM_PAGE, addr_buf, data, 4, size); + + do { + status = readFlagStatusReg(); + }while((status & FSR_PRG_ERS_CTRL) == 0); + + *result = status & (FSR_PROGRAM | FSR_PROTECTION); + + //Clear flag status register if an error has been encountered + if(*result != RESULT_OK) + { + clearFlagStatusReg(); + + //Manually reset write_enable latch + if((*result & RESULT_F_PROTECTION_ERROR) > 0) + { + writeDisable(); + } + } + } + + /** + * @brief Enables or disables read only mode + */ + void setReadOnly(bool readonly = true) + { + read_only_ = readonly; + } + + + bool isReadOnly() const + { + return read_only_; + } + + /** + * @brief Enable the next erase operation. Call this before every erase op. + */ + void enableErase() + { + writeEnable(); + } + + /** + * @brief Erases the subsector containing the specified address. For the operation + * to be successful, enable_erase_op must be called before this. + * + * @warning Estimated execution time is slightly less than 1 second. + * + * @param result + * @param address + */ + void eraseSubsector(uint8_t* result, uint32_t address) + { + erase(result, SUBSECTOR_ERASE, address); + } + + /** + * @brief Erases the sector containing the specified address. For the operation + * to be successful, enable_erase_op must be called before this. + * @warning Estimated execution time is about 2 seconds. + * + * @param result + * @param address + */ + void eraseSector(uint8_t* result, uint32_t address) + { + erase(result, SECTOR_ERASE, address); + } + + /** + * @brief Erases the entire die containing the specified address. For the operation + * to be successful, enable_erase_op must be called before this. + * @warning Estimated execution time is more than 4 minutes. + * + * @param result + * @param die: 0 to erase the first die, 1 to erase the second. + */ + void eraseDie(uint8_t* result, uint8_t die) + { + if(die == 0){ + erase(result, DIE_ERASE, 0); + }else if(die == 1){ + erase(result, DIE_ERASE, BANK_SIZE); + } + } + + /** + * Reads id information for this flash memory. + * @param buf Buffer with at least 20 bytes + */ + void readId(uint8_t* buf) + { + Bus::read(READ_ID, buf, 20); + } + + + /** + * @brief Software reset for the memory. All volatile bits are set to their + * default value. + */ + void softReset() + { + Bus::write(RESET_ENABLE); + usleep(5); + Bus::write(RESET_MEMORY); + waitUntilReady(); + } + + /** + * If the flash memory is in a bad state and can't be recovered by any + * other means, run this, then set CONFIG_REGISTER to 0xFFFF. + */ + template <class CS, class MOSI, class CLK> + static void factoryReset() + { + { + miosix::FastInterruptDisableLock dLock; + CS::mode(Mode::OUTPUT); + MOSI::mode(Mode::OUTPUT); + CLK::mode(Mode::OUTPUT); + } + MOSI::high(); + Thread::sleep(20); + + + factoryResetSequence<CS, CLK>(7); + factoryResetSequence<CS, CLK>(13); + factoryResetSequence<CS, CLK>(17); + factoryResetSequence<CS, CLK>(25); + factoryResetSequence<CS, CLK>(33); + + MOSI::low(); + Thread::sleep(50); + MOSI::high(); + factoryResetSequence<CS, CLK>(8); + + MOSI::low(); + CS::low(); + } + +private: + FlashDriver(){ + waitUntilReady(); + + uint8_t buf[20]; + readId(buf); + + if(buf[0] != 0x20) + { + logger.critical(logtag(), "Wrong id on FlashDriver instantiation"); + } + + writeStatusReg(0x00); + + clearFlagStatusReg(); + + + uint16_t config = readConfigReg(); + + //Last bit to 0 to enable 4 byte address mode + if(config != 0xFFFE) writeConfigReg(0xFFFE); + } + + void erase(uint8_t *result, uint8_t erase_cmd, uint32_t address) + { + if(address > MEMORY_SIZE) + { + *result = RESULT_F_OUT_OF_MEMORY; + return; + } + + uint8_t addr_buf[4], status; + addrToBuf(addr_buf, address); + Bus::write(erase_cmd, addr_buf, 4); + + do { + status = readFlagStatusReg(); + }while((status & FSR_PRG_ERS_CTRL) == 0); + + *result = status & (FSR_ERASE | FSR_PROTECTION); + + //Clear flag status register if an error has been encountered + if(*result != RESULT_OK) + { + clearFlagStatusReg(); + + //Manually reset write_enable latch + if((*result & RESULT_F_PROTECTION_ERROR) > 0) + { + writeDisable(); + } + } + } + + /** + * @brief Checks if a program operation is still in progress. + * @param result + */ + bool isBusy() + { + return readFlagStatusReg() & FSR_PRG_ERS_CTRL; + } + + /** + * @brief Waits until the memory is ready to perform a new write/erase op. + * @param result + */ + void waitUntilReady() + { + while((readFlagStatusReg() & FSR_PRG_ERS_CTRL) == 0) + { + + } + } + + uint8_t readStatusReg() + { + uint8_t ret = Bus::read(READ_STATUS_REG); + return ret; + } + + void writeStatusReg(uint8_t val) + { + writeEnable(); + Bus::write(WRITE_STATUS_REG, val); + writeDisable(); + waitUntilReady(); + } + + uint8_t readFlagStatusReg() + { + uint8_t ret = Bus::read(READ_FLAG_STATUS_REG); + return ret; + } + + void clearFlagStatusReg() + { + Bus::write(CLEAR_FLAG_STATUS_REG); + } + + uint16_t readConfigReg() + { + uint8_t buf[2]; + Bus::read(READ_NV_CONFIG_REG, buf, 2); + + uint16_t res = buf[0] | (buf[1] << 8); + + return res; + } + + void writeConfigReg(uint16_t val) + { + /* + * Mask some of the bits of the config register because + * we don't want to change them by accident. + */ + val = val | 0x0FEC; + + uint8_t buf[2]; + buf[0] = val & 0xFF; + buf[1] = (val >> 8) & 0xFF; + + writeEnable(); + Bus::write(WRITE_NV_CONFIG_REG, buf, 2); + writeDisable(); + waitUntilReady(); + } + + void writeEnable() + { + if(!read_only_) { + Bus::write(WRITE_ENABLE); + } + } + + void writeDisable() + { + Bus::write(WRITE_DISABLE); + } + + static void addrToBuf(uint8_t* buf, uint32_t addr) + { + for(int i = 0; i < 4; i++) + { + buf[3 - i] = (addr >> 8*i) & 0xFF; + } + } + + template <class CS, class CLK> + static void factoryResetSequence(int cycles) + { + CS::low(); + Thread::sleep(5); + for(int i = 0; i < cycles; i++) + { + CLK::high(); + Thread::sleep(5); + CLK::low(); + Thread::sleep(5); + } + CS::high(); + Thread::sleep(20); + } + + static std::string logtag() + { + return "FLASH_DRIVER"; + } + + enum Commands + { + READ_ID = 0x9F, + + WRITE_ENABLE = 0x06, + WRITE_DISABLE = 0x04, + + READ_STATUS_REG = 0x05, + WRITE_STATUS_REG = 0x01, + + READ_FLAG_STATUS_REG = 0x70, + CLEAR_FLAG_STATUS_REG = 0x50, + + //non-volatile configuration register + READ_NV_CONFIG_REG = 0xB5, + WRITE_NV_CONFIG_REG = 0xB1, + + //volatile configuration register + WRITE_VOL_CONFIG_REG = 0x85, + READ_VOL_CONFIG_REG = 0x81, + + READ_EXT_ADDRESS_REG = 0xC8, + WRITE_EXT_ADDRESS_REG = 0xC5, + + RESET_ENABLE = 0x66, + RESET_MEMORY = 0x99, + + READ = 0x03, + PROGRAM_PAGE = 0x02, + + SUBSECTOR_ERASE = 0x20, + SECTOR_ERASE = 0xD8, + DIE_ERASE = 0xC4 + }; + + bool read_only_ = false; + + /** + * Flag status register bit definitions + */ + static const uint8_t FSR_ADDRESSING_MODE = 0x01; + static const uint8_t FSR_PROTECTION = 0x02; + static const uint8_t FSR_PROGRAM_SUSPEND = 0x04; + static const uint8_t FSR_VPP = 0x08; + static const uint8_t FSR_PROGRAM = 0x10; + static const uint8_t FSR_ERASE = 0x20; + static const uint8_t FSR_ERASE_SUSPEND = 0x40; + static const uint8_t FSR_PRG_ERS_CTRL = 0x80; +}; + +} +#endif /* FLASHDRIVER_H */ diff --git a/src/shared/drivers/memory/FlashDriverInclude.h b/src/shared/drivers/memory/FlashDriverInclude.h new file mode 100644 index 0000000000000000000000000000000000000000..8770dcfebcea6fc187cc938064c50ec773219563 --- /dev/null +++ b/src/shared/drivers/memory/FlashDriverInclude.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2015-2017 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + + +#ifndef SRC_SHARED_DRIVERS_MEMORY_FLASHDRIVERINCLUDE_H_ +#define SRC_SHARED_DRIVERS_MEMORY_FLASHDRIVERINCLUDE_H_ + + +#ifdef FLASH_TEST + +#include <flashmemory/mockup/FlashDriver.h> + +#else + +#include <drivers/memory/FlashDriver.h> + +#endif /* FLASH_TEST */ + + +#endif /* SRC_SHARED_DRIVERS_MEMORY_FLASHDRIVERINCLUDE_H_ */ diff --git a/src/shared/drivers/memory/MultiFlashController.cpp b/src/shared/drivers/memory/MultiFlashController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f1bac737fe100aa5e37ae7c1e6f2c68e06d95857 --- /dev/null +++ b/src/shared/drivers/memory/MultiFlashController.cpp @@ -0,0 +1,106 @@ +/* Copyright (c) 2017 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#include "MultiFlashController.h" +#include <Singleton.h> +#include <diagnostic/NewLogger.h> + +using logging::logger; +/* stub crc class */ + +namespace flashmemory +{ +class CRCHelper +{ + public: + static uint16_t calcCRC16(uint8_t* data, uint32_t size) + { + return 0xABCD; + } +}; + +//miosix::Queue<FlashDataBlock,20> MultiFlashController::block_queue_; + +MultiFlashController::MultiFlashController() + : ActiveObject::ActiveObject() +{ + flash0_ = Singleton<FlashController<MemoryBus0>>::getInstance(); + + //block_queue_ = new miosix::Queue<FlashDataBlock, 5>(); +} + +MultiFlashController::~MultiFlashController() +{ +} + +/** + * @brief Infinite loop that process the block queue. + */ +void MultiFlashController::run() +{ + FlashDataBlock to_flash; + while (true) + { + block_queue_.waitUntilNotEmpty(); + + block_queue_.get(to_flash); + + bool result = true; + + result = result && flash0_->writeDataBlock(&to_flash); + + //TODO: Also write on 2nd and 3rd flash memory + //result = result && flash1_->writeBlock(&to_flash); + //result = result && flash1_->writeBlock(&to_flash); + + if (!result) + { + logger.error(logtag(), "Error writing blocks!"); + } + + } +} + +/** + * @brief Insert into the fixed queue a new FlashDataBlock to save in flash. + * + * @param buffer data do be saved + * @param size the size of the data (must be < FLASH_BUFFER_SIZE) + */ +void MultiFlashController::addData(const uint8_t* buffer, size_t size) +{ + /* limit data to FLASH_BUFFER_SIZE */ + uint16_t safe_size = (size <= FLASH_BUFFER_SIZE ? size : FLASH_BUFFER_SIZE); + FlashDataBlock to_save = { }; + + // create the Flash Data Block. + to_save.id = block_counter++; + to_save.timestamp = miosix::getTick(); + memcpy((uint8_t*) to_save.buffer, (uint8_t*) buffer, safe_size); + + //TODO crc of the whole block with toSave.crc = 0x0000? + to_save.crc = CRCHelper::calcCRC16((uint8_t*) to_save.buffer, safe_size); + + block_queue_.put(to_save); +} + +} //namespace flashmemory diff --git a/src/shared/drivers/memory/MultiFlashController.h b/src/shared/drivers/memory/MultiFlashController.h new file mode 100644 index 0000000000000000000000000000000000000000..32d7031655389f60c56c09f32cf778ba1df4b8ff --- /dev/null +++ b/src/shared/drivers/memory/MultiFlashController.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2017 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ +#ifndef MULTIFLASHCONTROLLER_H +#define MULTIFLASHCONTROLLER_H + +#include <Common.h> +#include <ActiveObject.h> +#include <drivers/BusTemplate.h> +#include "FlashController.h" + +using miosix::Gpio; + +//Forward declaration +namespace testing +{ +namespace flashmemorytests +{ +template<typename > +class FlashTest; +} +} + +namespace flashmemory +{ + +class MultiFlashController : public Singleton<MultiFlashController>, ActiveObject +{ + friend class Singleton<MultiFlashController>; + + template<typename > + friend class testing::flashmemorytests::FlashTest; + + typedef Gpio<GPIOA_BASE, 5> GpioSck; + typedef Gpio<GPIOA_BASE, 6> GpioMiso; + typedef Gpio<GPIOA_BASE, 7> GpioMosi; + typedef BusSPI<1, GpioMosi, GpioMiso, GpioSck> bus; + + typedef Gpio<GPIOC_BASE, 15> CS_FLASH0; + + public: + typedef ProtocolSPI<bus, CS_FLASH0> MemoryBus0; + + virtual ~MultiFlashController(); + + void addData(const uint8_t* buffer, size_t size); + + protected: + + void run() override; + + private: + MultiFlashController(); + + static std::string logtag() + { + return "MultiFlashCTRL"; + } + + uint32_t block_counter = 0; + miosix::Queue<FlashDataBlock,20> block_queue_; + + FlashController<MemoryBus0>* flash0_; + //FlashController<spi_flash0> flash1; //Change to spi_flash1 + //FlashController<spi_flash0> flash2; //Change to spi_flash2 +}; + +} //namespace flashmemory + +#endif /* MULTIFLASHCONTROLLER_H */ diff --git a/src/shared/drivers/modbus/ExceptionCodes.h b/src/shared/drivers/modbus/ExceptionCodes.h index 1397f36f635e5c27badf6095e20af135da826310..0290fd125a3aab91ac2e14d480fa3b80a908450e 100644 --- a/src/shared/drivers/modbus/ExceptionCodes.h +++ b/src/shared/drivers/modbus/ExceptionCodes.h @@ -1,6 +1,4 @@ -/* Modbus protocol exception codes - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/modbus/HooksInterface.h b/src/shared/drivers/modbus/HooksInterface.h index c40932bf89c0eafd231201a0e255a4210a68bbaa..375eef7d0213a0d7dbef88e860282abb4cd82d18 100644 --- a/src/shared/drivers/modbus/HooksInterface.h +++ b/src/shared/drivers/modbus/HooksInterface.h @@ -1,9 +1,4 @@ -/* Class that provides an interface made by a set of function interface used by - * Modbus slave engine to handle the server requests. - * By default the functions will return the error code "function unsupported", - * user must provide a correct implementation by subclassing this class - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -18,13 +13,19 @@ * * 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 + * 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. */ +/* Class that provides an interface made by a set of function interface used by + * Modbus slave engine to handle the server requests. + * By default the functions will return the error code "function unsupported", + * user must provide a correct implementation by subclassing this class + */ + #ifndef HOOKS_H #define HOOKS_H diff --git a/src/shared/drivers/modbus/Modbus.h b/src/shared/drivers/modbus/Modbus.h index 8f1b307d3346f833f08fb9316bf2898a29a091eb..ec01e2e3aba7a5eb2c98829065f6eea0bd9c12f1 100644 --- a/src/shared/drivers/modbus/Modbus.h +++ b/src/shared/drivers/modbus/Modbus.h @@ -1,6 +1,4 @@ -/* Container for all the headers, user has only to include this file - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/modbus/PDU.cpp b/src/shared/drivers/modbus/PDU.cpp index e4ea5675db1dc7a35b7b1aca031b96f301f2a37c..afa98b4b6b68f4ca0df7e0eb80dd518de44b4f0e 100644 --- a/src/shared/drivers/modbus/PDU.cpp +++ b/src/shared/drivers/modbus/PDU.cpp @@ -1,6 +1,4 @@ -/* Modbus protocol PDU data structure - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/modbus/PDU.h b/src/shared/drivers/modbus/PDU.h index 94e1df0f4d8befca6310689086a6cce1993fd330..c79cb7081a0f6c6a16a3785213417d7aa1adc753 100644 --- a/src/shared/drivers/modbus/PDU.h +++ b/src/shared/drivers/modbus/PDU.h @@ -1,6 +1,4 @@ -/* Modbus protocol PDU data structure - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/modbus/slave/RtuSlave.cpp b/src/shared/drivers/modbus/slave/RtuSlave.cpp index c48bb345a45ea23551e7121ce4c31d38aaa72907..661b850b11c8197652cc96841bca5cfa070be564 100644 --- a/src/shared/drivers/modbus/slave/RtuSlave.cpp +++ b/src/shared/drivers/modbus/slave/RtuSlave.cpp @@ -1,3 +1,25 @@ +/* Copyright (c) 2017 Skyward Experimental Rocketry + * Author: Silvano Seva + * + * 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. + */ + #include "RtuSlave.h" using namespace std; diff --git a/src/shared/drivers/modbus/slave/RtuSlave.h b/src/shared/drivers/modbus/slave/RtuSlave.h index ac8e301d35cd9d5b7bbd8071697e3689bf92e5f9..76fe486cb0d8dff4bee3faa8dddcfbb400dfe3d3 100644 --- a/src/shared/drivers/modbus/slave/RtuSlave.h +++ b/src/shared/drivers/modbus/slave/RtuSlave.h @@ -1,3 +1,25 @@ +/* Copyright (c) 2017 Skyward Experimental Rocketry + * Author: Silvano Seva + * + * 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. + */ + #ifndef RTUSLAVE_H #define RTUSLAVE_H diff --git a/src/shared/drivers/modbus/slave/SlaveEngine.cpp b/src/shared/drivers/modbus/slave/SlaveEngine.cpp index ad7e0dd6ad8cc38a4f8edb3256bba5d317b83565..96f825d141bd7cd82413bb897c50506462c49432 100644 --- a/src/shared/drivers/modbus/slave/SlaveEngine.cpp +++ b/src/shared/drivers/modbus/slave/SlaveEngine.cpp @@ -1,6 +1,4 @@ -/* Modbus protocol engine for slave modules - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/modbus/slave/SlaveEngine.h b/src/shared/drivers/modbus/slave/SlaveEngine.h index ca77fd62d8a86e4a05efe64acc0e7d8bf4ccec1c..c15111b3be5e6b59cba82e67227294b39c2c9f0d 100644 --- a/src/shared/drivers/modbus/slave/SlaveEngine.h +++ b/src/shared/drivers/modbus/slave/SlaveEngine.h @@ -1,6 +1,4 @@ -/* Modbus protocol engine for slave modules - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/modbus/slave/old/SlaveInterface.cpp b/src/shared/drivers/modbus/slave/old/SlaveInterface.cpp index f69174a654088d73a99301d75e00c0e7a013f845..d90b12457161b7f6a818b887f3234260f7e72073 100644 --- a/src/shared/drivers/modbus/slave/old/SlaveInterface.cpp +++ b/src/shared/drivers/modbus/slave/old/SlaveInterface.cpp @@ -1,6 +1,4 @@ -/* Serial driver for Modbus RTU slave device - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/modbus/slave/old/SlaveInterface.h b/src/shared/drivers/modbus/slave/old/SlaveInterface.h index b4646457e8669d4e0d71d87c3c82a0740953ae35..63495bdd2826e460da5f574f64760203899e6b61 100644 --- a/src/shared/drivers/modbus/slave/old/SlaveInterface.h +++ b/src/shared/drivers/modbus/slave/old/SlaveInterface.h @@ -1,6 +1,4 @@ -/* Serial driver for Modbus RTU slave device - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/modbus/slave/old/Timer.cpp b/src/shared/drivers/modbus/slave/old/Timer.cpp index a888516d663ceca10c88b92116125f22acf436eb..dc421894a6215dd38601eaa8f9572532d4b6d7aa 100644 --- a/src/shared/drivers/modbus/slave/old/Timer.cpp +++ b/src/shared/drivers/modbus/slave/old/Timer.cpp @@ -1,6 +1,4 @@ -/* Serial driver for Modbus RTU slave device - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/modbus/slave/old/Timer.h b/src/shared/drivers/modbus/slave/old/Timer.h index 1ffbaada525172ba354876a5601438f73da78755..1331e23769e5019d9fbceae4986e0b94d4760c54 100644 --- a/src/shared/drivers/modbus/slave/old/Timer.h +++ b/src/shared/drivers/modbus/slave/old/Timer.h @@ -1,6 +1,4 @@ -/* Serial driver for Modbus RTU slave device - * - * Copyright (c) 2017 Skyward Experimental Rocketry +/* Copyright (c) 2017 Skyward Experimental Rocketry * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/adc/AD7994.h b/src/shared/drivers/old_examples/AD7994/AD7994.h similarity index 98% rename from src/shared/drivers/adc/AD7994.h rename to src/shared/drivers/old_examples/AD7994/AD7994.h index 35d8f1abd7395b15b61db44016c3bc4866a772fe..44c72574ba7b8a472400f5fbb42373f041a892d4 100644 --- a/src/shared/drivers/adc/AD7994.h +++ b/src/shared/drivers/old_examples/AD7994/AD7994.h @@ -1,5 +1,5 @@ /* Copyright (c) 2018-2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/old_examples/AD7994/AD7994Data.h b/src/shared/drivers/old_examples/AD7994/AD7994Data.h new file mode 100644 index 0000000000000000000000000000000000000000..b6d844ab8f2672e97ac19c7229eb635c49f608f9 --- /dev/null +++ b/src/shared/drivers/old_examples/AD7994/AD7994Data.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#ifndef SRC_SHARED_DRIVERS_ADC_AD7994DATA_H +#define SRC_SHARED_DRIVERS_ADC_AD7994DATA_H + +#include <cstdint> +#include <ostream> +#include <string> + +struct AD7994Sample +{ + long long timestamp; + + uint8_t channel_id; // [1-4] + bool alert_flag; + uint16_t value; + + // Functions used to deserialize the binary logs into csv files + + static std::string header() { return "timestamp,ch_id,value,alert_flag\n"; } + + void print(std::ostream& os) const + { + os << timestamp << "," << channel_id << "," << value << "," + << (int)alert_flag << "\n"; + } +}; + +#endif \ No newline at end of file diff --git a/src/shared/drivers/piksi/piksi.cpp b/src/shared/drivers/old_examples/piksi/piksi.cpp similarity index 94% rename from src/shared/drivers/piksi/piksi.cpp rename to src/shared/drivers/old_examples/piksi/piksi.cpp index 356acc31b4912a82ffd0c479672fdd230333bd40..1a5e8d13a1b320fc0ad635eb8d083bd7a095acb9 100644 --- a/src/shared/drivers/piksi/piksi.cpp +++ b/src/shared/drivers/old_examples/piksi/piksi.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2017 Skyward Experimental Rocketry - * Authors: Federico Terraneo + * Author: Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -70,8 +70,8 @@ static unsigned short crc16piksi(const void *message, unsigned int length) Piksi::Piksi(const char *serialPath) { - memset(&data, 0, sizeof(GPSData)); - memset(&partialData, 0, sizeof(GPSData)); + memset(&data, 0, sizeof(PiksiGPSData)); + memset(&partialData, 0, sizeof(PiksiGPSData)); fd = open(serialPath, O_RDWR); if (fd < 0) @@ -99,9 +99,9 @@ Piksi::Piksi(const char *serialPath) pthread_cond_init(&cond, NULL); } -GPSData Piksi::getGpsData() +PiksiGPSData Piksi::getGpsData() { - GPSData result; + PiksiGPSData result; pthread_mutex_lock(&mutex); result = data; @@ -115,9 +115,9 @@ GPSData Piksi::getGpsData() return result; } -GPSData Piksi::waitForGpsData() +PiksiGPSData Piksi::waitForGpsData() { - GPSData result; + PiksiGPSData result; pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); result = data; @@ -188,8 +188,9 @@ unsigned int Piksi::lookForMessages(uint8_t *buffer, unsigned int size) if (messageSize > size) return consumed; // We don't have the entire message - uint16_t crc = - *reinterpret_cast<uint16_t *>(buffer + messageSize - crcSize); + uint16_t crc; + memcpy(&crc, buffer + messageSize - crcSize, sizeof(crc)); + if (crc16piksi(buffer + 1, messageSize - crcSize - 1) == crc) { processValidMessage(buffer, messageSize); diff --git a/src/shared/drivers/piksi/piksi.h b/src/shared/drivers/old_examples/piksi/piksi.h similarity index 95% rename from src/shared/drivers/piksi/piksi.h rename to src/shared/drivers/old_examples/piksi/piksi.h index 876a49e87f4d4e8863a06a486f11d9984cb04863..93926a82a71151aba6b7503e02b74926ee1bf34c 100644 --- a/src/shared/drivers/piksi/piksi.h +++ b/src/shared/drivers/old_examples/piksi/piksi.h @@ -1,5 +1,5 @@ /* Copyright (c) 2017 Skyward Experimental Rocketry - * Authors: Federico Terraneo + * Author: Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -24,7 +24,7 @@ #define PIKSI_H #include <pthread.h> -#include "contiguous_queue.h" +#include "utils/collections/contiguous_queue.h" #include "piksi_data.h" /** @@ -52,13 +52,13 @@ public: * use the timestamp field of the GPSData struct to know this. * \throws runtime_error is no data is available */ - GPSData getGpsData(); + PiksiGPSData getGpsData(); /** * \return the latest GPS data. If the GPS has yet got a fix or has lost * the fix, this function will block until the fix is regained */ - GPSData waitForGpsData(); + PiksiGPSData waitForGpsData(); /** * Destructor @@ -165,7 +165,7 @@ private: pthread_t thread; pthread_mutex_t mutex; pthread_cond_t cond; - GPSData data, partialData; + PiksiGPSData data, partialData; uint32_t gpsTimestamp = 0; bool pos = false; bool vel = false; diff --git a/src/shared/drivers/piksi/piksi_data.h b/src/shared/drivers/old_examples/piksi/piksi_data.h similarity index 92% rename from src/shared/drivers/piksi/piksi_data.h rename to src/shared/drivers/old_examples/piksi/piksi_data.h index 4def005a27f975394852448ff1383aa53373a7ba..a619a061b4ee97d614b6e75781da1062a38b6a13 100644 --- a/src/shared/drivers/piksi/piksi_data.h +++ b/src/shared/drivers/old_examples/piksi/piksi_data.h @@ -1,5 +1,5 @@ /* Copyright (c) 2017-2019 Skyward Experimental Rocketry - * Authors: Federico Terraneo + * Author: Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -25,7 +25,7 @@ /** * The GPS information */ -struct GPSData +struct PiksiGPSData { /// timestamp in ms (anakin time, not GPS time). getTick()-timestamp tells /// you how "old" the data is. diff --git a/src/shared/drivers/pwm/pwm.cpp b/src/shared/drivers/pwm/pwm.cpp index 67cdc26c3d8477cdd9b4e45f1ca84070197daecc..e0ca84caac219940d4454c6f5f05b4d4b13df1cc 100644 --- a/src/shared/drivers/pwm/pwm.cpp +++ b/src/shared/drivers/pwm/pwm.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/pwm/pwm.h b/src/shared/drivers/pwm/pwm.h index 9617f99e93a9fc5f0353b865361c9c1c4c678e74..ebc9078a2a9668b42f7ce3deb701093004628555 100644 --- a/src/shared/drivers/pwm/pwm.h +++ b/src/shared/drivers/pwm/pwm.h @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/pwm/pwmdata.h b/src/shared/drivers/pwm/pwmdata.h index 4eeed3a705d34b37e1ca44d0b91b5f6a488248db..c6f5ab777163b64c77946b5f6789d52fe96cf48d 100644 --- a/src/shared/drivers/pwm/pwmdata.h +++ b/src/shared/drivers/pwm/pwmdata.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/servo/servo.cpp b/src/shared/drivers/servo/servo.cpp index 6e6638a87de93d0b603fa98ee15827c3de2d79b4..fa605c26f021111b3f8e23bd1ddac360a9763c06 100644 --- a/src/shared/drivers/servo/servo.cpp +++ b/src/shared/drivers/servo/servo.cpp @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 @@ -22,10 +21,15 @@ */ #include "servo.h" + #include <cmath> +#include <cstring> // Initialize the pwm with 50 Hz frequency and 65535 levels of duty cycle -Servo::Servo(PWM::Timer t) : pwm(t, 50, 65535) { memset(&positions, 0, 4); } +Servo::Servo(PWM::Timer t) : pwm(t, 50, 65535) +{ + memset(&positions, 0, 4 * sizeof(float)); +} Servo::~Servo() {} @@ -68,7 +72,7 @@ void Servo::setMinPulseWidth(float min_pulse) void Servo::setMaxPulseWidth(float max_pulse) { this->max_pulse = fmax(1700.0f, fmin(2500.0f, max_pulse)); - + updateParameters(); } diff --git a/src/shared/drivers/servo/servo.h b/src/shared/drivers/servo/servo.h index 3d7770936e16773364b9edef2f16f67b01e5703b..5c9f10b812926ac733b4f9a8ab2b8875b4d5c288 100644 --- a/src/shared/drivers/servo/servo.h +++ b/src/shared/drivers/servo/servo.h @@ -1,7 +1,5 @@ -/** - * - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/spi/SPIBus.h b/src/shared/drivers/spi/SPIBus.h new file mode 100644 index 0000000000000000000000000000000000000000..4da39ae5e511997a54e2fcb0944c1eb27bd03742 --- /dev/null +++ b/src/shared/drivers/spi/SPIBus.h @@ -0,0 +1,302 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <cassert> + +#include "SPIBusInterface.h" + + +#ifndef USE_MOCK_PERIPHERALS +using SPIType = SPI_TypeDef; +#else +#include "test/FakeSpiTypedef.h" +using SPIType = FakeSpiTypedef; +#endif + +/** + * @brief Low level driver for communicating on a SPI Bus, provides + * SPIBusInterface. + */ +class SPIBus : public SPIBusInterface +{ +public: + /** + * @brief Instantiates a new SPIBus + * + * @param spi Pointer to the SPI peripheral to be used + */ + SPIBus(SPIType* spi) : spi(spi) {} + ~SPIBus() {} + + // Delete copy/move contructors/operators + SPIBus(const SPIBus&) = delete; + SPIBus& operator=(const SPIBus&) = delete; + + SPIBus(SPIBus&&) = delete; + SPIBus& operator=(SPIBus&&) = delete; + + /** + * @brief Disable bus configuration: calls to configure() will have no + * effect after calling this & the SPI peripheral will need to be configured + * manually. + */ + void disableBusConfiguration() { config_enabled = false; } + + /** + * @brief See SPIBusInterface::write() + */ + void write(uint8_t byte) override; + + /** + * @brief See SPIBusInterface::write() + */ + void write(uint8_t* data, size_t size) override; + + /** + * @brief See SPIBusInterface::read() + */ + uint8_t read() override; + + /** + * @brief See SPIBusInterface::read() + */ + void read(uint8_t* data, size_t size) override; + + /** + * @brief See SPIBusInterface::transfer() + */ + uint8_t transfer(uint8_t data) override; + + /** + * @brief See SPIBusInterface::transfer() + */ + void transfer(uint8_t* data, size_t size) override; + + /** + * @brief See SPIBusInterface::select() + */ + void select(GpioType& cs) override; + + /** + * @brief See SPIBusInterface::deselect() + */ + void deselect(GpioType& cs) override; + + /** + * @brief Obtains ownership of the bus, configuring it with the provided + * config. Since this implementation is not syncronized, if acquire() is + * called on an already locked bus, it will: + * - FAIL in DEBUG mode + * - Do nothing when NOT in DEBUG mode + * + * Use SyncedSPIBus if you need to synchronize access to the bus. + */ + void acquire(SPIBusConfig config) override; + +protected: + /** + * Writes a single byte on the SPI bus. + * + * @param byte Pointer to the byte to be written + */ + void write(uint8_t* byte); + + /** + * Reads a single byte from the SPI bus. + * + * @param byte Pointer to the byte where the read data will be stored + */ + void read(uint8_t* byte); + + /** + * Full duplex transfer. Writes a single byte on the SPI bus and replaces + * its content with the received data + * + * @param byte Pointer to the byte to be transfered + */ + void transfer(uint8_t* byte); + + void configure(SPIBusConfig new_config); + + SPIType* spi; + + SPIBusConfig config{}; + bool config_enabled = true; + bool first_config_applied = false; +}; + +// Defined here and not in the .cpp to make them inline + +inline void SPIBus::write(uint8_t data) { write(&data); } + +inline void SPIBus::write(uint8_t* data, size_t size) +{ + for (size_t i = 0; i < size; ++i) + { + write(data + i); + } +} + +inline uint8_t SPIBus::read() +{ + uint8_t data; + read(&data); + + return data; +} + +inline void SPIBus::read(uint8_t* data, size_t size) +{ + for (size_t i = 0; i < size; ++i) + { + read(data + i); + } +} + +inline uint8_t SPIBus::transfer(uint8_t data) +{ + transfer(&data); + return data; +} + +inline void SPIBus::transfer(uint8_t* data, size_t size) +{ + for (size_t i = 0; i < size; ++i) + { + transfer(data + i); + } +} + +inline void SPIBus::select(GpioType& cs) +{ + cs.low(); + if (config.cs_setup_time_us > 0) + { + delayUs(config.cs_setup_time_us); + } +} + +inline void SPIBus::deselect(GpioType& cs) +{ + if (config.cs_hold_time_us > 0) + { + delayUs(config.cs_hold_time_us); + } + cs.high(); +} + +inline void SPIBus::write(uint8_t* byte) +{ + // Wait until the peripheral is ready to transmit + while ((spi->SR & SPI_SR_TXE) == 0) + ; + // Write the byte in the transmit buffer + spi->DR = *byte; + + // Wait until byte is transmitted + while ((spi->SR & SPI_SR_RXNE) == 0) + ; + + // Clear the RX buffer by accessing the DR register + (void)spi->DR; +} + +inline void SPIBus::transfer(uint8_t* byte) +{ + // Wait until the peripheral is ready to transmit + while ((spi->SR & SPI_SR_TXE) == 0) + ; + // Write the byte in the transmit buffer + spi->DR = *byte; + + // Wait until byte is transmitted + while ((spi->SR & SPI_SR_RXNE) == 0) + ; + + // Store the received data in the byte + *byte = (uint8_t)spi->DR; +} + +inline void SPIBus::read(uint8_t* byte) +{ + // Wait until the peripheral is ready to transmit + while ((spi->SR & SPI_SR_TXE) == 0) + ; + // Write 0 in the transmit buffer + spi->DR = 0; + + // Wait until byte is transmitted + while ((spi->SR & SPI_SR_RXNE) == 0) + ; + + // Store the received data in the byte + *byte = (uint8_t)spi->DR; +} + +inline void SPIBus::acquire(SPIBusConfig new_config) +{ + // Assert that the bus is not already acquired. + // This bus is not syncronized: fail if someone tries to take ownership when + // the bus is already being used. Use SyncedSPIBus if you need to + // synchronize access to the bus +#ifdef DEBUG + assert(isBusy() == false); +#endif + + SPIBusInterface::acquire(new_config); + + configure(new_config); +} + +inline void SPIBus::configure(SPIBusConfig new_config) +{ + // Reconfigure the bus only if config enabled. Do not reconfigure if already + // in the correct configuration. + if (config_enabled && (!first_config_applied || new_config != config)) + { + first_config_applied = true; + config = new_config; + + // Wait until the peripheral is done before changing configuration + while ((spi->SR & SPI_SR_TXE) == 0) + ; + while ((spi->SR & SPI_SR_BSY) > 0) + ; + + spi->CR1 = 0; + + // Configure CPOL & CPHA bits + spi->CR1 |= static_cast<uint32_t>(config.mode); + + // Configure clock division (BR bits) + spi->CR1 |= static_cast<uint32_t>(config.clock_div); + + // Configure LSBFIRST bit + spi->CR1 |= static_cast<uint32_t>(config.bit_order); + + spi->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM // Use software chip-select + | SPI_CR1_MSTR // Master mode + | SPI_CR1_SPE; // Enable SPI + } +} \ No newline at end of file diff --git a/src/shared/drivers/spi/SPIBusInterface.h b/src/shared/drivers/spi/SPIBusInterface.h new file mode 100644 index 0000000000000000000000000000000000000000..8ac8beb9cc47484c4198eda71e61321aab3616c1 --- /dev/null +++ b/src/shared/drivers/spi/SPIBusInterface.h @@ -0,0 +1,284 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <miosix.h> + +#include <cstdint> +#include <cstdio> + +using miosix::delayUs; +using miosix::GpioPin; + +#ifndef USE_MOCK_PERIPHERALS +using GpioType = GpioPin; +#else +#include "utils/testutils/MockGpioPin.h" +using GpioType = MockGpioPin; +#endif + +/** + * @brief SPI Clock divider. + * SPI clock frequency will be equal to the SPI peripheral bus clock speed (see + * datasheet) divided by the value specified in this enum. + * + * Eg: DIV_2 --> spi clock freq = f_PCLK / 2 + * + * See register CR1 of the SPI peripheral on the reference manual for further + * information. + */ +enum class SPIClockDivider : uint8_t +{ + DIV2 = 0x00, + DIV4 = 0x08, + DIV8 = 0x10, + DIV16 = 0x18, + DIV32 = 0x20, + DIV64 = 0x28, + DIV128 = 0x30, + DIV256 = 0x38, +}; + +/** + * @brief SPI Mode. + * See slave device datasheet for information on which one to use. + */ +enum class SPIMode : uint8_t +{ + MODE0 = 0, ///> CPOL = 0, CPHA = 0 + MODE1 = 1, ///> CPOL = 0, CPHA = 1 + MODE2 = 2, ///> CPOL = 1, CPHA = 0 + MODE3 = 3 ///> CPOL = 1, CPHA = 1 +}; + +/** + * @brief SPI Bit Order + * See register CR1 of the SPI peripheral on the reference manual for further + * information. + */ +enum class SPIBitOrder : uint8_t +{ + MSB_FIRST = 0, + LSB_FIRST = 0x80 +}; + +/** + * @brief SPI Bus configuration for a specific slave. + * See slave datasheet for information on how to populate this struct + */ +struct SPIBusConfig +{ + SPIClockDivider clock_div = + SPIClockDivider::DIV256; ///> Peripheral clock division + SPIMode mode = SPIMode::MODE0; ///> Clock polarity (0 - 1) + SPIBitOrder bit_order = SPIBitOrder::MSB_FIRST; ///> MSB or LSB first + + unsigned int cs_setup_time_us = 0; ///> How long to wait before starting a + ///> a trasmission after CS is set (us) + unsigned int cs_hold_time_us = 0; ///> How long to hold cs after the end + ///> of a trasmission (us) + + // Custom comparison operator + bool operator==(const SPIBusConfig& other) const + { + // Compare member-by-member + // clang-format off + return clock_div == other.clock_div + && mode == other.mode + && bit_order == other.bit_order + && cs_setup_time_us == other.cs_setup_time_us + && cs_setup_time_us == other.cs_hold_time_us; + // clang-format on + } + + bool operator!=(const SPIBusConfig& other) const + { + return !(*this == other); + } +}; + +/** + * @brief Interface for low level access of a SPI bus + */ +class SPIBusInterface +{ +public: + SPIBusInterface() {} + + virtual ~SPIBusInterface() {} + + // Delete copy/move contructors/operators + SPIBusInterface(const SPIBusInterface&) = delete; + SPIBusInterface& operator=(const SPIBusInterface&) = delete; + + SPIBusInterface(SPIBusInterface&&) = delete; + SPIBusInterface& operator=(SPIBusInterface&&) = delete; + + /** + * @brief Writes a single \p byte to the bus. + * + * @param byte Byte to write + */ + virtual void write(uint8_t byte) = 0; + + /** + * @brief Writes \p data to the bus. + * + * @param data Buffer containing data to write + * @param size Number of bytes to write + */ + virtual void write(uint8_t* data, size_t size) = 0; + + /** + * @brief Reads a single byte from the bus. + * @return Byte read from the bus + */ + virtual uint8_t read() = 0; + + /** + * @brief Reads \p size bytes from the SPI bus, putting them in \p data. + * + * @param data Buffer to be filled with received data + * @param size Number of bytes to receive + */ + virtual void read(uint8_t* data, size_t size) = 0; + + /** + * @brief Full duplex transmission on the SPI bus. + * A \p byte is written on the bus and a byte is read and returned + * + * @param byte Byte to write + * @return Data read from the bus + */ + virtual uint8_t transfer(uint8_t byte) = 0; + + /** + * @brief Full duplex transmission on the SPI bus. + * \p data is written on the bus and its contents are then replaced with the + * received bytes. + * + * @param data Buffer containing data to transfer + * @param size Number of bytes to transfer + */ + virtual void transfer(uint8_t* data, size_t size) = 0; + + /** + * @brief Selects the slave + * + * @param cs Chip select pin for the slave + */ + virtual void select(GpioType& cs) = 0; + + /** + * @brief Deselects the slave + * + * @param cs Chip select pin for the slave + * @return + */ + virtual void deselect(GpioType& cs) = 0; + + /** + * @brief Configures the bus with the provided configuration parameters. + * Call this before every transaction, after the call to lock() and before + * select(..) + * + * @param config Configuration parameters + * @return + */ + virtual void acquire(SPIBusConfig config) + { + (void)config; + busy = true; + } + + /** + * @brief Releases ownership of the bus + */ + virtual void release() { busy = false; } + + /** + * @brief Checks wether the bus is currently being used + */ + virtual bool isBusy() { return busy; } + +private: + bool busy = false; // For use by SPITransaction +}; + +/** + * @brief Contains information about a single SPI slave device. + */ +struct SPISlave +{ + SPIBusInterface& bus; ///> Bus on which the slave is connected + + SPIBusConfig config; ///> How the bus should be configured to communicate + ///> with the slave. + GpioType cs; ///> Chip select pin + + SPISlave(SPIBusInterface& bus, GpioType cs, SPIBusConfig config) + : bus(bus), config(config), cs(cs) + { + } +}; + +/** + * @brief RAII Interface for SPI bus acquisition + * + */ +class SPIAcquireLock +{ +public: + SPIAcquireLock(SPISlave slave) : SPIAcquireLock(slave.bus, slave.config) {} + + SPIAcquireLock(SPIBusInterface& bus, SPIBusConfig cfg) : bus(bus) + { + bus.acquire(cfg); + } + + ~SPIAcquireLock() { bus.release(); } + +private: + SPIBusInterface& bus; +}; + +/** + * @brief RAII Interface for SPI chip selection + * + */ +class SPISelectLock +{ +public: + SPISelectLock(SPISlave slave) : SPISelectLock(slave.bus, slave.cs) {} + + SPISelectLock(SPIBusInterface& bus, GpioType cs) : bus(bus), cs(cs) + { + bus.select(cs); + } + + ~SPISelectLock() { bus.deselect(cs); } + +private: + SPIBusInterface& bus; + GpioType& cs; +}; \ No newline at end of file diff --git a/src/shared/math/Quaternion.h b/src/shared/drivers/spi/SPIDriver.h similarity index 78% rename from src/shared/math/Quaternion.h rename to src/shared/drivers/spi/SPIDriver.h index 245a4c4b35e63bdcdd5004080629258cd44a93b0..4b26d9f209ef58b7d0a306abe6eacd1e7388c4ed 100644 --- a/src/shared/math/Quaternion.h +++ b/src/shared/drivers/spi/SPIDriver.h @@ -1,7 +1,5 @@ -/* Quaternion - * - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,19 +13,16 @@ * * 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 + * 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. */ -#ifndef QUATERNION_H -#define QUATERNION_H -// TODO -class Quaternion -{ -}; +#pragma once -#endif /* ifndef QUATERNION_H */ +#include "SPIBusInterface.h" +#include "SPITransaction.h" +#include "SPIBus.h" \ No newline at end of file diff --git a/src/shared/drivers/spi/SPITransaction.cpp b/src/shared/drivers/spi/SPITransaction.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c1ae89f7b73264b8276165a6d8899495fb0f6ee3 --- /dev/null +++ b/src/shared/drivers/spi/SPITransaction.cpp @@ -0,0 +1,109 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#include "SPITransaction.h" + +SPITransaction::SPITransaction(SPISlave slave) + : SPITransaction(slave.bus, slave.cs, slave.config) +{ +} + +SPITransaction::SPITransaction(SPIBusInterface& bus, GpioType cs, + SPIBusConfig config) + : bus(bus), cs(cs) +{ + bus.acquire(config); +} + +SPITransaction::~SPITransaction() { bus.release(); } + +void SPITransaction::write(uint8_t cmd) +{ + bus.select(cs); + bus.write(cmd); + bus.deselect(cs); +} + +void SPITransaction::write(uint8_t reg, uint8_t val) +{ + bus.select(cs); + bus.write(reg); + bus.write(val); + bus.deselect(cs); +} + +void SPITransaction::write(uint8_t reg, uint8_t* data, size_t size) +{ + bus.select(cs); + bus.write(reg); + bus.write(data, size); + bus.deselect(cs); +} + +void SPITransaction::write(uint8_t* data, size_t size) +{ + bus.select(cs); + bus.write(data, size); + bus.deselect(cs); +} + +void SPITransaction::transfer(uint8_t* data, size_t size) +{ + bus.select(cs); + bus.transfer(data, size); + bus.deselect(cs); +} + +uint8_t SPITransaction::read(uint8_t reg, bool set_read_bit) +{ + if (set_read_bit) + { + reg = reg | 0x80; + } + + bus.select(cs); + bus.write(reg); + reg = bus.read(); + bus.deselect(cs); + return reg; +} + +void SPITransaction::read(uint8_t reg, uint8_t* data, size_t size, + bool set_read_bit) +{ + if (set_read_bit) + { + reg = reg | 0x80; + } + + bus.select(cs); + bus.write(reg); + bus.read(data, size); + bus.deselect(cs); +} + +void SPITransaction::read(uint8_t* data, size_t size) +{ + bus.select(cs); + bus.read(data, size); + bus.deselect(cs); +} \ No newline at end of file diff --git a/src/shared/drivers/spi/SPITransaction.h b/src/shared/drivers/spi/SPITransaction.h new file mode 100644 index 0000000000000000000000000000000000000000..698e7191575ba59a4f7188ae908e69d3c53b91d1 --- /dev/null +++ b/src/shared/drivers/spi/SPITransaction.h @@ -0,0 +1,162 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include "SPIBusInterface.h" + +/** + * @brief Provides high-level access to the SPI Bus for a single transaction. + * To make sure the bus is properly configured for the provided slave, you have + * to create a new instance of this class for every transaction, as the bus is + * configured upon instantiation. + * + * @warning DO NOT store an instance of this class for later use, as the bus may + * be incorrectly configured by then. + * + * Example transaction: + * + * { + * // Transaction begin: + * SPITransaction spi(bus, cs, config); // Configures the bus with the + * // provided parameters. + * + * spi.write(REG_EX, 0x56); // writes data to REG_EX + * uint8_t reg2 = spi.read(REG_EX_2); // reads from REG_EX_2 + * + * // ...As many read/writes as you wish... + * + * // transaction end. SPITransaction object is destructed and the bus is + * // freed for use by someone else + * } + */ +class SPITransaction +{ +public: + /** + * @brief Instatiates a new SPITransaction, configuring the bus with the + * provided parameters + * + * @param slave Slave to communicate with + */ + SPITransaction(SPISlave slave); + + /** + * @brief Instatiates a new SPITransaction, configuring the bus with the + * provided parameters + * + * @param bus Bus to communicate on + * @param cs Chip select of the slave to communicate to + * @param config Configuration of the bus for the selected slave + */ + SPITransaction(SPIBusInterface& bus, GpioType cs, SPIBusConfig config); + + ~SPITransaction(); + + // Delete copy/move contructors/operators + SPITransaction(const SPITransaction&) = delete; + SPITransaction& operator=(const SPITransaction&) = delete; + + SPITransaction(SPITransaction&&) = delete; + SPITransaction& operator=(SPITransaction&&) = delete; + + /** + * @brief Writes a command \p cmd to the bus + * + * @param cmd Command to write on the bus + */ + void write(uint8_t cmd); + + /** + * @brief Writes \p val into the \p reg register + * + * @param reg Slave device register + * @param val Value to be written in the register + */ + void write(uint8_t reg, uint8_t val); + + /** + * @brief Writes \p size bytes into the \p reg register + * + * @param reg Slave device register + * @param data Data to be written + * @param size Number of bytes to be written + */ + void write(uint8_t reg, uint8_t* data, size_t size); + + /** + * @brief Writes \p bytes on the bus + * + * @param data Bytes to be written + * @param size Number of bytes to be written + */ + void write(uint8_t* data, size_t size); + + /** + * @brief Read the contents of the \p reg register + * + * @param reg Slave device register + * @param set_read_bit Wether to set the read bit to 1 (MSB of reg) + * (default = true). + */ + uint8_t read(uint8_t reg, bool set_read_bit = true); + + /** + * @brief Reads \p size bytes from the \p reg register + * + * @param reg Slave device register + * @param data Buffer where read bytes will be stored + * @param size Number of bytes to read + * @param set_read_bit Wether to set the read bit to 1 (MSB of reg) + * (default = true). + */ + void read(uint8_t reg, uint8_t* data, size_t size, + bool set_read_bit = true); + + /** + * @brief Reads \p size bytes from the bus + * + * @param data Buffer where read bytes will be stored + * @param size Number of bytes to read + */ + void read(uint8_t* data, size_t size); + + /** + * @brief Full duplex transfer: \p data is written on the bus and its + * contents are replaced with the received bytes. + * + * @param data Buffer containign data to be transfered + * @param size Number of bytes to be transfer + */ + void transfer(uint8_t* data, size_t size); + + /** + * @brief Returns the underlying bus for low level access + * + * @return SPIBusInterface associated with this transaction + */ + SPIBusInterface& getBus() { return bus; } + +private: + SPIBusInterface& bus; + GpioType cs; +}; \ No newline at end of file diff --git a/src/shared/drivers/spi/SensorSpi.cpp b/src/shared/drivers/spi/SensorSpi.cpp index 62de6c6fa88d523ea4005af6fbdec6406c167a30..2907c80802567223a0c2577d22b4f409b6c160d4 100644 --- a/src/shared/drivers/spi/SensorSpi.cpp +++ b/src/shared/drivers/spi/SensorSpi.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/spi/SensorSpi.h b/src/shared/drivers/spi/SensorSpi.h index 856461ced6380347fc7ef9b13e405c0bb39edac7..04cbbf0a4821491e0ffc485242e3e989e64dd950 100644 --- a/src/shared/drivers/spi/SensorSpi.h +++ b/src/shared/drivers/spi/SensorSpi.h @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/spi/SyncedSPIBus.h b/src/shared/drivers/spi/SyncedSPIBus.h new file mode 100644 index 0000000000000000000000000000000000000000..f13e5f81aee9455f672b2fab1676ad9fc855f813 --- /dev/null +++ b/src/shared/drivers/spi/SyncedSPIBus.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include "SPIBus.h" +#include <miosix.h> + +using miosix::FastMutex; + +/** + * @brief Extension of SPIBus to sync access to the bus between multiple threads + */ +class SyncedSPIBus : public SPIBus +{ + virtual void acquire(SPIBusConfig config) override + { + mutex.lock(); + SPIBusInterface::acquire(config); + + configure(config); + } + + virtual void release() override + { + SPIBusInterface::release(); + mutex.unlock(); + } +private: + FastMutex mutex; +}; \ No newline at end of file diff --git a/src/shared/drivers/spi/test/FakeSpiTypedef.h b/src/shared/drivers/spi/test/FakeSpiTypedef.h new file mode 100644 index 0000000000000000000000000000000000000000..63932a08859c84a9c1c5f0b25d2169e613a04e7e --- /dev/null +++ b/src/shared/drivers/spi/test/FakeSpiTypedef.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once +#include <miosix.h> + +#include <cstdint> +#include <vector> +#include "utils/testutils/MockGpioPin.h" + +using std::vector; + +/** + * @brief Mock STM32F4 SPI peripheral: intercepts register value changes to + * emulate a real SPI peripheral / slave. + */ +struct FakeSpiTypedef +{ + uint32_t CR1 = 0; + uint32_t CR2 = 0; + uint32_t SR = 3; + + struct RegDR + { + // Intercept uint32_t assignements + void operator=(uint32_t DR) + { + // If slave is selected & bus configured properly + if (parent.cs.value() == 0 && parent.CR1 == parent.CR1_expected && + parent.CR2 == parent.CR2_expected) + { + out_buf.push_back(DR); + } + } + + operator uint32_t() + { + // If slave is selected + if (parent.cs.value() == 0 && parent.CR1 == parent.CR1_expected && + parent.CR2 == parent.CR2_expected) + { + return in_buf[in_it++]; + } + + return 0; + } + + RegDR(FakeSpiTypedef& parent) : parent(parent) {} + + unsigned int in_it = 0; + vector<uint32_t> in_buf; + vector<uint32_t> out_buf; + + private: + FakeSpiTypedef& parent; + }; + + uint32_t CR1_expected = 0; + uint32_t CR2_expected = 0; + + RegDR DR; + MockGpioPin cs; + + FakeSpiTypedef() : DR(*this) {cs.high();} +}; \ No newline at end of file diff --git a/src/shared/drivers/spi/test/MockSPIBus.h b/src/shared/drivers/spi/test/MockSPIBus.h new file mode 100644 index 0000000000000000000000000000000000000000..196a1346294335ecc03d1d2a530f2b82c83cdcfa --- /dev/null +++ b/src/shared/drivers/spi/test/MockSPIBus.h @@ -0,0 +1,297 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <cstdint> +#include <cstdio> +#include <vector> + +#include "../SPIBusInterface.h" + +using std::vector; + +/** + * @brief Mock SPI Bus to be used for testing communication to a single slave: + * data are read and written to two buffers on the memory that can then be + * checked. Operations on the mock bus will not be successfull if the + * configuration is not correct or the "slave" is not selected. + * + * Usage: + * 1. Set the expected config (data wont be written / read if the bus current + * configuration of the bus is different from the expected one) + * 2. Set the data to be read from the bus (in_buf). + * 3. Perform operations. write() will write bytes in out_buf, read() will + * return data from in_buf. + * 4. Check if out_buf contains the expected data. Check if data returned from + * read() is as expected from in_buf. + * 5. ??? + * 6. Profit. + */ + +#ifndef USE_MOCK_PERIPHERALS +#error \ + "SpiBusInterface must be built using MockGpioPin (-DUSE_MOCK_PERIPHERALS)" +#endif + +#include "utils/testutils/MockGpioPin.h" + +using miosix::FastMutex; +using miosix::Lock; +class MockSPIBus : public SPIBusInterface +{ +public: + MockSPIBus(SPIBusConfig expected_config) : expected_config(expected_config) + { + } + + ~MockSPIBus() {} + + // Delete copy/move contructors/operators + MockSPIBus(const MockSPIBus&) = delete; + MockSPIBus& operator=(const MockSPIBus&) = delete; + + MockSPIBus(MockSPIBus&&) = delete; + MockSPIBus& operator=(MockSPIBus&&) = delete; + + /** + * @brief See SPIBusInterface::write() + */ + virtual void write(uint8_t byte) override; + + /** + * @brief See SPIBusInterface::write() + */ + virtual void write(uint8_t* data, size_t size) override; + + /** + * @brief See SPIBusInterface::read() + */ + virtual uint8_t read() override; + + /** + * @brief See SPIBusInterface::read() + */ + void read(uint8_t* data, size_t size) override; + + /** + * @brief See SPIBusInterface::transfer() + */ + virtual uint8_t transfer(uint8_t data) override; + + /** + * @brief See SPIBusInterface::transfer() + */ + virtual void transfer(uint8_t* data, size_t size) override; + + /** + * @brief See SPIBusInterface::select() + * + */ + virtual void select(GpioType& cs) override; + + /** + * @brief See SPIBusInterface::deselect() + * + */ + virtual void deselect(GpioType& cs) override; + + /** + * @brief See SPIBusInterface::acquire() + */ + virtual void acquire(SPIBusConfig config) override; + + /** + * @brief Wether the chip select is asserted or not + */ + virtual bool isSelected() + { + + Lock<FastMutex> l(mutex); + return selected; + } + + virtual vector<uint8_t> getOutBuf() + { + Lock<FastMutex> l(mutex); + return out_buf; + } + + virtual vector<uint8_t> getInBuf() + { + Lock<FastMutex> l(mutex); + return in_buf; + } + + virtual void push(uint8_t* data, size_t len); + + virtual void clearBuffers() + { + out_buf.clear(); + in_buf.clear(); + in_buf_pos_cntr = 0; + } + +protected: + FastMutex mutex; + + vector<uint8_t> out_buf; // Data written on the bus are stored here + vector<uint8_t> in_buf; // Store here data to be read from the bus + size_t in_buf_pos_cntr = 0; + + SPIBusConfig expected_config; // Expected configuration of the bus + + virtual uint8_t _read(); + virtual void _write(uint8_t); + + virtual void _push(uint8_t* data, size_t len); + + bool canCommunicate(); + + SPIBusConfig current_config; + bool selected = false; +}; + +inline bool MockSPIBus::canCommunicate() +{ + bool result = selected && current_config == expected_config; + if (!result) + { + printf("Error, cannot communicato on MockSPIBus: "); + if (!selected) + { + printf("Chip select not asserted\n"); + } + else + { + printf("Incorrect configuration\n"); + } + } + return result; +} + +inline void MockSPIBus::_write(uint8_t byte) +{ + if (canCommunicate()) + { + out_buf.push_back(byte); + } + else + { + out_buf.push_back(0); + } +} + +inline void MockSPIBus::write(uint8_t byte) +{ + Lock<FastMutex> l(mutex); + _write(byte); +} + +inline void MockSPIBus::write(uint8_t* data, size_t size) +{ + Lock<FastMutex> l(mutex); + + for (size_t i = 0; i < size; i++) + { + _write(data[i]); + } +} + +inline uint8_t MockSPIBus::read() +{ + Lock<FastMutex> l(mutex); + return _read(); +} + +inline void MockSPIBus::read(uint8_t* data, size_t size) +{ + Lock<FastMutex> l(mutex); + for (size_t i = 0; i < size; i++) + { + *data = _read(); + data++; + } +} + +inline uint8_t MockSPIBus::_read() +{ + if (canCommunicate()) + { + if (in_buf_pos_cntr < in_buf.size()) + { + return in_buf[in_buf_pos_cntr++]; + } + } + return 0; +} + +inline uint8_t MockSPIBus::transfer(uint8_t data) +{ + Lock<FastMutex> l(mutex); + _write(data); + return _read(); +} + +inline void MockSPIBus::transfer(uint8_t* data, size_t size) +{ + Lock<FastMutex> l(mutex); + for (size_t i = 0; i < size; i++) + { + _write(data[i]); + data[i] = _read(); + } +} + +inline void MockSPIBus::select(GpioType& cs) +{ + Lock<FastMutex> l(mutex); + cs.low(); + selected = true; +} + +inline void MockSPIBus::deselect(GpioType& cs) +{ + Lock<FastMutex> l(mutex); + cs.high(); + selected = false; +} + +inline void MockSPIBus::acquire(SPIBusConfig config) +{ + Lock<FastMutex> l(mutex); + + SPIBusInterface::acquire(config); + + current_config = config; +} + +inline void MockSPIBus::push(uint8_t* data, size_t len) +{ + Lock<FastMutex> l(mutex); + _push(data, len); +} + +inline void MockSPIBus::_push(uint8_t* data, size_t len) +{ + in_buf.insert(in_buf.end(), data, data + len); +} \ No newline at end of file diff --git a/src/shared/events/Event.h b/src/shared/events/Event.h index 7f49995bc2357460a1167295d114f278c98dcb59..8c51c5610c799fd52f5b359b45a173c38b9159e4 100644 --- a/src/shared/events/Event.h +++ b/src/shared/events/Event.h @@ -1,7 +1,5 @@ -/* Event - * - * Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Author: Matteo Michele Piazzolla +/* Copyright (c) 2015-2018 Skyward Experimental Rocketry + * Author: Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/events/EventBroker.cpp b/src/shared/events/EventBroker.cpp index bb823917b3a1d277fea85ec5dffbde125d7406b3..8aeeef2c93654ea8b671694cb03a3aa817fe4d19 100644 --- a/src/shared/events/EventBroker.cpp +++ b/src/shared/events/EventBroker.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta, Matteo Michele Piazzolla + * Authors: Luca Erbetta, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -21,6 +21,7 @@ */ #include "EventBroker.h" + #include "Debug.h" #include "diagnostic/StackLogger.h" @@ -29,7 +30,7 @@ EventBroker::EventBroker() {} void EventBroker::post(const Event& ev, uint8_t topic) { #ifdef TRACE_EVENTS - TRACE("[EventBroker] Event: %d, Topic: %d\n", ev.sig, topic); + LOG_DEBUG(logger, "Event: {}, Topic: {}", ev.sig, topic); #endif Lock<FastMutex> lock(mtx_subscribers); diff --git a/src/shared/events/EventBroker.h b/src/shared/events/EventBroker.h index cbeb64919151a37f1247aa6f2c191db3fe681d78..9aca11782547ab1c1db702f9f2bf6cc00e646e6a 100644 --- a/src/shared/events/EventBroker.h +++ b/src/shared/events/EventBroker.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta, Matteo Michele Piazzolla + * Authors: Luca Erbetta, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -23,18 +23,18 @@ #ifndef SRC_SHARED_EVENTS_EVENT_BROKER_H #define SRC_SHARED_EVENTS_EVENT_BROKER_H -#include "ActiveObject.h" -#include "Singleton.h" - -#include "events/Event.h" -#include "events/FSM.h" - +#include <diagnostic/PrintLogger.h> #include <miosix.h> #include <stdint.h> #include <map> #include <vector> +#include "ActiveObject.h" +#include "Singleton.h" +#include "events/Event.h" +#include "events/FSM.h" + using std::map; using std::vector; @@ -183,6 +183,8 @@ private: FastMutex mtx_subscribers; uint16_t eventCounter = 0; + + PrintLogger logger = Logging::getLogger("eventbroker"); }; #define sEventBroker Singleton<EventBroker>::getInstance() diff --git a/src/shared/events/EventHandler.h b/src/shared/events/EventHandler.h index 335ab63cd3b52d4a88ba33ebd95a8598ec7d1f18..1c1fecc3ce0dde7c090896dcb962224865883a61 100644 --- a/src/shared/events/EventHandler.h +++ b/src/shared/events/EventHandler.h @@ -1,6 +1,4 @@ -/* Event Handler - * - * Copyright (c) 2015-2018 Skyward Experimental Rocketry +/* Copyright (c) 2015-2018 Skyward Experimental Rocketry * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/events/FSM.h b/src/shared/events/FSM.h index b16ae3f2ac3867d35db31eb93661582370c7fc9a..767538252f156912d54d5beb90f5fac956e3beb8 100644 --- a/src/shared/events/FSM.h +++ b/src/shared/events/FSM.h @@ -1,7 +1,5 @@ -/* Finite State Machine - * - * Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Author: Matteo Michele Piazzolla, Alain Carlucci, Luca Erbetta +/* Copyright (c) 2015-2018 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci, Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/events/HSM.h b/src/shared/events/HSM.h index f4f0c0ab417a34b2f7604c292cb97b26b56eb60d..aeaa771491d5933203272cabda9eebb57b960fac 100644 --- a/src/shared/events/HSM.h +++ b/src/shared/events/HSM.h @@ -1,29 +1,28 @@ -/* Hierarchical State Machine - * - * Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Author: Matteo Michele Piazzolla, Alain Carlucci, Luca Erbetta - * - * This work is derived from https://www.state-machine.com by Miro Samek. +/* Copyright (c) 2015-2018 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci, Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated docuthisntation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, thisrge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is + * 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 thisRCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEthisNT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * 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. */ + +// This work is derived from https://www.state-machine.com by Miro Samek. + #ifndef SRC_SHARED_EVENTS_HSM_H #define SRC_SHARED_EVENTS_HSM_H diff --git a/src/shared/events/utils/EventCounter.h b/src/shared/events/utils/EventCounter.h index 06771fb47c1e990a4824a4b19c0a5e55a5f37247..ba8ff049a02826fcf8c959d4e9d386d8489ae722 100644 --- a/src/shared/events/utils/EventCounter.h +++ b/src/shared/events/utils/EventCounter.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/events/utils/EventSniffer.h b/src/shared/events/utils/EventSniffer.h index 00c0197c39ce2a0f83fb751aa2743b488e935332..5d7aabfa5655eeb0c2705e8d7fc884234a754b66 100644 --- a/src/shared/events/utils/EventSniffer.h +++ b/src/shared/events/utils/EventSniffer.h @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/kalman/Kalman.h b/src/shared/kalman/Kalman.h index 587e36f9f067cb61b54249bf7f622d65cc499a27..9bb608ae9f495aca09cac34f2101b4aaea324e70 100644 --- a/src/shared/kalman/Kalman.h +++ b/src/shared/kalman/Kalman.h @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Mozzarelli + * Author: Luca Mozzarelli * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/kalman/KalmanEigen.h b/src/shared/kalman/KalmanEigen.h new file mode 100644 index 0000000000000000000000000000000000000000..6441a749e0c878673d1e1b90720c7ed61b12939d --- /dev/null +++ b/src/shared/kalman/KalmanEigen.h @@ -0,0 +1,172 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Authors: Alessandro Del Duca, Luca Conterio + * + * 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. + */ + +#pragma once + +#include <Eigen/Dense> + +using namespace Eigen; + +/** + * @brief Class representing a Kalman filter using the Eigen library for + * matrix computations + * + * WARNING : This class uses templates in order to know the size of each matrix + * at compile-time. This way we avoid Eigen to allocate memory + * dynamically. + */ +template <typename t, uint8_t n, uint8_t p> +class KalmanEigen +{ + using MatrixNN = Matrix<t, n, n>; + using MatrixPN = Matrix<t, p, n>; + using MatrixNP = Matrix<t, n, p>; + using MatrixPP = Matrix<t, p, p>; + using CVectorN = Matrix<t, n, 1>; + using CVectorP = Matrix<t, p, 1>; + +public: + /** + * @brief Configuration struct for the KalmanEigen class. + */ + struct KalmanConfig + { + MatrixNN F; + MatrixPN H; + MatrixNN Q; + MatrixPP R; + MatrixNN P; + CVectorN x; + }; + + /** + * @param config configuration object containing all the initialized + * matrices + */ + KalmanEigen(const KalmanConfig& config) + : F(config.F), H(config.H), Q(config.Q), R(config.R), P(config.P), + S(MatrixPP::Zero(p, p)), K(MatrixNP::Zero(n, p)), x(config.x) + { + I.setIdentity(); + } + + /** + * @brief Prediction step. + */ + void predict() + { + P = F * P * F.transpose() + Q; + x = F * x; + } + + /** + * @brief Prediction step. + * + * @param F_new updated F matrix + */ + void predict(const MatrixNN& F_new) + { + this->F = F_new; + this->predict(); + } + + /** + * @brief Correction step (correct the estimate). + * + * @param y The measurement vector + */ + bool correct(const CVectorP& y) + { + S = H * P * H.transpose() + R; + + // here the determinant is computed and + // then the inverse recomputes it, + // this passage could be optimized + if (S.determinant() < 1e-3) + { + return false; + } + + K = P * H.transpose() * S.inverse(); + P = (I - K * H) * P; + + x = x + K * (y - H * x); + + res = y - H * x; + + return true; + } + + /** + * @return state vector + */ + const CVectorN getState() { return x; } + + /** + * @return output vector + */ + const CVectorP getOutput() + { + y_hat = H * x; + return y_hat; + } + + /** + * @return residual error vector + */ + const CVectorP getResidual() { return res; } + + /** + * @brief Predicts k steps ahead the output + */ + const CVectorP predictOutput(uint32_t k) { return H * predictState(k); } + + /** + * @brief Predicts k steps ahead the state + */ + const CVectorN predictState(uint32_t k) + { + CVectorN x_hat = x; + + for (uint32_t i = 0; i < k; i++) + { + x_hat = F * x_hat; + } + return x_hat; + } + +private: + MatrixNN F; /**< State propagation matrix (n x n) */ + MatrixPN H; /**< Output matrix (p x n) */ + MatrixNN Q; /**< Model variance matrix (n x n) */ + MatrixPP R; /**< Measurement variance (p x p) */ + MatrixNN P; /**< Error covariance matrix (n x n) */ + + MatrixPP S; + MatrixNP K; /**< kalman gain */ + + MatrixNN I; /**< identity matrix (n x n) */ + + CVectorN x; /**< state vector (n x 1) */ + CVectorP y_hat; /**< output vector (p x 1) */ + CVectorP res; /**< residual error vector (p x 1) */ +}; \ No newline at end of file diff --git a/src/shared/logger/Deserializer.h b/src/shared/logger/Deserializer.h index aa673fdaa38496cd21644179171d1aae112a75e2..d5b0f4144d164f2ecb4e1e6cb5226a9028278618 100644 --- a/src/shared/logger/Deserializer.h +++ b/src/shared/logger/Deserializer.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -83,7 +83,7 @@ public: return false; } - char c_filename[64]; + char c_filename[128]; sprintf(c_filename, "%s%s_%s.csv", prefix.c_str(), logFile.c_str(), typeid(T).name()); diff --git a/src/shared/logger/LogStats.h b/src/shared/logger/LogStats.h index 6ebce642d057b33868534c21303416edd2abb8d0..db13a3d2350f2963366b6c247b5428cbf174fa71 100644 --- a/src/shared/logger/LogStats.h +++ b/src/shared/logger/LogStats.h @@ -1,29 +1,24 @@ -/*************************************************************************** - * Copyright (C) 2018 by Terraneo Federico * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * As a special exception, if other files instantiate templates or use * - * macros or inline functions from this file, or you compile this file * - * and link it with other works to produce a work based on this file, * - * this file does not by itself cause the resulting work to be covered * - * by the GNU General Public License. However the source code for this * - * file must still be made available in accordance with the GNU General * - * Public License. This exception does not invalidate any other reasons * - * why a work based on this file might be covered by the GNU General * - * Public License. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, see <http://www.gnu.org/licenses/> * - ***************************************************************************/ +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Federico Terraneo + * + * 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. + */ #pragma once @@ -49,7 +44,7 @@ public: static std::string header() { - return "timestamp,stat_toolarge,stat_dropped,stat_queued,stat_buf_" + return "timestamp,logNumber,stat_toolarge,stat_dropped,stat_queued,stat_buf_" "filled,stat_buf_written,stat_w_failed,stat_w_time,stat_max_" "time,stat_last_error\n"; } @@ -60,7 +55,7 @@ public: */ void print(std::ostream& os) const { - os << timestamp << "," << statTooLargeSamples << "," + os << timestamp << "," << logNumber << "," << statTooLargeSamples << "," << statDroppedSamples << "," << statQueuedSamples << "," << statBufferFilled << "," << statBufferWritten << "," << statWriteFailed << "," << statWriteTime << "," << statMaxWriteTime @@ -69,6 +64,7 @@ public: long long timestamp; ///< Timestamp + bool opened = false; int logNumber = 0; int statTooLargeSamples = 0; ///< Number of dropped samples because too large diff --git a/src/shared/logger/Logger.cpp b/src/shared/logger/Logger.cpp index afa1d7656c1076a320c80987a856a17df3062dbb..79e8b1de83dfc56216925e4373594796e2fc999f 100644 --- a/src/shared/logger/Logger.cpp +++ b/src/shared/logger/Logger.cpp @@ -1,41 +1,39 @@ -/*************************************************************************** - * Copyright (C) 2018 by Terraneo Federico * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * As a special exception, if other files instantiate templates or use * - * macros or inline functions from this file, or you compile this file * - * and link it with other works to produce a work based on this file, * - * this file does not by itself cause the resulting work to be covered * - * by the GNU General Public License. However the source code for this * - * file must still be made available in accordance with the GNU General * - * Public License. This exception does not invalidate any other reasons * - * why a work based on this file might be covered by the GNU General * - * Public License. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, see <http://www.gnu.org/licenses/> * - ***************************************************************************/ +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Federico Terraneo + * + * 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. + */ #include "Logger.h" + +#include <errno.h> #include <fcntl.h> #include <interfaces/atomic_ops.h> #include <sys/stat.h> #include <sys/types.h> #include <tscpp/buffer.h> + #include <stdexcept> + #include "Debug.h" #include "diagnostic/SkywardStack.h" #include "diagnostic/StackLogger.h" -#include <errno.h> using namespace std; using namespace miosix; @@ -74,7 +72,10 @@ int Logger::start() file = fopen(filename.c_str(), "ab"); if (file == NULL) + { + fileNumber = -1; throw runtime_error("Error opening log file"); + } setbuf(file, NULL); // The boring part, start threads one by one and if they fail, undo @@ -116,8 +117,8 @@ int Logger::start() fclose(file); throw runtime_error("Error creating stats thread"); }*/ - started = true; - + started = true; + s.opened = true; s.logNumber = fileNumber; return fileNumber; } @@ -133,6 +134,8 @@ void Logger::stop() writeT->join(); // statsT->join(); fclose(file); + + s.opened = false; } Logger::Logger() @@ -163,10 +166,11 @@ LogResult Logger::logImpl(const char* name, const void* data, unsigned int size) { if (started == false) { + //TRACE("Logger not started!"); ++s.statDroppedSamples; // Signal that we are trying to write to a closed log - s.statWriteError = EIO; + s.statWriteError = -1; return LogResult::Ignored; } diff --git a/src/shared/logger/Logger.h b/src/shared/logger/Logger.h index 0794e0fa6f67f2780226b17ec41612edecce2610..831131e161f081107b01e8fcb5aae2d369bc4382 100644 --- a/src/shared/logger/Logger.h +++ b/src/shared/logger/Logger.h @@ -1,41 +1,39 @@ -/*************************************************************************** - * Copyright (C) 2018 by Terraneo Federico * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * As a special exception, if other files instantiate templates or use * - * macros or inline functions from this file, or you compile this file * - * and link it with other works to produce a work based on this file, * - * this file does not by itself cause the resulting work to be covered * - * by the GNU General Public License. However the source code for this * - * file must still be made available in accordance with the GNU General * - * Public License. This exception does not invalidate any other reasons * - * why a work based on this file might be covered by the GNU General * - * Public License. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, see <http://www.gnu.org/licenses/> * - ***************************************************************************/ +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Federico Terraneo + * + * 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. + */ #pragma once #include <miosix.h> + #include <cstdio> #include <list> #include <queue> #include <string> #include <type_traits> + #include "LogStats.h" using std::string; + /** * Possible outcomes of Logger::log() */ @@ -101,9 +99,9 @@ public: } /** - * Returns current log filename - * @return - */ + * Returns current log filename + * @return + */ string getFileName() { return getFileName(fileNumber); } LogStats getLogStats() { return s; } @@ -129,7 +127,10 @@ public: template <typename T> LogResult log(const T &t) { - // static_assert(std::is_trivially_copyable<T>::value,""); + static_assert( + std::is_trivially_copyable<T>::value, + "A type T must be trivially copyable in order to be logged!"); + return logImpl(typeid(t).name(), &t, sizeof(t)); } @@ -175,9 +176,9 @@ private: } static const unsigned int filenameMaxRetry = - 100; ///< Limit on new filename - static const unsigned int maxRecordSize = 256; ///< Limit on logged data - static const unsigned int numRecords = 1024; ///< Size of record queues + 100; ///< Limit on new filename + static const unsigned int maxRecordSize = 512; ///< Limit on logged data + static const unsigned int numRecords = 512; ///< Size of record queues static const unsigned int bufferSize = 64 * 1024; ///< Size of each buffer static const unsigned int numBuffers = 8; ///< Number of buffers diff --git a/src/shared/logger/decoder/logdecoder.cpp b/src/shared/logger/decoder/logdecoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..99896b8a5ee168481080f9ef6700b6c9ad10da7f --- /dev/null +++ b/src/shared/logger/decoder/logdecoder.cpp @@ -0,0 +1,165 @@ +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Federico Terraneo + * + * 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. + */ + +/* + * This is a stub program for the program that will decode the logged data. + * You must first define a function + * "void registerTypes(Deserializer& ds);" + * + * that registers all the required log data types in the deserializer and then + * INCLUDE this cpp file in your source. finally compile and run + */ + +#include <sys/stat.h> +#include <sys/types.h> +#include <tscpp/stream.h> + +#include <filesystem> +#include <fstream> +#include <iostream> +#include <stdexcept> +#include <string> +#include <vector> + +using namespace std; +using namespace tscpp; + +namespace fs = filesystem; + +void showHelp(string cmdName) +{ + std::cerr << "Usage: " << cmdName + << " {-a [logs_diretory] | <log_file_path> | -h}" + << "Options:\n" + << "\t-h,--help\t\tShow help message\n" + << "\t-a,--all [dir=\".\"] Deserialize all logs in the provided directory\n" + << std::endl; +} + +vector<fs::path> listLogFiles(fs::path dir) +{ + vector<fs::path> out; + for (const auto& entry : fs::directory_iterator(dir)) + { + if (entry.exists() && entry.is_regular_file()) + { + if (entry.path().extension() == ".dat") + { + out.push_back(entry.path()); + } + } + } + return out; +} + +bool deserialize(fs::path log_path) +{ + cout << "Deserializing " << log_path << ".dat...\n"; + + // remove extension + log_path.replace_extension(""); + Deserializer d(log_path); + registerTypes(d); + + return d.deserialize(); +} + +bool deserializeAll(fs::path dir = ".") +{ + vector<fs::path> logs = listLogFiles(dir); + for (auto log : logs) + { + if (!deserialize(log.string())) + { + return false; + } + } + + return true; +} + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + showHelp(string(argv[0])); + return 1; + } + + bool success = false; + string arg1 = string(argv[1]); + if (arg1 == "-h" || arg1 == "--help") + { + showHelp(string(argv[0])); + return 0; + } + + if (arg1 == "-a" || arg1 == "--all") + { + fs::path dir = "."; + if (argc == 3) + { + string arg2 = string(argv[2]); + fs::directory_entry entry(arg2); + if (entry.exists() && entry.is_directory()) + { + dir = arg2; + } + else + { + cout << "Second argument after '-a' or '--all' must be a " + "directory\n"; + showHelp(string(argv[0])); + return 1; + } + } + cout << "Deserializing all logs...\n"; + success = deserializeAll(dir); + } + else if (arg1[0] == '-') + { + cerr << "Unknown option\n"; + return 1; + } + else + { + fs::directory_entry entry(arg1); + if (entry.exists() && entry.is_regular_file()) + { + success = deserialize(arg1); + } + else + { + showHelp(string(argv[0])); + return 1; + } + } + + if (success) + { + cout << "Deserialization completed successfully.\n"; + } + else + { + cout << "Deserialization ended with errors.\n"; + } +} diff --git a/src/shared/math/Matrix.cpp b/src/shared/math/Matrix.cpp index 562514337d82597f22466140d2d39e866aab70fe..400ed400f4a0155ada800bf0ef7bfbf966a0ed07 100644 --- a/src/shared/math/Matrix.cpp +++ b/src/shared/math/Matrix.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/math/Matrix.h b/src/shared/math/Matrix.h index 893d3eb575bb09ab9799d5ab978f70685473877c..1d819f3dbac5809b92bb2454bc0c2f0df529e8b9 100644 --- a/src/shared/math/Matrix.h +++ b/src/shared/math/Matrix.h @@ -1,5 +1,5 @@ /* Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/math/SkyQuaternion.cpp b/src/shared/math/SkyQuaternion.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c1b5351903eb371e91fbf2786725aec55756c25f --- /dev/null +++ b/src/shared/math/SkyQuaternion.cpp @@ -0,0 +1,191 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Marco Cella + * + * 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. + */ + +#include "SkyQuaternion.h" +#include "Constants.h" +#include "iostream" + +using namespace std; + +SkyQuaternion::SkyQuaternion() {} + +Vector4f SkyQuaternion::eul2quat(Vector3f degeul) // ZYX rotation +{ + float yaw = degeul(0) * DEGREES_TO_RADIANS; + float pitch = degeul(1) * DEGREES_TO_RADIANS; + float roll = degeul(2) * DEGREES_TO_RADIANS; + + float cyaw = cosf(yaw * 0.5F); + float syaw = sinf(yaw * 0.5F); + float cpitch = cosf(pitch * 0.5F); + float spitch = sinf(pitch * 0.5F); + float croll = cosf(roll * 0.5F); + float sroll = sinf(roll * 0.5F); + + float qx = cyaw * cpitch * sroll - syaw * spitch * croll; + float qy = cyaw * spitch * croll + syaw * cpitch * sroll; + float qz = syaw * cpitch * croll - cyaw * spitch * sroll; + float qw = cyaw * cpitch * croll + syaw * spitch * sroll; + + Vector4f quat(qx, qy, qz, qw); + + return quat; +} + +Vector3f SkyQuaternion::quat2eul(Vector4f quater) +{ + float qx = quater(0); + float qy = quater(1); + float qz = quater(2); + float qw = quater(3); + + float yaw = atan2f(2.0F * (qx * qy + qw * qz), + powf(qw, 2) + powf(qx, 2) - powf(qy, 2) - powf(qz, 2)); + + float a = -2.0F * (qx * qz - qw * qy); + if (a > 1.0F) + { + a = 1.0F; + } + else if (a < -1.0F) + { + a = -1.0F; + } + + float pitch = asinf(a); + + float roll = atan2f(2.0F * (qy * qz + qw * qx), + powf(qw, 2) - powf(qx, 2) - powf(qy, 2) + powf(qz, 2)); + + Vector3f eul(roll, pitch, yaw); + + return eul * 180.0F / PI; +} + +Vector4f SkyQuaternion::rotm2quat(Matrix3f R) +{ + float r11 = R(0, 0); + float r12 = R(0, 1); + float r13 = R(0, 2); + float r21 = R(1, 0); + float r22 = R(1, 1); + float r23 = R(1, 2); + float r31 = R(2, 0); + float r32 = R(2, 1); + float r33 = R(2, 2); + + float qx; + float qy; + float qz; + float qw; + + float tr = r11 + r22 + r33; + float r = sqrt(1 + tr); + + if (r32 - r23 > 0) + { + qx = 0.5 * sqrt(1 + r11 - r22 - r33); + } + else if (r32 - r23 < 0) + { + qx = -0.5 * sqrt(1 + r11 - r22 - r33); + } + else + { + qx = 0; + } + + if (r13 - r31 > 0) + { + qy = 0.5 * sqrt(1 - r11 + r22 - r33); + } + else if (r13 - r31 < 0) + { + qy = -0.5 * sqrt(1 - r11 + r22 - r33); + } + else + { + qy = 0; + } + + if (r21 - r12 > 0) + { + qz = 0.5 * sqrt(1 - r11 - r22 + r33); + } + else if (r21 - r12 < 0) + { + qz = -0.5 * sqrt(1 - r11 - r22 + r33); + } + else + { + qz = 0; + } + + qw = 0.5 * r; + + if (qw < 0) + { + qx = -qx; + qy = -qy; + qz = -qz; + qw = -qw; + } + + Vector4f quat(qx, qy, qz, qw); + + return quat / quat.norm(); +} + +bool SkyQuaternion::quatnormalize(Vector4f& quat) +{ + float den = sqrt(powf(quat(0), 2) + powf(quat(1), 2) + powf(quat(2), 2) + + powf(quat(3), 2)); + if (den < 1e-8) + return false; + + quat = quat / den; + + return true; +} + +Vector4f SkyQuaternion::quatProd(const Vector4f q1, const Vector4f q2) +{ + float q1x = q1(0); + float q1y = q1(1); + float q1z = q1(2); + Vector3f qv1(q1x, q1y, q1z); + float q1w = q1(3); + + float q2x = q2(0); + float q2y = q2(1); + float q2z = q2(2); + Vector3f qv2(q2x, q2y, q2z); + float q2w = q2(3); + + Vector4f quater; + quater << q1w * qv2 + q2w * qv1 - qv1.cross(qv2), q1w * q2w - qv1.dot(qv2); + float quater_norm = sqrt(quater(0) * quater(0) + quater(1) * quater(1) + + quater(2) * quater(2) + quater(3) * quater(3)); + quater = quater / quater_norm; + + return quater; +} diff --git a/src/shared/math/SkyQuaternion.h b/src/shared/math/SkyQuaternion.h new file mode 100644 index 0000000000000000000000000000000000000000..ff8cf3cadeb966beb57907ad77b9996d9d7ce267 --- /dev/null +++ b/src/shared/math/SkyQuaternion.h @@ -0,0 +1,83 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Marco Cella + * + * 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. + */ + +#pragma once + +#include <Eigen/Dense> +using namespace Eigen; + +/** + * @brief Class for managing quaternions. + * Convention used: qx, qy, qz, qw (scalar element as last element) + */ +class SkyQuaternion +{ + +public: + SkyQuaternion(); + + /** + * @brief Transform a vector of euler angles ZYX to quaternion. + * + * @param degeul the vector of euler angles to be transformed [deg] + * + * @return transformed quaternion + */ + Vector4f eul2quat(Vector3f degeul); + + /** + * @brief Transform a quaternion to a vector of euler angles. + * + * @param quat the quaternion to be transformed + * + * @return transformed vector of euler angles [deg] + */ + Vector3f quat2eul(Vector4f quat); + + /** + * @brief Transform a rotation matrix to a quaternion. + * + * @param R the rotation matrix to be transformed (3x3) + * + * @return transformed quaternion + */ + Vector4f rotm2quat(Matrix3f R); + + /** + * @brief Normalize a quaternion. + * + * @param quat the quaternion to be normalized + * + * @return boolean indicating if the operation succeded or not + */ + bool quatnormalize(Vector4f& quat); + + /** + * @brief Compute the product of two quaternions. + * + * @param q1 the first factor + * @param q2 the second factor + * + * @return the resulting quaternions product + */ + Vector4f quatProd(const Vector4f q1, const Vector4f q2); +}; diff --git a/src/shared/math/Stats.cpp b/src/shared/math/Stats.cpp index 3b3567a716bd53d048fa16aa723c5de331a22729..926964f884cc7e540dfd337a2975c5fd43805341 100644 --- a/src/shared/math/Stats.cpp +++ b/src/shared/math/Stats.cpp @@ -1,6 +1,4 @@ -/* Computes statistics (min, max, mean and variance) on a dataset - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry * Author: Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 @@ -42,7 +40,7 @@ ostream& operator<<(ostream& os, const StatsResult& sr) Stats::Stats() : minValue(numeric_limits<float>::max()), - maxValue(numeric_limits<float>::min()), mean(0.f), m2(0.f), n(0) + maxValue(numeric_limits<float>::lowest()), mean(0.f), m2(0.f), n(0) { } @@ -64,7 +62,7 @@ void Stats::add(float data) void Stats::reset() { minValue = numeric_limits<float>::max(); - maxValue = numeric_limits<float>::min(); + maxValue = numeric_limits<float>::lowest(); mean = 0.f; m2 = 0.f; n = 0; diff --git a/src/shared/math/Stats.h b/src/shared/math/Stats.h index c5fec2d57755a5f33836b59688dd72d9936209ce..4ffcf219541207342fe5e9b12f4a763eaff1b7a3 100644 --- a/src/shared/math/Stats.h +++ b/src/shared/math/Stats.h @@ -1,6 +1,4 @@ -/* Computes statistics (min, max, mean and variance) on a dataset - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry * Author: Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/math/Vec3.h b/src/shared/math/Vec3.h index 93bd6c6e3c59d5cbd78e34c923df218fface3824..155740beb05bf4f40ab115edafc6106fbb0376d0 100644 --- a/src/shared/math/Vec3.h +++ b/src/shared/math/Vec3.h @@ -1,7 +1,5 @@ -/* Vector3 - * - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci +/* Copyright (c) 2016 Skyward Experimental Rocketry + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 @@ -34,6 +32,13 @@ class Vec3 public: Vec3() { clear(); } + Vec3(const Vec3& rhs) + { + x = rhs.x; + y = rhs.y; + z = rhs.z; + } + Vec3(float x, float y, float z) { this->x = x; @@ -63,6 +68,12 @@ public: return true; } + void operator=(const Vec3& rhs){ + x = rhs.x; + y = rhs.y; + z = rhs.z; + } + void operator*=(float value) { x *= value; @@ -70,6 +81,13 @@ public: z *= value; } + void operator/=(float value) + { + x /= value; + y /= value; + z /= value; + } + void operator+=(const Vec3 &v) { x += v.getX(); @@ -84,7 +102,9 @@ public: z -= v.getZ(); } - Vec3 operator*(float v) const { return Vec3(x * v, y * v, z * v); } + Vec3 operator*(float t) const { return Vec3(x * t, y * t, z * t); } + + Vec3 operator/(float t) const { return Vec3(x / t, y / t, z / t); } Vec3 operator+(const Vec3 &v) const { @@ -96,6 +116,10 @@ public: return Vec3(x - v.getX(), y - v.getY(), z - v.getZ()); } + Vec3 operator-() const { + return Vec3(-x, -y, -z); + } + float dot(const Vec3 &v) const { return x * v.getX() + y * v.getY() + z * v.getZ(); diff --git a/src/shared/rls/RLS.h b/src/shared/rls/RLS.h new file mode 100644 index 0000000000000000000000000000000000000000..6ecb1470bd6bd42f48683b76934728d18723abe4 --- /dev/null +++ b/src/shared/rls/RLS.h @@ -0,0 +1,65 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Mozzarelli + * + * 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. + */ + +#pragma once + +#include <../libs/simple-template-matrix/matrix.h> +#include <iostream> + +/*! + * \class RLS + * \brief A performin Recursive least squares identification + * + * ``` n ```: number of parameters to be estimated + * ``` n ```: number of states + * ``` p ```: number of outputs + * + * The system model is: + * ``` + * y(t) = phi(t)^T * theta(t) + * ``` + * being y the output and theta the vector of parameters to be identified. + * See test-rls.cpp for an example. + */ +template <unsigned n> +class RLS +{ + using CVectorN = MatrixBase<float, n, 1>; + using MatrixNN = MatrixBase<float, n, n>; +private: + MatrixNN V; + CVectorN theta; + float mu; +public: + RLS(const MatrixNN &V_0, const CVectorN &theta_0, float mu) : V(V_0), theta(theta_0), mu(mu){}; + + + ~RLS(){}; + + void update(const CVectorN & y_kp1, const CVectorN & phi) + { + V = V/mu - (V/mu * phi*transpose(phi) * V/mu) / (1 + transpose(phi)*V/mu* phi)(0,0); + theta = theta + V*phi*(y_kp1 - transpose(theta)*phi); + }; + + CVectorN getEstimate(){ return theta; }; +}; \ No newline at end of file diff --git a/src/shared/scheduler/TaskScheduler.cpp b/src/shared/scheduler/TaskScheduler.cpp index 5d2cbb05f48a2763780950f26e349962ab3f3710..73581d1a8aae5387178cfca612b437e6cb33817e 100644 --- a/src/shared/scheduler/TaskScheduler.cpp +++ b/src/shared/scheduler/TaskScheduler.cpp @@ -1,7 +1,5 @@ -/* Event scheduler - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Author: Alain Carlucci, Federico Terraneo, Matteo Michele Piazzolla +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Alain Carlucci, Federico Terraneo, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/scheduler/TaskScheduler.h b/src/shared/scheduler/TaskScheduler.h index 38e3a2f080f38a2aef6a33d76db001b18777c49b..4506e86a0a5e27f24058fc134683c03ed444b78d 100644 --- a/src/shared/scheduler/TaskScheduler.h +++ b/src/shared/scheduler/TaskScheduler.h @@ -1,7 +1,5 @@ -/* Event scheduler - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Author: Alain Carlucci, Federico Terraneo, Matteo Michele Piazzolla +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Alain Carlucci, Federico Terraneo, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/scheduler/TaskSchedulerData.h b/src/shared/scheduler/TaskSchedulerData.h index e788c371e5469e9356b532d6f7195bee16deaa2f..a3b9af1839f826d2649a4fe755ac8ed5cbaed135 100644 --- a/src/shared/scheduler/TaskSchedulerData.h +++ b/src/shared/scheduler/TaskSchedulerData.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Alain Carlucci, Federico Terraneo, Matteo Michele Piazzolla +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Authors: Alain Carlucci, Federico Terraneo, Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/BME280/BME280.cpp b/src/shared/sensors/BME280/BME280.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b69ed859914262cc1a818f11f9a60f89430fd903 --- /dev/null +++ b/src/shared/sensors/BME280/BME280.cpp @@ -0,0 +1,392 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#include "BME280.h" + +#include "TimestampTimer.h" + +const BME280::BME280Config BME280::BME280_DEFAULT_CONFIG = { + SKIPPED, 0, 0, SLEEP_MODE, SKIPPED, SKIPPED, 0, FILTER_OFF, STB_TIME_0_5}; + +const BME280::BME280Config BME280::BME280_CONFIG_ALL_ENABLED = {OVERSAMPLING_1, + 0, + 0, + NORMAL_MODE, + OVERSAMPLING_16, + OVERSAMPLING_2, + 0, + FILTER_COEFF_16, + STB_TIME_0_5}; + +const BME280::BME280Config BME280::BME280_CONFIG_TEMP_SINGLE = { + SKIPPED, 0, 0, FORCED_MODE, SKIPPED, + OVERSAMPLING_1, 0, FILTER_OFF, STB_TIME_0_5}; + +BME280::BME280(SPISlave spiSlave_, BME280Config config_) + : spiSlave(spiSlave_), config(config_) +{ +} + +bool BME280::init() +{ + // Check if already initialized + if (initialized) + { + LOG_ERR(logger, "Already initialized"); + + last_error = SensorErrors::ALREADY_INIT; + + return false; + } + + // Check WHO AM I + if (!checkWhoAmI()) + { + LOG_ERR(logger, "Invalid WHO AM I"); + + last_error = SensorErrors::INVALID_WHOAMI; + + return false; + } + + loadCompensationParameters(); + + // Read once the temperature to compute t_fine + setConfiguration(BME280_CONFIG_TEMP_SINGLE); + miosix::Thread::sleep( + calculateMaxMeasurementTime(BME280_CONFIG_TEMP_SINGLE)); + readTemperature(); + + setConfiguration(); + + BME280Config readBackConfig = readConfiguration(); + + // Check if the configration on the device matches ours + if (config.bytes.ctrl_hum != readBackConfig.bytes.ctrl_hum || + config.bytes.ctrl_meas != readBackConfig.bytes.ctrl_meas || + config.bytes.config != readBackConfig.bytes.config) + { + LOG_ERR(logger, "Device configuration incorrect, setup failed"); + + last_error = SensorErrors::NOT_INIT; + + return false; + } + + initialized = true; + return true; +} + +void BME280::setHumidityOversampling(Oversampling oversampling) +{ + config.bits.osrs_h = oversampling; + + setConfiguration(); +} + +void BME280::setSensorMode(Mode mode) +{ + config.bits.mode = mode; + + setConfiguration(); +} + +void BME280::setPressureOversampling(Oversampling oversampling) +{ + config.bits.osrs_p = oversampling; + + setConfiguration(); +} + +void BME280::setTemperatureOversampling(Oversampling oversampling) +{ + config.bits.osrs_t = oversampling; + + setConfiguration(); +} + +void BME280::setFilterCoeff(FilterCoeff filterCoeff) +{ + config.bits.filter = filterCoeff; + + setConfiguration(); +} + +void BME280::setStandbyTime(StandbyTime standbyTime) +{ + config.bits.t_sb = standbyTime; + + setConfiguration(); +} + +HumidityData BME280::readHumidity() +{ + uint8_t buffer[2]; + int32_t adc_H = 0; + + { + SPITransaction transaction(spiSlave); + + transaction.read(REG_HUM_MSB, buffer, 2); + } + + adc_H |= ((uint32_t)buffer[0] << 8); + adc_H |= buffer[1]; + + // Compensate humidity + last_sample.humid_timestamp = TimestampTimer::getTimestamp(); + last_sample.humid = + (float)compensateHumidity(adc_H) / 1024; // Converto to %RH + + return last_sample; +} + +PressureData BME280::readPressure() +{ + uint8_t buffer[3]; + int32_t adc_P = 0; + + { + SPITransaction transaction(spiSlave); + + transaction.read(REG_PRESS_MSB, buffer, 3); + } + + adc_P |= ((uint32_t)buffer[0]) << 12; + adc_P |= ((uint32_t)buffer[1]) << 4; + adc_P |= (buffer[2] >> 4) & 0x0F; + + // Compensate pressure + last_sample.press_timestamp = TimestampTimer::getTimestamp(); + last_sample.press = + (float)compensatePressure(adc_P) / 256; // Convert to Pa + + return last_sample; +} + +TemperatureData BME280::readTemperature() +{ + uint8_t buffer[3]; + int32_t adc_T = 0; + + { + SPITransaction transaction(spiSlave); + + transaction.read(REG_TEMP_MSB, buffer, 3); + } + + adc_T |= ((uint32_t)buffer[0]) << 12; + adc_T |= ((uint32_t)buffer[1]) << 4; + adc_T |= (buffer[2] >> 4) & 0x0F; + + // Compensate temperature + t_fine = computeFineTemperature(adc_T); + last_sample.temp_timestamp = TimestampTimer::getTimestamp(); + last_sample.temp = + (float)compensateTemperature(t_fine) / 100; // Converto to DegC + + return last_sample; +} + +HumidityData BME280::getHumidity() { return last_sample; } + +PressureData BME280::getPressure() { return last_sample; } + +TemperatureData BME280::getTemerature() { return last_sample; } + +unsigned int BME280::calculateMaxMeasurementTime(BME280Config config_) +{ + return ceil(1.25 + (2.3 * config_.bits.osrs_t) + + (2.3 * config_.bits.osrs_p + 0.575) + + (2.3 * config_.bits.osrs_h + 0.575)); +} + +unsigned int BME280::getMaxMeasurementTime() +{ + return calculateMaxMeasurementTime(config); +} + +bool BME280::selfTest() { return checkWhoAmI(); } + +BME280Data BME280::sampleImpl() +{ + uint8_t buffer[8]; + int32_t adc_T = 0; + int32_t adc_P = 0; + int32_t adc_H = 0; + BME280Data data; + + // TODO: implement selective read! + + // Burst read pressure, temperature and humidity + { + SPITransaction transaction(spiSlave); + + transaction.read(REG_PRESS_MSB, buffer, 8); + } + + adc_T |= ((uint32_t)buffer[3]) << 12; + adc_T |= ((uint32_t)buffer[4]) << 4; + adc_T |= (buffer[5] >> 4) & 0x0F; + + adc_P |= ((uint32_t)buffer[0]) << 12; + adc_P |= ((uint32_t)buffer[1]) << 4; + adc_P |= (buffer[2] >> 4) & 0x0F; + + adc_H |= ((uint32_t)buffer[6] << 8); + adc_H |= buffer[7]; + + // Compensate temperature + t_fine = computeFineTemperature(adc_T); + data.temp_timestamp = TimestampTimer::getTimestamp(); + data.temp = (float)compensateTemperature(t_fine) / 100; // Converto to DegC + + // Compensate pressure + data.press_timestamp = TimestampTimer::getTimestamp(); + data.press = (float)compensatePressure(adc_P) / 256; // Convert to Pa + + // Compensate humidity + data.humid_timestamp = TimestampTimer::getTimestamp(); + data.humid = (float)compensateHumidity(adc_H) / 1024; // Converto to %RH + + return data; +} + +bool BME280::checkWhoAmI() +{ + SPITransaction transaction(spiSlave); + + uint8_t who_am_i_value = transaction.read(REG_ID); + + return who_am_i_value == REG_ID_VAL; +} + +void BME280::setConfiguration() { setConfiguration(config); } + +void BME280::setConfiguration(BME280Config config_) +{ + SPITransaction transaction(spiSlave); + + transaction.write(REG_CONFIG & 0x7F, config_.bytes.config); + transaction.write(REG_CTRL_HUM & 0x7F, config_.bytes.ctrl_hum); + transaction.write(REG_CTRL_MEAS & 0x7F, config_.bytes.ctrl_meas); +} + +BME280::BME280Config BME280::readConfiguration() +{ + BME280Config tmp; + SPITransaction transaction(spiSlave); + + transaction.read(REG_CTRL_HUM, (uint8_t *)&tmp, 4); + + return tmp; +} + +void BME280::loadCompensationParameters() +{ + // Read first batch of compensation parameters + { + SPITransaction transaction(spiSlave); + + transaction.read(REG_CALIB_0, (uint8_t *)&compParams, 25); + } + + // Reat second batch of compensation parameters + { + SPITransaction transaction(spiSlave); + + transaction.read(REG_CALIB_26, (uint8_t *)&compParams.bits.dig_H2, 7); + } + + // Adjust unaligned data + compParams.bytes_array[29] = + (compParams.bytes_array[29] << 4) | (compParams.bytes_array[29] >> 4); + compParams.bits.dig_H4 = + (compParams.bits.dig_H4 << 4) | (compParams.bits.dig_H4 >> 8); +} + +int32_t BME280::computeFineTemperature(int32_t adc_T) +{ + int32_t var1, var2; + var1 = ((((adc_T >> 3) - ((int32_t)compParams.bits.dig_T1 << 1))) * + ((int32_t)compParams.bits.dig_T2)) >> + 11; + var2 = (((((adc_T >> 4) - ((int32_t)compParams.bits.dig_T1)) * + ((adc_T >> 4) - ((int32_t)compParams.bits.dig_T1))) >> + 12) * + ((int32_t)compParams.bits.dig_T3)) >> + 14; + return var1 + var2; +} + +int32_t BME280::compensateTemperature(int32_t t_fine) +{ + return (t_fine * 5 + 128) >> 8; +} + +uint32_t BME280::compensatePressure(int32_t adc_P) +{ + int64_t var1, var2, p; + var1 = ((int64_t)t_fine) - 128000; + var2 = var1 * var1 * (int64_t)compParams.bits.dig_P6; + var2 = var2 + ((var1 * (int64_t)compParams.bits.dig_P5) << 17); + var2 = var2 + (((int64_t)compParams.bits.dig_P4) << 35); + var1 = ((var1 * var1 * (int64_t)compParams.bits.dig_P3) >> 8) + + ((var1 * ((int64_t)compParams.bits.dig_P2) << 12)); + var1 = + ((((int64_t)1) << 47) + var1) * ((int64_t)compParams.bits.dig_P1) >> 33; + if (var1 == 0) + { + return 0; // avoid exception caused by division by zero + } + p = 1048576 - adc_P; + p = (((p << 31) - var2) * 3125) / var1; + var1 = (((int64_t)compParams.bits.dig_P9) * (p >> 13) * (p >> 13)) >> 25; + var2 = (((int64_t)compParams.bits.dig_P8) * p) >> 19; + p = ((p + var1 + var2) >> 8) + (((int64_t)compParams.bits.dig_P7) << 4); + return (uint32_t)p; +} + +uint32_t BME280::compensateHumidity(int32_t adc_H) +{ + int32_t v_x1_u32r; + + v_x1_u32r = (t_fine - ((int32_t)768000)); + v_x1_u32r = (((((adc_H << 14) - (((int32_t)compParams.bits.dig_H4) << 20) - + (((int32_t)compParams.bits.dig_H5) * v_x1_u32r)) + + ((int32_t)16384)) >> + 15) * + (((((((v_x1_u32r * ((int32_t)compParams.bits.dig_H6)) >> 10) * + (((v_x1_u32r * ((int32_t)compParams.bits.dig_H3)) >> 11) + + ((int32_t)32768))) >> + 10) + + ((int32_t)2097152)) * + ((int32_t)compParams.bits.dig_H2) + + 8192) >> + 14)); + v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * + ((int32_t)compParams.bits.dig_H1)) >> + 4)); + v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); + v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); + return (uint32_t)(v_x1_u32r >> 12); +} diff --git a/src/shared/sensors/BME280/BME280.h b/src/shared/sensors/BME280/BME280.h index 129252df0298bd22626ee4c1ad1f168495208b19..562be6f706e693aecd3ab058fb8ad71e483c993a 100644 --- a/src/shared/sensors/BME280/BME280.h +++ b/src/shared/sensors/BME280/BME280.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,306 +13,288 @@ * * 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 + * 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. */ -#pragma once - -#include <stdio.h> +#include <diagnostic/PrintLogger.h> #include "BME280Data.h" +#include "drivers/spi/SPIDriver.h" #include "sensors/Sensor.h" -#include "Debug.h" -template <class Bus> -class BME280 : public TemperatureSensor, public PressureSensor +class BME280 : public Sensor<BME280Data> { public: - BME280() {} - virtual ~BME280() {} + enum Oversampling + { + SKIPPED = 0x0, ///< Skipped (output set to 0x8000) + OVERSAMPLING_1 = 0x1, ///< Oversampling x1 + OVERSAMPLING_2 = 0x2, ///< Oversampling x2 + OVERSAMPLING_4 = 0x3, ///< Oversampling x4 + OVERSAMPLING_8 = 0x4, ///< Oversampling x8 + OVERSAMPLING_16 = 0x5, ///< Oversampling x16 + }; - std::string printData() { return ""; } + enum Mode + { + SLEEP_MODE = 0x0, ///< Sleep mode + FORCED_MODE = 0x1, ///< Forced mode + NORMAL_MODE = 0x3 ///< Normal mode + }; - bool init() override + enum StandbyTime { - if (!initialized) - { - Bus::init(); - - uint8_t who_am_i = Bus::read(REG_WHO_AM_I); - if (who_am_i != ID) - { - last_error = ERR_NOT_ME; - TRACE("BME280 not found: who am i read 0x%02X\n", who_am_i); - return false; - } - - // T standby = 0.5 ms, Filter: 8x ("& 0x7F" to set bit 7 to 0) - Bus::write(REG_CONFIG & 0x7F, 0x0C); - - uint8_t config_val = Bus::read(REG_CONFIG); - if (config_val != 0x0C) - { - TRACE("Wrong value in config register: 0x%02X\n", config_val); - return false; - } - - // Disable humidity reading - Bus::write(REG_CTRL_HUM & 0x7F, 0x00); - - uint8_t hum_val = Bus::read(REG_CTRL_HUM); - if (hum_val != 0x00) - { - TRACE("Wrong value in hum register: 0x%02X\n", hum_val); - last_error = ERR_BUS_FAULT; - return false; - } - - // Pressure oversampling x4, temp oversampling x1, normal mode. - Bus::write(REG_CTRL_MEAS & 0x7F, (uint8_t)0x2F); - - uint8_t meas_val = Bus::read(REG_CTRL_MEAS); - if (meas_val != 0x2F) - { - TRACE("Wrong value in meas register: 0x%02X\n", meas_val); - last_error = ERR_BUS_FAULT; - return false; - } - - loadCompensationParams(); - - initialized = true; - return true; - } - else - { - return false; - } - } + STB_TIME_0_5 = 0x0, ///< 0.5 ms + STB_TIME_62_5 = 0x1, ///< 62.5 ms + STB_TIME_125 = 0x2, ///< 125 ms + STB_TIME_250 = 0x3, ///< 250 ms + STB_TIME_500 = 0x4, ///< 500 ms + STB_TIME_1000 = 0x5, ///< 1000 ms + STB_TIME_10 = 0x6, ///< 10 ms + STB_TIME_20 = 0x7 ///< 20 ms + }; - bool selfTest() override { return true; } + enum FilterCoeff + { + FILTER_OFF = 0x0, ///< Filter off + FILTER_COEFF_2 = 0x1, ///< Filter coefficient = 2 + FILTER_COEFF_4 = 0x2, ///< Filter coefficient = 4 + FILTER_COEFF_8 = 0x3, ///< Filter coefficient = 8 + FILTER_COEFF_16 = 0x4 ///< Filter coefficient = 16 + }; - void printCompensationParams() + union BME280Config { - if (initialized) + struct __attribute__((packed)) BME280ConfigBits { - printf("Compensation parameters: \n"); - - printf("dig_T1: 0x%04X\n", comp_params.dig_T1); - printf("dig_T2: 0x%04X\n", comp_params.dig_T2); - printf("dig_T3: 0x%04X\n", comp_params.dig_T3); - - printf("dig_P1: 0x%04X\n", comp_params.dig_P1); - printf("dig_P2: 0x%04X\n", comp_params.dig_P2); - printf("dig_P3: 0x%04X\n", comp_params.dig_P3); - printf("dig_P4: 0x%04X\n", comp_params.dig_P4); - printf("dig_P5: 0x%04X\n", comp_params.dig_P5); - printf("dig_P6: 0x%04X\n", comp_params.dig_P6); - printf("dig_P7: 0x%04X\n", comp_params.dig_P7); - printf("dig_P8: 0x%04X\n", comp_params.dig_P8); - printf("dig_P9: 0x%04X\n", comp_params.dig_P9); - } - } - - bool onSimpleUpdate() override - { - if (initialized) + // ctrl_hum + Oversampling osrs_h : 3; ///< Oversampling of humidity + uint8_t : 5; + + // status + /** + * '1' when the NVM data are being copied to image registers, '0' + * when the copying is done + */ + uint8_t im_update : 1; + uint8_t : 2; + /** + * '1' whenever a conversion is running, '0' when the result have + * been transferred to the data registers + */ + uint8_t measuring : 1; + uint8_t : 4; + + // ctrl_meas + Mode mode : 2; ///< Device modes + Oversampling osrs_p : 3; ///< Oversampling of pressure + Oversampling osrs_t : 3; ///< Oversampling of temperature + + // config + uint8_t spi3w_en : 1; ///< Enables 3-wire SPI interface + uint8_t : 1; + FilterCoeff filter : 3; ///< Time constant of the IIR filter + StandbyTime t_sb : 3; ///< Inactive duration in normal mode + } bits; + + struct { - // Burst read temperature and pressure registers - uint8_t buf[6]; - Bus::read(REG_PRESS_MSB, buf, 6); - - uint32_t press = 0; - press |= ((uint32_t)buf[0]) << 12; - press |= ((uint32_t)buf[1]) << 4; - press |= ((uint32_t)buf[2]) >> 4; + uint8_t ctrl_hum; ///< Humidity options + uint8_t status; ///< Device status + uint8_t ctrl_meas; ///< Pressure and temperature options + uint8_t config; ///< Rate, filter and interface options + } bytes; - uint32_t temp = 0; - temp |= ((uint32_t)buf[3]) << 12; - temp |= ((uint32_t)buf[4]) << 4; - temp |= ((uint32_t)buf[5]) >> 4; + uint8_t bytes_array[4]; + }; - data.raw_pressure = press; - data.raw_temperature = temp; + union BME280Comp + { + struct __attribute__((packed)) + { + uint16_t dig_T1; + int16_t dig_T2; + int16_t dig_T3; + uint16_t dig_P1; + int16_t dig_P2; + int16_t dig_P3; + int16_t dig_P4; + int16_t dig_P5; + int16_t dig_P6; + int16_t dig_P7; + int16_t dig_P8; + int16_t dig_P9; + uint8_t dig_H1; + int16_t dig_H2; + uint8_t dig_H3; + int16_t dig_H4 : 12; + int16_t dig_H5 : 12; + int8_t dig_H6; + } bits; + + uint8_t bytes_array[32]; + }; - // Compensate values - int32_t t_fine = getFineTemperature(data.raw_temperature); + enum BME280Registers : uint8_t + { + REG_CALIB_0 = 0x88, + // Calibration register 1-25 - data.temperature = compensateTemperature(t_fine) / 100.0f; - data.pressure = - compensatePressure(data.raw_pressure, t_fine) / 256.0f; + REG_ID = 0xD0, + REG_RESET = 0xE0, - data.timestamp = miosix::getTick(); + REG_CALIB_26 = 0xE1, + // Calibration register 27-41 - mLastPressure = data.pressure; - mLastTemp = data.temperature; + REG_CTRL_HUM = 0xF2, + REG_STATUS = 0xF3, + REG_CTRL_MEAS = 0xF4, + REG_CONFIG = 0xF5, - /*printf( - "Raw P:\t%d,\tRaw T:\t%d\nComp P:\t%d,\tComp T:\t%d,\tFine T: " - "%d\n", - (int)press, (int)temp, (int)(pressure_value / 256), - (int)temp_value, (int)t_fine);*/ - return true; - } - return false; - } + REG_PRESS_MSB = 0xF7, + REG_PRESS_LSB = 0xF8, + REG_PRESS_XLSB = 0xF9, + REG_TEMP_MSB = 0xFA, + REG_TEMP_LSB = 0xFB, + REG_TEMP_XLSB = 0xFC, + REG_HUM_MSB = 0xFD, + REG_HUM_LSB = 0xFE, + }; - const uint32_t* rawPressureDataPtr() { return &data.raw_pressure; } + static constexpr uint8_t REG_ID_VAL = 0x60; ///< Who am I value - const uint32_t* rawTempDataPtr() { return &data.raw_temperature; } + static const BME280Config + BME280_DEFAULT_CONFIG; ///< Default register values + static const BME280Config + BME280_CONFIG_ALL_ENABLED; ///< Datasheet values for indoor navigation + static const BME280Config + BME280_CONFIG_TEMP_SINGLE; ///< Temperature enabled in forced mode - const BME280Data* getDataPtr() { return &data; } + BME280(SPISlave spiSlave_, + BME280Config config_ = BME280_CONFIG_ALL_ENABLED); - BME280Data data; + /** + * @brief Initialize the device with the specified configuration + */ + bool init() override; -private: - void loadCompensationParams() - { - uint8_t buf[24]; - Bus::read(REG_CALIB_T, buf, 24); - - comp_params.dig_T1 = toUInt16(&buf[0]); - comp_params.dig_T2 = toInt16(&buf[2]); - comp_params.dig_T3 = toInt16(&buf[4]); - - comp_params.dig_P1 = toUInt16(&buf[6]); - comp_params.dig_P2 = toInt16(&buf[8]); - comp_params.dig_P3 = toInt16(&buf[10]); - comp_params.dig_P4 = toInt16(&buf[12]); - comp_params.dig_P5 = toInt16(&buf[14]); - comp_params.dig_P6 = toInt16(&buf[16]); - comp_params.dig_P7 = toInt16(&buf[18]); - comp_params.dig_P8 = toInt16(&buf[20]); - comp_params.dig_P9 = toInt16(&buf[22]); - } + /** + * @brief Sets the oversampling for humidity readings, use SKIPPED to + * disable humidity sampling + */ + void setHumidityOversampling(Oversampling oversampling); /** - * Returns the temperature in degC. - * Refer to the data sheet for a description of this method. + * @brief Sets the sensor mode + * + * Values: + * - SLEEP_MODE: No measurements are performed + * - FORCED_MODE: A single measurement is performed when this function + * writes the configuration, after the measurement the sensor return to + * sleep mode + * - NORMAL_MODE: Automated cycling between measurements, standby time can + * be set using setStandbyTime() */ - int32_t getFineTemperature(uint32_t raw_T) - { - int32_t adc_T = static_cast<int32_t>(raw_T); - int32_t var1, var2; - var1 = ((((adc_T / 8) - ((int32_t)comp_params.dig_T1 * 2))) * - ((int32_t)comp_params.dig_T2)) >> - 11; - var2 = (((((adc_T / 16) - ((int32_t)comp_params.dig_T1)) * - ((adc_T / 16) - ((int32_t)comp_params.dig_T1))) / - 4096) * - ((int32_t)comp_params.dig_T3)) / - 16384; - - return var1 + var2; - } - - int32_t compensateTemperature(int32_t fine_temperature) - { - return (fine_temperature * 5 + 128) / 256; - } + void setSensorMode(Mode mode); - uint32_t compensatePressure(uint32_t adc_P, int32_t fine_temperature) - { - int64_t var1, var2, p; - var1 = ((int64_t)fine_temperature) - 128000; + /** + * @brief Sets the oversampling for pressure readings, use SKIPPED to + * disable pressure sampling + */ + void setPressureOversampling(Oversampling oversampling); - var2 = var1 * var1 * (int64_t)comp_params.dig_P6; - var2 = var2 + ((var1 * (int64_t)comp_params.dig_P5) * 131072); - var2 = var2 + (((int64_t)comp_params.dig_P4) * 34359738368); + /** + * @brief Sets the oversampling for temperature readings, use SKIPPED to + * disable temperature sampling + */ + void setTemperatureOversampling(Oversampling oversampling); - var1 = ((var1 * var1 * (int64_t)comp_params.dig_P3) / 256) + - ((var1 * ((int64_t)comp_params.dig_P2) * 4096)); + /** + * @brief Sets the coefficient for the IIR filter (applied to temperature + * and pressure) + */ + void setFilterCoeff(FilterCoeff filterCoeff); - var1 = (((int64_t)1) * 140737488355328 + var1) * - ((int64_t)comp_params.dig_P1) / 8589934592; + /** + * @brief Sets the standby time between readings in normal mode + */ + void setStandbyTime(StandbyTime standbyTime); - if (var1 == 0) - { - return 0; // avoid exception caused by division by zero - } + /** + * @brief Reads only the humidity, does not set the configuration + */ + HumidityData readHumidity(); - p = 1048576 - (int32_t)adc_P; - p = (((p * 2147483648) - var2) * 3125) / var1; + /** + * @brief Reads only the pressure, does not set the configuration + */ + PressureData readPressure(); - var1 = (((int64_t)comp_params.dig_P9) * (p / 8192) * (p / 8192)) / - 33554432; + /** + * @brief Reads only the temperature, does not set the configuration + */ + TemperatureData readTemperature(); - var2 = (((int64_t)comp_params.dig_P8) * p) / 524288; + HumidityData getHumidity(); - p = ((p + var1 + var2) / 256) + (((int64_t)comp_params.dig_P7) * 16); + PressureData getPressure(); - // p = ((var3 / 2) * 100) / 128; - return (uint32_t)p; - } + TemperatureData getTemerature(); /** - * Converts two bytes into an unsigned int 16 - * @param buf: buffer containing at least two bytes + * @brief Maximum measurement time formula from datasheet page 51 + * + * @return Time in milliseconds */ - uint16_t toUInt16(uint8_t* buf) { return buf[0] | buf[1] << 8; } + static unsigned int calculateMaxMeasurementTime(BME280Config config_); + + unsigned int getMaxMeasurementTime(); /** - * Converts two bytes into a signed int 16 - * @param buf: buffer containing at least two bytes + * @brief Reads the WHO AM I register + * + * @return True if everything ok */ - int16_t toInt16(uint8_t* buf) - { - return static_cast<int16_t>(buf[0] | buf[1] << 8); - } + bool selfTest() override; - enum Registers : uint8_t - { - REG_WHO_AM_I = 0xD0, - REG_RESET = 0xE0, - REG_CTRL_HUM = 0xF2, - REG_STATUS = 0xF3, - REG_CTRL_MEAS = 0xF4, - REG_CONFIG = 0xF5, +private: + BME280Data sampleImpl() override; - REG_PRESS_MSB = 0xF7, - REG_PRESS_LSB = 0xF8, - REG_PRESS_XLSB = 0xF9, + void setConfiguration(); - REG_TEMP_MSB = 0xFA, - REG_TEMP_LSB = 0xFB, - REG_TEMP_XLSB = 0xFC, + void setConfiguration(BME280Config config_); - REG_HUM_MSB = 0xFD, - REG_HUM_LSB = 0xFE, + BME280Config readConfiguration(); - REG_CALIB_T = 0x88, - REG_CALUB_P = 0x8E - }; + void loadCompensationParameters(); + + // Compensation algorithm rev.1.1 from Bosh datasheet + + int32_t computeFineTemperature(int32_t adc_T); + + int32_t compensateTemperature(int32_t t_fine); + + uint32_t compensatePressure(int32_t adc_P); + + uint32_t compensateHumidity(int32_t adc_H); /** - * Compensation parameters to convert ADC values to real temperature and - * pressure - */ - struct CompensationParams - { - uint16_t dig_T1; - int16_t dig_T2; - int16_t dig_T3; - - uint16_t dig_P1; - int16_t dig_P2; - int16_t dig_P3; - int16_t dig_P4; - int16_t dig_P5; - int16_t dig_P6; - int16_t dig_P7; - int16_t dig_P8; - int16_t dig_P9; - }; + * @brief Check the WHO AM I code from the device. + * + * @return true if the device is recognized + */ + bool checkWhoAmI(); - bool initialized = false; + const SPISlave spiSlave; + BME280Config config; + BME280Comp compParams; + int32_t t_fine; // Used in compensation algorithm - const uint8_t ID = 0x60; - const uint8_t RESET_WORD = 0xB6; + bool initialized = false; // Whether the sensor has been initialized - CompensationParams comp_params; + PrintLogger logger = Logging::getLogger("bme280"); }; diff --git a/src/shared/sensors/BME280/BME280Data.h b/src/shared/sensors/BME280/BME280Data.h index b0fac260ab49776cae4503258c6c82ca488564a8..22e3eec8c9ad78865fa785c690593a60730dfaa0 100644 --- a/src/shared/sensors/BME280/BME280Data.h +++ b/src/shared/sensors/BME280/BME280Data.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,36 +13,43 @@ * * 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 + * 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. */ -#ifndef SRC_SHARED_SENSORS_BME280DATA_H -#define SRC_SHARED_SENSORS_BME280DATA_H -#include <ostream> -#include <string> +#pragma once -struct BME280Data +#include "sensors/SensorData.h" + +struct BME280Data : public TemperatureData, + public PressureData, + public HumidityData { - uint32_t timestamp = 0; - float pressure = 0; - float temperature = 0; - uint32_t raw_pressure = 0; - uint32_t raw_temperature = 0; - + BME280Data() + : TemperatureData{0, 0.0}, PressureData{0, 0.0}, HumidityData{0, 0.0} + { + } + + BME280Data(uint64_t timestamp, float temperature, float pressure, + float humidity) + : TemperatureData{timestamp, temperature}, + PressureData{timestamp, pressure}, HumidityData{timestamp, humidity} + + { + } + static std::string header() { - return "timestamp,pressure,temperature,raw_pressure,raw_temperature\n"; + return "temp_timestamp,temp,press_timestamp,press,humid_timestamp," + "humid\n"; } void print(std::ostream& os) const { - os << timestamp << "," << pressure << "," << temperature << "," - << raw_pressure << "," << raw_temperature << "\n"; + os << temp_timestamp << "," << temp << "," << press_timestamp << "," + << press << "," << humid_timestamp << "," << humid << "\n"; } -}; - -#endif /* SRC_SHARED_SENSORS_BME280DATA_H */ +}; \ No newline at end of file diff --git a/src/shared/sensors/BMX160/BMX160.cpp b/src/shared/sensors/BMX160/BMX160.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3189904e434d79d1a056f76f71fbe2613bcdaea1 --- /dev/null +++ b/src/shared/sensors/BMX160/BMX160.cpp @@ -0,0 +1,968 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Davide Mor + * + * 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. + */ + +#include "BMX160.h" + +BMX160::BMX160(SPIBusInterface& bus, GpioPin cs, BMX160Config config) + : BMX160(bus, cs, config, SPIBusConfig{}) +{ + spi_slave.config.clock_div = SPIClockDivider::DIV32; + old_mag.mag_timestamp = 0.0f; + old_gyr.gyro_timestamp = 0.0f; + old_acc.accel_timestamp = 0.0f; +} + +BMX160::BMX160(SPIBusInterface& bus, GpioPin cs, BMX160Config config, + SPIBusConfig bus_config) + : spi_slave(bus, cs, bus_config), config(config) +{ + old_mag.mag_timestamp = 0.0f; + old_gyr.gyro_timestamp = 0.0f; + old_acc.accel_timestamp = 0.0f; +} + +bool BMX160::init() +{ +#ifdef DEBUG + assert(!is_init && "init() should be called once"); +#endif + + if (!checkChipid()) + { + LOG_ERR(logger, "Got bad CHIPID"); + last_error = SensorErrors::INVALID_WHOAMI; + return false; + } + + softReset(); + + if (!setPowerMode()) + { + + LOG_ERR(logger, "Not all interfaces are up and running!"); + last_error = SensorErrors::INIT_FAIL; + return false; + } + + initAcc(); + initGyr(); + initMag(); + + initFifo(); + initInt(); + + return is_init = true; +} + +bool BMX160::selfTest() +{ +#ifdef DEBUG + assert(is_init && "init() was not called"); +#endif + + // The device will enter in an unusable state when testing. + is_init = false; + + if (!testAcc() || !testGyr() || !testMag()) + { + last_error = SensorErrors::SELF_TEST_FAIL; + return false; + } + else + { + // Finally reinitialize the device into a known state + return init(); + } +} + +void BMX160::IRQupdateTimestamp(uint64_t ts) +{ + // Prevent interrupts while reading fifo + if (irq_enabled) + { + SensorFIFO::IRQupdateTimestamp(ts); + } +} + +BMX160Data BMX160::sampleImpl() +{ +#ifdef DEBUG + assert(is_init && "init() was not called"); +#endif + // Reset any errors. + last_error = SensorErrors::NO_ERRORS; + + // Read temperature + if (config.temp_divider != 0 && temp_counter % config.temp_divider == 0) + readTemp(); + + temp_counter++; + + // Delete old samples + last_fifo_level = 0; + + switch (config.fifo_mode) + { + case BMX160Config::FifoMode::DISABLED: + // Just push one sample + readData(); + break; + + case BMX160Config::FifoMode::HEADERLESS: + // Read whole FIFO (headerless) + readFifo(true); + break; + + case BMX160Config::FifoMode::HEADER: + // Read whole FIFO (header) + readFifo(false); + break; + } + + if (last_error != SensorErrors::NO_ERRORS || last_fifo_level == 0) + { + // Something went wrong, return dummy data + return BMX160Data{}; + } + else + { + return last_fifo[last_fifo_level - 1]; + } +} + +BMX160Temperature BMX160::getTemperature() +{ + BMX160Temperature t; + t.temp_timestamp = TimestampTimer::getTimestamp(); + t.temp = temperature; + return t; +} + +BMX160FifoStats BMX160::getFifoStats() { return stats; } + +void BMX160::sendCmd(SPITransaction& spi, BMX160Defs::Cmd cmd, + BMX160Defs::PowerMode pmu) +{ + spi.write(BMX160Defs::REG_CMD, + static_cast<uint8_t>(cmd) | static_cast<uint8_t>(pmu)); +} + +void BMX160::pushSample(BMX160Data sample) +{ + last_fifo[last_fifo_level++] = sample; +} + +void BMX160::confMag(SPITransaction& spi, uint8_t value) +{ + spi.write(BMX160Defs::REG_MAG_IF_0, value); + miosix::Thread::sleep(10); +} + +void BMX160::mapMag(SPITransaction& spi, uint8_t reg) +{ + spi.write(BMX160Defs::REG_MAG_IF_1, reg); + miosix::Thread::sleep(10); +} + +uint8_t BMX160::readMag(SPITransaction& spi, uint8_t reg) +{ + mapMag(spi, reg); + return spi.read(BMX160Defs::REG_DATA_MAG); +} + +void BMX160::readMag(SPITransaction& spi, uint8_t reg, uint8_t* data, + size_t size) +{ + while (size != 0) + { + int burst = 0; + if (size >= 8) + { + confMag(spi, + BMX160Defs::MAG_IF_0_MANUAL | BMX160Defs::MAG_IF_0_BURST_8); + burst = 8; + } + else if (size >= 6) + { + confMag(spi, + BMX160Defs::MAG_IF_0_MANUAL | BMX160Defs::MAG_IF_0_BURST_6); + burst = 6; + } + else if (size >= 2) + { + confMag(spi, + BMX160Defs::MAG_IF_0_MANUAL | BMX160Defs::MAG_IF_0_BURST_2); + burst = 2; + } + else + { + confMag(spi, + BMX160Defs::MAG_IF_0_MANUAL | BMX160Defs::MAG_IF_0_BURST_1); + burst = 1; + } + + mapMag(spi, reg); + spi.read(BMX160Defs::REG_DATA_MAG, data, burst); + + reg += burst; + data += burst; + size -= burst; + } + + confMag(spi, BMX160Defs::MAG_IF_0_MANUAL); +} + +void BMX160::writeMag(SPITransaction& spi, uint8_t reg, uint8_t value) +{ + spi.write(BMX160Defs::REG_MAG_IF_3, value); + spi.write(BMX160Defs::REG_MAG_IF_2, reg); + miosix::Thread::sleep(10); +} + +bool BMX160::checkChipid() +{ + SPITransaction spi(spi_slave); + auto chip_id = spi.read(BMX160Defs::REG_CHIPID); + + return chip_id == BMX160Defs::CHIPID; +} + +void BMX160::softReset() +{ + SPITransaction spi(spi_slave); + + // Reset the state of the device, just to be sure. + sendCmd(spi, BMX160Defs::Cmd::SOFTRESET); + miosix::Thread::sleep(10); + + // Dummy read of REG_COMM_TEST to enable SPI + spi.read(BMX160Defs::REG_COMM_TEST); + miosix::Thread::sleep(10); +} + +bool BMX160::setPowerMode() +{ + SPITransaction spi(spi_slave); + + sendCmd(spi, BMX160Defs::Cmd::MAG_IF_SET_PMU_MODE, + BMX160Defs::PowerMode::NORMAL); + miosix::Thread::sleep(80); + + sendCmd(spi, BMX160Defs::Cmd::GYR_SET_PMU_MODE, + BMX160Defs::PowerMode::NORMAL); + miosix::Thread::sleep(80); + + sendCmd(spi, BMX160Defs::Cmd::ACC_SET_PMU_MODE, + BMX160Defs::PowerMode::NORMAL); + miosix::Thread::sleep(80); + + // Check if all sensors are up and running + return (spi.read(BMX160Defs::REG_PMU_STATUS) & + BMX160Defs::PMU_STATUS_ALL_MASK) == + BMX160Defs::PMU_STATUS_ALL_NORMAL; +} + +void BMX160::initAcc() +{ + // Calculate accelerometer sensibility + switch (config.acc_range) + { + case BMX160Config::AccelerometerRange::G_2: + acc_sensibility = 1.0f / 16384.0f; + break; + case BMX160Config::AccelerometerRange::G_4: + acc_sensibility = 1.0f / 8192.0f; + break; + case BMX160Config::AccelerometerRange::G_8: + acc_sensibility = 1.0f / 4096.0f; + break; + case BMX160Config::AccelerometerRange::G_16: + acc_sensibility = 1.0f / 2048.0f; + break; + } + + SPITransaction spi(spi_slave); + + spi.write(BMX160Defs::REG_ACC_CONF, + static_cast<uint8_t>(config.acc_odr) | + static_cast<uint8_t>(config.acc_bwp)); + spi.write(BMX160Defs::REG_ACC_RANGE, + static_cast<uint8_t>(config.acc_range)); +} + +void BMX160::initGyr() +{ + // Calculate gyro sensibility + switch (config.gyr_range) + { + case BMX160Config::GyroscopeRange::DEG_2000: + gyr_sensibility = 1.0f / 16.4f; + break; + case BMX160Config::GyroscopeRange::DEG_1000: + gyr_sensibility = 1.0f / 32.8f; + break; + case BMX160Config::GyroscopeRange::DEG_500: + gyr_sensibility = 1.0f / 65.6f; + break; + case BMX160Config::GyroscopeRange::DEG_250: + gyr_sensibility = 1.0f / 131.2f; + break; + case BMX160Config::GyroscopeRange::DEG_125: + gyr_sensibility = 1.0f / 262.4f; + break; + } + + SPITransaction spi(spi_slave); + spi.write(BMX160Defs::REG_GYR_CONF, + static_cast<uint8_t>(config.gyr_odr) | + static_cast<uint8_t>(config.gyr_bwp)); + spi.write(BMX160Defs::REG_GYR_RANGE, + static_cast<uint8_t>(config.gyr_range)); +} + +void BMX160::initMag() +{ + /* + Little explanation of this: + The magnetometer is not controlled directly, + instead we have a secondary controller, BMM150, + with its own register accessed with REG_MAG_IF_[1-3] + */ + + SPITransaction spi(spi_slave); + + // Enable manual configuration mode + confMag(spi, BMX160Defs::MAG_IF_0_MANUAL); + + // Put MAG into sleep mode (from suspend mode) + writeMag(spi, BMX160Defs::MAG_REG_RESET, + BMX160Defs::MAG_RESET_POWER_CONTROL); + + writeMag(spi, BMX160Defs::MAG_REG_REPXY, config.mag_repxy); + writeMag(spi, BMX160Defs::MAG_REG_REPZ, config.mag_repz); + + if (config.enable_compensation) + boschReadTrim(spi); + + // Magic sequence to init it + writeMag(spi, BMX160Defs::MAG_REG_CONTROL, BMX160Defs::MAG_CONTROL_FORCED); + mapMag(spi, BMX160Defs::MAG_REG_DATA); + + // Set mag output data rate + spi.write(BMX160Defs::REG_MAG_CONF, static_cast<uint8_t>(config.mag_odr)); + miosix::Thread::sleep(10); + + // Disable manual configuration mode + confMag(spi, 0); +} + +void BMX160::initFifo() +{ + if (config.fifo_mode != BMX160Config::FifoMode::DISABLED) + { + SPITransaction spi(spi_slave); + + uint8_t config_byte = BMX160Defs::FIFO_CONFIG_1_ACC_EN | + BMX160Defs::FIFO_CONFIG_1_GYR_EN | + BMX160Defs::FIFO_CONFIG_1_MAG_EN; + + if (config.fifo_mode == BMX160Config::FifoMode::HEADER) + config_byte |= BMX160Defs::FIFO_CONFIG_1_HEADER_EN; + + spi.write(BMX160Defs::REG_FIFO_CONFIG_1, config_byte); + + config_byte = + (config.fifo_gyr_downs & 3) | ((config.fifo_acc_downs & 3) << 4); + + if (config.fifo_acc_filtered) + config_byte |= BMX160Defs::FIFO_DOWNS_ACC_FILT_DATA; + + if (config.fifo_gyr_filtered) + config_byte |= BMX160Defs::FIFO_DOWNS_GYR_FILT_DATA; + + spi.write(BMX160Defs::REG_FIFO_DOWNS, config_byte); + + sendCmd(spi, BMX160Defs::Cmd::FIFO_FLUSH); + } +} + +void BMX160::initInt() +{ + if (config.fifo_int != BMX160Config::FifoInterruptPin::DISABLED) + { + SPITransaction spi(spi_slave); + + // Select mode between PUSH_PULL or OPEN_DRAIN + uint8_t out_ctrl = 0; + out_ctrl |= BMX160Defs::INT_OUT_CTRL_INT2_OUT_EN | + BMX160Defs::INT_OUT_CTRL_INT1_OUT_EN; + + if (config.int1_mode == BMX160Config::IntMode::OPEN_DRAIN) + { + out_ctrl |= BMX160Defs::INT_OUT_CTRL_INT1_OD; + } + if (config.int2_mode == BMX160Config::IntMode::OPEN_DRAIN) + { + out_ctrl |= BMX160Defs::INT_OUT_CTRL_INT2_OD; + } + + // Enable both interrupt pins, otherwise they'll just float. + // We configure both of them as push-pull and active-low + spi.write(BMX160Defs::REG_INT_OUT_CTRL, out_ctrl); + + // Set fifo watermark + spi.write(BMX160Defs::REG_FIFO_CONFIG_0, config.fifo_watermark); + + // Enable FIFO full interrupt and fifo watermark + spi.write(BMX160Defs::REG_INT_EN_1, + BMX160Defs::INT_EN_1_FIFO_FULL | + BMX160Defs::INT_EN_1_FIFO_WATERMARK); + + // Enable interrupt pin map + if (config.fifo_int == BMX160Config::FifoInterruptPin::PIN_INT1) + { + // Configure to use INT1 + spi.write(BMX160Defs::REG_INT_MAP_1, + BMX160Defs::INT_MAP_1_INT_1_FIFO_FULL | + BMX160Defs::INT_MAP_1_INT_1_FIFO_WATERMARK); + } + else + { + // Configure to use INT2 + spi.write(BMX160Defs::REG_INT_MAP_1, + BMX160Defs::INT_MAP_1_INT_2_FIFO_FULL | + BMX160Defs::INT_MAP_1_INT_2_FIFO_WATERMARK); + } + } +} + +bool BMX160::testAcc() +{ + const uint16_t SELF_TEST_LIMIT = 8192; + const uint8_t ACC_CONF_TEST = 0x2C; + const uint8_t ACC_RANGE_TEST = 0x08; + + SPITransaction spi(spi_slave); + + // The acc will complain otherwise... + spi.write(BMX160Defs::REG_ACC_CONF, ACC_CONF_TEST); + spi.write(BMX160Defs::REG_ACC_RANGE, ACC_RANGE_TEST); + + // Enable acc self-test + positive force + self-test deflection + spi.write(BMX160Defs::REG_SELF_TEST, BMX160Defs::SELF_TEST_ACC_AMP | + BMX160Defs::SELF_TEST_ACC_SIGN | + BMX160Defs::SELF_TEST_ACC_ENABLE); + miosix::Thread::sleep(50); + + int16_t pos_acc[3]; + spi.read(BMX160Defs::REG_DATA_ACC, reinterpret_cast<uint8_t*>(pos_acc), + sizeof(pos_acc)); + + // Enable acc self-test + negative force + self-test deflection + spi.write(BMX160Defs::REG_SELF_TEST, + BMX160Defs::SELF_TEST_ACC_AMP | BMX160Defs::SELF_TEST_ACC_ENABLE); + miosix::Thread::sleep(50); + + int16_t neg_acc[3]; + spi.read(BMX160Defs::REG_DATA_ACC, reinterpret_cast<uint8_t*>(neg_acc), + sizeof(neg_acc)); + + if ((neg_acc[0] - pos_acc[0]) < SELF_TEST_LIMIT || + (neg_acc[1] - pos_acc[1]) < SELF_TEST_LIMIT || + (neg_acc[2] - pos_acc[2]) < SELF_TEST_LIMIT) + { + LOG_ERR(logger, "Accelerometer self-test failed!"); + LOG_ERR(logger, "pos_acc: {} {} {}", pos_acc[0], pos_acc[1], + pos_acc[2]); + LOG_ERR(logger, "neg_acc: {} {} {}", neg_acc[0], neg_acc[1], + neg_acc[2]); + + return false; + } + + // Reset self-test + spi.write(BMX160Defs::REG_SELF_TEST, 0); + return true; +} + +bool BMX160::testGyr() +{ + // Start gyro self-test + SPITransaction spi(spi_slave); + + spi.write(BMX160Defs::REG_SELF_TEST, BMX160Defs::SELF_TEST_GYR); + + miosix::Thread::sleep(50); + + // Read back the results + if (!(spi.read(BMX160Defs::REG_STATUS) & 2)) + { + LOG_ERR(logger, "Gyroscope self-test failed!"); + return false; + } + else + { + return true; + } +} + +bool BMX160::testMag() +{ + SPITransaction spi(spi_slave); + + // Enable manual configuration mode + confMag(spi, BMX160Defs::MAG_IF_0_MANUAL); + + // Enable self-test and put magnetometer in sleep + writeMag(spi, BMX160Defs::MAG_REG_CONTROL, + BMX160Defs::MAG_CONTROL_SELF_TEST | BMX160Defs::MAG_CONTROL_SLEEP); + miosix::Thread::sleep(200); + + // Check if it has finished + if (readMag(spi, BMX160Defs::MAG_REG_CONTROL) & + BMX160Defs::MAG_CONTROL_SELF_TEST) + { + LOG_ERR(logger, "Magnetometer didn't finish self-test!"); + return false; + } + + // Read back test results + int16_t mag[4]; + readMag(spi, BMX160Defs::MAG_REG_DATA, reinterpret_cast<uint8_t*>(mag), + sizeof(mag)); + + // Test results are stored in the lower bit of the 3 axis + if (!(mag[0] & 1) || !(mag[1] & 1) || !(mag[2] & 1)) + { + LOG_ERR(logger, "Magnetometer self-test failed!"); + LOG_ERR(logger, "result: %d %d %d %d\n", mag[0], mag[1], mag[2], + mag[3]); + return false; + } + else + { + return true; + } +} + +MagnetometerData BMX160::buildMagData(BMX160Defs::MagRaw data, + uint64_t timestamp) +{ + // Strip the lower 3 bits for xy + data.x >>= 3; + data.y >>= 3; + // Strip the lower 1 bit for z + data.z >>= 1; + + if (config.enable_compensation) + { + return MagnetometerData{timestamp, + boschMagCompensateX(data.x, data.rhall), + boschMagCompensateY(data.y, data.rhall), + boschMagCompensateZ(data.z, data.rhall)}; + } + else + { + return MagnetometerData{timestamp, data.x * BMX160Defs::MAG_SENSIBILITY, + data.y * BMX160Defs::MAG_SENSIBILITY, + data.z * BMX160Defs::MAG_SENSIBILITY}; + } +} + +AccelerometerData BMX160::buildAccData(BMX160Defs::AccRaw data, + uint64_t timestamp) +{ + return AccelerometerData{timestamp, + data.x * acc_sensibility * EARTH_GRAVITY, + data.y * acc_sensibility * EARTH_GRAVITY, + data.z * acc_sensibility * EARTH_GRAVITY}; +} + +GyroscopeData BMX160::buildGyrData(BMX160Defs::GyrRaw data, uint64_t timestamp) +{ + if (config.gyr_unit == BMX160Config::GyroscopeMeasureUnit::DEG) + { + return GyroscopeData{timestamp, data.x * gyr_sensibility, + data.y * gyr_sensibility, + data.z * gyr_sensibility}; + } + else + { + return GyroscopeData{timestamp, + data.x * gyr_sensibility * DEGREES_TO_RADIANS, + data.y * gyr_sensibility * DEGREES_TO_RADIANS, + data.z * gyr_sensibility * DEGREES_TO_RADIANS}; + } +} + +const char* BMX160::debugErr(SPITransaction& spi) +{ + uint8_t err = spi.read(BMX160Defs::REG_ERR); + + if (err & 1) + { + return "Chip not operable"; + } + else if (err & 64) + { + return "Dropped command to register 0x7E"; + } + else + { + // Mask error code + err = (err >> 1) & 0x0F; + switch (err) + { + case 0: + return "No error"; + case 1: + case 2: + return "Generic error"; + case 3: + return "LPM and interrupt uses pre-filtered data"; + case 6: + return "ODR do not match"; + case 7: + return "LPM uses pre-filtered data"; + default: + return "Reserved error"; + } + } +} + +uint64_t BMX160::odrToTimeOffset(BMX160Config::OutputDataRate odr, + uint8_t downs) +{ + // Adjust ODR for downsampling + uint8_t real_odr = static_cast<uint64_t>(odr) - (downs & 3); + + // Hz = 100 / 2^(8-odr) + // Sec = 2^(13-odr) / 3200 + // Micro = (2^(13-odr)) * 10000 / 32; + + return ((1 << (13 - real_odr)) * 10000) >> 5; +} + +void BMX160::readTemp() +{ + SPITransaction spi(spi_slave); + + int16_t val = spi.read(BMX160Defs::REG_TEMPERATURE_0) | + (spi.read(BMX160Defs::REG_TEMPERATURE_1) << 8); + + // Correct for sensibility and offset + temperature = (val * BMX160Defs::TEMP_SENSIBILITY) + 23.0f; +} + +void BMX160::readData() +{ + SPITransaction spi(spi_slave); + + uint8_t buf[20]; + spi.read(BMX160Defs::REG_DATA, buf, sizeof(buf)); + + int idx = 0; + auto mag_raw = parseStruct<BMX160Defs::MagRaw>(buf, idx); + auto gyr_raw = parseStruct<BMX160Defs::GyrRaw>(buf, idx); + auto acc_raw = parseStruct<BMX160Defs::AccRaw>(buf, idx); + + // Push a new sample into the fifo + pushSample(BMX160Data{ + buildAccData(acc_raw, last_interrupt_us), + buildGyrData(gyr_raw, last_interrupt_us), + buildMagData(mag_raw, last_interrupt_us), + }); +} + +void BMX160::readFifo(bool headerless) +{ + irq_enabled = false; + + SPITransaction spi(spi_slave); + + int len = spi.read(BMX160Defs::REG_FIFO_LENGTH_0) | + ((spi.read(BMX160Defs::REG_FIFO_LENGTH_1) & 7) << 8); + + if (len == 0) + { + // The buffer is empty, return early + last_error = SensorErrors::NO_NEW_DATA; + return; + } + + uint8_t buf[FIFO_BUF_SIZE]; + +#ifdef DEBUG + assert(len <= static_cast<int>(sizeof(buf)) && "Buffer overflow!"); +#endif + + // Shift the old timestamps, this allows to use old timestamps with the + // current frame. + if (old_mag.mag_timestamp != 0) + old_mag.mag_timestamp -= dt_interrupt; + if (old_gyr.gyro_timestamp != 0) + old_gyr.gyro_timestamp -= dt_interrupt; + if (old_acc.accel_timestamp != 0) + old_acc.accel_timestamp -= dt_interrupt; + + // Calculate time offset + uint64_t time_offset = std::min({ + odrToTimeOffset(config.mag_odr, 0), + odrToTimeOffset(config.gyr_odr, config.fifo_gyr_downs), + odrToTimeOffset(config.acc_odr, config.fifo_acc_downs), + }); + + spi.read(BMX160Defs::REG_FIFO_DATA, buf, len); + uint64_t timestamp = 0; + uint64_t watermark_ts = 0; + + int idx = 0; + while (idx < len && buf[idx] != BMX160Defs::FIFO_STOP_BYTE) + { + + if (headerless) + { + auto mag_raw = parseStruct<BMX160Defs::MagRaw>(buf, idx); + auto gyr_raw = parseStruct<BMX160Defs::GyrRaw>(buf, idx); + auto acc_raw = parseStruct<BMX160Defs::AccRaw>(buf, idx); + + old_mag = buildMagData(mag_raw, timestamp); + old_gyr = buildGyrData(gyr_raw, timestamp); + old_acc = buildAccData(acc_raw, timestamp); + + // Push a new sample into the fifo + pushSample(BMX160Data{old_acc, old_gyr, old_mag}); + + if (watermark_ts == 0 && idx >= (config.fifo_watermark * 4)) + { + watermark_ts = timestamp; + } + + timestamp += time_offset; + } + else + { + uint8_t header = buf[idx++]; + + if ((header & BMX160Defs::FIFO_HEADER_MODE_MASK) == + BMX160Defs::FIFO_HEADER_MODE_REGULAR) + { + // This is a regular packet + + // Mask out everything but fh_parm + header &= BMX160Defs::FIFO_HEADER_PARM_MASK; + + // This contains magnet data + if (header & BMX160Defs::FIFO_HEADER_PARM_MAG_DATA) + { + auto mag_raw = parseStruct<BMX160Defs::MagRaw>(buf, idx); + old_mag = buildMagData(mag_raw, timestamp); + } + + // This contains gyro data + if (header & BMX160Defs::FIFO_HEADER_PARM_GYR_DATA) + { + auto gyr_raw = parseStruct<BMX160Defs::GyrRaw>(buf, idx); + old_gyr = buildGyrData(gyr_raw, timestamp); + } + + // This contains accel data + if (header & BMX160Defs::FIFO_HEADER_PARM_ACC_DATA) + { + auto acc_raw = parseStruct<BMX160Defs::AccRaw>(buf, idx); + old_acc = buildAccData(acc_raw, timestamp); + } + + // Push a new sample into the fifo + pushSample(BMX160Data{old_acc, old_gyr, old_mag}); + + if (watermark_ts == 0 && idx >= (config.fifo_watermark * 4)) + { + watermark_ts = timestamp; + } + + timestamp += time_offset; + } + else if ((header & BMX160Defs::FIFO_HEADER_MODE_MASK) == + BMX160Defs::FIFO_HEADER_MODE_CONTROL) + { + // This is a control packet + + // Mask out everything but fh_parm + header &= BMX160Defs::FIFO_HEADER_PARM_MASK; + + if (header == BMX160Defs::FIFO_HEADER_PARM_SKIP) + { + // Skip frame + idx += 1; + } + else if (header == BMX160Defs::FIFO_HEADER_PARM_SENSORTIME) + { + // Sensortime frame + idx += 3; + } + else if (header == BMX160Defs::FIFO_HEADER_PARM_CONFIG) + { + // FIFO_input_config_frame + idx += 1; + } + } + else + { + LOG_ERR(logger, "Malformed packet! Aborting fifo transfer..."); + + last_error = + static_cast<SensorErrors>(BMX160Errors::INVALID_FIFO_DATA); + break; + } + } + } + + // Update fifo statistics + stats.timestamp = TimestampTimer::getTimestamp(); + stats.watermark_ts = watermark_ts; + stats.fifo_duration = timestamp; + stats.interrupt_dt = dt_interrupt; + stats.len = len; + + // Adjust timestamps + for (int i = 0; i < last_fifo_level; i++) + { + last_fifo[i].accel_timestamp += last_interrupt_us - watermark_ts; + last_fifo[i].gyro_timestamp += last_interrupt_us - watermark_ts; + last_fifo[i].mag_timestamp += last_interrupt_us - watermark_ts; + } + + irq_enabled = true; +} + +template <typename T> +T BMX160::parseStruct(uint8_t* buf, int& idx) +{ + T data; + memcpy(&data, buf + idx, sizeof(T)); + idx += sizeof(T); + + return data; +} + +/**======================================================================== + * Warning: the following code is extrapolated from the bosch driver + * source code, I have no idea of what it does. + * https://github.com/BoschSensortec/BMM150-Sensor-API/blob/a20641f216057f0c54de115fe81b57368e119c01/bmm150.c#L1614 + * ======================================================================== + */ + +void BMX160::boschReadTrim(SPITransaction& spi) +{ + uint8_t trim_x1y1[2] = {0}; + uint8_t trim_xyz_data[4] = {0}; + uint8_t trim_xy1xy2[10] = {0}; + + readMag(spi, BMX160Defs::MAG_REG_DIG_X1, trim_x1y1, sizeof(trim_x1y1)); + readMag(spi, BMX160Defs::MAG_REG_DIG_Z4_0, trim_xyz_data, + sizeof(trim_xyz_data)); + readMag(spi, BMX160Defs::MAG_REG_DIG_Z2_0, trim_xy1xy2, + sizeof(trim_xy1xy2)); + + // Read trim registers + trim_data.dig_x1 = trim_x1y1[0]; + trim_data.dig_y1 = trim_x1y1[1]; + trim_data.dig_x2 = trim_xyz_data[2]; + trim_data.dig_y2 = trim_xyz_data[3]; + trim_data.dig_z1 = trim_xy1xy2[2] | (trim_xy1xy2[3] << 8); + trim_data.dig_z2 = trim_xy1xy2[0] | (trim_xy1xy2[1] << 8); + trim_data.dig_z3 = trim_xy1xy2[6] | (trim_xy1xy2[7] << 8); + trim_data.dig_z4 = trim_xyz_data[0] | (trim_xyz_data[1] << 8); + trim_data.dig_xy1 = trim_xy1xy2[9]; + trim_data.dig_xy2 = trim_xy1xy2[8]; + trim_data.dig_xyz1 = trim_xy1xy2[4] | ((trim_xy1xy2[5] & 0x7F) << 8); +} + +float BMX160::boschMagCompensateX(int16_t x, uint16_t rhall) +{ + /* clang-format off */ + float retval = 0; + float process_comp_x0; + float process_comp_x1; + float process_comp_x2; + float process_comp_x3; + float process_comp_x4; + + /* Processing compensation equations */ + process_comp_x0 = (((float)trim_data.dig_xyz1) * 16384.0f / rhall); + retval = (process_comp_x0 - 16384.0f); + process_comp_x1 = ((float)trim_data.dig_xy2) * (retval * retval / 268435456.0f); + process_comp_x2 = process_comp_x1 + retval * ((float)trim_data.dig_xy1) / 16384.0f; + process_comp_x3 = ((float)trim_data.dig_x2) + 160.0f; + process_comp_x4 = x * ((process_comp_x2 + 256.0f) * process_comp_x3); + retval = ((process_comp_x4 / 8192.0f) + (((float)trim_data.dig_x1) * 8.0f)) / 16.0f; + + return retval; + /* clang-format on */ +} + +float BMX160::boschMagCompensateY(int16_t y, uint16_t rhall) +{ + /* clang-format off */ + float retval = 0; + float process_comp_y0; + float process_comp_y1; + float process_comp_y2; + float process_comp_y3; + float process_comp_y4; + + /* Processing compensation equations */ + process_comp_y0 = ((float)trim_data.dig_xyz1) * 16384.0f / rhall; + retval = process_comp_y0 - 16384.0f; + process_comp_y1 = ((float)trim_data.dig_xy2) * (retval * retval / 268435456.0f); + process_comp_y2 = process_comp_y1 + retval * ((float)trim_data.dig_xy1) / 16384.0f; + process_comp_y3 = ((float)trim_data.dig_y2) + 160.0f; + process_comp_y4 = y * (((process_comp_y2) + 256.0f) * process_comp_y3); + retval = ((process_comp_y4 / 8192.0f) + (((float)trim_data.dig_y1) * 8.0f)) / 16.0f; + + return retval; + /* clang-format on */ +} + +float BMX160::boschMagCompensateZ(int16_t z, uint16_t rhall) +{ + /* clang-format off */ + float retval = 0; + float process_comp_z0; + float process_comp_z1; + float process_comp_z2; + float process_comp_z3; + float process_comp_z4; + float process_comp_z5; + + process_comp_z0 = ((float)z) - ((float)trim_data.dig_z4); + process_comp_z1 = ((float)rhall) - ((float)trim_data.dig_xyz1); + process_comp_z2 = (((float)trim_data.dig_z3) * process_comp_z1); + process_comp_z3 = ((float)trim_data.dig_z1) * ((float)rhall) / 32768.0f; + process_comp_z4 = ((float)trim_data.dig_z2) + process_comp_z3; + process_comp_z5 = (process_comp_z0 * 131072.0f) - process_comp_z2; + retval = (process_comp_z5 / ((process_comp_z4)*4.0f)) / 16.0f; + + return retval; + /* clang-format on */ +} \ No newline at end of file diff --git a/src/shared/sensors/BMX160/BMX160.h b/src/shared/sensors/BMX160/BMX160.h new file mode 100644 index 0000000000000000000000000000000000000000..9dd62c4b62634496856d26cf944cd1169d3a44b3 --- /dev/null +++ b/src/shared/sensors/BMX160/BMX160.h @@ -0,0 +1,351 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Davide Mor + * + * 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. + */ + +#pragma once + +#include <diagnostic/PrintLogger.h> +#include <drivers/spi/SPIDriver.h> +#include <sensors/Sensor.h> + +#include <algorithm> +#include <cassert> +#include <cstring> + +#include "BMX160Config.h" +#include "BMX160Data.h" +#include "BMX160Defs.h" +#include "Constants.h" +#include "TimestampTimer.h" + +/** + * @brief BMX160 Driver. + */ +class BMX160 : public SensorFIFO<BMX160Data, 200> +{ +public: + /** + * @brief BMX160 Custom errors. + */ + enum BMX160Errors : uint8_t + { + INVALID_FIFO_DATA = + SensorErrors::END_OF_BASE_ERRORS ///< The fifo contained invalid + ///< data. + }; + + /** + * @brief BMX160 Constructor. + * @param bus SPI bus + * @param cs SPI Chip Select pin + * @param config BMX160 configuration + */ + BMX160(SPIBusInterface& bus, GpioPin cs, BMX160Config config = {}); + + /** + * @brief BMX160 Constructor. + * @param bus SPI bus + * @param cs SPI Chip Select pin + * @param config BMX160 configuration + * @param bus_config SPI bus configuration + */ + BMX160(SPIBusInterface& bus, GpioPin cs, BMX160Config config, + SPIBusConfig bus_config); + + /** + * @brief Initialize the driver. + */ + bool init() override; + + /** + * @brief Perform selftest on the device. + */ + bool selfTest() override; + + /** + * @brief Gather data from FIFO/data registers and temperature sensor. + */ + BMX160Data sampleImpl() override; + + /** + * @brief Get last read temperature. + */ + BMX160Temperature getTemperature(); + + /** + * @brief Retrieve last fifo stats. + */ + BMX160FifoStats getFifoStats(); + + /** + * @brief Sometimes the sensor pulls down the interrupt pin while reading + * data. We override this method and update the timestamps only if we are + * not still reading the fifo ( @see{irq_enabled} ). + */ + void IRQupdateTimestamp(uint64_t ts) override; + +private: + /** + * @brief Execute CMD. + * + * @param cmd Command to be executed. + * @param pmu Powermode (MUST be set to PowerMode::SUSPEND when not needed). + */ + void sendCmd(SPITransaction& spi, BMX160Defs::Cmd cmd, + BMX160Defs::PowerMode pmu = BMX160Defs::PowerMode::SUSPEND); + + /** + * @brief Push a sample into the FIFO. + * + * @param sample Sample to be pushed. + */ + void pushSample(BMX160Data sample); + + /** + * @brief Convenience function to configure magnetometer. + * + * @param value bmm150 configuration value. + */ + void confMag(SPITransaction& spi, uint8_t value); + + /** + * @brief Convenience function to map magnetometer for read. + * + * @param reg bmm150 register to be mapped in REG_DATA_MAG. + */ + void mapMag(SPITransaction& spi, uint8_t reg); + + /** + * @brief Convenience function to read a single byte from magnetometer. + * + * @param reg Register to be read. + * @return Value read from the register. + */ + uint8_t readMag(SPITransaction& spi, uint8_t reg); + + /** + * @brief Convenience function to read from magnetometer. + * + * @param reg Register to be read. + * @param[out] data Buffer to fill with the data. + * @param size Size of the buffer. + */ + void readMag(SPITransaction& spi, uint8_t reg, uint8_t* data, size_t size); + + /** + * @brief Convenience function to write to magnetometer. + * + * @param reg Register to be written to. + * @param value Value to write to the register. + */ + void writeMag(SPITransaction& spi, uint8_t reg, uint8_t value); + + /** + * @brief Check for chipid validity. + * + * @return Returns false on failure. + */ + bool checkChipid(); + + /** + * @brief Perform a soft-reset. + */ + void softReset(); + + /** + * @brief Wake interfaces up, should be called after soft-reset. + * + * @return Returns false on failure. + */ + bool setPowerMode(); + + /** + * @brief Initialize accelerometer. + */ + void initAcc(); + + /** + * @brief Initialize gyroscope. + */ + void initGyr(); + + /** + * @brief Initialize magnetometer. + */ + void initMag(); + + /** + * @brief Initialize FIFO. + */ + void initFifo(); + + /** + * @brief Initialize interrupts. + */ + void initInt(); + + /** + * @brief Self-test accelerometer. + * + * @return Returns false on failure. + */ + bool testAcc(); + + /** + * @brief Self-test gyroscope. + * + * @return Returns false on failure. + */ + bool testGyr(); + + /** + * @brief Self-test magnetometer. + * + * @return Returns false on failure. + */ + bool testMag(); + + /** + * @brief Build magnetometer data. + * + * @param data Raw input data. + * @param timestamp Timestamp associated with the data. + */ + MagnetometerData buildMagData(BMX160Defs::MagRaw data, uint64_t timestamp); + + /** + * @brief Build gyroscope data. + * + * @param data Raw input data. + * @param timestamp Timestamp associated with the data. + */ + AccelerometerData buildAccData(BMX160Defs::AccRaw data, uint64_t timestamp); + + /** + * @brief Build accelerometer data. + * + * @param data Raw input data. + * @param timestamp Timestamp associated with the data. + */ + GyroscopeData buildGyrData(BMX160Defs::GyrRaw data, uint64_t timestamp); + + /** + * @brief Debug function used to print the current error state + * + * @return String representing the error. + */ + const char* debugErr(SPITransaction& spi); + + /** + * @brief Convert Output Data Rate to the time between samples. + * + * Warning, anything resulting in a frequency over 1Hz will underflow the + * calculations! + * + * @param odr input output data rate of the sensor. + * @param downs downsampling of the input. + * @return Time between samples. + */ + uint64_t odrToTimeOffset(BMX160Config::OutputDataRate odr, uint8_t downs); + + /** + * @brief Read the value of the temperature sensor + */ + void readTemp(); + + /** + * @brief Read the contents of the DATA register + */ + void readData(); + + /** + * @brief Read the contents of the fifo into buf. + * + * @return Returns false on failure. + */ + void readFifo(bool headerless); + + /** + * @brief Parses T out of a buffer. + * + * @param buf Input buffer. + * @param[in,out] idx Input buffer read index. + */ + template <typename T> + T parseStruct(uint8_t* buf, int& idx); + + /**======================================================================== + * Warning: the following code is extrapolated from the bosch driver + * source code, I have no idea of what it does. + * https://github.com/BoschSensortec/BMM150-Sensor-API/blob/a20641f216057f0c54de115fe81b57368e119c01/bmm150.c#L1614 + * ======================================================================== + */ + + /** + * @brief Read and parse bosch trim registers + */ + void boschReadTrim(SPITransaction& spi); + + /** + * @brief Bosch black-box algorithm + */ + float boschMagCompensateX(int16_t x, uint16_t rhall); + + /** + * @brief Bosch black-box algorithm + */ + float boschMagCompensateY(int16_t y, uint16_t rhall); + + /** + * @brief Bosch black-box algorithm + */ + float boschMagCompensateZ(int16_t z, uint16_t rhall); + + float temperature = 0.0f; + MagnetometerData old_mag; + GyroscopeData old_gyr; + AccelerometerData old_acc; + + bool is_init = false; + SPISlave spi_slave; + + BMX160Defs::TrimData trim_data; + BMX160Config config; + + BMX160FifoStats stats; + + float gyr_sensibility = 0.0f; + float acc_sensibility = 0.0f; + + int temp_counter = 0; + + // Sometimes the buffer gets over 1000 + static constexpr unsigned int FIFO_BUF_SIZE = 1100; + + /** + * Sometimes the sensor pulls down the interrupt pin while reading data. + * We use this variable to ignore incoming interrupts while reading the + * fifo. + */ + bool irq_enabled = true; + + PrintLogger logger = Logging::getLogger("bmx160"); +}; \ No newline at end of file diff --git a/src/shared/sensors/BMX160/BMX160Config.h b/src/shared/sensors/BMX160/BMX160Config.h new file mode 100644 index 0000000000000000000000000000000000000000..743c056a48857d34777d771fb045df7ec8aa35d3 --- /dev/null +++ b/src/shared/sensors/BMX160/BMX160Config.h @@ -0,0 +1,242 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Davide Mor + * + * 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. + */ + +#pragma once + +/** + * @brief BMX160 Configuration + */ +struct BMX160Config +{ + /** + * @brief Fifo operationg mode. + */ + enum class FifoMode + { + DISABLED, ///< The fifo is completely disabled. + HEADERLESS, ///< Sensors MUST have the same odr. + HEADER ///< Sensors can have different odr. + }; + + /** + * @brief Fifo interrupt mode. + * + * Uses fifo full/watermark as triggers. + * We configure the pin as push-pull and active-low. + */ + enum class FifoInterruptPin + { + + DISABLED, ///< The interrupts are completely disabled. + PIN_INT1, ///< Interrupts are enabled on pin 2. + PIN_INT2, ///< Interrupts are enabled on pin 1. + }; + + /** + * @brief Range of the accelerometer expressed in ±g. + */ + enum class AccelerometerRange + { + G_2 = 0x3, ///< Accelaration range ±2g. + G_4 = 0x5, ///< Accelaration range ±4g. + G_8 = 0x8, ///< Accelaration range ±8g. + G_16 = 0xC ///< Accelaration range ±16g. + }; + + /** + * @brief Gyroscope range expressed in °/sec. + */ + enum class GyroscopeRange + { + DEG_2000 = 0x0, ///< Gyroscope range 2000°/sec. + DEG_1000 = 0x1, ///< Gyroscope range 1000°/sec. + DEG_500 = 0x2, ///< Gyroscope range 500°/sec. + DEG_250 = 0x3, ///< Gyroscope range 250°/sec. + DEG_125 = 0x4 ///< Gyroscope range 125°/sec. + }; + + /** + * @brief Gyroscope measure unit (degrees or radiants). + */ + enum class GyroscopeMeasureUnit + { + DEG, ///< Degrees. + RAD ///< Radiants. + }; + + /** + * @brief Output Data Rate expressed in Hz. + * + * Limits are as follows (wrong values will error the device): + * - Accelerometer: 25/2 - 1600 (25/32 - 1600 if undersampling is enabled, + * which is not) + * - Gyroscope: 25 - 3200 + * - Magnetometer: 25/32 - 800 + */ + enum class OutputDataRate + { + HZ_0_78125 = 0x01, ///< Output Data Rate of 25/32Hz = 0.78125. + HZ_1_5625 = 0x02, ///< Output Data Rate of 25/16Hz = 1.5625. + HZ_3_125 = 0x03, ///< Output Data Rate of 25/8Hz = 3.125. + HZ_6_25 = 0x04, ///< Output Data Rate of 25/4Hz = 6.25. + HZ_12_5 = 0x05, ///< Output Data Rate of 25/2Hz = 12.5. + HZ_25 = 0x06, ///< Output Data Rate of 25Hz. + HZ_50 = 0x07, ///< Output Data Rate of 50Hz. + HZ_100 = 0x08, ///< Output Data Rate of 100Hz. + HZ_200 = 0x09, ///< Output Data Rate of 200Hz. + HZ_400 = 0x0A, ///< Output Data Rate of 400Hz. + HZ_800 = 0x0B, ///< Output Data Rate of 800Hz. + HZ_1600 = 0x0C, ///< Output Data Rate of 1600Hz. + HZ_3200 = 0x0D, ///< Output Data Rate of 3200Hz. + }; + + /** + * @brief Bandwidth parameter. + * + * For more detailed explanation, check the 2.4.1 Data Processing + * Accelerometer and 2.4.2 Data Processing Gyroscope chapters of the BMX160 + * datasheet. + */ + enum class BandwidthParameter + { + NORMAL = 0x20, ///< Normal filter operation. + OSR2 = 0x10, ///< Oversampling rate of 2. + OSR4 = 0x00, ///< Oversampling rate of 4. + }; + + /** + * @brief Interrupt pin mode. + * + * Configuration for interrupt pin behaviour. + */ + enum class IntMode + { + PUSH_PULL, //< Push-pull behaviour. + OPEN_DRAIN, //< Open drain behaviour. + }; + + /** + * @brief Fifo watermark to use, in multiples of 4. + * + * Only values between [0-250] make sense to use. + * A really high watermark value (the default) will disable it, falling back + * to FIFO full. + */ + uint8_t fifo_watermark = -1; + + /** + * @brief Repetitions for the XY axis. + * + * Repetitions represent how many internal samples are averaged in order to + * get the final outputted sample, these presets are the ones recommended + * in the BMX160 datasheet, for more informations consult the BMM150 + * datasheet, chapter 4.2.4 Active mode. + * + * This are the reccomended presets: + * - 0x01, RMS Noise (x/y/z) 1.0/1.0/1.4, Current: 0.17mA (Low power) + * - 0x04, RMS Noise (x/y/z) 0.6/0.6/0.6, Current: 0.5mA (Regular) + * (Default) + * - 0x07, RMS Noise (x/y/z) 0.5/0.5/0.5, Current: 0.8mA (Enhanced + * regular) + * - 0x17, RMS Noise (x/y/z) 0.3/0.3/0.3, Current: 4.9mA (High accuracy) + */ + uint8_t mag_repxy = 0x04; + + /** + * @brief Repetitions for the Z axis. + * + * Repetitions represent how many internal samples are averaged in order to + * get the final outputted sample, these presets are the ones reccomended + * in the BMX160 datasheet, for more informations consult the BMM150 + * datasheet, chapter 4.2.4 Active mode. + * + * This are the reccomended presets: + * - 0x02, RMS Noise (x/y/z) 1.0/1.0/1.4, Current: 0.17mA (Low power) + * - 0x0E, RMS Noise (x/y/z) 0.6/0.6/0.6, Current: 0.5mA (Regular) + * (Default) + * - 0x1A, RMS Noise (x/y/z) 0.5/0.5/0.5, Current: 0.8mA (Enhanced + * regular) + * - 0x52, RMS Noise (x/y/z) 0.3/0.3/0.3, Current: 4.9mA (High accuracy) + */ + uint8_t mag_repz = 0x0E; + + /** + * @brief Enable magnetometer data compensation. + * + * The magnetomer support compensation, but it's not documented, and the + * current implementation is based on the source of the bosch driver. + * + * The implementation is slow, probably buggy, and only god knows what it + * does, it's so bad that I added a switch to disable it, use it wisely! + */ + bool enable_compensation = true; + + /** + * @brief Divide the temperature sampling rate. + * + * This is used to limit the sampling of the temperature, use 0 to disable + * it completely. + * + * Every time you call onSimpleUpdate a value is incremented and only if + * the value is a multiple of this parameter a read is performed. + */ + int temp_divider = 0; + + /** + * @brief Should the fifo use accelerometer filtered data? + */ + bool fifo_acc_filtered = true; + + /** + * @brief Fifo accelerometer downsampling (between 0 and 15). + */ + uint8_t fifo_acc_downs = 0; + + /** + * @brief Should the fifo use gyroscope filtered data? + */ + bool fifo_gyr_filtered = true; + + /** + * @brief Fifo gyroscope downsampling (between 0 and 15). + */ + uint8_t fifo_gyr_downs = 0; + + FifoMode fifo_mode = FifoMode::DISABLED; + FifoInterruptPin fifo_int = FifoInterruptPin::DISABLED; + + OutputDataRate acc_odr = OutputDataRate::HZ_100; + BandwidthParameter acc_bwp = BandwidthParameter::NORMAL; + AccelerometerRange acc_range = AccelerometerRange::G_2; + + OutputDataRate gyr_odr = OutputDataRate::HZ_100; + BandwidthParameter gyr_bwp = BandwidthParameter::NORMAL; + GyroscopeRange gyr_range = GyroscopeRange::DEG_2000; + GyroscopeMeasureUnit gyr_unit = GyroscopeMeasureUnit::RAD; + + OutputDataRate mag_odr = OutputDataRate::HZ_100; + + IntMode int1_mode = IntMode::PUSH_PULL; + IntMode int2_mode = IntMode::PUSH_PULL; + + BMX160Config() {} +}; \ No newline at end of file diff --git a/src/shared/sensors/BMX160/BMX160Data.h b/src/shared/sensors/BMX160/BMX160Data.h new file mode 100644 index 0000000000000000000000000000000000000000..b5655c6d96ce32a70fea4c0b0424b31806c7398b --- /dev/null +++ b/src/shared/sensors/BMX160/BMX160Data.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Davide Mor + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct BMX160Data : public AccelerometerData, + public GyroscopeData, + public MagnetometerData +{ + BMX160Data() + : AccelerometerData{0, 0.0, 0.0, 0.0}, GyroscopeData{0, 0.0, 0.0, 0.0}, + MagnetometerData{0, 0.0, 0.0, 0.0} + { + } + + BMX160Data(AccelerometerData acc, GyroscopeData gyr, MagnetometerData mag) + : AccelerometerData(acc), GyroscopeData(gyr), MagnetometerData(mag) + { + } + + static std::string header() + { + return "accel_timestamp,accel_x,accel_y,accel_z,gyro_timestamp,gyro_x," + "gyro_y," + "gyro_z,mag_timestamp,mag_x,mag_y,mag_z\n"; + } + + void print(std::ostream& os) const + { + os << accel_timestamp << "," << accel_x << "," << accel_y << "," + << accel_z << "," << gyro_timestamp << "," << gyro_x << "," << gyro_y + << "," << gyro_z << "," << mag_timestamp << "," << mag_x << "," + << mag_y << "," << mag_z << "\n"; + } +}; + +struct BMX160Temperature : public TemperatureData +{ + static std::string header() { return "temp_timestamp,temperature\n"; } + + void print(std::ostream& os) const + { + os << temp_timestamp << "," << temp << "\n"; + } +}; + +/** + * @brief BMX160 fifo statistics. + */ +struct BMX160FifoStats +{ + uint64_t timestamp; + + uint64_t watermark_ts; //< Watermark timestamp (from the start of + // the fifo) + uint64_t fifo_duration; //< Total fifo duration + uint64_t interrupt_dt; //< Reported delta time between the previous + // interrupt and the current one. + int len; //< Fifo length in bytes. + + static std::string header() + { + return "timestamp,watermark_ts,fifo_duration,interrupt_dt,fifo_len\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << watermark_ts << "," << fifo_duration << "," + << interrupt_dt << "," << len << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/BMX160/BMX160Defs.h b/src/shared/sensors/BMX160/BMX160Defs.h new file mode 100644 index 0000000000000000000000000000000000000000..93679bc8eab214ac4a21eeb7fc22c3d44f990ce4 --- /dev/null +++ b/src/shared/sensors/BMX160/BMX160Defs.h @@ -0,0 +1,375 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Davide Mor + * + * 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. + */ + +#pragma once + +#include <cstdint> + +/** + * @brief Various BMX160 register/enums definitions. + */ +namespace BMX160Defs +{ + +/** + * @brief Temperature sensor sensibility. + */ +const float TEMP_SENSIBILITY = 64.0f / 32768.0f; + +/** + * @brief Magnetometer fixed sensibility. + */ +const float MAG_SENSIBILITY = 0.3f; + +/** + * @brief BMX160 Chip Id. + */ +const uint8_t CHIPID = 0xD8; + +/** + * @brief Default value for FIFO_CONFIG_1. + */ +const uint8_t FIFO_CONFIG_1_DEFAULT = 0x10; + +/** + * @brief Values for SELF_TEST register. + */ +enum SELF_TEST +{ + SELF_TEST_GYR = 0x10, ///< Starts selftest of the gyroscope. + SELF_TEST_ACC_AMP = 0x08, ///< Select amplitude of the selftest deflection. + SELF_TEST_ACC_SIGN = 0x04, ///< Select sign of selftest exitation. + SELF_TEST_ACC_ENABLE = 0x01 ///< Starts selftest of the accelerometer. +}; + +/** + * @brief Values for FIFO_CONFIG_1 register. + */ +enum FIFO_CONFIG_1 +{ + FIFO_CONFIG_1_ACC_EN = 0x80, ///< Store accelerometer data in fifo. + FIFO_CONFIG_1_GYR_EN = 0x40, ///< Store gyroscope data in fifo. + FIFO_CONFIG_1_MAG_EN = 0x20, ///< Store magnetometer data in fifo. + FIFO_CONFIG_1_HEADER_EN = 0x10, ///< Stores an header for each frame. +}; + +/** + * @brief Values for FIFO_DOWNS register. + */ +enum FIFO_DOWNS +{ + FIFO_DOWNS_ACC_FILT_DATA = 0x80, + FIFO_DOWNS_GYR_FILT_DATA = 0x08, +}; + +/** + * @brief Values for INT_OUT_CTRL register. + */ +enum INT_OUT_CTRL +{ + INT_OUT_CTRL_INT2_OUT_EN = 0x80, ///< Output enable for INT2 pin. + INT_OUT_CTRL_INT2_OD = 0x40, ///< Open drain enable for INT2 pin. + INT_OUT_CTRL_INT1_OUT_EN = 0x08, ///< Output enable for INT1 pin. + INT_OUT_CTRL_INT1_OD = 0x04, ///< Open drain enable for INT1 pin. +}; + +/** + * @brief Values for INT_MAP_1 register. + */ +enum INT_MAP_1 +{ + INT_MAP_1_INT_1_FIFO_WATERMARK = 0x40, + INT_MAP_1_INT_1_FIFO_FULL = 0x20, + INT_MAP_1_INT_2_FIFO_WATERMARK = 0x04, + INT_MAP_1_INT_2_FIFO_FULL = 0x02, +}; + +/** + * @brief Values for INT_EN_1 register. + */ +enum INT_EN_1 +{ + INT_EN_1_FIFO_WATERMARK = 0x40, ///< Enables interrupt for FIFO watermark. + INT_EN_1_FIFO_FULL = 0x20, ///< Enables interrupt for FIFO full. +}; + +/** + * @brief Values for MAG_IF_0 register. + */ +enum MAG_IF_0 +{ + MAG_IF_0_MANUAL = 0x80, + MAG_IF_0_BURST_1 = 0x00, ///< 1 byte of burst operation. + MAG_IF_0_BURST_2 = 0x01, ///< 2 byte of burst operation. + MAG_IF_0_BURST_6 = 0x02, ///< 6 byte of burst operation. + MAG_IF_0_BURST_8 = 0x03, ///< 8 byte of burst operation. +}; + +/** + * @brief Mask for PMU_STATUS register (Power Mode Unit). + */ +const uint8_t PMU_STATUS_ALL_MASK = 0x03 | 0x0C | 0x30; + +/** + * @brief Mask for PMU_STATUS register, normal status for all sensors. + */ +const uint8_t PMU_STATUS_ALL_NORMAL = 0x01 | 0x04 | 0x10; + +/** + * @brief This value indicates that the data in the FIFO stops prematurely. + */ +const uint8_t FIFO_STOP_BYTE = 128; + +/** + * @brief Mask for fifo header mode. + */ +const uint8_t FIFO_HEADER_MODE_MASK = 0xC0; + +/** + * @brief Mask for fifo header parm. + */ +const uint8_t FIFO_HEADER_PARM_MASK = 0x3C; + +/** + * @brief Mask for fifo header ext. + */ +const uint8_t FIFO_HEADER_EXT_MASK = 0x03; + +/** + * @brief Values for fifo header byte. + */ +enum FIFO_HEADER +{ + FIFO_HEADER_MODE_REGULAR = 0x80, + FIFO_HEADER_MODE_CONTROL = 0x40, + + FIFO_HEADER_PARM_ACC_DATA = 0x04, + FIFO_HEADER_PARM_GYR_DATA = 0x08, + FIFO_HEADER_PARM_MAG_DATA = 0x10, + + FIFO_HEADER_PARM_SKIP = 0x00, + FIFO_HEADER_PARM_SENSORTIME = 0x04, + FIFO_HEADER_PARM_CONFIG = 0x08, +}; + +/** + * @brief Values for mag RESET. + */ +enum MAG_RESET +{ + MAG_RESET_POWER_CONTROL = 0x01 +}; + +/** + * @brief Values for mag CONTROL. + */ +enum MAG_CONTROL +{ + MAG_CONTROL_SELF_TEST = 0x01, + MAG_CONTROL_NORMAL = 0x00, + MAG_CONTROL_FORCED = 0x02, + MAG_CONTROL_SLEEP = 0x06, +}; + +/** + * @brief Raw struct, read directly from device. + */ +struct MagRaw +{ + int16_t x, y, z; + uint16_t rhall; +}; + +/** + * @brief Raw struct, read directly from device. + */ +struct GyrRaw +{ + int16_t x, y, z; +}; + +/** + * @brief Raw struct, read directly from device. + */ +struct AccRaw +{ + int16_t x, y, z; +}; + +/** + * @brief Struct holding trim data used for magnetomer compensation. + */ +struct TrimData +{ + int8_t dig_x1; + int8_t dig_y1; + int8_t dig_x2; + int8_t dig_y2; + uint16_t dig_z1; + int16_t dig_z2; + int16_t dig_z3; + int16_t dig_z4; + uint8_t dig_xy1; + int8_t dig_xy2; + uint16_t dig_xyz1; +}; + +/** + * BMX160 Commands. + */ +enum class Cmd +{ + START_FOC = 0x03, ///< Starts Fast Offset Calibration for acc and gyro. + ACC_SET_PMU_MODE = 0x10, ///< Sets the PMU mode for the accelerometer. + GYR_SET_PMU_MODE = 0x14, ///< Sets the PMU mode for the gyroscope. + MAG_IF_SET_PMU_MODE = 0x18, ///< Sets the PMU mode for the magnetometer. + PROG_NVM = 0xA0, ///< Writes the NVM backed registers into NVM. + FIFO_FLUSH = 0xB0, ///< Clears all data in the fifo. + INT_RESET = 0xB1, ///< Resets interrupt engine, INT_STATUS reg and int pin. + SOFTRESET = 0xB6, ///< Triggers a reset including a reboot. + STEP_CNT_CLR = 0xB2, ///< Triggers a reset of the step counter. +}; + +/** + * BMX160 power modes. + */ +enum class PowerMode +{ + SUSPEND = 0x00, + NORMAL = 0x01, + LOW_POWER = 0x02, + FAST_START_UP = 0x03 +}; + +/** + * @brief Internal register definitions. + */ +enum Registers +{ + REG_CHIPID = 0x00, + REG_ERR = 0x02, + REG_PMU_STATUS = 0x03, + + REG_DATA = 0x04, + + REG_DATA_MAG = 0x04, + REG_DATA_GYR = 0x0C, + REG_DATA_ACC = 0x12, + + REG_SENSORTIME_0 = 0x18, + REG_SENSORTIME_1 = 0x19, + REG_SENSORTIME_2 = 0x1A, + + REG_STATUS = 0x1B, + + REG_TEMPERATURE_0 = 0x20, + REG_TEMPERATURE_1 = 0x21, + + REG_FIFO_LENGTH_0 = 0x22, + REG_FIFO_LENGTH_1 = 0x23, + + REG_FIFO_DATA = 0x24, + + REG_ACC_CONF = 0x40, + REG_ACC_RANGE = 0x41, + REG_GYR_CONF = 0x42, + REG_GYR_RANGE = 0x43, + REG_MAG_CONF = 0x44, + + REG_FIFO_DOWNS = 0x45, + REG_FIFO_CONFIG_0 = 0x46, + REG_FIFO_CONFIG_1 = 0x47, + + REG_MAG_IF_0 = 0x4C, + REG_MAG_IF_1 = 0x4D, + REG_MAG_IF_2 = 0x4E, + REG_MAG_IF_3 = 0x4F, + + REG_INT_EN_0 = 0x50, + REG_INT_EN_1 = 0x51, + REG_INT_EN_2 = 0x52, + + REG_INT_OUT_CTRL = 0x53, + REG_INT_LATCH = 0x54, + + REG_INT_MAP_0 = 0x55, + REG_INT_MAP_1 = 0x56, + REG_INT_MAP_2 = 0x57, + + // INT_DATA_[0-1] not needed + // INT_LOWHIGH_[0-5] not needed + // INT_MOTION_[0-3] not needed + // INT_TAP_[0-1] not needed + // INT_ORIENT_[0-1] not needed + // INT_FLAT_[0-1] not needed + + REG_FOC_CONF = 0x69, + REG_CONF = 0x6A, + REG_IF_CONF = 0x6B, + + // PMU_TRIGGER not needed + + REG_SELF_TEST = 0x6D, + + // NV_CONF not needed + // OFFSET_[0-6] not needed + // STEP_CNT_[0-1] not needed + // STEP_CONF_[0-1] not needed + + REG_CMD = 0x7E, + REG_COMM_TEST = 0x7F, +}; + +/** + * @brief Internal magnetometer definitions. + */ +enum MagnetometerRegisters +{ + MAG_REG_DATA = 0x42, + MAG_REG_RESET = 0x4B, + MAG_REG_CONTROL = 0x4C, + + MAG_REG_REPXY = 0x51, + MAG_REG_REPZ = 0x52, + + // Factory calibrated trim registers. This is all undocumented territory + // (the datasheet mentions this as "reserved"). + + MAG_REG_DIG_X1 = 0x5D, + MAG_REG_DIG_Y1 = 0x5E, + MAG_REG_DIG_Z4_0 = 0x62, + MAG_REG_DIG_Z4_1 = 0x63, + MAG_REG_DIG_X2 = 0x64, + MAG_REG_DIG_Y2 = 0x65, + MAG_REG_DIG_Z2_0 = 0x68, + MAG_REG_DIG_Z2_1 = 0x69, + MAG_REG_DIG_Z1_0 = 0x6A, + MAG_REG_DIG_Z1_1 = 0x6B, + MAG_REG_DIG_XYZ1_0 = 0x6C, + MAG_REG_DIG_XYZ1_1 = 0x6D, + MAG_REG_DIG_Z3_0 = 0x6E, + MAG_REG_DIG_Z3_1 = 0x6F, + MAG_REG_DIG_XY2 = 0x70, + MAG_REG_DIG_XY1 = 0x71, +}; + +} // namespace BMX160Defs \ No newline at end of file diff --git a/src/shared/sensors/BMX160/BMX160WithCorrection.cpp b/src/shared/sensors/BMX160/BMX160WithCorrection.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6617b728bf290412560f1b02b2e617525c7bc970 --- /dev/null +++ b/src/shared/sensors/BMX160/BMX160WithCorrection.cpp @@ -0,0 +1,324 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Riccardo Musso, Luca Conterio, Alberto Nidasio + * + * 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. + */ + +#include "BMX160WithCorrection.h" + +#include <miosix.h> + +#include <fstream> + +BMX160CorrectionParameters::BMX160CorrectionParameters() +{ + accelParams << 0, 0, 0, 0, 0, 0; + magnetoParams << 0, 0, 0, 0, 0, 0; +} + +std::string BMX160CorrectionParameters::header() +{ + return "accel_p1,accel_p2,accel_p3,accel_q1,accel_q2,accel_q3,mag_p1," + "mag_p2,mag_p3,mag_q1,mag_q2,mag_q3," + "minGyroSamplesForCalibration"; +} + +void BMX160CorrectionParameters::read(std::istream& inputStream) +{ + // Read accelerometer parameters + for (int i = 0; i < 2; i++) + { + for (int j = 0; j < 3; j++) + { + inputStream >> accelParams(j, i); + inputStream.ignore(1, ','); + } + } + + // Read magnetometer parameters + for (int i = 0; i < 2; i++) + { + for (int j = 0; j < 3; j++) + { + inputStream >> magnetoParams(j, i); + inputStream.ignore(1, ','); + } + } + + // Read gyroscope correction samples + inputStream >> minGyroSamplesForCalibration; +} + +void BMX160CorrectionParameters::print(std::ostream& outputStream) const +{ + // Print accelerometer data + outputStream << accelParams(0, 0) << "," << accelParams(1, 0) << "," + << accelParams(2, 0) << "," << accelParams(0, 1) << "," + << accelParams(1, 1) << "," << accelParams(2, 1) << ","; + + // Print magnetometer data + outputStream << magnetoParams(0, 0) << "," << magnetoParams(1, 0) << "," + << magnetoParams(2, 0) << "," << magnetoParams(0, 1) << "," + << magnetoParams(1, 1) << "," << magnetoParams(2, 1) << ","; + + // Print gyroscope correction samples + outputStream << minGyroSamplesForCalibration << "\n"; +} + +BMX160WithCorrection::BMX160WithCorrection( + BMX160* bmx160_, BMX160CorrectionParameters correctionParameters, + AxisOrthoOrientation rotation_) + : bmx160(bmx160_), minGyroSamplesForCalibration( + correctionParameters.minGyroSamplesForCalibration), + rotation(rotation_) +{ + accelerometerCorrector << correctionParameters.accelParams; + magnetometerCorrector << correctionParameters.magnetoParams; +} + +BMX160WithCorrection::BMX160WithCorrection( + BMX160* bmx160_, BMX160CorrectionParameters correctionParameters) + : bmx160(bmx160_), minGyroSamplesForCalibration( + correctionParameters.minGyroSamplesForCalibration) +{ + accelerometerCorrector << correctionParameters.accelParams; + magnetometerCorrector << correctionParameters.magnetoParams; +} + +BMX160WithCorrection::BMX160WithCorrection(BMX160* bmx160_) : bmx160(bmx160_) {} + +bool BMX160WithCorrection::init() { return true; } + +bool BMX160WithCorrection::selfTest() { return true; } + +bool BMX160WithCorrection::calibrate() +{ + if (!bmx160) + { + LOG_ERR(logger, "Driver doesn't point to valid sensor"); + return false; + } + + int samplesCounter = 0; + BiasCalibration<GyroscopeData> gyroscopeCalibrator; + BMX160Data fifoElement; + uint8_t fifoSize; + uint64_t gyroTimestamp = 0; + + // Set reference vector + gyroscopeCalibrator.setReferenceVector({0, 0, 0}); + + // Read the fifo and feed the gyroscope data to the calibrator + fifoSize = bmx160->getLastFifoSize(); + for (uint8_t i = 0; i < fifoSize; i++) + { + fifoElement = bmx160->getFifoElement(i); + + if (fifoElement.gyro_timestamp > gyroTimestamp) + { + gyroTimestamp = fifoElement.gyro_timestamp; + gyroscopeCalibrator.feed(fifoElement); + + samplesCounter++; + } + } + + // Continues until the averaged samples are at least the amount specified in + // the configuration + while (samplesCounter < minGyroSamplesForCalibration) + { + // Wait for another sample + miosix::Thread::sleep(100); + + // Read the fifo and feed the gyroscope data to the calibrator + fifoSize = bmx160->getLastFifoSize(); + for (uint8_t i = 0; i < fifoSize; i++) + { + fifoElement = bmx160->getFifoElement(i); + + if (fifoElement.gyro_timestamp > gyroTimestamp) + { + gyroTimestamp = fifoElement.gyro_timestamp; + gyroscopeCalibrator.feed(fifoElement); + + samplesCounter++; + } + } + } + + // Compute and save the calibration results + { + miosix::PauseKernelLock lock; + gyroscopeCorrector = gyroscopeCalibrator.computeResult(); + } + + // Print the calibraton data + gyroscopeCorrector >> gyroscopeCorrectionParameters; + LOG_INFO(logger, "Gyroscope bias vector from calibration\n"); + LOG_INFO(logger, "b = [ {: >2.5f} {: >2.5f} {: >2.5f} ]\n\n", + gyroscopeCorrectionParameters(0), gyroscopeCorrectionParameters(1), + gyroscopeCorrectionParameters(2)); + + return true; +} + +BMX160GyroscopeCalibrationBiases BMX160WithCorrection::getGyroscopeBiases() +{ + return BMX160GyroscopeCalibrationBiases{gyroscopeCorrectionParameters(0), + gyroscopeCorrectionParameters(1), + gyroscopeCorrectionParameters(2)}; +} + +BMX160CorrectionParameters +BMX160WithCorrection::readCorrectionParametersFromFile(const char* fileName) +{ + BMX160CorrectionParameters correctionParameters; + std::ifstream input(fileName); + + // Ignore header line (csv header) + input.ignore(1000, '\n'); + + correctionParameters.read(input); + + return correctionParameters; +} + +BMX160WithCorrectionData BMX160WithCorrection::sampleImpl() +{ + if (!bmx160) + { + LOG_ERR(logger, "Driver doesn't point to valid sensor"); + return BMX160WithCorrectionData{}; + } + + Vector3f avgAccel{0, 0, 0}, avgMag{0, 0, 0}, avgGyro{0, 0, 0}, vec; + uint64_t accelTimestamp = 0, magTimestamp = 0, gyroTimestamp = 0; + uint8_t fifoSize, numAccel = 0, numMag = 0, numGyro = 0; + BMX160Data fifoElement; + BMX160WithCorrectionData result; + + fifoSize = bmx160->getLastFifoSize(); + + // Read all data in the fifo + for (int i = 0; i < fifoSize; i++) + { + fifoElement = bmx160->getFifoElement(i); + + // Read acceleration data + if (fifoElement.accel_timestamp > accelTimestamp) + { + static_cast<AccelerometerData>(fifoElement) >> vec; + avgAccel += vec; + + accelTimestamp = fifoElement.accel_timestamp; + numAccel++; + } + + // Read magnetometer data + if (fifoElement.mag_timestamp > magTimestamp) + { + static_cast<MagnetometerData>(fifoElement) >> vec; + avgMag += vec; + + magTimestamp = fifoElement.mag_timestamp; + numMag++; + } + + // Read gyroscope data + if (fifoElement.gyro_timestamp > gyroTimestamp) + { + static_cast<GyroscopeData>(fifoElement) >> vec; + avgGyro += vec; + + gyroTimestamp = fifoElement.gyro_timestamp; + numGyro++; + } + } + + // Average the samples + if (numAccel == 0) + static_cast<AccelerometerData>(bmx160->getLastSample()) >> avgAccel; + else + avgAccel /= numAccel; + + if (numMag == 0) + static_cast<MagnetometerData>(bmx160->getLastSample()) >> avgMag; + else + avgMag /= numMag; + + if (numGyro == 0) + static_cast<GyroscopeData>(bmx160->getLastSample()) >> avgGyro; + else + avgGyro /= numGyro; + + static_cast<AccelerometerData&>(result) << avgAccel; + static_cast<MagnetometerData&>(result) << avgMag; + static_cast<GyroscopeData&>(result) << avgGyro; + + // Correct the averaged measurements + AccelerometerData acc = accelerometerCorrector.correct(result); + result = acc; + MagnetometerData mag = magnetometerCorrector.correct(result); + result = mag; + GyroscopeData gyro; + gyro = gyroscopeCorrector.correct(result); + result = gyro; + + // Get the timestamp of the newest value in fifo + result.accel_timestamp = fifoElement.accel_timestamp; + result.mag_timestamp = fifoElement.accel_timestamp; + result.gyro_timestamp = fifoElement.accel_timestamp; + + result = rotateAxis(result); + + return result; +} + +BMX160WithCorrectionData BMX160WithCorrection::rotateAxis( + BMX160WithCorrectionData data) +{ + // Rotate the axes according to the given rotation + + // Accelerometer + AccelerometerData accData = data; + Vector3f accDataVector; + accData >> accDataVector; + accDataVector = rotation.getMatrix() * accDataVector; + accData << accDataVector; + + // Gyroscope + GyroscopeData gyrData = data; + Vector3f gyrDataVector; + gyrData >> gyrDataVector; + gyrDataVector = rotation.getMatrix() * gyrDataVector; + gyrData << gyrDataVector; + + // Magnetometer + MagnetometerData magData = data; + Vector3f magDataVector; + magData >> magDataVector; + magDataVector = rotation.getMatrix() * magDataVector; + magData << magDataVector; + + data = accData; + data = gyrData; + data = magData; + + return data; +} diff --git a/src/shared/sensors/BMX160/BMX160WithCorrection.h b/src/shared/sensors/BMX160/BMX160WithCorrection.h new file mode 100644 index 0000000000000000000000000000000000000000..47738c3788bc8f4ad8f6840e025af45be1dd8997 --- /dev/null +++ b/src/shared/sensors/BMX160/BMX160WithCorrection.h @@ -0,0 +1,131 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Riccardo Musso, Luca Conterio, Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include <diagnostic/PrintLogger.h> +#include <sensors/BMX160/BMX160.h> +#include <sensors/calibration/BiasCalibration.h> +#include <sensors/calibration/SixParameterCalibration.h> + +#include "BMX160WithCorrectionData.h" + +/** + * Holds correction parameters for BMX160. + */ +struct BMX160CorrectionParameters +{ + Matrix<float, 3, 2> accelParams, magnetoParams; + int minGyroSamplesForCalibration = 0; + + BMX160CorrectionParameters(); + + static std::string header(); + + void read(std::istream& inputStream); + + void print(std::ostream& outputStream) const; +}; + +/** + * @brief Driver for BMX160 with calibration. + * + * Gets samples from a BMX160 and applies a specified correction and rotation. + * It also calibrates the gyroscope. + */ +class BMX160WithCorrection : public Sensor<BMX160WithCorrectionData> +{ +public: + /** + * @param bmx160_ already initialized bmx. + * @param correctionParameters correction parameter to apply. + * @param rotation_ axis rotation. + */ + BMX160WithCorrection(BMX160* bmx160_, + BMX160CorrectionParameters correctionParameters, + AxisOrthoOrientation rotation_); + + /** + * Constructor without rotation, no rotation will be applied. + * + * @param bmx160_ already initialized bmx. + * @param correctionParameters correction parameter to apply. + */ + BMX160WithCorrection(BMX160* bmx160_, + BMX160CorrectionParameters correctionParameters); + + /** + * @brief Constructor without correction nor rotation, no correciton and + * rotation will be applied. + * + * @param bmx160_ correction parameter to apply. + */ + BMX160WithCorrection(BMX160* bmx160_); + + bool init() override; + + bool selfTest() override; + + /** + * @brief Performs the gyroscope calibration. + * + * The gyroscope calibration consists in averaging some samples to measure + * the bias. + * This function is intended to run while another thread samples the bmx at + * at least 10Hz. + */ + bool calibrate(); + + /** + * @brief Utility function to read correction parameters from file. + */ + static BMX160CorrectionParameters readCorrectionParametersFromFile( + const char* fileName); + + /** + * @return Gyroscope calibration biases. + */ + BMX160GyroscopeCalibrationBiases getGyroscopeBiases(); + +private: + BMX160WithCorrectionData sampleImpl() override; + + /** + * @brief Rotates data axes as specified. + */ + BMX160WithCorrectionData rotateAxis(BMX160WithCorrectionData data); + + BMX160* bmx160; + + int minGyroSamplesForCalibration = 200; + + AxisOrthoOrientation rotation = {Direction::POSITIVE_X, + Direction::POSITIVE_Y}; + + SixParameterCorrector<AccelerometerData> accelerometerCorrector; + SixParameterCorrector<MagnetometerData> magnetometerCorrector; + BiasCorrector<GyroscopeData> gyroscopeCorrector{}; + + Vector3f gyroscopeCorrectionParameters; + + PrintLogger logger = Logging::getLogger("bmx160withcorrection"); +}; \ No newline at end of file diff --git a/src/shared/sensors/BMX160/BMX160WithCorrectionData.h b/src/shared/sensors/BMX160/BMX160WithCorrectionData.h new file mode 100644 index 0000000000000000000000000000000000000000..bda3331742fb2396785850af100f32d6fbef7d6d --- /dev/null +++ b/src/shared/sensors/BMX160/BMX160WithCorrectionData.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Luca Conterio, Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/BMX160/BMX160Data.h" + +struct BMX160WithCorrectionData : public BMX160Data +{ + BMX160WithCorrectionData() : BMX160Data() {} + + BMX160WithCorrectionData(const BMX160Data& data) + : BMX160WithCorrectionData(data, data, data) + { + } + + BMX160WithCorrectionData(AccelerometerData acc, GyroscopeData gyr, + MagnetometerData mag) + : BMX160Data(acc, gyr, mag) + { + } + + BMX160WithCorrectionData& operator=(AccelerometerData acc) + { + accel_x = acc.accel_x; + accel_y = acc.accel_y; + accel_z = acc.accel_z; + return *this; + } + + BMX160WithCorrectionData& operator=(GyroscopeData gyr) + { + gyro_x = gyr.gyro_x; + gyro_y = gyr.gyro_y; + gyro_z = gyr.gyro_z; + return *this; + } + + BMX160WithCorrectionData& operator=(MagnetometerData mag) + { + mag_x = mag.mag_x; + mag_y = mag.mag_y; + mag_z = mag.mag_z; + return *this; + } + + static std::string header() + { + return "accel_timestamp,accel_x,accel_y,accel_z,gyro_timestamp,gyro_x," + "gyro_y," + "gyro_z,mag_timestamp,mag_x,mag_y,mag_z\n"; + } + + void print(std::ostream& os) const + { + os << accel_timestamp << "," << accel_x << "," << accel_y << "," + << accel_z << "," << gyro_timestamp << "," << gyro_x << "," << gyro_y + << "," << gyro_z << "," << mag_timestamp << "," << mag_x << "," + << mag_y << "," << mag_z << "\n"; + } +}; + +struct BMX160GyroscopeCalibrationBiases +{ + float bx; + float by; + float bz; + + BMX160GyroscopeCalibrationBiases() {} + + BMX160GyroscopeCalibrationBiases(float x, float y, float z) + : bx(x), by(y), bz(z) + { + } + + static std::string header() + { + return "bias_x,bias_y,bias_z\n"; + } + + void print(std::ostream& os) const + { + os << bx << "," << by << "," << bz << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/L3GD20/L3GD20.h b/src/shared/sensors/L3GD20/L3GD20.h new file mode 100644 index 0000000000000000000000000000000000000000..2de2da52725ac5feda6e0e589b874cf8ffc5ef23 --- /dev/null +++ b/src/shared/sensors/L3GD20/L3GD20.h @@ -0,0 +1,348 @@ + +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once +#include <miosix.h> + +#include <array> + +#include "sensors/Sensor.h" +#include "L3GD20Data.h" +#include "drivers/spi/SPIDriver.h" +#include "math/Vec3.h" +#include "TimestampTimer.h" + +using miosix::GpioPin; +using std::array; + +static constexpr uint32_t L3GD20_FIFO_SIZE = 32; + +class L3GD20 : public SensorFIFO<L3GD20Data, L3GD20_FIFO_SIZE> +{ +public: + enum class FullScaleRange + { + FS_250 = 250, + FS_500 = 500, + FS_2000 = 2000 + }; + + enum class OutPutDataRate + { + ODR_95 = 95, + ODR_190 = 190, + ODR_380 = 380, + ODR_760 = 760 + }; + + /** + * @brief Creates an instance of an L3GD20 sensor + * + * @param bus SPI bus the sensor is connected to + * @param cs Chip Select GPIO + * @param range Full Scale Range (See datasheet) + * @param odr Output Data Rate (See datasheet) + * @param cutoff_freq Low pass filter cutoff frequency (See datasheet) + */ + L3GD20(SPIBusInterface& bus, GpioPin cs, + FullScaleRange range = FullScaleRange::FS_250, + OutPutDataRate odr = OutPutDataRate::ODR_95, + uint8_t cutoff_freq = 0x03) + : L3GD20(bus, cs, {}, range, odr, cutoff_freq) + { + // Configure SPI + spislave.config.clock_div = SPIClockDivider::DIV32; + } + + /** + * @brief Creates an instance of an L3GD20 sensor + * + * @param bus SPI bus the sensor is connected to + * @param cs Chip Select GPIO + * @param cfg Custom SPI bus configuration + * @param range Full Scale Range (See datasheet) + * @param odr Output Data Rate (See datasheet) + * @param cutoff_freq Low pass filter cutoff selector (See datasheet) + */ + L3GD20(SPIBusInterface& bus, GpioPin cs, SPIBusConfig cfg, + FullScaleRange range = FullScaleRange::FS_250, + OutPutDataRate odr = OutPutDataRate::ODR_95, + uint8_t cutoff_freq = 0x03) + : spislave(bus, cs, cfg), fs(range), odr(odr), cutoff_freq(cutoff_freq) + { + switch (fs) + { + case FullScaleRange::FS_250: + sensitivity = SENSITIVITY_250; + break; + case FullScaleRange::FS_500: + sensitivity = SENSITIVITY_500; + break; + case FullScaleRange::FS_2000: + sensitivity = SENSITIVITY_2000; + break; + } + } + + /** + * @brief Enables storing samples in a FIFO, must be called before init(). + * + * @param fifo_watermark How many samples in the FIFO when the fifo + * watermark input is generated on INT2. + */ + void enableFifo(unsigned int fifo_watermark) + { + fifo_enabled = true; + this->fifo_watermark = fifo_watermark; + } + + bool init() + { + SPITransaction spi(spislave); + + uint8_t whoami = spi.read(REG_WHO_AM_I); + + if (whoami != WHO_AM_I_VAL) + { + printf("WAMI: %d\n", whoami); + last_error = SensorErrors::INVALID_WHOAMI; + return false; + } + + switch (fs) + { + case FullScaleRange::FS_250: + spi.write(REG_CTRL4, 0); + break; + case FullScaleRange::FS_500: + spi.write(REG_CTRL4, (uint8_t)(1 << 4)); + break; + case FullScaleRange::FS_2000: + spi.write(REG_CTRL4, (uint8_t)(2 << 4)); + break; + default: + break; + } + + if (fifo_enabled) + { + // Enable fifo + spi.write(REG_CTRL5, 1 << 6); + + // Set watermark level to fifo_watermark samples + uint8_t fifo_ctrl = fifo_watermark; + + // Set fifo to STREAM mode + fifo_ctrl |= 0x02 << 5; + + spi.write(REG_FIFO_CTRL, fifo_ctrl); + + // Enable FIFO watermark interrupt on INT2 + spi.write(REG_CTRL3, 0x04); + } + else + { + // Enable DRDY interrupt on INT2 + spi.write(REG_CTRL3, 0x08); + } + + // Enter normal mode, enable output + uint8_t ctrl1 = 0x0F; + + // Configure cutoff frequency + ctrl1 |= (cutoff_freq & 0x03) << 4; + + // Configure ODR + switch (odr) + { + case OutPutDataRate::ODR_95: + break; + case OutPutDataRate::ODR_190: + ctrl1 |= 1 << 6; + break; + case OutPutDataRate::ODR_380: + ctrl1 |= 2 << 6; + break; + case OutPutDataRate::ODR_760: + ctrl1 |= 3 << 6; + break; + } + + spi.write(REG_CTRL1, ctrl1); + + return true; + } + + bool selfTest() { return true; } + + L3GD20Data sampleImpl() + { + if (!fifo_enabled) // FIFO not enabled + { + // Timestamp of the last sample + uint64_t last_sample_ts; + + // Read output data registers (X, Y, Z) + { + SPITransaction spi(spislave); + spi.read(REG_OUT_X_L | 0x40, buf, 6); + } + + { + // Disable interrupts and copy the last sample locally, as a new + // interrupt may come just as we are reading it and causing a + // race condition. + miosix::FastInterruptDisableLock dLock; + last_sample_ts = last_interrupt_us; + } + + int16_t x = buf[0] | buf[1] << 8; + int16_t y = buf[2] | buf[3] << 8; + int16_t z = buf[4] | buf[5] << 8; + // printf("%02X,%02X,%02X\n", x, y, z); + + Vec3 rads = toRadiansPerSecond(x, y, z); + last_fifo[0] = {last_sample_ts, rads.getX(), rads.getY(), + rads.getZ()}; + } + + else // FIFO is enabled + { + SPITransaction spi(spislave); + // Read last fifo level + uint8_t fifo_src = spi.read(REG_FIFO_SRC); + uint8_t ovr = (fifo_src & 0x40) >> 7; // Overrun bit + uint8_t fifo_level = (fifo_src & 0x1F) + ovr; + + // Read fifo + spi.read(REG_OUT_X_L | 0x40, buf, fifo_level * 6); + + uint64_t dt = dt_interrupt / last_fifo_level; + + uint8_t duplicates = 0; + for (uint8_t i = 0; i < fifo_level; ++i) + { + bool rmv; + // Check for duplicates: there seems to be a bug where the + // sensor occasionaly shifts out the same sample two times in a + // row: discard one + if (i < fifo_level - 1) + { + rmv = true; + for (uint8_t j = 0; j < 6; ++j) + { + if (buf[i * 6 + j] != buf[(i + 1) * 6 + j]) + { + rmv = false; + break; + } + } + } + else + { + rmv = false; + } + + if (rmv) + { + // Skip this sample; + ++duplicates; + continue; + } + + // Assign a timestamp to each sample in the FIFO based of the + // timestamp of the FIFO_WATERMARKth sample and the delta + // between the watermark interrupts (evenly distribute the + // timestamp between the two times) + // Ignoring possible duplicates, the timestamp of the ith sample + // in the fifo is: + // ts(i) = ts(fifo_watermark) + (i - fifo_watermark)*dt; + Vec3 rads = + toRadiansPerSecond(buf[i * 6] | buf[i * 6 + 1] << 8, + buf[i * 6 + 2] | buf[i * 6 + 3] << 8, + buf[i * 6 + 4] | buf[i * 6 + 5] << 8); + + last_fifo[i - duplicates] = L3GD20Data{ + last_interrupt_us + + ((int)i - (int)fifo_watermark - (int)duplicates) * dt, + rads.getX(), rads.getY(), rads.getZ()}; + } + + last_fifo_level = fifo_level - duplicates; + } + + return last_fifo[last_fifo_level - 1]; + } + +private: + Vec3 toRadiansPerSecond(int16_t x, int16_t y, int16_t z) + { + return Vec3(x * sensitivity, y * sensitivity, z * sensitivity); + } + + static constexpr float SENSITIVITY_250 = 0.00875f; + static constexpr float SENSITIVITY_500 = 0.0175f; + static constexpr float SENSITIVITY_2000 = 0.070f; + + SPISlave spislave; + + FullScaleRange fs = FullScaleRange::FS_250; + OutPutDataRate odr = OutPutDataRate::ODR_760; + uint8_t cutoff_freq = 0x03; + + bool fifo_enabled = false; + unsigned int fifo_watermark = 24; + + float sensitivity = SENSITIVITY_250; + + uint8_t buf[192]; + + constexpr static uint8_t WHO_AM_I_VAL = 212; + + enum RegMap + { + REG_WHO_AM_I = 0x0F, + + REG_CTRL1 = 0x20, + REG_CTRL2 = 0x21, + REG_CTRL3 = 0x22, + REG_CTRL4 = 0x23, + REG_CTRL5 = 0x24, + + REG_REFERENCE = 0x25, + REG_OUT_TEMP = 0x26, + REG_STATUS = 0x27, + + REG_OUT_X_L = 0x28, + REG_OUT_X_H = 0x29, + + REG_OUT_Y_L = 0x2A, + REG_OUT_Y_H = 0x2B, + + REG_OUT_Z_L = 0x2C, + REG_OUT_Z_H = 0x2D, + + REG_FIFO_CTRL = 0x2E, + REG_FIFO_SRC = 0x2F + }; +}; \ No newline at end of file diff --git a/src/shared/sensors/L3GD20/L3GD20Data.h b/src/shared/sensors/L3GD20/L3GD20Data.h new file mode 100644 index 0000000000000000000000000000000000000000..889510556d4f0bcff2dee034905f150377422f65 --- /dev/null +++ b/src/shared/sensors/L3GD20/L3GD20Data.h @@ -0,0 +1,49 @@ + +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct L3GD20Data : public GyroscopeData +{ + L3GD20Data() : GyroscopeData{0, 0.0, 0.0, 0.0} {} + + L3GD20Data(uint64_t t, float x, float y, float z) + : GyroscopeData{t, x, y, z} + { + } + + L3GD20Data(GyroscopeData gyr) : GyroscopeData(gyr) {} + + static std::string header() + { + return "gyro_timestamp,gyro_x,gyro_y,gyro_z\n"; + } + + void print(std::ostream& os) const + { + os << gyro_timestamp << "," << gyro_x << "," << gyro_y << "," << gyro_z + << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/LIS3DSH/LIS3DSH.h b/src/shared/sensors/LIS3DSH/LIS3DSH.h new file mode 100644 index 0000000000000000000000000000000000000000..517a744b4eac4f43391a482989c4103c7d0b567f --- /dev/null +++ b/src/shared/sensors/LIS3DSH/LIS3DSH.h @@ -0,0 +1,550 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#pragma once + +#include <diagnostic/PrintLogger.h> +#include <math.h> + +#include "LIS3DSHData.h" +#include "drivers/spi/SPIDriver.h" +#include "sensors/Sensor.h" + +/** + * Driver for stm32f407vg discovery on-board 3-axis + * accelerometer + temperature sensor. + * + * The sensor is connected to SPI1 using the + * following GPIOs: PA5 : clock + * PA6 : miso + * PA7 : mosi + * PE3 : chip select + */ +class LIS3DSH : public Sensor<LIS3DSHData> +{ +public: + /** + * @brief Constructor. + * + * @param bus the spi bus. + * @param chip_select the chip_select for the sensor. + * @param _odr output data rate for the accelerometer. + * Default value is 100 Hz. + * @param _bdu BlockDataUpdate value, continuous or non-continuous + * update mode. Default value is to update after data + * has been read (BDU=1). + * @param _full_scale full scale range (from +/-2g up to +/-16g). + * Default value is +/-2g. + */ + LIS3DSH(SPIBusInterface& bus, GpioPin chip_select, + uint8_t _odr = OutputDataRate::ODR_100_HZ, + uint8_t _bdu = BlockDataUpdate::UPDATE_AFTER_READ_MODE, + uint8_t _full_scale = FullScale::FULL_SCALE_2G) + : spi_slave(bus, chip_select, {}), odr(_odr), bdu(_bdu), + full_scale(_full_scale) + { + spi_slave.config.clock_div = + SPIClockDivider::DIV64; // used to set the spi baud rate (maximum + // is 10 Mhz) + } + + /** + * @brief Constructor. + * + * @param bus the spi bus. + * @param chip_select the chip_select for the sensor. + * @param config the spi bus configurations. + * @param _odr output data rate for the accelerometer. + * Default value is 100 Hz. + * @param _bdu BlockDataUpdate value, continuous or non-continuous + * update mode. Default value is to update after data + * has been read (BDU=1). + * @param _full_scale full scale range (from +/-2g up to +/-16g). + * Default value is +/-2g. + */ + LIS3DSH(SPIBusInterface& bus, GpioPin chip_select, SPIBusConfig config, + uint8_t _odr = OutputDataRate::ODR_100_HZ, + uint8_t _bdu = BlockDataUpdate::UPDATE_AFTER_READ_MODE, + uint8_t _full_scale = FullScale::FULL_SCALE_2G) + : spi_slave(bus, chip_select, config), odr(_odr), bdu(_bdu), + full_scale(_full_scale) + { + } + + /** + * @brief Initialize the sensor. + * + * @return boolean value indicating whether the operation succeded or not + */ + bool init() override + { + // check if the sensor is already initialized + if (initialized) + { + LOG_WARN(logger, "Already initialized"); + last_error = SensorErrors::ALREADY_INIT; + return false; + } + + // check if the sensor is working properly + if (!checkWhoAmI()) + { // whoami default value + return false; // sensor correctly initialized + } + + SPITransaction spi(spi_slave); + + // set the full scale value in CTRL_REG5 + uint8_t ctrl_reg5_value = (full_scale << 3); + spi.write(CTRL_REG5, ctrl_reg5_value); + + // select the correct sensitivity + // for the specified full scale range + sensitivity = select_sensitivity(); + + // set the output data rate and the BDU in CTRL_REG4 + // the three least significant bits are enable bits for X, Y and Z axis + uint8_t ctrl_reg4_value = + (odr << 4) | (bdu << 3) | (7 << 0); // 7 = 111 -> enable the 3 axis + spi.write(CTRL_REG4, ctrl_reg4_value); + + initialized = true; + + return true; + } + + /** + * @brief Check if the sensor is working. + * + * @return boolean indicating whether the sensor is correctly working or not + */ + bool selfTest() override + { + // check if the sensor is initialized + if (!initialized) + { + LOG_WARN(logger, + "Unable to perform selftest, sensor not initialized"); + last_error = SensorErrors::NOT_INIT; + return false; + } + + const uint8_t num_samples = 5; // number of samples to be used + // vectors for storing samples, both + // in self-test and no-self-test modes + float X_ST[num_samples] = {0}; + float Y_ST[num_samples] = {0}; + float Z_ST[num_samples] = {0}; + float X_NO_ST[num_samples] = {0}; + float Y_NO_ST[num_samples] = {0}; + float Z_NO_ST[num_samples] = {0}; + // vectors containing avg values for each axis + float AVG_ST[3] = {0}; // one element per axis + float AVG_NO_ST[3] = {0}; // one element per axis + + // set output data rate to 50 hz + uint8_t ctrl_reg4_value = + (OutputDataRate::ODR_100_HZ << 4) | + (BlockDataUpdate::UPDATE_AFTER_READ_MODE << 3) | (7 << 0); + + { + SPITransaction spi(spi_slave); + spi.write(CTRL_REG4, ctrl_reg4_value); + } + + // set full scale to default value +/-2g + // enable the self-test mode with positive sign + uint8_t ctrl_reg5_value = (FullScale::FULL_SCALE_2G << 3) | (1 << 1); + + { + SPITransaction spi(spi_slave); + spi.write(CTRL_REG5, ctrl_reg5_value); + } + + // read samples in self-test positive sign mode + for (uint8_t i = 0; i < num_samples; i++) + { + AccelerometerData accel_data = readAccelData(); + X_ST[i] = accel_data.accel_x; + Y_ST[i] = accel_data.accel_y; + Z_ST[i] = accel_data.accel_z; + miosix::Thread::sleep(10); + } + // reset the self-test bits + ctrl_reg5_value &= ~(3 << 1); + // normal mode with full scale range +/-2g + ctrl_reg5_value |= (FULL_SCALE_2G << 3); + + { + SPITransaction spi(spi_slave); + spi.write(CTRL_REG5, ctrl_reg5_value); + } + + // read samples in normal mode + for (uint8_t i = 0; i < num_samples; i++) + { + AccelerometerData accel_data = readAccelData(); + X_NO_ST[i] = accel_data.accel_x; + Y_NO_ST[i] = accel_data.accel_y; + Z_NO_ST[i] = accel_data.accel_z; + miosix::Thread::sleep(10); + } + // compute averages vectors: + // they contain one element for each axis + // (position 0 for x, 1 for y and 2 for z) + // AVG_ST : for self-test samples + // AVG_NO_ST : for normal mode samples + for (uint8_t i = 0; i < num_samples; i++) + { + AVG_ST[0] += X_ST[i]; + AVG_ST[1] += Y_ST[i]; + AVG_ST[2] += Z_ST[i]; + AVG_NO_ST[0] += X_NO_ST[i]; + AVG_NO_ST[1] += Y_NO_ST[i]; + AVG_NO_ST[2] += Z_NO_ST[i]; + } + for (uint8_t i = 0; i < 3; i++) + { + AVG_ST[i] /= num_samples; + AVG_NO_ST[i] /= num_samples; + } + + // Reset registers values with the ones + // specified in the constructor: + // set the output data rate value in CTRL_REG4 + ctrl_reg4_value = (odr << 4) | (bdu << 3) | (7 << 0); + + { + SPITransaction spi(spi_slave); + spi.write(CTRL_REG4, ctrl_reg4_value); + } + // set the full scale value in CTRL_REG5 + ctrl_reg5_value = (full_scale << 3); // normal mode + + { + SPITransaction spi(spi_slave); + spi.write(CTRL_REG5, ctrl_reg5_value); + } + + float delta[3] = {0}; + for (uint8_t i = 0; i < 3; i++) + { + delta[i] = fabs(AVG_NO_ST[i] - AVG_ST[i]); + } + + LOG_INFO(logger, + "Selftest: delta[x] = {}, delta[y] = {}, delta[z] = {}", + delta[0], delta[1], delta[2]); + + // check that the averages differences + // do not exceed maximum tolerance + if ((delta[0] > + SELF_TEST_DIFF_X_Y + SELF_TEST_DIFF_X_Y * SELF_TEST_TOLERANCE) || + (delta[1] > + SELF_TEST_DIFF_X_Y + SELF_TEST_DIFF_X_Y * SELF_TEST_TOLERANCE) || + (delta[2] > + SELF_TEST_DIFF_Z + SELF_TEST_DIFF_Z * SELF_TEST_TOLERANCE)) + { + LOG_ERR(logger, "Selftest failed"); + last_error = SensorErrors::SELF_TEST_FAIL; + return false; + } + + return true; + } + + /** + * @brief Output data rate allowed values (4 bits). + */ + enum OutputDataRate + { + ODR_POWER_DOWN = 0, // 0000 + ODR_3_125_HZ = 1, // 0001, 3.125 Hz + ODR_6_25_HZ = 2, // 0010, 6.25 Hz + ODR_12_5_HZ = 3, // 0011, 12.5 Hz + ODR_25_HZ = 4, // 0100 + ODR_50_HZ = 5, // 0101 + ODR_100_HZ = 6, // 0110, default value + ODR_400_HZ = 7, // 0111 + ODR_800_HZ = 8, // 1000 + ODR_1600_HZ = 9 // 1001 + }; + + /** + * @brief Full scale range allowed values (3 bits). + */ + enum FullScale + { + FULL_SCALE_2G = 0, // 000, +/- 2g + FULL_SCALE_4G = 1, // 001, +/- 4g + FULL_SCALE_6G = 2, // 010, +/- 6g + FULL_SCALE_8G = 3, // 011, +/- 8g + FULL_SCALE_16G = 4, // 100 +/- 16g + }; + + /** + * @brief Block data update allowed modes (1 bit). + */ + enum BlockDataUpdate + { + CONTINUOUS_UPDATE_MODE = 0, // continuous update of accelerometer data + UPDATE_AFTER_READ_MODE = + 1 // values updated only when MSB and LSB are read (recommended) + }; + +private: + /** + * @brief Read new data from the accelerometer. + * Acceleretions are returned in g. + * + * @return boolean value indicating whether the operation succeded or not + */ + LIS3DSHData sampleImpl() override + { + // check if the sensor is initialized + if (!initialized) + { + LOG_WARN(logger, "Unable to sample, sensor not initialized"); + last_error = SensorErrors::NOT_INIT; + return last_sample; + } + + AccelerometerData accel_data = readAccelData(); + TemperatureData temp_data = readTemperature(); + + if (last_error != SensorErrors::NO_ERRORS) + { + return last_sample; + } + else + { + return LIS3DSHData(accel_data, temp_data); + } + } + + /** + * @brief Read accelrometer data. + * + * @return the read accelrometer sample + */ + AccelerometerData readAccelData() + { + AccelerometerData accel_data; + + SPITransaction spi(spi_slave); + + // read the sensor's status register + uint8_t status = spi.read(STATUS); + + if (status & 0x08) + { // bit 3 of status set to 1 (new data available) + if (status & 0x80) + { // bit 7 of status set to 1 (some data overwritten) + + accel_data.accel_timestamp = TimestampTimer::getTimestamp(); + + // read acceleration on X + int8_t accel_L = spi.read(OUT_X_L); + int8_t accel_H = spi.read(OUT_X_H); + accel_data.accel_x = + static_cast<float>(combine(accel_H, accel_L)) * sensitivity; + + // read acceleration on Y + accel_L = spi.read(OUT_Y_L); + accel_H = spi.read(OUT_Y_H); + accel_data.accel_y = + static_cast<float>(combine(accel_H, accel_L)) * sensitivity; + + // read acceleration on Z + accel_L = spi.read(OUT_Z_L); + accel_H = spi.read(OUT_Z_H); + accel_data.accel_z = + static_cast<float>(combine(accel_H, accel_L)) * sensitivity; + + last_error = SensorErrors::NO_ERRORS; + } + } + else + { + last_error = SensorErrors::NO_NEW_DATA; + } + + return accel_data; + } + + /** + * @brief Read temperature data. + * + * @return the read temperature sample + */ + TemperatureData readTemperature() + { + SPITransaction spi(spi_slave); + + // the temperature is given as a 8-bits integer (in 2-complement) + int8_t t = spi.read(OUT_T); + + return TemperatureData{ + TimestampTimer::getTimestamp(), + t + TEMPERATURE_REF}; // add the 'zero' of the temperature sensor + } + + /** + * @brief Check that the WHO_AM_I register + * contains the correct value. + * + * @return boolean value indicating whether the value read + * from the WHO_AM_I register is correct or not + */ + bool checkWhoAmI() + { + SPITransaction spi(spi_slave); + + // check the WHO_AM_I_REG register + uint8_t who_am_i_value = spi.read(WHO_AM_I_REG); + if (who_am_i_value == WHO_AM_I_DEFAULT_VALUE) + { + LOG_DEBUG(logger, "Correct WHO_AM_I value"); + return true; + } + else + { + LOG_ERR(logger, "Wrong WHO_AM_I value, got {} instead of {}", + who_am_i_value, WHO_AM_I_DEFAULT_VALUE); + last_error = SensorErrors::INVALID_WHOAMI; + } + + return false; + } + + /** + * @brief Combine low and high bits in a single number. + * + * @param msb the most significatn bits + * @param lsb the least significant bits + * @return MSB and LSB combined in one value + */ + int16_t combine(uint8_t msb, uint8_t lsb) { return (msb << 8) | lsb; } + + /** + * @brief Given the requested full scale range, select the correct + * sensitivity value. + * + * @return the sensitivity value corresponding to the requested full scale + * range + */ + float select_sensitivity() + { + float s; + switch (full_scale) + { + case FULL_SCALE_2G: + s = sensitivity_values[FullScale::FULL_SCALE_2G]; + break; + case FULL_SCALE_4G: + s = sensitivity_values[FullScale::FULL_SCALE_4G]; + break; + case FULL_SCALE_6G: + s = sensitivity_values[FullScale::FULL_SCALE_6G]; + break; + case FULL_SCALE_8G: + s = sensitivity_values[FullScale::FULL_SCALE_8G]; + break; + case FULL_SCALE_16G: + s = sensitivity_values[FullScale::FULL_SCALE_16G]; + break; + default: + LOG_ERR(logger, "Invalid full scale range given, using +/-2g"); + this->full_scale = FullScale::FULL_SCALE_2G; + s = sensitivity_values[FullScale::FULL_SCALE_2G]; + break; + } + return s; + } + + /** + * @brief Registers' addresses definition. + */ + enum REG + { + + // whoami register + WHO_AM_I_REG = 0x0F, + + // control registers for the accelerometer + CTRL_REG4 = + 0x20, // control register to set accelerometer's ODR and BDU + CTRL_REG1 = 0x21, // state Machine 1 interrupt configuration register + CTRL_REG2 = 0x22, // state Machine 2 interrupt configuration register + CTRL_REG3 = 0x23, + CTRL_REG5 = + 0x24, // control register to set the accelerometer full scale + // range, anti-aliansing filter and self-test enable + CTRL_REG6 = 0x25, + + // status register + STATUS = 0x27, + + // accelerometer output registers + // for x, y and z axis + // (low and high bits in separate registers) + OUT_X_L = 0x28, + OUT_X_H = 0x29, + OUT_Y_L = 0x2A, + OUT_Y_H = 0x2B, + OUT_Z_L = 0x2C, + OUT_Z_H = 0x2D, + + // temperature output register + OUT_T = 0x0C, + }; + + SPISlave spi_slave; + + bool initialized = false; // whether the sensor has been initialized or not + + uint8_t odr; // output data rate, default 100 Hz + uint8_t bdu; // continuous or block after update + uint8_t full_scale; // full scale value, default +/- 2g + + /** + * @brief Sensitivity values corresponding to full scale range allowed + * values. + */ + const float sensitivity_values[5] = {0.06, 0.12, 0.18, 0.24, 0.73}; + + float sensitivity = + sensitivity_values[FullScale::FULL_SCALE_2G]; // default sensitivity + // value + + const uint8_t WHO_AM_I_DEFAULT_VALUE = 63; // 00111111 + + const float TEMPERATURE_REF = 25.0f; // temperature sensor 'zero'/reference + // value (value 0x00 from the sensor + // corresponds to 25 degrees celsius) + + const float SELF_TEST_DIFF_X_Y = 140.0f; // 140 mg + const float SELF_TEST_DIFF_Z = 590.0f; // 590 mg + const float SELF_TEST_TOLERANCE = 0.3f; + + PrintLogger logger = Logging::getLogger("lis3dsh"); +}; diff --git a/src/shared/sensors/LIS3DSH/LIS3DSHData.h b/src/shared/sensors/LIS3DSH/LIS3DSHData.h new file mode 100644 index 0000000000000000000000000000000000000000..4705c527faf0a46806f5fec32bc9e6324e5acfbc --- /dev/null +++ b/src/shared/sensors/LIS3DSH/LIS3DSHData.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct LIS3DSHData : public AccelerometerData, public TemperatureData +{ + LIS3DSHData() : AccelerometerData{0, 0.0, 0.0, 0.0}, TemperatureData{0, 0.0} + { + } + + LIS3DSHData(uint64_t t, float x, float y, float z, float temp) + : AccelerometerData{t, x, y, z}, + TemperatureData{t, temp} + { + } + + LIS3DSHData(AccelerometerData acc, TemperatureData temp) + : AccelerometerData{acc.accel_timestamp, acc.accel_x, acc.accel_y, acc.accel_z}, + TemperatureData{temp.temp_timestamp, temp.temp} + { + } + + static std::string header() + { + return "accel_timestamp,accel_x,accel_y,accel_z,temp_timestamp,temp\n"; + } + + void print(std::ostream& os) const + { + os << accel_timestamp << "," << accel_x << "," << accel_y << "," << accel_z + << "," << temp_timestamp << "," << temp << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/LIS3MDL/LIS3MDL.h b/src/shared/sensors/LIS3MDL/LIS3MDL.h new file mode 100644 index 0000000000000000000000000000000000000000..51eb11f843a21922b56cf58d5249b8c7bc857019 --- /dev/null +++ b/src/shared/sensors/LIS3MDL/LIS3MDL.h @@ -0,0 +1,647 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#ifndef SRC_SHARED_SENSORS_LIS3MDL_LIS3MDL_H +#define SRC_SHARED_SENSORS_LIS3MDL_LIS3MDL_H + +#include <Common.h> +#include <diagnostic/PrintLogger.h> + +#include "LIS3MDLData.h" +#include "drivers/spi/SPIDriver.h" +#include "miosix.h" +#include "sensors/Sensor.h" + +/** + * Driver for LIS3MDL, a three-axis magnetic sensor. + */ +class LIS3MDL : public Sensor<LIS3MDLData> +{ +public: + /** + * Constants for Output Data Rate configuration. + * + * Note: constants values already include + * axis operative mode selection and FAST_ODR + * options, so they are ready to be put inside + * CTRL_REG1 along with TEMP_EN and ST + */ + enum ODR : uint8_t + { + ODR_0_625_HZ = 0x00, //!< 0.625 Hz + ODR_1_25_HZ = 0x04, //!< 1.25 Hz + ODR_2_5_HZ = 0x08, //!< 2.5 Hz + ODR_5_HZ = 0x0c, //!< 5 Hz + ODR_10_HZ = 0x10, //!< 10 Hz + ODR_20_HZ = 0x14, //!< 20 Hz + ODR_40_HZ = 0x18, //!< 40 Hz + ODR_80_HZ = 0x1c, //!< 80 Hz + + ODR_155_HZ = 0x62, //!< 155 Hz + ODR_300_HZ = 0x42, //!< 300 Hz + ODR_560_HZ = 0x22, //!< 560 Hz + ODR_1000_HZ = 0x02, //!< 1000 Hz + + /** + * Constant used by the driver: this bit + * is 1 for ODR > 80 Hz, set to 0 otherwise. + */ + FAST_ODR_BIT = 0x02, + }; + + enum FullScale : uint8_t + { + FS_4_GAUSS = 0x00, //!< +/- 4 gauss + FS_8_GAUSS = 0x20, //!< +/- 8 gauss + FS_12_GAUSS = 0x40, //!< +/- 12 gauss + FS_16_GAUSS = 0x60, //!< +/- 16 gauss + }; + + /** + * @brief Operative mode constants + * + * Operative mode (that is power consumpion) options. + * Higher power implies better performance. + */ + enum OperativeMode : uint8_t + { + OM_LOW_POWER = 0x0, + OM_MEDIUM_POWER = 0x1, + OM_HIGH_POWER = 0x2, + OM_ULTRA_HIGH_POWER = 0x3, + }; + + /** + * @brief Sensor configuration + * + * This struct contains all the settings the user + * is able to modify with the help of the driver. + * They are applied in the constructor of LIS3MDL class + * and on each call of LIS3MDL::applyConfig() + */ + struct Config + { + /** + * @brief Constructor + * Creates an instance with the default settings. + */ + Config() {} + + /** + * Full scale setting + * @see LIS3MDL::FullScale + */ + FullScale scale = FS_8_GAUSS; + + /** + * @brief Data rate configuration + * + * Default: 40 Hz + * + * Important: if ODR is set more than 80 Hz, + * operative mode of x and y axis will be set + * accordingly. + * + * @see LIS3MDL::ODR + */ + ODR odr = ODR_40_HZ; + + /** + * Operative mode for x and y axis. + * Note: if ODR is greater than 80 Hz, + * this setting will be ignored and actual + * operative mode will be set depending of + * the chosen frequency. + * Default: ultra high performance + * + * @see LIS3MDL::OperativeMode + */ + OperativeMode xyMode = OM_ULTRA_HIGH_POWER; + + /** + * Operative mode for z axis. + * Default: ultra high performance + * + * @see LIS3MDL::OM + */ + OperativeMode zMode = OM_ULTRA_HIGH_POWER; + + /** + * Enables temperature sensor. + * Default: true + */ + bool enableTemperature = true; + + /** + * @brief Sets the value of tempDivider. + * + * With the given value you can instruct the driver to update + * the temperature according to a different rate. + * The temperature will be updated only once in `tempDivider` calls + * to sampleImpl(), so for example: + * 2 -> updated half the times, + * 1 -> updated every time. + * By default the divider is set to 1 + * + */ + unsigned temperatureDivider = 1; + + /** + * @brief Enables interrupts + * + * Whether are interrupts enabled respectively + * on the x, y and z axis. If it is set to true + * on at least one axis, the interrupts will be + * generated otherwise, they will be completely + * disabled by the driver. + * + * Default: disabled on all axis. + */ + bool enableInterrupt[3] = {false, false, false}; + + /** + * Absolute value of the threshold that triggers the interrupt + * (expressed in gauss). Default: 0 + */ + float threshold = 0; + + /** + * @brief BDU setting + * + * If set to true, the sensor won't update the data + * until it is read, if false the sensor data will be + * continously updated. Default: false + */ + bool doBlockDataUpdate = false; + }; + + /** + * Constructs the default config for SPI Bus. + * + * @returns the default SPIBusConfig + */ + static SPIBusConfig getDefaultSPIConfig() + { + SPIBusConfig config{}; + config.clock_div = SPIClockDivider::DIV32; + return config; + } + + /** + * @brief The constructor. + * + * Takes all relevant SPI info and the settings + * for the sensor. If no SPIBusConfig is given, it will + * be used the default configuration. + * + * @param bus The SPI interface + * @param pin The CS pin + * @param spiConfig SPI configuration, optional + * @param config Driver configuration, optional + * + * @see LIS3MDL::Config + */ + LIS3MDL(SPIBusInterface& bus, miosix::GpioPin pin, + SPIBusConfig spiConfig = getDefaultSPIConfig(), Config config = {}) + : mSlave{bus, pin, spiConfig}, mConfig(config), currDiv(0), + isInitialized(false) + { + } + + bool init() override + { + if (isInitialized) + { + LOG_ERR(logger, "Attempted to initialized sensor twice but failed"); + last_error = ALREADY_INIT; + return false; + } + + { + SPITransaction spi(mSlave); + uint8_t res = spi.read(WHO_AM_I); + + if (res != WHO_AM_I_VALUE) + { + LOG_ERR(logger, + "WHO_AM_I value differs from expectation: read 0x{02x} " + "but expected 0x{02x}", + res, WHO_AM_I_VALUE); + last_error = INVALID_WHOAMI; + return false; + } + } + + isInitialized = true; + return applyConfig(mConfig); + } + + /** + * @brief Executes self test + * + * The init() method must have been called before. + * + * @returns false if the sensor failed the self test, true otherwise. + */ + bool selfTest() override + { + if (!isInitialized) + { + LOG_ERR(logger, "Invoked selfTest() but sensor was unitialized"); + last_error = NOT_INIT; + return false; + } + + /* + * NUM_SAMPLES: number of samples used + * to take the average value before tests. + * NUM_TESTS: number of actual tests. + * SLEEP_TIME: millis between samples/tests + */ + constexpr int NUM_SAMPLES = 5, NUM_TESTS = 5, SLEEP_TIME = 50; + + /* + * Absolute value of extra tolerance + */ + constexpr float t = 0.1f; + + /* + * Range which delta must be between, one for + * axis and expressed as {min, max}. The unit is gauss + */ + constexpr float r[3][2] = {{1.f, 3.f}, {1.f, 3.f}, {0.1f, 1.f}}; + + float avg_x = 0.f, avg_y = 0.f, avg_z = 0.f; + + { + SPITransaction spi(mSlave); + spi.write(CTRL_REG2, FS_12_GAUSS); + } + updateUnit(FS_12_GAUSS); + + for (int i = 0; i < NUM_SAMPLES; ++i) + { + miosix::Thread::sleep(SLEEP_TIME); + + LIS3MDLData lastData = sampleImpl(); + avg_x += lastData.mag_x; + avg_y += lastData.mag_y; + avg_z += lastData.mag_z; + } + + avg_x /= NUM_SAMPLES; + avg_y /= NUM_SAMPLES; + avg_z /= NUM_SAMPLES; + + /* + * Setting up the sensor settings for + * proper usage of the self test mode. + */ + { + SPITransaction spi(mSlave); + + spi.write(CTRL_REG1, ODR_20_HZ | ENABLE_SELF_TEST | + (OM_ULTRA_HIGH_POWER << 4)); + spi.write(CTRL_REG2, FS_12_GAUSS); + spi.write(CTRL_REG4, OM_ULTRA_HIGH_POWER << 2); + } + + /* + * Deltas: absolute difference between + * the values measured before and during + * selftest + */ + float d[3]; + + for (int i = 0; i < NUM_TESTS; ++i) + { + miosix::Thread::sleep(SLEEP_TIME); + + LIS3MDLData lastData = sampleImpl(); + d[0] = std::abs(lastData.mag_x - avg_x); + d[1] = std::abs(lastData.mag_y - avg_y); + d[2] = std::abs(lastData.mag_z - avg_z); + + bool passed = true; + for (int j = 0; j < 3; ++j) + { + if (d[j] < (r[j][0] - t) || d[j] > (r[j][1] + t)) + passed = false; + } + + if (!passed) + { + // reset configuration, then return + applyConfig(mConfig); + + last_error = SELF_TEST_FAIL; + return false; + } + } + + return applyConfig(mConfig); + } + + /** + * @brief Overwrites the sensor settings. + * + * Writes a certain config to the sensor + * registers. This method is automatically + * called in LIS3MDL::init() using as parameter + * the configuration given in the constructor. + * + * This method checks if the values were actually + * written in the sensor's registers and returns false + * if at least one of them was not as expected. + * + * @param config The configuration to be applied + * @returns true if the configuration was applied successfully, + * false otherwise. + */ + bool applyConfig(Config config) + { + + SPITransaction spi(mSlave); + uint8_t reg = 0, err = 0; + + mConfig = config; + currDiv = 0; + + /* -- CTRL_REG1 -- */ + if (config.enableTemperature) + { + reg = ENABLE_TEMPERATURE; + } + reg |= config.odr; + + // odr <= 80Hz + if (!(config.odr & FAST_ODR_BIT)) + reg |= config.xyMode << 4; + spi.write(CTRL_REG1, reg); + err |= spi.read(CTRL_REG1) != reg; + + /* -- CTRL_REG2 -- */ + reg = config.scale; + spi.write(CTRL_REG2, reg); + err |= spi.read(CTRL_REG2) != reg; + + /* -- CTRL_REG3 -- */ + reg = CONTINOUS_CONVERSION; + spi.write(CTRL_REG3, reg); + err |= spi.read(CTRL_REG3) != reg; + + /* -- CTRL_REG4 -- */ + reg = config.zMode << 2; + spi.write(CTRL_REG4, reg); + err |= spi.read(CTRL_REG4) != reg; + + /* -- CTRL_REG5 -- */ + if (config.doBlockDataUpdate) + { + reg = ENABLE_BDU; + } + else + { + reg = 0; + } + + spi.write(CTRL_REG5, reg); + err |= spi.read(CTRL_REG5) != reg; + + /* -- INT_CFG -- */ + if (config.enableInterrupt[0]) + { + reg = ENABLE_INT_X; + } + else + { + reg = 0; + } + if (config.enableInterrupt[1]) + { + reg |= ENABLE_INT_Y; + } + if (config.enableInterrupt[2]) + { + reg |= ENABLE_INT_Z; + } + + // the interrupt of at least one axis is enabled + if (reg) + { + reg |= ENABLE_INT_PIN; + } + + reg |= 0x08; + spi.write(INT_CFG, reg); + err |= spi.read(INT_CFG) != reg; + + /** INT_THS */ + uint16_t val = + static_cast<uint16_t>(std::abs(config.threshold / mUnit)); + reg = static_cast<uint8_t>(0xff & val); + spi.write(INT_THS_L, reg); + err |= spi.read(INT_THS_L) != reg; + + reg = static_cast<uint8_t>(val >> 8); + reg &= + 0x7f; // remove MSB (according to the datasheet, it must be zero) + spi.write(INT_THS_H, reg); + err |= spi.read(INT_THS_H) != reg; + + /* Set mUnit according to scale */ + updateUnit(config.scale); + + if (err) + { + LOG_ERR(logger, "Spi error"); + last_error = BUS_FAULT; + return false; + } + + return true; + } + +private: + /** + * @brief Reads data from the sensor + * + * The init method must have been called before. + * Output data can be fetched with the methods + * compassDataPtr() and tempDataPtr inherited respectevely + * from CompassSensor and TemperatureSensor classes. + * Important: the temperature will be taken only once in a while + * according to the value of `temperatureDivider` + * + * @returns false if the sensor was unitialized, true otherwise. + */ + LIS3MDLData sampleImpl() override + { + if (!isInitialized) + { + LOG_ERR(logger, + "Invoked sampleImpl() but sensor was " + "unitialized"); + last_error = NOT_INIT; + return last_sample; + } + + SPITransaction spi(mSlave); + + if (!spi.read(STATUS_REG)) + { + last_error = NO_NEW_DATA; + return last_sample; + } + + // Reset any error + last_error = SensorErrors::NO_ERRORS; + + int16_t val; + LIS3MDLData newData{}; + + if (mConfig.enableTemperature) + { + if (currDiv == 0) + { + val = spi.read(TEMP_OUT_L); + val |= spi.read(TEMP_OUT_H) << 8; + + newData.temp_timestamp = TimestampTimer::getTimestamp(); + newData.temp = static_cast<float>(val) / LSB_PER_CELSIUS + + REFERENCE_TEMPERATURE; + } + else + { + // Keep old value + newData.temp = last_sample.temp; + } + + currDiv = (currDiv + 1) % mConfig.temperatureDivider; + } + + newData.mag_timestamp = TimestampTimer::getTimestamp(); + + val = spi.read(OUT_X_L); + val |= spi.read(OUT_X_H) << 8; + newData.mag_x = mUnit * val; + + val = spi.read(OUT_Y_L); + val |= spi.read(OUT_Y_H) << 8; + newData.mag_y = mUnit * val; + + val = spi.read(OUT_Z_L); + val |= spi.read(OUT_Z_H) << 8; + newData.mag_z = mUnit * val; + + return newData; + } + + SPISlave mSlave; + Config mConfig; + + unsigned currDiv; + bool isInitialized; + float mUnit = 0; + + void updateUnit(FullScale fs) + { + switch (fs) + { + case FS_4_GAUSS: + mUnit = 1.f / LSB_PER_GAUSS_FS_4; + break; + + case FS_8_GAUSS: + mUnit = 1.f / LSB_PER_GAUSS_FS_8; + break; + + case FS_12_GAUSS: + mUnit = 1.f / LSB_PER_GAUSS_FS_12; + break; + + case FS_16_GAUSS: + mUnit = 1.f / LSB_PER_GAUSS_FS_16; + break; + } + }; + + /** + * List of addresses of sensor registers + */ + enum Reg : uint8_t + { + WHO_AM_I = 0x0f, + + CTRL_REG1 = 0x20, + CTRL_REG2 = 0x21, + CTRL_REG3 = 0x22, + CTRL_REG4 = 0x23, + CTRL_REG5 = 0x24, + + STATUS_REG = 0x27, + OUT_X_L = 0x28, + OUT_X_H = 0x29, + OUT_Y_L = 0x2a, + OUT_Y_H = 0x2b, + OUT_Z_L = 0x2c, + OUT_Z_H = 0x2d, + + TEMP_OUT_L = 0x2e, + TEMP_OUT_H = 0x2f, + + INT_CFG = 0x30, + INT_THS_L = 0x32, + INT_THS_H = 0x33, + }; + + /** + * Misc. constants used by the driver. They are not + * particularly useful to the user. + */ + enum Constants : unsigned + { + WHO_AM_I_VALUE = 0x3d, + + CONTINOUS_CONVERSION = 0x0, + REFERENCE_TEMPERATURE = 25, + LSB_PER_CELSIUS = 8, + + LSB_PER_GAUSS_FS_4 = 6842, + LSB_PER_GAUSS_FS_8 = 3421, + LSB_PER_GAUSS_FS_12 = 2281, + LSB_PER_GAUSS_FS_16 = 1711, + + ENABLE_TEMPERATURE = 0x80, + ENABLE_SELF_TEST = 0x01, + ENABLE_BDU = 0x40, + + ENABLE_INT_PIN = 0x01, + ENABLE_INT_X = 0x80, + ENABLE_INT_Y = 0x40, + ENABLE_INT_Z = 0x20, + }; + + PrintLogger logger = Logging::getLogger("lis3mdl"); +}; + +#endif diff --git a/src/shared/sensors/LIS3MDL/LIS3MDLData.h b/src/shared/sensors/LIS3MDL/LIS3MDLData.h new file mode 100644 index 0000000000000000000000000000000000000000..b791437375abda30b56bbd5ae359301aa00b134f --- /dev/null +++ b/src/shared/sensors/LIS3MDL/LIS3MDLData.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct LIS3MDLData : public MagnetometerData, public TemperatureData +{ + LIS3MDLData() : MagnetometerData{0, 0.0, 0.0, 0.0}, TemperatureData{0, 0.0} + { + } + + LIS3MDLData(uint64_t t, float mx, float my, float mz, float deg) + : MagnetometerData{t, mx, my, mz}, + TemperatureData{t, deg} + + { + } + + LIS3MDLData(MagnetometerData mag_data, TemperatureData temp_data) + : MagnetometerData(mag_data), TemperatureData(temp_data) + + { + } + + static std::string header() + { + return "mag_timestamp,mag_x,mag_y,mag_z,temp_timestamp,temp\n"; + } + + void print(std::ostream& os) const + { + os << mag_timestamp << "," << mag_x << "," << mag_y << "," << mag_z + << "," << temp_timestamp << "," << temp << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/LSM6DS3H.h b/src/shared/sensors/LSM6DS3H.h deleted file mode 100644 index fbc35c321bde7a94201d8d728438d3d7ace4144b..0000000000000000000000000000000000000000 --- a/src/shared/sensors/LSM6DS3H.h +++ /dev/null @@ -1,423 +0,0 @@ -/* LSM6DS3H Driver - * - * Copyright (c) 2018-2019 Skyward Experimental Rocketry - * Authors: Soufiane Machkouk - * - * 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. - */ -#pragma once -#include <drivers/BusTemplate.h> -#include "Common.h" -#include "Sensor.h" -#include "miosix.h" -template <typename BusG> -class LSM6DS3H : public GyroSensor, public AccelSensor -{ -public: - LSM6DS3H(uint8_t accelFullScale, uint16_t gyroFullScale) - { - accelFS = accelFullScale; - gyroFS = gyroFullScale; - } - bool init() - { - selfTest(); - BusG::write(RegMap::CTRL5_C, 0x60); // Rounding read enabled for both accel and gyro - BusG::write(RegMap::CTRL3_C, 0x44 ); // incrementare l'indirizzo ad ogni lettura data LSB @ lower address - fifoConfiguration(); - accelConfiguration(); - gyroConfiguration(); - // TODO - uint8_t XLSamplesToDiscard=2; // - uint8_t gyroSamplesToDiscard=2; //since we set ODR to 416HZ - uint8_t buffer[2]={0, 0}; - BusG::read(RegMap::FIFO_DATA_OUT_L, buffer, XLSamplesToDiscard); - BusG::read(RegMap::FIFO_DATA_OUT_L, buffer, gyroSamplesToDiscard); - return true; - } - void fifoConfiguration() - { - uint8_t continuousMode = 0x36; // ODR is set to 416HZ - BusG::write(RegMap::FIFO_CTRL_REG5, continuousMode); - uint8_t bitToSet= BusG::read(RegMap::FIFO_CTRL_REG2); - bitToSet = bitToSet & 0xcf; - BusG::write(RegMap::FIFO_CTRL_REG2, bitToSet); - uint8_t noDecimation = 0x09; - BusG::write(RegMap::FIFO_CTRL_REG3, noDecimation); // NO decimation for both accel and gyro data - bitToSet = 0x00; - BusG::write(RegMap::FIFO_CTRL_REG5, bitToSet); - uint8_t registerStatus = BusG::read(RegMap::FIFO_STATUS4); - bitToSet = 0x03; - bitToSet = bitToSet & registerStatus; - BusG::write(RegMap::FIFO_STATUS4, bitToSet); - } - bool selfTest() - { - uint8_t whoami = BusG::read(RegMap::WHO_AM_I); - if (whoami != whoami_value) - { - last_error = ERR_NOT_ME; - return false; - } - return (accelSelfTest() & gyroSelfTest()); - } - void accelConfiguration() - { - uint8_t i=0; - uint8_t controlAccel = 0x61; - for (i = 0; i<=3; i++) - { - if ((uint8_t)accelFSMAP[i] == accelFS) - { - controlAccel = controlAccel | (i << 2); - BusG::write(RegMap::CTRL1_XL, controlAccel); // anti-aliasing filter set to 200Hz - // ODR to 416HZ - } - } - - BusG::write(RegMap::CTRL8_XL, 0x00); // no LPF2 - uint8_t enableXYZ = 0x38; - BusG::write(RegMap::CTRL9_XL, enableXYZ); - } - void gyroConfiguration() - { - uint8_t i=0; - uint8_t controlGyro = 0x60; - for(i=0; i<=3; i++) - { - if ((uint16_t)gyroFSMAP[i] == gyroFS) - { - controlGyro = controlGyro | (i << 2); - BusG::write(RegMap::CTRL2_G, controlGyro); - } - } - uint8_t enableXYZ = 0x39; - BusG::write(RegMap::CTRL10_C, enableXYZ); - //HPfilter disabled - } - bool onSimpleUpdate() { return false; } - void sampleUpdate() - { - //gyro data set ---> lettura da 6 byte - BusG::read(FIFO_DATA_OUT_L, datiGyro, 6); - // accel dataset ---> lettura da 6 byte - BusG::read(FIFO_DATA_OUT_L, datiAccel, 6); - Data.gyroXSample = toFloat(datiGyro[0]+datiGyro[1]<<8, gyroFS); - Data.gyroYSample = toFloat(datiGyro[2]+datiGyro[3]<<8, gyroFS); - Data.gyroZSample = toFloat(datiGyro[4]+datiGyro[5]<<8, gyroFS); - Data.accelXSample = toFloat(datiAccel[0]+datiAccel[1]<<8, accelFS); - Data.accelYSample = toFloat(datiAccel[2]+datiAccel[3]<<8, accelFS); - Data.accelZSample = toFloat(datiAccel[4]+datiAccel[5]<<8, accelFS); - - } - - bool accelSelfTest(){ - uint8_t discard; - uint8_t c=0; - uint8_t rxData[2]; - float OUTX_NOST[]={0, 0, 0, 0, 0}; - float outXNoST; - float OUTY_NOST[]={0, 0, 0, 0, 0}; - float outYNoST; - float OUTZ_NOST[]={0, 0, 0, 0, 0}; - float outZNoST; - float OUTX_ST[]={0, 0, 0, 0, 0}; - float outXST; - float OUTY_ST[]={0, 0, 0, 0, 0}; - float outYST; - float OUTZ_ST[]={0, 0, 0, 0, 0}; - float outZST; - BusG::write(CTRL1_XL, 0x30); - BusG::write(CTRL2_G, 0x00); - BusG::write(CTRL3_C, 0x44); - BusG::write(CTRL4_C, 0x00); - BusG::write(CTRL5_C, 0x00); - BusG::write(CTRL6_C, 0x00); - BusG::write(CTRL7_G, 0x00); - BusG::write(CTRL8_XL, 0x00); - BusG::write(CTRL9_XL, 0x38); - BusG::write(CTRL10_C, 0x00); - miosix::Thread::sleep(200); - discard = BusG::read(STATUS_REG); - while ((discard & 0x01) == 0 ){ - discard = BusG::read(STATUS_REG); - } - BusG::read(OUT_X_L_XL, rxData, 2); - BusG::read(OUT_Y_L_XL, rxData, 2); - BusG::read(OUT_Z_L_XL, rxData, 2); - discard = BusG::read(STATUS_REG); - while ( c<=5 ){ - while ((discard & 0x01) == 0 ){ - discard = BusG::read(STATUS_REG); - } - BusG::read(OUT_X_L_XL, rxData, 2); - OUTX_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - BusG::read(OUT_Y_L_XL, rxData, 2); - OUTY_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - BusG::read(OUT_Z_L_XL, rxData, 2); - OUTZ_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - c++; - } - outXNoST = (OUTX_NOST[0]+OUTX_NOST[1]+OUTX_NOST[2]+OUTX_NOST[3]+OUTX_NOST[4])/5; - outYNoST = (OUTY_NOST[0]+OUTY_NOST[1]+OUTY_NOST[2]+OUTY_NOST[3]+OUTY_NOST[4])/5; - outZNoST = (OUTZ_NOST[0]+OUTZ_NOST[1]+OUTZ_NOST[2]+OUTZ_NOST[3]+OUTZ_NOST[4])/5; - BusG::write(CTRL5_C, 0x01); - miosix::Thread::sleep(200); - discard = BusG::read(STATUS_REG); - while ((discard & 0x01) == 0 ){ - discard = BusG::read(STATUS_REG); - } - BusG::read(OUT_X_L_XL, rxData, 2); - BusG::read(OUT_Y_L_XL, rxData, 2); - BusG::read(OUT_Z_L_XL, rxData, 2); - discard = BusG::read(STATUS_REG); - c=0; - while ( c<=5 ){ - while ((discard & 0x01) == 0 ){ - discard = BusG::read(STATUS_REG); - } - BusG::read(OUT_X_L_XL, rxData, 2); - OUTX_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - BusG::read(OUT_Y_L_XL, rxData, 2); - OUTY_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - BusG::read(OUT_Z_L_XL, rxData, 2); - OUTZ_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - c++; - } - outXST = (OUTX_ST[0]+OUTX_ST[1]+OUTX_ST[2]+OUTX_ST[3]+OUTX_ST[4])/5; - outYST = (OUTY_ST[0]+OUTY_ST[1]+OUTY_ST[2]+OUTY_ST[3]+OUTY_ST[4])/5; - outZST = (OUTZ_ST[0]+OUTZ_ST[1]+OUTZ_ST[2]+OUTZ_ST[3]+OUTZ_ST[4])/5; - float maxSTX = max(OUTX_ST); - float maxSTY = max(OUTY_ST); - float maxSTZ = max(OUTZ_ST); - float minSTX = min(OUTX_ST); - float minSTY = min(OUTY_ST); - float minSTZ = min(OUTZ_ST); - if (((uint8_t)(outXST-outXNoST)<=maxSTX) & ((uint8_t)(outXST-outXNoST)>=minSTX)){ - if (((uint8_t)(outYST-outYNoST)<=maxSTY) & ((uint8_t)(outYST-outYNoST)>=minSTY)){ - if (((uint8_t)(outZST-outZNoST)<=maxSTZ) & ((uint8_t)(outZST-outZNoST)>=minSTZ)){ - BusG::write(CTRL1_XL, 0x00); - BusG::write(CTRL5_C, 0x00); - return true; - } - } - } - BusG::write(CTRL1_XL, 0x00); - BusG::write(CTRL5_C, 0x00); - return false; - } - bool gyroSelfTest(){ - uint8_t discard; - uint8_t c=0; - uint8_t rxData[2]; - float OUTX_NOST[]={0, 0, 0, 0, 0}; - float outXNoST; - float OUTY_NOST[]={0, 0, 0, 0, 0}; - float outYNoST; - float OUTZ_NOST[]={0, 0, 0, 0, 0}; - float outZNoST; - float OUTX_ST[]={0, 0, 0, 0, 0}; - float outXST; - float OUTY_ST[]={0, 0, 0, 0, 0}; - float outYST; - float OUTZ_ST[]={0, 0, 0, 0, 0}; - float outZST; - BusG::write(CTRL1_XL, 0x00); - BusG::write(CTRL2_G, 0x5C); - BusG::write(CTRL3_C, 0x44); - BusG::write(CTRL4_C, 0x00); - BusG::write(CTRL5_C, 0x00); - BusG::write(CTRL6_C, 0x00); - BusG::write(CTRL7_G, 0x00); - BusG::write(CTRL8_XL, 0x00); - BusG::write(CTRL9_XL, 0x00); - BusG::write(CTRL10_C, 0x38); - miosix::Thread::sleep(800); - discard = BusG::read(STATUS_REG); - while ((discard & 0x01) == 0 ){ - discard = BusG::read(STATUS_REG); - } - BusG::read(OUT_X_L_G, rxData, 2); - BusG::read(OUT_Y_L_G, rxData, 2); - BusG::read(OUT_Z_L_G, rxData, 2); - discard = BusG::read(STATUS_REG); - while ( c<=5 ){ - while ((discard & 0x02) == 0 ){ - discard = BusG::read(STATUS_REG); - } - BusG::read(OUT_X_L_G, rxData, 2); - OUTX_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - BusG::read(OUT_Y_L_G, rxData, 2); - OUTY_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - BusG::read(OUT_Z_L_G, rxData, 2); - OUTZ_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - c++; - } - outXNoST = (OUTX_NOST[0]+OUTX_NOST[1]+OUTX_NOST[2]+OUTX_NOST[3]+OUTX_NOST[4])/5; - outYNoST = (OUTY_NOST[0]+OUTY_NOST[1]+OUTY_NOST[2]+OUTY_NOST[3]+OUTY_NOST[4])/5; - outZNoST = (OUTZ_NOST[0]+OUTZ_NOST[1]+OUTZ_NOST[2]+OUTZ_NOST[3]+OUTZ_NOST[4])/5; - BusG::write(CTRL5_C, 0x04); - miosix::Thread::sleep(60); - discard = BusG::read(STATUS_REG); - while ((discard & 0x02) == 0 ){ - discard = BusG::read(STATUS_REG); - } - BusG::read(OUT_X_L_G, rxData, 2); - BusG::read(OUT_Y_L_G, rxData, 2); - BusG::read(OUT_Z_L_G, rxData, 2); - discard = BusG::read(STATUS_REG); - while ( c<=5 ){ - while ((discard & 0x02) == 0 ){ - discard = BusG::read(STATUS_REG); - } - BusG::read(OUT_X_L_G, rxData, 2); - OUTX_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - BusG::read(OUT_Y_L_G, rxData, 2); - OUTY_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - BusG::read(OUT_Z_L_G, rxData, 2); - OUTZ_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); - c++; - } - outXST = (OUTX_ST[0]+OUTX_ST[1]+OUTX_ST[2]+OUTX_ST[3]+OUTX_ST[4])/5; - outYST = (OUTY_ST[0]+OUTY_ST[1]+OUTY_ST[2]+OUTY_ST[3]+OUTY_ST[4])/5; - outZST = (OUTZ_ST[0]+OUTZ_ST[1]+OUTZ_ST[2]+OUTZ_ST[3]+OUTZ_ST[4])/5; - float maxSTX = max(OUTX_ST); - float maxSTY = max(OUTY_ST); - float maxSTZ = max(OUTZ_ST); - float minSTX = min(OUTX_ST); - float minSTY = min(OUTY_ST); - float minSTZ = min(OUTZ_ST); - if (((uint8_t)(outXST-outXNoST)<=maxSTX) & ((uint8_t)(outXST-outXNoST)>=minSTX)){ - if (((uint8_t)(outYST-outYNoST)<=maxSTY) & ((uint8_t)(outYST-outYNoST)>=minSTY)){ - if (((uint8_t)(outZST-outZNoST)<=maxSTZ) & ((uint8_t)(outZST-outZNoST)>=minSTZ)){ - BusG::write(CTRL2_G, 0x00); - BusG::write(CTRL5_C, 0x00); - return true; - } - } - } - BusG::write(CTRL2_G, 0x00); - BusG::write(CTRL5_C, 0x00); - return false; - - } - float min (float* var){ - uint8_t min=var[0]; - uint8_t i=0; - for(i=0; i<=5; i++){ - if (var[i]<= min){ - min= var[i]; - } - } - return min; - } - float max (float* var){ - uint8_t max=var[0]; - uint8_t i=0; - for(i=0; i<=5; i++){ - if (var[i]>= max){ - max= var[i]; - } - } - return max; - } - - float toFloat(uint16_t var, uint16_t FScale){ - float out; - if(var > 0x7FFF){ - out= var-0xFFFF; - } - else { - out= var; - } - out = out*FScale/0x8000; - return out; - } - uint8_t datiGyro[6] = {0, 0, 0, 0, 0, 0}; - uint8_t datiAccel[6] = {0, 0, 0, 0, 0, 0}; - - struct Samples - { - float gyroXSample; - float gyroYSample; - float gyroZSample; - float accelXSample; - float accelYSample; - float accelZSample; - }; - Samples Data; - -private: - uint8_t accelFS; - uint16_t gyroFS; - constexpr static uint8_t whoami_value = 0x69; - - uint8_t accelFSMAP[4] = {2, 16, 4, 8}; - uint16_t gyroFSMAP[4] = {250, 500, 1000, 2000}; - enum RegMap { - WHO_AM_I = 0x0F, // default value - // FIFO configuration registers - FIFO_CTRL_REG1 = 0x06, - FIFO_CTRL_REG2 = 0x07, - FIFO_CTRL_REG3 = 0x08, - FIFO_CTRL_REG4 = 0x09, - FIFO_CTRL_REG5 = 0x0A, - - // Accelerometer and gyroscope control registers - CTRL1_XL = 0x10, - CTRL2_G = 0x11, - CTRL3_C = 0x12, - CTRL4_C = 0x13, - CTRL5_C = 0x14, - CTRL6_C = 0x15, - CTRL7_G = 0x16, - CTRL8_XL = 0x17, - CTRL9_XL = 0x18, - CTRL10_C = 0x19, - - // Accelerometer output registers - - OUT_X_L_XL = 0x28, - OUT_X_H_XL = 0x29, - OUT_Y_L_XL = 0x2A, - OUT_Y_H_XL = 0x2B, - OUT_Z_L_XL = 0x2C, - OUT_Z_H_XL = 0x2D, - - // gyro data registers - OUT_X_L_G = 0x22, - OUT_X_H_G = 0x23, - OUT_Y_L_G = 0x24, - OUT_Y_H_G = 0x25, - OUT_Z_L_G = 0x26, - OUT_Z_H_G = 0x27, - - // FIFO status registers - FIFO_STATUS1 = 0x3A, - FIFO_STATUS2 = 0x3B, - FIFO_STATUS3 = 0x3C, - FIFO_STATUS4 = 0x3D, - - // FIFO data output registers - FIFO_DATA_OUT_L = 0x3E, - FIFO_DATA_OUT_H = 0x3F, - // STATUS register - STATUS_REG = 0X1E - }; -}; \ No newline at end of file diff --git a/src/shared/sensors/MPU9250/MPU9250.cpp b/src/shared/sensors/MPU9250/MPU9250.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6cfa201f36995a4c8f9ca9f02fa3b713fe1558da --- /dev/null +++ b/src/shared/sensors/MPU9250/MPU9250.cpp @@ -0,0 +1,447 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#include "MPU9250.h" + +#include "Constants.h" +#include "TimestampTimer.h" +#include "interfaces/endianness.h" + +MPU9250::MPU9250(SPISlave spiSlave_, unsigned short samplingRate_, + MPU9250GyroFSR gyroFsr_, MPU9250AccelFSR accelFsr_, + SPIClockDivider highSpeedSpiClockDivider_) + : spiSlave(spiSlave_), samplingRate(samplingRate_), gyroFsr(gyroFsr_), + accelFsr(accelFsr_), highSpeedSpiClockDivider(highSpeedSpiClockDivider_) +{ +} + +SPIBusConfig getDefaultSPIConfig() +{ + SPIBusConfig spiConfig{}; + spiConfig.clock_div = SPIClockDivider::DIV64; + spiConfig.mode = SPIMode::MODE3; + return spiConfig; +} + +bool MPU9250::init() +{ + // Check if already initialized + if (initialized) + { + LOG_ERR(logger, "Already initialized\n"); + + last_error = SensorErrors::ALREADY_INIT; + + return false; + } + + // Reset the device + resetDevice(); + miosix::Thread::sleep(100); + + // Wake up the chip + selectAutoClock(); + + // Check WHO AM I + if (!checkWhoAmI()) + { + LOG_ERR(logger, "Invalid WHO AM I\n"); + + last_error = SensorErrors::INVALID_WHOAMI; + + return false; + } + + // Setup I2C master interface to communicate with the AK8963 + enableMpuI2CMasterInterface(); + setMpuI2CMasterInterfaceClock(I2C_MST_IF_CLK_400); + + // Init the AK8963 + if (!initAk()) + { + return false; + } + + // Set full scale resolution for gyroscope and accelerometer (they are + // enabled by default) + setGyroFsr(gyroFsr); + setAccelFsr(accelFsr); + + // Set the sample rate + setSampleRate(samplingRate); + + LOG_DEBUG(logger, "Magnetometer sensitivity adjustment: %d, %d, %d\n", + magSensAdjCoeff[0], magSensAdjCoeff[1], magSensAdjCoeff[2]); + + initialized = true; + return true; +} + +MPU9250Data MPU9250::sampleImpl() +{ + MPU9250RawData rawData; + MPU9250Data data; + SPIClockDivider clockDivider = spiSlave.config.clock_div; + + // Read the data registers at high speed (up to 20MHz) + spiSlave.config.clock_div = highSpeedSpiClockDivider; + { + SPITransaction transaction(spiSlave); + transaction.read(REG_ACCEL_XOUT_H, (uint8_t*)rawData.bytes, + sizeof(MPU9250RawData)); + } + spiSlave.config.clock_div = clockDivider; + + // Save timestamps + uint64_t timestamp = TimestampTimer::getTimestamp(); + data.accel_timestamp = timestamp; + data.temp_timestamp = timestamp; + data.gyro_timestamp = timestamp; + data.mag_timestamp = timestamp; + + // Save data + data.accel_x = normalizeAcceleration(swapBytes16(rawData.bits.accelX)); + data.accel_y = normalizeAcceleration(swapBytes16(rawData.bits.accelY)); + data.accel_z = normalizeAcceleration(swapBytes16(rawData.bits.accelZ)); + data.temp = normalizeTemperature(swapBytes16(rawData.bits.temp)); + data.gyro_x = normalizeGyroscope(swapBytes16(rawData.bits.gyroX)); + data.gyro_y = normalizeGyroscope(swapBytes16(rawData.bits.gyroY)); + data.gyro_z = normalizeGyroscope(swapBytes16(rawData.bits.gyroZ)); + data.mag_x = normalizeMagnetometer(rawData.bits.magX, magSensAdjCoeff[0]); + data.mag_y = normalizeMagnetometer(rawData.bits.magY, magSensAdjCoeff[1]); + data.mag_z = normalizeMagnetometer(rawData.bits.magZ, magSensAdjCoeff[2]); + + return data; +} + +void MPU9250::resetDevice() +{ + SPITransaction transaction(spiSlave); + + transaction.write(REG_PWR_MGMT_1, REG_PWR_MGMT_1_BIT_H_RESET); +} + +void MPU9250::selectAutoClock() +{ + SPITransaction transaction(spiSlave); + + // Wake up by clearing sleep bit and set clock auto select + writeSPIWithDelay(transaction, REG_PWR_MGMT_1, REG_PWR_MGMT_1_CLKSEL_AUTO); +} + +void MPU9250::setGyroFsr(MPU9250GyroFSR fs) +{ + SPITransaction transaction(spiSlave); + + writeSPIWithDelay(transaction, REG_GYRO_CONFIG, fs); +} + +void MPU9250::setAccelFsr(MPU9250AccelFSR fs) +{ + SPITransaction transaction(spiSlave); + + writeSPIWithDelay(transaction, REG_ACCEL_CONFIG, fs); +} + +void MPU9250::setSampleRate(unsigned short rate) +{ + if (rate < 4) + rate = 4; + else if (rate > 1000) + rate = 1000; + + uint8_t data = 1000 / rate - 1; + + SPITransaction transaction(spiSlave); + + writeSPIWithDelay(transaction, REG_SMPLRT_DIV, data); + + // Set the digital low pass filter in order to use the sample rate divider + writeSPIWithDelay(transaction, REG_CONFIG, REG_CONFIG_DLPF_CFG_1); + + // We do not need to write Fchoise_b bits becouse they reset to 0 and they + // are the inverse of Fchoise, see page 13 of MPU9250 register map +} + +void MPU9250::enableMpuI2CMasterInterface() +{ + SPITransaction transaction(spiSlave); + + writeSPIWithDelay(transaction, REG_USER_CTRL, REG_USER_CTRL_I2C_MST_EN); +} + +void MPU9250::setMpuI2CMasterInterfaceClock(MPU9250I2CMasterInterfaceClock clk) +{ + SPITransaction transaction(spiSlave); + + writeSPIWithDelay(transaction, REG_I2C_MST_CTRL, clk); +} + +void MPU9250::setI2CMasterSlaveRead(uint8_t addr, uint8_t reg, uint8_t nBytes, + uint8_t slave) +{ + uint8_t regSlvAddr, regSlvReg, regSlvCtrl; + + switch (slave) + { + case 0: + regSlvAddr = REG_I2C_SLV0_ADDR; + regSlvReg = REG_I2C_SLV0_REG; + regSlvCtrl = REG_I2C_SLV0_CTRL; + break; + case 1: + regSlvAddr = REG_I2C_SLV1_ADDR; + regSlvReg = REG_I2C_SLV1_REG; + regSlvCtrl = REG_I2C_SLV1_CTRL; + break; + case 2: + regSlvAddr = REG_I2C_SLV2_ADDR; + regSlvReg = REG_I2C_SLV2_REG; + regSlvCtrl = REG_I2C_SLV2_CTRL; + break; + case 3: + regSlvAddr = REG_I2C_SLV3_ADDR; + regSlvReg = REG_I2C_SLV3_REG; + regSlvCtrl = REG_I2C_SLV3_CTRL; + break; + + default: + LOG_ERR(logger, + "invalid slave parameter in function " + "setI2CMasterSlaveRead"); + return; + } + + SPITransaction transaction(spiSlave); + + writeSPIWithDelay(transaction, regSlvAddr, addr | 0x80); // Set read bit + writeSPIWithDelay(transaction, regSlvReg, reg); + + // Set bytes number and enable the data transfer on each sample + writeSPIWithDelay(transaction, regSlvCtrl, REG_I2C_SLV_CTRL_EN | nBytes); +} + +void MPU9250::setI2CMasterSlaveWrite(uint8_t addr, uint8_t reg, uint8_t data, + uint8_t slave) +{ + uint8_t regSlvAddr, regSlvReg, regSlvCtrl, regSlvDo; + + switch (slave) + { + case 0: + regSlvAddr = REG_I2C_SLV0_ADDR; + regSlvReg = REG_I2C_SLV0_REG; + regSlvCtrl = REG_I2C_SLV0_CTRL; + regSlvDo = REG_I2C_SLV0_DO; + break; + case 1: + regSlvAddr = REG_I2C_SLV1_ADDR; + regSlvReg = REG_I2C_SLV1_REG; + regSlvCtrl = REG_I2C_SLV1_CTRL; + regSlvDo = REG_I2C_SLV1_DO; + break; + case 2: + regSlvAddr = REG_I2C_SLV2_ADDR; + regSlvReg = REG_I2C_SLV2_REG; + regSlvCtrl = REG_I2C_SLV2_CTRL; + regSlvDo = REG_I2C_SLV2_DO; + break; + case 3: + regSlvAddr = REG_I2C_SLV3_ADDR; + regSlvReg = REG_I2C_SLV3_REG; + regSlvCtrl = REG_I2C_SLV3_CTRL; + regSlvDo = REG_I2C_SLV3_DO; + break; + + default: + LOG_ERR(logger, + "invalid slave parameter in function " + "setI2CMasterSlaveRead"); + return; + } + + SPITransaction transaction(spiSlave); + + writeSPIWithDelay(transaction, regSlvAddr, addr); + writeSPIWithDelay(transaction, regSlvReg, reg); + writeSPIWithDelay(transaction, regSlvDo, data); + + // Enable the data transfer on each sample + writeSPIWithDelay(transaction, regSlvCtrl, REG_I2C_SLV_CTRL_EN | 0x1); +} + +void MPU9250::disableI2CMasterSlave(uint8_t slave) +{ + uint8_t regSlvCtrl; + + switch (slave) + { + case 0: + regSlvCtrl = REG_I2C_SLV0_CTRL; + break; + case 1: + regSlvCtrl = REG_I2C_SLV1_CTRL; + break; + case 2: + regSlvCtrl = REG_I2C_SLV2_CTRL; + break; + case 3: + regSlvCtrl = REG_I2C_SLV3_CTRL; + break; + // In the control register of slave 4 there is also the + // configuration for the slaves sampling rate. If slave 4 is needed + // the salmpling rate divider should not be changed case 4: + // regSlvCtrl = REG_I2C_SLV4_CTRL; + // break; + + default: + LOG_ERR(logger, + "invalid slave parameter in function " + "setI2CMasterSlaveRead"); + return; + } + + SPITransaction transaction(spiSlave); + + // Enable the data transfer on each sample + writeSPIWithDelay(transaction, regSlvCtrl, 0); +} + +uint8_t MPU9250::readFromAk(uint8_t reg) +{ + // Set the device address and register + setI2CMasterSlaveRead(AK8963_ADDR, reg); + + // Wait for the sample + miosix::Thread::sleep(1); + + SPITransaction transaction(spiSlave); + + return transaction.read(REG_EXT_SENS_DATA_00); +} + +void MPU9250::writeToAk(uint8_t reg, uint8_t data) +{ + // Set the device address, register and data byte + setI2CMasterSlaveWrite(AK8963_ADDR, reg, data); + miosix::Thread::sleep(1); +} + +bool MPU9250::initAk() +{ + // Set the sample rate to 1KHz in order to communicate faster, but safely, + // with the AK (if we use 8KHz the communication does not work properly) + setSampleRate(1000); + + // Power down the magnetometer + writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_POWER_DOWN_MODE); + + // Wait a bit more, the first slave communication seams to be delayed by 1 + // sample + miosix::Thread::sleep(1); + + // Reset + writeToAk(AK8963_REG_CNTL2, AK8963_REG_CNTL2_BIT_SRST); + + // Check AK8963 WHO AM I + if (!checkAkWhoAmI()) + { + LOG_ERR(logger, "Invalid AK8963 WHO AM I\n"); + + last_error = SensorErrors::INVALID_WHOAMI; + + return false; + } + + // Enter rom access mode + writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_POWER_DOWN_MODE); + writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_FUSE_ROM_ACCESS_MODE); + + // Read magnetometer sensitivity adjustment data (page 53 of register map) + magSensAdjCoeff[0] = + (((readFromAk(AK8963_REG_ASAX) - 128) + .5) / 128 + 1) * 4912.0f / + 32760.0f; + magSensAdjCoeff[1] = + (((readFromAk(AK8963_REG_ASAY) - 128) + .5) / 128 + 1) * 4912.0f / + 32760.0f; + magSensAdjCoeff[2] = + (((readFromAk(AK8963_REG_ASAZ) - 128) + .5) / 128 + 1) * 4912.0f / + 32760.0f; + + // Set continuos measurement mode + writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_POWER_DOWN_MODE); + writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_CONT_MES_MODE_2); + + // Set I2C master slave communication on each sample + setI2CMasterSlaveRead(AK8963_ADDR, AK8963_REG_ST1, 8); + + return true; +} + +bool MPU9250::checkWhoAmI() +{ + SPITransaction transaction(spiSlave); + + uint8_t who_am_i_value = transaction.read(REG_WHO_AM_I); + + return who_am_i_value == REG_WHO_AM_I_VAL; +} + +bool MPU9250::checkAkWhoAmI() +{ + uint8_t who_am_i_value = readFromAk(AK8963_REG_WHO_AM_I); + + return who_am_i_value == AK8963_REG_WHO_AM_I_VAL; +} + +void MPU9250::writeSPIWithDelay(SPITransaction& transaction, uint8_t reg, + uint8_t data) +{ + transaction.write(reg, data); + miosix::delayUs(1); + /*transaction.read(reg); + miosix::delayUs(1);*/ +} + +float MPU9250::normalizeAcceleration(int16_t rawValue) +{ + return static_cast<float>(rawValue) / 32768.0f * + ACCELERATION_FS_MAP[accelFsr >> 3] * EARTH_GRAVITY; +} + +// Page 33 of register map document +float MPU9250::normalizeTemperature(int16_t rawValue) +{ + return static_cast<float>(rawValue) / 512.0f + 21.0f; +} + +float MPU9250::normalizeGyroscope(int16_t rawValue) +{ + return static_cast<float>(rawValue) / 32768.0f * + GYROSCOPE_FS_MAP[gyroFsr >> 3] * DEGREES_TO_RADIANS; +} + +float MPU9250::normalizeMagnetometer(int16_t rawValue, float adjustmentCoeff) +{ + // Page 50 and 53 of register map document + return static_cast<float>(rawValue) * adjustmentCoeff; +} diff --git a/src/shared/sensors/MPU9250/MPU9250.h b/src/shared/sensors/MPU9250/MPU9250.h index 23a2edba773f8d90de53954439bb97d2600bca55..b6647bd5e128360832b3e5b206308fb7e581c0ae 100644 --- a/src/shared/sensors/MPU9250/MPU9250.h +++ b/src/shared/sensors/MPU9250/MPU9250.h @@ -1,7 +1,5 @@ -/* MPU9250 Driver - * - * Copyright (c) 2016,2019 Skyward Experimental Rocketry - * Authors: Alain Carlucci, Nuno Barcellos(Magnetometer) +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,500 +13,319 @@ * * 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 + * 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. */ -#ifndef MPU9250_H -#define MPU9250_H -#include <drivers/BusTemplate.h> -#include "../Sensor.h" -#include <miosix.h> - -// TODO: fix normalizeTemp() (is /512.0f correct?) -// TODO: Self-Test -template <typename Bus> -class MPU9250 : public GyroSensor, - public AccelSensor, - public CompassSensor, - public TemperatureSensor -{ +#pragma once -#pragma pack(1) - // __extension__ is needed to prevent compiler warnings for anonymous - // structs. - typedef union { - __extension__ struct - { - int16_t mag[3]; - }; - uint8_t raw[6]; - } akdata_t; +#include <diagnostic/PrintLogger.h> - typedef union { - __extension__ struct - { - int16_t accel[3]; - int16_t temp; - int16_t gyro[3]; - }; - int16_t buf[8]; - } mpudata_t; -#pragma pack() +#include "MPU9250Data.h" +#include "drivers/spi/SPIDriver.h" +#include "sensors/Sensor.h" +/** + * @brief Driver class for MPU9250 + */ +class MPU9250 : public Sensor<MPU9250Data> +{ public: - MPU9250(uint8_t accelFullScale, uint8_t gyroFullScale) + enum MPU9250GyroFSR : uint8_t { - accelFS = accelFullScale & 0x03; - gyroFS = gyroFullScale & 0x03; - magnetoFSMState = 0; - mLastTemp = 0.0f; - } - - ~MPU9250() {} + GYRO_FSR_250DPS = 0x0, + GYRO_FSR_500DPS = 0x08, + GYRO_FSR_1000DPS = 0x10, + GYRO_FSR_2000DPS = 0x18 + }; - std::vector<SPIRequest> buildDMARequest() override - { - // clang-format off - std::vector<uint8_t> v = - { - (REG_ACCEL_XOUT_H | 0x80), - 0,0,0,0,0,0, // accel - 0,0, // temp - 0,0,0,0,0,0, // gyro - }; - // clang-format on - - return {SPIRequest(0, Bus::getCSPin(), v)}; - } - - /* - * Only reads accelerometer, gyro and temp - */ - void onDMAUpdate(const SPIRequest &req) override + enum MPU9250AccelFSR : uint8_t { - const auto &r = req.readResponseFromPeripheral(); - mpudata_t raw_data; - - memcpy(&raw_data.buf, &(r[1]), sizeof(raw_data)); - for (int i = 0; i < 3; i++) - { - raw_data.accel[i] = fromBigEndian16(raw_data.accel[i]); - raw_data.gyro[i] = fromBigEndian16(raw_data.gyro[i]); - } - raw_data.temp = fromBigEndian16(raw_data.temp); - - mLastAccel.setX((normalizeAccel(raw_data.accel[0])-_axb)); - mLastAccel.setY((normalizeAccel(raw_data.accel[1])-_ayb)); - mLastAccel.setZ((normalizeAccel(raw_data.accel[2])-_azb)); - - mLastGyro.setX(normalizeGyro(raw_data.gyro[0])); - mLastGyro.setY(normalizeGyro(raw_data.gyro[1])); - mLastGyro.setZ(normalizeGyro(raw_data.gyro[2])); - - mLastTemp = normalizeTemp(raw_data.temp); - } - - bool init() override - { - miosix::Thread::sleep(100); - uint8_t whoami = Bus::read(REG_WHO_AM_I); - TRACE("MPU whoami: expected %x actual %x\n", who_am_i_value_mpu, whoami); - - if (whoami != who_am_i_value_mpu && whoami != 0x70) - { - last_error = ERR_NOT_ME; - return false; - } - - if(!initMagneto()) - { - TRACE("MPU9250 Magnetometer init failed\n"); - } - - // Initialize MPU9250 - // clang-format off - uint8_t init_data[][2] = - { - {REG_PWR_MGMT_1, 0x01}, // Clock Source - {REG_PWR_MGMT_2, 0x00}, // Enable all sensors - {REG_CONFIG, 0x00}, // DLPF_CFG = xxxxx000 - {REG_SMPLRT_DIV, 0x00}, // Do not divide - {REG_GYRO_CONFIG, (uint8_t) (0x03 | ((gyroFS & 3) << 3))}, - {REG_ACCEL_CONFIG, (uint8_t) (0x00 | ((accelFS & 3) << 3))}, - {REG_ACCEL_CONFIG2, 0x08}, // FCHOICE = 1, A_DLPF_CFG = 000 - }; - // clang-format on - - for (size_t i = 0; i < sizeof(init_data) / sizeof(init_data[0]); i++) - { - Bus::write(init_data[i][0], init_data[i][1]); - miosix::Thread::sleep(10); - } - - return true; - } + ACCEL_FSR_2G = 0x0, + ACCEL_FSR_4G = 0x08, + ACCEL_FSR_8G = 0x10, + ACCEL_FSR_16G = 0x18 + }; - /* Try to initialize AK8963 for 10 times because it doesn't always works */ - bool initMagneto() - { - for (size_t i = 0; i < 10; i++) - { - // clang-format off - uint8_t init_data[][2] = - { - {REG_PWR_MGMT_1, 0x80}, // Reset Device - {REG_INT_PIN_CFG, 0x16}, - - // I2C (MPU9250 <-> AK8963) - {REG_USER_CTRL, 0x20}, // I2C Master mode - - {REG_I2C_MST_CTRL, 0x48}, // I2C configuration multi-master IIC at 258hz - - {REG_I2C_SLV0_ADDR, AK8963_I2C_ADDR}, // Set the I2C slave addres of AK8963 and set for write. - {REG_I2C_SLV0_REG, AK8963_CNTL2}, // I2C slave 0 register address from where to begin data transfer - {REG_I2C_SLV0_DO, 0x01}, // Reset AK8963 - {REG_I2C_SLV0_CTRL, 0x81}, // Enable I2C and set 1 byte - - {REG_I2C_SLV0_REG, AK8963_CNTL1}, // I2C slave 0 register address from where to begin data transfer - {REG_I2C_SLV0_DO, 0x16}, // Register value to 100Hz continuous measurement in 16bit - // {REG_I2C_SLV0_DO, 0x12}, // Register value to 8Hz continuous measurement in 16bit - {REG_I2C_SLV0_CTRL, 0x81}, //Enable I2C and set 1 byte - }; - // clang-format on - - for (size_t i = 0; i < sizeof(init_data) / sizeof(init_data[0]); i++) - { - Bus::write(init_data[i][0], init_data[i][1]); - miosix::Thread::sleep(10); // It won't work without this delay - } - - uint8_t ak_wia = akReadWhoAmI(); - // printf("AK whoami: expected %x actual %x\n", who_am_i_value_ak, ak_wia); - if (ak_wia == who_am_i_value_ak) - { - magnetoFSMState = 1; - return true; - } - } - - // Reset the device if magnetometer initialization was unsuccessfull. - Bus::write(REG_PWR_MGMT_1,0x80); // Reset Device - miosix::Thread::sleep(10); - - return false; - } - - bool onSimpleUpdate() override + enum MPU9250I2CMasterInterfaceClock : uint8_t { - uint8_t buf[14]; - mpudata_t raw_data; - - Bus::read(REG_ACCEL_XOUT_H, buf, 14); - - memcpy(&raw_data.buf, buf, 14); - for (int i = 0; i < 3; i++) - { - raw_data.accel[i] = fromBigEndian16(raw_data.accel[i]); - raw_data.gyro[i] = fromBigEndian16(raw_data.gyro[i]); - } - raw_data.temp = fromBigEndian16(raw_data.temp); - - mLastAccel.setX((normalizeAccel(raw_data.accel[0])-_axb)); - mLastAccel.setY((normalizeAccel(raw_data.accel[1])-_ayb)); - mLastAccel.setZ((normalizeAccel(raw_data.accel[2])-_azb)); - - mLastGyro.setX(normalizeGyro(raw_data.gyro[0])); - mLastGyro.setY(normalizeGyro(raw_data.gyro[1])); - mLastGyro.setZ(normalizeGyro(raw_data.gyro[2])); - - mLastTemp = normalizeTemp(raw_data.temp); - - return true; - } + I2C_MST_IF_CLK_348 = 0x0, + I2C_MST_IF_CLK_333 = 0x1, + I2C_MST_IF_CLK_320 = 0x2, + I2C_MST_IF_CLK_308 = 0x3, + I2C_MST_IF_CLK_296 = 0x4, + I2C_MST_IF_CLK_286 = 0x5, + I2C_MST_IF_CLK_276 = 0x6, + I2C_MST_IF_CLK_267 = 0x7, + I2C_MST_IF_CLK_258 = 0x8, + I2C_MST_IF_CLK_500 = 0x9, + I2C_MST_IF_CLK_471 = 0xA, + I2C_MST_IF_CLK_444 = 0xB, + I2C_MST_IF_CLK_421 = 0xC, + I2C_MST_IF_CLK_400 = 0xD, + I2C_MST_IF_CLK_381 = 0xE, + I2C_MST_IF_CLK_364 = 0xF + }; - bool updateMagneto() + union MPU9250RawData { - /* Magnetometer FSM ( Board <-SPI-> MPU9250 <-I2C-> Magneto ) - * ______________ - * __________/ __v________ - * / State 1 \ / State 2 \ - * | ReadI2C | | CopyToMem | - * \__________/ \___________/ - * ^_______________/ - * - */ - switch (magnetoFSMState) + struct __attribute__((packed)) MPU9250RawDataBits { - case 1: // ReadI2C - akReadData1(); - magnetoFSMState = 2; - break; - case 2: // CopyToMem - akReadData2(); - magnetoFSMState = 1; - break; - default: - return false; - } - return true; - } - - /* Finds offset bias for the accelerometer*/ - void calibrateAccel() - { - accelFS = 0; - Bus::write(REG_ACCEL_CONFIG, accelFS); // setting the accel range to 2G - Bus::write(REG_ACCEL_CONFIG2, 0x04); // setting accel bandwidth to 20Hz - Bus::write(REG_SMPLRT_DIV,19); // setting the sample rate divider to 19 - - // accel bias and scale factor estimation - double _axbD = 0; - double _aybD = 0; - double _azbD = 0; - - for (size_t i = 0; i < _numSamples; i++) - { - onSimpleUpdate(); - _axbD += (mLastAccel.getX() + _axb)/((double)_numSamples); - _aybD += (mLastAccel.getY() + _ayb)/((double)_numSamples); - _azbD += (mLastAccel.getZ() + _azb)/((double)_numSamples); - miosix::Thread::sleep(10); - } - - _axb = _axbD; - _ayb = _aybD; - _azb = _azbD + EARTH_GRAVITY; - } - - void getAccelCalibParams(float* bias) - { - bias[0] = _axb; - bias[1] = _ayb; - bias[2] = _azb; - } - - void setAccelCalibParams(float* bias) - { - _axb = bias[0]; - _ayb = bias[1]; - _azb = bias[2]; - } - - bool selfTest() override - { - return true; - } + int16_t accelX; + int16_t accelY; + int16_t accelZ; + int16_t temp; + int16_t gyroX; + int16_t gyroY; + int16_t gyroZ; + uint8_t magSt1; + int16_t magX; + int16_t magY; + int16_t magZ; + } bits; + int8_t bytes[21]; + }; - // clang-format off - enum gyroFullScale + enum MPU9250Registers : uint8_t { - GYRO_FS_250 = 0, - GYRO_FS_500 = 1, - GYRO_FS_1000 = 2, - GYRO_FS_2000 = 3 + REG_SMPLRT_DIV = 0x19, + REG_CONFIG = 0x1A, + REG_GYRO_CONFIG = 0x1B, + REG_ACCEL_CONFIG = 0x1C, + REG_FIFO_EN = 0x23, + REG_I2C_MST_CTRL = 0x24, + REG_I2C_SLV0_ADDR = 0x25, + REG_I2C_SLV0_REG = 0x26, + REG_I2C_SLV0_CTRL = 0x27, + REG_I2C_SLV1_ADDR = 0x28, + REG_I2C_SLV1_REG = 0x29, + REG_I2C_SLV1_CTRL = 0x2A, + REG_I2C_SLV2_ADDR = 0x2B, + REG_I2C_SLV2_REG = 0x2C, + REG_I2C_SLV2_CTRL = 0x2D, + REG_I2C_SLV3_ADDR = 0x2E, + REG_I2C_SLV3_REG = 0x2F, + REG_I2C_SLV3_CTRL = 0x30, + REG_I2C_SLV4_ADDR = 0x31, + REG_I2C_SLV4_REG = 0x32, + REG_I2C_SLV4_DO = 0x33, + REG_I2C_SLV4_CTRL = 0x34, + REG_INT_PIN_CFG = 0x37, + REG_INT_ENABLE = 0x38, + REG_INT_STATUS = 0x3A, + REG_ACCEL_XOUT_H = 0x3B, + REG_ACCEL_XOUT_L = 0x3C, + REG_ACCEL_YOUT_H = 0x3D, + REG_ACCEL_YOUT_L = 0x3E, + REG_ACCEL_ZOUT_H = 0x3F, + REG_ACCEL_ZOUT_L = 0x40, + REG_TEMP_OUT_H = 0x41, + REG_TEMP_OUT_L = 0x42, + REG_GYRO_XOUT_H = 0x43, + REG_GYRO_XOUT_L = 0x44, + REG_GYRO_YOUT_H = 0x45, + REG_GYRO_YOUT_L = 0x46, + REG_GYRO_ZOUT_H = 0x47, + REG_GYRO_ZOUT_L = 0x48, + REG_EXT_SENS_DATA_00 = 0x49, + REG_I2C_SLV0_DO = 0x63, + REG_I2C_SLV1_DO = 0x64, + REG_I2C_SLV2_DO = 0x65, + REG_I2C_SLV3_DO = 0x66, + REG_MST_DELAY_CTRL = 0x67, + REG_USER_CTRL = 0x6A, + REG_PWR_MGMT_1 = 0x6B, + REG_PWR_MGMT_2 = 0x6C, + REG_WHO_AM_I = 0x75 }; - enum accelFullScale + enum AK8963Registers : uint8_t { - ACC_FS_2G = 0, - ACC_FS_4G = 1, - ACC_FS_8G = 2, - ACC_FS_16G = 3 + AK8963_REG_WHO_AM_I = 0x0, + AK8963_REG_ST1 = 0x2, + AK8963_REG_HXL = 0x3, + AK8963_REG_HXH = 0x4, + AK8963_REG_HYL = 0x5, + AK8963_REG_HYH = 0x6, + AK8963_REG_HZL = 0x7, + AK8963_REG_HZH = 0x8, + AK8963_REG_ST2 = 0x9, + AK8963_REG_CNTL1 = 0xA, + AK8963_REG_CNTL2 = 0xB, + AK8963_REG_ASAX = 0x10, + AK8963_REG_ASAY = 0x11, + AK8963_REG_ASAZ = 0x12 }; - // clang-format on -private: - constexpr static uint8_t who_am_i_value_mpu = 0x71; - constexpr static uint8_t who_am_i_value_ak = 0x48; - constexpr static float accelFSMAP[] = {2.0, 4.0, 8.0, 16.0}; - constexpr static float gyroFSMAP[] = {250, 500, 1000, 2000}; - - uint8_t gyroFS; - uint8_t accelFS; - uint8_t magnetoFSMState; - - // accel bias and scale factor estimation - size_t _numSamples = 1000; - float _axb = 0.0f; // offset bias - float _ayb = 0.0f; - float _azb = 0.0f; - - inline float normalizeAccel(int16_t val) - { - return static_cast<float>(val) / 32768.0f * accelFSMAP[accelFS] * - EARTH_GRAVITY; - } + // Registers bits + static constexpr uint8_t REG_CONFIG_DLPF_CFG_1 = 0x1; + static constexpr uint8_t REG_USER_CTRL_I2C_MST_EN = 0x20; + static constexpr uint8_t REG_I2C_SLV_CTRL_EN = 0x80; + static constexpr uint8_t REG_PWR_MGMT_1_BIT_H_RESET = 0x80; + static constexpr uint8_t REG_PWR_MGMT_1_CLKSEL_AUTO = 0x1; + static constexpr uint8_t REG_WHO_AM_I_VAL = 0x71; + + // AK8963 address and bits + static constexpr uint8_t AK8963_ADDR = 0xC; + static constexpr uint8_t AK8963_REG_WHO_AM_I_VAL = 0x48; + static constexpr uint8_t AK8963_REG_CNTL1_POWER_DOWN_MODE = 0x0; + static constexpr uint8_t AK8963_REG_CNTL1_SINGLE_MES_MODE = 0x11; // 16 bit + static constexpr uint8_t AK8963_REG_CNTL1_CONT_MES_MODE_1 = 0x12; // 16 bit + static constexpr uint8_t AK8963_REG_CNTL1_CONT_MES_MODE_2 = 0x16; // 16 bit + static constexpr uint8_t AK8963_REG_CNTL1_SELF_TEST_MODE = 0x8; + static constexpr uint8_t AK8963_REG_CNTL1_FUSE_ROM_ACCESS_MODE = 0xF; + static constexpr uint8_t AK8963_REG_CNTL1_BIT_16_BIT_OUT = 0xF; + static constexpr uint8_t AK8963_REG_CNTL2_BIT_SRST = 0x1; + + const float ACCELERATION_FS_MAP[4] = {2.0, 4.0, 8.0, 16.0}; + const float GYROSCOPE_FS_MAP[4] = {250, 500, 1000, 2000}; + + /** + * @brief Instantiates the driver + * + * @param highSpeedSpiClockDivider_ Clocl diver for 20MHz SPI communication + * with the device + */ + MPU9250(SPISlave spiSlave_, unsigned short samplingRate_ = 100, + MPU9250GyroFSR gyroFsr_ = GYRO_FSR_250DPS, + MPU9250AccelFSR accelFsr_ = ACCEL_FSR_2G, + SPIClockDivider highSpeedSpiClockDivider_ = SPIClockDivider::DIV4); + + /** + * @brief Constructs the default config for SPI Bus. + * + * @returns the default SPIBusConfig + */ + static SPIBusConfig getDefaultSPIConfig(); - inline float normalizeGyro(int16_t val) - { - return static_cast<float>(val) / 32768.0f * gyroFSMAP[gyroFS] * - DEGREES_TO_RADIANS; - } + /** + * @brief Initialize the device + * + * The AK8963 is initialized such ad its data are read by the MPU9250 at + * each sample cycle, the host only needs to communicate with the MPU9250 + */ + bool init() override; - inline float normalizeMagneto(int16_t val) - { - // Page 50 @ Register Map document - return static_cast<float>(val) / 32760.0f * 4912.0f; - } + /** + * @brief Self test + * + * @return True if everything ok + */ + bool selfTest() override { return false; }; - inline float normalizeTemp(int16_t val) - { - // Page 33 @ Register Map Document - return static_cast<float>(val) / 512.0f + 21.0f; - } +private: + MPU9250Data sampleImpl() override; + void resetDevice(); - uint8_t akReadWhoAmI() - { - // clang-format off - uint8_t regs[][2] = - { - { REG_I2C_SLV0_ADDR, (uint8_t)(AK8963_I2C_ADDR | 0x80)}, - { REG_I2C_SLV0_REG, AK8963_WIA}, - { REG_I2C_SLV0_CTRL, 0x81 }, //Read 1 byte from the magnetometer - }; - // clang-format on + void selectAutoClock(); - for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) - Bus::write(regs[i][0], regs[i][1]); + /** + * @brief Set the gyro full-scale range. + * + * @param fsr Desired full-scale range + */ + void setGyroFsr(MPU9250GyroFSR fs); - miosix::Thread::sleep(2); + /** + * @brief Set the accel full-scale range. + * + * @param fsr Desired full-scale range + */ + void setAccelFsr(MPU9250AccelFSR fs); + + /** + * @brief Set sampling rate. + * + * Sampling rate must be between 4Hz and 1kHz. + * + * @param rate Desired sampling rate (Hz) + */ + void setSampleRate(unsigned short rate); - uint8_t ret = Bus::read(REG_EXT_SENS_DATA_00|0x80); - return ret; - } + void enableMpuI2CMasterInterface(); - void akReadData() - { - // clang-format off - uint8_t regs[][2] = - { - { REG_I2C_SLV0_ADDR, (uint8_t)(AK8963_I2C_ADDR | 0x80)}, - { REG_I2C_SLV0_REG, AK8963_HXL}, - { REG_I2C_SLV0_CTRL, 0x87}, // Read 6 bytes from the magnetometer - }; - // clang-format on + void setMpuI2CMasterInterfaceClock(MPU9250I2CMasterInterfaceClock clk); - for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) - Bus::write(regs[i][0], regs[i][1]); + /** + * @brief Setup the I2C slave (0-3) for read operations + */ + void setI2CMasterSlaveRead(uint8_t addr, uint8_t reg, uint8_t nBytes = 1, + uint8_t slave = 0); - miosix::Thread::sleep(2); + /** + * @brief Setup the I2C slave (0-3) for write operations + */ + void setI2CMasterSlaveWrite(uint8_t addr, uint8_t reg, uint8_t data, + uint8_t slave = 0); - akdata_t ak; + /** + * @brief Disables the I2C slave communication + */ + void disableI2CMasterSlave(uint8_t slave = 0); - Bus::read(REG_EXT_SENS_DATA_00, ak.raw, sizeof(ak.raw)); + /** + * @brief Read a byte form the AK8963 throug the MPU9250 I2C master + * interface. + * + * Requires that MPU I2C master interface to be enabled. + */ + uint8_t readFromAk(uint8_t reg); - ak.mag[0] = (ak.raw[1] << 8) | ak.raw[0]; - ak.mag[1] = (ak.raw[3] << 8) | ak.raw[2]; - ak.mag[2] = (ak.raw[5] << 8) | ak.raw[4]; + /** + * @brief Writes a byte to the AK8963 throug the MPU9250 I2C master + * interface. + * + * Requires that MPU I2C master interface to be enabled. + */ + void writeToAk(uint8_t reg, uint8_t data); - mLastCompass.setX(normalizeMagneto(ak.mag[0])); - mLastCompass.setY(normalizeMagneto(ak.mag[1])); - mLastCompass.setZ(normalizeMagneto(ak.mag[2])); - } + /** + * @brief Initialize the AK8963. + * + * Requires the MPU I2C master interface to be enabled. + */ + bool initAk(); - // akReadData11 tells to the i2c master to begin reading and.. (akReadData2) - void akReadData1() - { - // clang-format off - uint8_t regs[][2] = - { - { REG_I2C_SLV0_ADDR, (uint8_t)(AK8963_I2C_ADDR | 0x80)}, - { REG_I2C_SLV0_REG, AK8963_HXL}, - { REG_I2C_SLV0_CTRL, 0x87}, // Read 6 bytes from the magnetometer - }; - // clang-format on - - for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) - Bus::write(regs[i][0], regs[i][1]); - } - - // akReadData2 this one moves the data from spi to the local processor - void akReadData2() - { - akdata_t ak; + /** + * @brief Check the WHO AM I code from the device. + * + * @return true if the device is recognized + */ + bool checkWhoAmI(); - Bus::read(REG_EXT_SENS_DATA_00, ak.raw, sizeof(ak.raw)); + /** + * @brief Check the WHO AM I code for the AK8963. + * + * @return true if the device is recognized + */ + bool checkAkWhoAmI(); - ak.mag[0] = (ak.raw[1] << 8) | ak.raw[0]; - ak.mag[1] = (ak.raw[3] << 8) | ak.raw[2]; - ak.mag[2] = (ak.raw[5] << 8) | ak.raw[4]; + inline void writeSPIWithDelay(SPITransaction& transaction, uint8_t reg, + uint8_t data); - mLastCompass.setX(normalizeMagneto(ak.mag[0])); - mLastCompass.setY(normalizeMagneto(ak.mag[1])); - mLastCompass.setZ(normalizeMagneto(ak.mag[2])); - } + inline float normalizeAcceleration(int16_t rawValue); - // clang-format off - enum magnetoMap - { - AK8963_I2C_ADDR = 0x0c, + inline float normalizeTemperature(int16_t rawValue); - AK8963_WIA = 0x00, - AK8963_STATUS1 = 0x02, - AK8963_HXL = 0x03, - AK8963_CNTL1 = 0x0A, - AK8963_CNTL2 = 0x0B, - }; + inline float normalizeGyroscope(int16_t rawValue); - enum regMap - { - REG_ST_GYRO = 0x00, - REG_ST_ACCEL = 0x0D, - - REG_SMPLRT_DIV = 0x19, - REG_CONFIG = 0x1A, - REG_GYRO_CONFIG = 0x1B, - REG_ACCEL_CONFIG = 0x1C, - REG_ACCEL_CONFIG2 = 0x1D, - - REG_I2C_MST_CTRL = 0x24, - REG_I2C_SLV0_ADDR = 0x25, - REG_I2C_SLV0_REG = 0x26, - REG_I2C_SLV0_CTRL = 0x27, - REG_I2C_SLV0_DO = 0x63, - - REG_I2C_SLV4_ADDR = 0x31, - REG_I2C_SLV4_REG = 0x32, - REG_I2C_SLV4_DO = 0x33, - REG_I2C_SLV4_CTRL = 0x34, - REG_I2C_SLV4_DI = 0x35, - REG_I2C_MST_STATUS = 0x36, - - REG_ACCEL_XOUT_H = 0x3b, - - REG_INT_PIN_CFG = 0x37, - REG_INT_ENABLE = 0x38, - REG_INT_STATUS = 0x3A, - - REG_EXT_SENS_DATA_00= 0x49, - - REG_USER_CTRL = 0x6A, - REG_PWR_MGMT_1 = 0x6B, - REG_PWR_MGMT_2 = 0x6C, - REG_WHO_AM_I = 0x75 - }; - // clang-format on + inline float normalizeMagnetometer(int16_t rawValue, float adjustmentCoeff); - void readRAWData(mpudata_t &out) - { - Bus::read(REG_ACCEL_XOUT_H, reinterpret_cast<uint8_t *>(out.buf), - sizeof(out.buf)); + SPISlave spiSlave; + + const unsigned short samplingRate; - // BigEndian -> CPUArch (LittleEndian as usual) - for (size_t i = 0; i < (sizeof(out.buf) / sizeof(out.buf[0])); i++) - out.buf[i] = fromBigEndian16(out.buf[i]); - } -}; + const MPU9250GyroFSR gyroFsr; + const MPU9250AccelFSR accelFsr; + float magSensAdjCoeff[3]; // Page 32 on datasheet -template <typename Bus> -constexpr float MPU9250<Bus>::accelFSMAP[]; + const SPIClockDivider highSpeedSpiClockDivider; -template <typename Bus> -constexpr float MPU9250<Bus>::gyroFSMAP[]; + bool initialized = false; // Whether the sensor has been initialized -#endif /* ifndef MPU9250 */ + PrintLogger logger = Logging::getLogger("mpu9250"); +}; \ No newline at end of file diff --git a/src/shared/sensors/MPU9250/MPU9250Data.h b/src/shared/sensors/MPU9250/MPU9250Data.h index 7c850c56bbf708da68a4aa17e0bdfcda1e398b3f..4204b0791e656d4b44cde96828890c4af88e4519 100644 --- a/src/shared/sensors/MPU9250/MPU9250Data.h +++ b/src/shared/sensors/MPU9250/MPU9250Data.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,39 +13,41 @@ * * 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 + * 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. */ -#ifndef SRC_SHARED_SENSORS_MPU9250_MPU9250DATA_H -#define SRC_SHARED_SENSORS_MPU9250_MPU9250DATA_H -#include <ostream> -#include "math/Vec3.h" +#pragma once -struct MPU9250Data +#include "sensors/SensorData.h" + +struct MPU9250Data : public AccelerometerData, + public GyroscopeData, + public MagnetometerData, + public TemperatureData { - long long timestamp; - Vec3 accel; - Vec3 gyro; - Vec3 compass; - float temp; + MPU9250Data() + : AccelerometerData{0, 0.0, 0.0, 0.0}, GyroscopeData{0, 0.0, 0.0, 0.0}, + MagnetometerData{0, 0.0, 0.0, 0.0}, TemperatureData{0, 0.0} + { + } static std::string header() { - return "timestamp,acc_x,acc_y,acc_z,gyro_x,gyro_y,gyro_z,compass_x," - "compass_y,compass_z\n"; + return "accel_timestamp,accel_x,accel_y,accel_z,gyro_timestamp,gyro_x," + "gyro_y,gyro_z,mag_timestamp,mag_x,mag_y,mag_z,temp_timestamp," + "temp\n"; } void print(std::ostream& os) const { - os << timestamp << "," << accel.getX() << "," << accel.getY() << "," - << accel.getZ() << "," << gyro.getX() << "," << gyro.getY() << "," - << gyro.getZ() << "," << compass.getX() << "," << compass.getY() - << "," << compass.getZ() << "\n"; + os << accel_timestamp << "," << accel_x << "," << accel_y << "," + << accel_z << "," << gyro_timestamp << "," << gyro_x << "," << gyro_y + << "," << gyro_z << "," << mag_timestamp << "," << mag_x << "," + << mag_y << "," << mag_z << "," << temp_timestamp << "," << temp + << "\n"; } -}; - -#endif /* SRC_SHARED_SENSORS_MPU9250_MPU9250DATA_H */ +}; \ No newline at end of file diff --git a/src/shared/sensors/MPU9250/MPU9250_magneto.h b/src/shared/sensors/MPU9250/MPU9250_magneto.h deleted file mode 100644 index 952caa7af3746f28920fdb62dd880914c0e85e1c..0000000000000000000000000000000000000000 --- a/src/shared/sensors/MPU9250/MPU9250_magneto.h +++ /dev/null @@ -1,435 +0,0 @@ -/* MAX9250 Driver - * - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci - * - * 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. - */ - -#ifndef MAX9250_H -#define MAX9250_H -#include <drivers/BusTemplate.h> -#include "../Sensor.h" - -/* THIS FILE INCLUDES THE DRIVER FOR THE MAGNETOMETER PART, WHICH IS CURRENTLY - * NOT WORKING. :( - */ - -// TODO: fix normalizeTemp() (is /512.0f correct?) -// TODO: Self-Test -template <typename Bus> -class MPU9250 : public GyroSensor, - public AccelSensor, - public CompassSensor, - public TemperatureSensor -{ - -#pragma pack(1) - typedef union { - __extension__ struct - { - uint8_t status1; - int16_t mag[3]; - uint8_t status2; - }; - uint8_t raw[9]; - } akdata_t; - - typedef union { - __extension__ struct - { - int16_t accel[3]; - int16_t temp; - int16_t gyro[3]; - }; - int16_t buf[8]; - } mpudata_t; -#pragma pack() - -public: - MPU9250(uint8_t accelFullScale, uint8_t gyroFullScale) - { - accelFS = accelFullScale & 0x03; - gyroFS = gyroFullScale & 0x03; - magnetoFSMState = 0; - mLastTemp = 0.0f; - } - - ~MPU9250() {} - - std::vector<SPIRequest> buildDMARequest() override - { - // clang-format off - std::vector<uint8_t> v = - { - (REG_ACCEL_XOUT_H | 0x80), - 0,0,0,0,0,0, // accel - 0,0, // temp - 0,0,0,0,0,0, // gyro - }; - // clang-format on - - return {SPIRequest(0, Bus::getCSPin(), v)}; - } - - /* - * Only reads accelerometer, gyro and temp - */ - void onDMAUpdate(const SPIRequest &req) override - { - const auto &r = req.readResponseFromPeripheral(); - mpudata_t raw_data; - - memcpy(&raw_data.buf, &(r[1]), sizeof(raw_data)); - for (int i = 0; i < 3; i++) - { - raw_data.accel[i] = fromBigEndian16(raw_data.accel[i]); - raw_data.gyro[i] = fromBigEndian16(raw_data.gyro[i]); - } - raw_data.temp = fromBigEndian16(raw_data.temp); - - mLastAccel.setX(normalizeAccel(raw_data.accel[0])); - mLastAccel.setY(normalizeAccel(raw_data.accel[1])); - mLastAccel.setZ(normalizeAccel(raw_data.accel[2])); - - mLastGyro.setX(normalizeGyro(raw_data.gyro[0])); - mLastGyro.setY(normalizeGyro(raw_data.gyro[1])); - mLastGyro.setZ(normalizeGyro(raw_data.gyro[2])); - - mLastTemp = normalizeTemp(raw_data.temp); - } - - bool init() override - { - uint8_t whoami = Bus::read(REG_WHO_AM_I); - // printf("WHOAMI: %d\n", whoami); - if (whoami != who_am_i_value) - { - last_error = ERR_NOT_ME; - return false; - } - - // Warning: do not reset this sensor at startup, otherwise - // its magnetometer could randomly stop working. - - // clang-format off - uint8_t init_data[][2] = - { - {REG_PWR_MGMT_1, 0x01}, - {REG_PWR_MGMT_2, 0x00}, // Enable all sensors - {REG_CONFIG, 0x00}, // DLPF_CFG = xxxxx000 - {REG_SMPLRT_DIV, 0x00}, // Do not divide - {REG_GYRO_CONFIG, (uint8_t) (0x03 | ((gyroFS & 3) << 3))}, - {REG_ACCEL_CONFIG, (uint8_t) (0x00 | ((accelFS & 3) << 3))}, - {REG_ACCEL_CONFIG2, 0x08}, // FCHOICE = 1, A_DLPF_CFG = 000 - {REG_INT_PIN_CFG, 0x30}, - {REG_INT_ENABLE, 0x00}, // No interrupts - - // I2C (MPU9250 <-> AK8963) - {REG_USER_CTRL, 0x20}, // Master mode TODO: I2C_IF_DIS??? - {REG_I2C_MST_CTRL, 0x00}, // I2C @ 348khz(400 seems giving issues) - }; - // clang-format on - - for (size_t i = 0; i < sizeof(init_data) / sizeof(init_data[0]); i++) - Bus::write(init_data[i][0], init_data[i][1]); - - // MAGNETOMETER NOT WORKING - /* - uint8_t ak_wia = akReadReg(AK8963_WIA); - - if (ak_wia != 0x48) - { - last_error = ERR_CANT_TALK_TO_CHILD; // TODO - return false; - } - - akWriteReg(AK8963_CNTL2, 0x01); - miosix::Thread::sleep(1); - akWriteReg(AK8963_CNTL1, 0x16); - miosix::Thread::sleep(1); - - */ - magnetoFSMState = 1; - return true; - } - - /** - * MAGNETOMETER NOT WORKING - * Reads external magnetometer. - - bool onSimpleUpdate() override - { - - akdata_t ak; - - /* Magnetometer FSM ( Board <-SPI-> MPU9250 <-I2C-> Magneto ) - * ______________ - * __________/ __v________ - * / State 1 \ / State 2 \ - * | ReadI2C | | CopyToMem | - * \__________/ \___________/ - * ^_______________/ - * - - - switch (magnetoFSMState) - { - case 1: // ReadI2C - akReadReg_1(AK8963_STATUS1, sizeof(ak.raw)); - magnetoFSMState = 2; - break; - case 2: // CopyToMem - akReadReg_2(ak.raw, sizeof(ak.raw)); - - mLastCompass.setX(normalizeMagneto(ak.mag[0])); - mLastCompass.setY(normalizeMagneto(ak.mag[1])); - mLastCompass.setZ(normalizeMagneto(ak.mag[2])); - magnetoFSMState = 1; - break; - default: - return false; - } - - return true; - } - */ - - bool selfTest() override - { - uint8_t st_gyro[3], st_accel[3]; - mpudata_t test, real; - uint16_t cfg; // [ REG_GYRO_CONFIG REG_ACCEL_CONFIG ] - - readRAWData(real); - Bus::read(REG_GYRO_CONFIG, reinterpret_cast<uint8_t *>(&cfg), - sizeof(cfg)); - - // -- ENABLE SELF TEST -- - cfg |= 0xe0e0; - Bus::write(REG_GYRO_CONFIG, reinterpret_cast<uint8_t *>(&cfg), - sizeof(cfg)); - - // -- SELF TEST ROUTINE -- - Bus::read(REG_ST_GYRO, st_gyro, sizeof(st_gyro)); - Bus::read(REG_ST_ACCEL, st_accel, sizeof(st_accel)); - miosix::Thread::sleep(10); - readRAWData(test); - - // -- DISABLE SELF TEST -- - cfg &= ~(0xe0e0); - Bus::write(REG_GYRO_CONFIG, reinterpret_cast<uint8_t *>(&cfg), - sizeof(cfg)); - - // TODO Self-Test - - return false; - } - - // clang-format off - enum gyroFullScale - { - GYRO_FS_250 = 0, - GYRO_FS_500 = 1, - GYRO_FS_1000 = 2, - GYRO_FS_2000 = 3 - }; - - enum accelFullScale - { - ACC_FS_2G = 0, - ACC_FS_4G = 1, - ACC_FS_8G = 2, - ACC_FS_16G = 3 - }; - // clang-format on - -private: - constexpr static uint8_t who_am_i_value = 0x71; - constexpr static float accelFSMAP[] = {2.0, 4.0, 8.0, 16.0}; - constexpr static float gyroFSMAP[] = {250, 500, 1000, 2000}; - - uint8_t gyroFS; - uint8_t accelFS; - uint8_t magnetoFSMState; - - inline float normalizeAccel(int16_t val) - { - return static_cast<float>(val) / 32768.0f * accelFSMAP[accelFS] * - EARTH_GRAVITY; - } - - inline float normalizeGyro(int16_t val) - { - return static_cast<float>(val) / 32768.0f * gyroFSMAP[gyroFS] * - DEGREES_TO_RADIANS; - } - - inline float normalizeMagneto(int16_t val) - { - // Page 50 @ Register Map document - return static_cast<float>(val) / 32760.0f * 4912.0f; - } - - inline float normalizeTemp(int16_t val) - { - // Page 33 @ Register Map Document - return static_cast<float>(val) / 512.0f + 21.0f; - } - - uint8_t akReadReg(uint8_t reg) - { - // clang-format off - uint8_t regs[][2] = - { - { REG_I2C_SLV4_ADDR, (uint8_t)(AK8963_I2C_ADDR | 0x80)}, - { REG_I2C_SLV4_REG, reg }, - { REG_I2C_SLV4_CTRL, 0x80 }, - }; - // clang-format on - - for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) - Bus::write(regs[i][0], regs[i][1]); - - int timeout = 20; - while (--timeout >= 0) - { - if (Bus::read(REG_I2C_MST_STATUS) & 0x40) - break; - miosix::Thread::sleep(1); - } - - if (timeout < 0) - return -1; - - uint8_t ret = Bus::read(REG_I2C_SLV4_DI); - return ret; - } - - // This is an optimized version of the common (i2c) burst-read: - // _1 tells to the i2c master to begin reading and.. (See _2) - void akReadReg_1(uint8_t addr, uint8_t len) - { - assert(len < 16 && len > 0); - // clang-format off - uint8_t regs[][2] = - { - { REG_I2C_SLV0_ADDR, (uint8_t)(AK8963_I2C_ADDR | 0x80)}, - { REG_I2C_SLV0_REG, addr }, - { REG_I2C_SLV0_CTRL, (uint8_t)(0x80 + len)}, - }; - // clang-format on - - for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) - Bus::write(regs[i][0], regs[i][1]); - } - - // _2 this one moves the data from spi to the local processor - void akReadReg_2(uint8_t *buf, uint8_t len) - { - Bus::read(REG_EXT_SENS_DATA_00, buf, len); - } - - void akWriteReg(uint8_t reg, uint8_t data) - { - // clang-format off - uint8_t regs[][2] = - { - { REG_I2C_SLV4_ADDR, AK8963_I2C_ADDR}, - { REG_I2C_SLV4_REG, reg}, - { REG_I2C_SLV4_DO, data}, - { REG_I2C_SLV4_CTRL, 0x80}, - }; - // clang-format on - - for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) - Bus::write(regs[i][0], regs[i][1]); - - miosix::Thread::sleep(1); - } - - // clang-format off - enum magnetoMap - { - AK8963_I2C_ADDR = 0x0c, - - AK8963_WIA = 0x00, - AK8963_STATUS1 = 0x02, - AK8963_HXL = 0x03, - AK8963_CNTL1 = 0x0a, - AK8963_CNTL2 = 0x0b, - }; - - enum regMap - { - REG_ST_GYRO = 0x00, - REG_ST_ACCEL = 0x0D, - - REG_SMPLRT_DIV = 0x19, - REG_CONFIG = 0x1A, - REG_GYRO_CONFIG = 0x1B, - REG_ACCEL_CONFIG = 0x1C, - REG_ACCEL_CONFIG2 = 0x1D, - - REG_I2C_MST_CTRL = 0x24, - REG_I2C_SLV0_ADDR = 0x25, - REG_I2C_SLV0_REG = 0x26, - REG_I2C_SLV0_CTRL = 0x27, - REG_I2C_SLV0_DO = 0x63, - - REG_I2C_SLV4_ADDR = 0x31, - REG_I2C_SLV4_REG = 0x32, - REG_I2C_SLV4_DO = 0x33, - REG_I2C_SLV4_CTRL = 0x34, - REG_I2C_SLV4_DI = 0x35, - REG_I2C_MST_STATUS = 0x36, - - REG_ACCEL_XOUT_H = 0x3b, - - REG_INT_PIN_CFG = 0x37, - REG_INT_ENABLE = 0x38, - REG_INT_STATUS = 0x3A, - - REG_EXT_SENS_DATA_00= 0x49, - - REG_USER_CTRL = 0x6A, - REG_PWR_MGMT_1 = 0x6B, - REG_PWR_MGMT_2 = 0x6C, - REG_WHO_AM_I = 0x75 - }; - // clang-format on - - void readRAWData(mpudata_t &out) - { - Bus::read(REG_ACCEL_XOUT_H, reinterpret_cast<uint8_t *>(out.buf), - sizeof(out.buf)); - - // BigEndian -> CPUArch (LittleEndian as usual) - for (size_t i = 0; i < (sizeof(out.buf) / sizeof(out.buf[0])); i++) - out.buf[i] = fromBigEndian16(out.buf[i]); - } -}; - -template <typename Bus> -constexpr float MPU9250<Bus>::accelFSMAP[]; - -template <typename Bus> -constexpr float MPU9250<Bus>::gyroFSMAP[]; - -#endif /* ifndef MAX9250_H */ diff --git a/src/shared/sensors/MS5803/MS5803.cpp b/src/shared/sensors/MS5803/MS5803.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b7de74b25648d5254b077683f6402cb9008715d6 --- /dev/null +++ b/src/shared/sensors/MS5803/MS5803.cpp @@ -0,0 +1,200 @@ + +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Alberto Nidasio + * + * 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. + */ + +#include "MS5803.h" + +#include "TimestampTimer.h" + +MS5803::MS5803(SPISlave spiSlave_, uint16_t temperatureDivider_) + : spiSlave(spiSlave_), temperatureDivider(temperatureDivider_) +{ +} + +MS5803::MS5803(SPIBusInterface& spiBus_, miosix::GpioPin cs_, + SPIBusConfig spiConfig_, uint16_t temperatureDivider_) + : spiSlave(spiBus_, cs_, spiConfig_), + temperatureDivider(temperatureDivider_) +{ +} + +bool MS5803::init() +{ + SPITransaction transaction{spiSlave}; + + // Read calibration data + calibrationData.sens = + readReg(transaction, REG_PROM_READ_MASK | REG_PROM_SENS_MASK); + calibrationData.off = + readReg(transaction, REG_PROM_READ_MASK | REG_PROM_OFF_MASK); + calibrationData.tcs = + readReg(transaction, REG_PROM_READ_MASK | REG_PROM_TCS_MASK); + calibrationData.tco = + readReg(transaction, REG_PROM_READ_MASK | REG_PROM_TCO_MASK); + calibrationData.tref = + readReg(transaction, REG_PROM_READ_MASK | REG_PROM_TREF_MASK); + calibrationData.tempsens = + readReg(transaction, REG_PROM_READ_MASK | REG_PROM_TEMPSENS_MASK); + + LOG_INFO(logger, "Init: off={}, tcs={}, tco={}, tref={}, tsens={}", + calibrationData.off, calibrationData.tcs, calibrationData.tco, + calibrationData.tref, calibrationData.tempsens); + + return true; +} + +bool MS5803::selfTest() { return true; } + +MS5803Data MS5803::sampleImpl() +{ + SPITransaction transaction{spiSlave}; + + uint8_t buffer[3]; + + switch (deviceState) + { + case STATE_INIT: + { + // Begin temperature sampling + transaction.write(REG_CONVERT_D2_4096); + deviceState = STATE_SAMPLED_TEMP; + break; + } + case STATE_SAMPLED_TEMP: + { + // Read back the sampled temperature + transaction.read(REG_ADC_READ, buffer, 3, false); + + uint32_t tmpRawTemperature = (uint32_t)buffer[2] | + ((uint32_t)buffer[1] << 8) | + ((uint32_t)buffer[0] << 16); + lastTemperatureTimestamp = TimestampTimer::getTimestamp(); + + // Check if the value is valid + if (tmpRawTemperature != 0) + { + rawTemperature = tmpRawTemperature; + } + else + { + LOG_ERR(logger, "The read raw temperature isn't valid"); + } + + // Begin pressure sampling + transaction.write(REG_CONVERT_D1_4096); + deviceState = STATE_SAMPLED_PRESS; + break; + } + case STATE_SAMPLED_PRESS: + { + // Read back the sampled pressure + transaction.read(REG_ADC_READ, buffer, 3, false); + + uint32_t tmpRawPressure = (uint32_t)buffer[2] | + ((uint32_t)buffer[1] << 8) | + ((uint32_t)buffer[0] << 16); + + // Check if the value is valid + if (tmpRawPressure != 0) + { + rawPressure = tmpRawPressure; + } + else + { + LOG_ERR(logger, "The read raw pressure isn't valid"); + } + + last_sample = updateData(); + + // Check whether to read the pressure or the temperature + tempCounter++; + if (tempCounter % temperatureDivider == 0) + { + // Begin temperature sampling + transaction.write(REG_CONVERT_D2_4096); + deviceState = STATE_SAMPLED_TEMP; + } + else + { + // Begin pressure sampling again + transaction.write(REG_CONVERT_D1_4096); + } + break; + } + default: + break; + } + + return last_sample; +} + +MS5803Data MS5803::updateData() +{ + // First order compensation + int32_t dt = rawTemperature - (((uint32_t)calibrationData.tref) << 8); + int32_t temp = 2000 + (((uint64_t)dt * calibrationData.tempsens) >> 23); + + int64_t offs = ((int64_t)calibrationData.off << 16) + + (((int64_t)calibrationData.tco * dt) >> 7); + int64_t sens = ((int64_t)calibrationData.sens << 15) + + (((int64_t)calibrationData.tcs * dt) >> 8); + + int64_t t2 = 0, off2 = 0, sens2 = 0; + + // Second order temperature compensation + if (temp < 2000) + { + t2 = (((int64_t)dt) * dt) >> 31; + off2 = 3 * (temp - 2000) * (temp - 2000); + sens2 = (7 * (temp - 2000) * (temp - 2000)) >> 3; + + if (temp < -1500) + { + sens2 = sens2 + 2 * (temp + 1500) * (temp + 1500); + } + } + else if (temp >= 4500) + { + sens2 = sens2 - (((temp - 4500) * (temp - 4500)) >> 3); + } + + temp = temp - t2; + offs = offs - off2; + sens = sens - sens2; + + float pressure = + (((((int64_t)rawPressure) * sens) / 2097152.0) - offs) / 32786.0; + + // Pressure in Pascal + float temp_ = temp / 100.0f; + + return MS5803Data(TimestampTimer::getTimestamp(), pressure, + lastTemperatureTimestamp, temp_); +} + +uint16_t MS5803::readReg(SPITransaction& transaction, uint8_t reg) +{ + uint8_t rcv[2]; + transaction.read(reg, rcv, 2); + uint16_t data = (rcv[0] << 8) | rcv[1]; + return data; +} \ No newline at end of file diff --git a/src/shared/sensors/MS5803/MS5803.h b/src/shared/sensors/MS5803/MS5803.h new file mode 100644 index 0000000000000000000000000000000000000000..66ff70ac84c51dec7c1cd0bfd7de644aed08ce10 --- /dev/null +++ b/src/shared/sensors/MS5803/MS5803.h @@ -0,0 +1,112 @@ +/* Copyright (c) 2015-2021 Skyward Experimental Rocketry + * Authors: Davide Benini, Matteo Piazzolla, Alain Carlucci, Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include <Debug.h> +#include <diagnostic/PrintLogger.h> +#include <drivers/spi/SPIDriver.h> +#include <sensors/MS5803/MS5803Data.h> +#include <sensors/Sensor.h> + +class MS5803 : public Sensor<MS5803Data> +{ +public: + enum FSMStates + { + STATE_INIT = 0, + STATE_SAMPLED_TEMP, + STATE_SAMPLED_PRESS + }; + + enum MS5803Registers : uint8_t + { + REG_RESET = 0x1E, + + // Conversion commands for pressure and output data resolution + REG_CONVERT_D1_256 = 0x40, // Conversion time: 0.48-0.54-0.60ms + REG_CONVERT_D1_512 = 0x42, // Conversion time: 0.95-1.06-1.17ms + REG_CONVERT_D1_1024 = 0x44, // Conversion time: 1.88-2.08-2.28ms + REG_CONVERT_D1_2048 = 0x46, // Conversion time: 3.72-4.13-4-54ms + REG_CONVERT_D1_4096 = 0x48, // Conversion time: 7.40-8.22-9.04ms + + // Conversion commands for temperature and output data resolution + REG_CONVERT_D2_256 = 0x50, // Conversion time: 0.48-0.54-0.60ms + REG_CONVERT_D2_512 = 0x52, // Conversion time: 0.95-1.06-1.17ms + REG_CONVERT_D2_1024 = 0x54, // Conversion time: 1.88-2.08-2.28ms + REG_CONVERT_D2_2048 = 0x56, // Conversion time: 3.72-4.13-4-54ms + REG_CONVERT_D2_4096 = 0x58, // Conversion time: 7.40-8.22-9.04ms + + // Read command for adc output + REG_ADC_READ = 0x00, + + REG_PROM_READ_MASK = 0xA0, + REG_PROM_SENS_MASK = 0x02, + REG_PROM_OFF_MASK = 0x04, + REG_PROM_TCS_MASK = 0x06, + REG_PROM_TCO_MASK = 0x08, + REG_PROM_TREF_MASK = 0x0A, + REG_PROM_TEMPSENS_MASK = 0x0C, + }; + + static constexpr uint8_t TIMEOUT = 5; + + MS5803(SPISlave spiSlave_, uint16_t temperatureDivider_ = 1); + MS5803(SPIBusInterface& spiBus_, miosix::GpioPin cs_, + SPIBusConfig spiConfig_, uint16_t temperatureDivider_ = 1); + + bool init() override; + + bool selfTest() override; + +private: + /** + * Implements a state machines composed of 3 states: + * 1. Command pressure sample + * 2. Read Pressure sample & command temperature sample + * 3. Read temperature sample & command pressure sample + * + * After the first call to sample() (state 1), the machine + * transitions between states 2 and 3: The effective sampling rate is half + * the rate at which this function is called. + * Example: call sample() at 100 Hz -> Pressure & Temperature sample + * Rate = 50 Hz + */ + MS5803Data sampleImpl() override; + + MS5803Data updateData(); + + uint16_t readReg(SPITransaction& spi, uint8_t reg); + + const SPISlave spiSlave; + + MS5803CalibrationData calibrationData; + uint16_t temperatureDivider; + + uint8_t deviceState = STATE_INIT; + uint32_t rawTemperature = 0; + uint32_t rawPressure = 0; + uint64_t lastTemperatureTimestamp = 0; + uint16_t tempCounter = 0; + + PrintLogger logger = Logging::getLogger("ms5803"); +}; diff --git a/src/shared/sensors/MS5803/MS5803Data.h b/src/shared/sensors/MS5803/MS5803Data.h new file mode 100644 index 0000000000000000000000000000000000000000..0d7afe0e146d079624df50d9af1c3874a9dbf757 --- /dev/null +++ b/src/shared/sensors/MS5803/MS5803Data.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2015-2021 Skyward Experimental Rocketry + * Author: Luca Erbetta, Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +/** + * @brief MS5803 calibration data. See page 13 of datasheet for more details. + */ +struct MS5803CalibrationData +{ + uint16_t sens = 0; + uint16_t off = 0; + uint16_t tcs = 0; + uint16_t tco = 0; + uint16_t tref = 0; + uint16_t tempsens = 0; +}; + +struct MS5803Data : public PressureData, TemperatureData +{ + + MS5803Data() : PressureData{0, 0.0}, TemperatureData{0, 0.0} {} + + MS5803Data(uint64_t pressureTimestamp, float pressure, + uint64_t temperatureTimestamp, float temperature) + : PressureData{pressureTimestamp, pressure}, TemperatureData{ + temperatureTimestamp, + temperature} + { + } + + static std::string header() + { + return "press_timestamp,press,temp_timestamp,temp\n"; + } + + void print(std::ostream& os) const + { + os << press_timestamp << "," << press << "," << temp_timestamp << "," + << temp << "\n"; + } +}; diff --git a/src/shared/sensors/MS580301BA07/MS580301BA07.h b/src/shared/sensors/MS580301BA07/MS580301BA07.h deleted file mode 100644 index b52b1474f8b0e74093602ea0b6517bbf3a3d30ac..0000000000000000000000000000000000000000 --- a/src/shared/sensors/MS580301BA07/MS580301BA07.h +++ /dev/null @@ -1,244 +0,0 @@ -/* MS580301BA07 Driver - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Davide Benini, Matteo Michele Piazzolla, Alain Carlucci - * - * 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. - */ - -#pragma once - -#include <drivers/BusTemplate.h> -#include "../Sensor.h" -#include "MS580301BA07Data.h" -#include "Debug.h" - -// TODO second order temperature compensation -template <class Bus> -class MS580301BA07 : public PressureSensor, public TemperatureSensor -{ - -public: - /* Class constructor. Reset lastPressure and lastTemperature */ - MS580301BA07() - { - memset(&cd, 0, sizeof(calibration_data)); - mLastTemp = 0; - mLastPressure = 0; - } - - ~MS580301BA07() {} - - bool init() - { - int timeout = 10; - do - { - Bus::read(RESET_DEV); - - miosix::Thread::sleep(3); - - cd.sens = readReg(PROM_READ_MASK | PROM_SENS_MASK); - if (cd.sens == 0){ - miosix::Thread::sleep(1); - TRACE("Could not read cd.sens\n"); - } - } while (cd.sens == 0 && --timeout > 0); - - if (timeout <= 0) - { - last_error = ERR_RESET_TIMEOUT; - return false; - } - - cd.off = readReg(PROM_READ_MASK | PROM_OFF_MASK); - cd.tcs = readReg(PROM_READ_MASK | PROM_TCS_MASK); - cd.tco = readReg(PROM_READ_MASK | PROM_TCO_MASK); - cd.tref = readReg(PROM_READ_MASK | PROM_TREF_MASK); - cd.tempsens = readReg(PROM_READ_MASK | PROM_TEMPSENS_MASK); - - TRACE("off: %d, tcs: %d, tco: %d, tref: %d, tsens: %d\n", (int)cd.off, - (int)cd.tcs, (int)cd.tco, (int)cd.tref, (int)cd.tempsens); - - mStatus = 0; - - return true; - } - - bool selfTest() { return false; } - - /** - * Implements a state machines composed of 3 states: - * 1. Command pressure sample - * 2. Read Pressure sample & command temperature sample - * 3. Read temperature sample & command pressure sample - * - * After the first call to onSimpleUpdate() (state 1), the machine - * transitions between states 2 and 3: The effectoive sampling rate is half - * the rate at which this function is called. - * Example: call onSimpleUpdate() at 100 Hz -> Pressure & Temperature sample - * Rate = 50 Hz - */ - bool onSimpleUpdate() - { - uint8_t rcvbuf[3]; - uint32_t temperature = 0; - - switch (mStatus) - { - case STATE_INIT: - Bus::write(CONVERT_D1_4096); - mStatus = STATE_SAMPLED_PRESSURE; - case STATE_SAMPLED_PRESSURE: - Bus::read_low(ADC_READ, rcvbuf, 3); - mInternalPressure = rcvbuf[2] | ((uint32_t)rcvbuf[1] << 8) | - ((uint32_t)rcvbuf[0] << 16); - - Bus::write(CONVERT_D2_4096); // Begin temperature sampling - mStatus = STATE_SAMPLED_TEMPERATURE; - - break; - case STATE_SAMPLED_TEMPERATURE: - Bus::read_low(ADC_READ, rcvbuf, 3); - - // TODO use swapBytes - temperature = (uint32_t)rcvbuf[2] | ((uint32_t)rcvbuf[1] << 8) | - ((uint32_t)rcvbuf[0] << 16); - - updateData(mInternalPressure, temperature); - Bus::write(CONVERT_D1_4096); // Begin pressure sampling - mStatus = STATE_SAMPLED_PRESSURE; - - break; - } - - return true; - } - - MS5803Data getData() { return dataStruct; } - - enum FSM_State - { - STATE_INIT = 0, - STATE_SAMPLED_PRESSURE = 1, - STATE_SAMPLED_TEMPERATURE = 2 - }; - - uint8_t getState() { return mStatus; } - -private: - static constexpr uint8_t TIMEOUT = 5; - uint8_t mStatus; - uint32_t mInternalPressure; - - MS5803Data dataStruct; - - void updateData(uint32_t pressure, uint32_t temperature) - { - int32_t dt = temperature - (((uint32_t)cd.tref) << 8); - int32_t temp = 2000 + (((uint64_t)dt * cd.tempsens) >> 23); - - int64_t offs = ((int64_t)cd.off << 16) + (((int64_t)cd.tco * dt) >> 7); - int64_t sens = ((int64_t)cd.sens << 15) + (((int64_t)cd.tcs * dt) >> 8); - - int64_t t2 = 0, off2 = 0, sens2 = 0; - - // Second order temperature compensation - if (temp < 2000) - { - t2 = (((int64_t)dt) * dt) >> 31; - off2 = 3 * (temp - 2000) * (temp - 2000); - sens2 = (7 * (temp - 2000) * (temp - 2000)) >> 3; - - if (temp < -1500) - { - sens2 = sens2 + 2 * (temp + 1500) * (temp + 1500); - } - } - else if (temp >= 4500) - { - sens2 = sens2 - (((temp - 4500) * (temp - 4500)) >> 3); - } - - temp = temp - t2; - offs = offs - off2; - sens = sens - sens2; - - int64_t ttemp = ((int64_t)pressure) * sens; - int32_t pres = ((ttemp >> 21) - offs) >> 15; - - mLastTemp = temp / 100.0f; - mLastPressure = pres; - - dataStruct.raw_temp = temperature; - dataStruct.temp = mLastTemp; - dataStruct.raw_press = pressure; - dataStruct.pressure = mLastPressure; - dataStruct.timestamp = miosix::getTick(); - } - - typedef struct - { - uint16_t sens; - uint16_t off; - uint16_t tcs; - uint16_t tco; - uint16_t tref; - uint16_t tempsens; - } calibration_data; - - calibration_data cd; - - uint16_t readReg(uint8_t reg) - { - uint8_t rcv[2]; - Bus::read(reg, rcv, 2); - uint16_t data = (rcv[0] << 8) | rcv[1]; - return data; - } - - // clang-format off - enum eRegisters - { - RESET_DEV = 0x1E, - - CONVERT_D1_256 = 0x40, - CONVERT_D1_512 = 0x42, - CONVERT_D1_1024 = 0x44, - CONVERT_D1_2048 = 0x46, - CONVERT_D1_4096 = 0x48, - - CONVERT_D2_256 = 0x50, - CONVERT_D2_512 = 0x52, - CONVERT_D2_1024 = 0x54, - CONVERT_D2_2048 = 0x56, - CONVERT_D2_4096 = 0x58, - - ADC_READ = 0x00, - - PROM_READ_MASK = 0xA0, - PROM_SENS_MASK = 0x02, - PROM_OFF_MASK = 0x04, - PROM_TCS_MASK = 0x06, - PROM_TCO_MASK = 0x08, - PROM_TREF_MASK = 0x0A, - PROM_TEMPSENS_MASK = 0x0C, - }; - // clang-format on -}; diff --git a/src/shared/sensors/Sensor.h b/src/shared/sensors/Sensor.h index 5ab85199cab91f4c1584d3b808852f323c3549a0..5dedcd0d15527bcdce526e90b8c99f8b559ff321 100644 --- a/src/shared/sensors/Sensor.h +++ b/src/shared/sensors/Sensor.h @@ -1,7 +1,5 @@ -/* Sensors Base Classes - * - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci +/* Copyright (c) 2016-2020 Skyward Experimental Rocketry + * Authors: Alain Carlucci, Luca Conterio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,160 +13,140 @@ * * 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 + * 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. */ -#ifndef SENSORS_H -#define SENSORS_H -#include <Common.h> -#include <drivers/spi/SensorSpi.h> -#include <math/Quaternion.h> -#include <math/Vec3.h> - -/** Sensors class diagram - * ________ - * | Sensor | <- Sensor parent - * _______/ -------- \________ - * ____/_______ _|__________ __\_______ - *| GyroSensor | | ...Sensor | | AnySensor| <- Virtual childs - * ------------ ------------ ---------- - * | _______/ \ / - * __|____/_ __\_____/_ - * | ABC1234 | | LOL12345 | <- You write these - * --------- ---------- +#pragma once + +#include <array> +#include <type_traits> + +#include "SensorData.h" + +/** + * @brief Check that a given type has a method called `getData()` and that the + * return type of this method is a subclass of the expected data type. */ +template <class T, class ExpectedDataType> +struct checkIfProduces + : std::is_base_of<ExpectedDataType, + typename std::remove_reference< + decltype(std::declval<T>().getLastSample())>::type> +{ +}; -class Sensor +/** + * @brief Base abstract class for sensor drivers. + */ +class AbstractSensor { +protected: + SensorErrors last_error = SensorErrors::NO_ERRORS; + public: - /** Here the code to initialize this sensor */ - virtual bool init() { return true; }; - - /** Self test code - * It should return a boolean: - * True = sensor ok - * False = sensor ko, write into last_error the error code. - * Anyone should be able to call getLastError() and read the error. - */ - virtual bool selfTest() = 0; + virtual ~AbstractSensor() {} - virtual std::vector<SPIRequest> buildDMARequest() - { - // printf("** SENSOR::buildDMARequest **\n"); - return std::vector<SPIRequest>(); - } + /** + * @brief Initialize the sensor. + * @return boolean value indicating whether the operation succeded or not + */ + virtual bool init() = 0; - virtual void onDMAUpdate(const SPIRequest& req) - { - (void)req; - // printf("** SENSOR::onDMAUpdate **\n"); - } + /** + * @brief Check if the sensor is working. + * @return boolean indicating whether the sensor is correctly working or not + */ + virtual bool selfTest() = 0; /** - * This method is called once every N msec, read new values and - * store them in local variables. - * - * You should check getLastError() if this function returns false. + * @brief Sample the sensor. */ - virtual bool onSimpleUpdate() = 0; - - /** Return last error code */ - uint8_t getLastError() const { return last_error; } - - /** Errors (for each subsensor) */ - // clang-format off - enum eErrors - { - ERR_NOT_ME = 0x01, - ERR_RESET_TIMEOUT = 0x02, - ERR_BUS_FAULT = 0x03, // A bus op has encountered an error - ERR_X_SELFTEST_FAIL = 0x04, - ERR_Y_SELFTEST_FAIL = 0x05, - ERR_Z_SELFTEST_FAIL = 0x06, - ERR_ACCEL_SELFTEST = 0x07, - ERR_GYRO_SELFTEST = 0x08, - ERR_CANT_TALK_TO_CHILD = 0x09, // MPU9250 can't talk to AK8963 - }; - // clang-format on + virtual void sample() = 0; -protected: - uint8_t last_error = 0; + /** + * @brief Get last error for debugging purposes. Avoid silent fails. + * @return the last error recorded by this sensor + */ + SensorErrors getLastError() { return last_error; }; }; -class GyroSensor : public virtual Sensor +/** + * @brief Base sensor class with has to be extended by any sensor driver. + * + * A sensor driver can define a custom data structure extending any + * combination of base sensors data structures, defined in `SensorData.h`. + */ +template <typename Data> +class Sensor : public virtual AbstractSensor { -public: - const Vec3* gyroDataPtr() const { return &mLastGyro; } - protected: - Vec3 mLastGyro; -}; + Data last_sample; -class AccelSensor : public virtual Sensor -{ -public: - const Vec3* accelDataPtr() const { return &mLastAccel; } - -protected: - Vec3 mLastAccel; -}; + /** + * @brief Read a data sample from the sensor. + * In case of errors, the method should return the last + * available correct sample. + * @return sensor data sample + */ + virtual Data sampleImpl() = 0; -class CompassSensor : public virtual Sensor -{ public: - const Vec3* compassDataPtr() const { return &mLastCompass; } + virtual ~Sensor() {} -protected: - Vec3 mLastCompass; -}; - -class TemperatureSensor : public virtual Sensor -{ -public: - const float* tempDataPtr() const { return &mLastTemp; } + void sample() override { last_sample = sampleImpl(); } -protected: - float mLastTemp; + /** + * @return last available sample from this sensor + */ + virtual const Data& getLastSample() { return last_sample; } }; -class HumiditySensor : public virtual Sensor +/** + * @brief Interface for sensor that implement a FIFO. + */ +template <typename Data, uint32_t FifoSize> +class SensorFIFO : public Sensor<Data> { -public: - const float* humidityDataPtr() const { return &mLastHumidity; } - protected: - float mLastHumidity; -}; + std::array<Data, FifoSize> last_fifo; + uint8_t last_fifo_level = 1; /**< number of samples in last_fifo */ -class PressureSensor : public virtual Sensor -{ -public: - const float* pressureDataPtr() const { return &mLastPressure; } + uint64_t last_interrupt_us = 0; /**< last interrupt timestamp */ + uint64_t dt_interrupt = 0; /**< delta between previous interrupt timestamp + and the last received one */ -protected: - float mLastPressure; -}; - -class AltitudeSensor : public virtual Sensor -{ public: - const float* altitudeDataPtr() const { return &mLastAltitude; } + /** + * @return last FIFO sampled from the sensor + */ + const std::array<Data, FifoSize>& getLastFifo() { return last_fifo; } -protected: - float mLastAltitude; -}; + /** + * @param i index of the requested item inside the FIFO + * + * @return the i-th element of the FIFO + */ + const Data& getFifoElement(uint32_t i) const { return last_fifo[i]; } -class DebugIntSensor : public virtual Sensor -{ -public: - const int* debugIntPtr() const { return &mDebugInt; } + /** + * @return number of elements in the last FIFO sampled from the sensor + */ + uint8_t getLastFifoSize() const { return last_fifo_level; } -protected: - int mDebugInt; + /** + * @brief Called by the interrupt handling routine: provides the timestamp + * of the last interrupt (if FIFO is disabled) or the last watermark + * interrupt (if FIFO enabled) + * + * @param ts Timestamp of the lasts interrupt, in microseconds + */ + inline virtual void IRQupdateTimestamp(uint64_t ts) + { + dt_interrupt = ts - last_interrupt_us; + last_interrupt_us = ts; + } }; - -#endif /* ifndef SENSORS_H */ diff --git a/src/shared/sensors/SensorData.h b/src/shared/sensors/SensorData.h new file mode 100644 index 0000000000000000000000000000000000000000..06ea8ffdc77656261dfdec4f6d8a879835de36ad --- /dev/null +++ b/src/shared/sensors/SensorData.h @@ -0,0 +1,142 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#pragma once + +#include <ostream> + +/** + * @brief Generic error codes that a sensor can generate. + * + * Sensors can extend this enum by defining a new set of errors, + * starting from END_OF_BASE_ERRORS. + */ +enum SensorErrors : uint8_t +{ + NO_ERRORS = 0, + INVALID_WHOAMI = 1, + INIT_FAIL = 2, + NOT_INIT = 3, // if some method called before init() + ALREADY_INIT = 4, // if init() called multiple times + SELF_TEST_FAIL = 5, + BUS_FAULT = 6, + NO_NEW_DATA = 7, // no new data available from the sensor + INVALID_FIFO_INDEX = 8, + DMA_ERROR = 9, + END_OF_BASE_ERRORS = 10 // used to extend this enum +}; + +/** + * @brief Structure to handle sensor data timestamp. + */ +struct TimestampData +{ + uint64_t timestamp; +}; + +/** + * @brief Structure to handle temperature data. + */ +struct TemperatureData +{ + uint64_t temp_timestamp; + float temp; +}; + +/** + * @brief Structure to handle pressure data. + */ +struct PressureData +{ + uint64_t press_timestamp; + float press; +}; + +/** + * @brief Structure to handle humidity data. + */ +struct HumidityData +{ + uint64_t humid_timestamp; + float humid; +}; + +/** + * @brief Structure to handle accelerometer data. + */ +struct AccelerometerData +{ + uint64_t accel_timestamp; + float accel_x; + float accel_y; + float accel_z; +}; + +/** + * @brief Structure to handle gyroscope data. + */ +struct GyroscopeData +{ + uint64_t gyro_timestamp; + float gyro_x; + float gyro_y; + float gyro_z; +}; + +/** + * @brief Structure to handle magnetometer data. + */ +struct MagnetometerData +{ + uint64_t mag_timestamp; + float mag_x; + float mag_y; + float mag_z; +}; + +/** + * @brief Structure to handle GPS data. + */ +struct GPSData +{ + uint64_t gps_timestamp; + float latitude; /**< [deg] */ + float longitude; /**< [deg] */ + float height; /**< [m] */ + float velocity_north; /**< [m/s] */ + float velocity_east; /**< [m/s] */ + float velocity_down; /**< [m/s] */ + float speed; /**< [m/s] */ + float track; /**< [deg] */ + uint8_t num_satellites; /**< [1] */ + bool fix; +}; + +/** + * @brief Structure to handle ADC data. + */ +struct ADCData +{ + uint64_t adc_timestamp; + uint8_t channel_id; + float voltage; +}; \ No newline at end of file diff --git a/src/shared/sensors/SensorInfo.h b/src/shared/sensors/SensorInfo.h new file mode 100644 index 0000000000000000000000000000000000000000..0bc2ea6b603573df0018013ab54a940f239d7e3a --- /dev/null +++ b/src/shared/sensors/SensorInfo.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#pragma once + +#include <atomic> +#include <functional> + +/** + * @brief Sensors information struct needed by the SensorManager. + * + * This structure contains the sampling period of a sensor, + * the function to be called after the sampling (callback) and + * two boolean values indicating if the sensor uses DMA and if + * the sensor has to be sampled (is enabled). + */ +struct SensorInfo +{ + std::string id; + uint32_t period; // Period in ms + std::function<void()> callback; + bool is_dma; + bool is_enabled; + bool is_initialized; + + SensorInfo(std::string id, uint32_t period, std::function<void()> callback, + bool is_dma, bool is_enabled) + : id(id), period(period), callback(callback), is_dma(is_dma), + is_enabled(is_enabled), is_initialized(false) + { + } + + SensorInfo() + : id(""), period(0), callback([]() {}), is_dma(false), + is_enabled(false), is_initialized(false) + { + } +}; \ No newline at end of file diff --git a/src/shared/sensors/SensorManager.cpp b/src/shared/sensors/SensorManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dcb900d945dfe1b855ee892c61c888229260c38d --- /dev/null +++ b/src/shared/sensors/SensorManager.cpp @@ -0,0 +1,260 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#include "SensorManager.h" + +#include <Debug.h> + +SensorManager::SensorManager(const SensorMap_t& sensors_map) + : SensorManager(new TaskScheduler(), sensors_map) +{ +} + +SensorManager::SensorManager(TaskScheduler* scheduler, + const SensorMap_t& sensors_map) + : scheduler(scheduler) +{ + if (!init(sensors_map)) + { + LOG_ERR(logger, "Initialization failed"); + } +} + +SensorManager::~SensorManager() +{ + for (auto s : samplers) + { + delete s; + } +} + +bool SensorManager::start() +{ + return scheduler->start() && sensors_init_result; +} + +void SensorManager::stop() { scheduler->stop(); } + +void SensorManager::enableSensor(AbstractSensor* sensor) +{ + if (samplers_map.find(sensor) != samplers_map.end()) + { + samplers_map[sensor]->toggleSensor(sensor, true); + } + else + { + LOG_ERR(logger, "Can't enable sensor {} it does not exist", + static_cast<void*>(sensor)); + } +} + +void SensorManager::disableSensor(AbstractSensor* sensor) +{ + if (samplers_map.find(sensor) != samplers_map.end()) + { + samplers_map[sensor]->toggleSensor(sensor, false); + } + else + { + LOG_ERR(logger, "Can't disable sensor {}, it does not exist", + static_cast<void*>(sensor)); + } +} + +void SensorManager::enableAllSensors() +{ + for (auto s : samplers) + { + s->enableAllSensors(); + } +} + +void SensorManager::disableAllSensors() +{ + for (auto s : samplers) + { + s->disableAllSensors(); + } +} + +const SensorInfo SensorManager::getSensorInfo(AbstractSensor* sensor) +{ + if (samplers_map.find(sensor) != samplers_map.end()) + { + return samplers_map[sensor]->getSensorInfo(sensor); + } + + LOG_ERR(logger, "Sensor {} not found, can't return SensorInfo", + static_cast<void*>(sensor)); + + return SensorInfo{}; +} + +const vector<TaskStatResult> SensorManager::getSamplersStats() +{ + return scheduler->getTaskStats(); +} + +bool SensorManager::init(const SensorMap_t& sensors_map) +{ + uint8_t current_sampler_id = getFirstTaskID(); + + if (current_sampler_id != 0) + { + LOG_INFO(logger, "Task scheduler not empty: starting from task ID {}", + current_sampler_id); + } + + for (auto it = sensors_map.begin(); it != sensors_map.end(); it++) + { + AbstractSensor* sensor = it->first; + SensorInfo sensor_info = it->second; + + // avoid adding sensors that fail to be initalized + if (!initSensor(sensor)) + { + sensor_info.is_enabled = false; // disable the failing sensor + + sensors_init_result = false; + + LOG_ERR( + logger, + "Failed to initialize sensor {} -> Error: {} (period: {} ms)", + sensor_info.id.c_str(), sensor->getLastError(), + sensor_info.period); + } + else + { + sensor_info.is_initialized = true; + } + + // add sensor even if not initialized correctly, its is_initialized info + // field will be false + LOG_DEBUG(logger, "Adding {} -> period: {} ms, enabled = {}", + sensor_info.id.c_str(), sensor_info.period, + sensor_info.is_enabled); + + // check if a sampler with the same sampling period and the same + // type exists + bool found = false; + for (auto s : samplers) + { + if (sensor_info.period == s->getSamplingPeriod() && + sensor_info.is_dma == s->isDMA()) + { + s->addSensor(sensor, sensor_info); + samplers_map[sensor] = s; + found = true; + } + } + + if (!found) + { + // a sampler with the required period does not exist yet + SensorSampler* new_sampler = createSampler( + current_sampler_id, sensor_info.period, sensor_info.is_dma); + + new_sampler->addSensor(sensor, sensor_info); + + samplers.push_back(new_sampler); + samplers_map[sensor] = new_sampler; + + if (current_sampler_id == MAX_TASK_ID) + { + LOG_WARN(logger, + "Max task ID (255) reached in task scheduler, IDs " + "will start again from 0"); + } + + current_sampler_id++; + } + } + + initScheduler(); + + return sensors_init_result; +} + +bool SensorManager::initSensor(AbstractSensor* sensor) +{ + return sensor->init() && sensor->selfTest(); +} + +void SensorManager::initScheduler() +{ + // sort the vector to have lower period samplers + // (higher frequency) inserted before + // higher period ones into the TaskScheduler + std::sort(samplers.begin(), samplers.end(), + [](auto& left, auto& right) { + return left->getSamplingPeriod() < right->getSamplingPeriod(); + }); + + uint64_t start_time = miosix::getTick() + 10; + + // add all the samplers to the scheduler + for (auto& s : samplers) + { + function_t sampler_update_function = + bind(&SensorSampler::sampleAndCallback, s); + + scheduler->add(sampler_update_function, s->getSamplingPeriod(), + s->getID(), start_time); + } +} + +uint8_t SensorManager::getFirstTaskID() +{ + std::vector<TaskStatResult> tasks_stats = scheduler->getTaskStats(); + + if (tasks_stats.empty()) + { + return 0; + } + + uint8_t max_id = 0; + for (auto& stats : tasks_stats) + { + if (stats.id > max_id) + { + max_id = stats.id; + } + } + + return max_id + 1; +} + +SensorSampler* SensorManager::createSampler(uint8_t id, uint32_t period, + bool is_dma) +{ + LOG_DEBUG(logger, "Creating Sampler {} with sampling period {} ms", id, + period); + + if (is_dma) + { + return new DMASensorSampler(id, period); + } + else + { + return new SimpleSensorSampler(id, period); + } +} \ No newline at end of file diff --git a/src/shared/sensors/SensorManager.h b/src/shared/sensors/SensorManager.h new file mode 100755 index 0000000000000000000000000000000000000000..63e3207eb1ea464baee56ba69303efb6a23ff8b2 --- /dev/null +++ b/src/shared/sensors/SensorManager.h @@ -0,0 +1,181 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#pragma once + +#include <diagnostic/PrintLogger.h> + +#include "SensorInfo.h" +#include "SensorSampler.h" +#include "scheduler/TaskScheduler.h" +// #include "diagnostic/PrintLogger.h" + +using namespace std; + +/** + * @brief The SensorManager class manages all the sensors connected to the + * Board. + * + * Sensors are grouped in various SensorSampler objects by "type" (if they use + * DMA or not) and "sampling period". These SensorSampler objects are then + * added to the scheduler that manages the timings for the sampling. + */ +class SensorManager +{ +public: + using function_t = function<void()>; + using SensorMap_t = map<AbstractSensor*, SensorInfo>; + + /** + * @brief Constructor. + * + * @param sensors_map map containing references to the sensors as keys, + * and objects of type SensorInfo as values. + */ + SensorManager(const SensorMap_t& sensors_map); + + /** + * @brief Constructor taking an external TaskScheduler object. + * + * @param sensors_map map containing references to the sensors as keys, + * and objects of type SensorInfo as values. + */ + SensorManager(TaskScheduler* scheduler, const SensorMap_t& sensors_map); + + /** + * @brief Destructor. + * + * Deallocates samplers (through the samplers vector). + */ + ~SensorManager(); + + /** + * @brief Start the sensor manager. + */ + bool start(); + + /** + * @brief Stop the sensor manager. + */ + void stop(); + + /** + * @brief Enable sampling for the passed sensor. + * + * @param sensor the sensor to be enabled + */ + void enableSensor(AbstractSensor* sensor); + + /** + * @brief Disable sampling for the passed sensor. + * + * @param sensor the sensor to be disabled + */ + void disableSensor(AbstractSensor* sensor); + + /** + * @brief Enable sampling for all the sensors. + */ + void enableAllSensors(); + + /** + * @brief Disable sampling for all the sensors. + */ + void disableAllSensors(); + + /** + * @return the information related to the given sensor + */ + const SensorInfo getSensorInfo(AbstractSensor* sensor); + + /** + * @return vector of statistics, one for each sampler, taken from the + * scheduler + */ + const vector<TaskStatResult> getSamplersStats(); + +private: + /** + * @brief Copy constructor. Deleted. + */ + SensorManager(const SensorManager&) = delete; + + /** + * @brief Initializes samplers vector and sensors_map with the given sensors + * map, giving incremental IDs to SensorSampler objects. + * In case a TaskScheduler was passed in the costructor, + * the SensorManager will assign to SensorSamplers incremental IDs + * starting from the maximum among the tasks already existing + * in the TaskScheduler. + * + * @param sensors_map map containing sensors and their respective + * information for the sampling + */ + bool init(const SensorMap_t& sensors_map); + + /** + * @brief Initialize a sensor and run its self-test. + * + * @param sensor the sensor to be initialized + */ + bool initSensor(AbstractSensor* sensor); + + /** + * @brief Initialize scheduler by adding all the SensorSamplers tasks. + */ + void initScheduler(); + + /** + * @brief Avoid creating duplicate IDs for tasks in case the scheduler + * is received from outside. + * + * @return maximum ID among those assigned to tasks in the scheduler. + */ + uint8_t getFirstTaskID(); + + /** + * @brief Create a sampler object according to the fact that it needs to use + * DMA or not. + * + * @param id new sampler's identifier + * @param period sampling period of the new sampler + * @param is_dma indicate if the sampler manages sensors that use DMA + * + * @return pointer to the newly created sampler + */ + SensorSampler* createSampler(uint8_t id, uint32_t period, bool is_dma); + + const uint8_t MAX_TASK_ID = 255; /**< max id for tasks in the scheduler */ + + TaskScheduler* scheduler; /**< scheduler to update the samplers at the + correct period */ + + vector<SensorSampler*> samplers; /**< vector of all the samplers (unique) */ + + map<AbstractSensor*, SensorSampler*> + samplers_map; /**< map each sensor to the corresponding sampler */ + + bool sensors_init_result = + true; /**< true if all the sensors are initialized correctly */ + + PrintLogger logger = Logging::getLogger("sensormanager"); +}; diff --git a/src/shared/sensors/SensorSampler.cpp b/src/shared/sensors/SensorSampler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfa4e892c604efdaa569ef4272f02b62adc4909c --- /dev/null +++ b/src/shared/sensors/SensorSampler.cpp @@ -0,0 +1,140 @@ +/* Copyright (c) 2017-2020 Skyward Experimental Rocketry + * Authors: Alain Carlucci, Luca Conterio + * + * 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. + */ + +#include "SensorSampler.h" +// #include <diagnostic/PrintLogger.h> + +using namespace std; + +SensorSampler::SensorSampler(uint8_t id, uint32_t period, bool is_dma) + : id(id), period(period), is_dma(is_dma) +{ +} + +SensorSampler::~SensorSampler() { sensors.clear(); } + +void SensorSampler::sampleAndCallback() +{ + for (auto& s : sensors) + { + // sample only if that sensor is enabled + if (s.second.is_enabled && s.second.is_initialized) + { + sampleSensor(s.first); + s.second.callback(); + } + } +} + +void SensorSampler::toggleSensor(AbstractSensor* sensor, bool is_en) +{ + for (auto& s : sensors) + { + if (s.first == sensor) + { + s.second.is_enabled = is_en; + LOG_DEBUG(logger, "Sampler {}, toggle Sensor {} ---> enabled = {}", + getID(), static_cast<void*>(sensor), s.second.is_enabled); + break; + } + } +} + +void SensorSampler::enableAllSensors() +{ + for (auto& s : sensors) + { + s.second.is_enabled = true; + } +} + +void SensorSampler::disableAllSensors() +{ + for (auto& s : sensors) + { + s.second.is_enabled = false; + } +} + +bool SensorSampler::isDMA() { return is_dma; } + +uint8_t SensorSampler::getID() { return id; } + +uint32_t SensorSampler::getSamplingPeriod() { return period; } + +unsigned int SensorSampler::getNumSensors() { return sensors.size(); } + +const SensorInfo SensorSampler::getSensorInfo(AbstractSensor* sensor) +{ + for (auto it = sensors.begin(); it != sensors.end(); ++it) + { + if (it->first == sensor) + { + return it->second; + } + } + + LOG_ERR(logger, "Sampler {}: sensor {} not found", this->id, + static_cast<void*>(sensor)); + + return SensorInfo{}; +} + +// simple sampler +SimpleSensorSampler::SimpleSensorSampler(uint8_t id, uint32_t period) + : SensorSampler(id, period, false) +{ +} + +SimpleSensorSampler::~SimpleSensorSampler() {} + +void SimpleSensorSampler::addSensor(AbstractSensor* sensor, + SensorInfo sensor_info) +{ + sensors.push_back(make_pair(sensor, sensor_info)); +} + +void SimpleSensorSampler::sampleSensor(AbstractSensor* sensor) +{ + sensor->sample(); +} + +// DMA sampler +DMASensorSampler::DMASensorSampler(uint8_t id, uint32_t period) + : SensorSampler(id, period, true) +{ +} + +DMASensorSampler::~DMASensorSampler() {} + +void DMASensorSampler::addSensor(AbstractSensor* sensor, SensorInfo sensor_info) +{ + sensors.push_back(make_pair(sensor, sensor_info)); +} + +/* + TODO : Handle sensors that use DMA +*/ +void DMASensorSampler::sampleSensor(AbstractSensor* sensor) +{ + sensor->sample(); +} \ No newline at end of file diff --git a/src/shared/sensors/SensorSampler.h b/src/shared/sensors/SensorSampler.h new file mode 100644 index 0000000000000000000000000000000000000000..534ef8945ad0d149b10bd34e63824ad83c796f4b --- /dev/null +++ b/src/shared/sensors/SensorSampler.h @@ -0,0 +1,159 @@ +/* Copyright (c) 2017-2020 Skyward Experimental Rocketry + * Authors: Alain Carlucci, Luca Conterio + * + * 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. + */ + +#pragma once + +#include <Common.h> +#include <diagnostic/PrintLogger.h> + +#include "Sensor.h" +#include "SensorInfo.h" + +using namespace std; + +/** + * @brief Virtual sensor sampler class. + * + * When requested, a SensorSampler samples all the enabled sensors it manages. + * After sampling a sensor, the SensorSampler calls the callback associated + * with that sensor, where these samples can be processed. + */ +class SensorSampler +{ +public: + /** + * @param id sampler identifier + * @param period period at which the sampler performs sensors update + * @param is_dma indicate if the sampler manages DMA sensors or not + */ + SensorSampler(uint8_t id, uint32_t period, bool is_dma); + + virtual ~SensorSampler(); + + /** + * @brief Add a sensor to the sensors map. + * + * @param sensor the sensor to be added + */ + virtual void addSensor(AbstractSensor* sensor, SensorInfo sensor_info) = 0; + + /** + * @brief Enabled or disable a sensor. + * + * @param sensor the sensor to be toggled (enabled/disabled) + * @param is_en bool value to be set in the sensor info to indicate if + * the sensor will be enabled or disabled + */ + void toggleSensor(AbstractSensor* sensor, bool is_en); + + /** + * @brief Enable sampling for all the sensors. + */ + void enableAllSensors(); + + /** + * @brief Disable sampling for all the sensors. + */ + void disableAllSensors(); + + /** + * @brief For each sensor, sample it and call the corresponding callback. + */ + void sampleAndCallback(); + + /** + * @return the sampler's type + */ + bool isDMA(); + + /** + * @return the sampler's ID + */ + uint8_t getID(); + + /** + * @return the sampler's activation period + */ + uint32_t getSamplingPeriod(); + + /** + * @return the number of sensors assigned to this sampler + */ + unsigned int getNumSensors(); + + /** + * @return the information related to the given sensor + */ + const SensorInfo getSensorInfo(AbstractSensor* sensor); + +private: + /** + * @brief Perform the update of all the sensors in the sampler. + */ + virtual void sampleSensor(AbstractSensor* s) = 0; + + uint8_t id; /**< sampler id used in the task scheduler */ + uint32_t period; /**< sampler update/activation period */ + bool is_dma; /**< the sampler's type (if it uses DMA or not) */ + +protected: + vector<pair<AbstractSensor*, SensorInfo>> sensors; + + PrintLogger logger = Logging::getLogger("sensorsampler"); +}; + +/** + * @brief Sampler for simple sensors, those that are simply + * sampled by calling the sample() method. + */ +class SimpleSensorSampler : public virtual SensorSampler +{ +public: + SimpleSensorSampler(uint8_t id, uint32_t period); + + ~SimpleSensorSampler(); + + void addSensor(AbstractSensor* sensor, SensorInfo sensor_info) override; + + void sampleSensor(AbstractSensor* s) override; + +private: + SimpleSensorSampler(const SimpleSensorSampler&) = delete; +}; + +/** + * @brief Sampler for sensors that use DMA for updates. + */ +class DMASensorSampler : public virtual SensorSampler +{ +public: + DMASensorSampler(uint8_t id, uint32_t period); + + ~DMASensorSampler(); + + void addSensor(AbstractSensor* sensor, SensorInfo sensor_info) override; + + void sampleSensor(AbstractSensor* s) override; + +private: + DMASensorSampler(const DMASensorSampler&) = delete; +}; \ No newline at end of file diff --git a/src/shared/sensors/SensorSampling.h b/src/shared/sensors/SensorSampling.h deleted file mode 100644 index 06bb4b9eae810c7158b9d81da9d4dfbe0eb84398..0000000000000000000000000000000000000000 --- a/src/shared/sensors/SensorSampling.h +++ /dev/null @@ -1,99 +0,0 @@ -/* Copyright (c) 2017 Skyward Experimental Rocketry - * Author: Alain Carlucci - * - * 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. - */ -#ifndef SENSOR_SAMPLING_H -#define SENSOR_SAMPLING_H - -#include <Common.h> -#include <diagnostic/Log.h> -#include <drivers/spi/SensorSpi.h> - -#include "Sensor.h" - -class DMASensorSampler -{ -public: - DMASensorSampler() {} - ~DMASensorSampler() {} - - void AddSensor(Sensor* sensor) - { - std::vector<SPIRequest> requests = sensor->buildDMARequest(); - for (size_t i = 0; i < requests.size(); i++) - { - mRequests.push_back(requests[i]); - mSensors.push_back(sensor); - } - } - - void Update() - { - TRACE("Update\n"); - auto& driver = SPIDriver::instance(); - bool ret = driver.transaction(mRequests); - - if (ret) - for (size_t i = 0; i < mSensors.size(); i++) - mSensors[i]->onDMAUpdate(mRequests[i]); - - TRACE("Update end\n"); - - } - - void UpdateAndCallback(std::function<void()> onSampleUpdateCallback) - { - Update(); - TRACE("Update callback\n"); - - onSampleUpdateCallback(); - } - -private: - std::vector<Sensor*> mSensors; - std::vector<SPIRequest> mRequests; -}; - -class SimpleSensorSampler -{ -public: - SimpleSensorSampler() {} - ~SimpleSensorSampler() {} - - void AddSensor(Sensor* sensor) { mSensors.push_back(sensor); } - - void Update() - { - for (Sensor* s : mSensors) - s->onSimpleUpdate(); - } - - void UpdateAndCallback(std::function<void()> onSampleUpdateCallback) - { - Update(); - - onSampleUpdateCallback(); - } - -private: - std::vector<Sensor*> mSensors; -}; - -#endif /* ifndef SENSOR_SAMPLING_H */ diff --git a/src/shared/sensors/analog/battery/BatteryVoltageSensor.h b/src/shared/sensors/analog/battery/BatteryVoltageSensor.h new file mode 100644 index 0000000000000000000000000000000000000000..b04bafc73f028a5b266d0969b3f567e8d9cb3ffe --- /dev/null +++ b/src/shared/sensors/analog/battery/BatteryVoltageSensor.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#pragma once + +#include <sensors/Sensor.h> + +#include <functional> + +#include "BatteryVoltageSensorData.h" + +/** + * @brief Common class for battery voltage sensors + * + * It needs the conversion ratio, in order to convert the raw voltage to + * battery voltage. + */ +class BatteryVoltageSensor : public Sensor<BatteryVoltageSensorData> +{ +public: + static constexpr int MOVING_AVAERAGE_N = 20; + + BatteryVoltageSensor(std::function<ADCData()> getADCVoltage_, + float conversionCoeff_) + : getADCVoltage(getADCVoltage_), conversionCoeff(conversionCoeff_) + { + last_sample.bat_voltage = 0; + } + + bool init() override { return true; }; + + bool selfTest() override { return true; }; + + ///< Converts the adc voltage value to battery voltage + BatteryVoltageSensorData sampleImpl() override + { + ADCData adc_data = getADCVoltage(); + + if (last_sample.bat_voltage == 0) + { + last_sample.bat_voltage = adcToBatteryVoltage(adc_data.voltage); + } + + BatteryVoltageSensorData bat_data; + bat_data.adc_timestamp = adc_data.adc_timestamp; + bat_data.channel_id = adc_data.channel_id; + bat_data.voltage = adc_data.voltage; + + // Moving average + bat_data.bat_voltage = + last_sample.bat_voltage * MOVING_AVAERAGE_COMP_COEFF; + bat_data.bat_voltage += + adcToBatteryVoltage(adc_data.voltage) * MOVING_AVAERAGE_COEFF; + + return bat_data; + } + +private: + ///< Conversion function from adc volts to battery volts + float adcToBatteryVoltage(float voltage) + { + return voltage * conversionCoeff; + } + + ///< Function that returns the adc voltage + std::function<ADCData()> getADCVoltage; + + float conversionCoeff; + + static constexpr float MOVING_AVAERAGE_COEFF = 1 / (float)MOVING_AVAERAGE_N; + static constexpr float MOVING_AVAERAGE_COMP_COEFF = + 1 - MOVING_AVAERAGE_COEFF; +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/battery/BatteryVoltageSensorData.h b/src/shared/sensors/analog/battery/BatteryVoltageSensorData.h new file mode 100644 index 0000000000000000000000000000000000000000..2c3afba9b1570cf56323c51a20fd6309ee7ea95e --- /dev/null +++ b/src/shared/sensors/analog/battery/BatteryVoltageSensorData.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +/** + * @brief Structure to handle battery voltage data. + */ +struct BatteryVoltageSensorData : public ADCData +{ + float bat_voltage; + + static std::string header() + { + return "adc_timestamp,channel_id,voltage,bat_voltage\n"; + } + + void print(std::ostream& os) const + { + os << adc_timestamp << "," << (int)channel_id << "," << voltage << "," + << bat_voltage << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/current/CurrentSensor.h b/src/shared/sensors/analog/current/CurrentSensor.h new file mode 100644 index 0000000000000000000000000000000000000000..0c69d5ec214454bb3cdca5f33e61958c167bdb35 --- /dev/null +++ b/src/shared/sensors/analog/current/CurrentSensor.h @@ -0,0 +1,85 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#pragma once + +#include <sensors/Sensor.h> + +#include <functional> + +#include "CurrentSensorData.h" + +/** + * @brief Common class for current sense sensors + * + * It needs a transfer function to convert the read voltage into current. + */ +class CurrentSensor : public Sensor<CurrentSensorData> +{ +public: + static constexpr int MOVING_AVAERAGE_N = 20; + + CurrentSensor(std::function<ADCData()> getADCVoltage_, + std::function<float(float)> adcToCurrent_) + : getADCVoltage(getADCVoltage_), adcToCurrent(adcToCurrent_) + { + last_sample.current = 0; + } + + bool init() override { return true; }; + + bool selfTest() override { return true; }; + + ///< Converts the voltage value to pressure + CurrentSensorData sampleImpl() override + { + ADCData adc_data = getADCVoltage(); + + if (last_sample.current == 0) + { + last_sample.current = adcToCurrent(adc_data.voltage); + } + + CurrentSensorData current_data; + current_data.adc_timestamp = adc_data.adc_timestamp; + current_data.channel_id = adc_data.channel_id; + current_data.voltage = adc_data.voltage; + + // Moving average + current_data.current = last_sample.current * MOVING_AVAERAGE_COMP_COEFF; + current_data.current += + adcToCurrent(adc_data.voltage) * MOVING_AVAERAGE_COEFF; + + return current_data; + }; + +private: + ///< Function that returns the adc voltage + std::function<ADCData()> getADCVoltage; + + ///< Function that converts adc voltage to current + std::function<float(float)> adcToCurrent; + + static constexpr float MOVING_AVAERAGE_COEFF = 1 / (float)MOVING_AVAERAGE_N; + static constexpr float MOVING_AVAERAGE_COMP_COEFF = + 1 - MOVING_AVAERAGE_COEFF; +}; \ No newline at end of file diff --git a/src/shared/sensors/MS580301BA07/MS580301BA07Data.h b/src/shared/sensors/analog/current/CurrentSensorData.h similarity index 69% rename from src/shared/sensors/MS580301BA07/MS580301BA07Data.h rename to src/shared/sensors/analog/current/CurrentSensorData.h index e6c2613243c3d039c1783edb1ef8f528aff987eb..665e75f7afd56892d71ec9babc7ccb74f00ce99b 100644 --- a/src/shared/sensors/MS580301BA07/MS580301BA07Data.h +++ b/src/shared/sensors/analog/current/CurrentSensorData.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,33 +13,32 @@ * * 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 + * 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. */ + #pragma once -#include <ostream> +#include "sensors/SensorData.h" -struct MS5803Data +/** + * @brief Structure to handle current sense data. + */ +struct CurrentSensorData : public ADCData { - long long int timestamp; - - uint32_t raw_press; - float pressure; - uint32_t raw_temp; - float temp; + float current; static std::string header() { - return "timestamp,raw_temp,raw_press,temp,pressure\n"; + return "adc_timestamp,channel_id,voltage,current\n"; } void print(std::ostream& os) const { - os << timestamp << "," << raw_temp << "," << raw_press << "," << temp - << "," << pressure << "\n"; + os << adc_timestamp << "," << (int)channel_id << "," << voltage << "," + << current << "\n"; } -}; +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/AnalogPressureSensor.h b/src/shared/sensors/analog/pressure/AnalogPressureSensor.h new file mode 100644 index 0000000000000000000000000000000000000000..15c626681f8537efdbbf51ae29eb4f7a5c136c56 --- /dev/null +++ b/src/shared/sensors/analog/pressure/AnalogPressureSensor.h @@ -0,0 +1,91 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include <sensors/Sensor.h> + +#include <functional> + +/** + * @brief Common class for all analog pressure sensors + * + * All analog pressure sensors have a transfer function to convert the read + * voltage into pressure and a range within which they operate. + */ +template <typename AnalogPressureData> +class AnalogPressureSensor : public Sensor<AnalogPressureData> +{ +public: + AnalogPressureSensor(std::function<ADCData()> getSensorVoltage_, + const float V_SUPPLY_ = 5.0, + const float maxPressure_ = 0, + const float minPressure_ = 0) + : getSensorVoltage(getSensorVoltage_), V_SUPPLY(V_SUPPLY_), + maxPressure(maxPressure_), minPressure(minPressure_) + { + } + + bool init() override { return true; }; + + bool selfTest() override { return true; }; + + ///< Converts the voltage value to pressure + AnalogPressureData sampleImpl() override + { + AnalogPressureData pressure = AnalogPressureData(); + + // Retrieve the voltage + ADCData voltage = getSensorVoltage(); + + // Save the timestamp + pressure.press_timestamp = voltage.adc_timestamp; + + // Convert the value + pressure.press = voltageToPressure(voltage.voltage); + + // Check if the pressure is in range + if (pressure.press < minPressure) + { + pressure.press = minPressure; + } + else if (pressure.press > maxPressure) + { + pressure.press = maxPressure; + } + + return pressure; + } + +protected: + ///< Conversion function from volts to pascals + virtual float voltageToPressure(float voltage) = 0; + + ///< Function that returns the sensor voltage + std::function<ADCData()> getSensorVoltage; + + const float V_SUPPLY; ///< Suppply voltage + + const float maxPressure; + + const float minPressure; +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130A.h b/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130A.h new file mode 100644 index 0000000000000000000000000000000000000000..83ddd18af9cc004dc529c8bfc14bc205212dde14 --- /dev/null +++ b/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130A.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "../AnalogPressureSensor.h" +#include "MPXHZ6130AData.h" + +/** + * @brief Driver for NXP's MPXHZ6130A pressure sensor + */ +class MPXHZ6130A final : public AnalogPressureSensor<MPXHZ6130AData> +{ +public: + MPXHZ6130A(std::function<ADCData()> getSensorVoltage_, + const float V_SUPPLY_ = 5.0) + : AnalogPressureSensor(getSensorVoltage_, V_SUPPLY_, 130000) + { + } + +private: + float voltageToPressure(float voltage) override + { + return (((voltage / V_SUPPLY) + CONST_B) / CONST_A) * 1000; + } + + // Constants from datasheet + static constexpr float CONST_A = 0.007826; + static constexpr float CONST_B = 0.07739; +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130AData.h b/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130AData.h new file mode 100644 index 0000000000000000000000000000000000000000..23add8fca491bae79a026d4529440d3391e35d7a --- /dev/null +++ b/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130AData.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct MPXHZ6130AData : public PressureData +{ + static std::string header() + { + return "press_timestamp,pressure\n"; + } + + void print(std::ostream& os) const + { + os << press_timestamp << "," << press << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/SoftwareDifferentialPressureSensor.h b/src/shared/sensors/analog/pressure/SoftwareDifferentialPressureSensor.h new file mode 100644 index 0000000000000000000000000000000000000000..c87ec09f3bf4a1cbf59c64ba43620f57649e4259 --- /dev/null +++ b/src/shared/sensors/analog/pressure/SoftwareDifferentialPressureSensor.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#pragma once + +#include <sensors/Sensor.h> + +/** + * @brief Class used to simulate a differential pressure sensor in software. + * + * It takes the pressure measures of two sensors (one for the total pressure + * and one for the static one) and returns the difference between the two + * pressure values. + */ +template <typename FirstPressureData, typename SecondPressureData> +class SoftwareDifferentialPressureSensor : public Sensor<PressureData> +{ + static_assert( + checkIfProduces<Sensor<FirstPressureData>, PressureData>::value, + "First template argument must be a sensor that produces pressure " + "data."); + static_assert( + checkIfProduces<Sensor<SecondPressureData>, PressureData>::value, + "Second template argument must be a sensor that produces pressure " + "data."); + +public: + SoftwareDifferentialPressureSensor( + Sensor<FirstPressureData>* first_pressure_sensor, + Sensor<SecondPressureData>* second_pressure_sensor) + : first_pressure_sensor(first_pressure_sensor), + second_pressure_sensor(second_pressure_sensor) + { + } + + bool init() override { return true; } + + bool selfTest() override { return true; }; + + /** + * @return the difference between the two pressure values + */ + PressureData sampleImpl() override + { + float p1 = first_pressure_sensor->getLastSample().press; + float p2 = second_pressure_sensor->getLastSample().press; + + return PressureData{miosix::getTick(), p1 - p2}; + } + +private: + Sensor<FirstPressureData>* first_pressure_sensor; + Sensor<SecondPressureData>* second_pressure_sensor; +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PA.h b/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PA.h new file mode 100644 index 0000000000000000000000000000000000000000..34982bfedd05f87f1645c23e70157f1431a65b98 --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PA.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "HSCMAND015PAData.h" +#include "HoneywellPressureSensor.h" + +/** + * @brief Absolute pressure sensor with a 0-103kPa range (0-15psi) + */ +class HSCMAND015PA final : public HoneywellPressureSensor<HSCMAND015PAData> +{ +public: + HSCMAND015PA(std::function<ADCData()> getSensorVoltage_, + const float V_SUPPLY_ = 5.0) + : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 103421.3594) + { + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PAData.h b/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PAData.h new file mode 100644 index 0000000000000000000000000000000000000000..92b64579717deb6236ecf4a76ef66bf424ee7d7f --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PAData.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct HSCMAND015PAData : public PressureData +{ + static std::string header() + { + return "press_timestamp,pressure\n"; + } + + void print(std::ostream& os) const + { + os << press_timestamp << "," << press << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PA.h b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PA.h new file mode 100644 index 0000000000000000000000000000000000000000..f6caa4972a0a9c966f79f0892cca43563ef12c8a --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PA.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "HSCMRNN030PAData.h" +#include "HoneywellPressureSensor.h" + +/** + * @brief Absolute pressure sensor with a 0-206kPa range (0-30psi) + */ +class HSCMRNN030PA final : public HoneywellPressureSensor<HSCMRNN030PAData> +{ +public: + HSCMRNN030PA(std::function<ADCData()> getSensorVoltage_, + const float V_SUPPLY_ = 5.0) + : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 206842.7188) + { + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PAData.h b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PAData.h new file mode 100644 index 0000000000000000000000000000000000000000..b75584a788916e3d0eac26c8968eabc12404ba9d --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PAData.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct HSCMRNN030PAData : public PressureData +{ + static std::string header() + { + return "press_timestamp,pressure\n"; + } + + void print(std::ostream& os) const + { + os << press_timestamp << "," << press << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KA.h b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KA.h new file mode 100644 index 0000000000000000000000000000000000000000..7ba2ca14840b0b78c85a4491f621568f516ddc1e --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KA.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "HSCMRNN160KAData.h" +#include "HoneywellPressureSensor.h" + +/** + * @brief Absolute pressure sensor with a 0-160kPa range + */ +class HSCMRNN160KA final : public HoneywellPressureSensor<HSCMRNN160KAData> +{ +public: + HSCMRNN160KA(std::function<ADCData()> getSensorVoltage_, + const float V_SUPPLY_ = 5.0) + : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 160000) + { + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KAData.h b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KAData.h new file mode 100644 index 0000000000000000000000000000000000000000..dbfd5a3bb858d18ca0eb4cd9d5a21c40ee2444e1 --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KAData.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct HSCMRNN160KAData : public PressureData +{ + static std::string header() + { + return "press_timestamp,pressure\n"; + } + + void print(std::ostream& os) const + { + os << press_timestamp << "," << press << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/HoneywellPressureSensor.h b/src/shared/sensors/analog/pressure/honeywell/HoneywellPressureSensor.h new file mode 100644 index 0000000000000000000000000000000000000000..0e3cfe483b4f125ba7acc2959527598edf5107d5 --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/HoneywellPressureSensor.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "../AnalogPressureSensor.h" + +/** + * @brief Driver for Honeywell's pressure sensors (absolute and differential) + * + * All this sensors shares the same transfer function which varies only by few + * parameters: voltage supply and the sensor pressure range + */ +template <typename HoneywellPressureData> +class HoneywellPressureSensor + : public AnalogPressureSensor<HoneywellPressureData> +{ +public: + using AnalogPressureSensor<HoneywellPressureData>::AnalogPressureSensor; + +protected: + ///< Common transfer function from volts to pascals (from datasheet pag 11) + inline float voltageToPressure(float voltage) + { + float tmp; + + tmp = voltage - + 0.1 * AnalogPressureSensor<HoneywellPressureData>::V_SUPPLY; + tmp *= AnalogPressureSensor<HoneywellPressureData>::maxPressure - + AnalogPressureSensor<HoneywellPressureData>::minPressure; + tmp /= 0.8 * AnalogPressureSensor<HoneywellPressureData>::V_SUPPLY; + tmp += AnalogPressureSensor<HoneywellPressureData>::minPressure; + + return tmp; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAA.h b/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAA.h new file mode 100644 index 0000000000000000000000000000000000000000..f95ebc7f859511d967874ecbafa8df9e05d6906c --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAA.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "HoneywellPressureSensor.h" +#include "SSCDANN030PAAData.h" + +/** + * @brief Absolute pressure sensor with a 0-206kPa range (0-30psi) + */ +class SSCDANN030PAA final : public HoneywellPressureSensor<SSCDANN030PAAData> +{ +public: + SSCDANN030PAA(std::function<ADCData()> getSensorVoltage_, + const float V_SUPPLY_ = 5.0) + : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 206842.7188) + { + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAAData.h b/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAAData.h new file mode 100644 index 0000000000000000000000000000000000000000..fc31db7beedbae26bf255601ffeccb8917d07eb1 --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAAData.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct SSCDANN030PAAData : public PressureData +{ + static std::string header() + { + return "press_timestamp,pressure\n"; + } + + void print(std::ostream& os) const + { + os << press_timestamp << "," << press << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h new file mode 100644 index 0000000000000000000000000000000000000000..0efc3c37a3dbcead629454ace4f693bf45bce2a5 --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "math/Stats.h" + +#include "HoneywellPressureSensor.h" +#include "SSCDRRN015PDAData.h" + +/** + * @brief Differential pressure sensor with a ±103kPa range (±15psi) + */ +class SSCDRRN015PDA final : public HoneywellPressureSensor<SSCDRRN015PDAData> +{ +public: + SSCDRRN015PDA(std::function<ADCData()> getSensorVoltage_, + const float V_SUPPLY_ = 5.0, + const unsigned int num_calib_samples_ = 200) + : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 103421.3594, + -103421.3594), + offset(0.0), num_calib_samples(num_calib_samples_) + { + } + + SSCDRRN015PDAData sampleImpl() override + { + last_sample = HoneywellPressureSensor<SSCDRRN015PDAData>::sampleImpl(); + + if (calibrating) + { + press_stats.add(last_sample.press); + + if (press_stats.getStats().nSamples >= num_calib_samples) + { + calibrating = false; + offset = press_stats.getStats().mean; + + TRACE("Differential barometer offset : %.2f \n", offset); + } + } + + last_sample.press = last_sample.press - offset; + + return last_sample; + } + + void calibrate() + { + press_stats.reset(); + offset = 0.0f; + calibrating = true; + } + + bool isCalibrating() { return calibrating; } + +private: + bool calibrating = false; + float offset; + Stats press_stats; + unsigned int num_calib_samples; +}; \ No newline at end of file diff --git a/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDAData.h b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDAData.h new file mode 100644 index 0000000000000000000000000000000000000000..bac2d1a2d1610e51755e8a7ff63933dff364b297 --- /dev/null +++ b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDAData.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#pragma once + +#include "sensors/SensorData.h" + +struct SSCDRRN015PDAData : public PressureData +{ + static std::string header() + { + return "press_timestamp,pressure\n"; + } + + void print(std::ostream& os) const + { + os << press_timestamp << "," << press << "\n"; + } +}; \ No newline at end of file diff --git a/src/shared/sensors/bme280.h b/src/shared/sensors/bme280.h deleted file mode 100644 index 96a90b1559e534850ea93c0231ddcb52203185f6..0000000000000000000000000000000000000000 --- a/src/shared/sensors/bme280.h +++ /dev/null @@ -1,337 +0,0 @@ -/* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta - * - * 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. - */ - -#pragma once - -#include <stdio.h> -#include <ostream> -#include <string> -#include "Sensor.h" -// #include "debug.h" - - -struct BME280Data -{ - uint32_t timestamp; - float pressure, temperature; - uint32_t raw_pressure, raw_temperature; - - static std::string header() - { - return "timestamp,pressure,temperature,raw_pressure,raw_temperature"; - } - - void print(std::ostream& os) - { - os << timestamp << "," << pressure << "," << temperature << "," - << raw_pressure << "," << raw_temperature; - } -}; - -template <class Bus> -class BME280 : public TemperatureSensor, public PressureSensor -{ -public: - BME280() {} - virtual ~BME280() {} - - std::string printData() { return ""; } - - bool init() override - { - if (!initialized) - { - Bus::init(); - - uint8_t who_am_i = Bus::read(REG_WHO_AM_I); - if (who_am_i != ID) - { - last_error = ERR_NOT_ME; - printf("BME280 not found: who am i read 0x%02X\n", who_am_i); - return false; - } - - // T standby = 0.5 ms, Filter: 8x ("& 0x7F" to set bit 7 to 0) - Bus::write(REG_CONFIG & 0x7F, 0x0C); - - uint8_t config_val = Bus::read(REG_CONFIG); - if (config_val != 0x0C) - { - // TRACE("Wrong value in config register: 0x%02X\n", config_val); - return false; - } - - // Disable humidity reading - Bus::write(REG_CTRL_HUM & 0x7F, 0x00); - - uint8_t hum_val = Bus::read(REG_CTRL_HUM); - if (hum_val != 0x00) - { - // TRACE("Wrong value in hum register: 0x%02X\n", hum_val); - last_error = ERR_BUS_FAULT; - return false; - } - - // Pressure oversampling x4, temp oversampling x1, normal mode. - Bus::write(REG_CTRL_MEAS & 0x7F, (uint8_t)0x2F); - - uint8_t meas_val = Bus::read(REG_CTRL_MEAS); - if (meas_val != 0x2F) - { - // TRACE("Wrong value in meas register: 0x%02X\n", meas_val); - last_error = ERR_BUS_FAULT; - return false; - } - - loadCompensationParams(); - - initialized = true; - return true; - } - else - { - return false; - } - } - - bool selfTest() override { return true; } - - void printCompensationParams() - { - if (initialized) - { - printf("Compensation parameters: \n"); - - printf("dig_T1: 0x%04X\n", comp_params.dig_T1); - printf("dig_T2: 0x%04X\n", comp_params.dig_T2); - printf("dig_T3: 0x%04X\n", comp_params.dig_T3); - - printf("dig_P1: 0x%04X\n", comp_params.dig_P1); - printf("dig_P2: 0x%04X\n", comp_params.dig_P2); - printf("dig_P3: 0x%04X\n", comp_params.dig_P3); - printf("dig_P4: 0x%04X\n", comp_params.dig_P4); - printf("dig_P5: 0x%04X\n", comp_params.dig_P5); - printf("dig_P6: 0x%04X\n", comp_params.dig_P6); - printf("dig_P7: 0x%04X\n", comp_params.dig_P7); - printf("dig_P8: 0x%04X\n", comp_params.dig_P8); - printf("dig_P9: 0x%04X\n", comp_params.dig_P9); - } - } - - bool onSimpleUpdate() override - { - if (initialized) - { - // Burst read temperature and pressure registers - uint8_t buf[6]; - Bus::read(REG_PRESS_MSB, buf, 6); - - uint32_t press = 0; - press |= ((uint32_t)buf[0]) << 12; - press |= ((uint32_t)buf[1]) << 4; - press |= ((uint32_t)buf[2]) >> 4; - - uint32_t temp = 0; - temp |= ((uint32_t)buf[3]) << 12; - temp |= ((uint32_t)buf[4]) << 4; - temp |= ((uint32_t)buf[5]) >> 4; - - data.raw_pressure = press; - data.raw_temperature = temp; - - // Compensate values - int32_t t_fine = getFineTemperature(data.raw_temperature); - - data.temperature = compensateTemperature(t_fine) / 100.0f; - data.pressure = - compensatePressure(data.raw_pressure, t_fine) / 256.0f; - - data.timestamp = miosix::getTick(); - - mLastPressure = data.pressure; - mLastTemp = data.temperature; - - /*printf( - "Raw P:\t%d,\tRaw T:\t%d\nComp P:\t%d,\tComp T:\t%d,\tFine T: " - "%d\n", - (int)press, (int)temp, (int)(pressure_value / 256), - (int)temp_value, (int)t_fine);*/ - return true; - } - return false; - } - - const uint32_t* rawPressureDataPtr() { return &data.raw_pressure; } - - const uint32_t* rawTempDataPtr() { return &data.raw_temperature; } - - const BME280Data* getDataPtr() { return &data; } - - BME280Data data; - -private: - void loadCompensationParams() - { - uint8_t buf[24]; - Bus::read(REG_CALIB_T, buf, 24); - - comp_params.dig_T1 = toUInt16(&buf[0]); - comp_params.dig_T2 = toInt16(&buf[2]); - comp_params.dig_T3 = toInt16(&buf[4]); - - comp_params.dig_P1 = toUInt16(&buf[6]); - comp_params.dig_P2 = toInt16(&buf[8]); - comp_params.dig_P3 = toInt16(&buf[10]); - comp_params.dig_P4 = toInt16(&buf[12]); - comp_params.dig_P5 = toInt16(&buf[14]); - comp_params.dig_P6 = toInt16(&buf[16]); - comp_params.dig_P7 = toInt16(&buf[18]); - comp_params.dig_P8 = toInt16(&buf[20]); - comp_params.dig_P9 = toInt16(&buf[22]); - } - - /** - * Returns the temperature in degC. - * Refer to the data sheet for a description of this method. - */ - int32_t getFineTemperature(uint32_t raw_T) - { - int32_t adc_T = static_cast<int32_t>(raw_T); - int32_t var1, var2; - var1 = ((((adc_T / 8) - ((int32_t)comp_params.dig_T1 * 2))) * - ((int32_t)comp_params.dig_T2)) >> - 11; - var2 = (((((adc_T / 16) - ((int32_t)comp_params.dig_T1)) * - ((adc_T / 16) - ((int32_t)comp_params.dig_T1))) / - 4096) * - ((int32_t)comp_params.dig_T3)) / - 16384; - - return var1 + var2; - } - - int32_t compensateTemperature(int32_t fine_temperature) - { - return (fine_temperature * 5 + 128) / 256; - } - - uint32_t compensatePressure(uint32_t adc_P, int32_t fine_temperature) - { - int64_t var1, var2, p; - var1 = ((int64_t)fine_temperature) - 128000; - - var2 = var1 * var1 * (int64_t)comp_params.dig_P6; - var2 = var2 + ((var1 * (int64_t)comp_params.dig_P5) * 131072); - var2 = var2 + (((int64_t)comp_params.dig_P4) * 34359738368); - - var1 = ((var1 * var1 * (int64_t)comp_params.dig_P3) / 256) + - ((var1 * ((int64_t)comp_params.dig_P2) * 4096)); - - var1 = (((int64_t)1) * 140737488355328 + var1) * - ((int64_t)comp_params.dig_P1) / 8589934592; - - if (var1 == 0) - { - return 0; // avoid exception caused by division by zero - } - - p = 1048576 - (int32_t)adc_P; - p = (((p * 2147483648) - var2) * 3125) / var1; - - var1 = (((int64_t)comp_params.dig_P9) * (p / 8192) * (p / 8192)) / - 33554432; - - var2 = (((int64_t)comp_params.dig_P8) * p) / 524288; - - p = ((p + var1 + var2) / 256) + (((int64_t)comp_params.dig_P7) * 16); - - // p = ((var3 / 2) * 100) / 128; - return (uint32_t)p; - } - - /** - * Converts two bytes into an unsigned int 16 - * @param buf: buffer containing at least two bytes - */ - uint16_t toUInt16(uint8_t* buf) { return buf[0] | buf[1] << 8; } - - /** - * Converts two bytes into a signed int 16 - * @param buf: buffer containing at least two bytes - */ - int16_t toInt16(uint8_t* buf) - { - return static_cast<int16_t>(buf[0] | buf[1] << 8); - } - - enum Registers : uint8_t - { - REG_WHO_AM_I = 0xD0, - REG_RESET = 0xE0, - REG_CTRL_HUM = 0xF2, - REG_STATUS = 0xF3, - REG_CTRL_MEAS = 0xF4, - REG_CONFIG = 0xF5, - - REG_PRESS_MSB = 0xF7, - REG_PRESS_LSB = 0xF8, - REG_PRESS_XLSB = 0xF9, - - REG_TEMP_MSB = 0xFA, - REG_TEMP_LSB = 0xFB, - REG_TEMP_XLSB = 0xFC, - - REG_HUM_MSB = 0xFD, - REG_HUM_LSB = 0xFE, - - REG_CALIB_T = 0x88, - REG_CALUB_P = 0x8E - }; - - /** - * Compensation parameters to convert ADC values to real temperature and - * pressure - */ - struct CompensationParams - { - uint16_t dig_T1; - int16_t dig_T2; - int16_t dig_T3; - - uint16_t dig_P1; - int16_t dig_P2; - int16_t dig_P3; - int16_t dig_P4; - int16_t dig_P5; - int16_t dig_P6; - int16_t dig_P7; - int16_t dig_P8; - int16_t dig_P9; - }; - - bool initialized = false; - - const uint8_t ID = 0x60; - const uint8_t RESET_WORD = 0xB6; - - CompensationParams comp_params; -}; \ No newline at end of file diff --git a/src/shared/sensors/calibration/BiasCalibration.h b/src/shared/sensors/calibration/BiasCalibration.h new file mode 100644 index 0000000000000000000000000000000000000000..e5d8b204c543d6e33ec96b3e0c747cf0c0389b07 --- /dev/null +++ b/src/shared/sensors/calibration/BiasCalibration.h @@ -0,0 +1,110 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#pragma once + +#include <Eigen/Core> + +#include "Calibration.h" +#include "sensors/SensorData.h" + +using namespace Eigen; + +/** + * This is the dumbest type of calibration possible: it stores a 3d vector + * (called "bias") that will be added to every measurement. + * During the calibration phase it will use a given reference vector (for + * example the gravitational acceleration for the accelerometer), and every time + * you'll feed the model with a new value, you have to give it the orientation + * of the sensor, so it can guess the bias. + */ +template <typename T> +class BiasCorrector : public ValuesCorrector<T> +{ + +public: + BiasCorrector() : bias(0, 0, 0) {} + BiasCorrector(const Vector3f& _bias) : bias(_bias) {} + + void operator>>(Vector3f& rhs) { rhs = bias; } + + void operator<<(const Vector3f& rhs) { bias = rhs; } + + void setIdentity() override { bias = {0, 0, 0}; } + + T correct(const T& data) const override + { + Vector3f tmp; + T out; + + data >> tmp; + tmp += bias; + out << tmp; + + return out; + } + +private: + Vector3f bias; +}; + +template <typename T> +class BiasCalibration + : public AbstractCalibrationModel<T, BiasCorrector<T>, AxisOrientation> +{ +public: + BiasCalibration() : sum(0, 0, 0), ref(0, 0, 0), numSamples(0) {} + + void setReferenceVector(Vector3f vec) { ref = vec; } + Vector3f getReferenceVector() { return ref; } + + /** + * BiasCalibration accepts an indefinite number of samples, + * so feed(...) never returns false. + */ + bool feed(const T& measured, const AxisOrientation& transform) override + { + Vector3f vec; + measured >> vec; + + sum += (transform.getMatrix().transpose() * ref) - vec; + numSamples++; + + return true; + } + + bool feed(const T& measured) + { + return feed(measured, AxisOrthoOrientation()); + } + + BiasCorrector<T> computeResult() + { + if (numSamples == 0) + return {Vector3f{0, 0, 0}}; + return {sum / numSamples}; + } + +private: + Vector3f sum, ref; + unsigned numSamples; +}; diff --git a/src/shared/sensors/calibration/Calibration.h b/src/shared/sensors/calibration/Calibration.h new file mode 100644 index 0000000000000000000000000000000000000000..a263ab8e0d971af76d04a1fbc8dbf363a9696eff --- /dev/null +++ b/src/shared/sensors/calibration/Calibration.h @@ -0,0 +1,334 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#pragma once + +#include <Eigen/Core> +#include <Eigen/Geometry> + +#include "SensorDataExtra.h" +#include "sensors/Sensor.h" + +using namespace Eigen; + +/** + * This class is used to adjust the values given by sensors during the flight. + * An object can be obtained only via deserialization or if produced by an + * instance of the "CalibrationModel" class. + * + * T is the type of sensor data the model is applied to (e.g. + * GyroscopeData, MagnetometerData). This is needed because certain models could + * work slightly differently depending on it + * + * Note: derived classes of ValuesCorrector<XX> MUST implement the following + * operators (to make possible to store and load the coefficients): + * + * operator << (const XX& t); + * operator >> (XX& t); + * + * where XX is a datatype that can fully contain all the coefficients used + * by the function correct(input). + * + * Also, an empty constructor must create a neutral instance (identity + * transformation). + */ +template <typename T> +class ValuesCorrector +{ +public: + /** + * This method will sets the internal coefficients so that the corrected + * values are exactly the same of the inputted ones. + */ + virtual void setIdentity() = 0; + + virtual T correct(const T& input) const = 0; +}; + +/** + * AbstractCalibrationModel represents a "factory" of ValuesCorrector instances, + * and it's necessary to create one. You will always use one of its derived + * classes, of course. + * + * Values given to the feed() function are needed for the training of the model. + * + * T is the sensor data type + * C is a class that extends ValuesCorrector<T> + */ +template <typename T, typename C, typename... AdditionalFeedParams> +class AbstractCalibrationModel +{ +public: + /** + * Gives to the model a single measurement to store and use to produce the + * adapted ValueCorrector instance + * + * @returns false if the model can't accept the given data (usually because + * the internal buffers are full) + */ + virtual bool feed(const T& measurement, + const AdditionalFeedParams&... params) = 0; + + /** + * Creates the best ValuesCorrector instance for the given measurements. + * Note: you must feed some data to the model before getting the result! + */ + virtual C computeResult() = 0; + + virtual ~AbstractCalibrationModel(){}; +}; + +/** + * This class acts like a Sensor driver but incorporates both a Sensor<T> + * instance and a ValuesCorrector. It can be useful to add a calibration step to + * alredy existing code that uses the Sensor API. + */ +template <typename SensorData> +class SensorWrapper : public Sensor<SensorData> +{ + using S = Sensor<SensorData>; + using C = ValuesCorrector<SensorData>; + +public: + SensorWrapper(S* _sensor, C* _calib) : sensor(_sensor), calib(_calib) {} + + S* getSensor() { return sensor; } + void setSensor(S* s) { sensor = s; } + + C* getValuesCorrector() { return calib; } + void setValuesCorrector(C* c) { calib = c; } + + void init() override { sensor->init(); } + + bool test() override { return sensor->test(); } + + SensorData sampleImpl() override + { + return calib->correct(sensor->sampleImpl()); + } + + SensorErrors getWrappedSensorError() { return sensor->getLastError(); } + +private: + S* sensor; + C* calib; +}; + +/** + * This enum act like versors towards the chosen axis. + * + * X, Y and Z always set according to the right hand rule, so that: + * X is the index + * Y is the second finger + * Z is the thumb + * + */ +enum class Direction : uint8_t +{ + POSITIVE_X = 0, + NEGATIVE_X, + POSITIVE_Y, + NEGATIVE_Y, + POSITIVE_Z, + NEGATIVE_Z, +}; + +constexpr const char* humanFriendlyDirection[]{ + "North", "South", "East", "West", "Down", "Up", +}; + +inline Vector3f orientationToVector(Direction val) +{ + switch (val) + { + case Direction::POSITIVE_X: + return {1, 0, 0}; + case Direction::NEGATIVE_X: + return {-1, 0, 0}; + case Direction::POSITIVE_Y: + return {0, 1, 0}; + case Direction::NEGATIVE_Y: + return {0, -1, 0}; + case Direction::POSITIVE_Z: + return {0, 0, 1}; + case Direction::NEGATIVE_Z: + return {0, 0, -1}; + default: + /* never happens, added just to shut up the warnings */ + return {0, 0, 0}; + } +} + +/** + * This struct represents in the most general way any kind of transformation of + * the reference frame (axis X, Y and Z). + * This data type is intended to simplify the code, so you shouldn't instantiate + * this struct directly, but rather use the structures AxisAngleOrientation or + * AxisOrthoOrientation that will be automatically corrected to this one, thanks + * to the implicit cast in favor of AxisOrientation. + * + * For example: + * + * AxisAngleOrientation angles ( PI/2, PI, 0); + * AxisOrthoOrientation ortho ( Direction::NEGATIVE_X, + * Direction::POSITIVE_Z ); + * + * // The implicit cast is supported and recommended + * AxisOrientation converted1 = angles; + * AxisOrientation converted2 = ortho; + * + * // Now we can use the generated matrix: + * Vector3f zeta = convertedX.getMatrix() * Vector3f { 0, 0, 1 } + * + */ +struct AxisOrientation +{ + Matrix3f mat; + + AxisOrientation() : mat(Matrix3f::Identity()) {} + + AxisOrientation(Matrix3f _mat) : mat(_mat) {} + + void setMatrix(Matrix3f _mat) { mat = _mat; } + + Matrix3f getMatrix() const { return mat; } +}; + +/** + * This struct uses the three angles yaw, pitch and roll to define the + * transformation of the reference frame, so according to N.E.D standard we get: + * + * X (north) + * / + * / + * .----> Y (east) + * | + * | + * v + * Z (down) + * + * Where: + * Yaw is rotation of Z axis + * Pitch is rotation of Y axis + * Roll is rotation of X axis + */ +struct AxisAngleOrientation +{ + float yaw, pitch, roll; + + AxisAngleOrientation() : yaw(0), pitch(0), roll(0) {} + + AxisAngleOrientation(float _yaw, float _pitch, float _roll) + : yaw(_yaw), pitch(_pitch), roll(_roll) + { + } + + operator AxisOrientation() const { return AxisOrientation(getMatrix()); } + + Matrix3f getMatrix() const + { + return (AngleAxisf(yaw, Vector3f{0, 0, 1}) * + AngleAxisf(pitch, Vector3f{0, 1, 0}) * + AngleAxisf(roll, Vector3f{1, 0, 0})) + .toRotationMatrix(); + } +}; + +/** + * This struct represents the orientation of the reference frame relative + * to X, Y, Z in the start orientation. + * If we know the orientation of the X and Y axis, using the right hand rule + * we can infer the Z axis. + * + * For example, if the base reference is: + * + * z + * ^ + * | + * | + * /----> y + * / + * x + * + * Then if we set x = NEGATIVE_Y, y = POSITIVE_Z, we get: + * + * y z + * ^ ^ + * | / + * | / + * x <----/ + * + */ +struct AxisOrthoOrientation +{ + Direction xAxis, yAxis; + + AxisOrthoOrientation() + : xAxis(Direction::POSITIVE_X), yAxis(Direction::POSITIVE_Y) + { + } + + AxisOrthoOrientation(Direction _xAxis, Direction _yAxis) + : xAxis(_xAxis), yAxis(_yAxis) + { + } + + operator AxisOrientation() const { return AxisOrientation(getMatrix()); } + + Matrix3f getMatrix() const + { + Vector3f vx, vy, vz; + + vx = orientationToVector(xAxis); + vy = orientationToVector(yAxis); + vz = vx.cross(vy); + + Matrix3f mat; + mat.col(0) << vx; + mat.col(1) << vy; + mat.col(2) << vz; + return mat; + } +}; + +/** + * This struct represents axis orientation relative to a reference system. + * Operatively it simply combines two transformations. It is particularly useful + * to obtain an AxisOrientation from a reference system generally not N.E.D. + */ +struct AxisRelativeOrientation +{ + AxisOrientation systemOrientation, orientation; + + AxisRelativeOrientation(const AxisOrientation& _systemOrientation, + const AxisOrientation& _orientation) + : systemOrientation(_systemOrientation), orientation(_orientation) + { + } + + operator AxisOrientation() const { return {getMatrix()}; } + + Matrix3f getMatrix() const + { + return systemOrientation.getMatrix() * orientation.getMatrix(); + } +}; diff --git a/src/shared/sensors/calibration/HardIronCalibration.h b/src/shared/sensors/calibration/HardIronCalibration.h new file mode 100644 index 0000000000000000000000000000000000000000..feaa0fdf6cf92e88d249891ba853fa3db791d426 --- /dev/null +++ b/src/shared/sensors/calibration/HardIronCalibration.h @@ -0,0 +1,115 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#pragma once + +#include <Eigen/Core> + +#include "Calibration.h" +#include "sensors/SensorData.h" + +/* + * The Hard-iron calibration removes the bias due to the so named Hard-Iron + * distortion of the magnetic field. Unlike bias calibration, 6-parameter + * calibration, etc., hard-iron calibration can be applied only to magnetometer + * samples. + */ +class HardIronCorrector : public ValuesCorrector<MagnetometerData> +{ +public: + HardIronCorrector() : HardIronCorrector({0, 0, 0}) {} + + HardIronCorrector(const Vector3f& _bias) : bias(_bias) {} + + void setIdentity() override { bias = {0, 0, 0}; } + + void operator>>(Vector3f& rhs) { rhs = bias; } + + void operator<<(const Vector3f& rhs) { bias = rhs; } + + MagnetometerData correct(const MagnetometerData& input) const override + { + MagnetometerData output; + Vector3f vec; + + input >> vec; + vec += bias; + output << vec; + + return output; + } + +private: + Vector3f bias; +}; + +template <unsigned MaxSamples> +class HardIronCalibration + : public AbstractCalibrationModel<MagnetometerData, HardIronCorrector> +{ +public: + HardIronCalibration() : samples(), numSamples(0) {} + + bool feed(const MagnetometerData& data) override + { + if (numSamples >= MaxSamples) + return false; + + Vector3f vec; + data >> vec; + + samples.block(numSamples, 0, 1, 3) = vec.transpose(); + samples(numSamples, 3) = 1; + samples(numSamples, 4) = + vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]; + + numSamples++; + return true; + } + + HardIronCorrector computeResult() override + { + Vector4f sol; + Vector3f bias; + + MatrixXf coeffs = samples.block(0, 0, numSamples, 4); + VectorXf terms = samples.block(0, 4, numSamples, 1); + + auto colPiv = coeffs.colPivHouseholderQr(); + sol = colPiv.solve(terms); + + bias[0] = -sol[0] / 2; + bias[1] = -sol[1] / 2; + bias[2] = -sol[2] / 2; + + return {bias}; + } + +private: + /* + * The matrix contains x, y, z measured, a column of 1s and x^2+y^2+z^2 + * row. Its shape is (N x 5) + */ + Matrix<float, MaxSamples, 5> samples; + unsigned numSamples; +}; + diff --git a/src/shared/sensors/calibration/SensorDataExtra.cpp b/src/shared/sensors/calibration/SensorDataExtra.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7afc87dc85b3574dac80789ac34229989a64f6a1 --- /dev/null +++ b/src/shared/sensors/calibration/SensorDataExtra.cpp @@ -0,0 +1,67 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#include "SensorDataExtra.h" + +using namespace Eigen; + +void operator<<(AccelerometerData& lhs, const Vector3f& rhs) +{ + lhs.accel_x = rhs[0]; + lhs.accel_y = rhs[1]; + lhs.accel_z = rhs[2]; +} + +void operator<<(GyroscopeData& lhs, const Vector3f& rhs) +{ + lhs.gyro_x = rhs[0]; + lhs.gyro_y = rhs[1]; + lhs.gyro_z = rhs[2]; +} + +void operator<<(MagnetometerData& lhs, const Vector3f& rhs) +{ + lhs.mag_x = rhs[0]; + lhs.mag_y = rhs[1]; + lhs.mag_z = rhs[2]; +} + +void operator>>(const AccelerometerData& lhs, Vector3f& rhs) +{ + rhs[0] = lhs.accel_x; + rhs[1] = lhs.accel_y; + rhs[2] = lhs.accel_z; +} + +void operator>>(const GyroscopeData& lhs, Vector3f& rhs) +{ + rhs[0] = lhs.gyro_x; + rhs[1] = lhs.gyro_y; + rhs[2] = lhs.gyro_z; +} + +void operator>>(const MagnetometerData& lhs, Vector3f& rhs) +{ + rhs[0] = lhs.mag_x; + rhs[1] = lhs.mag_y; + rhs[2] = lhs.mag_z; +} diff --git a/src/shared/sensors/calibration/SensorDataExtra.h b/src/shared/sensors/calibration/SensorDataExtra.h new file mode 100644 index 0000000000000000000000000000000000000000..46c76e8a7074244b51364460f235198e46d66606 --- /dev/null +++ b/src/shared/sensors/calibration/SensorDataExtra.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +/* This header file defines operators << and >> from some types + * of SensorData. + * + * This way you can write for example: + * + * GenericSensorData data; + * data << Vector3f{0.f, 0.f, 0.f}; + * + * Vector3f vec; + * data >> vec; + * + * That works if GenericSensorData is any of AccelerometerData, GyroscopeData + * and MagnetometerData + */ + +#pragma once + +#include <Eigen/Core> + +#include "sensors/SensorData.h" + +using namespace Eigen; + +void operator<<(AccelerometerData& lhs, const Vector3f& rhs); + +void operator<<(GyroscopeData& lhs, const Vector3f& rhs); + +void operator<<(MagnetometerData& lhs, const Vector3f& rhs); + +void operator>>(const AccelerometerData& lhs, Vector3f& rhs); + +void operator>>(const GyroscopeData& lhs, Vector3f& rhs); + +void operator>>(const MagnetometerData& lhs, Vector3f& rhs); + diff --git a/src/shared/sensors/calibration/SixParameterCalibration.h b/src/shared/sensors/calibration/SixParameterCalibration.h new file mode 100644 index 0000000000000000000000000000000000000000..c42ab4417db63790bf4fdbdc8b00d1f0c2f61897 --- /dev/null +++ b/src/shared/sensors/calibration/SixParameterCalibration.h @@ -0,0 +1,159 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#pragma once + +#include <Common.h> + +#include <Eigen/Core> +#include <iostream> + +#include "Calibration.h" +#include "sensors/SensorData.h" + +/* + * Six-Parameter Calibration uses, for each axis, a coefficient to be multiplied + * and a constant to be added, so that is verified the formula: + * + * x' = p1*x + q1; + * y' = p2*y + q2; + * z' = p3*z + q3; + * + * Where (x, y, z) is the input and (x', y', z') the output. + */ +template <typename T> +class SixParameterCorrector : public ValuesCorrector<T> +{ +public: + SixParameterCorrector() : SixParameterCorrector({1, 1, 1}, {0, 0, 0}) {} + + SixParameterCorrector(const Vector3f& _p, const Vector3f& _q) : p(_p), q(_q) + { + } + + void setIdentity() override + { + p = {1, 1, 1}; + q = {0, 0, 0}; + } + + void operator>>(Matrix<float, 3, 2>& out) const + { + out.col(0) = p.transpose(); + out.col(1) = q.transpose(); + } + + void operator<<(const Matrix<float, 3, 2>& in) + { + p = in.col(0).transpose(); + q = in.col(1).transpose(); + } + + T correct(const T& input) const override + { + T output; + Vector3f vec; + + input >> vec; + vec = p.cwiseProduct(vec) + q; + output << vec; + + return output; + } + +private: + Vector3f p, q; +}; + +template <typename T, unsigned MaxSamples> +class SixParameterCalibration + : public AbstractCalibrationModel<T, SixParameterCorrector<T>, + AxisOrientation> +{ +public: + SixParameterCalibration() : samples(), ref(1, 0, 0), numSamples(0) {} + + void setReferenceVector(Vector3f vec) { ref = vec; } + Vector3f getReferenceVector() { return ref; } + + bool feed(const T& data, const AxisOrientation& transform) override + { + if (numSamples >= MaxSamples) + return false; + + Vector3f measured, expected; + + data >> measured; + // TODO: understand why the rotation, although it is needed, it does not + // work + expected = transform.getMatrix() * ref; + + /* + * measered and expected are column vectors, we need to traspose them + * to be row vectors + */ + samples.block(numSamples, 0, 1, 3) = measured.transpose(); + samples.block(numSamples, 3, 1, 3) = expected.transpose(); + + numSamples++; + return true; + } + + SixParameterCorrector<T> computeResult() override + { + Vector3f p, q; + + for (int i = 0; i < 3; ++i) + { + MatrixXf coeffs(numSamples, 2); + VectorXf terms = samples.block(0, i + 3, numSamples, 1); + + Vector2f solution; + coeffs.fill(1); + coeffs.block(0, 0, numSamples, 1) = + samples.block(0, i, numSamples, 1); + + auto solver = coeffs.colPivHouseholderQr(); + solution = solver.solve(terms); + + p[i] = solution[0]; + q[i] = solution[1]; + } + + return {p, q}; + } + +private: + /* + * The matrix contains x, y, z measured and x', y', z' expected for each + * row. Its shape is (N x 6) + */ + Matrix<float, MaxSamples, 6> samples; + Vector3f ref; + unsigned numSamples; + +public: + /* + * Only needed for test-calibration.cpp entry point + */ + EIGEN_MAKE_ALIGNED_OPERATOR_NEW +}; diff --git a/src/shared/sensors/calibration/SoftIronCalibration.h b/src/shared/sensors/calibration/SoftIronCalibration.h new file mode 100644 index 0000000000000000000000000000000000000000..d7db084d85d0ca37d70a132fef983a99f85bc2a6 --- /dev/null +++ b/src/shared/sensors/calibration/SoftIronCalibration.h @@ -0,0 +1,187 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#pragma once + +#include <Common.h> + +#include <Eigen/Core> +#include <Eigen/Eigenvalues> + +#include "Calibration.h" +#include "sensors/SensorData.h" + +/* + * The Soft-iron calibration removes the measurement error given by both the + * Hard and Soft Iron distortion of the magnetic field. + */ +class SoftIronCorrector : public ValuesCorrector<MagnetometerData> +{ +public: + SoftIronCorrector() : SoftIronCorrector({1, 1, 1}, {0, 0, 0}) {} + + SoftIronCorrector(const Vector3f& _p, const Vector3f& _q) : p(_p), q(_q) {} + + void setIdentity() override + { + p = {1, 1, 1}; + q = {0, 0, 0}; + } + + void operator>>(Matrix<float, 3, 2>& rhs) + { + rhs.col(0) = p.transpose(); + rhs.col(1) = q.transpose(); + } + + void operator<<(const Matrix<float, 3, 2>& rhs) + { + p = rhs.col(0).transpose(); + q = rhs.col(1).transpose(); + } + + MagnetometerData correct(const MagnetometerData& input) const override + { + MagnetometerData output; + Vector3f vec; + + input >> vec; + vec = vec.cwiseProduct(p) + q; + output << vec; + + return output; + } + +private: + Vector3f p, q; +}; + +template <unsigned MaxSamples> +class SoftIronCalibration + : public AbstractCalibrationModel<MagnetometerData, SoftIronCorrector> +{ +public: + SoftIronCalibration() : samples(), numSamples(0) {} + + bool feed(const MagnetometerData& data) override + { + if (numSamples >= MaxSamples) + return false; + + Vector3f vec; + data >> vec; + + for (int i = 0; i < 3; ++i) + { + samples(numSamples, i) = vec[i] * vec[i]; + samples(numSamples, i + 3) = vec[i]; + } + samples(numSamples, 6) = 1; + + numSamples++; + return true; + } + + SoftIronCorrector computeResult() override + { + using Mx = Matrix<float, 7, 7>; + using Vec7 = Matrix<float, 7, 1>; + + float minValue, det; + int minIdx; + + MatrixXf tmp1, tmp2; + Vector3f p, q; + Vec7 vec; + Mx mat; + + tmp1 = samples.block(0, 0, numSamples, 7); + tmp2 = tmp1.transpose(); + + // mat = tmp2 * tmp1; + + /* + * tmp1: N x 7 + * tmp2: 7 x N + * + * Big matrices multiplication: if done with Eigen, the program crashes + * (bus fault), so we'll do that with good old for's + */ + for (unsigned i = 0; i < 7; ++i) + { + for (unsigned j = 0; j < 7; ++j) + { + mat(i, j) = 0; + + for (unsigned k = 0; k < numSamples; ++k) + { + mat(i, j) += tmp2(i, k) * tmp1(k, j); + } + } + } + + SelfAdjointEigenSolver<Mx> solver(mat); + auto eigenvalues = solver.eigenvalues(); + + minValue = eigenvalues[0]; + minIdx = 0; + + for (int i = 1; i < eigenvalues.rows(); ++i) + { + if (minValue > eigenvalues[i]) + { + minValue = eigenvalues[i]; + minIdx = i; + } + } + + vec = solver.eigenvectors().col(minIdx); + det = vec[0] * vec[1] * vec[2]; + + if (det < 0) + { + vec *= -1; + det *= -1; + } + + p = vec.block(0, 0, 3, 1) / cbrt(det); + q = {vec[3] / vec[0] / 2, vec[4] / vec[1] / 2, vec[5] / vec[2] / 2}; + + p[0] = sqrt(p[0]); + p[1] = sqrt(p[1]); + p[2] = sqrt(p[2]); + + q = q.cwiseProduct(p); + + return {p, q}; + } + + Matrix<float, MaxSamples, 7> getSamples() { return samples; } + +private: + /* + * The matrix contains x, y, z, x^2, y^2, z^2 and a column of 1s + * row. Its shape is (N x 7) + */ + Matrix<float, MaxSamples, 7> samples; + unsigned numSamples; +}; diff --git a/src/shared/sensors/calibration/TwelveParameterCalibration.h b/src/shared/sensors/calibration/TwelveParameterCalibration.h new file mode 100644 index 0000000000000000000000000000000000000000..e8230697cf0abf24bf80cb5396aed6deaec5a361 --- /dev/null +++ b/src/shared/sensors/calibration/TwelveParameterCalibration.h @@ -0,0 +1,152 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#pragma once + +#include <Eigen/Core> +#include <Eigen/Geometry> + +#include "Calibration.h" + +using namespace Eigen; + +/** + * This type of correction uses a 3x3 matrix multiplied by the input + * data and adds a 3d vector bias, so that: + * + * | x' | | w11 w12 w13 | | x | | b1 | + * | y' | = | w21 w22 w23 | . | y | + | b2 | + * | z' | | w31 w32 w33 | | z | | b3 | + * + * Where (x, y, z) is the input and (x', y', z') the output. + */ +template <typename T> +class TwelveParameterCorrector : public ValuesCorrector<T> +{ +public: + TwelveParameterCorrector() + : TwelveParameterCorrector(Matrix3f::Identity(), {0, 0, 0}) + { + } + + TwelveParameterCorrector(const Matrix3f& _w, const Vector3f& _b) + : w(_w), b(_b) + { + } + + TwelveParameterCorrector(const Matrix<float, 3, 4>& mat) + { + operator<<(mat); + } + + void operator<<(const Matrix<float, 3, 4>& rhs) + { + w = rhs.block<3, 3>(0, 0); + b = rhs.col(3).transpose(); + } + + void operator>>(Matrix<float, 3, 4>& rhs) const + { + rhs.block<3, 3>(0, 0) = w; + rhs.col(3) = b.transpose(); + } + + void setIdentity() override + { + w.setIdentity(); + b.setZero(); + } + + T correct(const T& input) const + { + T output; + Vector3f vec; + + input >> vec; + vec = w * vec + b; + output << vec; + + return output; + } + +private: + Matrix3f w; + Vector3f b; +}; + +template <typename T, unsigned MaxSamples> +class TwelveParameterCalibration + : public AbstractCalibrationModel<T, TwelveParameterCorrector<T>, + AxisOrientation> +{ +public: + TwelveParameterCalibration() : samples(), numSamples(0), ref({1, 0, 0}) {} + + void setReferenceVector(Vector3f vec) { ref = vec; } + Vector3f getReferenceVector() { return ref; } + + bool feed(const T& data, const AxisOrientation& transform) override + { + if (numSamples == MaxSamples) + return false; + + Vector3f expected, measured; + data >> measured; + expected = transform.getMatrix().transpose() * ref; + + samples.row(numSamples) << measured.transpose(), 1, + expected.transpose(); + numSamples++; + + return true; + } + + TwelveParameterCorrector<T> computeResult() override + { + Matrix<float, 3, 4> solutions; + MatrixXf coeffs = samples.block(0, 0, numSamples, 4); + auto qr = coeffs.colPivHouseholderQr(); + + for (int i = 0; i < 3; ++i) + { + VectorXf terms = samples.block(0, 4 + i, numSamples, 1); + Vector4f sol = qr.solve(terms); + solutions.col(i) = sol.head<3>(); + solutions(i, 3) = sol[3]; + } + + return {solutions}; + } + +private: + /* + * The matrix contains x, y, z measured and x', y', z' expected for each + * sample. Between (x, y, z) and (x', y', z') there is a column of 1s + * Its shape is (N x 7) + */ + Matrix<float, MaxSamples, 7> samples; + unsigned numSamples; + Vector3f ref; + +public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW +}; diff --git a/src/shared/sensors/ADIS16405/ADIS16405.h b/src/shared/sensors/old_examples/ADIS16405/ADIS16405.h similarity index 98% rename from src/shared/sensors/ADIS16405/ADIS16405.h rename to src/shared/sensors/old_examples/ADIS16405/ADIS16405.h index 89b0af4ed9aff3ddcd1b8b017425d29138b4917f..48d537b392fd7e3ee1a429ff3968aa093444fd60 100644 --- a/src/shared/sensors/ADIS16405/ADIS16405.h +++ b/src/shared/sensors/old_examples/ADIS16405/ADIS16405.h @@ -1,7 +1,5 @@ -/* ADIS16405 Driver - * - * Copyright (c) 2018-2019 Skyward Experimental Rocketry - * Authors: Nuno Barcellos +/* Copyright (c) 2018-2019 Skyward Experimental Rocketry + * Author: Nuno Barcellos * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/ADIS16405/ADIS16405Data.h b/src/shared/sensors/old_examples/ADIS16405/ADIS16405Data.h similarity index 94% rename from src/shared/sensors/ADIS16405/ADIS16405Data.h rename to src/shared/sensors/old_examples/ADIS16405/ADIS16405Data.h index 5ce9f6f89b96294ac250f6d78ed4ff09ecbb3d8d..f414b7e06e565854a5dfd9f134303510b27dbb76 100644 --- a/src/shared/sensors/ADIS16405/ADIS16405Data.h +++ b/src/shared/sensors/old_examples/ADIS16405/ADIS16405Data.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Nuno Barcellos +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Nuno Barcellos * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/FXAS21002.h b/src/shared/sensors/old_examples/FXAS21002.h similarity index 97% rename from src/shared/sensors/FXAS21002.h rename to src/shared/sensors/old_examples/FXAS21002.h index 8c396bbb4692c3db8e8c45eebade045e6e12c7c9..32a90964302eb95e2a1edf525e3412c2fccd7952 100644 --- a/src/shared/sensors/FXAS21002.h +++ b/src/shared/sensors/old_examples/FXAS21002.h @@ -1,7 +1,5 @@ -/* FXAS21002 Driver - * - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci, Matteo Michele Piazzolla, Silvano Seva +/* Copyright (c) 2016 Skyward Experimental Rocketry + * Authors: Alain Carlucci, Matteo Piazzolla, Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/LM75B.h b/src/shared/sensors/old_examples/LM75B.h similarity index 97% rename from src/shared/sensors/LM75B.h rename to src/shared/sensors/old_examples/LM75B.h index 509829c10c8bc8954bce8727614d092b97ec5f59..031eecfc896d48baca9d1419fa7db25891ff6448 100644 --- a/src/shared/sensors/LM75B.h +++ b/src/shared/sensors/old_examples/LM75B.h @@ -1,7 +1,5 @@ -/* LM75B Driver - * - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Alessio Galluccio +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Alessio Galluccio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/LPS331AP.h b/src/shared/sensors/old_examples/LPS331AP.h similarity index 95% rename from src/shared/sensors/LPS331AP.h rename to src/shared/sensors/old_examples/LPS331AP.h index f7c01e8d3826a73f62ac5f3c811f3752a60f1de8..a7d58338748b5edf2f798896e8e0bab29db93b5b 100644 --- a/src/shared/sensors/LPS331AP.h +++ b/src/shared/sensors/old_examples/LPS331AP.h @@ -1,7 +1,5 @@ -/* LPS331AP Driver - * - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Alain Carlucci +/* Copyright (c) 2016 Skyward Experimental Rocketry + * Author: Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3H.h b/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3H.h new file mode 100644 index 0000000000000000000000000000000000000000..c30d71139d41979497a4dfc0e57ad8dc254b89c2 --- /dev/null +++ b/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3H.h @@ -0,0 +1,218 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Nuno Barcellos + * + * 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. + */ + +#ifndef LSM6DS3H_H +#define LSM6DS3H_H + +#include <drivers/BusTemplate.h> + +#include "../Sensor.h" +#include "Common.h" +#include "miosix.h" + +/** ********ATTENTION*********** + * @warning Once upon a time, this driver was working, but due to hardware + * problems we were unable to confirm it and test it properly. If you need this, + * you should test it a bit before using it in production code. + */ + +template <typename Bus> +class LSM6DS3H : public GyroSensor, + public AccelSensor, + public CompassSensor, + public TemperatureSensor +{ +#pragma pack(1) + // __extension__ is needed to prevent compiler warnings for anonymous + // structs + typedef union { + __extension__ struct + { + int16_t temp; + int16_t gyro[3]; + int16_t accel[3]; + }; + int16_t buf[8]; + } lsmData_t; + +#pragma pack() + +public: + LSM6DS3H(uint8_t accelFullScale, uint16_t gyroFullScale) + { + accelFS = accelFullScale; + gyroFS = gyroFullScale; + } + + bool init() override + { + // SPI clock frequency up to 10 MHz + // The device is compatible with SPI modes 0 and 3 + + uint8_t whoami = Bus::read(RegMap::WHO_AM_I); + printf("[LSM] expected: %x actual: %x\n", whoami_value, whoami); + if (whoami != whoami_value) + { + last_error = ERR_NOT_ME; + return false; + } + + // Reset device + uint8_t reg = Bus::read(RegMap::CTRL3_C); + Bus::write(reg | 0x01); + + miosix::Thread::sleep(100); + + // clang-format off + uint8_t init_data[][2] = + { + {RegMap::CTRL3_C, 0x44}, // Register address automatically incremented during a multiple + // byte access with a serial interface; LSB @ lower address; + // SPI 4 wire; Output registers not updated until MSB and LSB + // have been read + {RegMap::CTRL1_XL, (uint8_t) (0x70 | (accelFS << 2))}, // Accel ODR to 833 Hz, + // Anti-aliasing filter to 400 hz + // {RegMap::CTRL5_C, 0x60}, // Rounding read enabled for both accel and gyro + {RegMap::CTRL2_G, (uint8_t) (0x70 | (gyroFS << 2))}, // Gyro ODR to 833 Hz + }; + // clang-format on + + for (size_t i = 0; i < sizeof(init_data) / sizeof(init_data[0]); i++) + Bus::write(init_data[i][0], init_data[i][1]); + + return true; + } + + bool selfTest() override { return true; } + + bool onSimpleUpdate() + { + lsmData_t raw_data; + uint8_t buf[20]; + + // Read temp, gyro, accel + Bus::read(RegMap::OUT_TEMP_L, buf, 14); + memcpy(&raw_data.buf, buf, 14); + + mLastTemp = normalizeTemp(raw_data.temp); + + mLastGyro.setX(normalizeGyro(raw_data.gyro[0])); + mLastGyro.setY(normalizeGyro(raw_data.gyro[1])); + mLastGyro.setZ(normalizeGyro(raw_data.gyro[2])); + + mLastAccel.setX(normalizeAccel(raw_data.accel[0])); + mLastAccel.setY(normalizeAccel(raw_data.accel[1])); + mLastAccel.setZ(normalizeAccel(raw_data.accel[2])); + + return true; + } + + // clang-format off + enum gyroFullScale + { + GYRO_FS_250 = 0, + GYRO_FS_500 = 1, + GYRO_FS_1000 = 2, + GYRO_FS_2000 = 3 + }; + + enum accelFullScale + { + ACC_FS_2G = 0, // Do not change this sequence + ACC_FS_4G = 2, + ACC_FS_8G = 3, + ACC_FS_16G = 1 + }; + // clang-format on + +private: + constexpr static uint8_t whoami_value = 0x69; + constexpr static float accelFSMAP[4] = {0.061, 0.488, 0.122, 0.244}; + constexpr static float gyroFSMAP[4] = {8.75, 17.50, 35.0, 70.0}; + uint8_t accelFS; + uint16_t gyroFS; + + inline float normalizeAccel(int16_t val) + { + return static_cast<float>(val) / 1000.0f * accelFSMAP[accelFS] * + EARTH_GRAVITY; // [m/ss] + } + + inline float normalizeGyro(int16_t val) + { + return static_cast<float>(val) / 1000.0f * gyroFSMAP[gyroFS] * + DEGREES_TO_RADIANS; // [rad/s] + } + + inline float normalizeTemp(int16_t val) + { + return static_cast<float>(val) / 16.0f + 25.0f; // [deg C] + } + + enum RegMap + { + WHO_AM_I = 0x0F, // default value + + // Accelerometer and gyroscope control registers + CTRL1_XL = 0x10, + CTRL2_G = 0x11, + CTRL3_C = 0x12, + CTRL4_C = 0x13, + CTRL5_C = 0x14, + CTRL6_C = 0x15, + CTRL7_G = 0x16, + CTRL8_XL = 0x17, + CTRL9_XL = 0x18, + CTRL10_C = 0x19, + + // Temperature output registers + OUT_TEMP_L = 0x20, + OUT_TEMP_H = 0x21, + + // Gyro data registers + OUT_X_L_G = 0x22, + OUT_X_H_G = 0x23, + OUT_Y_L_G = 0x24, + OUT_Y_H_G = 0x25, + OUT_Z_L_G = 0x26, + OUT_Z_H_G = 0x27, + + // Accelerometer output registers + OUT_X_L_XL = 0x28, + OUT_X_H_XL = 0x29, + OUT_Y_L_XL = 0x2A, + OUT_Y_H_XL = 0x2B, + OUT_Z_L_XL = 0x2C, + OUT_Z_H_XL = 0x2D, + + // STATUS register + STATUS_REG = 0X1E + }; +}; + +template <typename Bus> +constexpr float LSM6DS3H<Bus>::accelFSMAP[]; + +template <typename Bus> +constexpr float LSM6DS3H<Bus>::gyroFSMAP[]; + +#endif /* ifndef LSM6DS3H */ diff --git a/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3HData.h b/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3HData.h new file mode 100644 index 0000000000000000000000000000000000000000..abd46b5293279a93ce95cc6c2060a86c4dab811e --- /dev/null +++ b/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3HData.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Nuno Barcellos + * + * 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. + */ +#ifndef SRC_SHARED_SENSORS_LSM6DS3H_LSM6DS3HDATA_H +#define SRC_SHARED_SENSORS_LSM6DS3H_LSM6DS3HDATA_H + +#include <ostream> +#include "math/Vec3.h" + +struct LSM6DS3HData +{ + long long timestamp; + Vec3 accel; + Vec3 gyro; + float temp; + + static std::string header() + { + return "timestamp,acc_x,acc_y,acc_z,gyro_x,gyro_y,gyro_z\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << accel.getX() << "," << accel.getY() << "," + << accel.getZ() << "," << gyro.getX() << "," << gyro.getY() << "," + << gyro.getZ() << "\n"; + } +}; + +#endif /* SRC_SHARED_SENSORS_LSM6DS3H_LSM6DS3HDATA_H */ diff --git a/src/shared/sensors/MAX21105.h b/src/shared/sensors/old_examples/MAX21105.h similarity index 97% rename from src/shared/sensors/MAX21105.h rename to src/shared/sensors/old_examples/MAX21105.h index 3091e9beb56efc820d9b1b082f768ee5fc2a6e6f..21123002afd93f724e3ae439a94ed35955d94980 100644 --- a/src/shared/sensors/MAX21105.h +++ b/src/shared/sensors/old_examples/MAX21105.h @@ -1,7 +1,5 @@ -/* MAX21105 Driver - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Alain Carlucci +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/MAX31856.h b/src/shared/sensors/old_examples/MAX31856.h similarity index 97% rename from src/shared/sensors/MAX31856.h rename to src/shared/sensors/old_examples/MAX31856.h index 24f5148844b169092de4c54b715afa0600122e33..49a15ae76356f78657b24055c7e8dd9938828f92 100644 --- a/src/shared/sensors/MAX31856.h +++ b/src/shared/sensors/old_examples/MAX31856.h @@ -1,7 +1,5 @@ -/* MAX31856 Driver - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Authors: Silvano Seva +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/MPL3115.h b/src/shared/sensors/old_examples/MPL3115.h similarity index 97% rename from src/shared/sensors/MPL3115.h rename to src/shared/sensors/old_examples/MPL3115.h index acb1cd96219d63280b7ad19f10f43bf8159dcefd..00c545ff481cc63294e46a32ae100859381f9c17 100644 --- a/src/shared/sensors/MPL3115.h +++ b/src/shared/sensors/old_examples/MPL3115.h @@ -1,7 +1,5 @@ -/* Freescale MPL3115A barometer driver - * - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Silvano Seva +/* Copyright (c) 2016 Skyward Experimental Rocketry + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/Si7021.h b/src/shared/sensors/old_examples/Si7021.h similarity index 96% rename from src/shared/sensors/Si7021.h rename to src/shared/sensors/old_examples/Si7021.h index 01e3caef8284cfdb0fde19c244b47515632722d0..e0b43629a9cde9dc73470727cc3bf3536e3071d7 100644 --- a/src/shared/sensors/Si7021.h +++ b/src/shared/sensors/old_examples/Si7021.h @@ -1,7 +1,5 @@ -/* Si7021 Driver - * - * Copyright (c) 2016 Skyward Experimental Rocketry - * Authors: Silvano Seva +/* Copyright (c) 2016 Skyward Experimental Rocketry + * Author: Silvano Seva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/sensors/iNemo.h b/src/shared/sensors/old_examples/iNemo.h similarity index 97% rename from src/shared/sensors/iNemo.h rename to src/shared/sensors/old_examples/iNemo.h index 6d02eaed06f8075027efe06dd0cc839af06dff35..dddd4155f491843e2000a7d908212b0031e3b218 100644 --- a/src/shared/sensors/iNemo.h +++ b/src/shared/sensors/old_examples/iNemo.h @@ -1,7 +1,5 @@ -/* ST iNEMO LSM9DS0 Driver - * - * Copyright (c) 2016-2017 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla, Alain Carlucci +/* Copyright (c) 2016-2017 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/utils/ButtonHandler.h b/src/shared/utils/ButtonHandler.h index 7e19669afe78d6496b7e8c442b4badde33d05e86..31fca2d6e3451f4232f4413729e5b5758b9e8d3e 100644 --- a/src/shared/utils/ButtonHandler.h +++ b/src/shared/utils/ButtonHandler.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -22,6 +22,8 @@ #pragma once +#include <diagnostic/PrintLogger.h> + #include <functional> #include "ActiveObject.h" @@ -83,20 +85,21 @@ protected: { if (pressed_ticks >= VERY_LONG_PRESS_TICKS) { - TRACE("Button pressed (very long) (%d ticks).\n", - pressed_ticks); + LOG_DEBUG(logger, "Button pressed (very long) (%d ticks)", + pressed_ticks); callback(btn_id, ButtonPress::VERY_LONG); } else if (pressed_ticks >= LONG_PRESS_TICKS) { - TRACE("Button pressed (long) (%d ticks).\n", pressed_ticks); + LOG_DEBUG(logger, "Button pressed (long) (%d ticks)", + pressed_ticks); callback(btn_id, ButtonPress::LONG); } else { - TRACE("Button pressed (short) (%d ticks).\n", - pressed_ticks); + LOG_DEBUG(logger, "Button pressed (short) (%d ticks)", + pressed_ticks); callback(btn_id, ButtonPress::SHORT); } @@ -116,4 +119,6 @@ private: int pressed_ticks = 0; ButtonCallback callback; + + PrintLogger logger = Logging::getLogger("buttonhandler"); }; \ No newline at end of file diff --git a/src/shared/utils/PinObserver.h b/src/shared/utils/PinObserver.h index e7fc197c7368d53510189ce5883203ad0fbfa51d..aaefb4cd4bccc3ebba70dc7f9c14df6e718e3228 100644 --- a/src/shared/utils/PinObserver.h +++ b/src/shared/utils/PinObserver.h @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/utils/aero/AeroUtils.cpp b/src/shared/utils/aero/AeroUtils.cpp index c035c679744deab813ce53a1d1fb3a326ff92a4b..73bfa22cd84db1651c8c6a25ebe61cd37368f3d8 100644 --- a/src/shared/utils/aero/AeroUtils.cpp +++ b/src/shared/utils/aero/AeroUtils.cpp @@ -1,3 +1,26 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + + #include "AeroUtils.h" namespace aeroutils @@ -10,6 +33,16 @@ float relAltitude(float pressure, float pressure_ref, float temperature_ref) return temperature_ref / a * (1 - powf(pressure / pressure_ref, n_inv)); } +float relDensity(float pressure, float pressure_ref, float altitude_ref, + float temperature_ref) +{ + using namespace constants; + + return pressure / + (R * a * altitude_ref + + R * temperature_ref * powf(pressure / pressure_ref, n_inv)); +} + float mslPressure(float pressure_ref, float temperature_ref, float altitude_ref) { using namespace constants; diff --git a/src/shared/utils/aero/AeroUtils.h b/src/shared/utils/aero/AeroUtils.h index 6dff5dce00f9878c3453a7188d3ae99132f183e4..90378715ec5a704bf5a38cd4898e86f33db9b52d 100644 --- a/src/shared/utils/aero/AeroUtils.h +++ b/src/shared/utils/aero/AeroUtils.h @@ -1,8 +1,5 @@ -/** - * Collection of functions for common aerospace calculations - * - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -16,7 +13,7 @@ * * 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 + * 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 @@ -64,6 +61,19 @@ constexpr float n_inv = (R * a) / g; */ float relAltitude(float pressure, float pressure_ref, float temperature_ref); +/** + * Returns the current air density with respect to a reference density and + * temperature, using the Internation Standard Atmosphere model. + * + * @param pressure Current atmospheric pressure [Pascal] + * @param pressure_ref Pressure at reference altitude (must be > 0) [Pascal] + * @param altitude_ref Reference altitude [m] + * @param temperature_ref Temperature at reference altitude [Kelvin] + * @return Current air density [Kg/m^3] + */ +float relDensity(float pressure, float pressure_ref, float altitude_ref, + float temperature_ref); + /** * Returns the expected pressure at mean sea level based on temperature and * pressure at a reference altitude, using International Standard Atmosphere diff --git a/src/shared/utils/collections/CircularBuffer.h b/src/shared/utils/collections/CircularBuffer.h index 49b88bf6de8cb535a05fe79fc96ef8ad594d11af..be08c9b30c612c07481bc068974f9c2332cd4b18 100644 --- a/src/shared/utils/collections/CircularBuffer.h +++ b/src/shared/utils/collections/CircularBuffer.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -47,7 +47,7 @@ public: virtual T& put(const T& elem) { buffer[write_ptr] = elem; - T& added = buffer[write_ptr]; + T& added = buffer[write_ptr]; if (!empty && write_ptr == read_ptr) { @@ -62,12 +62,15 @@ public: /** * Gets an element from the buffer, without removing it - * Index starts at the element returned by get() or pop(): get(0) is - * the same as get() + * Index starts from the oldest element in the buffer: get(0) returns the + * same element as get() * * @warning Remember to catch the exception! + * @throw range_error if index >= count() + * + * @param i Index of the elemnt to get, starting from the oldest + * * @return the element - * @throws range_error if buffer is empty */ virtual T& get(unsigned int i) { @@ -81,21 +84,19 @@ public: } /** - * @brief Returns the last element added in the buffer + * @brief Returns the last element added in the buffer. + * + * @throw range_error if buffer is empty * @warning Remember to catch the exception! * @return the element - * @throws range_error if buffer is empty */ - virtual T& last() - { - return get(count() - 1); - } + virtual T& last() { return get(count() - 1); } /** * Gets the first element from the buffer, without removing it + * @throw range_error if buffer is empty * @warning Remember to catch the exception! * @return the element - * @throws range_error if buffer is empty */ virtual T& get() { @@ -109,9 +110,9 @@ public: /** * Pops the first element in the buffer. + * @throw range_error if buffer is empty * @warning Remember to catch the exception! * @return the element that has been popped - * @throws range_error if buffer is empty */ virtual const T& pop() { @@ -148,7 +149,10 @@ public: virtual bool isEmpty() const { return empty; } - virtual bool isFull() const { return count() == Size; } + virtual bool isFull() const + { + return CircularBuffer<T, Size>::count() == Size; + } /** * Returns the maximum number of elements that can be stored in the buffer * @return buffer size diff --git a/src/shared/utils/collections/SyncCircularBuffer.h b/src/shared/utils/collections/SyncCircularBuffer.h index 1844d78007b40099dd3171550ac1e689329321b4..5d09f360453556425b77141736648bf634154303 100644 --- a/src/shared/utils/collections/SyncCircularBuffer.h +++ b/src/shared/utils/collections/SyncCircularBuffer.h @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/utils/collections/SyncPacketQueue.h b/src/shared/utils/collections/SyncPacketQueue.h index c9970c8304c7b94f8523df17cd45f91636cefba5..2c00c03f5dc75e1822252088a59c5cbd8cffbaf1 100644 --- a/src/shared/utils/collections/SyncPacketQueue.h +++ b/src/shared/utils/collections/SyncPacketQueue.h @@ -1,6 +1,4 @@ -/* Synchronized Packet Queue - * - * Copyright (c) 2019 Skyward Experimental Rocketry +/* Copyright (c) 2019 Skyward Experimental Rocketry * Author: Alvise de'Faveri Tron * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +13,7 @@ * * 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 + * 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 @@ -33,7 +31,6 @@ #include "CircularBuffer.h" - // This header can be compiled to run on a PC, for easier testing. #ifdef COMPILE_FOR_X86 #warning The flag COMPILE_FOR_X86 is active! If this is flight code, shame on you @@ -41,13 +38,13 @@ #define MIOSIX_ONLY(x) #else #define MIOSIX_ONLY(x) x -#include "miosix.h" #include "Debug.h" +#include "miosix.h" using miosix::ConditionVariable; -using std::range_error; using miosix::FastMutex; using miosix::Lock; +using std::range_error; #endif /******************************************************************************* @@ -266,16 +263,16 @@ public: { // Mark the packet as ready (in the case it wasn't already) last.markAsReady(); - - if(buffer.isFull()) + + if (buffer.isFull()) { // We have dropped a packet ++dropped; } // Add a new packet and fill that instead Pkt& newpkt = buffer.put(Pkt{}); - - if(!newpkt.tryAppend(msg, msg_len)) + + if (!newpkt.tryAppend(msg, msg_len)) { TRACE("Packet is too big!\n"); return -1; diff --git a/src/shared/utils/collections/SyncQueue.h b/src/shared/utils/collections/SyncQueue.h index 234300c1b2a13c8e58ab2b96f54254ba179b940b..1de11bd8c103d433dd737cc7f6a7ccfd4983230b 100644 --- a/src/shared/utils/collections/SyncQueue.h +++ b/src/shared/utils/collections/SyncQueue.h @@ -1,7 +1,5 @@ -/* FSM SynchronizedQueue - * - * Copyright (c) 2015-2016 Skyward Experimental Rocketry - * Author: Matteo Michele Piazzolla, Alain Carlucci +/* Copyright (c) 2015-2016 Skyward Experimental Rocketry + * Authors: Matteo Piazzolla, Alain Carlucci * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -15,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/drivers/piksi/contiguous_queue.h b/src/shared/utils/collections/contiguous_queue.h similarity index 64% rename from src/shared/drivers/piksi/contiguous_queue.h rename to src/shared/utils/collections/contiguous_queue.h index 1fa3587f438fd7a8fe56554cc9f3d6957ddc8a13..989beb7ddcaf028e57ce2bd37003e5504a1d4799 100644 --- a/src/shared/drivers/piksi/contiguous_queue.h +++ b/src/shared/utils/collections/contiguous_queue.h @@ -1,29 +1,25 @@ -/*************************************************************************** - * Copyright (C) 2017 by Terraneo Federico * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * As a special exception, if other files instantiate templates or use * - * macros or inline functions from this file, or you compile this file * - * and link it with other works to produce a work based on this file, * - * this file does not by itself cause the resulting work to be covered * - * by the GNU General Public License. However the source code for this * - * file must still be made available in accordance with the GNU General * - * Public License. This exception does not invalidate any other reasons * - * why a work based on this file might be covered by the GNU General * - * Public License. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, see <http://www.gnu.org/licenses/> * - ***************************************************************************/ +/* Copyright (c) 2017 Skyward Experimental Rocketry + * Author: Federico Terraneo + * + * 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. + */ + #ifndef CONTIGUOUS_QUEUE #define CONTIGUOUS_QUEUE diff --git a/src/shared/utils/gui/GridLayout.h b/src/shared/utils/gui/GridLayout.h new file mode 100644 index 0000000000000000000000000000000000000000..27ae0c02237eeeedbe5a2aa0e0e2a866ef263980 --- /dev/null +++ b/src/shared/utils/gui/GridLayout.h @@ -0,0 +1,208 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <mxgui/misc_inst.h> + +#include <map> + +#include "View.h" + +/** + * @brief Displays childs in a num_rows*num_cols grid + */ +class GridLayout : public View +{ +public: + using GridPosition = std::pair<uint8_t, uint8_t>; + + /** + * @brief Creates a new GridLayout + * + * @param num_rows Number of rows + * @param num_cols Number of columns + * @param spacing Distance in pixels between each cell + */ + GridLayout(uint8_t num_rows, uint8_t num_cols, short int spacing = 0) + : View(), num_rows(num_rows), num_cols(num_cols), spacing(spacing) + { + } + + virtual ~GridLayout() {} + + void setCell(View* child, unsigned int position) + { + uint8_t col = position % num_cols; + uint8_t row = (position - col) / num_cols; + + setCell(child, row, col); + } + + void setCell(View* child, uint8_t row, uint8_t col) + { + if (row >= num_rows || col >= num_cols) + { + return; + } + + map_childs[GridPosition(row, col)] = child; + + updateChildBounds(); + invalidate(); + } + + View* getCell(uint8_t row, uint8_t col) + { + GridPosition pos(row, col); + + if (map_childs.count(pos) > 0) + return map_childs[pos]; + else + return nullptr; + } + + View* getCell(unsigned int position) + { + uint8_t col = position % num_cols; + uint8_t row = position - col / num_rows; + + return getCell(row, col); + } + + void clearCell(View* child) + { + for (auto it = map_childs.begin(); it != map_childs.end(); it++) + { + if (it->second == child) + { + map_childs.erase(it); + break; + } + } + + invalidate(); + } + + /** + * @brief Wether to draw the borders of each cell or not + * + * @param draw_border True: draw the border + * @param color Border color + */ + void setDrawBorder(bool draw_border, mxgui::Color color = mxgui::white) + { + this->draw_border = draw_border; + border_color = color; + + invalidate(); + } + + void setBounds(Bounds bounds) override + { + View::setBounds(bounds); + + updateChildBounds(); + } + + virtual void draw(mxgui::DrawingContext& context) override + { + View::draw(context); + + for (uint8_t row = 0; row < num_rows; ++row) + { + for (uint8_t col = 0; col < num_cols; ++col) + { + GridPosition pos(row, col); + + bool child_selected = false; + if (map_childs.count(pos) > 0) + { + map_childs[pos]->draw(context); + child_selected = map_childs[pos]->isSelected(); + } + + if (draw_border && !child_selected) + { + context.drawRectangle( + map_child_bounds[pos].topLeft(), + map_child_bounds[pos].bottomRight(), border_color); + } + } + } + } + + uint8_t getRows() { return num_rows; } + + uint8_t getCols() { return num_cols; } + + std::vector<View*> getChilds() override + { + std::vector<View*> out; + for (auto it = map_childs.begin(); it != map_childs.end(); it++) + { + out.push_back(it->second); + } + return out; + } + +private: + void updateChildBounds() + { + Bounds bounds = getBounds(); + Bounds child_bounds; + + child_bounds.size.width = std::max( + 0, (bounds.size.width - (num_cols + 1) * spacing) / num_cols); + child_bounds.size.height = std::max( + 0, (bounds.size.height - (num_rows + 1) * spacing) / num_rows); + + for (uint8_t row = 0; row < num_rows; ++row) + { + for (uint8_t col = 0; col < num_cols; ++col) + { + GridPosition pos(row, col); + + child_bounds.pos.x = bounds.pos.x + (col + 1) * spacing + + col * child_bounds.size.width; + child_bounds.pos.y = bounds.pos.y + (row + 1) * spacing + + row * child_bounds.size.height; + + map_child_bounds[pos] = child_bounds; + if (map_childs.count(pos) > 0) + { + map_childs[pos]->setBounds(child_bounds); + } + } + } + } + + uint8_t num_rows; + uint8_t num_cols; + short int spacing; + + bool draw_border = false; + mxgui::Color border_color = mxgui::white; + + std::map<GridPosition, View*> map_childs; + std::map<GridPosition, Bounds> map_child_bounds; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/ImageView.h b/src/shared/utils/gui/ImageView.h new file mode 100644 index 0000000000000000000000000000000000000000..4168ba748bb479bffb36ec11789150f4c0eb8eb8 --- /dev/null +++ b/src/shared/utils/gui/ImageView.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <mxgui/image.h> + +#include "View.h" + +/** + * @brief Simple view that displays an image + */ +class ImageView : public View +{ +public: + ImageView(const mxgui::Image* image) : image(image) {} + + void setImage(mxgui::Image* image) + { + this->image = image; + invalidate(); + } + + const mxgui::Image* getImage() { return image; } + + void draw(mxgui::DrawingContext& dc) override + { + if (isInvalidated()) + { + View::draw(dc); + + dc.clippedDrawImage(getBounds().topLeft(), getBounds().topLeft(), + getBounds().bottomRight(), *image); + } + } +private: + const mxgui::Image* image; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/Misc.h b/src/shared/utils/gui/Misc.h new file mode 100644 index 0000000000000000000000000000000000000000..62c97bde95bc0858e50d758ca8da2ff11c5c0e78 --- /dev/null +++ b/src/shared/utils/gui/Misc.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once +#include <mxgui/display.h> + +/** + * @brief Converts a 24bit color to a 16bit color + * + * @param rgb 24 bit color + * @return Color Closest 16 bit color + */ +mxgui::Color color24to16(uint32_t rgb) +{ + uint8_t r, g, b; + r = ((rgb >> 2) & 0xFF) >> 3; + g = ((rgb >> 1) & 0xFF) >> 3; + b = (rgb & 0xFF) >> 3; + return ((b << 10) | (g << 5) | (r)); +} \ No newline at end of file diff --git a/src/shared/utils/gui/NavController.h b/src/shared/utils/gui/NavController.h new file mode 100644 index 0000000000000000000000000000000000000000..3d097fdc8340bf83d19b53de19d9fc739c8ac55f --- /dev/null +++ b/src/shared/utils/gui/NavController.h @@ -0,0 +1,151 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <diagnostic/PrintLogger.h> + +#include <vector> + +#include "Debug.h" +#include "TextView.h" +#include "View.h" +#include "utils/ButtonHandler.h" + +/** + * @brief UI navigation controller: listens for button clicks and dispatches the + * interactions to the view tree. + */ +class NavController +{ +public: + NavController() {} + + /** + * @brief + * + * @param root + */ + void updateViewTree(View* root) + { + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index)->setSelected(false); + } + + vec_selectable.clear(); + selected_index = 0; + + updateSelectableViews(root); + + if (vec_selectable.size() > 0) + { + vec_selectable.at(0)->setSelected(true); + } + } + + void onButtonPress(ButtonPress press) + { + switch (press) + { + case ButtonPress::SHORT: + if (vec_selectable.size() > 0) + { + selectNext(); + } + break; + case ButtonPress::DOWN: + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index) + ->performInteraction(Interaction::BTN_DOWN); + } + break; + case ButtonPress::UP: + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index) + ->performInteraction(Interaction::BTN_UP); + } + break; + case ButtonPress::LONG: + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index) + ->performInteraction(Interaction::CLICK); + } + break; + case ButtonPress::VERY_LONG: + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index) + ->performInteraction(Interaction::LONG_CLICK); + } + break; + default: + break; + } + } + +private: + void selectNext() + { + // Deselect old drawble + if (selected_index < vec_selectable.size()) + { + vec_selectable.at(selected_index)->setSelected(false); + } + + if (vec_selectable.size() > 0) + { + selected_index = (selected_index + 1) % vec_selectable.size(); + + vec_selectable.at(selected_index)->setSelected(true); + + TextView* text = + dynamic_cast<TextView*>(vec_selectable.at(selected_index)); + + if (text) + { + LOG_DEBUG(logger, "{}", text->getText().c_str()); + } + } + } + + void updateSelectableViews(View* root) + { + std::vector<View*> childs = root->getChilds(); + for (auto child : childs) + { + if (child->isSelectable()) + { + vec_selectable.push_back(child); + } + updateSelectableViews(child); + } + } + + unsigned int selected_index = 0; + std::vector<View*> vec_selectable; + + PrintLogger logger = Logging::getLogger("navcontroller"); +}; \ No newline at end of file diff --git a/src/shared/utils/gui/OptionView.h b/src/shared/utils/gui/OptionView.h new file mode 100644 index 0000000000000000000000000000000000000000..a10aad93ff1bad27d7b0cf6cdf41d950ef1e5c9c --- /dev/null +++ b/src/shared/utils/gui/OptionView.h @@ -0,0 +1,169 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <map> +#include <string> + +#include "GridLayout.h" +#include "TextView.h" + +/** + * @brief View used to display an option list, so the user can select one by + * clicking on it. + */ +class OptionView : public View +{ +public: + using OnOptionChosenListener = std::function<void(unsigned int id)>; + + OptionView(std::string name, std::map<uint8_t, std::string> options, + uint8_t default_option = 0, uint8_t num_cols = 4) + : tv_title(new TextView(name)) + { + uint8_t num_rows = options.size() / num_cols; + if (options.size() % num_cols != 0) + { + ++num_rows; + } + + grid_options = new GridLayout(num_rows, num_cols); + + unsigned int grid_pos = 0; + unsigned int i = 0; + for (auto it = options.begin(); it != options.end(); it++) + { + using namespace std::placeholders; + + TextView* tv_opt = new TextView(it->second); + tv_opt->setSelectable(true); + tv_opt->addOnInteractionListener( + std::bind(&OptionView::onOptionClick, this, _1, _2)); + tv_opt->setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + map_options[tv_opt] = it->first; + + grid_options->setCell(tv_opt, grid_pos++); + + if (i == default_option) + { + selectOption(tv_opt); + } + + ++i; + } + } + + virtual ~OptionView() + { + delete grid_options; + delete tv_title; + for (auto it = map_options.begin(); it != map_options.end(); it++) + { + delete it->first; + } + } + + void setBounds(Bounds bounds) override + { + View::setBounds(bounds); + + Bounds title_bounds = getBounds(); + title_bounds.size.height = tv_title->getFont().getHeight(); + tv_title->setBounds(title_bounds); + + Bounds grid_bounds = getBounds(); + grid_bounds.pos.y = getBounds().pos.y + title_bounds.size.height + 1; + grid_bounds.size.height = + getBounds().size.height - title_bounds.size.height; + + grid_options->setBounds(grid_bounds); + } + + /** + * @brief Adds a callback to be called each time an option is selected by + * the user + */ + void addOnOptionChosenListener(OnOptionChosenListener listener) + { + opt_listeners.push_back(listener); + } + + std::vector<View*> getChilds() override + { + std::vector<View*> out; + out.push_back(tv_title); + out.push_back(grid_options); + + return out; + } + + void draw(mxgui::DrawingContext& dc) override + { + View::draw(dc); + tv_title->draw(dc); + grid_options->draw(dc); + } + +private: + void selectOption(TextView* opt) + { + if (selected_option != nullptr) + { + selected_option->setBackgroundColor(col_bg_normal); + } + + selected_option = opt; + + selected_option->setBackgroundColor(col_bg_highlight); + } + + void onOptionClick(View* option, Interaction action) + { + if (action == Interaction::CLICK) + { + TextView* textview = static_cast<TextView*>(option); + + selectOption(textview); + + for (auto l : opt_listeners) + { + if (l != nullptr) + { + l(map_options[textview]); + } + } + } + } + + TextView* tv_title; + mxgui::Color col_bg_normal = mxgui::black; + mxgui::Color col_bg_highlight = mxgui::blue; + + GridLayout* grid_options; + std::map<TextView*, uint8_t> map_options; + + std::vector<OnOptionChosenListener> opt_listeners; + + TextView* selected_option = nullptr; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/ScreenManager.h b/src/shared/utils/gui/ScreenManager.h new file mode 100644 index 0000000000000000000000000000000000000000..2738f12cb0abec4e95b76b410d55751a005954d5 --- /dev/null +++ b/src/shared/utils/gui/ScreenManager.h @@ -0,0 +1,124 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <miosix.h> + +#include <map> +#include <deque> + +#include "NavController.h" +#include "View.h" + +/** + * @brief UI Thread: Manages multiple view trees ("Screen") and draws the active + * one at the provided refresh rate + */ +class ScreenManager : public ActiveObject +{ +public: + ScreenManager(mxgui::DisplayManager& display, unsigned int refresh_rate) + : dc(display.getDisplay()), refresh_interval(1000 / refresh_rate) + { + } + + void showScreen(uint8_t id) + { + active_screen = id; + controller.updateViewTree(screens[id]); + } + + uint8_t getScreen() { return active_screen; } + + void addScreen(uint8_t id, View* root) + { + screens[id] = root; + + root->setBounds({{0, 0}, {dc.getWidth(), dc.getHeight()}}); + + if (screens.size() == 1) + { + showScreen(id); + } + } + + void onButtonPress(ButtonPress press) { controller.onButtonPress(press); } + + mxgui::DrawingContext& getDrawingContext() { return dc; } + +protected: + void run() override + { + uint8_t last_screen = 0; + while (!shouldStop()) + { + if (active_screen != last_screen) + { + last_screen = active_screen; + dc.clear(mxgui::black); + screens[active_screen]->invalidateTree(); + } + + long long start = miosix::getTick(); + + drawViewTree(screens[active_screen], dc); + + Thread::sleepUntil(start + refresh_interval); + } + } +private: + /** + * @brief Draws the provided view tree on screen + * + * @param root Root of the view tree + * @param dc Drawing context + */ + void drawViewTree(View* root, mxgui::DrawingContext& dc) + { + // Avoid recursion + std::deque<View*> views_dc; + views_dc.push_back(root); + + while(views_dc.size() != 0) + { + View* view = views_dc.front(); + views_dc.pop_front(); + + view->draw(dc); + + for(View* c : view->getChilds()) + { + views_dc.push_back(c); + } + } + } + + mxgui::DrawingContext dc; + + unsigned int refresh_interval; + + NavController controller; + std::map<uint8_t, View*> screens; + + uint8_t active_screen = 0; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/TextView.h b/src/shared/utils/gui/TextView.h new file mode 100644 index 0000000000000000000000000000000000000000..f487d191c040ea55727db223a0399a8deff8ee8e --- /dev/null +++ b/src/shared/utils/gui/TextView.h @@ -0,0 +1,180 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <mxgui/misc_inst.h> + +#include "View.h" + +/** + * @brief Simple view to display text on screen. + * + */ +class TextView : public View +{ +public: + /** + * @brief Creates a new TextView + * + * @param text Text + * @param text_color Text color + * @param padding Spacing from view bounds in pixels + */ + TextView(std::string text = "", mxgui::Color text_color = mxgui::white, + uint8_t padding = 1) + : View(), text(text), col_text(text_color), padding(padding) + { + } + + void setVerticalAlignment(VertAlignment alignment) + { + vert_align = alignment; + invalidate(); + } + + void setHorizontalAlignment(HorizAlignment alignment) + { + horiz_align = alignment; + invalidate(); + } + + void setAlignment(HorizAlignment horiz_align, VertAlignment vert_align) + { + setVerticalAlignment(vert_align); + setHorizontalAlignment(horiz_align); + } + + void setText(std::string text) + { + if (text != this->text) + { + invalidate(); + this->text = text; + } + } + + std::string getText() { return text; } + + virtual void setTextColor(mxgui::Color color) + { + if (color != col_text) + { + this->col_text = color; + invalidate(); + } + } + + mxgui::Color getTextColor() { return col_text; } + + virtual void draw(mxgui::DrawingContext& dc) override + { + if (isInvalidated()) + { + View::draw(dc); + + Bounds padded_bounds = getBounds(); + padded_bounds.pos += Position{padding, padding}; + padded_bounds.size.height -= padding; + padded_bounds.size.width -= padding; + + Position top_left = padded_bounds.topLeft(); + switch (vert_align) + { + case VertAlignment::BOTTOM: + top_left.y = + padded_bounds.bottomLeft().y - dc.getFont().getHeight(); + break; + case VertAlignment::CENTER: + top_left.y = + padded_bounds.pos.y + + (padded_bounds.size.height - dc.getFont().getHeight()) / + 2; + break; + case VertAlignment::TOP: + break; + } + + switch (horiz_align) + { + case HorizAlignment::RIGHT: + top_left.x = + padded_bounds.topRight().x - getTextWidth(text); + break; + case HorizAlignment::CENTER: + top_left.x = + padded_bounds.pos.x + + (padded_bounds.size.width - getTextWidth(text)) / 2; + break; + case HorizAlignment::LEFT: + break; + } + + mxgui::Font old_font = dc.getFont(); + dc.setFont(font); + + dc.setTextColor(getTextColor(), getBackgroundColor()); + dc.clippedWrite(top_left, padded_bounds.topLeft(), + padded_bounds.bottomRight(), text); + + dc.setFont(old_font); + } + } + + void setFont(mxgui::Font font) + { + this->font = font; + invalidate(); + } + + mxgui::Font getFont() { return font; } + +protected: + short int getTextWidth(std::string text) + { + short int w = 0; + for (char c : text) + { + if (c >= font.getStartChar() && c <= font.getEndChar()) + { + if (font.isFixedWidth()) + { + w += font.getWidth(); + } + else + { + w += font.getWidths()[c - font.getStartChar()]; + } + } + } + return w; + } + +private: + mxgui::Font font = mxgui::tahoma; + VertAlignment vert_align = VertAlignment::TOP; + HorizAlignment horiz_align = HorizAlignment::LEFT; + std::string text; + + mxgui::Color col_text = mxgui::white; + uint8_t padding; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/VerticalLayout.h b/src/shared/utils/gui/VerticalLayout.h new file mode 100644 index 0000000000000000000000000000000000000000..d8da0215f74dc25a3ecea15e7e290d53e9ce58ed --- /dev/null +++ b/src/shared/utils/gui/VerticalLayout.h @@ -0,0 +1,123 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include "View.h" + +/** + * @brief Positions the childs in a vertical grid. The height of each child is + * dictated by its weight parameter + */ +class VerticalLayout : public View +{ +public: + /** + * @brief Creates a new VerticalLayout + * + * @param spacing Vertical space between each child in pixels + */ + VerticalLayout(short int spacing = 0) : View(), spacing(spacing) {} + + /** + * @brief Adds a view to the layout. + * + * @param view Pointer to the view to be added + * @param weight Relative weight of the view to determine its display height + */ + void addView(View* view, float weight) + { + if (weight < 0) + weight = 0; + childs.push_back({weight, view}); + weigth_sum += weight; + + // Recalculated child bounds + updateChildBounds(); + + // Redraw + invalidate(); + } + + void setBounds(Bounds bounds) override + { + View::setBounds(bounds); + + updateChildBounds(); + } + + void draw(mxgui::DrawingContext& dc) override + { + View::draw(dc); + + for (auto view : childs) + { + view.drawable->draw(dc); + } + } + + virtual std::vector<View*> getChilds() override + { + std::vector<View*> out; + out.reserve(childs.size()); + for (auto v : childs) + { + out.push_back(v.drawable); + } + return out; + } + +private: + void updateChildBounds() + { + if (weigth_sum > 0) + { + short int aval_height = + getBounds().size.height - (childs.size() - 1) * spacing; + + short int y = 0; + + for (auto view : childs) + { + Bounds view_bounds{{0, y}, {getBounds().size.width, 0}}; + + view_bounds.size.height = + aval_height / weigth_sum * view.vert_weight; + view_bounds.pos += getBounds().pos; + + view.drawable->setBounds(view_bounds); + + y += view_bounds.size.height + spacing; + } + } + } + + struct Child + { + float vert_weight; + View* drawable; + }; + + float weigth_sum = 0; + short int spacing; + std::vector<Child> childs; +}; \ No newline at end of file diff --git a/src/shared/utils/gui/View.h b/src/shared/utils/gui/View.h new file mode 100644 index 0000000000000000000000000000000000000000..230f318f40b72a6dfc32f452c634837990d1fdb3 --- /dev/null +++ b/src/shared/utils/gui/View.h @@ -0,0 +1,260 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <mxgui/display.h> +#include <mxgui/misc_inst.h> + +#include <functional> + +#include "Misc.h" + +struct Size +{ + short int width; + short int height; +}; + +struct Position +{ + short int x; + short int y; + + operator mxgui::Point() { return mxgui::Point(x, y); } + + Position operator+(const Position& other) + { + return {short(x + other.x), short(y + other.y)}; + } + + Position operator-(const Position& other) + { + return {short(x - other.x), short(y - other.y)}; + } + + void operator+=(const Position& other) + { + x += other.x; + y += other.y; + } + + void operator-=(const Position& other) + { + x -= other.x; + y -= other.y; + } + + void print() { printf("pos: %d %d\n", x, y); } +}; + +struct Bounds +{ + Position pos; + Size size; + + Position topLeft() { return {pos.x, pos.y}; } + Position topRight() { return {short(pos.x + size.width - 1), pos.y}; } + + Position bottomRight() + { + return {short(pos.x + size.width - 1), short(pos.y + size.height - 1)}; + } + + Position bottomLeft() + { + return {pos.x, short(pos.y + short(size.height - 1))}; + } + + void print() + { + printf("Bounds: p:{%d %d} s:{%d %d}\n", pos.x, pos.y, size.width, + size.height); + } +}; + +enum class VertAlignment +{ + TOP, + CENTER, + BOTTOM +}; + +enum class HorizAlignment +{ + LEFT, + CENTER, + RIGHT +}; + +enum class Interaction +{ + BTN_DOWN, + BTN_UP, + CLICK, + LONG_CLICK +}; + +/** + * @brief Base class for anything that can be drawn on the screen and interacted + * with + */ +class View +{ +public: + using OnInteractionListener = std::function<void(View*, Interaction)>; + + View() {} + virtual ~View() {} + + /** + * @brief Sets the bounds in which the view will be drawn + */ + virtual void setBounds(Bounds bounds) + { + this->bounds = bounds; + invalidate(); + } + + Bounds getBounds() { return bounds; } + + /** + * @brief Signal that what has been previously drawn is now invalid and has + * to be redrawn. + */ + void invalidate() + { + if (!invalidated) + { + invalidated = true; + } + } + + /** + * @brief Invalidate the view tree which has this view as a root + */ + void invalidateTree() + { + invalidate(); + + std::vector<View*> childs = getChilds(); + for (auto child : childs) + { + child->invalidateTree(); + } + } + + /** + * @brief Draw the view in its bounds + * + * @param dc Reference to a drawingcontext + */ + virtual void draw(mxgui::DrawingContext& dc) + { + if (invalidated) + { + invalidated = false; + + dc.clear(bounds.topLeft(), bounds.bottomRight(), + getBackgroundColor()); + + dc.drawRectangle(bounds.topLeft(), bounds.bottomRight(), + selected ? col_selected : getBackgroundColor()); + } + } + + /** + * @brief Returns the list of childs + * + * @return std::vector<View*> + */ + virtual std::vector<View*> getChilds() + { + // Default behavious is no childs + return {}; + } + + virtual void setBackgroundColor(mxgui::Color color) + { + if (color != col_bg) + { + col_bg = color; + invalidate(); + } + } + + virtual mxgui::Color getBackgroundColor() { return col_bg; } + + void setSelectedColor(mxgui::Color color) + { + col_selected = color; + invalidate(); + } + + void setSelectable(bool selectable) { this->selectable = selectable; } + + bool isSelectable() { return selectable; } + + void setSelected(bool selected) + { + this->selected = selected; + invalidate(); + } + + bool isSelected() { return selected && selectable; } + + void addOnInteractionListener(OnInteractionListener listener) + { + listeners.push_back(listener); + } + + virtual void performInteraction(Interaction action) + { + for (auto lst : listeners) + { + if (lst) + { + lst(this, action); + } + } + } + +protected: + bool isInvalidated() { return invalidated; } + +private: + mxgui::Color col_selected = mxgui::red; + mxgui::Color col_bg = mxgui::black; + + std::vector<OnInteractionListener> listeners; + + bool selected = false; + bool selectable = false; + bool invalidated = true; + + Bounds bounds{{0, 0}, {0, 0}}; + + // Drawing big solid chunks (such as backgrounds) takes a lot of time: do + // not redraw if not necessary + bool was_selected = false; + mxgui::Color last_bg_color = col_bg; +}; diff --git a/src/shared/utils/testutils/BusTemplateMock.h b/src/shared/utils/testutils/BusTemplateMock.h index 288d8f3499abe737df60a2a87ff5f5e121a970ed..b8e4d267f1f3127534852a2359065230eefeb1b4 100644 --- a/src/shared/utils/testutils/BusTemplateMock.h +++ b/src/shared/utils/testutils/BusTemplateMock.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/utils/testutils/MockGpioPin.h b/src/shared/utils/testutils/MockGpioPin.h new file mode 100644 index 0000000000000000000000000000000000000000..ed8b0a38013097bf90500aaed868f052001531c0 --- /dev/null +++ b/src/shared/utils/testutils/MockGpioPin.h @@ -0,0 +1,51 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + + +#pragma once + +#include <miosix.h> + +#include <memory> + +class MockGpioPin +{ +public: + MockGpioPin() + : val(new int(0)), + gpio_mode(new miosix::Mode::Mode_(miosix::Mode::OUTPUT)) + { + } + + void mode(miosix::Mode::Mode_ m) { *gpio_mode = m; } + + void high() { *val = 1; } + + void low() { *val = 0; } + + int value() { return *val; } + + // shared_ptr: make all copies of this instance share the same val / + // gpio_mode + std::shared_ptr<int> val; + std::shared_ptr<miosix::Mode::Mode_> gpio_mode; +}; \ No newline at end of file diff --git a/src/shared/utils/testutils/TestHelper.cpp b/src/shared/utils/testutils/TestHelper.cpp index 24bd22bb7f1ba3780b64f1d8a04ae2401a05b0d3..adb843e5e8969e775756c23f205891e7de330b13 100644 --- a/src/shared/utils/testutils/TestHelper.cpp +++ b/src/shared/utils/testutils/TestHelper.cpp @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/utils/testutils/TestHelper.h b/src/shared/utils/testutils/TestHelper.h index c8c6a652884babbe1e82874ea1f43a803cb1f994..61612fecfaa0fbda5a8abcf134ec2e1110a6184f 100644 --- a/src/shared/utils/testutils/TestHelper.h +++ b/src/shared/utils/testutils/TestHelper.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/shared/utils/testutils/TestSensor.h b/src/shared/utils/testutils/TestSensor.h new file mode 100755 index 0000000000000000000000000000000000000000..e1f4dbe997e23f90812bf3d17090f2267bb4ee7a --- /dev/null +++ b/src/shared/utils/testutils/TestSensor.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2015-2020 Skyward Experimental Rocketry + * Authors: Luca Erbetta, Luca Conterio + * + * 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. + */ + +#pragma once + +#include <cmath> + +#include "Common.h" +#include "sensors/Sensor.h" + +using miosix::getTick; +using miosix::TICK_FREQ; + +struct TestData : public TimestampData +{ + float value; + + TestData(float v) : TimestampData{static_cast<uint64_t>(getTick())}, value(v) {} + + TestData() : TimestampData{static_cast<uint64_t>(getTick())}, value(0.0) {} +}; + +class TestSensor : public Sensor<TestData> +{ +public: + TestSensor() {} + ~TestSensor() {} + + bool init() override { return true; } + + bool selfTest() override { return true; } + + TestData sampleImpl() override + { + TRACE("[TestSensor] sampleImpl() \n"); + return TestData(10 * sin(PI * static_cast<float>(getTick()) / + static_cast<float>(TICK_FREQ))); + } +}; diff --git a/src/shared/utils/testutils/ThroughputCalculator.h b/src/shared/utils/testutils/ThroughputCalculator.h new file mode 100644 index 0000000000000000000000000000000000000000..009ff7e4e13a1ab73bc238388e2822f307934009 --- /dev/null +++ b/src/shared/utils/testutils/ThroughputCalculator.h @@ -0,0 +1,175 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <miosix.h> + +#include <deque> +#include <limits> + +using miosix::FastMutex; +using miosix::Lock; +using std::deque; + +struct DataRateResult +{ + float data_rate; + float packet_loss; + float packets_per_second; +}; + +/** + * Helper class for calculating the throughput of communications. Just + * pass the size of a packet to addPacket(...) as soon as the packet is + * received. + */ +class ThroughputCalculator +{ +public: + /** + * @brief Creates a new ThroughputCalculator + * + * @param expected_pkt_interval Expected packet delivery interval for packet + * loss estimation + * @param window_duration Duration of the observation window. Longer windows + * means more stable values but slow response to abrupt changes + */ + ThroughputCalculator(unsigned int expected_pkt_interval, + unsigned int window_duration = 2000) + : expected_pkt_interval(expected_pkt_interval), + window_duration(window_duration) + { + } + + /** + * @brief Signal that a new packet has arrived. Call this as soon as the + * packet is available + * + * @param packet_size The size of the packet + */ + void addPacket(size_t packet_size) + { + Lock<FastMutex> lock(mutex_pkt); + + long long ts = miosix::getTick(); + + unsigned int interval = 0; + if (packets.size() > 0) + { + interval = static_cast<unsigned int>(ts - packets.back().timestamp); + } + + packets.push_back({ts, packet_size, interval}); + + removeOldPackets(ts); + } + + /** + * @brief Returns the datarate in Bytes/second averaged over the duration of + * the window + */ + float getDataRate() + { + Lock<FastMutex> lock(mutex_pkt); + + long long ts = miosix::getTick(); + removeOldPackets(ts); + + float sum = 0; + for (size_t i = 0; i < packets.size(); i++) + { + sum += packets[i].size; + } + return sum / (window_duration / 1000.0f); + } + + /** + * @brief Returns the packet loss percentage ([0-1]) based on the expected packet + * delivery interval + */ + float getPacketLoss() + { + Lock<FastMutex> lock(mutex_pkt); + long long ts = miosix::getTick(); + removeOldPackets(ts); + + float avg_interval = std::numeric_limits<float>::infinity(); + if (packets.size() > 0) + { + avg_interval = packets[0].interval; + for (size_t i = 1; i < packets.size(); i++) + { + avg_interval += packets[i].interval; + } + + avg_interval /= packets.size(); + } + + return 1 - expected_pkt_interval / avg_interval; + } + + /** + * @brief Returns the pps value averaged over the duration of the window + */ + float getPacketsPerSecond() + { + Lock<FastMutex> lock(mutex_pkt); + long long ts = miosix::getTick(); + removeOldPackets(ts); + + return (float)packets.size() / (window_duration / 1000.0f); + } + + DataRateResult getResult() + { + DataRateResult r; + r.data_rate = getDataRate(); + r.packet_loss = getPacketLoss(); + r.packets_per_second = getPacketsPerSecond(); + + return r; + } + +private: + struct Packet + { + long long timestamp; + size_t size; + unsigned int interval; + }; + + void removeOldPackets(long long ts) + { + while (packets.size() > 0 && + packets.front().timestamp < ts - window_duration) + { + packets.pop_front(); + } + } + + unsigned int expected_pkt_interval; + unsigned int window_duration; + deque<Packet> packets; + + FastMutex mutex_pkt; +}; \ No newline at end of file diff --git a/src/tests/calibration/accelerometer-data.h b/src/tests/calibration/accelerometer-data.h new file mode 100644 index 0000000000000000000000000000000000000000..7362152b2ce8b540aa605c999bf7c05fcf6679e2 --- /dev/null +++ b/src/tests/calibration/accelerometer-data.h @@ -0,0 +1,289 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +/* + * This data was obtained from the accelerometer LIS3DSH on March 2021 + * on the 6 different orientation + */ + +#pragma once + +#include "sensors/SensorData.h" +#include "sensors/calibration/Calibration.h" + +namespace accData +{ +constexpr unsigned nOrientations = 6; +constexpr unsigned nSamples = 60; + +AxisOrthoOrientation orientations[nOrientations]{ + {Direction::POSITIVE_X, Direction::POSITIVE_Y}, + {Direction::POSITIVE_Y, Direction::POSITIVE_Z}, + {Direction::POSITIVE_Z, Direction::POSITIVE_X}, + {Direction::POSITIVE_Y, Direction::POSITIVE_X}, + {Direction::POSITIVE_Z, Direction::POSITIVE_Y}, + {Direction::POSITIVE_X, Direction::POSITIVE_Z}, +}; + +AccelerometerData samples[nOrientations][nSamples] = { + + /* -------------------------- 1 ------------------------ */ + + { + {0, -0.009180, 0.013140, 0.998820}, + {0, -0.009720, 0.007560, 0.991440}, + {0, -0.011700, 0.011340, 0.990720}, + {0, -0.020520, 0.027000, 0.990000}, + {0, -0.009540, 0.010080, 0.990360}, + {0, -0.008460, 0.005760, 0.995220}, + {0, -0.016920, 0.019440, 0.995580}, + {0, -0.015480, 0.007560, 0.992520}, + {0, -0.009180, 0.003060, 0.994320}, + {0, -0.010620, 0.007920, 0.994860}, + {0, -0.011520, 0.011880, 0.998280}, + {0, -0.011520, 0.001620, 0.998640}, + {0, 0.003780, 0.030600, 0.992340}, + {0, -0.008640, 0.003420, 0.992160}, + {0, -0.012240, 0.014220, 0.995220}, + {0, -0.008100, 0.007020, 0.997380}, + {0, -0.006480, 0.009540, 0.993960}, + {0, -0.017460, 0.005760, 0.992520}, + {0, -0.000360, -0.005220, 1.000260}, + {0, -0.019260, 0.010080, 0.996480}, + {0, -0.011520, 0.008820, 0.995040}, + {0, -0.014040, 0.013680, 0.991980}, + {0, -0.009180, 0.000000, 1.002420}, + {0, -0.004320, 0.000900, 0.997560}, + {0, -0.018180, 0.010260, 0.999000}, + {0, -0.003420, 0.002700, 0.997740}, + {0, -0.015660, 0.015660, 0.996300}, + {0, -0.007920, 0.003060, 0.996300}, + {0, -0.017820, 0.013860, 0.993060}, + {0, -0.023040, 0.018900, 0.995040}, + {0, -0.004320, 0.001620, 0.998100}, + {0, -0.008280, 0.006120, 0.995580}, + {0, -0.012240, 0.008100, 0.996300}, + {0, -0.004860, 0.008820, 0.996480}, + {0, -0.005400, 0.005760, 0.999360}, + {0, -0.008460, 0.007740, 0.997200}, + {0, -0.011160, 0.010080, 0.996840}, + {0, -0.006840, 0.005220, 0.994860}, + {0, -0.012600, 0.008100, 0.994500}, + {0, -0.002520, -0.006840, 0.994680}, + {0, -0.016200, 0.010440, 0.997020}, + {0, -0.009720, 0.008640, 0.992880}, + {0, -0.017280, 0.012960, 1.000080}, + {0, -0.010080, 0.005220, 0.995940}, + {0, -0.012240, 0.010800, 0.999540}, + {0, -0.012780, 0.012600, 0.996840}, + {0, -0.012960, 0.012420, 1.001340}, + {0, -0.011700, 0.015480, 0.998280}, + {0, -0.009180, 0.007740, 0.995760}, + {0, -0.008820, 0.020520, 0.995040}, + {0, -0.014760, 0.012780, 0.996840}, + {0, -0.009900, 0.009540, 0.991620}, + {0, -0.014220, 0.011520, 0.995400}, + {0, -0.003060, 0.011700, 0.992160}, + {0, -0.015480, 0.006660, 0.995940}, + {0, -0.017820, 0.005220, 0.998280}, + {0, -0.014040, 0.009540, 0.995580}, + {0, -0.012060, 0.006840, 0.999900}, + {0, -0.007740, 0.004680, 0.994860}, + {0, -0.012420, 0.011700, 0.991260}, + }, + + /* -------------------------- 2 ------------------------ */ + + { + {0, 0.003780, 1.038960, 0.043020}, {0, 0.011160, 1.036800, 0.026820}, + {0, 0.005220, 1.038960, 0.028980}, {0, 0.005040, 1.036260, 0.007020}, + {0, 0.012600, 1.040940, 0.021780}, {0, 0.005400, 1.042920, 0.002700}, + {0, 0.020520, 1.039140, 0.010440}, {0, 0.007920, 1.041300, -0.001440}, + {0, 0.006120, 1.036260, 0.012600}, {0, 0.002520, 1.041120, 0.027900}, + {0, 0.009540, 1.041840, 0.021420}, {0, 0.005580, 1.037520, 0.011520}, + {0, -0.041940, 1.025100, 0.018720}, {0, 0.005040, 1.040580, 0.022140}, + {0, 0.010980, 1.036440, 0.014400}, {0, 0.007200, 1.040040, 0.018720}, + {0, 0.006840, 1.040940, 0.020340}, {0, 0.007380, 1.038960, 0.026640}, + {0, 0.009000, 1.043820, 0.028980}, {0, 0.006660, 1.039500, 0.027180}, + {0, 0.009540, 1.040400, 0.020520}, {0, 0.008460, 1.039680, 0.022680}, + {0, 0.012420, 1.044540, 0.032040}, {0, 0.010800, 1.038600, 0.022680}, + {0, 0.003960, 1.036980, 0.019440}, {0, 0.007560, 1.039860, 0.017460}, + {0, 0.007020, 1.042740, 0.011340}, {0, 0.004680, 1.039140, 0.009000}, + {0, 0.007380, 1.041120, 0.018900}, {0, 0.005580, 1.042380, 0.026820}, + {0, 0.012240, 1.039140, 0.019800}, {0, 0.010260, 1.038780, 0.004140}, + {0, 0.007740, 1.041120, 0.016740}, {0, 0.002880, 1.038960, 0.016740}, + {0, 0.003960, 1.040760, 0.026820}, {0, 0.009000, 1.040580, 0.021600}, + {0, 0.007920, 1.035720, 0.020880}, {0, 0.004680, 1.041840, 0.021600}, + {0, 0.004140, 1.036440, 0.010980}, {0, 0.010260, 1.038420, 0.015480}, + {0, 0.014040, 1.036980, 0.018540}, {0, 0.009900, 1.036620, 0.018000}, + {0, 0.009000, 1.035900, 0.018540}, {0, 0.009180, 1.038780, 0.023760}, + {0, 0.011340, 1.041840, 0.019620}, {0, 0.004320, 1.036440, 0.009360}, + {0, 0.008280, 1.036980, 0.014940}, {0, 0.007020, 1.036620, 0.011340}, + {0, 0.005940, 1.035000, 0.024300}, {0, 0.005220, 1.041840, 0.023220}, + {0, 0.009720, 1.037340, 0.032220}, {0, 0.007380, 1.036260, 0.036900}, + {0, 0.008640, 1.035720, 0.020160}, {0, 0.009000, 1.042200, 0.014760}, + {0, 0.011880, 1.038960, 0.015840}, {0, 0.004140, 1.033560, 0.016200}, + {0, 0.011880, 1.040760, 0.007200}, {0, 0.004860, 1.039140, 0.021780}, + {0, 0.006660, 1.038960, 0.008820}, {0, 0.009360, 1.039680, 0.017100}, + }, + + /* -------------------------- 3 ------------------------ */ + + { + {0, 1.026180, 0.020340, -0.035100}, {0, 1.026900, 0.021240, -0.037080}, + {0, 1.024920, 0.022140, -0.032580}, {0, 1.024200, 0.013320, -0.039420}, + {0, 1.022400, 0.018720, -0.033120}, {0, 1.025640, 0.016740, -0.046080}, + {0, 1.027260, 0.018720, -0.044100}, {0, 1.025100, 0.024300, -0.040680}, + {0, 1.026000, 0.024480, -0.041940}, {0, 1.025100, 0.021240, -0.049860}, + {0, 1.023840, 0.018540, -0.044280}, {0, 1.024200, 0.020520, -0.036900}, + {0, 1.025460, 0.019080, -0.045540}, {0, 1.024920, 0.025200, -0.043020}, + {0, 1.022040, 0.018000, -0.045540}, {0, 1.025820, 0.018540, -0.045540}, + {0, 1.024200, 0.014040, -0.043380}, {0, 1.027440, 0.021780, -0.044820}, + {0, 1.026900, 0.021600, -0.047880}, {0, 1.024020, 0.021420, -0.043740}, + {0, 1.020600, 0.021240, -0.043920}, {0, 1.023120, 0.017280, -0.055080}, + {0, 1.023480, 0.019440, -0.058680}, {0, 1.026900, 0.021420, -0.041580}, + {0, 1.026720, 0.019260, -0.045000}, {0, 1.021500, 0.021240, -0.040500}, + {0, 1.025280, 0.024300, -0.040680}, {0, 1.029600, 0.016200, -0.047340}, + {0, 1.024560, 0.020160, -0.024660}, {0, 1.030860, 0.021600, -0.041580}, + {0, 1.028700, 0.023040, -0.039960}, {0, 1.024560, 0.019080, -0.037980}, + {0, 1.027800, 0.021240, -0.051480}, {0, 1.029060, 0.022320, -0.061380}, + {0, 1.024560, 0.021780, -0.053100}, {0, 1.027080, 0.018000, -0.055800}, + {0, 1.023300, 0.018900, -0.056700}, {0, 1.024380, 0.017280, -0.059220}, + {0, 1.025640, 0.018540, -0.062820}, {0, 1.029240, 0.022680, -0.046080}, + {0, 1.023300, 0.019440, -0.061020}, {0, 1.024740, 0.018360, -0.068220}, + {0, 1.026000, 0.025920, -0.054540}, {0, 1.023120, 0.020160, -0.054900}, + {0, 1.028160, 0.019980, -0.063000}, {0, 1.026000, 0.017280, -0.054360}, + {0, 1.024920, 0.022680, -0.050220}, {0, 1.023120, 0.017820, -0.058860}, + {0, 1.028520, 0.026460, -0.047160}, {0, 1.021860, 0.019080, -0.053640}, + {0, 1.033380, 0.019080, -0.057780}, {0, 1.023120, 0.020880, -0.050400}, + {0, 1.022580, 0.020160, -0.058500}, {0, 1.024560, 0.019080, -0.049320}, + {0, 1.027980, 0.022500, -0.052380}, {0, 1.026180, 0.021060, -0.053100}, + {0, 1.028340, 0.019980, -0.053280}, {0, 1.025280, 0.018900, -0.054540}, + {0, 1.023660, 0.021420, -0.039420}, {0, 1.023300, 0.021600, -0.048600}, + }, + + /* -------------------------- 4 ------------------------ */ + + { + {0, 0.009180, 0.021420, -1.006920}, {0, 0.007920, 0.017280, -1.005300}, + {0, 0.004500, 0.019260, -1.013040}, {0, 0.012600, 0.022500, -1.006920}, + {0, 0.006480, 0.021060, -1.008180}, {0, 0.004500, 0.012780, -1.006740}, + {0, 0.005220, 0.015300, -1.013760}, {0, 0.011520, 0.021420, -1.012320}, + {0, 0.005940, 0.016560, -1.010700}, {0, -0.008280, 0.005220, -1.010700}, + {0, 0.002880, 0.013140, -1.011600}, {0, 0.010800, 0.019800, -1.010160}, + {0, 0.010080, 0.021960, -1.005480}, {0, -0.003960, 0.008820, -1.010520}, + {0, 0.009180, 0.021060, -1.009800}, {0, 0.011160, 0.023400, -1.010880}, + {0, 0.002700, 0.017100, -1.012860}, {0, 0.005760, 0.021600, -1.009620}, + {0, 0.011160, 0.016740, -1.011060}, {0, 0.005040, 0.018360, -1.012680}, + {0, 0.009540, 0.020880, -1.009440}, {0, 0.005220, 0.018900, -1.008900}, + {0, 0.004680, 0.015840, -1.010340}, {0, 0.010440, 0.023040, -1.014300}, + {0, 0.012420, 0.021600, -1.007820}, {0, 0.008820, 0.022140, -1.006200}, + {0, 0.008460, 0.021780, -1.008540}, {0, 0.009540, 0.019980, -1.008720}, + {0, 0.012600, 0.019980, -1.010880}, {0, 0.010260, 0.019620, -1.011960}, + {0, 0.009540, 0.018000, -1.007640}, {0, 0.010440, 0.021060, -1.010160}, + {0, 0.011160, 0.018360, -1.013760}, {0, 0.008460, 0.014940, -1.003320}, + {0, 0.009720, 0.017820, -1.007280}, {0, 0.013320, 0.022860, -1.005840}, + {0, 0.004680, 0.016020, -1.007820}, {0, 0.011700, 0.020340, -1.008360}, + {0, 0.009000, 0.022500, -1.013940}, {0, 0.010080, 0.021420, -1.010880}, + {0, 0.007020, 0.021240, -1.014300}, {0, 0.010800, 0.018180, -1.010160}, + {0, 0.010260, 0.018900, -1.010520}, {0, 0.007920, 0.022500, -1.008180}, + {0, 0.006300, 0.016920, -1.008720}, {0, 0.008460, 0.019620, -1.011780}, + {0, 0.007200, 0.018720, -1.008900}, {0, 0.008460, 0.020340, -1.009620}, + {0, 0.014580, 0.023580, -1.010520}, {0, 0.007200, 0.021060, -1.008000}, + {0, 0.005400, 0.019620, -1.005480}, {0, 0.012780, 0.020520, -1.010520}, + {0, 0.009000, 0.018000, -1.009080}, {0, 0.011700, 0.024480, -1.008540}, + {0, 0.008280, 0.015480, -1.007460}, {0, 0.006840, 0.021240, -1.010160}, + {0, 0.004500, 0.015660, -1.010700}, {0, 0.014040, 0.021960, -1.007280}, + {0, 0.010260, 0.018180, -1.005840}, {0, 0.007740, 0.017280, -1.006920}, + }, + + /* -------------------------- 5 ------------------------ */ + + { + {0, 1.026540, 0.028800, 0.023580}, {0, 1.027800, 0.022680, 0.016560}, + {0, 1.029240, 0.021600, 0.015660}, {0, 1.027260, 0.025020, 0.012060}, + {0, 1.031220, 0.025740, 0.014400}, {0, 1.027980, 0.025020, 0.022140}, + {0, 1.026360, 0.025020, 0.019980}, {0, 1.026540, 0.024660, 0.018720}, + {0, 1.028160, 0.024480, 0.010260}, {0, 1.028520, 0.025740, 0.016200}, + {0, 1.026180, 0.027720, 0.016380}, {0, 1.031940, 0.030780, 0.015120}, + {0, 1.023660, 0.022680, 0.011880}, {0, 1.024920, 0.023580, 0.016560}, + {0, 1.027080, 0.025560, 0.012240}, {0, 1.029240, 0.022140, 0.018900}, + {0, 1.028880, 0.026100, 0.017820}, {0, 1.027260, 0.026100, 0.030240}, + {0, 1.024020, 0.026460, 0.021600}, {0, 1.029420, 0.023040, 0.015120}, + {0, 1.026900, 0.019800, 0.018360}, {0, 1.026720, 0.026280, 0.017100}, + {0, 1.028880, 0.025020, 0.013140}, {0, 1.025640, 0.023220, 0.019440}, + {0, 1.028160, 0.026460, 0.021240}, {0, 1.026180, 0.028080, 0.020160}, + {0, 1.031400, 0.034560, 0.020520}, {0, 1.026180, 0.029160, 0.020340}, + {0, 1.027980, 0.024120, 0.015300}, {0, 1.026180, 0.026100, 0.023580}, + {0, 1.029240, 0.026820, 0.015840}, {0, 1.026720, 0.026820, 0.025200}, + {0, 1.023300, 0.025560, 0.022680}, {0, 1.028880, 0.029520, 0.019980}, + {0, 1.025820, 0.028800, 0.020160}, {0, 1.025640, 0.022680, 0.025740}, + {0, 1.026720, 0.028800, 0.026100}, {0, 1.025100, 0.026640, 0.018360}, + {0, 1.022400, 0.023580, 0.020340}, {0, 1.024020, 0.026100, 0.018720}, + {0, 1.029060, 0.024300, 0.024480}, {0, 1.028520, 0.022860, 0.015480}, + {0, 1.022940, 0.023220, 0.021780}, {0, 1.026360, 0.025740, 0.020160}, + {0, 1.026900, 0.024480, 0.023580}, {0, 1.024380, 0.026640, 0.027900}, + {0, 1.024200, 0.025020, 0.027720}, {0, 1.028700, 0.024300, 0.018000}, + {0, 1.025100, 0.028800, 0.030060}, {0, 1.024380, 0.027000, 0.018180}, + {0, 1.026180, 0.027180, 0.020340}, {0, 1.020960, 0.025380, 0.030780}, + {0, 1.028520, 0.026820, 0.019440}, {0, 1.022040, 0.026640, 0.027720}, + {0, 1.028700, 0.024660, 0.019080}, {0, 1.028520, 0.030780, 0.020160}, + {0, 1.025100, 0.028980, 0.021240}, {0, 1.027440, 0.027540, 0.019260}, + {0, 1.027440, 0.027540, 0.024660}, {0, 1.028700, 0.019980, 0.024660}, + }, + + /* -------------------------- 6 ------------------------ */ + + { + {0, 0.023400, 1.033740, -0.036720}, {0, 0.022860, 1.035180, -0.030780}, + {0, 0.019260, 1.042200, -0.045180}, {0, 0.022500, 1.039500, -0.037260}, + {0, 0.025920, 1.035360, -0.033480}, {0, 0.022140, 1.040400, -0.035460}, + {0, 0.024660, 1.038240, -0.036900}, {0, 0.024120, 1.036440, -0.036540}, + {0, 0.019260, 1.038240, -0.032040}, {0, 0.021960, 1.038600, -0.044640}, + {0, 0.021420, 1.038960, -0.037800}, {0, 0.020340, 1.039860, -0.042300}, + {0, 0.025020, 1.040580, -0.030780}, {0, 0.025920, 1.038780, -0.039240}, + {0, 0.016560, 1.038960, -0.036000}, {0, 0.027180, 1.037520, -0.037260}, + {0, 0.022320, 1.036440, -0.039960}, {0, 0.021600, 1.038420, -0.036540}, + {0, 0.022140, 1.038420, -0.032940}, {0, 0.018720, 1.038960, -0.033300}, + {0, 0.019620, 1.035180, -0.029340}, {0, 0.033480, 1.029780, -0.025380}, + {0, 0.017460, 1.035000, -0.037620}, {0, 0.018540, 1.035720, -0.024840}, + {0, 0.023400, 1.034280, -0.028800}, {0, 0.022320, 1.035360, -0.034560}, + {0, 0.021960, 1.041840, -0.038880}, {0, 0.023940, 1.033380, -0.034020}, + {0, 0.023220, 1.036800, -0.035820}, {0, 0.023400, 1.039140, -0.042300}, + {0, 0.022140, 1.037880, -0.036900}, {0, 0.022320, 1.037880, -0.025380}, + {0, 0.023760, 1.042920, -0.038520}, {0, 0.023220, 1.043640, -0.033660}, + {0, 0.021960, 1.035540, -0.036000}, {0, 0.023400, 1.036440, -0.040140}, + {0, 0.022140, 1.039500, -0.041040}, {0, 0.024120, 1.036620, -0.048600}, + {0, 0.020160, 1.036800, -0.039960}, {0, 0.023760, 1.041300, -0.055620}, + {0, 0.023040, 1.041660, -0.042480}, {0, 0.025920, 1.034640, -0.033480}, + {0, 0.024300, 1.035540, -0.043200}, {0, 0.024300, 1.036260, -0.042660}, + {0, 0.019980, 1.042380, -0.052920}, {0, 0.022320, 1.036800, -0.037800}, + {0, 0.024480, 1.036800, -0.040320}, {0, 0.023220, 1.035720, -0.052740}, + {0, 0.027720, 1.039860, -0.037080}, {0, 0.023400, 1.040220, -0.043740}, + {0, 0.020340, 1.034460, -0.045360}, {0, 0.018180, 1.037520, -0.036900}, + {0, 0.024480, 1.034100, -0.021960}, {0, 0.021060, 1.040580, -0.045900}, + {0, 0.025380, 1.040760, -0.032400}, {0, 0.024120, 1.039320, -0.032580}, + {0, 0.023400, 1.035720, -0.034740}, {0, 0.024480, 1.039860, -0.047700}, + {0, 0.028980, 1.032300, -0.032040}, {0, 0.025200, 1.038240, -0.027540}, + }, +}; +} // namespace accData diff --git a/src/tests/calibration/magnetometer-data.h b/src/tests/calibration/magnetometer-data.h new file mode 100644 index 0000000000000000000000000000000000000000..b841ccfa28071835b392c383329a42463b7262e9 --- /dev/null +++ b/src/tests/calibration/magnetometer-data.h @@ -0,0 +1,256 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +// This data is a stripped down version of LOG_6.dat file of the calibration repository. + +#pragma once + +#include "sensors/SensorData.h" + +namespace magnetoData { + constexpr unsigned nSamples = 885; + + MagnetometerData samples[nSamples]{ + { 0, 0.038,0.271,0.015 }, { 0, 0.048,0.252,0.001 }, { 0, 0.256,0.388,-0.008 }, { 0, 0.438,0.500,0.110 }, + { 0, 0.113,0.529,0.068 }, { 0, -0.086,0.551,0.368 }, { 0, 0.017,0.601,0.503 }, { 0, 0.201,0.571,0.593 }, + { 0, -0.065,0.429,0.609 }, { 0, -0.141,0.443,0.219 }, { 0, 0.165,0.487,0.027 }, { 0, 0.481,0.135,0.666 }, + { 0, 0.357,0.021,0.701 }, { 0, 0.272,-0.041,0.689 }, { 0, 0.040,-0.021,0.695 }, { 0, -0.121,0.199,0.120 }, + { 0, 0.003,0.203,0.020 }, { 0, -0.006,0.226,0.027 }, { 0, -0.191,0.234,0.530 }, { 0, 0.560,0.093,0.568 }, + { 0, 0.636,0.225,0.213 }, { 0, 0.378,0.597,0.235 }, { 0, 0.257,0.643,0.417 }, { 0, 0.220,0.562,0.601 }, + { 0, 0.380,0.496,0.608 }, { 0, 0.377,0.451,0.639 }, { 0, 0.236,0.425,0.702 }, { 0, 0.038,0.574,0.189 }, + { 0, 0.169,0.492,0.050 }, { 0, 0.369,0.419,0.037 }, { 0, 0.569,0.316,0.134 }, { 0, 0.543,-0.010,0.509 }, + { 0, 0.361,-0.060,0.656 }, { 0, 0.152,-0.045,0.704 }, { 0, -0.126,0.058,0.572 }, { 0, -0.100,0.077,0.614 }, + { 0, -0.156,0.081,0.519 }, { 0, -0.172,0.164,0.211 }, { 0, -0.111,0.182,0.116 }, { 0, 0.049,0.342,0.018 }, + { 0, 0.363,0.394,0.020 }, { 0, 0.475,0.356,0.074 }, { 0, 0.574,0.319,0.151 }, { 0, 0.182,0.518,0.051 }, + { 0, -0.055,0.550,0.214 }, { 0, -0.014,0.588,0.468 }, { 0, 0.117,0.650,0.462 }, { 0, 0.019,0.535,0.094 }, + { 0, -0.095,0.541,0.197 }, { 0, -0.099,0.557,0.303 }, { 0, -0.184,0.444,0.440 }, { 0, -0.208,0.378,0.402 }, + { 0, -0.117,0.367,0.143 }, { 0, 0.331,0.400,0.018 }, { 0, 0.498,0.440,0.154 }, { 0, 0.410,0.592,0.347 }, + { 0, 0.261,0.646,0.285 }, { 0, 0.309,0.629,0.281 }, { 0, 0.334,0.632,0.292 }, { 0, 0.311,0.637,0.307 }, + { 0, 0.260,0.646,0.267 }, { 0, 0.267,0.621,0.195 }, { 0, 0.325,0.574,0.145 }, { 0, 0.645,0.271,0.285 }, + { 0, 0.651,0.148,0.433 }, { 0, 0.527,0.043,0.625 }, { 0, 0.231,-0.049,0.710 }, { 0, -0.075,0.027,0.651 }, + { 0, -0.163,0.127,0.594 }, { 0, -0.092,0.103,0.680 }, { 0, 0.227,-0.063,0.724 }, { 0, 0.632,0.071,0.362 }, + { 0, 0.606,0.379,0.419 }, { 0, 0.540,0.467,0.455 }, { 0, 0.483,0.503,0.516 }, { 0, 0.405,0.522,0.573 }, + { 0, 0.403,0.530,0.558 }, { 0, 0.441,0.501,0.563 }, { 0, 0.451,0.451,0.617 }, { 0, 0.597,0.118,0.494 }, + { 0, 0.599,0.057,0.438 }, { 0, 0.591,0.046,0.377 }, { 0, 0.506,-0.030,0.180 }, { 0, 0.426,0.079,0.038 }, + { 0, 0.332,0.194,-0.029 }, { 0, 0.259,0.412,-0.012 }, { 0, 0.337,0.304,-0.027 }, { 0, 0.457,-0.050,0.136 }, + { 0, 0.454,-0.104,0.544 }, { 0, 0.357,-0.003,0.674 }, { 0, 0.317,0.125,0.747 }, { 0, 0.228,0.641,0.476 }, + { 0, 0.235,0.659,0.338 }, { 0, 0.317,0.619,0.224 }, { 0, 0.320,0.547,0.116 }, { 0, 0.313,0.317,-0.034 }, + { 0, 0.315,0.276,-0.045 }, { 0, 0.345,0.182,-0.033 }, { 0, 0.373,0.064,0.003 }, { 0, 0.417,-0.068,0.096 }, + { 0, 0.417,-0.068,0.096 }, { 0, 0.453,-0.122,0.207 }, { 0, 0.488,-0.119,0.246 }, { 0, 0.570,0.006,0.196 }, + { 0, 0.612,0.113,0.172 }, { 0, 0.641,0.179,0.204 }, { 0, 0.657,0.235,0.266 }, { 0, 0.631,0.336,0.280 }, + { 0, 0.625,0.362,0.284 }, { 0, 0.617,0.385,0.318 }, { 0, 0.579,0.439,0.358 }, { 0, 0.506,0.523,0.396 }, + { 0, 0.497,0.507,0.448 }, { 0, 0.523,0.469,0.490 }, { 0, 0.557,0.333,0.562 }, { 0, 0.604,0.246,0.501 }, + { 0, 0.621,0.242,0.465 }, { 0, 0.632,0.191,0.392 }, { 0, 0.630,0.146,0.332 }, { 0, 0.590,0.081,0.237 }, + { 0, 0.526,0.038,0.144 }, { 0, 0.490,0.015,0.119 }, { 0, 0.461,0.003,0.107 }, { 0, 0.342,0.050,0.030 }, + { 0, 0.331,0.062,0.014 }, { 0, 0.301,0.086,-0.008 }, { 0, 0.306,0.083,0.014 }, { 0, 0.564,-0.013,0.331 }, + { 0, 0.559,-0.012,0.444 }, { 0, 0.487,0.049,0.624 }, { 0, 0.300,0.198,0.755 }, { 0, 0.224,0.350,0.752 }, + { 0, 0.197,0.405,0.730 }, { 0, 0.103,0.583,0.581 }, { 0, 0.033,0.630,0.428 }, { 0, 0.068,0.578,0.161 }, + { 0, 0.220,0.437,0.016 }, { 0, 0.238,0.381,-0.017 }, { 0, 0.268,0.278,-0.040 }, { 0, 0.301,0.077,-0.008 }, + { 0, 0.345,0.013,0.040 }, { 0, 0.371,-0.031,0.070 }, { 0, 0.400,-0.083,0.123 }, { 0, 0.514,-0.082,0.208 }, + { 0, 0.532,-0.057,0.201 }, { 0, 0.541,-0.040,0.195 }, { 0, 0.600,0.035,0.212 }, { 0, 0.621,0.049,0.236 }, + { 0, 0.637,0.073,0.270 }, { 0, 0.652,0.114,0.300 }, { 0, 0.659,0.141,0.301 }, { 0, 0.649,0.249,0.336 }, + { 0, 0.626,0.288,0.441 }, { 0, 0.614,0.246,0.506 }, { 0, 0.600,0.221,0.531 }, { 0, 0.541,0.114,0.604 }, + { 0, 0.541,0.070,0.587 }, { 0, 0.543,0.032,0.561 }, { 0, 0.519,-0.056,0.507 }, { 0, 0.461,-0.146,0.293 }, + { 0, 0.435,-0.143,0.197 }, { 0, 0.364,-0.119,0.130 }, { 0, 0.212,-0.056,0.057 }, { 0, 0.150,-0.091,0.101 }, + { 0, 0.109,-0.151,0.195 }, { 0, 0.018,-0.166,0.400 }, { 0, 0.007,-0.155,0.458 }, { 0, 0.122,-0.132,0.635 }, + { 0, 0.263,-0.097,0.676 }, { 0, 0.399,-0.050,0.664 }, { 0, 0.463,-0.005,0.651 }, { 0, 0.502,0.190,0.668 }, + { 0, 0.482,0.359,0.632 }, { 0, 0.468,0.423,0.617 }, { 0, 0.424,0.482,0.599 }, { 0, 0.326,0.574,0.552 }, + { 0, 0.223,0.638,0.478 }, { 0, 0.183,0.653,0.421 }, { 0, 0.169,0.627,0.219 }, { 0, 0.323,0.519,0.095 }, + { 0, 0.408,0.482,0.107 }, { 0, 0.589,0.396,0.256 }, { 0, 0.601,0.403,0.317 }, { 0, 0.585,0.426,0.421 }, + { 0, 0.543,0.472,0.239 }, { 0, 0.488,0.473,0.141 }, { 0, 0.331,0.446,0.010 }, { 0, 0.052,0.241,-0.021 }, + { 0, 0.008,0.176,0.010 }, { 0, -0.038,0.117,0.046 }, { 0, -0.071,0.033,0.133 }, { 0, -0.088,-0.029,0.193 }, + { 0, -0.040,-0.016,0.120 }, { 0, 0.156,0.058,-0.006 }, { 0, 0.437,0.191,0.001 }, { 0, 0.578,0.284,0.142 }, + { 0, 0.621,0.287,0.223 }, { 0, 0.632,0.269,0.265 }, { 0, 0.639,0.241,0.273 }, { 0, 0.626,0.186,0.235 }, + { 0, 0.611,0.197,0.201 }, { 0, 0.622,0.206,0.212 }, { 0, 0.638,0.228,0.278 }, { 0, 0.629,0.189,0.426 }, + { 0, 0.605,0.167,0.487 }, { 0, 0.577,0.128,0.530 }, { 0, 0.555,0.094,0.557 }, { 0, 0.458,-0.049,0.568 }, + { 0, 0.469,-0.121,0.429 }, { 0, 0.444,-0.142,0.377 }, { 0, 0.387,-0.171,0.280 }, { 0, 0.365,-0.112,0.135 }, + { 0, 0.307,-0.012,0.030 }, { 0, 0.281,0.112,-0.021 }, { 0, 0.279,0.141,-0.032 }, { 0, 0.301,0.155,-0.026 }, + { 0, 0.505,0.078,0.097 }, { 0, 0.607,0.026,0.305 }, { 0, 0.612,0.015,0.408 }, { 0, 0.322,0.124,0.755 }, + { 0, 0.269,0.145,0.768 }, { 0, 0.262,0.175,0.779 }, { 0, 0.171,0.239,0.771 }, { 0, -0.073,0.462,0.609 }, + { 0, -0.116,0.485,0.540 }, { 0, -0.156,0.489,0.335 }, { 0, -0.069,0.460,0.146 }, { 0, 0.091,0.425,0.030 }, + { 0, 0.149,0.425,0.019 }, { 0, 0.308,0.431,0.029 }, { 0, 0.415,0.430,0.072 }, { 0, 0.537,0.422,0.193 }, + { 0, 0.575,0.434,0.332 }, { 0, 0.575,0.434,0.332 }, { 0, 0.564,0.449,0.420 }, { 0, 0.538,0.469,0.452 }, + { 0, 0.527,0.500,0.397 }, { 0, 0.528,0.502,0.350 }, { 0, 0.495,0.510,0.207 }, { 0, 0.263,0.599,0.169 }, + { 0, 0.251,0.627,0.226 }, { 0, 0.241,0.625,0.244 }, { 0, 0.295,0.610,0.217 }, { 0, 0.222,0.620,0.182 }, + { 0, 0.168,0.609,0.158 }, { 0, 0.109,0.589,0.148 }, { 0, 0.106,0.594,0.153 }, { 0, 0.148,0.608,0.153 }, + { 0, 0.129,0.611,0.141 }, { 0, 0.166,0.597,0.120 }, { 0, 0.184,0.588,0.116 }, { 0, 0.345,0.491,0.059 }, + { 0, 0.376,0.478,0.067 }, { 0, 0.461,0.481,0.151 }, { 0, 0.493,0.490,0.221 }, { 0, 0.470,0.540,0.401 }, + { 0, 0.454,0.554,0.421 }, { 0, 0.439,0.553,0.445 }, { 0, 0.324,0.603,0.487 }, { 0, 0.242,0.647,0.359 }, + { 0, 0.242,0.647,0.359 }, { 0, 0.295,0.622,0.239 }, { 0, 0.296,0.621,0.241 }, { 0, 0.281,0.616,0.199 }, + { 0, 0.292,0.606,0.208 }, { 0, 0.292,0.606,0.208 }, { 0, 0.297,0.563,0.139 }, { 0, 0.342,0.430,0.021 }, + { 0, 0.418,0.325,-0.002 }, { 0, 0.500,0.240,0.047 }, { 0, 0.525,0.205,0.065 }, { 0, 0.652,0.133,0.310 }, + { 0, 0.655,0.163,0.364 }, { 0, 0.623,0.223,0.512 }, { 0, 0.556,0.285,0.596 }, { 0, 0.455,0.356,0.674 }, + { 0, 0.384,0.356,0.716 }, { 0, 0.228,0.384,0.752 }, { 0, 0.148,0.387,0.746 }, { 0, 0.003,0.418,0.696 }, + { 0, -0.159,0.472,0.490 }, { 0, -0.171,0.488,0.419 }, { 0, -0.165,0.482,0.455 }, { 0, 0.007,0.470,0.679 }, + { 0, 0.096,0.480,0.709 }, { 0, 0.251,0.473,0.706 }, { 0, 0.251,0.473,0.706 }, { 0, 0.540,0.414,0.529 }, + { 0, 0.563,0.420,0.485 }, { 0, 0.579,0.440,0.321 }, { 0, 0.493,0.461,0.147 }, { 0, 0.441,0.488,0.122 }, + { 0, 0.436,0.524,0.173 }, { 0, 0.426,0.548,0.203 }, { 0, 0.402,0.573,0.206 }, { 0, 0.380,0.596,0.253 }, + { 0, 0.348,0.623,0.317 }, { 0, 0.357,0.620,0.325 }, { 0, 0.342,0.629,0.339 }, { 0, 0.406,0.601,0.366 }, + { 0, 0.441,0.574,0.367 }, { 0, 0.443,0.570,0.389 }, { 0, 0.456,0.556,0.387 }, { 0, 0.471,0.534,0.450 }, + { 0, 0.487,0.505,0.482 }, { 0, 0.508,0.476,0.483 }, { 0, 0.557,0.405,0.495 }, { 0, 0.610,0.311,0.448 }, + { 0, 0.632,0.260,0.428 }, { 0, 0.635,0.150,0.423 }, { 0, 0.635,0.150,0.423 }, { 0, 0.601,0.061,0.482 }, + { 0, 0.568,0.007,0.484 }, { 0, 0.557,-0.014,0.511 }, { 0, 0.550,-0.031,0.507 }, { 0, 0.555,-0.053,0.480 }, + { 0, 0.552,-0.055,0.464 }, { 0, 0.560,-0.033,0.448 }, { 0, 0.560,-0.033,0.448 }, { 0, 0.541,-0.033,0.213 }, + { 0, 0.541,-0.033,0.213 }, { 0, 0.505,-0.007,0.138 }, { 0, 0.443,0.054,0.048 }, { 0, 0.408,0.135,-0.002 }, + { 0, 0.370,0.168,-0.026 }, { 0, 0.326,0.221,-0.046 }, { 0, 0.312,0.249,-0.047 }, { 0, 0.305,0.326,-0.037 }, + { 0, 0.279,0.403,-0.015 }, { 0, 0.265,0.444,0.007 }, { 0, 0.256,0.485,0.043 }, { 0, 0.259,0.583,0.140 }, + { 0, 0.253,0.600,0.160 }, { 0, 0.249,0.617,0.190 }, { 0, 0.247,0.630,0.214 }, { 0, 0.255,0.646,0.274 }, + { 0, 0.278,0.644,0.274 }, { 0, 0.300,0.634,0.264 }, { 0, 0.320,0.621,0.228 }, { 0, 0.358,0.548,0.143 }, + { 0, 0.441,0.415,0.077 }, { 0, 0.488,0.345,0.076 }, { 0, 0.544,0.262,0.108 }, { 0, 0.623,0.127,0.255 }, + { 0, 0.600,0.033,0.440 }, { 0, 0.565,0.025,0.514 }, { 0, 0.524,0.021,0.570 }, { 0, 0.354,0.063,0.722 }, + { 0, 0.273,0.116,0.748 }, { 0, 0.191,0.182,0.765 }, { 0, 0.191,0.182,0.765 }, { 0, -0.029,0.452,0.639 }, + { 0, -0.029,0.452,0.639 }, { 0, -0.039,0.513,0.586 }, { 0, -0.036,0.596,0.420 }, { 0, 0.073,0.602,0.191 }, + { 0, 0.073,0.602,0.191 }, { 0, 0.155,0.539,0.091 }, { 0, 0.206,0.492,0.047 }, { 0, 0.306,0.373,-0.009 }, + { 0, 0.381,0.243,-0.017 }, { 0, 0.412,0.183,0.002 }, { 0, 0.418,0.137,0.005 }, { 0, 0.431,0.011,0.055 }, + { 0, 0.438,-0.028,0.079 }, { 0, 0.438,-0.052,0.099 }, { 0, 0.429,-0.069,0.117 }, { 0, 0.430,-0.095,0.123 }, + { 0, 0.423,-0.080,0.109 }, { 0, 0.416,-0.044,0.075 }, { 0, 0.420,0.041,0.032 }, { 0, 0.429,0.142,-0.004 }, + { 0, 0.430,0.180,-0.007 }, { 0, 0.416,0.257,-0.006 }, { 0, 0.422,0.279,0.000 }, { 0, 0.424,0.323,0.015 }, + { 0, 0.416,0.360,0.028 }, { 0, 0.418,0.459,0.084 }, { 0, 0.418,0.459,0.084 }, { 0, 0.425,0.550,0.199 }, + { 0, 0.421,0.579,0.260 }, { 0, 0.427,0.583,0.326 }, { 0, 0.444,0.574,0.377 }, { 0, 0.502,0.472,0.523 }, + { 0, 0.525,0.404,0.574 }, { 0, 0.528,0.345,0.610 }, { 0, 0.533,0.253,0.630 }, { 0, 0.530,0.150,0.623 }, + { 0, 0.544,0.090,0.602 }, { 0, 0.526,-0.006,0.557 }, { 0, 0.522,-0.042,0.520 }, { 0, 0.510,-0.083,0.414 }, + { 0, 0.472,-0.113,0.282 }, { 0, 0.472,-0.113,0.282 }, { 0, 0.421,-0.104,0.186 }, { 0, 0.398,-0.029,0.072 }, + { 0, 0.413,0.016,0.046 }, { 0, 0.413,0.016,0.046 }, { 0, 0.420,0.108,0.023 }, { 0, 0.379,0.256,-0.008 }, + { 0, 0.362,0.327,-0.001 }, { 0, 0.374,0.383,0.036 }, { 0, 0.421,0.456,0.106 }, { 0, 0.450,0.500,0.191 }, + { 0, 0.452,0.518,0.220 }, { 0, 0.454,0.539,0.274 }, { 0, 0.460,0.547,0.315 }, { 0, 0.459,0.548,0.387 }, + { 0, 0.434,0.549,0.455 }, { 0, 0.425,0.540,0.492 }, { 0, 0.430,0.516,0.530 }, { 0, 0.354,0.505,0.617 }, + { 0, 0.354,0.505,0.617 }, { 0, 0.312,0.535,0.610 }, { 0, 0.312,0.535,0.610 }, { 0, 0.267,0.637,0.411 }, + { 0, 0.265,0.637,0.300 }, { 0, 0.276,0.613,0.216 }, { 0, 0.386,0.456,0.074 }, { 0, 0.446,0.267,0.030 }, + { 0, 0.460,0.175,0.030 }, { 0, 0.441,-0.006,0.078 }, { 0, 0.408,-0.086,0.129 }, { 0, 0.336,-0.193,0.268 }, + { 0, 0.238,-0.218,0.374 }, { 0, 0.192,-0.220,0.412 }, { 0, 0.156,-0.200,0.460 }, { 0, 0.099,-0.093,0.645 }, + { 0, 0.073,-0.041,0.690 }, { 0, 0.057,0.027,0.722 }, { 0, 0.026,0.096,0.738 }, { 0, -0.071,0.355,0.665 }, + { 0, -0.073,0.435,0.617 }, { 0, -0.059,0.511,0.554 }, { 0, -0.017,0.564,0.498 }, { 0, 0.164,0.642,0.313 }, + { 0, 0.225,0.631,0.261 }, { 0, 0.348,0.582,0.202 }, { 0, 0.348,0.582,0.202 }, { 0, 0.460,0.518,0.205 }, + { 0, 0.553,0.441,0.235 }, { 0, 0.589,0.405,0.273 }, { 0, 0.620,0.365,0.312 }, { 0, 0.621,0.320,0.466 }, + { 0, 0.617,0.290,0.516 }, { 0, 0.585,0.256,0.570 }, { 0, 0.570,0.231,0.618 }, { 0, 0.555,0.213,0.609 }, + { 0, 0.597,0.210,0.537 }, { 0, 0.637,0.217,0.441 }, { 0, 0.611,0.296,0.195 }, { 0, 0.483,0.359,0.061 }, + { 0, 0.483,0.359,0.061 }, { 0, 0.300,0.412,-0.004 }, { 0, 0.090,0.419,0.019 }, { 0, 0.010,0.402,0.038 }, + { 0, -0.093,0.287,0.071 }, { 0, -0.112,0.221,0.091 }, { 0, -0.155,0.079,0.204 }, { 0, -0.161,0.001,0.341 }, + { 0, -0.149,-0.023,0.409 }, { 0, -0.116,-0.044,0.486 }, { 0, -0.068,-0.043,0.577 }, { 0, 0.057,0.070,0.742 }, + { 0, 0.078,0.136,0.764 }, { 0, 0.081,0.210,0.770 }, { 0, 0.042,0.366,0.728 }, { 0, 0.024,0.492,0.650 }, + { 0, 0.014,0.544,0.597 }, { 0, 0.014,0.610,0.470 }, { 0, 0.030,0.629,0.414 }, { 0, 0.059,0.630,0.260 }, + { 0, 0.162,0.573,0.131 }, { 0, 0.214,0.549,0.110 }, { 0, 0.214,0.549,0.110 }, { 0, 0.376,0.485,0.098 }, + { 0, 0.436,0.460,0.117 }, { 0, 0.487,0.433,0.135 }, { 0, 0.515,0.408,0.142 }, { 0, 0.581,0.291,0.161 }, + { 0, 0.599,0.240,0.160 }, { 0, 0.615,0.192,0.180 }, { 0, 0.633,0.098,0.246 }, { 0, 0.619,0.010,0.389 }, + { 0, 0.619,0.010,0.389 }, { 0, 0.531,-0.096,0.465 }, { 0, 0.509,-0.119,0.454 }, { 0, 0.467,-0.150,0.458 }, + { 0, 0.442,-0.163,0.461 }, { 0, 0.444,-0.170,0.447 }, { 0, 0.440,-0.175,0.430 }, { 0, 0.388,-0.191,0.488 }, + { 0, 0.364,-0.187,0.505 }, { 0, 0.350,-0.199,0.502 }, { 0, 0.343,-0.200,0.476 }, { 0, 0.331,-0.210,0.425 }, + { 0, 0.333,-0.219,0.411 }, { 0, 0.324,-0.225,0.407 }, { 0, 0.351,-0.216,0.342 }, { 0, 0.403,-0.194,0.304 }, + { 0, 0.419,-0.187,0.289 }, { 0, 0.441,-0.160,0.239 }, { 0, 0.462,-0.151,0.247 }, { 0, 0.510,-0.115,0.245 }, + { 0, 0.515,-0.115,0.242 }, { 0, 0.519,-0.113,0.256 }, { 0, 0.519,-0.113,0.256 }, { 0, 0.537,-0.118,0.371 }, + { 0, 0.524,-0.129,0.423 }, { 0, 0.505,-0.136,0.480 }, { 0, 0.474,-0.130,0.527 }, { 0, 0.380,-0.082,0.666 }, + { 0, 0.336,-0.049,0.704 }, { 0, 0.298,-0.023,0.726 }, { 0, 0.273,0.010,0.747 }, { 0, 0.275,0.033,0.754 }, + { 0, 0.275,0.033,0.754 }, { 0, 0.249,0.063,0.769 }, { 0, 0.225,0.091,0.781 }, { 0, 0.301,0.221,0.779 }, + { 0, 0.333,0.275,0.768 }, { 0, 0.340,0.337,0.741 }, { 0, 0.347,0.377,0.721 }, { 0, 0.347,0.512,0.631 }, + { 0, 0.355,0.539,0.588 }, { 0, 0.355,0.570,0.556 }, { 0, 0.338,0.596,0.527 }, { 0, 0.300,0.649,0.404 }, + { 0, 0.273,0.655,0.356 }, { 0, 0.257,0.660,0.321 }, { 0, 0.216,0.654,0.260 }, { 0, 0.209,0.645,0.222 }, + { 0, 0.185,0.629,0.182 }, { 0, 0.093,0.570,0.115 }, { 0, 0.032,0.532,0.104 }, { 0, -0.079,0.483,0.140 }, + { 0, -0.139,0.436,0.191 }, { 0, -0.155,0.423,0.222 }, { 0, -0.167,0.416,0.250 }, { 0, -0.211,0.379,0.380 }, + { 0, -0.211,0.379,0.380 }, { 0, -0.207,0.380,0.356 }, { 0, -0.190,0.386,0.280 }, { 0, -0.004,0.443,0.072 }, + { 0, 0.141,0.493,0.062 }, { 0, 0.247,0.536,0.084 }, { 0, 0.308,0.564,0.125 }, { 0, 0.354,0.605,0.206 }, + { 0, 0.356,0.622,0.279 }, { 0, 0.327,0.632,0.342 }, { 0, 0.289,0.647,0.360 }, { 0, 0.282,0.649,0.357 }, + { 0, 0.290,0.631,0.198 }, { 0, 0.188,0.581,0.083 }, { 0, 0.188,0.581,0.083 }, { 0, -0.084,0.553,0.235 }, + { 0, -0.093,0.568,0.323 }, { 0, -0.093,0.568,0.323 }, { 0, -0.088,0.575,0.376 }, { 0, -0.072,0.572,0.434 }, + { 0, -0.093,0.570,0.378 }, { 0, -0.093,0.570,0.378 }, { 0, -0.048,0.558,0.191 }, { 0, 0.059,0.573,0.146 }, + { 0, 0.190,0.588,0.146 }, { 0, 0.307,0.634,0.281 }, { 0, 0.307,0.634,0.281 }, { 0, 0.253,0.654,0.372 }, + { 0, 0.208,0.656,0.395 }, { 0, 0.151,0.659,0.411 }, { 0, 0.082,0.649,0.438 }, { 0, -0.058,0.596,0.446 }, + { 0, -0.110,0.562,0.425 }, { 0, -0.133,0.543,0.381 }, { 0, -0.155,0.520,0.326 }, { 0, -0.153,0.464,0.219 }, + { 0, -0.143,0.442,0.179 }, { 0, -0.142,0.417,0.148 }, { 0, -0.114,0.373,0.098 }, { 0, 0.009,0.267,-0.004 }, + { 0, 0.062,0.241,-0.029 }, { 0, 0.062,0.233,-0.030 }, { 0, 0.054,0.233,-0.022 }, { 0, 0.056,0.277,-0.016 }, + { 0, 0.075,0.309,-0.024 }, { 0, 0.110,0.348,-0.015 }, { 0, 0.138,0.377,-0.011 }, { 0, 0.186,0.419,-0.002 }, + { 0, 0.200,0.425,-0.003 }, { 0, 0.218,0.421,-0.007 }, { 0, 0.215,0.410,-0.021 }, { 0, 0.231,0.409,-0.011 }, + { 0, 0.253,0.425,0.001 }, { 0, 0.253,0.425,0.001 }, { 0, 0.329,0.437,0.030 }, { 0, 0.376,0.412,0.036 }, + { 0, 0.408,0.383,0.033 }, { 0, 0.423,0.316,0.024 }, { 0, 0.423,0.316,0.024 }, { 0, 0.355,0.272,-0.025 }, + { 0, 0.295,0.331,-0.014 }, { 0, 0.280,0.365,-0.016 }, { 0, 0.280,0.365,-0.016 }, { 0, 0.205,0.448,0.020 }, + { 0, 0.205,0.448,0.020 }, { 0, 0.189,0.452,0.019 }, { 0, 0.181,0.432,0.006 }, { 0, 0.212,0.394,-0.016 }, + { 0, 0.218,0.386,-0.017 }, { 0, 0.217,0.346,-0.033 }, { 0, 0.242,0.299,-0.048 }, { 0, 0.234,0.260,-0.055 }, + { 0, 0.221,0.220,-0.047 }, { 0, 0.215,0.193,-0.041 }, { 0, 0.212,0.164,-0.042 }, { 0, 0.249,0.112,-0.028 }, + { 0, 0.355,0.083,0.012 }, { 0, 0.355,0.083,0.012 }, { 0, 0.418,0.056,0.044 }, { 0, 0.513,0.026,0.123 }, + { 0, 0.541,0.008,0.180 }, { 0, 0.566,-0.013,0.251 }, { 0, 0.582,-0.023,0.342 }, { 0, 0.513,-0.051,0.528 }, + { 0, 0.460,-0.067,0.573 }, { 0, 0.406,-0.071,0.615 }, { 0, 0.352,-0.072,0.642 }, { 0, 0.201,-0.030,0.693 }, + { 0, 0.142,0.004,0.717 }, { 0, 0.088,0.045,0.721 }, { 0, 0.028,0.091,0.720 }, { 0, -0.036,0.153,0.717 }, + { 0, -0.052,0.172,0.711 }, { 0, -0.057,0.180,0.717 }, { 0, -0.028,0.177,0.735 }, { 0, -0.088,0.177,0.675 }, + { 0, -0.129,0.177,0.641 }, { 0, -0.178,0.200,0.571 }, { 0, -0.203,0.237,0.501 }, { 0, -0.225,0.270,0.407 }, + { 0, -0.225,0.270,0.407 }, { 0, -0.214,0.296,0.306 }, { 0, -0.214,0.296,0.306 }, { 0, -0.181,0.328,0.216 }, + { 0, -0.123,0.355,0.156 }, { 0, -0.123,0.355,0.156 }, { 0, -0.081,0.394,0.117 }, { 0, -0.047,0.422,0.100 }, + { 0, -0.006,0.440,0.083 }, { 0, -0.006,0.440,0.083 }, { 0, -0.006,0.440,0.083 }, { 0, 0.031,0.454,0.066 }, + { 0, 0.063,0.455,0.049 }, { 0, 0.063,0.455,0.049 }, { 0, 0.091,0.467,0.041 }, { 0, 0.108,0.470,0.045 }, + { 0, 0.108,0.470,0.045 }, { 0, 0.126,0.472,0.041 }, { 0, 0.126,0.472,0.041 }, { 0, 0.154,0.477,0.043 }, + { 0, 0.154,0.477,0.043 }, { 0, 0.166,0.476,0.030 }, { 0, 0.165,0.458,0.029 }, { 0, 0.165,0.458,0.029 }, + { 0, 0.141,0.451,0.029 }, { 0, 0.141,0.451,0.029 }, { 0, 0.108,0.458,0.031 }, { 0, 0.089,0.463,0.038 }, + { 0, 0.089,0.463,0.038 }, { 0, 0.061,0.477,0.056 }, { 0, 0.061,0.477,0.056 }, { 0, 0.035,0.508,0.094 }, + { 0, 0.025,0.537,0.126 }, { 0, 0.025,0.537,0.126 }, { 0, 0.020,0.570,0.159 }, { 0, 0.030,0.582,0.184 }, + { 0, 0.030,0.582,0.184 }, { 0, 0.038,0.592,0.195 }, { 0, 0.038,0.592,0.195 }, { 0, 0.051,0.588,0.182 }, + { 0, 0.051,0.588,0.182 }, { 0, 0.044,0.586,0.175 }, { 0, 0.044,0.586,0.175 }, { 0, 0.039,0.577,0.170 }, + { 0, 0.039,0.578,0.158 }, { 0, 0.039,0.578,0.158 }, { 0, 0.034,0.561,0.140 }, { 0, 0.046,0.540,0.106 }, + { 0, 0.046,0.540,0.106 }, { 0, 0.046,0.540,0.106 }, { 0, 0.067,0.528,0.087 }, { 0, 0.076,0.502,0.059 }, + { 0, 0.076,0.502,0.059 }, { 0, 0.088,0.482,0.036 }, { 0, 0.088,0.482,0.036 }, { 0, 0.117,0.441,0.016 }, + { 0, 0.189,0.401,-0.009 }, { 0, 0.189,0.401,-0.009 }, { 0, 0.281,0.357,-0.028 }, { 0, 0.397,0.331,0.010 }, + { 0, 0.397,0.331,0.010 }, { 0, 0.504,0.293,0.076 }, { 0, 0.504,0.293,0.076 }, { 0, 0.592,0.267,0.173 }, + { 0, 0.637,0.244,0.291 }, { 0, 0.637,0.244,0.291 }, { 0, 0.635,0.237,0.402 }, { 0, 0.612,0.247,0.485 }, + { 0, 0.612,0.247,0.485 }, { 0, 0.573,0.266,0.562 }, { 0, 0.573,0.266,0.562 }, { 0, 0.497,0.289,0.645 }, + { 0, 0.382,0.306,0.729 }, { 0, 0.382,0.306,0.729 }, { 0, 0.382,0.306,0.729 }, { 0, 0.135,0.303,0.770 }, + { 0, 0.135,0.303,0.770 }, { 0, 0.135,0.303,0.770 }, { 0, 0.048,0.300,0.755 }, { 0, -0.020,0.306,0.728 }, + { 0, -0.053,0.310,0.715 }, { 0, -0.053,0.310,0.715 }, { 0, -0.053,0.310,0.715 }, { 0, -0.089,0.305,0.682 }, + { 0, -0.134,0.308,0.631 }, { 0, -0.168,0.318,0.574 }, { 0, -0.168,0.318,0.574 }, { 0, -0.194,0.324,0.528 }, + { 0, -0.207,0.327,0.473 }, { 0, -0.207,0.327,0.473 }, { 0, -0.207,0.327,0.473 }, { 0, -0.206,0.330,0.434 }, + { 0, -0.205,0.341,0.403 }, { 0, -0.205,0.341,0.403 }, { 0, -0.205,0.358,0.363 }, { 0, -0.202,0.360,0.338 }, + { 0, -0.202,0.360,0.338 }, { 0, -0.197,0.363,0.299 }, { 0, -0.197,0.363,0.299 }, { 0, -0.190,0.360,0.253 }, + { 0, -0.190,0.360,0.253 }, { 0, -0.168,0.347,0.202 }, { 0, -0.144,0.338,0.154 }, { 0, -0.103,0.327,0.109 }, + { 0, -0.103,0.327,0.109 }, { 0, -0.068,0.308,0.066 }, { 0, -0.068,0.308,0.066 }, { 0, -0.032,0.301,0.040 }, + { 0, 0.006,0.269,0.017 }, { 0, 0.006,0.269,0.017 }, { 0, 0.039,0.234,0.002 }, { 0, 0.074,0.194,-0.017 }, + { 0, 0.074,0.194,-0.017 }, { 0, 0.074,0.194,-0.017 }, { 0, 0.087,0.148,-0.010 }, { 0, 0.094,0.105,-0.005 }, + { 0, 0.110,0.053,0.008 }, { 0, 0.110,0.053,0.008 }, { 0, 0.130,0.014,0.022 }, { 0, 0.160,-0.030,0.039 }, + { 0, 0.160,-0.030,0.039 }, { 0, 0.180,-0.067,0.057 }, { 0, 0.180,-0.067,0.057 }, { 0, 0.197,-0.096,0.083 }, + { 0, 0.206,-0.116,0.107 }, { 0, 0.206,-0.116,0.107 }, { 0, 0.206,-0.116,0.107 }, { 0, 0.259,-0.136,0.131 }, + { 0, 0.259,-0.136,0.131 }, { 0, 0.259,-0.136,0.131 }, { 0, 0.285,-0.143,0.130 }, { 0, 0.323,-0.148,0.148 }, + { 0, 0.339,-0.161,0.163 }, { 0, 0.339,-0.161,0.163 }, { 0, 0.350,-0.177,0.186 }, { 0, 0.350,-0.177,0.186 }, + { 0, 0.347,-0.201,0.225 }, { 0, 0.332,-0.216,0.251 }, { 0, 0.332,-0.216,0.251 }, { 0, 0.320,-0.223,0.284 }, + { 0, 0.310,-0.216,0.310 }, { 0, 0.310,-0.216,0.310 }, { 0, 0.295,-0.220,0.337 }, { 0, 0.295,-0.220,0.337 }, + { 0, 0.291,-0.231,0.348 }, { 0, 0.307,-0.235,0.352 }, { 0, 0.307,-0.235,0.352 }, { 0, 0.308,-0.253,0.363 }, + { 0, 0.291,-0.250,0.363 }, { 0, 0.291,-0.250,0.363 }, { 0, 0.269,-0.251,0.369 }, { 0, 0.269,-0.251,0.369 }, + { 0, 0.228,-0.237,0.362 }, { 0, 0.201,-0.238,0.367 }, { 0, 0.201,-0.238,0.367 }, { 0, 0.183,-0.235,0.343 }, + { 0, 0.161,-0.219,0.313 }, { 0, 0.161,-0.219,0.313 }, { 0, 0.149,-0.208,0.290 }, { 0, 0.151,-0.205,0.273 }, + { 0, 0.151,-0.205,0.273 }, { 0, 0.160,-0.213,0.275 }, { 0, 0.143,-0.220,0.311 }, { 0, 0.143,-0.220,0.311 }, + { 0, 0.143,-0.220,0.311 }, { 0, 0.128,-0.222,0.363 }, { 0, 0.128,-0.222,0.363 }, { 0, 0.116,-0.214,0.430 }, + { 0, 0.116,-0.214,0.430 }, { 0, 0.116,-0.214,0.430 }, { 0, 0.089,-0.188,0.503 }, { 0, 0.089,-0.188,0.503 }, + { 0, 0.057,-0.158,0.560 }, { 0, 0.057,-0.158,0.560 }, { 0, 0.057,-0.158,0.560 }, { 0, 0.023,-0.115,0.611 }, + { 0, 0.023,-0.115,0.611 }, { 0, 0.023,-0.115,0.611 }, { 0, 0.002,-0.066,0.649 }, { 0, 0.002,-0.066,0.649 }, + { 0, -0.009,-0.017,0.672 }, { 0, -0.009,-0.017,0.672 }, { 0, -0.009,-0.017,0.672 }, { 0, -0.016,0.046,0.708 }, + { 0, -0.016,0.046,0.708 }, { 0, -0.006,0.120,0.737 }, { 0, -0.006,0.120,0.737 }, { 0, 0.019,0.193,0.760 }, + { 0, 0.019,0.193,0.760 }, { 0, 0.019,0.193,0.760 }, { 0, 0.019,0.193,0.760 }, { 0, 0.046,0.268,0.766 }, + { 0, 0.046,0.268,0.766 }, { 0, 0.058,0.334,0.748 }, { 0, 0.058,0.334,0.748 }, { 0, 0.034,0.380,0.726 }, + { 0, 0.034,0.380,0.726 }, { 0, 0.034,0.380,0.726 }, { 0, 0.001,0.411,0.685 }, { 0, 0.001,0.411,0.685 }, + { 0, -0.047,0.435,0.657 }, { 0, -0.047,0.435,0.657 }, { 0, -0.047,0.435,0.657 }, { 0, -0.073,0.450,0.612 }, + { 0, -0.073,0.450,0.612 }, { 0, -0.073,0.450,0.612 }, { 0, -0.101,0.459,0.579 }, { 0, -0.101,0.459,0.579 }, + { 0, -0.129,0.464,0.529 }, { 0, -0.129,0.464,0.529 }, { 0, -0.129,0.464,0.529 }, { 0, -0.155,0.461,0.475 }, + { 0, -0.155,0.461,0.475 }, { 0, -0.171,0.459,0.424 }, { 0, -0.171,0.459,0.424 }, { 0, -0.171,0.459,0.424 }, + { 0, -0.187,0.449,0.347 }, { 0, -0.187,0.449,0.347 }, { 0, -0.178,0.438,0.289 }, { 0, -0.178,0.438,0.289 }, + { 0, -0.151,0.418,0.218 }, { 0, -0.151,0.418,0.218 }, { 0, -0.151,0.418,0.218 }, { 0, -0.112,0.383,0.131 }, + { 0, -0.112,0.383,0.131 }, { 0, -0.112,0.383,0.131 }, { 0, -0.044,0.351,0.057 }, { 0, -0.044,0.351,0.057 }, + { 0, 0.034,0.304,0.012 }, { 0, 0.034,0.304,0.012 }, { 0, 0.034,0.304,0.012 }, { 0, 0.119,0.270,-0.016 }, + { 0, 0.119,0.270,-0.016 }, { 0, 0.200,0.235,-0.032 }, { 0, 0.200,0.235,-0.032 }, { 0, 0.264,0.217,-0.025 }, + { 0, 0.264,0.217,-0.025 }, { 0, 0.264,0.217,-0.025 }, { 0, 0.264,0.217,-0.025 }, { 0, 0.308,0.186,-0.018 }, + { 0, 0.308,0.186,-0.018 }, { 0, 0.349,0.171,-0.012 }, { 0, 0.349,0.171,-0.012 }, { 0, 0.361,0.165,0.001 }, + { 0, 0.361,0.165,0.001 } + }; +} diff --git a/src/tests/calibration/test-calibration-benchmark.cpp b/src/tests/calibration/test-calibration-benchmark.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f739ae7e7515d71a8d9dcbc21239b63b96575d93 --- /dev/null +++ b/src/tests/calibration/test-calibration-benchmark.cpp @@ -0,0 +1,113 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +/* ACCELEROMETER calibration: please set the chosen one to 1 */ +#define BIAS_CALIBRATION_LOAD_TEST 1 +#define SIX_PARAMETER_CALIBRATION_LOAD_TEST 0 +#define TWELVE_PARAMETER_CALIBRATION_LOAD_TEST 0 + +/* Expressed in Hertz: valid values: 1 <= frequency <= 1000 */ +#define SAMPLE_FREQUENCY_LOAD_TEST 1000 + +#include <Common.h> +#include <drivers/spi/SPIDriver.h> +#include <diagnostic/CpuMeter.h> +#include <miosix.h> + +#include "sensors/LIS3DSH/LIS3DSH.h" +#include "sensors/calibration/BiasCalibration.h" +#include "sensors/calibration/HardIronCalibration.h" +#include "sensors/calibration/SixParameterCalibration.h" +#include "sensors/calibration/SoftIronCalibration.h" +#include "sensors/calibration/TwelveParameterCalibration.h" + +using namespace miosix; + +/* using volatile so that the compiler won't optimize out the variable */ +volatile AccelerometerData testData; + +int main() +{ +#if BIAS_CALIBRATION_LOAD_TEST + BiasCorrector<AccelerometerData> corrector; + TRACE("Using BIAS calibration model.\n"); +#endif + +#if SIX_PARAMETER_CALIBRATION_LOAD_TEST + SixParameterCorrector<AccelerometerData> corrector; + TRACE("Using SIX-PARAMETER calibration model.\n"); +#endif + +#if TWELVE_PARAMETER_CALIBRATION_LOAD_TEST + TwelveParameterCorrector<AccelerometerData> corrector; + TRACE("Using TWELVE-PARAMETER calibration model.\n"); +#endif + + SPIBus bus(SPI1); + + GpioPin spi_sck(GPIOA_BASE, 5); + GpioPin spi_miso(GPIOA_BASE, 6); + GpioPin spi_mosi(GPIOA_BASE, 7); + GpioPin cs(GPIOE_BASE, 3); + + { + miosix::FastInterruptDisableLock dLock; + + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // Enable SPI1 bus + + spi_sck.mode(miosix::Mode::ALTERNATE); + spi_sck.alternateFunction(5); + spi_miso.mode(miosix::Mode::ALTERNATE); + spi_miso.alternateFunction(5); + spi_mosi.mode(miosix::Mode::ALTERNATE); + spi_mosi.alternateFunction(5); + + cs.mode(miosix::Mode::OUTPUT); + } + cs.high(); + + LIS3DSH sensor(bus, cs, sensor.ODR_100_HZ, sensor.UPDATE_AFTER_READ_MODE, + sensor.FULL_SCALE_4G); + sensor.init(); + + constexpr unsigned sleepMillis = 1000 / SAMPLE_FREQUENCY_LOAD_TEST; + unsigned elapsed = 0; + + while(1){ + Thread::sleep(sleepMillis); + + elapsed += sleepMillis; + sensor.sample(); + + AccelerometerData tmp = corrector.correct(sensor.getLastSample()); + + testData.accel_x = tmp.accel_x; + testData.accel_y = tmp.accel_y; + testData.accel_z = tmp.accel_z; + + if(elapsed > 500){ + elapsed = 0; + TRACE("Average CPU usage: %f %%\n", averageCpuUtilization()); + } + } +} + diff --git a/src/tests/calibration/test-calibration-stats.cpp b/src/tests/calibration/test-calibration-stats.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b083edfbeee7973686148699024481c6faa3c20d --- /dev/null +++ b/src/tests/calibration/test-calibration-stats.cpp @@ -0,0 +1,312 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#define SENSOR_LIS3DSH_STATS_TEST 1 + +#include <Common.h> +#include <drivers/spi/SPIDriver.h> +#include <math/Stats.h> +#include <miosix.h> + +#if SENSOR_LIS3DSH_STATS_TEST +#include "sensors/LIS3DSH/LIS3DSH.h" +#endif + +#include "sensors/calibration/Calibration.h" + +using namespace miosix; + +/* using volatile so that the compiler won't optimize out the variable */ +volatile AccelerometerData testData; + +/* + * For future readers: new lines are inserted so that each line does no exceed + * 57 characters (tab counts as 8 + 49 chars). + */ +const char* helpInfo = + "Here are all available commands:\n\n" + " ?\n\tDisplay this information.\n\n" + " olist\n\tList which of the orientations are still to sample.\n\n" + " oshow\n\tDescribe the current orientation.\n\n" + " onext\n\tGo to the next orientation of the list (marking\n\tthe current " + "one as sampled.\n\n" + " oset [idx]\n\tManually sets the current orientation to the " + "[idx]\n\tone of the list given by 'olist'.\n\n" + " take [n]\n\tTake [n] samples from the sensor and use them " + "to\n\tcalculate the descriptive values such as min, max," + "\n\tvariance, etc. Note: old values will be used unless\n\t'discard' " + "command is called.\n\n" + " setsleep [millis]\n\tSet the sleep time between samples to " + "[millis].\n\tThe default sleep time value is 0.\n\n" + " discard\n\tReset all stats about the current orientation.\n\n" + " min\n\tShow the minimum value measured on each axis.\n\n" + " max\n\tShow the maximum value.\n\n" + " mean\n\tShow the mean.\n\n" + " stddev\n\tShow the standard deviation.\n\n" + " samples\n\tShow how many samples are used for the calculation\n\t" + "of the descriptive values (for current orientation\n\tonly).\n\n"; + +int main() +{ + SPIBus bus(SPI1); + + GpioPin spi_sck(GPIOA_BASE, 5); + GpioPin spi_miso(GPIOA_BASE, 6); + GpioPin spi_mosi(GPIOA_BASE, 7); + GpioPin cs(GPIOE_BASE, 3); + + { + miosix::FastInterruptDisableLock dLock; + + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // Enable SPI1 bus + + spi_sck.mode(miosix::Mode::ALTERNATE); + spi_sck.alternateFunction(5); + spi_miso.mode(miosix::Mode::ALTERNATE); + spi_miso.alternateFunction(5); + spi_mosi.mode(miosix::Mode::ALTERNATE); + spi_mosi.alternateFunction(5); + + cs.mode(miosix::Mode::OUTPUT); + } + cs.high(); + + LIS3DSH sensor(bus, cs, sensor.ODR_100_HZ, sensor.UPDATE_AFTER_READ_MODE, + sensor.FULL_SCALE_4G); + sensor.init(); + + const AxisOrthoOrientation orientations[] = { + {Direction::POSITIVE_X, Direction::POSITIVE_Y}, + {Direction::POSITIVE_Y, Direction::POSITIVE_Z}, + {Direction::POSITIVE_Z, Direction::POSITIVE_X}, + {Direction::POSITIVE_Y, Direction::POSITIVE_X}, + {Direction::POSITIVE_Z, Direction::POSITIVE_Y}, + {Direction::POSITIVE_X, Direction::POSITIVE_Z}, + }; + + int selected = 0, sleepTime = 0; + char input[30]; + + Stats xAxis[6], yAxis[6], zAxis[6]; + + printf( + "Welcome to the sensor stats tool.\n" + "Commands are given via serial input. Type ? to see accepted " + "commands\n"); + + while (1) + { + printf("> "); + fgets(input, 30, stdin); + + if (!strncmp("?", input, 1)) + { + printf("%s", helpInfo); + } + else if (!strncmp("olist", input, 5)) + { + printf("Here are all the recognized orientations:\n"); + + for (int i = 0; i < 6; i++) + { + const char* xLabel = + humanFriendlyDirection[(uint8_t)orientations[i].xAxis]; + const char* yLabel = + humanFriendlyDirection[(uint8_t)orientations[i].yAxis]; + + printf(" %d. X towards %s, Y towards %s (%d samples)", i, + xLabel, yLabel, xAxis[i].getStats().nSamples); + + if (selected == i) + printf(" [selected]"); + printf("\n"); + } + } + else if (!strncmp("oshow", input, 5)) + { + iLoveSpaghettiCode: + const char* xLabel = + humanFriendlyDirection[(uint8_t)orientations[selected].xAxis]; + const char* yLabel = + humanFriendlyDirection[(uint8_t)orientations[selected].yAxis]; + printf("Current orientation is: X towards %s, Y towards %s\n", + xLabel, yLabel); + + printf("You took %d samples for each axis on this orientation.\n", + xAxis[selected].getStats().nSamples); + } + else if (!strncmp("onext", input, 5)) + { + selected++; + + printf("Switching to orientation n. %d\n", selected); + goto iLoveSpaghettiCode; + } + else if (!strncmp("oset", input, 4)) + { + int idx; + if (!sscanf(input, "oset %d", &idx) || idx < 0 || idx > 6) + { + printf("oset: invalid argument. %d \n", idx); + } + else + { + selected = idx; + + printf("Switching to orientation n. %d\n", selected); + goto iLoveSpaghettiCode; + } + } + else if (!strncmp("take", input, 4)) + { + int n; + if (!sscanf(input, "take %d", &n) || n <= 0) + { + printf("take: invalid argument.\n"); + } + else + { + for (int i = 0; i < n; ++i) + { + sensor.sample(); + auto data = sensor.getLastSample(); + + xAxis[selected].add(data.accel_x); + yAxis[selected].add(data.accel_y); + zAxis[selected].add(data.accel_z); + + printf("Added sample: %f, %f, %f\n", data.accel_x, + data.accel_y, data.accel_z); + + if (sleepTime > 0) + { + Thread::sleep(sleepTime); + } + } + } + } + else if (!strncmp("setsleep", input, 8)) + { + int newSleep; + if (!sscanf(input, "setsleep %d", &newSleep) || newSleep < 0) + { + printf("setsleep: invalid argument.\n"); + } + else + { + sleepTime = newSleep; + printf("Set new sleep time to %d milliseconds.\n", newSleep); + } + } + else if (!strncmp("discard", input, 7)) + { + xAxis[selected].reset(); + yAxis[selected].reset(); + zAxis[selected].reset(); + + printf("Data discarded.\n"); + } + else if (!strncmp("min", input, 3)) + { + StatsResult xRes = xAxis[selected].getStats(), + yRes = yAxis[selected].getStats(), + zRes = zAxis[selected].getStats(); + + if (xRes.nSamples == 0) + { + printf( + "Error: not enough samples. Please invoke command " + "'take'.\n"); + } + else + { + printf("\t%f, %f, %f\n", xRes.minValue, yRes.minValue, + zRes.minValue); + } + } + else if (!strncmp("max", input, 3)) + { + StatsResult xRes = xAxis[selected].getStats(), + yRes = yAxis[selected].getStats(), + zRes = zAxis[selected].getStats(); + + if (xRes.nSamples == 0) + { + printf( + "Error: not enough samples. Please invoke command " + "'take'.\n"); + } + else + { + printf("\t%f, %f, %f\n", + xRes.maxValue, yRes.maxValue, zRes.maxValue); + } + } + else if (!strncmp("mean", input, 3)) + { + StatsResult xRes = xAxis[selected].getStats(), + yRes = yAxis[selected].getStats(), + zRes = zAxis[selected].getStats(); + + if (xRes.nSamples == 0) + { + printf( + "Error: not enough samples. Please invoke command " + "'take'.\n"); + } + else + { + printf("\t%f, %f, %f\n", + xRes.mean, yRes.mean, zRes.mean); + } + } + else if (!strncmp("stddev", input, 3)) + { + StatsResult xRes = xAxis[selected].getStats(), + yRes = yAxis[selected].getStats(), + zRes = zAxis[selected].getStats(); + + if (xRes.nSamples == 0) + { + printf( + "Error: not enough samples. Please invoke command " + "'take'.\n"); + } + else + { + printf( + "\t%f, %f, %f\n", + xRes.stdev, yRes.stdev, zRes.stdev); + } + } + else if (!strncmp("samples", input, 7)) + { + printf("Taken %d samples on the current orientation.\n", + xAxis[selected].getStats().nSamples); + } + else + { + printf("Unrecognized command. Type ? for help.\n"); + } + } +} + diff --git a/src/tests/calibration/test-calibration.cpp b/src/tests/calibration/test-calibration.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e7a8da549ee0c2c48809e211128e70d7011e4b4c --- /dev/null +++ b/src/tests/calibration/test-calibration.cpp @@ -0,0 +1,267 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +/* Please choose one between accelerometer or magnetometer */ +#define TEST_ACCELEROMETER_DATA 1 +#define TEST_MAGNETOMETER_DATA 0 + +/* ACCELEROMETER calibration: please set the chosen one to 1 */ +#define BIAS_CALIBRATION_TEST 0 +#define SIX_PARAMETER_CALIBRATION_TEST 0 +#define TWELVE_PARAMETER_CALIBRATION_TEST 1 + +/* MAGNETOMETER calibration: please set the chosen one to 1 */ +#define HARD_IRON_CALIBRATION_TEST 1 +#define SOFT_IRON_CALIBRATION_TEST 0 + +#include <Common.h> +#include <drivers/spi/SPIDriver.h> +#include <miosix.h> + +#include "sensors/LIS3DSH/LIS3DSH.h" +#include "sensors/calibration/BiasCalibration.h" +#include "sensors/calibration/HardIronCalibration.h" +#include "sensors/calibration/SixParameterCalibration.h" +#include "sensors/calibration/SoftIronCalibration.h" +#include "sensors/calibration/TwelveParameterCalibration.h" + +#if TEST_ACCELEROMETER_DATA +#include "accelerometer-data.h" +#elif TEST_MAGNETOMETER_DATA +#include "magnetometer-data.h" +#endif + +using namespace miosix; + +int main() +{ +#if TEST_ACCELEROMETER_DATA + + constexpr unsigned NumSamples = accData::nOrientations * accData::nSamples; + +#if BIAS_CALIBRATION_TEST + auto* model = new BiasCalibration<AccelerometerData>; + + TRACE("Using BIAS calibration model.\n"); +#endif + +#if SIX_PARAMETER_CALIBRATION_TEST + auto* model = new SixParameterCalibration<AccelerometerData, NumSamples>; + model->setReferenceVector({0, 0, 1}); + + TRACE("Using SIX-PARAMETER calibration model.\n"); +#endif + +#if TWELVE_PARAMETER_CALIBRATION_TEST + auto* model = new TwelveParameterCalibration<AccelerometerData, NumSamples>; + model->setReferenceVector({0, 0, 1}); + + TRACE("Using TWELVE-PARAMETER calibration model.\n"); +#endif + + TRACE("Feeding accelerometer data to the calibration model ... \n"); + + for (unsigned i = 0; i < accData::nOrientations; i++) + for (unsigned j = 0; j < accData::nSamples; j++) + model->feed(accData::samples[i][j], accData::orientations[i]); + + TRACE("Computing the result ... \n"); + + auto corrector = model->computeResult(); + + TRACE("Now testing with the same data: \n"); + + float err0 = 0.f, err1 = 0.f; + Vector3f vec0 = {0, 0, 0}, vec1 = {0, 0, 0}; + + for (unsigned i = 0; i < accData::nOrientations; i++) + { + TRACE( + " --- Orientation n. %d: X axis oriented toward %s, Y axis " + "oriented toward %s\n", + i, + humanFriendlyDirection[static_cast<uint8_t>( + accData::orientations[i].xAxis)], + humanFriendlyDirection[static_cast<uint8_t>( + accData::orientations[i].yAxis)]); + + for (unsigned j = 0; j < accData::nSamples; j++) + { + AccelerometerData out; + Vector3f exact, before, after, d0, d1; + + exact = accData::orientations[i].getMatrix().transpose() * + Vector3f{0, 0, 1}; + out = corrector.correct(accData::samples[i][j]); + out >> after; + accData::samples[i][j] >> before; + + /* Every five samples, print one */ + if (j % 5 == 0) + { + TRACE("%f %f %f -> %f %f %f\n", before[0], before[1], before[2], + after[0], after[1], after[2]); + } + + d0 = exact - before; + d1 = exact - after; + + err0 += d0.norm(); + err1 += d1.norm(); + + vec0 += Vector3f{abs(d0[0]), abs(d0[1]), abs(d0[2])}; + vec1 += Vector3f{abs(d1[0]), abs(d1[1]), abs(d1[2])}; + } + } + + const unsigned n = accData::nOrientations * accData::nSamples; + + err0 /= n; + err1 /= n; + vec0 /= n; + vec1 /= n; + + TRACE( + "Average norm of displacement vector on accelerometer data: %.3f " + "(before it was %.3f)\n", + err1, err0); + TRACE("Here is the average error per axis (in percentage):\n"); + TRACE("Before calibration:\t\tx: %3.3f%%\ty: %3.3f%%\tz: %3.3f%%\n", + vec0[0] * 100, vec0[1] * 100, vec0[2] * 100); + TRACE("After calibration:\t\tx: %3.3f%%\ty: %3.3f%%\tz: %3.3f%%\n", + vec1[0] * 100, vec1[1] * 100, vec1[2] * 100); + + TRACE("The parameters are:\n\n"); + +#if BIAS_CALIBRATION_TEST + Vector3f bias; + corrector >> bias; + + TRACE("b: the bias vector\n"); + TRACE("b = [ % 2.5f % 2.5f % 2.5f ]\n\n", bias[0], bias[1], + bias[2]); +#endif + +#if SIX_PARAMETER_CALIBRATION_TEST + Matrix<float, 3, 2> m; + corrector >> m; + + TRACE("b: the bias vector\n"); + TRACE("b = [ % 2.5f % 2.5f % 2.5f ]\n\n", m(0, 1), m(1, 1), + m(2, 1)); + + TRACE("M: the matrix to be multiplied to the input vector\n"); + + TRACE(" | % 2.5f % 2.5f % 2.5f |\n", m(0, 0), 0.f, 0.f); + TRACE("M = | % 2.5f % 2.5f % 2.5f |\n", 0.f, m(1, 0), 0.f); + TRACE(" | % 2.5f % 2.5f % 2.5f |\n", 0.f, 0.f, m(2, 0)); +#endif + +#if TWELVE_PARAMETER_CALIBRATION_TEST + Matrix<float, 3, 4> m; + corrector >> m; + + TRACE("b: the bias vector\n"); + TRACE("b = [ % 2.5f % 2.5f % 2.5f ]\n\n", m(0, 1), m(1, 1), + m(2, 1)); + + TRACE("M: the matrix to be multiplied to the input vector\n"); + + TRACE(" | % 2.5f % 2.5f % 2.5f |\n", m(0, 0), m(0, 1), + m(0, 2)); + TRACE("M = | % 2.5f % 2.5f % 2.5f |\n", m(1, 0), m(1, 1), + m(1, 2)); + TRACE(" | % 2.5f % 2.5f % 2.5f |\n", m(2, 0), m(2, 1), + m(2, 2)); +#endif + +#endif /* #if TEST_ACCELEROMETER_DATA */ + +#if TEST_MAGNETOMETER_DATA + + constexpr unsigned NumSamples = magnetoData::nSamples; + +#if HARD_IRON_CALIBRATION_TEST + auto* model = new HardIronCalibration<NumSamples>; + + TRACE("Using Hard-iron correction model.\n"); +#endif + +#if SOFT_IRON_CALIBRATION_TEST + auto* model = new SoftIronCalibration<NumSamples>; + + TRACE("Using Soft-iron correction model.\n"); +#endif + + TRACE("Feeding magnetometer data to the calibration model ... \n"); + + for (unsigned i = 0; i < magnetoData::nSamples; i++) + model->feed(magnetoData::samples[i]); + + TRACE("Computing the result ... \n"); + + auto corrector = model->computeResult(); + + TRACE("Done.\n"); + + /* Prints only 1/10 of samples */ + for (unsigned i = 0; i < magnetoData::nSamples; i += 10) + { + MagnetometerData out; + Vector3f before, after; + + out = corrector.correct(magnetoData::samples[i]); + out >> after; + magnetoData::samples[i] >> before; + + TRACE("%f %f %f -> %f %f %f\n", before[0], before[1], before[2], + after[0], after[1], after[2]); + } + + TRACE("The parameters are:\n\n"); + +#if HARD_IRON_CALIBRATION_TEST + Vector3f bias; + corrector >> bias; + + TRACE("v: the hard iron correction\n"); + TRACE("v = [ % 2.5f % 2.5f % 2.5f ]\n\n", bias[0], bias[1], + bias[2]); +#endif + +#if SOFT_IRON_CALIBRATION_TEST + Matrix<float, 3, 2> m; + corrector >> m; + + TRACE("v: the hard iron correction\n"); + TRACE("v = [ % 2.5f % 2.5f % 2.5f ]\n\n", m(0, 1), m(1, 1), + m(2, 1)); + + TRACE("W: the soft iron correction\n"); + + TRACE(" | % 2.5f % 2.5f % 2.5f |\n", m(0, 0), 0.f, 0.f); + TRACE("W = | % 2.5f % 2.5f % 2.5f |\n", 0.f, m(1, 0), 0.f); + TRACE(" | % 2.5f % 2.5f % 2.5f |\n", 0.f, 0.f, m(2, 0)); +#endif + +#endif /* #if TEST_MAGNETOMETER_DATA */ +} diff --git a/src/tests/catch/catch-tests-entry.cpp b/src/tests/catch/catch-tests-entry.cpp index b3d785223af626e93de1ccb1fbbaf31a70032f0d..119c654e0942973cc8de1a9eca7d6ea3f33b3692 100644 --- a/src/tests/catch/catch-tests-entry.cpp +++ b/src/tests/catch/catch-tests-entry.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/catch/examples/example-test-factorial.cpp b/src/tests/catch/examples/example-test-factorial.cpp index c2b2a0a015d50061fd3fb9c71ad89f2d5c345c1a..2e0ed3d531130c1b7080c38864030d97f6ba3636 100644 --- a/src/tests/catch/examples/example-test-factorial.cpp +++ b/src/tests/catch/examples/example-test-factorial.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/catch/examples/example-test-fsm.cpp b/src/tests/catch/examples/example-test-fsm.cpp index 5a9559c029d7c0fc5deedebf88792a7159fcff65..7c480923cb82561e393f72c554c9cd256756484b 100644 --- a/src/tests/catch/examples/example-test-fsm.cpp +++ b/src/tests/catch/examples/example-test-fsm.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/catch/examples/example-test-fsm.h b/src/tests/catch/examples/example-test-fsm.h index f8c7aada32e09492a65603181a7efcbb19c4bcb0..6a2b84c14cece5a75ad277b10f6bff22c61314f6 100644 --- a/src/tests/catch/examples/example-test-fsm.h +++ b/src/tests/catch/examples/example-test-fsm.h @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/catch/spidriver/test-spidriver.cpp b/src/tests/catch/spidriver/test-spidriver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0bcc3975f80f5bd47006326a310c370535f20e42 --- /dev/null +++ b/src/tests/catch/spidriver/test-spidriver.cpp @@ -0,0 +1,547 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#ifdef STANDALONE_CATCH1_TEST +#include "../catch-tests-entry.cpp" +#endif + +#ifndef USE_MOCK_PERIPHERALS +#error "This test requires SpiBusInterface built with MockGpioPin (-DUSE_MOCK_PERIPHERALS)" +#endif + +#include <utils/testutils/catch.hpp> + +#include "drivers/spi/SPIBus.h" +#include "drivers/spi/SPIDriver.h" +#include "drivers/spi/test/FakeSpiTypedef.h" +#include "drivers/spi/test/MockSPIBus.h" + +template <typename T1, typename T2> +bool bufcmp(T1* buf1, T2* buf2, size_t size) +{ + for (size_t i = 0; i < size; i++) + { + if (*buf1 != *buf2) + return false; + + buf1++; + buf2++; + } + return true; +} + +TEST_CASE("SPIBus - Bus Configuration") +{ + FakeSpiTypedef spi; + + SPIBus bus{&spi}; + + REQUIRE(spi.CR1 == 0); + + SECTION("Configure & check CR1") + { + SPIBusConfig config; + REQUIRE(spi.CR1 == 0); + + SECTION("Mode") + { + config.mode = SPIMode::MODE0; + uint32_t expected_CR1 = 0x0344; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.mode = SPIMode::MODE1; + expected_CR1 = 0x0345; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.mode = SPIMode::MODE2; + expected_CR1 = 0x0346; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.mode = SPIMode::MODE3; + expected_CR1 = 0x0347; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + } + + SECTION("Clock Divider") + { + config.clock_div = SPIClockDivider::DIV2; + uint32_t expected_CR1 = 0x0344; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.clock_div = SPIClockDivider::DIV4; + expected_CR1 = 0x034C; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.clock_div = SPIClockDivider::DIV8; + expected_CR1 = 0x0354; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.clock_div = SPIClockDivider::DIV16; + expected_CR1 = 0x035C; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.clock_div = SPIClockDivider::DIV32; + expected_CR1 = 0x0364; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.clock_div = SPIClockDivider::DIV64; + expected_CR1 = 0x036C; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.clock_div = SPIClockDivider::DIV128; + expected_CR1 = 0x0374; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.clock_div = SPIClockDivider::DIV256; + expected_CR1 = 0x037C; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + } + + SECTION("Bit order") + { + config.bit_order = SPIBitOrder::MSB_FIRST; + uint32_t expected_CR1 = 0x0344; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + + config.bit_order = SPIBitOrder::LSB_FIRST; + expected_CR1 = 0x03C4; + bus.acquire(config); + REQUIRE(spi.CR1 == expected_CR1); + bus.release(); + } + } + + SECTION("Disable configuration") + { + SPIBusConfig config; + config.clock_div = SPIClockDivider::DIV16; + + config.mode = SPIMode::MODE3; + config.bit_order = SPIBitOrder::LSB_FIRST; + + bus.disableBusConfiguration(); + bus.acquire(config); + REQUIRE(spi.CR1 == 0); + bus.release(); + } +} + +TEST_CASE("SPIBus - Chip select") +{ + FakeSpiTypedef spi; + + SPIBus bus{&spi}; + + REQUIRE(spi.cs.value() == 1); + + bus.select(spi.cs); + REQUIRE(spi.cs.value() == 0); + + bus.deselect(spi.cs); + REQUIRE(spi.cs.value() == 1); +} + +TEST_CASE("SPIBus - One byte operations") +{ + FakeSpiTypedef spi; + + spi.DR.in_buf = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + spi.CR1_expected = 0x03DF; + + SPIBus bus{&spi}; + + SPIBusConfig config; + config.clock_div = SPIClockDivider::DIV16; + + config.mode = SPIMode::MODE3; + config.bit_order = SPIBitOrder::LSB_FIRST; + + bus.acquire(config); + bus.select(spi.cs); + + SECTION("Write") + { + bus.write(1); + REQUIRE(spi.DR.out_buf.back() == 1); + REQUIRE(spi.DR.out_buf.size() == 1); + + bus.write(2); + REQUIRE(spi.DR.out_buf.back() == 2); + REQUIRE(spi.DR.out_buf.size() == 2); + } + SECTION("Read") + { + REQUIRE(bus.read() == spi.DR.in_buf[0]); + REQUIRE(spi.DR.out_buf.size() == 1); + REQUIRE(spi.DR.out_buf.back() == 0); + + REQUIRE(bus.read() == spi.DR.in_buf[1]); + REQUIRE(spi.DR.out_buf.size() == 2); + REQUIRE(spi.DR.out_buf.back() == 0); + } + + SECTION("Transfer") + { + REQUIRE(bus.transfer(55) == spi.DR.in_buf[0]); + REQUIRE(spi.DR.out_buf.back() == 55); + REQUIRE(spi.DR.out_buf.size() == 1); + + REQUIRE(bus.transfer(255) == spi.DR.in_buf[1]); + REQUIRE(spi.DR.out_buf.back() == 255); + REQUIRE(spi.DR.out_buf.size() == 2); + } + + bus.deselect(spi.cs); + bus.release(); +} + +TEST_CASE("SPIBus - Multi byte operations") +{ + FakeSpiTypedef spi; + + spi.DR.in_buf = {100, 101, 102, 103, 104, 105, 106, 107, 108}; + spi.CR1_expected = 0x03DF; + + SPIBus bus{&spi}; + + SPIBusConfig config; + config.clock_div = SPIClockDivider::DIV16; + + config.mode = SPIMode::MODE3; + config.bit_order = SPIBitOrder::LSB_FIRST; + + bus.acquire(config); + bus.select(spi.cs); + + // 2 identical buffers + uint8_t buf[] = {5, 4, 3, 2, 1}; + uint8_t bufc[] = {5, 4, 3, 2, 1}; + + SECTION("Write") + { + bus.write(buf, 0); + REQUIRE(spi.DR.out_buf.size() == 0); + + bus.write(buf, 1); + REQUIRE(spi.DR.out_buf.size() == 1); + REQUIRE(spi.DR.out_buf.back() == bufc[0]); + + bus.write(buf, 4); + REQUIRE(spi.DR.out_buf.size() == 5); + REQUIRE(bufcmp(bufc, spi.DR.out_buf.data() + 1, 4)); + } + + SECTION("Read") + { + bus.read(buf, 0); + // Nothing read + REQUIRE(bufcmp(bufc, buf, 5)); + + bus.read(buf, 1); + REQUIRE(bufcmp(buf, spi.DR.in_buf.data(), 1)); + // No overflows + REQUIRE(bufcmp(bufc + 1, buf + 1, 4)); + + bus.read(buf, 4); + REQUIRE(bufcmp(buf, spi.DR.in_buf.data() + 1, 4)); + // No overflows + REQUIRE(bufcmp(bufc + 4, buf + 4, 1)); + } + + SECTION("Transfer") + { + bus.transfer(buf, 0); + // Nothing read + REQUIRE(bufcmp(bufc, buf, 4)); + // Nothing written + REQUIRE(spi.DR.out_buf.size() == 0); + + bus.transfer(buf, 1); + REQUIRE(spi.DR.out_buf.size() == 1); + REQUIRE(bufcmp(bufc, spi.DR.out_buf.data(), 1)); + REQUIRE(bufcmp(buf, spi.DR.in_buf.data(), 1)); + // No overflows + REQUIRE(bufcmp(bufc + 1, buf + 1, 4)); + + bus.transfer(buf + 1, 3); + REQUIRE(spi.DR.out_buf.size() == 4); + REQUIRE(bufcmp(bufc + 1, spi.DR.out_buf.data() + 1, 3)); + REQUIRE(bufcmp(buf + 1, spi.DR.in_buf.data() + 1, 3)); + // No overflows + REQUIRE(bufcmp(bufc + 4, buf + 4, 1)); + } + + bus.deselect(spi.cs); + bus.release(); +} + +TEST_CASE("SPITransaction - writes") +{ + SPIBusConfig config1{}; + + config1.mode = SPIMode::MODE1; + config1.clock_div = SPIClockDivider::DIV32; + + MockSPIBus bus(config1); + MockGpioPin cs; + + SECTION("Transaction") + { + SPITransaction spi(bus, cs, config1); + + REQUIRE(bus.getOutBuf().size() == 0); + + SECTION("cmd write") + { + spi.write(9); + REQUIRE_FALSE(bus.isSelected()); + REQUIRE(bus.getOutBuf().size() == 1); + REQUIRE(bus.getOutBuf().back() == 9); + } + + SECTION("1 byte reg write") + { + spi.write(10, 77); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 2); + REQUIRE(bus.getOutBuf()[0] == 10); + REQUIRE(bus.getOutBuf()[1] == 77); + } + + SECTION("multi byte reg write") + { + uint8_t buf[] = {1, 2, 3, 4, 5, 6}; + + SECTION("0 size write") + { + spi.write(10, buf, 0); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 1); + REQUIRE(bus.getOutBuf()[0] == 10); + } + + SECTION("2 writes") + { + spi.write(10, buf, 4); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 5); + + REQUIRE(bus.getOutBuf()[0] == 10); + REQUIRE(bufcmp(buf, bus.getOutBuf().data() + 1, 4)); + + spi.write(99, buf, 6); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 12); + + REQUIRE(bus.getOutBuf()[5] == 99); + REQUIRE(bufcmp(buf, bus.getOutBuf().data() + 6, 6)); + } + } + + SECTION("raw write") + { + uint8_t buf[] = {1, 2, 3, 4, 5, 6}; + + spi.write(buf, 0); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 0); + + spi.write(buf, 4); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 4); + + REQUIRE(bufcmp(buf, bus.getOutBuf().data(), 4)); + + spi.write(buf, 6); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 10); + + REQUIRE(bufcmp(buf, bus.getOutBuf().data() + 4, 6)); + } + } +} + +TEST_CASE("SPITransaction - reads") +{ + SPIBusConfig config1; + + config1.mode = SPIMode::MODE1; + config1.clock_div = SPIClockDivider::DIV32; + + MockSPIBus bus(config1); + MockGpioPin cs; + + uint8_t in_data[10] = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109}; + bus.push(in_data, 10); + + SECTION("Transaction") + { + SPISlave slave(bus, cs, config1); + SPITransaction spi(slave); + + REQUIRE(bus.getOutBuf().size() == 0); + + SECTION("1 byte reg read") + { + + REQUIRE(spi.read(0x05) == in_data[0]); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 1); + REQUIRE(bus.getOutBuf().back() == 0x85); + + REQUIRE(spi.read(0x05, true) == in_data[1]); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 2); + REQUIRE(bus.getOutBuf().back() == 0x85); + + REQUIRE(spi.read(0x05, false) == in_data[2]); + REQUIRE_FALSE(bus.isSelected()); + + REQUIRE(bus.getOutBuf().size() == 3); + REQUIRE(bus.getOutBuf().back() == 0x05); + } + + SECTION("multi byte reg read") + { + const int buf_size = 7; + uint8_t buf[] = {1, 2, 3, 4, 5, 6, 7}; + uint8_t cmp[] = {1, 2, 3, 4, 5, 6, 7}; + + spi.read(0x05, buf, 0); + REQUIRE_FALSE(bus.isSelected()); + REQUIRE(bus.getOutBuf().size() == 1); + REQUIRE(bus.getOutBuf().back() == 0x85); + REQUIRE(bufcmp(buf, cmp, buf_size)); + + spi.read(0x05, buf, 3); + REQUIRE_FALSE(bus.isSelected()); + REQUIRE(bus.getOutBuf().size() == 2); + REQUIRE(bus.getOutBuf().back() == 0x85); + REQUIRE(bufcmp(buf, &in_data[0], 3)); + REQUIRE(bufcmp(buf + 3, cmp + 3, buf_size - 3)); + + spi.read(0x05, buf, 3, true); + REQUIRE_FALSE(bus.isSelected()); + REQUIRE(bus.getOutBuf().size() == 3); + REQUIRE(bus.getOutBuf().back() == 0x85); + REQUIRE(bufcmp(buf, &in_data[3], 3)); + REQUIRE(bufcmp(buf + 3, cmp + 3, buf_size - 3)); + + spi.read(0x05, buf, 4, false); + REQUIRE_FALSE(bus.isSelected()); + REQUIRE(bus.getOutBuf().size() == 4); + REQUIRE(bus.getOutBuf().back() == 0x05); + REQUIRE(bufcmp(buf,&in_data[6], 4)); + REQUIRE(bufcmp(buf + 4, cmp + 4, buf_size - 4)); + } + + SECTION("multi byte raw read") + { + const int buf_size = 7; + uint8_t buf[] = {1, 2, 3, 4, 5, 6, 7}; + uint8_t cmp[] = {1, 2, 3, 4, 5, 6, 7}; + + spi.read(buf, 0); + REQUIRE_FALSE(bus.isSelected()); + REQUIRE(bus.getOutBuf().size() == 0); + REQUIRE(bufcmp(buf, cmp, buf_size)); + + spi.read(buf, 3); + REQUIRE_FALSE(bus.isSelected()); + REQUIRE(bus.getOutBuf().size() == 0); + REQUIRE(bufcmp(buf, &in_data[0], 3)); + REQUIRE(bufcmp(buf + 3, cmp + 3, buf_size - 3)); + } + } +} + +TEST_CASE("SPITransaction - transfer") +{ + SPIBusConfig config1; + + config1.mode = SPIMode::MODE1; + config1.clock_div = SPIClockDivider::DIV32; + + MockSPIBus bus(config1); + MockGpioPin cs; + + uint8_t data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + bus.push(data, 10); + + SECTION("Transaction") + { + SPISlave slave(bus, cs, config1); + SPITransaction spi(slave); + + const int buf_size = 7; + uint8_t buf[] = {1, 2, 3, 4, 5, 6, 7}; + uint8_t cmp[] = {1, 2, 3, 4, 5, 6, 7}; + + spi.transfer(buf, 0); + REQUIRE_FALSE(bus.isSelected()); + REQUIRE(bus.getOutBuf().size() == 0); + REQUIRE(bufcmp(buf, cmp, buf_size)); + + spi.transfer(buf, 4); + REQUIRE_FALSE(bus.isSelected()); + REQUIRE(bus.getOutBuf().size() == 4); + REQUIRE(bufcmp(buf, &data[0], 4)); + REQUIRE(bufcmp(cmp, bus.getOutBuf().data(), 4)); + REQUIRE(bufcmp(buf + 4, cmp + 4, buf_size - 4)); + } +} \ No newline at end of file diff --git a/src/tests/catch/test-aero.cpp b/src/tests/catch/test-aero.cpp index 440bd0c444ad45c0a4b01c475afc80ce97d0ca4e..b9014c3a4bcf46a17c2e06bccacbc14e3e2ca494 100644 --- a/src/tests/catch/test-aero.cpp +++ b/src/tests/catch/test-aero.cpp @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 @@ -80,6 +79,22 @@ TEST_CASE("[AeroUtils] relAltitude") Approx(-100).epsilon(0.0001)); } +TEST_CASE("[AeroUtils] relDensity") +{ + using namespace aeroutils; + + REQUIRE(relDensity(101325, 101325, 0, 288.15) == + Approx(1.225).epsilon(0.0001)); + REQUIRE(relDensity(100129.438691069, 101325, 0, 288.15) == + Approx(1.21328277727309).epsilon(0.0001)); + REQUIRE(relDensity(89874.5715517214, 101325, 0, 288.15) == + Approx(1.11164259066989).epsilon(0.0001)); + REQUIRE(relDensity(70108.5471843675, 101325, 0, 288.15) == + Approx(0.909122116038058).epsilon(0.0001)); + REQUIRE_FALSE(relDensity(101325, 101325, 0, 288.15) == + Approx(1.226).epsilon(0.0001)); +} + TEST_CASE("[AeroUtils] verticalSpeed") { using namespace aeroutils; diff --git a/src/tests/catch/test-buttonhandler.cpp b/src/tests/catch/test-buttonhandler.cpp index f9df51d0f4fa7201e6ed273eb62fd837ac2fbf22..6be1aa02c5186a31a533946667ee92995c72eee6 100644 --- a/src/tests/catch/test-buttonhandler.cpp +++ b/src/tests/catch/test-buttonhandler.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/catch/test-circularbuffer.cpp b/src/tests/catch/test-circularbuffer.cpp index c4c1b63d732d63e74a1d9b744331ec9ba9025365..403d74cb5b54fe4e04d218c175f4f726223251f6 100644 --- a/src/tests/catch/test-circularbuffer.cpp +++ b/src/tests/catch/test-circularbuffer.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/catch/test-eventbroker.cpp b/src/tests/catch/test-eventbroker.cpp index 8ae055781de48bc6a1250d1da559e3d800cc676c..1e9ff378d9ef20d48ebf911fc6b25696b10dc669 100644 --- a/src/tests/catch/test-eventbroker.cpp +++ b/src/tests/catch/test-eventbroker.cpp @@ -1,12 +1,11 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * - * Permission is herbrokery 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 + * 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 @@ -14,13 +13,14 @@ * * 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 + * 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. */ + #ifdef STANDALONE_CATCH1_TEST #include "catch-tests-entry.cpp" #endif diff --git a/src/tests/catch/test-hardwaretimer.cpp b/src/tests/catch/test-hardwaretimer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cc817a8784f277f8e9cc0e1718a606a718e09686 --- /dev/null +++ b/src/tests/catch/test-hardwaretimer.cpp @@ -0,0 +1,126 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#ifdef STANDALONE_CATCH1_TEST +#include "catch-tests-entry.cpp" +#endif + +#include <drivers/HardwareTimer.h> +#include <miosix.h> + +#include <utils/testutils/catch.hpp> + +using namespace miosix; + +class TimerTestFixture +{ +public: + TimerTestFixture() + : timer32(TIM5, TimerUtils::getPrescalerInputFrequency( + TimerUtils::InputClock::APB1)), + timer16(TIM10, TimerUtils::getPrescalerInputFrequency( + TimerUtils::InputClock::APB2)) + { + RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; + RCC->APB2ENR |= RCC_APB2ENR_TIM10EN; + } + + ~TimerTestFixture() + { + timer16.stop(); + timer32.stop(); + + RCC->APB1ENR &= ~RCC_APB1ENR_TIM5EN; + RCC->APB2ENR &= ~RCC_APB2ENR_TIM10EN; + } + + HardwareTimer<uint32_t> timer32; + HardwareTimer<uint16_t> timer16; +}; + +TEST_CASE_METHOD(TimerTestFixture, "Test basic functionality") +{ + timer32.setPrescaler(63); + timer16.setPrescaler(127); + + REQUIRE(timer32.getMaxDuration() == Approx(3272.356).margin(0.001)); + REQUIRE(timer16.getMaxDuration() == Approx(0.049).margin(0.001)); + + REQUIRE(timer32.getResolution() == Approx(0.761).margin(0.001)); + REQUIRE(timer16.getResolution() == Approx(0.761).margin(0.001)); + + REQUIRE(timer32.tick() == 0); + REQUIRE(timer16.tick() == 0); + + REQUIRE(timer32.start() == 0); + REQUIRE(timer16.start() == 0); + + Thread::sleep(10); + + uint32_t tick32 = timer32.tick(); + uint32_t tick16 = timer16.tick(); + + REQUIRE(timer32.toMilliSeconds(tick32) == Approx(10).margin(1)); + REQUIRE(timer16.toMilliSeconds(tick16) == Approx(10).margin(1)); + + Thread::sleep(30); + + REQUIRE(timer32.toMilliSeconds(timer32.tick() - tick32) == + Approx(30).margin(1)); + REQUIRE(timer16.toMilliSeconds(timer16.tick() - tick16) == + Approx(30).margin(1)); + + tick32 = timer32.stop(); + tick16 = timer16.stop(); + + Thread::sleep(20); + + REQUIRE(timer32.tick() == tick32); + REQUIRE(timer16.tick() == tick16); +} + +TEST_CASE_METHOD(TimerTestFixture, "Test long term precision") +{ + timer32.setPrescaler(63); + timer16.setPrescaler(65535); // Max prescaler + + REQUIRE(timer32.getMaxDuration() == Approx(3272.356).margin(0.001)); + REQUIRE(timer16.getMaxDuration() == Approx(25.565).margin(0.001)); + + REQUIRE(timer32.getResolution() == Approx(0.761).margin(0.001)); + REQUIRE(timer16.getResolution() == Approx(390.095).margin(0.001)); + + REQUIRE(timer32.start() == 0); + REQUIRE(timer16.start() == 0); + + Thread::sleep(24000); + + uint32_t tick32 = timer32.tick(); + uint32_t tick16 = timer16.tick(); + + REQUIRE(timer32.toMilliSeconds(timer32.tick()) == Approx(24000).margin(1)); + REQUIRE(timer16.toMilliSeconds(timer16.tick()) == Approx(24000).margin(1)); + + Thread::sleep(36000); + + REQUIRE(timer32.toMilliSeconds(timer32.tick()) == Approx(60000).margin(1)); +} \ No newline at end of file diff --git a/src/tests/catch/test-kalman-eigen.cpp b/src/tests/catch/test-kalman-eigen.cpp new file mode 100644 index 0000000000000000000000000000000000000000..62074f3e32f2ea7357abaf4e779a77af41acfe19 --- /dev/null +++ b/src/tests/catch/test-kalman-eigen.cpp @@ -0,0 +1,175 @@ +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Luca Mozzarelli + * + * 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. + */ + +#ifdef STANDALONE_CATCH1_TEST +#include "catch-tests-entry.cpp" +#endif + +#define EIGEN_RUNTIME_NO_MALLOC + +#include <src/tests/kalman/test-kalman-data.h> + +#include <iostream> +#include <utils/testutils/catch.hpp> + +#include "kalman/KalmanEigen.h" + +using namespace Eigen; + +static const uint8_t STATES_DIM = 3; +static const uint8_t OUTPUTS_DIM = 1; + +static const Matrix<float, STATES_DIM, STATES_DIM> F = + (Matrix<float, STATES_DIM, STATES_DIM>(STATES_DIM, STATES_DIM) << 1, 0.2, + 0.02, 0, 1, 0.2, 0, 0, 1) + .finished(); +// Output matrix +static const Matrix<float, OUTPUTS_DIM, STATES_DIM> H{1, 0, 0}; + +// Initial error covariance matrix +static const Matrix<float, STATES_DIM, STATES_DIM> P = + (Matrix<float, STATES_DIM, STATES_DIM>(STATES_DIM, STATES_DIM) << 0.1, 0, 0, + 0, 0.1, 0, 0, 0, 0.1) + .finished(); +// Model variance matrix +static const Matrix<float, STATES_DIM, STATES_DIM> Q = + (Matrix<float, STATES_DIM, STATES_DIM>(STATES_DIM, STATES_DIM) << 0.01, 0, + 0, 0, 0.01, 0, 0, 0, 0.01) + .finished(); +// Measurement variance +static const Matrix<float, OUTPUTS_DIM, OUTPUTS_DIM> R{10}; +// State vector +static const Matrix<float, STATES_DIM, 1> x0(INPUT[0], 0.0, 0.0); + +static const KalmanEigen<float, STATES_DIM, OUTPUTS_DIM>::KalmanConfig +getKalmanConfig() +{ + KalmanEigen<float, STATES_DIM, OUTPUTS_DIM>::KalmanConfig config; + config.F = F; + config.H = H; + config.Q = Q; + config.R = R; + config.P = P; + config.x = x0; + + return config; +} + +TEST_CASE("Update test") +{ + KalmanEigen<float, STATES_DIM, OUTPUTS_DIM> filter(getKalmanConfig()); + + Matrix<float, OUTPUTS_DIM, 1> y{}; + float T; + float last_time = TIME[0]; + + for (unsigned i = 1; i < 101; i++) + { + // printf("i = %d \n", i); + + y(0, 0) = INPUT[i]; + T = TIME[i] - last_time; + + Matrix<float, STATES_DIM, STATES_DIM> F_new; + F_new << F; + F_new(0, 1) = T; + F_new(0, 2) = 0.5 * T * T; + F_new(1, 2) = T; + + filter.predict(F_new); + + if (!filter.correct(y)) + { + FAIL("Correction failed at iteration : %d \n", i); + } + + if (filter.getState()(0, 0) != Approx(STATE_1[i]).epsilon(0.01)) + { + FAIL("FAILED X(0,0) " << filter.getState()(0, 0) + << " != " << STATE_1[i]); + } + else + { + SUCCEED(); + } + if (filter.getState()(1, 0) != Approx(STATE_2[i]).epsilon(0.01)) + { + FAIL("FAILED X(1,0) " << filter.getState()(1, 0) + << " != " << STATE_2[i]); + } + else + { + SUCCEED(); + } + if (filter.getState()(2, 0) != Approx(STATE_3[i]).epsilon(0.01)) + { + FAIL("FAILED X(2,0) " << filter.getState()(2, 0) + << " != " << STATE_3[i]); + } + else + { + SUCCEED(); + } + + auto predictedState = filter.predictState(5); + if (predictedState(0, 0) != Approx(PRED_STATE_1[i]).epsilon(0.01)) + { + FAIL("FAILED PREDICTED X(0,0) " << predictedState(0, 0) + << " != " << PRED_STATE_1[i]); + } + else + { + SUCCEED(); + } + if (predictedState(1, 0) != Approx(PRED_STATE_2[i]).epsilon(0.01)) + { + FAIL("FAILED PREDICTED X(1,0) " << predictedState(1, 0) + << " != " << PRED_STATE_2[i]); + } + else + { + SUCCEED(); + } + if (predictedState(2, 0) != Approx(PRED_STATE_3[i]).epsilon(0.01)) + { + FAIL("FAILED PREDICTED X(2,0) " << predictedState(2, 0) + << " != " << PRED_STATE_3[i]); + } + else + { + SUCCEED(); + } + + auto predictedOutput = filter.predictOutput(5); + if (predictedOutput(0, 0) != Approx(PRED_STATE_1[i]).epsilon(0.01)) + { + FAIL("FAILED PREDICTED Y " << predictedState(0, 0) + << " != " << PRED_STATE_1[i]); + } + else + { + SUCCEED(); + } + + last_time = TIME[i]; + } +} \ No newline at end of file diff --git a/src/tests/catch/test-kalman.cpp b/src/tests/catch/test-kalman.cpp index e5819da1ebcf7a5d2253099fa12e685cef202746..340fc96a7d34b1fd1f3858146a10aa4285e91326 100644 --- a/src/tests/catch/test-kalman.cpp +++ b/src/tests/catch/test-kalman.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Mozzarelli + * Author: Luca Mozzarelli * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -26,10 +26,10 @@ #include <utils/testutils/catch.hpp> #include <iostream> -#include <src/tests/kalman/test-kalman-data.h> -#include "kalman/Kalman.h" +#include <src/tests/kalman/test-kalman-data.h> +#include "kalman/Kalman.h" static MatrixBase<float, 3, 3> A{1, 0.2, 0.02, 0, 1, 0.2, 0, 0, 1}; diff --git a/src/tests/catch/test-matrix.cpp b/src/tests/catch/test-matrix.cpp index 32ee05424d6897230642bc8afe077bdc30bb82a7..82d5c7f480a111ceb936297b9d6ede9f2fa74502 100644 --- a/src/tests/catch/test-matrix.cpp +++ b/src/tests/catch/test-matrix.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Mozzarelli + * Author: Luca Mozzarelli * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -34,6 +34,8 @@ TEST_CASE("Multiply test") MatrixBase<float, 3, 1> B{1.0f, 2.0f, 3.7f}; MatrixBase<float, 3, 1> C{}; + C = A * B; + REQUIRE( C(0,0) == Approx( 8.1f) ); REQUIRE( C(1,0) == Approx(-7.7f) ); REQUIRE( C(2,0) == Approx(-10.3f) ); @@ -46,6 +48,8 @@ TEST_CASE("Sum test") MatrixBase<float, 3, 3> C{}; + C = A + B; + REQUIRE( C(0,0) == Approx(2.0f )); REQUIRE( C(0,1) == Approx(0.0f )); REQUIRE( C(0,2) == Approx(6.0f )); @@ -64,6 +68,8 @@ TEST_CASE("Subtract test") MatrixBase<float, 3, 3> C{}; + C = A - B; + REQUIRE( C(0,0) == Approx(0.0f )); REQUIRE( C(0,1) == Approx(-4.0f )); REQUIRE( C(0,2) == Approx(0.0f )); diff --git a/src/tests/catch/test-packetqueue.cpp b/src/tests/catch/test-packetqueue.cpp index 58e6bc12145070d0c6cfc177276686592d99de87..01be7038568b90b88d0278f094d81d6488e7fe57 100644 --- a/src/tests/catch/test-packetqueue.cpp +++ b/src/tests/catch/test-packetqueue.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/catch/test-sensormanager-catch.cpp b/src/tests/catch/test-sensormanager-catch.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ec4cec8f378ef38b79c5d8f550953222d50895fc --- /dev/null +++ b/src/tests/catch/test-sensormanager-catch.cpp @@ -0,0 +1,306 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#ifdef STANDALONE_CATCH1_TEST +#include "catch-tests-entry.cpp" +#endif + +#include <iostream> + +#include "utils/testutils/TestSensor.h" +#include "utils/testutils/catch.hpp" + +#define private public +#define protected public + +#include "sensors/SensorInfo.h" +#include "sensors/SensorManager.h" + +static const uint8_t FIRST_TASK_ID = 7; // used to test IDs assignment to tasks + +class FailingSensor : public Sensor<TestData> +{ + bool init() { return true; } + + bool selfTest() { return false; } // always fail self-test + + TestData sampleImpl() { return TestData{}; } +}; + +class SensorManagerFixture +{ +public: + SensorManagerFixture() + { + scheduler = new TaskScheduler(); + scheduler->add([]() { std::cout << "Task Callback!" << endl; }, + 2000, // inserst a test function in the scheduler + FIRST_TASK_ID); + + sensor_manager = new SensorManager(scheduler, {{&s1, s1_info}, + {&s2, s2_info}, + {&s3, s3_info}, + {&s4, s4_info}, + {&s5, s5_info}}); + + sampler1 = sensor_manager->samplers_map[&s1]; + sampler2 = sensor_manager->samplers_map[&s2]; + sampler3 = sensor_manager->samplers_map[&s3]; + sampler4 = sensor_manager->samplers_map[&s4]; + sampler5 = sensor_manager->samplers_map[&s5]; + } + + ~SensorManagerFixture() + { + sensor_manager->stop(); + + delete sensor_manager; + } + +private: + TaskScheduler* scheduler; + + SensorManager* sensor_manager; + + SensorSampler* sampler1; + SensorSampler* sampler2; + SensorSampler* sampler3; + SensorSampler* sampler4; + SensorSampler* sampler5; + + TestSensor s1; + SensorInfo s1_info{ + /*ID=*/"s1", + /*Period=*/1000, + /*Callback=*/[]() { std::cout << "Callback 1!" << endl; }, + /*DMA=*/false, + /*Enabled=*/true}; + + TestSensor s2; + SensorInfo s2_info{ + /*ID=*/"s2", + /*Period=*/1000, + /*Callback=*/[]() { std::cout << "Callback 2!" << endl; }, + /*DMA=*/false, + /*Enabled=*/false}; + + TestSensor s3; + SensorInfo s3_info{ + /*ID=*/"s3", + /*Period=*/500, + /*Callback=*/[]() { std::cout << "Callback 3!" << endl; }, + /*DMA=*/false, + /*Enabled=*/true}; + + // same period as s1 and s2 but uses DMA + TestSensor s4; + SensorInfo s4_info{ + /*ID=*/"s4", + /*Period=*/1000, + /*Callback=*/[]() { std::cout << "Callback 4!" << endl; }, + /*DMA=*/true, + /*Enabled=*/true}; + + // always failing self-test + FailingSensor s5; + SensorInfo s5_info{ + /*ID=*/"s5", + /*Period=*/2000, + /*Callback=*/[]() { std::cout << "Callback 5!" << endl; }, + /*DMA=*/false, + /*Enabled=*/true}; +}; + +bool operator==(const SensorInfo& lhs, const SensorInfo& rhs) +{ + return lhs.id == rhs.id && lhs.period == rhs.period && + lhs.callback.target_type() == rhs.callback.target_type() && + lhs.callback.target<void()>() == rhs.callback.target<void()>() && + lhs.is_dma == rhs.is_dma && lhs.is_enabled == rhs.is_enabled; +} + +bool operator==(const SensorSampler& lhs, const SensorSampler& rhs) +{ + return lhs.id == rhs.id && lhs.period == rhs.period && + lhs.is_dma == rhs.is_dma && lhs.sensors.size() == rhs.sensors.size(); +} + +TEST_CASE_METHOD(SensorManagerFixture, + "Samplers IDs should incrementally start from FIRST_TASK_ID") +{ + sensor_manager->start(); + + std::vector<TaskStatResult> tasks_stats = scheduler->getTaskStats(); + + std::cout << "Tasks number : " << tasks_stats.size() << endl; + + // Sampler with lower period are inserted in the TaskScheduler + // before higher period ones + // => + // Task id 8 : sampler at 1000 ms (1 Hz), not DMA + // Task id 9 : sampler at 500 ms (2 Hz), not DMA ---> first to be added + // to the scheduler + // Task id 10 : sampler at 1000 ms (1 Hz), with DMA + REQUIRE(tasks_stats[0].id == FIRST_TASK_ID); + REQUIRE(tasks_stats[1].id == static_cast<uint8_t>(FIRST_TASK_ID + 2)); + REQUIRE(tasks_stats[2].id == static_cast<uint8_t>(FIRST_TASK_ID + 1)); + REQUIRE(tasks_stats[3].id == static_cast<uint8_t>(FIRST_TASK_ID + 3)); + REQUIRE(tasks_stats[4].id == static_cast<uint8_t>(FIRST_TASK_ID + 4)); +} + +TEST_CASE_METHOD(SensorManagerFixture, + "Sensors are correctly added to the samplers") +{ + // check that 3 samplers exist (1 hz, 2 hz, 1 hz with DMA and 0.5 Hz) + REQUIRE(sensor_manager->samplers.size() == 4); + + // samplers are sorted by period, in decreasing order! + + // check that s1, s2 and s3 are assigned to correct samplers + REQUIRE(sampler1 == sensor_manager->samplers[1]); + REQUIRE(*sampler1 == *(sensor_manager->samplers[1])); + // s1 and s2 are assigned to same sampler + REQUIRE(sampler1 == sampler2); + REQUIRE(*sampler1 == *sampler2); + // s3 assigned to another sampler + REQUIRE(sampler3 == sensor_manager->samplers[0]); + REQUIRE(*sampler3 == *(sensor_manager->samplers[0])); + // s4 assigned to another sampler + REQUIRE(sampler4 == sensor_manager->samplers[2]); + REQUIRE(*sampler4 == *(sensor_manager->samplers[2])); + // s5 assigned to the last sampler + REQUIRE(sampler5 == sensor_manager->samplers[3]); + REQUIRE(*sampler5 == *(sensor_manager->samplers[3])); +} + +TEST_CASE_METHOD(SensorManagerFixture, + "Sensors are correctly coupled with their info and failing sensors are automatically disabled") +{ + SensorInfo info1 = sampler1->getSensorInfo(&s1); + SensorInfo info2 = sampler2->getSensorInfo(&s2); + SensorInfo info3 = sampler3->getSensorInfo(&s3); + SensorInfo info4 = sampler4->getSensorInfo(&s4); + SensorInfo info5 = sampler5->getSensorInfo(&s5); + + // correctly initialized sensors + REQUIRE(s1_info == info1); + REQUIRE(s2_info == info2); + REQUIRE(s3_info == info3); + REQUIRE(s4_info == info4); + + REQUIRE(!(s5_info == info5)); // it fails, so is_enabled is set to false instead of true + REQUIRE(s5_info.id == info5.id); + REQUIRE(s5_info.period == info5.period); + REQUIRE(s5_info.is_dma == info5.is_dma); + REQUIRE(info5.is_enabled == false); // disabled even if it was created as enabled + REQUIRE(info5.is_initialized == false); // always fails the initialization +} + +TEST_CASE_METHOD(SensorManagerFixture, + "Samplers have the correct number of sensors") +{ + // check that sampler at 1000 ms (1 Hz) has 2 sensors + // sampler at 500 ms (2 Hz) has 1 sensor + // sampler at 1000 ms (1 Hz) with DMA has 1 sensor + for (auto s : sensor_manager->samplers) + { + if (s->getSamplingPeriod() == 1000 && s->isDMA() == false) + { + REQUIRE(s->getNumSensors() == 2); + } + else if (s->getSamplingPeriod() == 500) + { + REQUIRE(s->getNumSensors() == 1); + } + else if (s->getSamplingPeriod() == 1000 && s->isDMA() == true) + { + REQUIRE(s->getNumSensors() == 1); + } + else if (s->getSamplingPeriod() == 2000 && s->isDMA() == false) + { + REQUIRE(s->getNumSensors() == 1); + } + else + { + FAIL( + "Can't exist a sampler with period different from 500, 1000 or " + "2000 ms"); // no sampler with a different period exist + } + } +} + +TEST_CASE_METHOD(SensorManagerFixture, "Enable/disable sensors at runtime") +{ + sensor_manager->start(); + + sensor_manager->enableSensor(&s2); + sensor_manager->disableSensor(&s4); + + REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == true); + REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == false); + + sensor_manager->disableSensor(&s2); + sensor_manager->enableSensor(&s4); + + REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == false); + REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == true); +} + +TEST_CASE_METHOD(SensorManagerFixture, "Enable/disable all sensors at runtime") +{ + sensor_manager->start(); + + sensor_manager->enableAllSensors(); + + REQUIRE(sensor_manager->getSensorInfo(&s1).is_enabled == true); + REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == true); + REQUIRE(sensor_manager->getSensorInfo(&s3).is_enabled == true); + REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == true); + REQUIRE(sensor_manager->getSensorInfo(&s5).is_enabled == true); + + sensor_manager->disableAllSensors(); + + REQUIRE(sensor_manager->getSensorInfo(&s1).is_enabled == false); + REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == false); + REQUIRE(sensor_manager->getSensorInfo(&s3).is_enabled == false); + REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == false); + REQUIRE(sensor_manager->getSensorInfo(&s5).is_enabled == false); + + sensor_manager->enableAllSensors(); + + REQUIRE(sensor_manager->getSensorInfo(&s1).is_enabled == true); + REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == true); + REQUIRE(sensor_manager->getSensorInfo(&s3).is_enabled == true); + REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == true); + REQUIRE(sensor_manager->getSensorInfo(&s5).is_enabled == true); +} + +TEST_CASE_METHOD(SensorManagerFixture, + "Try to get info about a non-existing sensor") +{ + TestSensor invalid_sensor; + SensorInfo invalid_info = sensor_manager->getSensorInfo(&invalid_sensor); + REQUIRE(invalid_info == SensorInfo{}); + + SensorInfo valid_info = sensor_manager->getSensorInfo(&s2); + REQUIRE(valid_info == s2_info); +} \ No newline at end of file diff --git a/src/tests/catch/test-xbee.cpp b/src/tests/catch/test-xbee.cpp index 887e0523be452250cbcbed34ac755c0f88bcf1f8..64f7f0c00c8ec37669fb42e25efb90759166d7a6 100644 --- a/src/tests/catch/test-xbee.cpp +++ b/src/tests/catch/test-xbee.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/catch/xbee/MockXbeeSPIBus.h b/src/tests/catch/xbee/MockXbeeSPIBus.h new file mode 100644 index 0000000000000000000000000000000000000000..47de217724c2b686b75c526e02465d188667303f --- /dev/null +++ b/src/tests/catch/xbee/MockXbeeSPIBus.h @@ -0,0 +1,171 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <deque> +#include <functional> +#include <memory> + +#include "drivers/Xbee/APIFrameParser.h" +#include "drivers/Xbee/APIFrames.h" +#include "drivers/spi/test/MockSPIBus.h" +#include "utils/testutils/MockGpioPin.h" + +using std::deque; +using std::function; +using std::unique_ptr; + +class MockXbeeSPIBus : public MockSPIBus +{ +public: + MockXbeeSPIBus(SPIBusConfig expected_config, MockGpioPin& attn) + : MockSPIBus(expected_config), attn(attn) + { + } + + void registerCallback(function<void(MockGpioPin&)> callback) + { + this->callback = callback; + } + + void removeCallback() { this->callback = nullptr; } + + void pushApiFrame(Xbee::APIFrame& api) + { + unique_ptr<uint8_t> bytes(new uint8_t[Xbee::MAX_API_FRAME_SIZE]); + + size_t len = api.toBytes(bytes.get()); + push(bytes.get(), len); + } + + deque<Xbee::APIFrame> getParsedFrames() + { + Lock<FastMutex> l(mutex); + return parsed_frames; + } + + /** + * @brief Wether to generate a tx_status responde upon receiving a tx + * request + * + */ + void setRespondWithTxStatus(bool respond, uint8_t delivery_status = 0) + { + tx_status_delivery_status = delivery_status; + respond_with_tx_status = respond; + } + +protected: + void _push(uint8_t* data, size_t len) override + { + MockSPIBus::_push(data, len); + + assertATTN(); + } + + uint8_t _read() override + { + uint8_t r = MockSPIBus::_read(); + + if (in_buf_pos_cntr == in_buf.size()) + { + resetATTN(); + } + + return r; + } + + void _write(uint8_t byte) override + { + MockSPIBus::_write(byte); + + Xbee::APIFrameParser::ParseResult res = + parser.parse(byte, &parsing_frame); + + if (res == Xbee::APIFrameParser::ParseResult::SUCCESS) + { + parsed_frames.push_back(parsing_frame); + + if (parsing_frame.frame_type == Xbee::FTYPE_TX_REQUEST && + respond_with_tx_status) + { + Xbee::TXRequestFrame* tx_req = + parsing_frame.toFrameType<Xbee::TXRequestFrame>(); + Xbee::TXStatusFrame tx_stat; + + tx_stat.setFrameID(tx_req->getFrameID()); + tx_stat.setDeliveryStatus(tx_status_delivery_status); + tx_stat.setDiscoveryStatus(0); + tx_stat.setTransmitRetryCount(0); + tx_stat.calcChecksum(); + + _pushApiFrame(tx_stat); + } + } + else if (res == Xbee::APIFrameParser::ParseResult::FAIL) + { + printf("[MockXbeeSPI] Failed parsing frame written to SPI bus\n"); + } + } + +private: + void _pushApiFrame(Xbee::APIFrame& api) + { + uint8_t* bytes = new uint8_t[Xbee::MAX_API_FRAME_SIZE]; + size_t len = api.toBytes(bytes); + _push(bytes, len); + + delete[] bytes; + } + + void assertATTN() + { + if (attn.value() != 0) + { + attn.low(); + if (callback) + callback(attn); + } + } + + void resetATTN() + { + if (attn.value() == 0) + { + attn.high(); + if (callback) + callback(attn); + } + } + + deque<Xbee::APIFrame> parsed_frames; + + Xbee::APIFrameParser parser; + Xbee::APIFrame parsing_frame; + + function<void(MockGpioPin&)> callback; + MockGpioPin attn; + + bool respond_with_tx_status = true; + uint8_t tx_status_delivery_status = 0; +}; \ No newline at end of file diff --git a/src/tests/catch/xbee/test-xbee-driver.cpp b/src/tests/catch/xbee/test-xbee-driver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5a1ddb7c78d2392220e0ab596e3352a08ef77b86 --- /dev/null +++ b/src/tests/catch/xbee/test-xbee-driver.cpp @@ -0,0 +1,414 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#ifdef STANDALONE_CATCH1_TEST +#include "../catch-tests-entry.cpp" +#endif +#include <ctime> +#include <future> +#include <memory> + +#include "MockXbeeSPIBus.h" +#include "drivers/Xbee/Xbee.h" +#include "drivers/spi/SPIDriver.h" +#include "utils/testutils/catch.hpp" + +using namespace Xbee; +using std::async; +using std::unique_ptr; + +using uint8_ptr = unique_ptr<uint8_t>; + +/** + * @brief Generates a sequence of incremental bytes + */ +uint8_ptr incrementalBytes(size_t length, uint8_t start_from = 0) +{ + uint8_ptr bytes(new uint8_t[length]); + + for (size_t i = 0; i < length; i++) + { + bytes.get()[i] = (start_from + i) % 256; + } + + return bytes; +} + +class XbeeWrapper +{ +public: + XbeeWrapper(unsigned int tx_timeout) + : bus(new MockXbeeSPIBus(xbee_cfg, attn)), + xbee(new Xbee::Xbee(*bus.get(), xbee_cfg, cs, attn, rst, tx_timeout)) + { + attn.high(); + } + + MockGpioPin cs; + MockGpioPin attn; + + MockGpioPin rst; + + SPIBusConfig xbee_cfg{}; + + unique_ptr<MockXbeeSPIBus> bus; + unique_ptr<Xbee::Xbee> xbee; +}; + +TEST_CASE("[Xbee] Test xbee.send(...) by itself") +{ + uint8_ptr pkt = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH + 1); + uint8_ptr pkt_orig = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH + 1); + + XbeeWrapper wrap(DEFAULT_TX_TIMEOUT); + + SECTION("Middle payload length") + { + REQUIRE(wrap.xbee->send(pkt.get(), 128)); + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + + REQUIRE(wrap.bus->getParsedFrames().front().frame_type == + Xbee::FTYPE_TX_REQUEST); + + TXRequestFrame tx_req = *wrap.bus->getParsedFrames() + .front() + .toFrameType<Xbee::TXRequestFrame>(); + + REQUIRE(tx_req.getRFDataLength() == 128); + REQUIRE(memcmp(tx_req.getRFDataPointer(), pkt_orig.get(), 128) == 0); + } + + SECTION("Max payload length") + { + REQUIRE(wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH)); + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + + REQUIRE(wrap.bus->getParsedFrames().front().frame_type == + Xbee::FTYPE_TX_REQUEST); + + TXRequestFrame tx_req = *wrap.bus->getParsedFrames() + .front() + .toFrameType<Xbee::TXRequestFrame>(); + + REQUIRE(tx_req.getRFDataLength() == Xbee::MAX_PACKET_PAYLOAD_LENGTH); + REQUIRE(memcmp(tx_req.getRFDataPointer(), pkt_orig.get(), + Xbee::MAX_PACKET_PAYLOAD_LENGTH) == 0); + } + + SECTION("Oversize payload") + { + REQUIRE_FALSE( + wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH + 1)); + REQUIRE(wrap.bus->getParsedFrames().size() == 0); + } + + SECTION("No payload") + { + REQUIRE_FALSE(wrap.xbee->send(pkt.get(), 0)); + REQUIRE(wrap.bus->getParsedFrames().size() == 0); + } + + SECTION("Send error") + { + wrap.bus->setRespondWithTxStatus(true, DELS_NO_SPECTRUM_AVAILABLE); + + REQUIRE_FALSE( + wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH)); + } + + SECTION("TX status timeout") + { + wrap.bus->setRespondWithTxStatus(false); + + long long start = miosix::getTick(); + + REQUIRE_FALSE( + wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH)); + + // Should not have returned until the timeout has expired + REQUIRE(miosix::getTick() >= start + DEFAULT_TX_TIMEOUT); + } +} + +TEST_CASE("[Xbee] Test xbee.receive(...) by itself") +{ + uint8_ptr rx_buf(new uint8_t[MAX_PACKET_PAYLOAD_LENGTH]); + + uint8_ptr pkt = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH); + + XbeeWrapper wrap(DEFAULT_TX_TIMEOUT); + + RXPacketFrame rx; + rx.setRXDataLength(MAX_PACKET_PAYLOAD_LENGTH); + memcpy(rx.getRXDataPointer(), pkt.get(), MAX_PACKET_PAYLOAD_LENGTH); + rx.setReceiveOptions(RO_POINT_MULTIPOINT); + rx.setSourceAddress(0x1122334455667788); + + SECTION("RX buffer bigger than RX packet payload") + { + rx.setRXDataLength(50); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + + REQUIRE(wrap.xbee->receive(rx_buf.get(), MAX_PACKET_PAYLOAD_LENGTH) == + 50); + + REQUIRE(memcmp(rx_buf.get(), pkt.get(), 50) == 0); + } + + SECTION("RX buffer smaller than RX packet payload") + { + rx.setRXDataLength(130); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + + REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == 50); + REQUIRE(wrap.xbee->receive(rx_buf.get() + 50, 50) == 50); + REQUIRE(wrap.xbee->receive(rx_buf.get() + 100, 50) == 30); + + REQUIRE(memcmp(rx_buf.get(), pkt.get(), 130) == 0); + } + + SECTION("RX buffer == 1") + { + rx.setRXDataLength(30); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + + for (int i = 0; i < 30; i++) + { + REQUIRE(wrap.xbee->receive(rx_buf.get() + i, 1) == 1); + } + + REQUIRE(memcmp(rx_buf.get(), pkt.get(), 30) == 0); + } + + SECTION("RX packet with wrong checksum") + { + rx.setRXDataLength(20); + rx.calcChecksum(); + wrap.bus->pushApiFrame(rx); + + rx.setRXDataLength(21); + rx.calcChecksum(); + rx.setRXDataLength(20); // Now the checksum is wrong + wrap.bus->pushApiFrame(rx); + + // Only receive one of the two frames + REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == 20); + + auto dont_care = async(std::launch::async, [&]() { + miosix::Thread::sleep(1000); + wrap.xbee->wakeReceiver(true); + }); + + // This should block until we force it to return + REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == -1); + } + + SECTION("receive() blocks until an interrupt is received") + { + rx.setRXDataLength(20); + rx.calcChecksum(); + + // Send an rx frame in one second + auto dont_care = async(std::launch::async, [&]() { + miosix::Thread::sleep(1000); + wrap.bus->pushApiFrame(rx); + wrap.xbee->wakeReceiver(); + }); + + long long start = miosix::getTick(); + REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == 20); + + // Should not have returned before we sent the rx frame + REQUIRE(miosix::getTick() >= start + 1000); + } +} + +TEST_CASE("[Xbee] Receive while sending") +{ + uint8_ptr rx_buf(new uint8_t[MAX_PACKET_PAYLOAD_LENGTH]); + + uint8_ptr pkt_rx = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH); + uint8_ptr pkt_tx = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH); + + XbeeWrapper wrap(1000); + wrap.bus->setRespondWithTxStatus(true, 0); + + RXPacketFrame rx; + rx.setRXDataLength(MAX_PACKET_PAYLOAD_LENGTH); + memcpy(rx.getRXDataPointer(), pkt_rx.get(), MAX_PACKET_PAYLOAD_LENGTH); + rx.setReceiveOptions(RO_POINT_MULTIPOINT); + rx.setSourceAddress(0x1122334455667788); + + int num_rx_while_tx = 0; + + wrap.xbee->setOnFrameReceivedListener([&](APIFrame& api) { + if (api.frame_type == FTYPE_RX_PACKET_FRAME) + { + ++num_rx_while_tx; + } + }); + + SECTION("2 RX packet smaller than one TX packet") + { + rx.setRXDataLength(50); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + wrap.bus->pushApiFrame(rx); + + REQUIRE(wrap.xbee->send(pkt_tx.get(), 150)); + + REQUIRE(num_rx_while_tx == 2); + + size_t rx_len = 0; + while (rx_len < 100) + { + rx_len += wrap.xbee->receive(rx_buf.get() + rx_len, 200); + } + + REQUIRE(rx_len == 100); + REQUIRE(memcmp(rx_buf.get(), pkt_rx.get(), 50) == 0); + REQUIRE(memcmp(rx_buf.get() + 50, pkt_rx.get(), 50) == 0); + } + + SECTION("More RX packets than buffer size: drop the oldest") + { + rx.setRXDataLength(10); + + // 1 packet too many + unsigned int numpkts = RX_FRAMES_BUF_SIZE + 1; + for (unsigned int i = 0; i < numpkts; i++) + { + memcpy(rx.getRXDataPointer(), pkt_rx.get() + i, 10); + rx.calcChecksum(); + wrap.bus->pushApiFrame(rx); + } + + // Should receive all the packets while sending + REQUIRE(wrap.xbee->send(pkt_tx.get(), MAX_PACKET_PAYLOAD_LENGTH)); + + REQUIRE(num_rx_while_tx == numpkts); + + // Only receive the last three packets + size_t rx_len = 0; + while (rx_len < (numpkts - 1) * 10) + { + rx_len += wrap.xbee->receive(rx_buf.get() + rx_len, 200); + } + + REQUIRE(rx_len == (numpkts - 1) * 10); + + for (unsigned int i = 0; i < numpkts - 1; i++) + { + REQUIRE(memcmp(rx_buf.get() + i * 10, pkt_rx.get() + i + 1, 10) == + 0); + } + } + + SECTION("2 RX packet smaller than one TX packet, 1 byte receive") + { + rx.setRXDataLength(50); + rx.calcChecksum(); + + wrap.bus->pushApiFrame(rx); + wrap.bus->pushApiFrame(rx); + + REQUIRE(wrap.xbee->send(pkt_tx.get(), 150)); + + REQUIRE(num_rx_while_tx == 2); + + for (int i = 0; i < 50; i++) + { + REQUIRE(wrap.xbee->receive(rx_buf.get() + i, 1) == 1); + } + REQUIRE(memcmp(rx_buf.get(), pkt_rx.get(), 50) == 0); + + for (int i = 0; i < 50; i++) + { + REQUIRE(wrap.xbee->receive(rx_buf.get() + i, 1) == 1); + } + REQUIRE(memcmp(rx_buf.get(), pkt_rx.get(), 50) == 0); + } +} + +TEST_CASE("[Xbee] Test Xbee::sendAtCommand(...)") +{ + XbeeWrapper wrap(DEFAULT_TX_TIMEOUT); + + SECTION("AT Command, no response required") + { + wrap.xbee->sendATCommand("AB"); + + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + REQUIRE(wrap.bus->getParsedFrames()[0].frame_type == FTYPE_AT_COMMAND); + } + + SECTION("AT Command, response required but not received") + { + ATCommandResponseFrame response; + long long start = miosix::getTick(); + REQUIRE_FALSE( + wrap.xbee->sendATCommand("AB", &response, nullptr, 0, 1000)); + REQUIRE(miosix::getTick() >= start + 1000); + + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + REQUIRE(wrap.bus->getParsedFrames()[0].frame_type == FTYPE_AT_COMMAND); + } + + SECTION("AT Command, response required and received") + { + // Padding required in order to not receive the ATCommandResponse + // frame too early in testing + RXPacketFrame padding; + padding.setRXDataLength(50); + padding.calcChecksum(); + + ATCommandResponseFrame resp; + resp.setATCommand("AB"); + resp.setFrameID(1); + resp.setCommandDataSize(1); + resp.getCommandDataPointer()[0] = 0xAB; + resp.calcChecksum(); + + wrap.bus->pushApiFrame(padding); + wrap.bus->pushApiFrame(resp); + + ATCommandResponseFrame received_response; + long long start = miosix::getTick(); + + REQUIRE(wrap.xbee->sendATCommand("AB", &received_response, nullptr, 0, + 1000)); + REQUIRE(miosix::getTick() < start + 1000); + + REQUIRE(wrap.bus->getParsedFrames().size() > 0); + REQUIRE(wrap.bus->getParsedFrames()[0].frame_type == FTYPE_AT_COMMAND); + + REQUIRE(received_response.getCommandDataLength() == 1); + REQUIRE(received_response.getCommandDataPointer()[0] == 0xAB); + } +} \ No newline at end of file diff --git a/src/tests/catch/xbee/test-xbee-parser.cpp b/src/tests/catch/xbee/test-xbee-parser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d51eafb6b3c417cc485c462d4b68d51ef7cf0a00 --- /dev/null +++ b/src/tests/catch/xbee/test-xbee-parser.cpp @@ -0,0 +1,574 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +// Disable asserts +#define NDEBUG + +#include <cstdio> + +#ifdef STANDALONE_CATCH1_TEST +#include "../catch-tests-entry.cpp" +#endif + +#include <cstdio> +#include <cstring> + +#include "drivers/Xbee/APIFrameParser.h" +#include "utils/testutils/catch.hpp" + +using namespace Xbee; + +/** + * @brief Calculates the checksum for a sequence of bytes representing an API + * frame. + * + * @param frame bytes (including start delimiter, and checksum set to 0) + * @param frame_size size of the frame, including start delimiter and checksum + */ +void calcChecksum(uint8_t* frame, size_t frame_size) +{ + frame[frame_size - 1] = 0; + for (size_t i = 3; i < frame_size - 1; i++) + { + frame[frame_size - 1] += frame[i]; + } + frame[frame_size - 1] = 0xFF - frame[frame_size - 1]; +} + +void printHex(uint8_t* frame, size_t frame_size) +{ + for (size_t i = 0; i < frame_size; i++) + { + printf("%02X ", frame[i]); + } + printf("\n"); +} + +void printu64(uint64_t v) +{ + uint8_t* p = reinterpret_cast<uint8_t*>(&v); + + for (int i = 0; i < 8; i++) + { + printf("%02X ", p[i]); + } + + printf("\n"); +} + +void printBuf(uint8_t* buf, size_t size) +{ + for (size_t i = 0; i < size; i++) + { + printf("%02X ", buf[i]); + } + + printf("\n"); +} + +bool compareAPIFrames(const APIFrame& a, const APIFrame& b) +{ + bool result = a.start_del == b.start_del && a.frame_type == b.frame_type && + a.length == b.length && a.checksum == b.checksum; + + return result && + memcmp(a.frame_data, b.frame_data, a.getFrameDataLength()) == 0; +} + +APIFrame parse(APIFrameParser& parser, uint8_t* data, size_t len) +{ + APIFrame out; + for (size_t i = 0; i < len; i++) + { + APIFrameParser::ParseResult pres = parser.parse(data[i], &out); + + if (i < len - 1) + { + CAPTURE(i); + REQUIRE(pres == APIFrameParser::ParseResult::PARSING); + } + else + { + REQUIRE(pres == APIFrameParser::ParseResult::SUCCESS); + } + } + + REQUIRE(out.verifyChecksum()); + + return out; +} + +template <typename FrameType> +void testParse(APIFrameParser& parser, FrameType orig) +{ + uint8_t bytes[MAX_API_FRAME_SIZE]; + + size_t len = orig.toBytes(bytes); + + APIFrame parsed_api = parse(parser, bytes, len); + + REQUIRE(compareAPIFrames(orig, parsed_api)); +} + +// Taken from examples in the datasheet +TEST_CASE("Frame serialization") +{ + uint8_t bytes[MAX_API_FRAME_SIZE]; + APIFrame deserialized; + + SECTION("AT Command") + { + ATCommandFrame at_orig; + at_orig.setATCommand("NH"); + REQUIRE(memcmp(at_orig.getATCommand(), "NH", 2) == 0); + + at_orig.setFrameID(0x01); + REQUIRE(at_orig.getFrameID() == 0x01); + + at_orig.setParameterSize(2); + at_orig.getCommandDataPointer()[0] = 0x07; + at_orig.getCommandDataPointer()[1] = 0x17; + + REQUIRE(at_orig.getCommandDataLength() == 2); + REQUIRE(at_orig.getFrameDataLength() == 5); + + at_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 6, 0x08, 1, + 0x4E, 0x48, 0x07, 0x17, 0x00}; + + calcChecksum(expected_bytes, 10); + + size_t len = at_orig.toBytes(bytes); + + REQUIRE(len == 10); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, at_orig)); + } + + SECTION("AT Command, No parameters") + { + ATCommandFrame at_orig; + + at_orig.setATCommand("NH"); + + at_orig.setFrameID(0x01); + REQUIRE(at_orig.getFrameID() == 0x01); + + REQUIRE(at_orig.getCommandDataLength() == 0); + REQUIRE(at_orig.getFrameDataLength() == 3); + + at_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 4, 0x08, 1, 0x4E, 0x48, 0x60}; + + size_t len = at_orig.toBytes(bytes); + + REQUIRE(len == 8); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, at_orig)); + } + + SECTION("AT Command Response") + { + ATCommandResponseFrame at_orig; + at_orig.setATCommand("BD"); + REQUIRE(memcmp(at_orig.getATCommand(), "BD", 2) == 0); + + at_orig.setFrameID(0x01); + REQUIRE(at_orig.getFrameID() == 0x01); + + at_orig.setCommandDataSize(3); + at_orig.getCommandDataPointer()[0] = 0x07; + at_orig.getCommandDataPointer()[1] = 0x17; + at_orig.getCommandDataPointer()[2] = 0x27; + + REQUIRE(at_orig.getCommandDataLength() == 3); + REQUIRE(at_orig.getFrameDataLength() == 7); + + at_orig.setCommandStatus(0x55); + REQUIRE(at_orig.getCommandStatus() == 0x55); + + at_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 8, 0x88, 0x01, 0x42, + 0x44, 0x55, 0x07, 0x17, 0x27, 0}; + + calcChecksum(expected_bytes, 12); + + size_t len = at_orig.toBytes(bytes); + REQUIRE(len == 12); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, at_orig)); + } + + SECTION("AT Command Response, No cmd data") + { + ATCommandResponseFrame at_orig; + at_orig.setATCommand("BD"); + + at_orig.setFrameID(0x01); + REQUIRE(at_orig.getFrameID() == 0x01); + + REQUIRE(at_orig.getCommandDataLength() == 0); + REQUIRE(at_orig.getFrameDataLength() == 4); + + at_orig.setCommandStatus(0x0); + REQUIRE(at_orig.getCommandStatus() == 0x00); + + at_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 5, 0x88, 0x01, + 0x42, 0x44, 0x00, 0xF0}; + + calcChecksum(expected_bytes, 9); + + size_t len = at_orig.toBytes(bytes); + REQUIRE(len == 9); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, at_orig)); + } + + SECTION("TX Request Frame") + { + TXRequestFrame tx_orig; + + tx_orig.setFrameID(0x01); + REQUIRE(tx_orig.getFrameID() == 0x01); + + tx_orig.setDestAddress(0x0013A200400A0127); + + bool addr_cmp = tx_orig.getDestAddress() == 0x0013A200400A0127; + REQUIRE(addr_cmp); + + tx_orig.setBroadcastRadius(0x55); + REQUIRE(tx_orig.getBroadcastRadius() == 0x55); + + tx_orig.setTransmitOptions(0x40); + REQUIRE(tx_orig.getTrasmitOptions() == 0x40); + + uint8_t* rf_data = tx_orig.getRFDataPointer(); + + rf_data[0] = 0x54; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + rf_data[6] = 0x30; + rf_data[7] = 0x41; + + tx_orig.setRFDataLength(8); + + REQUIRE(tx_orig.getRFDataLength() == 8); + REQUIRE(tx_orig.getFrameDataLength() == 21); + + uint8_t expected_bytes[] = {0x7E, 0, 0x16, 0x10, 0x01, 0x00, 0x13, + 0xA2, 0x00, 0x40, 0x0A, 0x01, 0x27, 0xFF, + 0xFE, 0x55, 0x40, 0x54, 0x78, 0x44, 0x61, + 0x74, 0x61, 0x30, 0x41, 0x00}; + + // Datasheet example checksum is wrong + calcChecksum(expected_bytes, 26); + tx_orig.calcChecksum(); + size_t len = tx_orig.toBytes(bytes); + + REQUIRE(len == 26); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, tx_orig)); + } + + SECTION("TX Request Frame - No payload") + { + TXRequestFrame tx_orig; + + tx_orig.setFrameID(0x01); + REQUIRE(tx_orig.getFrameID() == 0x01); + + tx_orig.setDestAddress(0x0013A200400A0127); + bool addr_cmp = tx_orig.getDestAddress() == 0x0013A200400A0127; + REQUIRE(addr_cmp); + + tx_orig.setBroadcastRadius(0x55); + REQUIRE(tx_orig.getBroadcastRadius() == 0x55); + + tx_orig.setTransmitOptions(0x40); + REQUIRE(tx_orig.getTrasmitOptions() == 0x40); + + uint8_t* rf_data = tx_orig.getRFDataPointer(); + + REQUIRE(tx_orig.getRFDataLength() == 0); + REQUIRE(tx_orig.getFrameDataLength() == 13); + + tx_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 14, 0x10, 0x01, 0x00, + 0x13, 0xA2, 0x00, 0x40, 0x0A, 0x01, + 0x27, 0xFF, 0xFE, 0x55, 0x40, 0x00}; + + calcChecksum(expected_bytes, 18); + + size_t len = tx_orig.toBytes(bytes); + + REQUIRE(len == 18); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, tx_orig)); + } + + SECTION("Modem status frame") + { + ModemStatusFrame mod_orig; + + mod_orig.setStatus(0x55); + + mod_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 2, 0x8A, 0x55, 0x00}; + + calcChecksum(expected_bytes, 6); + + size_t len = mod_orig.toBytes(bytes); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, mod_orig)); + } + + SECTION("TX Status frame") + { + constexpr size_t frame_size = 11; + TXStatusFrame tx_orig{}; + + tx_orig.setFrameID(0x47); + REQUIRE(tx_orig.getFrameID() == 0x47); + tx_orig.setTransmitRetryCount(0x55); + REQUIRE(tx_orig.getTransmitRetryCount() == 0x55); + + tx_orig.setDeliveryStatus(0x44); + REQUIRE(tx_orig.getDeliveryStatus() == 0x44); + + tx_orig.setDiscoveryStatus(2); + REQUIRE(tx_orig.getDiscoveryStatus() == 2); + + tx_orig.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 0x07, 0x8B, 0x47, 0xFF, + 0xFE, 0x55, 0x44, 0x02, 0x00}; + + calcChecksum(expected_bytes, 11); + + size_t len = tx_orig.toBytes(bytes); + REQUIRE(len == 11); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, tx_orig)); + } + + SECTION("RX Packet frame") + { + RXPacketFrame rx; + + rx.setSourceAddress(0x0013A20040522BAA); + bool addr_cmp = rx.getSourceAddress() == 0x0013A20040522BAA; + REQUIRE(addr_cmp); + + rx.setReceiveOptions(0x01); + REQUIRE(rx.getReceiveOptions() == 0x01); + + uint8_t* rf_data = rx.getRXDataPointer(); + + rf_data[0] = 0x52; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + + rx.setRXDataLength(6); + REQUIRE(rx.getRXDataLength() == 6); + REQUIRE(rx.getFrameDataLength() == 0x11); + + rx.calcChecksum(); + + uint8_t expected_bytes[] = { + 0x7E, 0, 0x12, 0x90, 0x00, 0x13, 0xA2, 0x00, 0x40, 0x52, 0x2B, + 0xAA, 0xFF, 0xFE, 0x01, 0x52, 0x78, 0x44, 0x61, 0x74, 0x61, 0x11}; + + // calcChecksum(expected_bytes, frame_size); + + size_t len = rx.toBytes(bytes); + + REQUIRE(len == 22); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, rx)); + } + + SECTION("RX Packet frame - no payload") + { + RXPacketFrame rx; + + rx.setSourceAddress(0x0013A20040522BAA); + bool addr_cmp = rx.getSourceAddress() == 0x0013A20040522BAA; + REQUIRE(addr_cmp); + + rx.setReceiveOptions(0x01); + REQUIRE(rx.getReceiveOptions() == 0x01); + + REQUIRE(rx.getRXDataLength() == 0); + REQUIRE(rx.getFrameDataLength() == 11); + + rx.calcChecksum(); + + uint8_t expected_bytes[] = {0x7E, 0, 12, 0x90, 0x00, 0x13, + 0xA2, 0x00, 0x40, 0x52, 0x2B, 0xAA, + 0xFF, 0xFE, 0x01, 0x00}; + + calcChecksum(expected_bytes, 16); + + size_t len = rx.toBytes(bytes); + + REQUIRE(len == 16); + + REQUIRE(memcmp(bytes, expected_bytes, len) == 0); + + memset(deserialized.frame_data, 0, FRAME_DATA_SIZE); + REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized)); + REQUIRE(compareAPIFrames(deserialized, rx)); + } +} + +TEST_CASE("Frame parsing") +{ + APIFrameParser parser; + + SECTION("RXPacketFrame Parsing") + { + RXPacketFrame rx; + rx.setSourceAddress(0x0013A20040522BAA); + rx.setReceiveOptions(0x01); + uint8_t* rf_data = rx.getRXDataPointer(); + + rf_data[0] = 0x52; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + + rx.setRXDataLength(6); + rx.calcChecksum(); + + // Parse 3 times the same frame + testParse(parser, rx); + testParse(parser, rx); + testParse(parser, rx); + } +} + +TEST_CASE("Parser edge cases") +{ + APIFrameParser parser; + + SECTION("Wrong checksum") + { + RXPacketFrame rx; + rx.setSourceAddress(0x0013A20040522BAA); + rx.setReceiveOptions(0x01); + uint8_t* rf_data = rx.getRXDataPointer(); + + rf_data[0] = 0x52; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + + rx.setRXDataLength(6); + rx.calcChecksum(); + + uint8_t bytes[MAX_API_FRAME_SIZE]; + + size_t len = rx.toBytes(bytes); + + bytes[len - 1] += 1; + + APIFrame out; + for (size_t i = 0; i < len; i++) + { + APIFrameParser::ParseResult res = parser.parse(bytes[i], &out); + REQUIRE_FALSE(res == APIFrameParser::ParseResult::SUCCESS); + + if (i == len - 1) + { + REQUIRE(res == APIFrameParser::ParseResult::FAIL); + } + } + } + + SECTION("Wrong start delimiter") + { + RXPacketFrame rx; + rx.start_del = 0x55; + rx.setSourceAddress(0x0013A20040522BAA); + rx.setReceiveOptions(0x01); + uint8_t* rf_data = rx.getRXDataPointer(); + + rf_data[0] = 0x52; + rf_data[1] = 0x78; + rf_data[2] = 0x44; + rf_data[3] = 0x61; + rf_data[4] = 0x74; + rf_data[5] = 0x61; + + rx.setRXDataLength(6); + rx.calcChecksum(); + + uint8_t bytes[MAX_API_FRAME_SIZE]; + + size_t len = rx.toBytes(bytes); + + APIFrame out; + for (size_t i = 0; i < len; i++) + { + APIFrameParser::ParseResult res = parser.parse(bytes[i], &out); + REQUIRE(res == APIFrameParser::ParseResult::IDLE); + } + } +} \ No newline at end of file diff --git a/src/tests/drivers/analog/test-analog-pressure-sensors.cpp b/src/tests/drivers/analog/test-analog-pressure-sensors.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ee0b704a0d763dc00e29c19fae8158e913b408cd --- /dev/null +++ b/src/tests/drivers/analog/test-analog-pressure-sensors.cpp @@ -0,0 +1,122 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#include <diagnostic/PrintLogger.h> +#include <miosix.h> + +#include "TimestampTimer.h" +#include "drivers/adc/ADS1118/ADS1118.h" +#include "drivers/spi/SPIDriver.h" +#include "sensors/analog/pressure/AnalogPressureSensor.h" +#include "sensors/analog/pressure/honeywell/HSCMAND015PA.h" +#include "sensors/analog/pressure/honeywell/HSCMRNN030PA.h" + +#include "TimestampTimer.h" + +GpioPin sckPin = GpioPin(GPIOB_BASE, 13); +GpioPin misoPin = GpioPin(GPIOB_BASE, 14); +GpioPin mosiPin = GpioPin(GPIOB_BASE, 15); +GpioPin csPin = GpioPin(GPIOC_BASE, 1); + +constexpr ADS1118::ADS1118Mux channel1 = ADS1118::ADS1118Mux::MUX_AIN2_GND; +constexpr ADS1118::ADS1118Mux channel2 = ADS1118::ADS1118Mux::MUX_AIN3_GND; + +void initBoard() +{ + // Enable SPI clock for SPI2 interface + RCC->APB1ENR |= RCC_APB1ENR_SPI2EN; + + // Alternate function configuration for SPI pins + sckPin.mode(miosix::Mode::ALTERNATE); + sckPin.alternateFunction(5); // SPI function + mosiPin.mode(miosix::Mode::ALTERNATE); + mosiPin.alternateFunction(5); // SPI function + misoPin.mode(miosix::Mode::ALTERNATE); + misoPin.alternateFunction(5); // SPI function + + // Chip select pin as output starting high + csPin.mode(miosix::Mode::OUTPUT); + csPin.high(); +} + +int main() +{ + PrintLogger log = Logging::getLogger("test-analog-pressure-sensors"); + + // Enable SPI clock and set gpios + initBoard(); + + TimestampTimer::enableTimestampTimer(); + + // SPI configuration setup + SPIBusConfig spiConfig; + spiConfig.clock_div = SPIClockDivider::DIV32; + spiConfig.mode = SPIMode::MODE1; + SPIBus spiBus(SPI2); + SPISlave spiSlave(spiBus, csPin, spiConfig); + + // Device initialization + ADS1118 ads1118(spiSlave); + + std::function<ADCData()> get_voltage_function = + std::bind(&ADS1118::getVoltage, ads1118, channel1); + HSCMAND015PA analog_sensor(get_voltage_function); + + analog_sensor.init(); + analog_sensor.selfTest(); + + std::function<ADCData()> get_voltage_function2 = + std::bind(&ADS1118::getVoltage, ads1118, channel2); + HSCMRNN030PA analog_sensor2(get_voltage_function2); + + analog_sensor2.init(); + analog_sensor2.selfTest(); + + // Enable channels + ads1118.enableInput(channel1, ADS1118::ADS1118DataRate::DR_250, + ADS1118::ADS1118Pga::FSR_4_096); + ads1118.enableInput(channel2, ADS1118::ADS1118DataRate::DR_250, + ADS1118::ADS1118Pga::FSR_4_096); + + // Initialize the device + ads1118.init(); + + // Self test + if (ads1118.selfTest()) + { + TRACE("Self test successful!\n"); + } + else + { + TRACE("Self test failed :( error: %d\n", ads1118.getLastError()); + } + + // Read samples with sample() + while (1) + { + ads1118.sample(); + miosix::Thread::sleep(5); + + printf("%.2f\t%.2f\n", ads1118.getVoltage(channel1).voltage, + ads1118.getVoltage(channel2).voltage); + } +} \ No newline at end of file diff --git a/src/tests/drivers/analog/test-battery-voltage.cpp b/src/tests/drivers/analog/test-battery-voltage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..93e1d06f05791041688eca44371e105427899a7f --- /dev/null +++ b/src/tests/drivers/analog/test-battery-voltage.cpp @@ -0,0 +1,97 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#include <miosix.h> + +#include "Debug.h" +#include "TimestampTimer.h" +#include "drivers/adc/InternalADC/InternalADC.h" +#include "sensors/analog/battery/BatteryVoltageSensor.h" + +using namespace miosix; + +GpioPin battery_pin = GpioPin(GPIOA_BASE, 3); +InternalADC::Channel ADC_CHANNEL = InternalADC::Channel::CH3; +ADC_TypeDef& ADCx = *ADC3; +InternalADC adc(ADCx); + +void initBoard() +{ + { + miosix::FastInterruptDisableLock dLock; + + battery_pin.mode(miosix::Mode::INPUT_ANALOG); + + // Set pins PA0 PA1 PA2 PA3 as analog input + // RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; + // GPIOA->MODER = 0xFF; + + // Enable ADC3 clock + RCC->APB2ENR |= RCC_APB2ENR_ADC3EN; // <- CHANGE THIS! + + // Set the clock divider for the analog circuitry (/8) + ADC->CCR |= ADC_CCR_ADCPRE_0 | ADC_CCR_ADCPRE_1; + } + + TimestampTimer::enableTimestampTimer(); +} + +int main() +{ + // Enable SPI clock and set gpios + initBoard(); + + adc.enableChannel(ADC_CHANNEL); + + printf("Configuration completed\n"); + + std::function<ADCData()> get_voltage_function = []() + { return adc.getVoltage(ADC_CHANNEL); }; + // std::bind(&InternalADC::getVoltage, adc, ADC_CHANNEL); + + BatteryVoltageSensor battery_sensor(get_voltage_function, 0.2063); + + if (!adc.init() || !adc.selfTest()) + { + printf("ERROR : %d\n", adc.getLastError()); + } + else + { + battery_sensor.init(); + + // Read samples with sample() + while (1) + { + adc.sample(); + + miosix::Thread::sleep(100); + + battery_sensor.sample(); + + BatteryVoltageSensorData bat_data = battery_sensor.getLastSample(); + printf("%llu %u %f %f \n", bat_data.adc_timestamp, + bat_data.channel_id, bat_data.voltage, bat_data.bat_voltage); + + miosix::Thread::sleep(100); + } + } +} \ No newline at end of file diff --git a/src/tests/drivers/analog/test-current-sensor.cpp b/src/tests/drivers/analog/test-current-sensor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..08df0b0553b9fc8541b89704d33e74b7dd312560 --- /dev/null +++ b/src/tests/drivers/analog/test-current-sensor.cpp @@ -0,0 +1,100 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#include <miosix.h> + +#include "Debug.h" +#include "TimestampTimer.h" +#include "drivers/adc/InternalADC/InternalADC.h" +#include "sensors/analog/current/CurrentSensor.h" + +using namespace miosix; + +GpioPin battery_pin = GpioPin(GPIOA_BASE, 3); +InternalADC::Channel ADC_CHANNEL = InternalADC::Channel::CH3; +ADC_TypeDef& ADCx = *ADC3; +InternalADC adc(ADCx); + +void initBoard() +{ + { + miosix::FastInterruptDisableLock dLock; + + battery_pin.mode(miosix::Mode::INPUT_ANALOG); + + // Set pins PA0 PA1 PA2 PA3 as analog input + // RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; + // GPIOA->MODER = 0xFF; + + // Enable ADC3 clock + RCC->APB2ENR |= RCC_APB2ENR_ADC3EN; // <- CHANGE THIS! + + // Set the clock divider for the analog circuitry (/8) + ADC->CCR |= ADC_CCR_ADCPRE_0 | ADC_CCR_ADCPRE_1; + } + + TimestampTimer::enableTimestampTimer(); +} + +int main() +{ + // Enable SPI clock and set gpios + initBoard(); + + adc.enableChannel(ADC_CHANNEL); + + printf("Configuration completed\n"); + + std::function<ADCData()> get_voltage_function = []() + { return adc.getVoltage(ADC_CHANNEL); }; + // std::bind(&InternalADC::getVoltage, adc, ADC_CHANNEL); + // example transfer function + std::function<float(float)> adc_to_current = [](float adc_in) + { return (adc_in - 107.0f) * 32.4f; }; + CurrentSensor current_sensor(get_voltage_function, adc_to_current); + + if (!adc.init() || !adc.selfTest()) + { + printf("ERROR : %d\n", adc.getLastError()); + } + else + { + current_sensor.init(); + + // Read samples with sample() + while (1) + { + adc.sample(); + + miosix::Thread::sleep(500); + + current_sensor.sample(); + + CurrentSensorData current_data = current_sensor.getLastSample(); + printf("%llu %u %f %f \n", current_data.adc_timestamp, + current_data.channel_id, current_data.voltage, + current_data.current); + + miosix::Thread::sleep(100); + } + } +} \ No newline at end of file diff --git a/src/tests/drivers/flashmemory/FlashControllerTests.h b/src/tests/drivers/flashmemory/FlashControllerTests.h new file mode 100644 index 0000000000000000000000000000000000000000..64d9f811f4fb9f90d08a961909faa9b9835d2aa1 --- /dev/null +++ b/src/tests/drivers/flashmemory/FlashControllerTests.h @@ -0,0 +1,708 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#ifndef FLASHCONTROLLERTESTS_H +#define FLASHCONTROLLERTESTS_H + +#include <Common.h> +#include <helper/MultiFlashController.h> +#include <diagnostic/NewLogger.h> +#include <drivers/timer.h> +#include <Tests.h> +#include <vector> +#include <limits> +#include <string> + +using std::vector; +using logging::logger; +using namespace flashmemory; + +namespace testing +{ +namespace flashmemorytests +{ + +template<typename MemorySPI> +class FlashTest : public Test +{ + public: + + FlashTest(MultiFlashController* multi) + : Test(), multi(multi), controller(multi->flash0_), driver( + controller->flash_) + { + + } + protected: + typedef FlashController<MemorySPI> FlashControllerType; + + void controller_setBlockIndex(uint32_t block_index) + { + controller->block_index_ = block_index; + } + + uint32_t controller_getBlockIndex() + { + return controller->block_index_; + } + + bool controller_format() + { +#ifdef FLASH_TEST + return FlashControllerType::format(); +#else + return FlashControllerType::fastFormat(); +#endif + } + + void controller_init() + { + controller->init(); + } + + bool controller_readSectorHeader(uint32_t sector, SectorHeader* header) + { + return FlashControllerType::readSectorHeader(sector, header); + } + + bool controller_readBlock(uint32_t block_index, uint8_t* buffer) + { + return FlashControllerType::readBlock(block_index, buffer); + } + + bool controller_readDataBlock(uint32_t block_index, FlashDataBlock* block) + { + return FlashControllerType::readDataBlock(block_index, block); + } + + bool controller_writeBlock(FlashDataBlock* block) + { + return controller->writeDataBlock(block); + } + + bool controller_writeSectorHeader(uint32_t sector, SectorHeader& sheader) + { + return FlashControllerType::writeSectorHeader(sector, sheader); + } + + MultiFlashController* multi; + FlashController<MemorySPI>* controller; + FlashDriver<MemorySPI>* driver; +}; + +/* + * Copy this to create a new FlashTest + * + + template<typename MemorySPI> + class EmptyFlashTest : public FlashTest<MemorySPI> + { + typedef FlashTest<MemorySPI> Base; + + public: + + EmptyFlashTest(MultiFlashController* multi) + : Base::FlashTest(multi) + { + + } + + bool setup() + { + return true; + } + + bool run() + { + return true; + } + + string name() + { + return "EmptyFlashTest"; + } + }; + + */ + +class CompareFlashHeaderTest : public Test +{ + public: + + CompareFlashHeaderTest() + : Test() + { + + } + + bool run() + { + bool success = true; + FlashHeader header1, header2; + + bool test = header1 == header2; + success = success && test; + logger.info(logtag(), "Compare equal headers: ", (test ? "OK" : "Error")); + + header1.read_only = 54; + + test = header1 == header2; + success = success && (test == false); + logger.info(logtag(), "Compare different headers: ", + (test == false ? "OK" : "Error")); + + return success; + } + + string name() + { + return "CompareFlashHeaderTest"; + } + + private: + static string logtag() + { + return "CompareFHTest"; + } +}; + +template<typename MemorySPI> +class MeasureRebootTimeTest : public FlashTest<MemorySPI> +{ + typedef FlashTest<MemorySPI> Base; + typedef Timer32<2> Timer2; + public: + MeasureRebootTimeTest(MultiFlashController* multi) + : Base::FlashTest(multi) + { + + } + + bool run() + { +#ifdef FLASH_TEST + logger.warning(logtag(), "You are using the mockup memory!"); +#endif + logger.info(logtag(), "Running 100 read repetitions!"); + uint8_t result; + SectorHeader header; + float avg, min = std::numeric_limits<float>::max(), max = 0; + Timer2::start(); + for (int j = 0; j < 100; j++) + { + uint32_t t0 = Timer2::tick(); + for (uint32_t i = 0; i < SECTORS_NUM; i++) + { + Base::driver->read(&result, i * SECTOR_SIZE, (uint8_t*) (&header), + sizeof(SectorHeader)); + if (result != RESULT_OK) + { + break; + } + } + uint32_t t1 = Timer2::tick(); + float t = Timer2::milliSeconds(t1 - t0); + avg += t; + if (t < min) + { + min = t; + } else if (t > max) + { + max = t; + } + } + Timer2::stop(); + avg = avg / 100; + if (result == RESULT_OK) + { + logger.info(logtag(), "Avg time: ", avg, " ms"); + logger.info(logtag(), "Min time: ", min, " ms, Max time: ", max, " ms"); + return true; + } else + { + return false; + } + } + + string name() + { + return "MeasureWorstRebootTimeTest"; + } + + private: + static string logtag() + { + return "RebootTimeTest"; + } +}; + +template<typename MemorySPI> +class FlashHeaderTest : public FlashTest<MemorySPI> +{ + typedef FlashTest<MemorySPI> Base; + //using Base::driver; + //using Base::controller; + + public: + + FlashHeaderTest(MultiFlashController* multi) + : Base::FlashTest(multi) + { + + } + + bool setup() + { + return cleanFirstSubsector(); + } + + bool run() + { + FlashHeader orig_header, read_header; + generateRandomHeader(&orig_header); + orig_header.first_boot = FIRST_BOOT_KEY; + logger.info(logtag(), "--SUBTEST 1: Write & read over clean memory"); + + if (!writeAndReadHeader(orig_header, read_header, false)) + { + return false; + } + if (orig_header != read_header) + { + logger.error(logtag(), "Original & written headers are not the same!"); + return false; + } + logger.info(logtag(), "Success."); + + orig_header.first_boot = 0; + logger.info(logtag(), "--SUBTEST 2: Overwrite first_boot"); + + if (!writeAndReadHeader(orig_header, read_header, false)) + { + return false; + } + if (orig_header != read_header) + { + logger.error(logtag(), "Original & written headers are not the same!"); + return false; + } + logger.info(logtag(), "Success."); + + logger.info(logtag(), "--SUBTEST 3: Write & read over dirty memory"); + FlashHeader dirtywrite_header; + generateRandomHeader(&dirtywrite_header); + + if (Base::FlashControllerType::writeFlashHeader(dirtywrite_header, false)) + { + logger.error( + logtag(), + "Function writeFlashHeader should have returned RESULT_CHECK_FAIL!"); + return false; + } + logger.info(logtag(), "Success."); + + logger.info(logtag(), + "--SUBTEST 4: Write with erase & read over dirty memory"); + generateRandomHeader(&dirtywrite_header); + + if (!writeAndReadHeader(dirtywrite_header, read_header, true)) + { + return false; + } + if (dirtywrite_header != read_header) + { + logger.error(logtag(), "Original & written headers are not the same!"); + return false; + } + logger.info(logtag(), "Success."); + return true; + } + + string name() + { + return "WriteFlashHeaderTest"; + } + + private: + static string logtag() + { + return "WrtFHeaderTest"; + } + + bool cleanFirstSubsector() + { + uint8_t result; + Base::driver->enableErase(); + Base::driver->eraseSubsector(&result, 0); + + return result == RESULT_OK; + } + + void generateRandomHeader(FlashHeader* header) + { + header->start_index = (uint32_t) rand(); + header->read_only = (uint32_t) rand(); + header->first_boot = (uint32_t) rand(); + } + + bool writeAndReadHeader(FlashHeader& write_header, FlashHeader& read_header, + bool erase) + { + if (!Base::FlashControllerType::writeFlashHeader(write_header, erase)) + { + logger.error(logtag(), "Error writing header"); + return false; + } + + if (!Base::FlashControllerType::readFlashHeader(&read_header)) + { + logger.error(logtag(), "Error reading header"); + return false; + } + + return true; + } +}; + +template<typename MemorySPI> +class FlashBlockTest : public FlashTest<MemorySPI> +{ + typedef FlashTest<MemorySPI> Base; + + public: + + FlashBlockTest(MultiFlashController* multi) + : Base::FlashTest(multi) + { + + } + + bool setup() + { + if (Base::controller_format()) + { + Base::controller_init(); + return true; + } + return false; + } + + bool run() + { + logger.info(logtag(), "--SUBTEST 1: Just write and read"); + if (!subtest1()) + return false; + logger.info(logtag(), "Success."); + + logger.info(logtag(), "--SUBTEST 2: Write near the end of a sector"); + if (!subtest2()) + return false; + logger.info(logtag(), "Success."); + + logger.info(logtag(), "--SUBTEST 3: Write at the end of the memory"); + if (!subtest3()) + return false; + logger.info(logtag(), "Success."); + + return true; + } + + string name() + { + return "FlashBlockTest"; + } + + private: + static string logtag() + { + return "FBlockTest"; + } + + bool subtest1() + { + uint32_t block_index = SUBSECTOR_SIZE / BLOCK_SIZE; + + Base::controller_setBlockIndex(block_index); + FlashDataBlock write_block, read_block; + generateDataBlock(&write_block); + + if (!writeBlock(&write_block)) + { + logger.error(logtag(), "Cannot write block"); + return false; + } + + if (!readBlock(block_index, &read_block)) + { + logger.error(logtag(), "Cannot read block"); + return false; + } + + if (read_block != write_block) + { + logger.error(logtag(), "Written & read block do not match!"); + return false; + } + return true; + } + + bool subtest2() + { + uint32_t block_index = SECTOR_SIZE / BLOCK_SIZE - 1; + + Base::controller_setBlockIndex(block_index); + + FlashDataBlock write_blocks[2]; + FlashDataBlock read_blocks[2]; + for (int i = 0; i < 2; i++) + { + generateDataBlock(&write_blocks[i]); + if (!writeBlock(&write_blocks[i])) + { + logger.error(logtag(), "Cannot write block ", i + 1); + return false; + } + } + + SectorHeader sheader; + if (!Base::controller_readSectorHeader(1, &sheader)) + { + logger.error(logtag(), "Cannot read sector header"); + return false; + } + + if (sheader.content != SECTOR_WRITTEN) + { + logger.error(logtag(), "Sector header not correctly written"); + logger.error(logtag(), "Sector header content: 0x%02X; should be: 0x%02X", + sheader.content, SECTOR_WRITTEN); + return false; + } + + if (!readBlock(block_index, &read_blocks[0])) + { + logger.error(logtag(), "Cannot read block 1"); + return false; + } + + if (!readBlock(block_index + 2, &read_blocks[1])) + { + logger.error(logtag(), "Cannot read block 2"); + return false; + } + + if (!compareBlocks(write_blocks, read_blocks, 2)) + { + logger.error(logtag(), "Written & read blocks do not match."); + return false; + } + return true; + } + + bool subtest3() + { + //Last block of the memory + uint32_t block_index = MEMORY_SIZE / BLOCK_SIZE - 1; + Base::controller_setBlockIndex(block_index); + + //Keep the filesystem consistent. + SectorHeader sheader; + sheader.content = SECTOR_WRITTEN; + Base::controller_writeSectorHeader(MEMORY_SIZE / SECTOR_SIZE - 1, sheader); + + FlashDataBlock write_blocks[2]; + FlashDataBlock read_blocks[2]; + + for (int i = 0; i < 2; i++) + { + generateDataBlock(&write_blocks[i]); + } + + if (!writeBlock(&write_blocks[0])) + { + logger.error(logtag(), + "Write block failed on the last available address!"); + return false; + } + + if (writeBlock(&write_blocks[1])) + { + logger.error( + logtag(), + "Write block didn't fail while writing outside of the memory!"); + return false; + } + if (!readBlock(block_index, &read_blocks[0])) + { + logger.error(logtag(), "Cannot read block 1"); + return false; + } + + if (readBlock(block_index + 2, &read_blocks[1])) + { + logger.error( + logtag(), + "readBlock didn't fail while reading outside of the memory!"); + return false; + } + + if (read_blocks[0] != write_blocks[0]) + { + logger.error(logtag(), "Written & read blocks do not match."); + return false; + } + + return true; + } + + bool writeBlock(FlashDataBlock* block) + { + return Base::controller_writeBlock(block); + } + + bool readBlock(uint32_t block_index, FlashDataBlock* block) + { + return Base::controller_readBlock(block_index, (uint8_t*) (block)); + } + + bool compareBlocks(FlashDataBlock* blocks1, FlashDataBlock* blocks2, + size_t num_blocks) + { + for (size_t i = 0; i < num_blocks; i++) + { + if (blocks1[i] != blocks2[i]) + { + return false; + } + } + return true; + } + + void generateDataBlock(FlashDataBlock* generated) + { + generated->id = (uint32_t) rand(); + generated->crc = (uint16_t) rand(); + generated->timestamp = (uint32_t) rand(); + + for (int i = 0; i < FLASH_BUFFER_SIZE; i++) + { + generated->buffer[i] = (uint8_t) (rand() % 256); + } + } +}; + +template<typename MemorySPI> +class MultiFlashCTRLTest : public FlashTest<MemorySPI> +{ + typedef FlashTest<MemorySPI> Base; + + public: + + MultiFlashCTRLTest(MultiFlashController* multi) + : Base::FlashTest(multi) + { + + } + + bool setup() + { + if (!Base::controller_format()) + { + return false; + } + Base::controller_init(); + if (Base::controller->getStatus() + != Base::FlashControllerType::Status::WRITE_READY) + { + logger.error(logtag(), "Error initializing flash controller."); + return false; + } + return true; + } + + bool run() + { + logger.info(logtag(), "--SUBTEST 1: Write @ beginning"); + if (!subtest1()) + return false; + logger.info(logtag(), "Success."); + + return true; + } + + string name() + { + return "EmptyFlashTest"; + } + + private: + bool subtest1() + { + uint32_t index = Base::controller_getBlockIndex(); + + uint8_t rand_buff1[FLASH_BUFFER_SIZE]; + uint8_t rand_buff2[FLASH_BUFFER_SIZE]; + Base::multi->addData(rand_buff1, FLASH_BUFFER_SIZE); + Base::multi->addData(rand_buff2, FLASH_BUFFER_SIZE); + + Thread::sleep(500); //Wait for data to be written + + logger.info(logtag(), "Reading blocks."); + FlashDataBlock read_block; + + if(!Base::controller_readDataBlock(index, &read_block)) + { + logger.error(logtag(), "Error reading first block."); + return false; + } + + if(memcmp(rand_buff1, &(read_block.buffer), FLASH_BUFFER_SIZE) != 0) + { + logger.error(logtag(), "Read block 1 is not the same as written one."); + return false; + } + + if(!Base::controller_readDataBlock(index+1, &read_block)) + { + logger.error(logtag(), "Error reading second block."); + return false; + } + + if(memcmp(rand_buff2, &(read_block.buffer), FLASH_BUFFER_SIZE) != 0) + { + logger.error(logtag(), "Read block 2 is not the same as written one."); + return false; + } + + return true; + } + + static string logtag() + { + return "MultiFlashCTRLTest"; + } +}; + +} //namespace flashmemorytests +} //namespace testing + +#endif /*FLASHCONTROLLERTESTS_H*/ diff --git a/src/tests/drivers/flashmemory/mockup/FlashDriver.h b/src/tests/drivers/flashmemory/mockup/FlashDriver.h new file mode 100644 index 0000000000000000000000000000000000000000..f8658ea180a17a4d7753f22c8efaf74de41cb5a8 --- /dev/null +++ b/src/tests/drivers/flashmemory/mockup/FlashDriver.h @@ -0,0 +1,620 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#ifndef FLASHDRIVER_H +#define FLASHDRIVER_H + +#include <miosix.h> +#include <Singleton.h> +#include <diagnostic/NewLogger.h> + +using miosix::Thread; +using miosix::Mode; +using logging::logger; + +//Forward declaration +namespace testing +{ +namespace flashmemorytests +{ +template<typename> +class FlashTest; +} +} + +namespace flashmemory +{ + +static const uint32_t PAGES_PER_SUBSECTOR = 4U; +static const uint32_t PAGES_PER_SECTOR = 16U; +static const uint32_t SUBSECTORS_PER_SECTOR = 4U; + +static const uint32_t SUBSECTORS_NUM = 48U; +static const uint32_t SECTORS_NUM = 12U; +static const uint32_t PAGE_SIZE = 256U; + +static const uint32_t SUBSECTOR_SIZE = PAGE_SIZE * PAGES_PER_SUBSECTOR; +static const uint32_t SECTOR_SIZE = PAGE_SIZE * PAGES_PER_SECTOR; + +static const uint32_t BANK_SIZE = SECTOR_SIZE * SECTORS_NUM / 2; +static const uint32_t MEMORY_SIZE = SECTOR_SIZE * SECTORS_NUM; + +/** + * @brief Definitions for results of various flash operations. + * + * Definitions for result flags of various flash operations. + */ + +enum OpResultFlags +{ + RESULT_OK = 0x00, + + + RESULT_F_OUT_OF_MEMORY = 0x01, + RESULT_F_CHECK_FAIL = 0x04, + + + //Bits reserved for future use + RESULT_F_UNUSED_1 = 0x08, + RESULT_F_UNUSED_2 = 0x40, + RESULT_F_UNUSED_3 = 0x80, + + + /** + * These bits are in the same positions of the corresponding bits in the + * FLAG STATUS REGISTER + */ + RESULT_F_PROTECTION_ERROR = 0x02, + RESULT_F_PROGRAM_ERROR = 0x10, + RESULT_F_ERASE_ERROR = 0x20 +}; + +template<typename Bus> +class FlashDriver : Singleton<FlashDriver<Bus>> +{ + typedef Singleton<FlashDriver<Bus>> SingletonType; + + friend class Singleton<FlashDriver<Bus>>; + + template<typename > + friend class FlashDriverTest; + + template<typename > + friend class testing::flashmemorytests::FlashTest; + + public: + + ~FlashDriver() + { + delete fake_memory_; + } + + /** + * @brief Read n bytes into a buffer, starting from the specified address + * + * @param result + * @param address Starting address + * @param buf Buffer to read data into + * @param size Number of bytes to read + */ + void read(uint8_t *result, uint32_t address, uint8_t* buf, uint32_t size) + { + if (address + size > MEMORY_SIZE) + { + logger.error(logtag(), "Read outside of memory bounds addr + size: ", + address + size, " MEMSIZE: ", MEMORY_SIZE); + *result = RESULT_F_OUT_OF_MEMORY; + return; + } + + uint32_t rsize; + if (address < BANK_SIZE) + rsize = std::min(size, BANK_SIZE - address); + else + rsize = size; + + //uint8_t addr_buf[4]; + //addr_to_buf(addr_buf, address); + + fakeRead(address, buf, rsize); + //Bus::read(READ, addr_buf, buf, 4, rsize); + + size -= rsize; + + //This means that we reached the end of the first die but we still need + //to read some data. + if (size > 0) + { + address += rsize; + buf += rsize; + + //addr_to_buf(addr_buf, address); + + //Read the remaining bytes + fakeRead(address, buf, size); + //Bus::read(READ, addr_buf, buf, 4, size); + } + + *result = RESULT_OK; + } + + /** + * @brief Writes data on the flash starting from the specified address. + * + * @param address + * @param data + * @param size + */ + void write(uint8_t *result, uint32_t address, uint8_t* data, uint32_t size) + { + //Do not write outside of the memory + if (address + size > MEMORY_SIZE) + { + *result = RESULT_F_OUT_OF_MEMORY; + return; + } + + *result = RESULT_OK; + + //uint8_t addr_buf[4], status; + + uint32_t next_page, wsize; + + do + { + next_page = (address / 256) * 256 + 256; + + wsize = std::min(next_page - address, size); + + //addr_to_buf(addr_buf, address); + //_write_enable(); + + fakeWrite(address, data, wsize); + /*Bus::write(PROGRAM_PAGE, addr_buf, data, 4, wsize); + + do { + status = _read_flag_status_reg(); + }while((status & FSR_PRG_ERS_CTRL) == 0);*/ + + data += wsize; + size -= wsize; + address = next_page; + + } while (size > 0 && *result == RESULT_OK); + + /*//Clear flag status register if an error occurred + if(*result != RESULT_OK) + { + _clear_flag_status_reg(); + + //Manually reset write_enable latch + if((*result & RESULT_PROTECTION_ERROR) > 0) + { + _write_disable(); + } + }*/ + } + + /** + * @brief Writes data on the flash starting from the specified address, then + * checks if everything was written correctly. + * + * @param address + * @param data + * @param size + */ + void writeAndCheck(uint8_t *result, uint32_t address, uint8_t* data, + uint32_t size) + { //TODO: size_t + write(result, address, data, size); + if (*result == RESULT_OK) + { + uint8_t *check = new uint8_t[size]; + read(result, address, check, size); + for (uint32_t i = 0; i < size; i++) + { + if (*check++ != *data++) + { + //Something was not written/read correctly. + *result = RESULT_F_CHECK_FAIL; + break; + } + } + } + } + + /** + * @brief Writes data on the flash starting from the specified address, only + * if the provided data fits in a single page. + * + * Writes data on the flash starting from the specified address, only + * if the provided data fits in a single page. If too much data is provided + * nothing will be written and result will be set to FLASH_OUT_OF_RANGE. + * + * @param result + * @param address + * @param data + * @param size + */ + void programPage(uint8_t *result, uint32_t address, uint8_t* data, + uint32_t size) + { + /*//Do now wrap around a page and do not try to write outside of the memory + uint32_t next_page = (address / 256)*256 + 256; + if(address + size > next_page || address > MEMORY_SIZE) + { + *result = RESULT_OUT_OF_RNG; + return; + } + + uint8_t addr_buf[4], status; + addr_to_buf(addr_buf, address); + + _write_enable(); + Bus::write(PROGRAM_PAGE, addr_buf, data, 4, size); + + do { + status = _read_flag_status_reg(); + }while((status & FSR_PRG_ERS_CTRL) == 0); + + *result = status & (FSR_PROGRAM | FSR_PROTECTION); + + //Clear flag status register if an error has been encountered + if(*result != RESULT_OK) + { + _clear_flag_status_reg(); + + //Manually reset write_enable latch + if((*result & RESULT_PROTECTION_ERROR) > 0) + { + _write_disable(); + } + }*/ + } + + /** + * @brief Enables or disables read only mode + */ + void setReadOnly(bool readonly = true) + { + read_only_ = readonly; + } + + bool isReadOnly() const + { + return read_only_; + } + + /** + * @brief Enable the next erase operation. Call this before every erase op. + */ + void enableErase() + { + m_erase_enable_ = true; + } + + /** + * @brief Erases the subsector containing the specified address. For the operation + * to be successful, enable_erase_op must be called before this. + * + * @warning Estimated execution time is slightly less than 1 second. + * + * @param result + * @param address + */ + void eraseSubsector(uint8_t* result, uint32_t address) + { + erase(result, SUBSECTOR_ERASE, address); + } + + /** + * @brief Erases the sector containing the specified address. For the operation + * to be successful, enable_erase_op must be called before this. + * @warning Estimated execution time is about 2 seconds. + * + * @param result + * @param address + */ + void eraseSector(uint8_t* result, uint32_t address) + { + erase(result, SECTOR_ERASE, address); + } + + /** + * @brief Erases the entire die containing the specified address. For the operation + * to be successful, enable_erase_op must be called before this. + * @warning Estimated execution time is more than 4 minutes. + * + * @param result + * @param die: 0 to erase the first die, 1 to erase the second. + */ + void eraseDie(uint8_t* result, uint8_t die) + { + if (die == 0) + { + erase(result, DIE_ERASE, 0); + } else if (die == 1) + { + erase(result, DIE_ERASE, BANK_SIZE); + } + } + + /** + * Reads id information for this flash memory. + * @param buf Buffer with at least 20 bytes + */ + void readId(uint8_t* buf) + { + //Bus::read(READ_ID, buf, 20); + } + + /** + * @brief Software reset for the memory. All volatile bits are set to their + * default value. + */ + void softReset() + { + /*Bus::write(RESET_ENABLE); + usleep(5); + Bus::write(RESET_MEMORY); + _wait_until_ready();*/ + } + + private: + + FlashDriver() + { + printf("Mock memory size: %d\n", (int) MEMORY_SIZE); + memset((void*) (fake_memory_), 0xFF, MEMORY_SIZE); + } + + void erase(uint8_t *result, uint8_t erase_cmd, uint32_t address) + { + if (address > MEMORY_SIZE) + { + *result = RESULT_F_OUT_OF_MEMORY; + return; + } + + if (!m_erase_enable_) + { + *result = RESULT_F_ERASE_ERROR; + return; + } + + //uint8_t addr_buf[4], status; + //addr_to_buf(addr_buf, address); + *result = RESULT_OK; + switch (erase_cmd) + { + case SUBSECTOR_ERASE: + address = address / SUBSECTOR_SIZE * SUBSECTOR_SIZE; + fakeErase(address, SUBSECTOR_SIZE); + break; + case SECTOR_ERASE: + address = address / SECTOR_SIZE * SECTOR_SIZE; + fakeErase(address, SECTOR_SIZE); + break; + case DIE_ERASE: + address = address / BANK_SIZE * BANK_SIZE; + fakeErase(address, BANK_SIZE); + break; + default: + *result = RESULT_F_ERASE_ERROR; + } + + m_erase_enable_ = false; + } + + /** + * @brief Checks if a program operation is still in progress. + * @param result + */ + bool isBusy() + { + return false; //_read_flag_status_reg() & FSR_PRG_ERS_CTRL; + } + + /** + * @brief Waits until the memory is ready to perform a new write/erase op. + * @param result + */ + void waitUntilReady() + { + /*while((_read_flag_status_reg() & FSR_PRG_ERS_CTRL) == 0) + { + + }*/ + } + + uint8_t readStatusReg() + { + return 0; + /*uint8_t ret = Bus::read(READ_STATUS_REG); + return ret;*/ + } + + void writeStatusReg(uint8_t val) + { + /*_write_enable(); + Bus::write(WRITE_STATUS_REG, val); + _write_disable(); + _wait_until_ready();*/ + } + + uint8_t readFlagStatusReg() + { + /*uint8_t ret = Bus::read(READ_FLAG_STATUS_REG); + return ret;*/ + return 0; + } + + void clearFlagStatusReg() + { + //Bus::write(CLEAR_FLAG_STATUS_REG); + } + + uint16_t readConfigReg() + { + /*uint8_t buf[2]; + Bus::read(READ_NV_CONFIG_REG, buf, 2); + + uint16_t res = buf[0] | (buf[1] << 8);*/ + + return 0; + } + + void writeConfigReg(uint16_t val) + { + /* + * Mask some of the bits of the config register because + * we don't want to change them by accident. + */ + /*val = val | 0x0FEC; + + uint8_t buf[2]; + buf[0] = val & 0xFF; + buf[1] = (val >> 8) & 0xFF; + + _write_enable(); + Bus::write(WRITE_NV_CONFIG_REG, buf, 2); + _write_disable(); + _wait_until_ready();*/ + } + + void writeEnable() + { + /*if(!m_read_only) { + Bus::write(WRITE_ENABLE); + }*/ + } + + void writeDisable() + { + //Bus::write(WRITE_DISABLE); + } + + static void addrToBuf(uint8_t* buf, uint32_t addr) + { + for (int i = 0; i < 4; i++) + { + buf[3 - i] = (addr >> 8 * i) & 0xFF; + } + } + + void fakeRead(uint32_t address, uint8_t* data, uint32_t size) + { + //printf("Reading %d bytes from %d \n", (int)size, (int)address); + if (address + size > MEMORY_SIZE) + { + return; + } + + memcpy((void*) (data), (void*) (fake_memory_ + address), size); + } + + void fakeWrite(uint32_t address, uint8_t* data, uint32_t size) + { + if (address + size > MEMORY_SIZE) + { + return; + } + + for (uint32_t i = 0; i < size; i++) + { + fake_memory_[address + i] &= data[i]; + } + + //memcpy((void*)(fake_memory_ + address), (void*)(data), size); + } + + void fakeErase(uint32_t address, uint32_t size) + { + if (address + size > MEMORY_SIZE) + { + return; + } + + memset((void*) (fake_memory_ + address), 0xFF, size); + } + + static std::string logtag() + { + return "FlashDriver"; + } + + enum Commands + { + READ_ID = 0x9F, + + WRITE_ENABLE = 0x06, + WRITE_DISABLE = 0x04, + + READ_STATUS_REG = 0x05, + WRITE_STATUS_REG = 0x01, + + READ_FLAG_STATUS_REG = 0x70, + CLEAR_FLAG_STATUS_REG = 0x50, + + //non-volatile configuration register + READ_NV_CONFIG_REG = 0xB5, + WRITE_NV_CONFIG_REG = 0xB1, + + //volatile configuration register + WRITE_VOL_CONFIG_REG = 0x85, + READ_VOL_CONFIG_REG = 0x81, + + READ_EXT_ADDRESS_REG = 0xC8, + WRITE_EXT_ADDRESS_REG = 0xC5, + + RESET_ENABLE = 0x66, + RESET_MEMORY = 0x99, + + READ = 0x03, + PROGRAM_PAGE = 0x02, + + SUBSECTOR_ERASE = 0x20, + SECTOR_ERASE = 0xD8, + DIE_ERASE = 0xC4 + }; + + uint8_t* fake_memory_ = new uint8_t[MEMORY_SIZE]; + bool read_only_ = false; + bool m_erase_enable_ = false; + + /** + * Flag status register bit definitions + */ + static const uint8_t FSR_ADDRESSING_MODE = 0x01; + static const uint8_t FSR_PROTECTION = 0x02; + static const uint8_t FSR_PROGRAM_SUSPEND = 0x04; + static const uint8_t FSR_VPP = 0x08; + static const uint8_t FSR_PROGRAM = 0x10; + static const uint8_t FSR_ERASE = 0x20; + static const uint8_t FSR_ERASE_SUSPEND = 0x40; + static const uint8_t FSR_PRG_ERS_CTRL = 0x80; +}; + +} //namespace flashmemory + + +#endif /* FLASHDRIVER_H */ diff --git a/src/tests/drivers/test-ad7994-bare.cpp b/src/tests/drivers/old_examples/test-ad7994-bare.cpp similarity index 94% rename from src/tests/drivers/test-ad7994-bare.cpp rename to src/tests/drivers/old_examples/test-ad7994-bare.cpp index 6ff83b86cc6d9c15659cb656635a1bd328faed45..e5ab760a4f089eb5143b36ca2e9f46affe5b177b 100644 --- a/src/tests/drivers/test-ad7994-bare.cpp +++ b/src/tests/drivers/old_examples/test-ad7994-bare.cpp @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/drivers/test-ad7994.cpp b/src/tests/drivers/old_examples/test-ad7994.cpp similarity index 95% rename from src/tests/drivers/test-ad7994.cpp rename to src/tests/drivers/old_examples/test-ad7994.cpp index 93ccf70ca4281ea0c0e41fc6ce4cc1c498a05719..7fbd40b95a40c9580bd907f558e4f8abe50b6445 100644 --- a/src/tests/drivers/test-ad7994.cpp +++ b/src/tests/drivers/old_examples/test-ad7994.cpp @@ -1,5 +1,4 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry +/* Copyright (c) 2019 Skyward Experimental Rocketry * Authors: Luca Erbetta, Nuno Barcellos * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/drivers/test-i2c-mpu9255.cpp b/src/tests/drivers/old_examples/test-i2c-mpu9255.cpp similarity index 93% rename from src/tests/drivers/test-i2c-mpu9255.cpp rename to src/tests/drivers/old_examples/test-i2c-mpu9255.cpp index 21d4e7ff3346c0ec805df7b3e6130ac44584d9a7..6ee890ebd7f852d88a9e44a2ffaa03fe8a4cd1bb 100644 --- a/src/tests/drivers/test-i2c-mpu9255.cpp +++ b/src/tests/drivers/old_examples/test-i2c-mpu9255.cpp @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/drivers/test-imu-adis.cpp b/src/tests/drivers/old_examples/test-imu-adis.cpp similarity index 82% rename from src/tests/drivers/test-imu-adis.cpp rename to src/tests/drivers/old_examples/test-imu-adis.cpp index 52889cacb147e4563210b5c3d70aaa5ca3ea970b..4bdaf74114e6bf09d13dcc993033777e0205d3ab 100644 --- a/src/tests/drivers/test-imu-adis.cpp +++ b/src/tests/drivers/old_examples/test-imu-adis.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Nuno Barcellos + * Author: Nuno Barcellos * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -24,7 +24,7 @@ #include <drivers/BusTemplate.h> #include <interfaces-impl/hwmapping.h> #include <sensors/ADIS16405/ADIS16405.h> -#include <sensors/SensorSampling.h> +#include <sensors/SensorSampler.h> #include <math/Stats.h> #include <diagnostic/CpuMeter.h> #include <drivers/spi/SensorSpi.h> @@ -38,31 +38,27 @@ typedef Gpio<GPIOD_BASE, 5> rstPin; // PD5 for the HomeoneBoard // SPI1 binding to the sensor typedef BusSPI<1,spi1::mosi,spi1::miso,spi1::sck> busSPI1; //Create SPI1 typedef ProtocolSPI<busSPI1,miosix::sensors::adis16405::cs> spiADIS16405; //La lego al Chip Select 1 per la IMU 1 -typedef ADIS16405<spiADIS16405,rstPin> adis_t; //Passo il bus creato al sensore int main() { spiADIS16405::init(); Thread::sleep(1000); - adis_t adis(adis_t::GYRO_FS_300); + ADIS16405<spiADIS16405,rstPin>* adis = new ADIS16405<spiADIS16405,rstPin>(adis->GYRO_FS_300); - if(adis.init()) + if(adis->init()) printf("[ADIS16405] Init succeeded\n" ); else printf("[ADIS16405] Init failed\n"); - if(adis.selfTest()) + if(adis->selfTest()) printf("[ADIS16405] Self test succeeded\n" ); else printf("[ADIS16405] Self test failed\n"); - SimpleSensorSampler sampler; - sampler.AddSensor(&adis); - - // DMASensorSampler sampler; - // sampler.AddSensor(&adis); + SimpleSensorSampler sampler(250, 1); + sampler.addSensor(adis, std::bind([&]() {})); StatsResult statResult; Stats stats; @@ -70,7 +66,7 @@ int main() int counter = 0; while(true) { - sampler.Update(); + sampler.sampleAndCallback(); stats.add(averageCpuUtilization()); @@ -79,7 +75,7 @@ int main() printf("CPU usage: %f\n", statResult.mean); counter = 0; - const Vec3* last_data = adis.accelDataPtr(); + const Vec3* last_data = adis->accelDataPtr(); printf("%f %f %f\n", last_data->getX(), last_data->getY(), last_data->getZ()); } diff --git a/src/tests/drivers/calibrate-mpu9250.cpp b/src/tests/drivers/old_examples/test-lsm.cpp similarity index 52% rename from src/tests/drivers/calibrate-mpu9250.cpp rename to src/tests/drivers/old_examples/test-lsm.cpp index 54467bace1bc84ba09b93fb321a99e2be543eaea..1dec3957b04706945c38a2eb653d7a85e22e84f8 100644 --- a/src/tests/drivers/calibrate-mpu9250.cpp +++ b/src/tests/drivers/old_examples/test-lsm.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Nuno Barcellos + * Author: Nuno Barcellos * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -23,69 +23,58 @@ #include <Common.h> #include <drivers/BusTemplate.h> #include <interfaces-impl/hwmapping.h> -#include <sensors/MPU9250/MPU9250.h> +#include <sensors/LSM6DS3H/LSM6DS3H.h> #include <drivers/spi/SensorSpi.h> -#include <sensors/SensorSampling.h> +#include <sensors/SensorSampler.h> +#include <math/Stats.h> +#include <diagnostic/CpuMeter.h> using namespace miosix; using namespace miosix::interfaces; +/* SPI1 binding to the sensor */ typedef BusSPI<1,spi1::mosi,spi1::miso,spi1::sck> busSPI1; -typedef ProtocolSPI<busSPI1,sensors::mpu9250::cs> spiMPU9250_a; -typedef MPU9250<spiMPU9250_a> mpu_t; +typedef ProtocolSPI<busSPI1,sensors::lsm6ds3h::cs> spiLSM6DS3H0_a; int main() { - float bias[3]; + SimpleSensorSampler sampler(250, 1); + spiLSM6DS3H0_a::init(); - SimpleSensorSampler sampler; - - spiMPU9250_a::init(); - mpu_t* mpu = new mpu_t(1, 1); - - Thread::sleep(100); + LSM6DS3H<spiLSM6DS3H0_a>* lsm6ds3h = new LSM6DS3H<spiLSM6DS3H0_a>(3,3); - if(mpu->init()){ - printf("MPU9250 Init succeeded\n" ); - sampler.AddSensor(mpu); + if(lsm6ds3h->init()) + { + printf("[LSM6DS3H] Init succeeded\n" ); + sampler.addSensor(lsm6ds3h, std::bind([&]() {})); } - else { - printf("MPU9250 Init failed\n"); + else + { + printf("[LSM6DS3H] Init failed\n"); - while(!mpu->init()) { - printf("MPU9250 Init failed\n"); + while(!lsm6ds3h->init()) { + printf("[LSM6DS3H] Init failed\n"); Thread::sleep(1000); } - } - - Thread::sleep(100); - printf("This script calculates the accelerometer's offsets, follow the instructions:\n"); - printf("Leave the MPU9250 pointing up (aligned with the gravity vector) and steady, then press ENTER "); - getchar(); - mpu->calibrateAccel(); - - printf("Calibration succeeded\n"); - printf("Copy the following parameters into the MPU9250 class constructor: \n\n"); - - mpu->getAccelCalibParams(bias); - printf("_axb = %f;\n",bias[0]); - printf("_ayb = %f;\n",bias[1]); - printf("_azb = %f;\n",bias[2]); - - printf("Press ENTER to read the accelerometer data and to check if the calibration is good\n"); - getchar(); + } - mpu->setAccelCalibParams(bias); while(true) { - sampler.Update(); + sampler.sampleAndCallback(); + + // const Vec3* last_data = lsm6ds3h->gyroDataPtr(); + // printf("%f %f %f\n", last_data->getX(), last_data->getY(), + // last_data->getZ()); - const Vec3* last_data = mpu->accelDataPtr(); + const Vec3* last_data = lsm6ds3h->accelDataPtr(); printf("%f %f %f\n", last_data->getX(), last_data->getY(), last_data->getZ()); + + // const float* last_temp = lsm6ds3h->tempDataPtr(); + // printf("temp: %f\n", *last_temp); Thread::sleep(100); } diff --git a/src/tests/drivers/test-spi2.cpp b/src/tests/drivers/old_examples/test-spi2.cpp similarity index 89% rename from src/tests/drivers/test-spi2.cpp rename to src/tests/drivers/old_examples/test-spi2.cpp index 6a87562ff3e1c991b635c5e5ceedf6a9c3d6942b..212607b8d6f012ba3c611296d4ab47a6eda49ac9 100644 --- a/src/tests/drivers/test-spi2.cpp +++ b/src/tests/drivers/old_examples/test-spi2.cpp @@ -1,20 +1,19 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta - * +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * * 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 + * 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 diff --git a/src/tests/drivers/test-tempSensor.cpp b/src/tests/drivers/old_examples/test-tempSensor.cpp similarity index 94% rename from src/tests/drivers/test-tempSensor.cpp rename to src/tests/drivers/old_examples/test-tempSensor.cpp index aba103075430985174ee64c8178afddba3a1a64e..5a954a68b7aff2d063551312d1c4287c955904d9 100644 --- a/src/tests/drivers/test-tempSensor.cpp +++ b/src/tests/drivers/old_examples/test-tempSensor.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: + * Author: Alessio Galluccio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/catch/test-unit-LM75B.cpp b/src/tests/drivers/old_examples/test-unit-LM75B.cpp similarity index 93% rename from src/tests/catch/test-unit-LM75B.cpp rename to src/tests/drivers/old_examples/test-unit-LM75B.cpp index f3f95248884ff9a694f4ea4a132f4b061635d983..5db478695da58d0b2226dad7248ab3da63f9913e 100644 --- a/src/tests/catch/test-unit-LM75B.cpp +++ b/src/tests/drivers/old_examples/test-unit-LM75B.cpp @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/drivers/test-IMU.cpp b/src/tests/drivers/test-IMU.cpp deleted file mode 100644 index 06e5aadd8972947e504ddfc34942c0c385c692ce..0000000000000000000000000000000000000000 --- a/src/tests/drivers/test-IMU.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include <sensors/LSM6DS3H.h> - -// SPI1 -typedef miosix::Gpio<GPIOA_BASE, 5> GpioSck; -typedef miosix::Gpio<GPIOA_BASE, 6> GpioMiso; -typedef miosix::Gpio<GPIOA_BASE, 7> GpioMosi; -typedef miosix::Gpio<GPIOC_BASE, 3> CS_MPU9250; - -// SPI1 binding al sensore -typedef BusSPI<1, GpioMosi, GpioMiso, GpioSck> busSPI1; // Creo la SPI1 -typedef ProtocolSPI<busSPI1, CS_MPU9250> - spiProt; // La lego al Chip Select 1 per la IMU 1 -typedef LSM6DS3H<spiProt> lsm_t; // Passo il bus creato al sensore - -int main (){ - lsm_t* lsm = new lsm_t(16, 500); - UNUSED(lsm); - while(1) - { - - } -} \ No newline at end of file diff --git a/src/tests/drivers/test-ads1118.cpp b/src/tests/drivers/test-ads1118.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eac7b6819722daf969b6818cbdacd1b5efd73edd --- /dev/null +++ b/src/tests/drivers/test-ads1118.cpp @@ -0,0 +1,167 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +/** + * @file test-ads1118.cpp + * @author Alberto Nidasio (alberto.nidasio@skywarder.eu) + * @brief Tests the ads1118 adc + * @version 1.0 + * @date 2020-11-19 + * + * This test has been setup for the following configuration: + * + * SPI pheripheral 2 (SPI2) with /32 divider + * + * Pins (STM32F407 - ADS1118): + * PB12 (NSS) - NC (we use pin C1 as chip select) + * PB13 (SCK) - SCK + * PB14 (MISO) - DOUT + * PB15 (MOSI) - DIN + * PC1 - CS + * + * The ADS1118's input channels can be connected as follow: + * AIN2 - GND + * AIN3 - VCC (3V) + * + * In the developing test a function generator was used as variable source + */ + +#include <Debug.h> +#include <drivers/adc/ADS1118/ADS1118.h> +#include <drivers/spi/SPIDriver.h> +#include <miosix.h> + +#include "TimestampTimer.h" + +GpioPin sckPin = GpioPin(GPIOB_BASE, 13); +GpioPin misoPin = GpioPin(GPIOB_BASE, 14); +GpioPin mosiPin = GpioPin(GPIOB_BASE, 15); +GpioPin csPin = GpioPin(GPIOC_BASE, 1); + +constexpr ADS1118::ADS1118Mux channel1 = ADS1118::ADS1118Mux::MUX_AIN2_GND; +constexpr ADS1118::ADS1118Mux channel2 = ADS1118::ADS1118Mux::MUX_AIN3_GND; + +void initBoard() +{ + // Enable SPI clock for SPI2 interface + RCC->APB1ENR |= RCC_APB1ENR_SPI2EN; + + // Alternate function configuration for SPI pins + sckPin.mode(miosix::Mode::ALTERNATE); + sckPin.alternateFunction(5); // SPI function + mosiPin.mode(miosix::Mode::ALTERNATE); + mosiPin.alternateFunction(5); // SPI function + misoPin.mode(miosix::Mode::ALTERNATE); + misoPin.alternateFunction(5); // SPI function + + // Chip select pin as output starting high + csPin.mode(miosix::Mode::OUTPUT); + csPin.high(); +} + +int main() +{ + // Enable SPI clock and set gpios + initBoard(); + + TimestampTimer::enableTimestampTimer(); + + // SPI configuration setup + SPIBusConfig spiConfig = ADS1118::getDefaultSPIConfig(); + SPIBus spiBus(SPI2); + SPISlave spiSlave(spiBus, csPin, spiConfig); + + // Device initialization + ADS1118 ads1118(spiSlave); + + // Initialize the device + ads1118.init(); + + // Enable the two channels and the temperature + ads1118.enableInput(channel1, ADS1118::ADS1118DataRate::DR_250, + ADS1118::ADS1118Pga::FSR_4_096); + ads1118.enableInput(channel2, ADS1118::ADS1118DataRate::DR_250, + ADS1118::ADS1118Pga::FSR_4_096); + ads1118.enableTemperature(); + + // Self test + if (ads1118.selfTest()) + { + TRACE("Self test successful!\n"); + } + else + { + TRACE("Self test failed :( error: %d\n", ads1118.getLastError()); + } + + // Read the two channels indipendently + TRACE("\nNow reading the two channels with 'readInputAndWait()'\n"); + TRACE("Channel 1: %f\n", ads1118.readInputAndWait(channel1).voltage); + TRACE("Channel 2: %f\n", ads1118.readInputAndWait(channel2).voltage); + + // Read samples with sample() + TRACE("\nNow reading the two channels with 'sample()'\n"); + for (auto i = 0; i < 500; i++) + { + // Call 3 times sample() to read the 3 enabled channels + ads1118.sample(); + miosix::Thread::sleep(5); + ads1118.sample(); + miosix::Thread::sleep(5); + ads1118.sample(); + + printf("%.2f\t%.2f\t%.2f\n", ads1118.getTemperature().temp, + ads1118.getVoltage(channel1).voltage, + ads1118.getVoltage(channel2).voltage); + } + + // Read a few times the first channel and then switch to the second one + TRACE( + "\nNow reading the first channel and then the second one with " + "'sample()'\n"); + + ads1118.disableAllInputs(); + ads1118.enableInput(channel1, ADS1118::ADS1118DataRate::DR_250, + ADS1118::ADS1118Pga::FSR_4_096); + + TRACE("First channel:\n"); + for (auto i = 0; i < 500; i++) + { + ads1118.sample(); + miosix::Thread::sleep(5); + + printf("%.2f\n", ads1118.getVoltage(channel1).voltage); + } + + ads1118.disableAllInputs(); + ads1118.enableInput(channel2, ADS1118::ADS1118DataRate::DR_250, + ADS1118::ADS1118Pga::FSR_4_096); + + TRACE("Second channel:\n"); + for (auto i = 0; i < 500; i++) + { + ads1118.sample(); + miosix::Thread::sleep(5); + + printf("%.2f\n", ads1118.getVoltage(channel2).voltage); + } +} \ No newline at end of file diff --git a/src/tests/drivers/test-bme280.cpp b/src/tests/drivers/test-bme280.cpp index c0bf9235984e8c185e02c6e10a14b078241e0906..e9db90a0cafd0612d42ede2b5cebfd01c13f458b 100644 --- a/src/tests/drivers/test-bme280.cpp +++ b/src/tests/drivers/test-bme280.cpp @@ -1,52 +1,127 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta - * +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * * 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 + * 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. */ -#include <sensors/BME280/BME280.h> +/** + * This test has been setup for the following configuration: + * + * SPI pheripheral 2 (SPI2) with /32 divider + * + * Pins (STM32F407 - BME280): + * PB12 (NSS) - not connected (we use pin C1 as chip select) + * PB13 (SCK) - SCK + * PB14 (MISO) - SDO + * PB15 (MOSI) - SDA + * PC1 - CBS + * + * The BME280 is powered by 3.3V + * + * In the developing test a function generator was used as variable source + */ + +#include <Debug.h> +#include <drivers/spi/SPIDriver.h> #include <miosix.h> -#include <drivers/BusTemplate.h> +#include <sensors/BME280/BME280.h> -// SPI1 -typedef miosix::Gpio<GPIOA_BASE, 5> GpioSck; -typedef miosix::Gpio<GPIOB_BASE, 4> GpioMiso; -typedef miosix::Gpio<GPIOA_BASE, 7> GpioMosi; -typedef miosix::Gpio<GPIOC_BASE, 1> CS; +#include "TimestampTimer.h" -// SPI1 binding al sensore -typedef BusSPI<1, GpioMosi, GpioMiso, GpioSck> busSPI1; // Creo la SPI1 -typedef ProtocolSPI<busSPI1, CS> spiProt; // La lego al Chip Select 1 per la IMU 1 +GpioPin sckPin = GpioPin(GPIOB_BASE, 13); +GpioPin misoPin = GpioPin(GPIOB_BASE, 14); +GpioPin mosiPin = GpioPin(GPIOB_BASE, 15); +GpioPin csPin = GpioPin(GPIOC_BASE, 1); -//typedef BME280<spiProt> bme_t; // Passo il bus creato al sensore +void initBoard() +{ + // Enable SPI clock for SPI2 interface + RCC->APB1ENR |= RCC_APB1ENR_SPI2EN; -int main () + // Alternate function configuration for SPI pins + sckPin.mode(miosix::Mode::ALTERNATE); + sckPin.alternateFunction(5); // SPI function + mosiPin.mode(miosix::Mode::ALTERNATE); + mosiPin.alternateFunction(5); // SPI function + misoPin.mode(miosix::Mode::ALTERNATE); + misoPin.alternateFunction(5); // SPI function + + // Chip select pin as output starting high + csPin.mode(miosix::Mode::OUTPUT); + csPin.high(); +} + +int main() { - BME280<spiProt> bme; - bme.init(); - - while(1) + // Enable SPI clock and set gpios + initBoard(); + + TimestampTimer::enableTimestampTimer(); + + // SPI configuration setup + SPIBusConfig spiConfig; + spiConfig.clock_div = SPIClockDivider::DIV32; + spiConfig.mode = SPIMode::MODE0; + SPIBus spiBus(SPI2); + SPISlave spiSlave(spiBus, csPin, spiConfig); + + // Device initialization + BME280 bme280(spiSlave); + + bme280.init(); + + // In practice the self test reads the who am i reagister, this is already + // done in init() + if (!bme280.selfTest()) { - bme.onSimpleUpdate(); - printf("%d,%f,%f\n", bme.getDataPtr()->timestamp, bme.getDataPtr()->temperature, bme.getDataPtr()->pressure); + TRACE("Self test failed!\n"); + + return -1; + } + + // Try forced mode + TRACE("Forced mode\n"); + for (int i = 0; i < 10; i++) + { + bme280.setSensorMode(BME280::FORCED_MODE); + + miosix::Thread::sleep(bme280.getMaxMeasurementTime()); + + bme280.sample(); + + TRACE("temp: %.2f DegC\tpress: %.2f hPa\thumid: %.2f %%RH\n", + bme280.getLastSample().temp, bme280.getLastSample().press, + bme280.getLastSample().humid); + + miosix::Thread::sleep(1000); + } + + TRACE("Normal mode\n"); + bme280.setSensorMode(BME280::NORMAL_MODE); + while (true) + { + bme280.sample(); + + TRACE("temp: %.2f DegC\tpress: %.2f hPa\thumid: %.2f %%RH\n", + bme280.getLastSample().temp, bme280.getLastSample().press, + bme280.getLastSample().humid); - miosix::Thread::sleep(500); + miosix::Thread::sleep(40); // 25Hz } } \ No newline at end of file diff --git a/src/tests/drivers/test-bmx160-with-correction.cpp b/src/tests/drivers/test-bmx160-with-correction.cpp new file mode 100644 index 0000000000000000000000000000000000000000..499fbd6a3d2524a8e606699b617343376f06460f --- /dev/null +++ b/src/tests/drivers/test-bmx160-with-correction.cpp @@ -0,0 +1,176 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Riccardo Musso, Alberto Nidasio + * + * 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. + */ + +#include <Common.h> +#include <drivers/HardwareTimer.h> +#include <drivers/interrupt/external_interrupts.h> +#include <sensors/BMX160/BMX160.h> +#include <sensors/BMX160/BMX160WithCorrection.h> + +constexpr const char *CORRECTION_PARAMETER_FILE = "/sd/bmx160_params.csv"; + +constexpr int UPDATE_RATE = 5; // Hz +bool stopSamplingThread = false; + +BMX160 *bmx160 = nullptr; + +// BMX160 Watermark interrupt +void __attribute__((used)) EXTI5_IRQHandlerImpl() +{ + if (bmx160) + { + bmx160->IRQupdateTimestamp(TimestampTimer::getTimestamp()); + } +} + +void bmx160Sample(void *args); + +int main() +{ + TimestampTimer::enableTimestampTimer(); + + // Enable interrupt from BMX pin + enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE); + + SPIBus bus(SPI1); + + BMX160Config bmx_config; + bmx_config.fifo_mode = BMX160Config::FifoMode::HEADER; + bmx_config.fifo_watermark = 500 / 4; + bmx_config.fifo_int = BMX160Config::FifoInterruptPin::PIN_INT1; + + bmx_config.temp_divider = 0; + + bmx_config.acc_range = BMX160Config::AccelerometerRange::G_16; + bmx_config.gyr_range = BMX160Config::GyroscopeRange::DEG_2000; + + bmx_config.acc_odr = BMX160Config::OutputDataRate::HZ_1600; + bmx_config.gyr_odr = BMX160Config::OutputDataRate::HZ_1600; + bmx_config.mag_odr = BMX160Config::OutputDataRate::HZ_50; + + bmx_config.gyr_unit = BMX160Config::GyroscopeMeasureUnit::RAD; + + bmx160 = new BMX160(bus, miosix::sensors::bmx160::cs::getPin(), bmx_config); + + // Read the correction parameters + BMX160CorrectionParameters correctionParameters = + BMX160WithCorrection::readCorrectionParametersFromFile( + CORRECTION_PARAMETER_FILE); + + // Show the user the current correction values + printf("\nCurrent bias vector\n"); + printf("b = [ % 2.5f % 2.5f % 2.5f ]\n\n", + correctionParameters.accelParams(0, 1), + correctionParameters.accelParams(1, 1), + correctionParameters.accelParams(2, 1)); + printf("Matrix to be multiplied to the input vector\n"); + printf(" | % 2.5f % 2.5f % 2.5f |\n", + correctionParameters.accelParams(0, 0), 0.f, 0.f); + printf("M = | % 2.5f % 2.5f % 2.5f |\n", 0.f, + correctionParameters.accelParams(1, 0), 0.f); + printf(" | % 2.5f % 2.5f % 2.5f |\n", 0.f, 0.f, + correctionParameters.accelParams(2, 0)); + + BMX160WithCorrection bmx160WithCorrection( + bmx160, correctionParameters, + {Direction::NEGATIVE_Z, Direction::NEGATIVE_Y}); + + TRACE("Initializing BMX160...\n"); + + if (!bmx160->init()) + { + TRACE("Init failed! (code: %d)\n", bmx160->getLastError()); + return -1; + } + + TRACE("Performing self-test...\n"); + + if (!bmx160->selfTest()) + { + TRACE("Self-test failed! (code: %d)\n", bmx160->getLastError()); + return -1; + } + + TRACE("Initialization and self-test completed!\n"); + + // Calibrate the sensor + miosix::Thread *samplingThread = + miosix::Thread::create(bmx160Sample, 2048, miosix::MAIN_PRIORITY, + nullptr, miosix::Thread::JOINABLE); + if (!bmx160WithCorrection.calibrate()) + { + TRACE("Calibration failed!\n"); + return -1; + } + stopSamplingThread = true; + samplingThread->join(); + TRACE("Calibration completed\n"); + + while (1) + { + bmx160->sample(); + bmx160WithCorrection.sample(); + + BMX160Data data = bmx160->getLastSample(); + BMX160WithCorrectionData correctedData = + bmx160WithCorrection.getLastSample(); + + printf("\n"); + + printf("[Raw] "); + printf("Mag: %7.3f %7.3f %7.3f", data.mag_x, data.mag_y, data.mag_z); + printf("\tGyr: %7.3f %7.3f %7.3f", data.gyro_x, data.gyro_y, + data.gyro_z); + printf("\tAcc: %7.3f %7.3f %7.3f\n", data.accel_x, data.accel_y, + data.accel_z); + + printf("[Corrected] "); + printf("Mag: %7.3f %7.3f %7.3f", correctedData.mag_x, + correctedData.mag_y, correctedData.mag_z); + printf("\tGyr: %7.3f %7.3f %7.3f", correctedData.gyro_x, + correctedData.gyro_y, correctedData.gyro_z); + printf("\tAcc: %7.3f %7.3f %7.3f\n", correctedData.accel_x, + correctedData.accel_y, correctedData.accel_z); + + miosix::Thread::sleep(1000 / UPDATE_RATE); + } + + return 0; +} + +void bmx160Sample(void *args) +{ + UNUSED(args); + + while (!stopSamplingThread) + { + // Sample the bmx160 + bmx160->sample(); + if (bmx160->getLastError() != SensorErrors::NO_ERRORS) + { + TRACE("Failed to read data! (error: %d)\n", bmx160->getLastError()); + continue; + } + + miosix::Thread::sleep(1000 / UPDATE_RATE); + } +} \ No newline at end of file diff --git a/src/tests/drivers/test-bmx160.cpp b/src/tests/drivers/test-bmx160.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a1c0372e497eb0a2b47e059f11448ad7be12f17c --- /dev/null +++ b/src/tests/drivers/test-bmx160.cpp @@ -0,0 +1,125 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Davide Mor + * + * 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. + */ + +#include <Common.h> +#include <drivers/HardwareTimer.h> +#include <drivers/interrupt/external_interrupts.h> +#include <sensors/BMX160/BMX160.h> + +SPIBus bus(SPI1); +GpioPin cs(GPIOA_BASE, 8); + +GpioPin spi_sck(GPIOA_BASE, 5); +GpioPin spi_miso(GPIOA_BASE, 6); +GpioPin spi_mosi(GPIOA_BASE, 7); + +BMX160 *sensor = nullptr; +uint32_t tick = 0; + +void __attribute__((used)) EXTI5_IRQHandlerImpl() +{ + tick = TimestampTimer::getTimestamp(); + if (sensor) + { + sensor->IRQupdateTimestamp(tick); + } +} + +int main() +{ + TimestampTimer::enableTimestampTimer(); + + cs.high(); + + enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE); + + BMX160Config config; + config.fifo_mode = BMX160Config::FifoMode::HEADER; + config.fifo_int = BMX160Config::FifoInterruptPin::PIN_INT1; + config.fifo_watermark = 100; + config.temp_divider = 1; + + sensor = new BMX160(bus, cs, config); + + TRACE("Initializing BMX160...\n"); + + if (!sensor->init()) + { + TRACE("Init failed! (code: %d)\n", sensor->getLastError()); + while (1) + { + } + return -1; + } + + TRACE("Performing self-test...\n"); + + if (!sensor->selfTest()) + { + TRACE("Self-test failed! (code: %d)\n", sensor->getLastError()); + return -1; + } + + TRACE("Self-test successful!\n"); + + while (1) + { + miosix::delayUs(250 * 1000); + + printf("----------------------------\n"); + + sensor->sample(); + if (sensor->getLastError() != SensorErrors::NO_ERRORS) + { + TRACE("Failed to read data!\n"); + continue; + } + + uint64_t now = TimestampTimer::getTimestamp(); + + printf("Tick: %.4f s, Now: %.4f s\n", tick / 1000000.0f, + now / 1000000.0f); + printf("Temp: %.2f deg\n", sensor->getTemperature().temp); + printf("Fill: %d\n", sensor->getLastFifoSize()); + + printf("----------------------------\n"); + uint8_t len = std::min(sensor->getLastFifoSize(), (uint8_t)5); + + for (uint8_t i = 0; i < len; i++) + { + BMX160Data data = sensor->getFifoElement(i); + printf("Mag [%.4f s]:\t%.2f\t%.2f\t%.2f\n", + data.mag_timestamp / 1000000.0f, data.mag_x, data.mag_y, + data.mag_z); + + printf("Gyr [%.4f s]:\t%.2f\t%.2f\t%.2f\n", + data.gyro_timestamp / 1000000.0f, data.gyro_x, data.gyro_y, + data.gyro_z); + + printf("Acc [%.4f s]:\t%.2f\t%.2f\t%.2f\n", + data.accel_timestamp / 1000000.0f, data.accel_x, + data.accel_y, data.accel_z); + } + } + + return 0; +} \ No newline at end of file diff --git a/src/tests/drivers/test-canbus.cpp b/src/tests/drivers/test-canbus.cpp index 4f303017e52a13682b98608e59601857a07631cf..ff3afa46059416c08d3d71d87a3d73640ae7676c 100644 --- a/src/tests/drivers/test-canbus.cpp +++ b/src/tests/drivers/test-canbus.cpp @@ -1,3 +1,26 @@ +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Alvise de'Faveri Tron + * + * 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. + */ + + #include <Common.h> #include <drivers/canbus/CanManager.h> #include <drivers/canbus/CanUtils.h> diff --git a/src/tests/drivers/test-dsgamma.cpp b/src/tests/drivers/test-dsgamma.cpp index 5ff13c1baaaf45e57e67a4eb0b1f19bfada0b0dd..1c84aa7a97f81ac89139fc1213f4be2c220f7ce1 100644 --- a/src/tests/drivers/test-dsgamma.cpp +++ b/src/tests/drivers/test-dsgamma.cpp @@ -1,3 +1,25 @@ +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Authors: Alain Carlucci, Alvise de'Faveri Tron, Luca Erbetta + * + * 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. + */ + #include <stdio.h> #include <string.h> #include "Common.h" @@ -16,7 +38,6 @@ using miosix::Gpio; /* DISCOVERY F429I*/ typedef Gpio<GPIOA_BASE, 0> button; -typedef HardwareTimer<uint32_t, 4> Clock; // RTT calculation // long long sendTime = 0; enum State diff --git a/src/tests/drivers/test-hbridge.cpp b/src/tests/drivers/test-hbridge.cpp new file mode 100644 index 0000000000000000000000000000000000000000..da00dea208bc397b9c932855dd0626d3ca781e92 --- /dev/null +++ b/src/tests/drivers/test-hbridge.cpp @@ -0,0 +1,130 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Authors: Luca Erbetta, Luca Conterio + * + * 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. + */ + +#include <drivers/HardwareTimer.h> +#include <miosix.h> + +#include <iostream> +#include <sstream> + +#include "Common.h" +#include "drivers/hbridge/HBridge.h" + +using namespace miosix; +using namespace std; + +static constexpr int PWM_DURATION = 60 * 1000; + +static const PWM::Timer HBRIDGE_TIM{ + TIM3, &(RCC->APB1ENR), RCC_APB1ENR_TIM3EN, + TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB2)}; + +static const PWMChannel HBRIDGE_PWM_CHANNEL = PWMChannel::CH2; + +GpioPin hbridge_in(GPIOB_BASE, 5); // pwm pin +GpioPin hbridge_inhibit(GPIOB_BASE, 7); // inhibit pin for the hbridge + +bool print = true; // print the elapsed time or not + +long long measured_time = 0; +void wait() +{ + long long t = getTick(); + long long t0 = t; + + while(t < t0 + PWM_DURATION) + { + Thread::sleep(50); + + t = getTick(); + + if (print) + { + TRACE("Elapsed time : %.2f \n", (t - t0) / 1000.0); + } + } + + measured_time = t - t0; +} + +int main() +{ + { + FastInterruptDisableLock l; + hbridge_in.mode(Mode::ALTERNATE); + hbridge_in.alternateFunction(2); + hbridge_inhibit.mode(Mode::OUTPUT); + hbridge_inhibit.low(); + } + + TimestampTimer::enableTimestampTimer(); + + for (;;) + { + uint32_t freq = 0; + float duty = 0; + string temp; + + do + { + cout << "Insert h-bridge frequency (1-30000 Hz): \n"; + getline(cin, temp); + stringstream(temp) >> freq; + + cout << "Insert h-bridge duty cycle (0-100 %): \n"; + getline(cin, temp); + stringstream(temp) >> duty; + + if (!(freq >= 1 && freq <= 30000 && duty >= 0.0f && duty <= 100.0f)) + { + TRACE("Invalid inputs!\n"); + } + } while ( + !(freq >= 1 && freq <= 30000 && duty >= 0.0f && duty <= 100.0f)); + + do + { + cout << "READY!\nWrite 'yeet' to begin:\n"; + getline(cin, temp); + } while (temp != "yeet"); + + HBridge hbridge(hbridge_inhibit, HBRIDGE_TIM, HBRIDGE_PWM_CHANNEL, freq, + duty / 100); + + hbridge.enable(); + TRACE("Hbridge status : timestamp = %llu - state = %d \n", + hbridge.getStatus().timestamp, hbridge.getStatus().state); + + wait(); + + hbridge.disable(); + TRACE("Hbridge status : timestamp = %llu - state = %d \n", + hbridge.getStatus().timestamp, hbridge.getStatus().state); + + Thread::sleep(500); + + TRACE("Elapsed time: %.2f s\n", (measured_time) / 1000.0f); + TRACE("Done!\n\n"); + } + + return 0; +} diff --git a/src/tests/drivers/test-hwtimer-chain.cpp b/src/tests/drivers/test-hwtimer-chain.cpp new file mode 100644 index 0000000000000000000000000000000000000000..de0259136c827d1189280a335391e9422dead78c --- /dev/null +++ b/src/tests/drivers/test-hwtimer-chain.cpp @@ -0,0 +1,61 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Davide Mor + * + * 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. + */ + +#include <drivers/HardwareTimer.h> +#include <Common.h> + +int main() +{ + { + miosix::FastInterruptDisableLock dLock; + // Enable TIM2 + TIM3 + TIM4 peripheral clock + RCC->APB1ENR |= RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM4EN; + } + + uint32_t prescaler = TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1); + TRACE("Prescaler: %d\n", prescaler); + + HardwareTimer<uint32_t, TimerMode::Chain> timer1(TIM3, TIM4, + TimerTrigger::ITR2, + prescaler); + + + HardwareTimer<uint32_t, TimerMode::Single> timer2(TIM2, prescaler); + + timer1.start(); + timer2.start(); + + while(true) { + miosix::Thread::sleep(1000); + + uint32_t tick1 = timer1.tick(); + uint32_t tick2 = timer2.tick(); + + TRACE("Timer1: %f\n", timer1.toMilliSeconds(tick1)); + TRACE("Timer2: %f\n", timer2.toMilliSeconds(tick1)); + + // Delta should remain constant + TRACE("Delta: %d\n", tick1 - tick2); + } + + return 0; +} \ No newline at end of file diff --git a/src/tests/drivers/test-internal-adc-dma.cpp b/src/tests/drivers/test-internal-adc-dma.cpp new file mode 100644 index 0000000000000000000000000000000000000000..01b3a984cde868f338a1b57e73e209c487298a52 --- /dev/null +++ b/src/tests/drivers/test-internal-adc-dma.cpp @@ -0,0 +1,126 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#include <drivers/adc/InternalADC/InternalADC.h> +#include <miosix.h> + +ADC_TypeDef& ADCx = *ADC3; +DMA_Stream_TypeDef* DMAx_Streamx = DMA2_Stream1; + +int main() +{ + + // DMA INIT --------------------------------------------------------------- + { + // 1: Disable the stream + + // Disable stream + DMAx_Streamx->CR &= ~DMA_SxCR_EN; + + // Wait for the stream to be disabled + while (DMAx_Streamx->CR & DMA_SxCR_EN) + ; + + // Reset DMA configuration + DMAx_Streamx->CR = 0; + + // Ensures all the status bits are cleared by explicitly clearing them + DMA2->LIFCR = 0x0F7D0F7D; // Mask excluding reserved bits + DMA2->HIFCR = 0x0F7D0F7D; + + // 2-4: The memory and peripheral addresses, also the number of data + // items will be set by the driver + + // 5: Select the DMA channel + DMAx_Streamx->CR |= DMA_SxCR_CHSEL_1; // <- CHANGE THIS! + + // 6: The peripheral is not intended to be the flow controller + // (it will function only as a trigger) + DMAx_Streamx->CR &= ~DMA_SxCR_PFCTRL; + + // 7: Configure the stream priority to very high + DMAx_Streamx->CR |= DMA_SxCR_PL; + + // 8: Configure fifo usage (disable => direct mode) + DMAx_Streamx->FCR &= ~DMA_SxFCR_DMDIS; + + // 9: Other configuration + + // Data transfer peripheral-to-memory + DMAx_Streamx->CR &= ~DMA_SxCR_DIR; + + // Address increment mode + DMAx_Streamx->CR &= ~DMA_SxCR_PINC; // Fixed + DMAx_Streamx->CR |= DMA_SxCR_MINC; // Incremented + + // Single or burst transaction (single) + DMAx_Streamx->CR &= ~DMA_SxCR_MBURST; + + // Data width (16 bit) + DMAx_Streamx->CR |= DMA_SxCR_PSIZE_0; + DMAx_Streamx->CR |= DMA_SxCR_MSIZE_0; + + // Circular mode (on) + + DMAx_Streamx->CR |= DMA_SxCR_CIRC; + + // Double buffer mode (off) + DMAx_Streamx->CR &= ~DMA_SxCR_DBM; + + // Interrupt not used + + // 10: Activate the stream after peripheral configuration (see warning) + } + + // Set pins PA0 PA1 PA2 PA3 as analog input + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; + GPIOA->MODER = 0xFF; + + // Enable ADC clock + RCC->APB2ENR |= RCC_APB2ENR_ADC3EN; // <-- CHANGE THIS! + + // Set the clock divider for the analog circuitry (/8) + ADC->CCR |= ADC_CCR_ADCPRE_0 | ADC_CCR_ADCPRE_1; + // In this case I've set the maximum value, check the datasheet for the + // maximum frequency the analog circuitry supports and compare it with the + // parent clock + + InternalADC adc(ADCx, 3.0, DMAx_Streamx); + adc.enableChannel(InternalADC::CH0); + adc.enableChannel(InternalADC::CH3); + adc.init(); + + printf("Configuration completed\n"); + + while (1) + { + adc.sample(); + + printf("%8lld:%.2f\t%8lld:%.2f\n", + adc.getVoltage(InternalADC::CH0).adc_timestamp, + adc.getVoltage(InternalADC::CH0).voltage, + adc.getVoltage(InternalADC::CH0).adc_timestamp, + adc.getVoltage(InternalADC::CH3).voltage); + + miosix::delayMs(1000); + } +} \ No newline at end of file diff --git a/src/tests/drivers/test-internal-adc.cpp b/src/tests/drivers/test-internal-adc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7803a536cc5bb5cf76e66520b97066e48298d95e --- /dev/null +++ b/src/tests/drivers/test-internal-adc.cpp @@ -0,0 +1,59 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * 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. + */ + +#include <drivers/adc/InternalADC/InternalADC.h> +#include <miosix.h> + +#include "TimestampTimer.h" + +ADC_TypeDef& ADCx = *ADC3; + +int main() +{ + // Set the clock divider for the analog circuitry (/8) + ADC->CCR |= ADC_CCR_ADCPRE_0 | ADC_CCR_ADCPRE_1; + // In this case I've set the maximum value, check the datasheet for the + // maximum frequency the analog circuitry supports and compare it with the + // parent clock + + TimestampTimer::enableTimestampTimer(); + + InternalADC adc(ADCx, 3.3); + adc.enableChannel(InternalADC::CH4); // PF6 + adc.enableChannel(InternalADC::CH5); // PF7 + adc.enableChannel(InternalADC::CH6); // PF8 + adc.init(); + + printf("Configuration completed\n"); + + while (1) + { + adc.sample(); + + printf("CH4:%1.3f\tCH5:%1.3f\tCH6:%1.3f\n", + adc.getVoltage(InternalADC::CH4).voltage, + adc.getVoltage(InternalADC::CH5).voltage, + adc.getVoltage(InternalADC::CH6).voltage); + + miosix::delayMs(1000); + } +} \ No newline at end of file diff --git a/src/tests/drivers/test-l3gd20-fifo.cpp b/src/tests/drivers/test-l3gd20-fifo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..64920761034b5efe40bee8f9d24f143278c4d47e --- /dev/null +++ b/src/tests/drivers/test-l3gd20-fifo.cpp @@ -0,0 +1,252 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +/** + * Reads data at the highest possible rate from the sensor (~760 Hz) using the + * FIFO on the sensor as a buffer to avoid polling too frequently. + * We perform polling to maintain compatibility with the SensorManager and + * simplify sampling multiple sensors on the same thread. + * + * We read the FIFO at an interval such that it fills up to about 25 samples, to + * leave us a bit of wiggle room (fifo size is 32 samples). A watermark + * interrupt is set at about half the samples we plan to read (12 samples), in + * order to estimate the timestamp of the samples as precisely as possible. The + * watermark is intentionally set in the "middle" of the fifo as to minimize + * the possibility of desyncronizations between the sensor and the polling loop: + * If the watermark is set too close to the end of the fifo, the watermark + * interrupt may arrive *after* we start emptying the fifo, causing the driver + * to use an incorrect timestamp for the data. A similar case happens if the + * timestamp is too close to the beginning of the FIFO. + * + * The timestamp for each sample in the fifo is calculated as follows (inside + * the sensor driver): Knowing the timestamp of the 12th sample (thanks to the + * watermark interrup) we can estimate the timestamp of every other (previous or + * subsequent) sample in the FIFO, by doing + * + * timestamp(i) = timestamp(12) + SAMPLE_PERIOD*(i-12) + * + * After we have collected enough samples, dump them on the serial in CSV format + * togheter with other useful data. + */ + +#include <array> + +#include "diagnostic/CpuMeter.h" +#include "drivers/HardwareTimer.h" +#include "drivers/interrupt/external_interrupts.h" +#include "drivers/spi/SPIDriver.h" +#include "TimestampTimer.h" +#include "sensors/L3GD20/L3GD20.h" + +using namespace miosix; +using std::array; + +typedef Gpio<GPIOF_BASE, 7> GpioSck; +typedef Gpio<GPIOF_BASE, 8> GpioMiso; +typedef Gpio<GPIOF_BASE, 9> GpioMosi; +typedef Gpio<GPIOA_BASE, 2> GpioINT2; + +// L3GD20 SPI +SPIBus bus(SPI5); +GpioPin cs(GPIOC_BASE, 1); + +L3GD20* gyro = nullptr; + +// Send the watermark interrupt when the FIFO contains FIFO_WATERMARK samples. +static constexpr unsigned int FIFO_WATERMARK = 12; + +// Expected frequency from the datasheet is 760 Hz, but due to clock +// misalignment / temperature errors and other factors, the observed clock (and +// output data rate) is the following: +static constexpr float SAMPLE_FREQUENCY = 782.3f; + +struct GyroSample +{ + int fifo_num; + L3GD20Data gyro; + int level; + uint64_t wtm_delta; + float cpu; + uint64_t update; +}; + +// How many samples to collect +static constexpr int NUM_SAMPLES = SAMPLE_FREQUENCY * 20; + +GyroSample data[NUM_SAMPLES]; +int data_counter = 0; + +// High resolution hardware timer using TIM5 +HardwareTimer<uint32_t> hrclock{ + TIM5, TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1)}; + +// Last interrupt received timer tick +volatile uint32_t last_watermark_tick; // Stores the high-res tick of the last + // interrupt (L3GD20 watermark event) +volatile uint32_t watermark_delta; // Tick delta between the last 2 watermark + // events + +/** + * Interrupt handling routine. Called each time the fifo is filled with + * FIFO_WATERMARK samples. Stores the timestamp of the interrupt, representing + * the time the 12th sample was collected. From this, knowing the sample rate, + * we can obtain the timestamp of all previous and subsequent samples in the + * FIFO. Also calculates the time delta from the previous watermark, then + * provides the timestamp (in microseconds) to the sensor. + */ +void __attribute__((used)) EXTI2_IRQHandlerImpl() +{ + // Current high resolution tick + uint32_t tick = hrclock.tick(); + watermark_delta = tick - last_watermark_tick; + last_watermark_tick = tick; + + // Pass timestamp to the sensor + if (gyro != nullptr) + { + gyro->IRQupdateTimestamp(hrclock.toIntMicroSeconds(tick)); + } +} + +void configure() +{ + { + FastInterruptDisableLock dLock; + + RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; + RCC->APB2ENR |= RCC_APB2ENR_SPI5EN; + + GpioSck::mode(Mode::ALTERNATE); + GpioMiso::mode(Mode::ALTERNATE); + GpioMosi::mode(Mode::ALTERNATE); + + GpioSck::alternateFunction(5); + GpioMiso::alternateFunction(5); + GpioMosi::alternateFunction(5); + + // Interrupt + GpioINT2::mode(Mode::INPUT_PULL_DOWN); + + cs.mode(Mode::OUTPUT); + } + + cs.high(); + + enableExternalInterrupt(GPIOA_BASE, 2, InterruptTrigger::RISING_EDGE); + + TimestampTimer::enableTimestampTimer(); + + // High resolution clock configuration + hrclock.setPrescaler(382); + hrclock.start(); +} + +int main() +{ + configure(); + + // Setup sensor + gyro = new L3GD20(bus, cs, L3GD20::FullScaleRange::FS_250, + L3GD20::OutPutDataRate::ODR_760, 0x03); + + // Enable fifo with the specified watermark before calling init() + gyro->enableFifo(FIFO_WATERMARK); + + // Init the gyro + while (!gyro->init()) + { + printf("Gyro initialization failure!\n"); + Thread::sleep(100); + } + + // Sample NUM_SAMPLES data + int fifo_num = 0; + while (data_counter < NUM_SAMPLES) + { + long last_tick = miosix::getTick(); + + // Read the fifo + uint32_t update = hrclock.tick(); + gyro->sample(); + + // Measure how long we take to read the fifo + update = hrclock.tick() - update; + + uint8_t level = + gyro->getLastFifoSize(); // Current number of samples in the FIFO + + // Obtain the FIFO + const array<L3GD20Data, L3GD20_FIFO_SIZE>& fifo = + gyro->getLastFifo(); + + // Store everything in the data buffer + for (int i = 0; i < level; i++) + { + // data[data_counter++] = fifo[i]; + data[data_counter++] = {fifo_num, + fifo[i], + level, + hrclock.toIntMicroSeconds(watermark_delta), + averageCpuUtilization(), + hrclock.toIntMicroSeconds(update)}; + + // Stop if we have enough data + if (data_counter >= NUM_SAMPLES) + { + break; + } + } + ++fifo_num; + + // Wait until fifo has about 25 samples. The fifo size is 32 samples, so + // we have 7 samples (~ 9 ms) of wiggle room before we start losing + // data, in case we sleep a bit too much (may happen if an higher + // priority thread has a long task to perform) + Thread::sleepUntil(last_tick + 25.5 * 1000 / SAMPLE_FREQUENCY); + } + + // Dump buffer content as CSV on the serial (might take a while) + // printf("t,x,y,z\n"); + printf("FIFO_num,timestamp,int_delta,read_time,sample_delta,x,y,z,cpu\n"); + + for (int i = 1; i < data_counter; i++) + { + // clang-format off + printf("%d,%llu,%llu,%llu,%llu,%f,%f,%f,%.2f\n", + data[i].fifo_num, + data[i].gyro.gyro_timestamp, + data[i].wtm_delta, + data[i].update, + (data[i].gyro.gyro_timestamp - data[i - 1].gyro.gyro_timestamp), + data[i].gyro.gyro_x, + data[i].gyro.gyro_y, + data[i].gyro.gyro_z, + data[i].cpu); + // clang-format on + } + + printf("\n\n\nend.\n"); + for (;;) + { + Thread::sleep(1000); + } +} diff --git a/src/tests/drivers/test-l3gd20.cpp b/src/tests/drivers/test-l3gd20.cpp new file mode 100644 index 0000000000000000000000000000000000000000..787fc27b7fe24c14143882dfa0c4d5d19a03f69e --- /dev/null +++ b/src/tests/drivers/test-l3gd20.cpp @@ -0,0 +1,197 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +/** + * Polls the sensor at an high rate (1000 Hz), while measuring a precise + * timestamp for each sample using the DataReady interrupt provided by the + * sensor. This interrupt is used to measure the timestamp of the last sample + * read. After reading a certain number of samples, dumps them on the serial in + * CSV format. + */ + +#include <array> + +#include "diagnostic/CpuMeter.h" +#include "drivers/HardwareTimer.h" +#include "drivers/interrupt/external_interrupts.h" +#include "drivers/spi/SPIDriver.h" +#include "TimestampTimer.h" +#include "sensors/L3GD20/L3GD20.h" + +using namespace miosix; +using std::array; + +// GPIOs +typedef Gpio<GPIOF_BASE, 7> GpioSck; +typedef Gpio<GPIOF_BASE, 8> GpioMiso; +typedef Gpio<GPIOF_BASE, 9> GpioMosi; +typedef Gpio<GPIOA_BASE, 2> GpioINT2; + +// L3GD20 SPI +SPIBus bus(SPI5); +GpioPin cs(GPIOC_BASE, 1); + +L3GD20* gyro; + +static constexpr int SAMPLE_RATE = 1000; + +static constexpr int NUM_SAMPLES = SAMPLE_RATE * 5; + +struct GyroSample +{ + uint64_t timestamp; + uint64_t sample_delta; + L3GD20Data data; + float cpu; +}; + +GyroSample data[NUM_SAMPLES]; +int data_counter = 0; + +// High resolution hardware timer using TIM5 +HardwareTimer<uint32_t> hrclock{ + TIM5, TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1)}; + +// Last interrupt received timer tick +uint32_t last_sample_tick; // Stores the high-res tick of the last + // interrupt (L3GD20 watermark event) +uint32_t sample_delta; // Tick delta between the last 2 watermark + // events + +/** + * Interrupt handling routine. Called each time a new sample is available from + * the gyroscope (Data Ready function on INT2 on the sensor). + * Stores the timestamp of the last sample and calculates the time delta from + * the previous sample, then provides the timestamp (in microseconds) to the + * sensor. + */ +void __attribute__((used)) EXTI2_IRQHandlerImpl() +{ + // Current high resolution tick + uint32_t tick = hrclock.tick(); + sample_delta = tick - last_sample_tick; + last_sample_tick = tick; + + // Pass timestamp to sensor + if (gyro != nullptr) + gyro->IRQupdateTimestamp(hrclock.toIntMicroSeconds(tick)); +} + +void configure() +{ + { + FastInterruptDisableLock dLock; + + // Enable SPI5 and TIM5 peripheral clocks + RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; + RCC->APB2ENR |= RCC_APB2ENR_SPI5EN; + + // Set SPI pins to correct alternate mode + GpioSck::mode(Mode::ALTERNATE); + GpioMiso::mode(Mode::ALTERNATE); + GpioMosi::mode(Mode::ALTERNATE); + + GpioSck::alternateFunction(5); + GpioMiso::alternateFunction(5); + GpioMosi::alternateFunction(5); + + // Setup interrupt pin (sensors pulls INT2 up, so we set it to PULL_DOWN + // to avoid spurious interrupts) + GpioINT2::mode(Mode::INPUT_PULL_DOWN); + + // Set chip select pin to OUTPUT + cs.mode(Mode::OUTPUT); + } + + // Chip select starts high (not asserted) + cs.high(); + + // Enable rising-edge interrupt detection on PA2 + enableExternalInterrupt(GPIOA_BASE, 2, InterruptTrigger::RISING_EDGE); + + TimestampTimer::enableTimestampTimer(); + + // High resolution clock configuration: Sets the prescaler as to obtain + // 1.8 hours run time and 1.5 microseconds of resolution + hrclock.setPrescaler(127); + hrclock.start(); +} + +int main() +{ + configure(); + + gyro = new L3GD20(bus, cs, L3GD20::FullScaleRange::FS_250, + L3GD20::OutPutDataRate::ODR_760, 0x03); + + // Init the gyro + while (!gyro->init()) + { + printf("Gyro initialization failure!\n"); + Thread::sleep(100); + } + + Thread::sleep(500); + + // Collect NUM_SAMPLE samples + while (data_counter < NUM_SAMPLES) + { + long last_tick = miosix::getTick(); + + // Read data from the sensor + gyro->sample(); + + // Obtain the data + L3GD20Data d = gyro->getLastSample(); + + // Store the sample in the array, togheter with other useful data + data[data_counter++] = {d.gyro_timestamp, sample_delta, d, + averageCpuUtilization()}; + + // Wait until SAMPLE_PERIOD milliseconds from the start of this + // iteration have passed (SAMPLE_PERIOD = 1000 / SAMPLE_RATE) + Thread::sleepUntil(last_tick + 1000 / SAMPLE_RATE); + } + + // Dump buffer content as CSV on the serial (might take a while) + printf("FIFO_num,timestamp,int_delta,sample_delta,x,y,z,cpu\n"); + for (int i = 1; i < data_counter; i++) + { + // clang-format off + printf("%d,%llu,%llu,%llu,%f,%f,%f,%.2f\n", + 0, + data[i].timestamp, + hrclock.toIntMicroSeconds(data[i].sample_delta), + (data[i].timestamp - data[i - 1].timestamp), + data[i].data.gyro_x, + data[i].data.gyro_y, + data[i].data.gyro_z, + data[i].cpu); + // clang-format on + } + + printf("\n\n\nend.\n"); + for (;;) + { + Thread::sleep(1000); + } +} diff --git a/src/tests/drivers/test-lis3dsh.cpp b/src/tests/drivers/test-lis3dsh.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d33281a286293d635841b04f81dbd6c111d67d15 --- /dev/null +++ b/src/tests/drivers/test-lis3dsh.cpp @@ -0,0 +1,131 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#include <drivers/spi/SPIDriver.h> +#include <miosix.h> + +#include "Common.h" +#include "sensors/LIS3DSH/LIS3DSH.h" + +using namespace std; +using namespace miosix; + +SPIBus bus(SPI1); + +GpioPin spi_sck(GPIOA_BASE, 5); +GpioPin spi_miso(GPIOA_BASE, 6); +GpioPin spi_mosi(GPIOA_BASE, 7); +GpioPin cs(GPIOE_BASE, 3); + +int main() +{ + { + miosix::FastInterruptDisableLock dLock; + + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // Enable SPI1 bus + + spi_sck.mode(miosix::Mode::ALTERNATE); + spi_sck.alternateFunction(5); + spi_miso.mode(miosix::Mode::ALTERNATE); + spi_miso.alternateFunction(5); + spi_mosi.mode(miosix::Mode::ALTERNATE); + spi_mosi.alternateFunction(5); + + cs.mode(miosix::Mode::OUTPUT); + } + cs.high(); + + TimestampTimer::enableTimestampTimer(); + + LIS3DSH sensor(bus, cs, sensor.ODR_100_HZ, sensor.UPDATE_AFTER_READ_MODE, + sensor.FULL_SCALE_4G); + + LIS3DSHData data; + + // sensor not initialized, should give the error + sensor.sample(); + if (sensor.getLastError() != SensorErrors::NOT_INIT) + { + printf("\nTEST FAILED: sensor not initialized \n"); + return -1; + } + + bool initialized = false; + // initialize imu + if (!sensor.init()) + { + if (sensor.getLastError() == SensorErrors::INVALID_WHOAMI) + { + printf("TEST FAILED: invalid WHO_AM_I value, init failed \n"); + } + else + { + printf("TEST FAILED: init failed \n"); + } + return -1; + } + initialized = true; + + // check if the sensor is properly working + if (!sensor.selfTest()) + { + printf("\nTEST FAILED: self-test failed \n"); + return -1; + } + + // if sensor already inizialized, init() should return false + if (initialized) + { + if (sensor.init()) + { + printf("\nTEST FAILED: sensor is already initialized \n"); + return -1; + } + } + + Thread::sleep(500); + + // sample some data from the sensor + for (int i = 0; i < 5; i++) + { + // sensor intitialized, should return error if no new data exist + sensor.sample(); + + if (sensor.getLastError() == SensorErrors::NO_NEW_DATA) + { + printf("\nWarning: no new data to be read \n"); + } + + data = sensor.getLastSample(); + + printf("\nTimestamp: %llu \n", data.accel_timestamp); + printf("Acc: x: %f | y: %f | z: %f \n", data.accel_x, data.accel_y, + data.accel_z); + printf("Temp: %.2f C \n", data.temp); + + Thread::sleep(200); + } + + printf("\nLIS3DSH TEST OK ! \n"); + + return 0; +} \ No newline at end of file diff --git a/src/tests/drivers/test-lis3mdl.cpp b/src/tests/drivers/test-lis3mdl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b3c1329e722feed5b22224deb747dc0b32ef17f9 --- /dev/null +++ b/src/tests/drivers/test-lis3mdl.cpp @@ -0,0 +1,94 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Riccardo Musso + * + * 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. + */ + +#include <Common.h> +#include <drivers/spi/SPIDriver.h> +#include <miosix.h> + +#include "sensors/LIS3MDL/LIS3MDL.h" + +using namespace miosix; + +int main() +{ + TimestampTimer::enableTimestampTimer(); + + GpioPin cs(GPIOB_BASE, 1), miso(GPIOB_BASE, 4), mosi(GPIOB_BASE, 5), + clk(GPIOB_BASE, 3); + + { + FastInterruptDisableLock dLock; + + /* Enable SPI1 */ + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; + + cs.mode(Mode::OUTPUT); + cs.high(); + + clk.mode(Mode::ALTERNATE); + clk.alternateFunction(5); + + miso.mode(Mode::ALTERNATE); + miso.alternateFunction(5); + + mosi.mode(Mode::ALTERNATE); + mosi.alternateFunction(5); + } + + + SPIBus bus(SPI1); + + SPIBusConfig busConfig; + busConfig.clock_div = SPIClockDivider::DIV32; + + LIS3MDL::Config config; + config.odr = LIS3MDL::ODR_560_HZ; + config.scale = LIS3MDL::FS_16_GAUSS; + config.temperatureDivider = 5; + + LIS3MDL sensor(bus, cs, busConfig, config); + + if (!sensor.init()) + { + TRACE("LIS3MDL: Init failed"); + return 1; + } + + TRACE("Doing self test!\n"); + bool ok = sensor.selfTest(); + if (!ok) + { + TRACE("Error: selfTest() returned false!\n"); + } + + TRACE("Now printing some sensor data:\n"); + Thread::sleep(100); + + while (true) + { + sensor.sample(); + LIS3MDLData data = sensor.getLastSample(); + TRACE("%f C | x: %f | y: %f | z %f\n", data.temp, data.mag_x, + data.mag_y, data.mag_z); + miosix::Thread::sleep(2000); + } +} diff --git a/src/tests/drivers/test-mavlink-multi.cpp b/src/tests/drivers/test-mavlink-multi.cpp index 546ce2d4169c72d8b988ea93faf87336f3485644..3fb1646db74f86ff9e37863c173962fee007e24e 100644 --- a/src/tests/drivers/test-mavlink-multi.cpp +++ b/src/tests/drivers/test-mavlink-multi.cpp @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/drivers/test-mavlink.cpp b/src/tests/drivers/test-mavlink.cpp index e15a022eeb87ea186aebf8f7b93396af27976155..88c90246f88efd25beec11d9a4917e0ada714450 100644 --- a/src/tests/drivers/test-mavlink.cpp +++ b/src/tests/drivers/test-mavlink.cpp @@ -13,14 +13,20 @@ * * 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 + * 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. */ -#include <libs/mavlink_skyward_lib/mavlink_lib/r2a/mavlink.h> +// Ignore warnings, as we don't want to change third party generated files to +// fix them +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-align" +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" +#include <libs/mavlink_skyward_lib/mavlink_lib/lynx/mavlink.h> +#pragma GCC diagnostic pop #include <Common.h> #include <drivers/gamma868/Gamma868.h> diff --git a/src/tests/drivers/test-mpu9250.cpp b/src/tests/drivers/test-mpu9250.cpp index f53889a6de00d03789cb215c2f6487b0878cf9c2..b7536ec816009c9380452552eb909ede30ff180d 100644 --- a/src/tests/drivers/test-mpu9250.cpp +++ b/src/tests/drivers/test-mpu9250.cpp @@ -1,5 +1,5 @@ -/* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Nuno Barcellos +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,61 +13,78 @@ * * 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 + * 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. */ -#include <Common.h> -#include <drivers/BusTemplate.h> -#include <interfaces-impl/hwmapping.h> +#include <Debug.h> +#include <drivers/spi/SPIDriver.h> +#include <miosix.h> #include <sensors/MPU9250/MPU9250.h> -#include <drivers/spi/SensorSpi.h> -#include <sensors/SensorSampling.h> +#include "TimestampTimer.h" -using namespace miosix; -using namespace miosix::interfaces; +GpioPin sckPin = GpioPin(GPIOB_BASE, 13); +GpioPin misoPin = GpioPin(GPIOB_BASE, 14); +GpioPin mosiPin = GpioPin(GPIOB_BASE, 15); +GpioPin csPin = GpioPin(GPIOC_BASE, 1); -typedef BusSPI<1,spi1::mosi,spi1::miso,spi1::sck> busSPI1; -typedef ProtocolSPI<busSPI1,sensors::mpu9250::cs> spiMPU9250_a; -typedef MPU9250<spiMPU9250_a> mpu_t; +void initBoard() +{ + // Enable SPI clock for SPI2 interface + RCC->APB1ENR |= RCC_APB1ENR_SPI2EN; + + // Alternate function configuration for SPI pins + sckPin.mode(miosix::Mode::ALTERNATE); + sckPin.alternateFunction(5); // SPI function + mosiPin.mode(miosix::Mode::ALTERNATE); + mosiPin.alternateFunction(5); // SPI function + misoPin.mode(miosix::Mode::ALTERNATE); + misoPin.alternateFunction(5); // SPI function + + // Chip select pin as output starting high + csPin.mode(miosix::Mode::OUTPUT); + csPin.high(); +} int main() -{ - SimpleSensorSampler sampler; +{ + // Enable SPI clock and set gpios + initBoard(); - spiMPU9250_a::init(); - mpu_t* mpu = new mpu_t(1, 1); + TimestampTimer::enableTimestampTimer(); - Thread::sleep(100); - - if(mpu->init()){ - printf("MPU9250 Init succeeded\n" ); - sampler.AddSensor(mpu); - } - else { - printf("MPU9250 Init failed\n"); + // SPI configuration setup - while(!mpu->init()) { - printf("MPU9250 Init failed\n"); - Thread::sleep(1000); - } - } + SPIBusConfig spiConfig; + spiConfig.clock_div = SPIClockDivider::DIV64; + spiConfig.mode = SPIMode::MODE3; + SPIBus spiBus(SPI2); + SPISlave spiSlave(spiBus, csPin, spiConfig); - Thread::sleep(100); + // Device initialization + MPU9250 mpu9250(spiSlave); - while(true) - { - sampler.Update(); - mpu->updateMagneto(); + // Initialize the device + mpu9250.init(); - const Vec3* last_data = mpu->compassDataPtr(); - printf("%f %f %f\n", last_data->getX(), last_data->getY(), - last_data->getZ()); + while (true) + { + mpu9250.sample(); + MPU9250Data data = mpu9250.getLastSample(); + printf("%lld,%f,%f,%f;", data.accel_timestamp, data.accel_x, + data.accel_y, data.accel_z); + printf("%lld,%f,%f,%f;", data.gyro_timestamp, data.gyro_x, data.gyro_y, + data.gyro_z); + printf("%lld,%f,%f,%f\n", data.mag_timestamp, data.mag_x, data.mag_y, + data.mag_z); - Thread::sleep(100); + // Serial communicaion at 115200 baud takes aprx. 10ms + // miosix::delayMs(10); } + + TRACE("Test completed\n"); } \ No newline at end of file diff --git a/src/tests/drivers/test-ms5803.cpp b/src/tests/drivers/test-ms5803.cpp index e35b80540ef18b2850160b9ba149b7592d233d2d..1e73fb96bc4cbcf9fea4e378fe35b6d0445f06e5 100644 --- a/src/tests/drivers/test-ms5803.cpp +++ b/src/tests/drivers/test-ms5803.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Nuno Barcellos + * Author: Nuno Barcellos * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -21,58 +21,47 @@ */ #include <Common.h> -#include <drivers/BusTemplate.h> -#include <interfaces-impl/hwmapping.h> -#include "sensors/MS580301BA07/MS580301BA07.h" - +#include <drivers/spi/SPIDriver.h> #include <drivers/spi/SensorSpi.h> -#include <sensors/SensorSampling.h> +#include <interfaces-impl/hwmapping.h> +#include <sensors/MS5803/MS5803.h> +#include <sensors/SensorSampler.h> using namespace miosix; -using namespace miosix::interfaces; -typedef Gpio<GPIOD_BASE, 7> cs_ms58; -typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI1; -typedef ProtocolSPI<busSPI1, cs_ms58> spiMS58; -typedef MS580301BA07<spiMS58> ms58_t; +/** + * This test is intended to be run on the Death Stack X + */ int main() { - SimpleSensorSampler sampler; + TimestampTimer::enableTimestampTimer(); + + SPIBusConfig spiConfig; + SPIBus spiBus(SPI1); + SPISlave spiSlave(spiBus, miosix::sensors::ms5803::cs::getPin(), spiConfig); - spiMS58::init(); - ms58_t* ms58 = new ms58_t(); + // Sample temperature every 5 pressure samples + MS5803 sensor(spiSlave, 10); Thread::sleep(100); - if (ms58->init()) + if (!sensor.init()) { - printf("MS58 Init succeeded\n"); - sampler.AddSensor(ms58); - } - else - { - printf("MS58 Init failed\n"); - - while (!ms58->init()) - { - printf("MS58 Init failed\n"); - Thread::sleep(1000); - } + printf("MS5803 Init failed\n"); } Thread::sleep(100); - printf("raw_p,p,raw_t,t\n"); + printf("press_timestamp,press,temp_timestamp,temp\n"); + while (true) { - sampler.Update(); + sensor.sample(); - const float* last_pressure = ms58->pressureDataPtr(); - const float* last_temp = ms58->tempDataPtr(); - MS5803Data md = ms58->getData(); - printf("%d,%f,%d,%f\n", (int)md.raw_press, - *last_pressure, (int)md.raw_temp, *last_temp); + MS5803Data data = sensor.getLastSample(); + printf("%llu,%f,%llu,%f\n", data.press_timestamp, data.press, + data.temp_timestamp, data.temp); - Thread::sleep(100); + Thread::sleep(10); } } \ No newline at end of file diff --git a/src/tests/drivers/test-piksi.cpp b/src/tests/drivers/test-piksi.cpp index b4e01bc9e9c3983c7bcb311b4bbf29b12195000e..1965de769ef5dab31c770fd6d115994c654ba6fe 100644 --- a/src/tests/drivers/test-piksi.cpp +++ b/src/tests/drivers/test-piksi.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2017 Skyward Experimental Rocketry - * Authors: Federico Terraneo + * Author: Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,14 +13,14 @@ * * 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 + * 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. */ -#include <drivers/piksi/piksi.h> +#include <drivers/old_examples/piksi/piksi.h> #include <time.h> #include <iostream> using namespace std; diff --git a/src/tests/drivers/test-pwm.cpp b/src/tests/drivers/test-pwm.cpp index 077899a25b9031fed687a71e9f3e771c6ede4148..597a5e3608c3ae4f4072d6825aef246e07921f5c 100644 --- a/src/tests/drivers/test-pwm.cpp +++ b/src/tests/drivers/test-pwm.cpp @@ -1,3 +1,26 @@ +/* Copyright (c) 2018-2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + + #include "Common.h" #include "drivers/HardwareTimer.h" #include "drivers/pwm/pwm.h" diff --git a/src/tests/drivers/test-servo.cpp b/src/tests/drivers/test-servo.cpp index 2f7cf6249bb430ad343ede68f42080854e6bc567..19940b6a2d8375ab7553a64065ba8e874a2e4179 100644 --- a/src/tests/drivers/test-servo.cpp +++ b/src/tests/drivers/test-servo.cpp @@ -1,6 +1,5 @@ -/** - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/drivers/test-spi.cpp b/src/tests/drivers/test-spi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cf785972687dd8c3a33d12ec30d1594011e4e453 --- /dev/null +++ b/src/tests/drivers/test-spi.cpp @@ -0,0 +1,89 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#include <Common.h> +#include <cstdio> +#include <iostream> +#include <sstream> +#include "drivers/spi/SPIDriver.h" + +using namespace std; +using namespace miosix; + +SPIBus bus(SPI1); + +GpioPin spi_sck(GPIOA_BASE, 5); +GpioPin spi_miso(GPIOA_BASE, 6); +GpioPin spi_mosi(GPIOA_BASE, 7); +GpioPin cs(GPIOE_BASE, 3); + +int main() +{ + { + miosix::FastInterruptDisableLock dLock; + + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // Enable SPI1 bus + + spi_sck.mode(miosix::Mode::ALTERNATE); + spi_sck.alternateFunction(5); + spi_miso.mode(miosix::Mode::ALTERNATE); + spi_miso.alternateFunction(5); + spi_mosi.mode(miosix::Mode::ALTERNATE); + spi_mosi.alternateFunction(5); + + cs.mode(miosix::Mode::OUTPUT); + } + cs.high(); + + SPISlave spi_slave(bus, cs, {}); + spi_slave.config.clock_div = SPIClockDivider::DIV64; + + while (true) + { + printf("Insert a number (0-255): \n"); + + string temp; + int cmd; + getline(cin, temp); + stringstream(temp) >> cmd; + + { + SPITransaction spi(spi_slave); + + // spam over spi in order to easily find + // the data with a logic analyzer + for (int i = 0; i < 10; i++) + { + for (int i = 0; i < 10; i++) + { + spi.write((uint8_t)cmd); + } + + Thread::sleep(10); + } + } + + Thread::sleep(100); + } + + return 0; +} diff --git a/src/tests/drivers/test-ublox.cpp b/src/tests/drivers/test-ublox.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bbd3c3dee206cf0fd561d371fb16d2abc01f0b5a --- /dev/null +++ b/src/tests/drivers/test-ublox.cpp @@ -0,0 +1,88 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio + * + * 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. + */ + +#include <Common.h> +#include <drivers/gps/ublox/UbloxGPS.h> + +#include <cstdio> + +#include "TimestampTimer.h" + +using namespace miosix; + +#define RATE 4 + +int main() +{ + TimestampTimer::enableTimestampTimer(); + + printf("Welcome to the ublox test\n"); + + // Keep GPS baud rate at default for easier testing + UbloxGPS gps(921600, RATE, 2, "gps", 38400); + UbloxGPSData dataGPS; + printf("Gps allocated\n"); + + // Init the gps + if (gps.init()) + { + printf("Successful gps initialization\n"); + } + else + { + printf("Failed gps initialization\n"); + } + + // Perform the selftest + if (gps.selfTest()) + { + printf("Successful gps selftest\n"); + } + else + { + printf("Failed gps selftest\n"); + } + + // Start the gps thread + gps.start(); + printf("Gps started\n"); + + while (true) + { + // Give time to the thread + Thread::sleep(1000 / RATE); + + // Sample + gps.sample(); + dataGPS = gps.getLastSample(); + + // Print out the latest sample + TRACE( + "[gps] timestamp: % 4.3f, fix: %01d lat: % f lon: % f " + "height: %4.1f nsat: %2d speed: %3.2f velN: % 3.2f velE: % 3.2f " + "track %3.1f\n", + (float)dataGPS.gps_timestamp / 1000000, dataGPS.fix, + dataGPS.latitude, dataGPS.longitude, dataGPS.height, + dataGPS.num_satellites, dataGPS.speed, dataGPS.velocity_north, + dataGPS.velocity_east, dataGPS.track); + } +} diff --git a/src/tests/drivers/xbee/EnergyScanData.h b/src/tests/drivers/xbee/EnergyScanData.h new file mode 100644 index 0000000000000000000000000000000000000000..48e48fb402438aaeb7c12ef5e0b3bdd332e56841 --- /dev/null +++ b/src/tests/drivers/xbee/EnergyScanData.h @@ -0,0 +1,69 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <array> +#include <string> +#include <ostream> + +using std::array; +using std::string; +using std::to_string; + +struct EnergyScanData +{ + long long timestamp; + int channel_data[30]; + + EnergyScanData() = default; + + EnergyScanData(long long ts, array<int, 30> scan) + { + for(int i = 0; i < 30; i++) + { + channel_data[i] = scan[i]; + } + } + + static string header() + { + string out = "timestamp"; + for(int i = 0; i < 30; i++) + { + out += ",channel_" + to_string(i); + } + return out + "\n"; + } + + void print(std::ostream& os) const + { + os << timestamp; + + for(int i = 0; i < 30; i++) + { + os << "," << channel_data[i]; + } + + os << "\n"; + } +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/Mark.h b/src/tests/drivers/xbee/Mark.h new file mode 100644 index 0000000000000000000000000000000000000000..e2068e9bbebabb587f04025c55898e1b4cff8170 --- /dev/null +++ b/src/tests/drivers/xbee/Mark.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <string> +#include <ostream> + +using std::string; +/** + * @brief Used to mark a specific instant in the log + */ +struct Mark +{ + long long timestamp; + unsigned int seq; + + static string header() + { + return "timestamp,seq\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << seq << "\n"; + } +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/XbeeTestData.h b/src/tests/drivers/xbee/XbeeTestData.h new file mode 100644 index 0000000000000000000000000000000000000000..bab88b1dcabcc80bd81c382c87e02f3d3e24e2b5 --- /dev/null +++ b/src/tests/drivers/xbee/XbeeTestData.h @@ -0,0 +1,152 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <ostream> +#include <array> +#include <string> + +#include "math/Stats.h" + +using std::array; +using std::string; +using std::to_string; + +struct XbeeConfig +{ + long long timestamp; + bool tx_enabled = false; + uint16_t packet_size = 256; + unsigned int send_interval = 0; + bool freq_hop = true; + bool data_rate_80k = false; + + void print() + { + printf("+++XBee configuration+++\n\n"); + printf("Tx: %s, pkt: %u B, int: %d ms\n", + tx_enabled ? "enabled" : "disabled", packet_size, send_interval); + printf("Freq hop: %s, data rate: %s \n", freq_hop ? "on" : "off", + data_rate_80k ? "80 kbps" : "10 kbps"); + } + + static string header() + { + return "timestamp,tx_enabled,packet_size,send_interval,freq_hop,data_" + "rate_80k\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << tx_enabled << "," << packet_size << "," + << send_interval << "," << freq_hop << "," << data_rate_80k << "\n"; + } +}; + +struct TxData +{ + long long timestamp = 0LL; + unsigned int packet_size = 0; + unsigned int time_since_last_send = 0; + unsigned int time_to_send = 0; + unsigned int tx_success_counter = 0; + unsigned int tx_fail_counter = 0; + + static string header() + { + return "timestamp,packet_size,time_since_last_send,time_to_send,tx_" + "success_cnt,tx_fail_cnt\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << packet_size << "," << time_since_last_send + << "," << time_to_send << "," << tx_success_counter << "," + << tx_fail_counter << "\n"; + } +}; + +struct RxData +{ + long long timestamp; + size_t pkt_size = 0; + long long last_packet_timestamp = 0; + int RSSI = 0; + unsigned int rcv_count = 0; + unsigned int packets_lost = 0; + unsigned int rcv_errors = 0; + unsigned int rcv_wrong_payload = 0; + + static string header() + { + return "timestamp,packet_size,last_packet_timestamp,RSSI,rcv_cnt,rcv_errors," + "rcv_wrong\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << pkt_size << "," << last_packet_timestamp << "," + << RSSI << "," << rcv_count << "," << rcv_errors << "," + << rcv_wrong_payload << "\n"; + } +}; + +struct EnergyScanData +{ + long long timestamp; + int channel_data[30]; + + EnergyScanData() = default; + + EnergyScanData(long long ts, array<int, 30> scan) + { + for(int i = 0; i < 30; i++) + { + channel_data[i] = scan[i]; + } + + timestamp = ts; + } + + static string header() + { + string out = "timestamp"; + for(int i = 0; i < 30; i++) + { + out += ",channel_" + to_string(i); + } + return out + "\n"; + } + + void print(std::ostream& os) const + { + os << timestamp; + + for(int i = 0; i < 30; i++) + { + os << "," << channel_data[i]; + } + + os << "\n"; + } +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/XbeeTransceiver.h b/src/tests/drivers/xbee/XbeeTransceiver.h new file mode 100644 index 0000000000000000000000000000000000000000..fc30fe06d4db69f4fe41c0bec3fe661c4ae7cd50 --- /dev/null +++ b/src/tests/drivers/xbee/XbeeTransceiver.h @@ -0,0 +1,423 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <arch/common/drivers/stm32_hardware_rng.h> +#include <miosix.h> + +#include <functional> + +#include "ActiveObject.h" +#include "XbeeTestData.h" +#include "drivers/Xbee/APIFramesLog.h" +#include "drivers/Xbee/Xbee.h" +#include "logger/Logger.h" +#include "utils/testutils/ThroughputCalculator.h" + +using miosix::getTick; +using std::bind; +using std::function; + +static constexpr size_t RCV_BUF_SIZE = 256; +static constexpr uint32_t PACKET_FIRST_INT = 0xF0E1C2B3; + +/** + * @brief Repeats the provided 32 bit value in the buffer, filling it. + * + * @param buf Buffer to fill + * @param val Value to fill the buffer with + * @param buf_size size of the buffer + */ +void memset32(uint8_t* buf, uint32_t val, int buf_size) +{ + int i = 0; + while (i <= buf_size - (int)sizeof(val)) + { + memcpy(buf + i, &val, sizeof(val)); + i += sizeof(val); + } + + memcpy(buf + i, &val, buf_size % sizeof(val)); +} + +struct SendIntervalBase +{ + virtual unsigned int getInterval() const = 0; +}; + +struct ConstSendInterval : public SendIntervalBase +{ + ConstSendInterval(unsigned int interval) : interval(interval) {} + unsigned int interval; + unsigned int getInterval() const override { return interval; } +}; + +struct RandSendInterval : public SendIntervalBase +{ + RandSendInterval(unsigned int min, unsigned int max) + : min(min), diff(max - min) + { + } + unsigned int min; + unsigned int diff; + unsigned int getInterval() const override + { + return (miosix::HardwareRng::instance().get() % diff) + min; + } +}; + +class Sender : public ActiveObject +{ + friend class XbeeTransceiver; + +public: + Sender(Xbee::Xbee& xbee, Logger& logger, const SendIntervalBase& interval, + size_t packet_size, long long expected_packet_interval) + : xbee(xbee), logger(logger), dr_calc(expected_packet_interval), + interval(interval) + { + data.packet_size = packet_size; + packet_counter = 0; + } + + TxData getTxData() const { return data; } + + DataRateResult getDataRate() { return dr_calc.getResult(); } + +protected: + void run() override + { + long long loop_start_ts = getTick(); + while (!shouldStop()) + { + data.time_since_last_send = getTick() - loop_start_ts; + loop_start_ts = getTick(); + + // Create packet + memcpy(buf, &PACKET_FIRST_INT, sizeof(uint32_t)); + memset32(buf + sizeof(uint32_t), packet_counter++, + data.packet_size - sizeof(uint32_t)); + + long long send_start_ts = getTick(); + + bool result = xbee.send(buf, data.packet_size); + + data.time_to_send = getTick() - send_start_ts; + data.timestamp = send_start_ts; + + if (result) + { + ++data.tx_success_counter; + dr_calc.addPacket(data.packet_size); + } + else + { + ++data.tx_fail_counter; + } + + logger.log(data); + + miosix::Thread::sleepUntil(loop_start_ts + interval.getInterval()); + } + } + +private: + Xbee::Xbee& xbee; + Logger& logger; + ThroughputCalculator dr_calc; + TxData data; + const SendIntervalBase& interval; + + uint32_t packet_counter = 0; + + uint8_t buf[Xbee::MAX_PACKET_PAYLOAD_LENGTH]; +}; + +class Receiver : public ActiveObject +{ + friend class XbeeTransceiver; + +public: + Receiver(Xbee::Xbee& xbee, Logger& logger, + long long expected_packet_interval) + : xbee(xbee), logger(logger), dr_calc(expected_packet_interval) + { + } + + void stop() override + { + should_stop = true; + xbee.wakeReceiver(true); + ActiveObject::stop(); + } + + RxData getRxData() const { return data; } + + DataRateResult getDataRate() { return dr_calc.getResult(); } + +protected: + void run() override + { + while (!shouldStop()) + { + long long start = getTick(); + + size_t len = xbee.receive(buf, RCV_BUF_SIZE); + + data.last_packet_timestamp = getTick(); + data.timestamp = start; + + ++data.rcv_count; + + if (len > sizeof(uint32_t)) + { + data.pkt_size = len; + + // Packet payload is a repeated sequential 32 bit number + uint32_t num, strt; + memcpy(&strt, buf, sizeof(uint32_t)); + memcpy(&num, buf + sizeof(uint32_t), sizeof(uint32_t)); + + if (checkPacket(num, buf, len)) + { + dr_calc.addPacket(len); + if (last_packet_num > 0) + { + if (num > last_packet_num) + data.packets_lost += num - last_packet_num - 1; + } + last_packet_num = num; + } + else + { + TRACE("[XBeeTransceiver] Wrong packet payload! num: %u, start: 0x%08X\n", num, strt); + ++data.rcv_wrong_payload; + } + } + + logger.log(data); + } + } + +private: + bool checkPacket(uint32_t expected_num, uint8_t* packet, size_t packet_size) + { + if (memcmp(packet, &PACKET_FIRST_INT, sizeof(uint32_t)) == 0) + { + int i = sizeof(uint32_t); + while (i <= (int)packet_size - (int)sizeof(expected_num)) + { + if (memcmp(packet + i, &expected_num, sizeof(expected_num)) != + 0) + { + return false; + } + i += sizeof(expected_num); + } + memcpy(packet + i, &expected_num, + packet_size % sizeof(expected_num)); + return true; + } + return false; + } + + Xbee::Xbee& xbee; + Logger& logger; + ThroughputCalculator dr_calc; + RxData data{}; + + uint8_t buf[RCV_BUF_SIZE]; + uint32_t last_packet_num = 0; +}; + +class XbeeTransceiver +{ +public: + XbeeTransceiver(Xbee::Xbee& xbee, Logger& logger, + const SendIntervalBase& snd_interval, size_t tx_pkt_size, + long long expected_packet_interval) + : sender(new Sender(xbee, logger, snd_interval, tx_pkt_size, + expected_packet_interval)), + receiver(new Receiver(xbee, logger, expected_packet_interval)), + logger(logger) + { + using namespace std::placeholders; + xbee.setOnFrameReceivedListener( + bind(&XbeeTransceiver::handleApiFrame, this, _1)); + } + + ~XbeeTransceiver() + { + sender->stop(); + receiver->stop(); + + delete sender; + delete receiver; + } + + void disableSender() { sender_enabled = false; } + + void disableReceiver() { receiver_enabled = false; } + + void start() + { + if (sender_enabled) + { + sender->start(); + } + if (receiver_enabled) + { + receiver->start(); + } + } + + void stop() + { + sender->stop(); + receiver->stop(); + } + + void setOnFrameReceivedListener( + Xbee::Xbee::OnFrameReceivedListener frame_listener) + { + this->frame_listener = frame_listener; + } + + Sender& getSender() { return *sender; } + + Receiver& getReceiver() { return *receiver; } + +private: + void handleApiFrame(Xbee::APIFrame& frame) + { + logAPIFrame(frame); + + if (frame_listener) + { + frame_listener(frame); + } + + if (frame.frame_type == Xbee::FTYPE_AT_COMMAND_RESPONSE) + { + Xbee::ATCommandResponseFrame* at = + frame.toFrameType<Xbee::ATCommandResponseFrame>(); + + if (strncmp(at->getATCommand(), "DB", 2) == 0) + { + receiver->data.RSSI = -*at->getCommandDataPointer(); + } + + if (strncmp(at->getATCommand(), "ER", 2) == 0) + { + uint16_t errs; + memcpy(&errs, at->getCommandDataPointer(), 2); + receiver->data.rcv_errors = swapBytes16(errs); + } + } + } + + void logAPIFrame(Xbee::APIFrame& frame) + { + using namespace Xbee; + bool logged = false; + switch (frame.frame_type) + { + case FTYPE_AT_COMMAND: + { + ATCommandFrameLog dest; + logged = ATCommandFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_AT_COMMAND_RESPONSE: + { + ATCommandResponseFrameLog dest; + logged = ATCommandResponseFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_MODEM_STATUS: + { + ModemStatusFrameLog dest; + logged = ModemStatusFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_TX_REQUEST: + { + TXRequestFrameLog dest; + logged = TXRequestFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_TX_STATUS: + { + TXStatusFrameLog dest; + logged = TXStatusFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + case FTYPE_RX_PACKET_FRAME: + { + RXPacketFrameLog dest; + logged = RXPacketFrameLog::toFrameType(frame, &dest); + if (logged) + { + logger.log(dest); + } + break; + } + } + + if (!logged) + { + APIFrameLog api; + APIFrameLog::fromAPIFrame(frame, &api); + logger.log(api); + } + } + + bool sender_enabled = true; + bool receiver_enabled = true; + + Sender* sender; + Receiver* receiver; + Logger& logger; + + Xbee::Xbee::OnFrameReceivedListener frame_listener; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/ConfigScreen.h b/src/tests/drivers/xbee/gui/ConfigScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..b72fbc7d163a699dbda10cc93217ca2d107a9d6a --- /dev/null +++ b/src/tests/drivers/xbee/gui/ConfigScreen.h @@ -0,0 +1,223 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <cstdint> +#include <mxgui/display.h> + +#include "utils/gui/GridLayout.h" +#include "utils/gui/TextView.h" +#include "utils/gui/VerticalLayout.h" +#include "utils/gui/OptionView.h" + +#include "../XbeeTestData.h" + +struct ConfigScreen +{ + XbeeConfig config; + + enum OptSendEnableIDs : uint8_t + { + TX_ENABLED, + TX_DISABLED + }; + + enum OptFreqHopIDs : uint8_t + { + FH_ENABLED, + FH_DISABLED + }; + + enum OptDataRateIDs : uint8_t + { + DR_10K, + DR_80K + }; + + enum OptPacketSizeIDs : uint8_t + { + PKTSIZE_64, + PKTSIZE_128, + PKTSIZE_256 + }; + + enum OptSendIntervalIDs : uint8_t + { + TXINT_CONTINUOUS, + TXINT_200MS, + TXINT_250MS, + TXINT_333MS, + TXINT_500MS, + TXINT_1000MS + }; + + ConfigScreen() + { + title.setFont(mxgui::miscFixedBold); + title.setTextColor(mxgui::black); + title.setBackgroundColor(mxgui::green); + title.setAlignment(HorizAlignment::LEFT, VertAlignment::CENTER); + + tv_log_status.setFont(mxgui::miscFixedBold); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + tv_log_status.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + grid_title.setCell(&title, 0, 0); + grid_title.setCell(&tv_log_status, 0, 1); + + grid.setCell(&btn_energy, 0); + grid.setCell(&btn_start, 1); + grid.setDrawBorder(true); + + btn_start.setSelectable(true); + btn_start.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_start.setBackgroundColor(mxgui::darkGrey); + + btn_energy.setSelectable(true); + btn_energy.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_energy.setBackgroundColor(mxgui::darkGrey); + + root.addView(&grid_title, 0.6); + root.addView(&opt_send_enable, 1); + root.addView(&opt_packet_size, 1); + + root.addView(&opt_send_interval, 2); + + root.addView(&opt_freq_hop, 1); + root.addView(&opt_data_rate, 1); + root.addView(&grid, 1); + + addOptionCallbacks(); + } + + void updateLogStatus(Logger& logger) + { + if (logger.getLogNumber() >= 0) + { + string log_name = logger.getFileName(logger.getLogNumber()); + + tv_log_status.setText(log_name); + tv_log_status.setTextColor(mxgui::black); + tv_log_status.setBackgroundColor(mxgui::green); + } + else + { + tv_log_status.setText("SD ERR"); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + } + } + + VerticalLayout root{10}; + + TextView btn_start{"Start"}; + TextView btn_energy{"Energy scan"}; + +private: + void addOptionCallbacks() + { + opt_send_enable.addOnOptionChosenListener( + [&](unsigned int id) { config.tx_enabled = id == TX_ENABLED; }); + + opt_send_interval.addOnOptionChosenListener([&](unsigned int id) { + switch (id) + { + case TXINT_CONTINUOUS: + config.send_interval = 0; + break; + case TXINT_200MS: + config.send_interval = 200; + break; + case TXINT_250MS: + config.send_interval = 250; + break; + case TXINT_333MS: + config.send_interval = 333; + break; + case TXINT_500MS: + config.send_interval = 500; + break; + case TXINT_1000MS: + config.send_interval = 1000; + break; + } + }); + + opt_packet_size.addOnOptionChosenListener([&](unsigned int id) { + switch (id) + { + case PKTSIZE_64: + config.packet_size = 64; + break; + case PKTSIZE_128: + config.packet_size = 128; + break; + case PKTSIZE_256: + config.packet_size = 256; + break; + } + }); + + opt_freq_hop.addOnOptionChosenListener( + [&](unsigned int id) { config.freq_hop = id == FH_ENABLED; }); + + opt_data_rate.addOnOptionChosenListener( + [&](unsigned int id) { config.data_rate_80k = id == DR_80K; }); + } + + OptionView opt_send_enable{ + "Transmission", + {{TX_ENABLED, "Enabled"}, {TX_DISABLED, "Disabled"}}, + TX_DISABLED, + 2}; + + OptionView opt_packet_size{ + "TX Packet Size", + {{PKTSIZE_64, "64 B"}, {PKTSIZE_128, "128 B"}, {PKTSIZE_256, "256 B"}}, + PKTSIZE_256, + 3}; + + OptionView opt_send_interval{"Send Interval", + {{TXINT_CONTINUOUS, "Cont"}, + {TXINT_200MS, "200 ms"}, + {TXINT_250MS, "250 ms"}, + {TXINT_333MS, "333 ms"}, + {TXINT_500MS, "500 ms"}, + {TXINT_1000MS, "1 sec"}}, + TXINT_CONTINUOUS, + 4}; + + OptionView opt_freq_hop{ + "Frequency hopping", + {{FH_ENABLED, "Enabled"}, {FH_DISABLED, "Disabled"}}, + FH_ENABLED, + 2}; + OptionView opt_data_rate{ + "Xbee datarate", {{DR_10K, "10 kbps"}, {DR_80K, "80 kbps"}}, DR_10K, 2}; + + GridLayout grid{1, 2}; + GridLayout grid_title{1,2}; + TextView title{"Xbee Setup"}; + TextView tv_log_status{"SD ERR!"}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/EndScreen.h b/src/tests/drivers/xbee/gui/EndScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..89576496b78c37453d7bd6d4ec4d28241b6f31cf --- /dev/null +++ b/src/tests/drivers/xbee/gui/EndScreen.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <cstdint> +#include <mxgui/display.h> + +#include "utils/gui/GridLayout.h" +#include "utils/gui/TextView.h" +#include "utils/gui/VerticalLayout.h" +#include "utils/gui/OptionView.h" + + + +struct EndScreen +{ + EndScreen() + { + tv_title.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + tv_f.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + tv_reset.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + tv_f.setSelectable(true); + tv_reset.setSelectable(true); + + root.addView(&tv_title, 1); + root.addView(&tv_f, 1); + root.addView(&tv_reset, 1); + } + + VerticalLayout root; + + TextView tv_f{"Press F to pay respects"}; + TextView tv_reset{"Or RESET to start again"}; +private: + TextView tv_title{"You killed the program, you maniac!"}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/EnergyScanScreen.h b/src/tests/drivers/xbee/gui/EnergyScanScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..eb6ffb6f650bf23824e70a66894b6ba18e708561 --- /dev/null +++ b/src/tests/drivers/xbee/gui/EnergyScanScreen.h @@ -0,0 +1,231 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <mxgui/display.h> + +#include <array> +#include <cstdint> +#include <limits> +#include <string> +#include <utility> + +#include "math/Stats.h" +#include "utils/gui/GridLayout.h" +#include "utils/gui/TextView.h" +#include "utils/gui/VerticalLayout.h" + +using std::array; +using std::to_string; + +struct EnergyScanScreen +{ + static constexpr unsigned int NUM_CHANNELS = 30; + static constexpr unsigned int NUM_CHANNEL_ROWS = (NUM_CHANNELS + 1) / 2; + static constexpr unsigned int NUM_COLS = 8; + + static constexpr float COLOR_PERCENTILE = 0.15f; + EnergyScanScreen() + { + tv_title.setFont(mxgui::miscFixedBold); + tv_title.setTextColor(mxgui::black); + tv_title.setBackgroundColor(mxgui::green); + tv_title.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + tv_log_status.setFont(mxgui::miscFixedBold); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + tv_log_status.setAlignment(HorizAlignment::CENTER, + VertAlignment::CENTER); + + grid_title.setCell(&tv_title, 0, 0); + grid_title.setCell(&tv_log_status, 0, 1); + + for (unsigned int i = 0; i < NUM_COLS; i++) + { + col_titles[i] = new TextView(titles[i % (NUM_COLS / 2)]); + col_titles[i]->setTextColor(mxgui::blue); + + grid_channels.setCell(col_titles[i], 0, i); + } + + mxgui::Color pink(0xFC59); + for (unsigned int i = 0; i < NUM_CHANNELS; ++i) + { + char buf[3]; + snprintf(buf, 3, "%02d", i); + + col_names[i] = new TextView(buf); + col_names[i]->setTextColor(pink); + + col_current[i] = new TextView("-40"); + col_min[i] = new TextView("-40"); + col_max[i] = new TextView("-40"); + + grid_channels.setCell(col_names[i], (i % NUM_CHANNEL_ROWS) + 1, + (i / NUM_CHANNEL_ROWS) * 4 + 0); + grid_channels.setCell(col_current[i], (i % NUM_CHANNEL_ROWS) + 1, + (i / NUM_CHANNEL_ROWS) * 4 + 1); + grid_channels.setCell(col_min[i], (i % NUM_CHANNEL_ROWS) + 1, + (i / NUM_CHANNEL_ROWS) * 4 + 2); + grid_channels.setCell(col_max[i], (i % NUM_CHANNEL_ROWS) + 1, + (i / NUM_CHANNEL_ROWS) * 4 + 3); + } + + btn_mark.setSelectable(true); + btn_mark.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_mark.setBackgroundColor(mxgui::darkGrey); + + btn_stop.setSelectable(true); + btn_stop.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_stop.setBackgroundColor(mxgui::darkGrey); + + btn_reset.setSelectable(true); + btn_reset.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_reset.setBackgroundColor(mxgui::darkGrey); + + grid_buttons.setCell(&btn_mark, 0); + grid_buttons.setCell(&btn_reset, 1); + grid_buttons.setCell(&btn_stop, 2); + grid_buttons.setDrawBorder(true); + + root.addView(&grid_title, 0.8); + root.addView(&grid_channels, 10); + root.addView(&grid_buttons, 1); + } + + ~EnergyScanScreen() + { + for (unsigned int i = 0; i < NUM_COLS; i++) + { + delete col_titles[i]; + } + + for (unsigned int i = 0; i < NUM_CHANNELS; ++i) + { + delete col_names[i]; + delete col_current[i]; + delete col_min[i]; + delete col_max[i]; + } + } + + void updateScan(array<int, NUM_CHANNELS> scan) + { + for (unsigned int i = 0; i < NUM_CHANNELS; i++) + { + ch_stats[i].add(scan[i]); + + StatsResult r = ch_stats[i].getStats(); + + abs_max = std::max(r.maxValue, abs_max); + abs_min = std::min(r.minValue, abs_min); + } + + for (unsigned int i = 0; i < NUM_CHANNELS; i++) + { + StatsResult r = ch_stats[i].getStats(); + + col_min[i]->setText(to_string((int)r.minValue)); + setColor(col_min[i], r.minValue); + + col_current[i]->setText(to_string(scan[i])); + setColor(col_current[i], scan[i]); + + col_max[i]->setText(to_string((int)r.maxValue)); + setColor(col_max[i], r.maxValue); + } + } + + void resetStats() + { + for (unsigned int i = 0; i < NUM_CHANNELS; i++) + { + ch_stats[i].reset(); + } + + abs_max = std::numeric_limits<float>::lowest(); + abs_min = std::numeric_limits<float>::max(); + } + + void updateLogStatus(Logger& logger) + { + if (logger.getLogNumber() >= 0) + { + string log_name = logger.getFileName(logger.getLogNumber()); + + tv_log_status.setText(log_name); + tv_log_status.setTextColor(mxgui::black); + tv_log_status.setBackgroundColor(mxgui::green); + } + else + { + tv_log_status.setText("SD ERR"); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + } + } + + VerticalLayout root{5}; + + TextView btn_mark{"Mark Log (1)"}; + TextView btn_stop{"Stop"}; + TextView btn_reset{"Reset"}; + +private: + void setColor(TextView* tv, float val) + { + float delta = abs_max - abs_min; + if (val >= abs_max - delta * COLOR_PERCENTILE) + { + tv->setTextColor(mxgui::green); + } + else if (val <= abs_min + delta * COLOR_PERCENTILE) + { + tv->setTextColor(0xFDC0); // Light orange + } + else + { + tv->setTextColor(mxgui::white); + } + } + float abs_max = std::numeric_limits<float>::lowest(); + float abs_min = std::numeric_limits<float>::max(); + + GridLayout grid_channels{NUM_CHANNEL_ROWS + 1, NUM_COLS}; + GridLayout grid_title{1, 2}; + GridLayout grid_buttons{1, 3}; + + TextView tv_log_status{"SD ERR"}; + TextView tv_title{"Energy Scan"}; + + array<std::string, NUM_COLS / 2> titles = {"CH", "Curr", "Min", "Max"}; + array<TextView*, NUM_COLS> col_titles; + + array<TextView*, NUM_CHANNELS> col_names; + array<TextView*, NUM_CHANNELS> col_current; + array<TextView*, NUM_CHANNELS> col_min; + array<TextView*, NUM_CHANNELS> col_max; + + array<Stats, NUM_CHANNELS> ch_stats; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/RespectScreen.h b/src/tests/drivers/xbee/gui/RespectScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..5b82d461974a8b544b2c0824ca370aec74a79902 --- /dev/null +++ b/src/tests/drivers/xbee/gui/RespectScreen.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <cstdint> +#include <mxgui/display.h> + +#include "res/respect.h" +#include "utils/gui/ImageView.h" + +struct RespectScreen +{ + RespectScreen() + { + + } + + ImageView root{&respect}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/StatusScreen.h b/src/tests/drivers/xbee/gui/StatusScreen.h new file mode 100644 index 0000000000000000000000000000000000000000..66b0e63241b73593df40a7042c1085f36d89e63a --- /dev/null +++ b/src/tests/drivers/xbee/gui/StatusScreen.h @@ -0,0 +1,333 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <mxgui/display.h> + +#include <cstdint> +#include <string> +#include <cstring> + +#include "utils/testutils/ThroughputCalculator.h" +#include "../XbeeTestData.h" +#include "logger/Logger.h" + +#include "utils/gui/GridLayout.h" +#include "utils/gui/OptionView.h" +#include "utils/gui/TextView.h" +#include "utils/gui/VerticalLayout.h" + +using std::to_string; + +/** + * @brief Converts tick in milliseconds to the HH:MM:SS format + */ +std::string tickToHMS(long long tick) +{ + char buf[15]; + + int h = tick / (1000 * 3600); + tick -= h * (1000 * 3600); + int m = tick / (1000 * 60); + tick -= m * (1000 * 60); + int s = tick/1000; + + snprintf(buf, 15, "%02d:%02d:%02d", h, m, s); + + return string(buf); +} + +struct StatusScreen +{ + XbeeConfig config; + + StatusScreen() + { + title.setFont(mxgui::miscFixedBold); + title.setTextColor(mxgui::black); + title.setBackgroundColor(mxgui::green); + title.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + + tv_log_status.setFont(mxgui::miscFixedBold); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + tv_log_status.setAlignment(HorizAlignment::CENTER, + VertAlignment::CENTER); + + grid_title.setCell(&title, 0, 0); + grid_title.setCell(&tv_log_status, 0, 1); + + grid_config.setCell(&tv_cfg_txt_tx_enabled, 0, 0); + grid_config.setCell(&tv_cfg_tx_enabled, 0, 1); + + grid_config.setCell(&tv_cfg_txt_pkt_size, 0, 2); + grid_config.setCell(&tv_cfg_pkt_size, 0, 3); + + grid_config.setCell(&tv_cfg_txt_snd_interval, 1, 0); + grid_config.setCell(&tv_cfg_snd_interval, 1, 1); + + grid_config.setCell(&tv_cfg_txt_freq_hop, 1, 2); + grid_config.setCell(&tv_cfg_freq_hop, 1, 3); + + grid_config.setCell(&tv_cfg_txt_data_rate, 2, 0); + grid_config.setCell(&tv_cfg_data_rate, 2, 1); + + tv_log_title.setTextColor(mxgui::blue); + + grid_log_status.setCell(&tv_log_title, 0, 0); + + grid_log_status.setCell(&tv_log_txt_buf_written, 1, 0); + grid_log_status.setCell(&tv_log_buf_written, 1, 1); + + grid_log_status.setCell(&tv_log_txt_buf_ttw, 1, 2); + grid_log_status.setCell(&tv_log_buf_ttw, 1, 3); + + grid_log_status.setCell(&tv_log_txt_buf_dropped, 2, 0); + grid_log_status.setCell(&tv_log_buf_dropped, 2, 1); + + grid_log_status.setCell(&tv_log_txt_buf_failed, 2, 2); + grid_log_status.setCell(&tv_log_buf_failed, 2, 3); + + tv_tx_title.setTextColor(mxgui::blue); + + grid_data.setCell(&tv_tx_title, 0, 0); + + grid_data.setCell(&tv_tx_txt_num_pkt, 1, 0); + grid_data.setCell(&tv_tx_num_pkt, 1, 1); + + grid_data.setCell(&tv_tx_txt_num_fail, 2, 0); + grid_data.setCell(&tv_tx_num_fail, 2, 1); + + grid_data.setCell(&tv_tx_txt_pps, 3, 0); + grid_data.setCell(&tv_tx_pps, 3, 1); + + grid_data.setCell(&tv_tx_txt_TTS, 4, 0); + grid_data.setCell(&tv_tx_tts, 4, 1); + + grid_data.setCell(&tv_tx_txt_last_status, 5, 0); + grid_data.setCell(&tv_tx_last_status, 5, 1); + + grid_data.setCell(&tv_tx_txt_last_err, 6, 0); + grid_data.setCell(&tv_tx_last_err, 6, 1); + + tv_rx_title.setTextColor(mxgui::blue); + grid_data.setCell(&tv_rx_title, 0, 2); + + grid_data.setCell(&tv_rx_txt_num_pkt, 1, 2); + grid_data.setCell(&tv_rx_num_pkt, 1, 3); + + grid_data.setCell(&tv_rx_txt_num_fail, 2, 2); + grid_data.setCell(&tv_rx_num_fail, 2, 3); + + grid_data.setCell(&tv_rx_txt_lost, 3, 2); + grid_data.setCell(&tv_rx_lost, 3, 3); + + grid_data.setCell(&tv_rx_txt_RSSI, 4, 2); + grid_data.setCell(&tv_rx_RSSI, 4, 3); + + grid_data.setCell(&tv_rx_txt_data_rate, 5, 2); + grid_data.setCell(&tv_rx_data_rate, 5, 3); + + grid_data.setCell(&tv_rx_txt_pps, 6, 2); + grid_data.setCell(&tv_rx_pps, 6, 3); + + grid_data.setCell(&tv_rx_txt_time_since_last_rx, 7, 2); + grid_data.setCell(&tv_rx_time_since_last_rx, 7, 3); + + btn_mark.setSelectable(true); + btn_mark.setAlignment(HorizAlignment::CENTER, + VertAlignment::CENTER); + btn_mark.setBackgroundColor(mxgui::darkGrey); + + btn_stop.setSelectable(true); + btn_stop.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER); + btn_stop.setBackgroundColor(mxgui::darkGrey); + + grid_buttons.setCell(&btn_mark, 0); + grid_buttons.setCell(&btn_stop, 1); + grid_buttons.setDrawBorder(true); + + root.addView(&grid_title, 0.8); + root.addView(&grid_config, 1.5); + root.addView(&grid_log_status, 1.5); + root.addView(&grid_data, 5); + root.addView(&grid_buttons, 1); + } + + void updateConfig(XbeeConfig cfg) + { + // Update GUI with selected config values + tv_cfg_tx_enabled.setText(cfg.tx_enabled ? "Enabled" : "Disabled"); + tv_cfg_pkt_size.setText(std::to_string(cfg.packet_size)); + tv_cfg_snd_interval.setText(cfg.send_interval == 0 + ? "Cont" + : std::to_string(cfg.send_interval)); + + tv_cfg_freq_hop.setText(cfg.freq_hop ? "Enabled" : "Disabled"); + tv_cfg_data_rate.setText(cfg.data_rate_80k ? "80 kbps" : "10 kbps"); + } + + void updateLogStatus(Logger& logger) + { + LogStats stats = logger.getLogStats(); + + if (logger.getLogNumber() >= 0) + { + string log_name = logger.getFileName(logger.getLogNumber()); + + tv_log_status.setText(log_name); + tv_log_status.setTextColor(mxgui::black); + tv_log_status.setBackgroundColor(mxgui::green); + } + else + { + tv_log_status.setText("SD ERR"); + tv_log_status.setTextColor(mxgui::white); + tv_log_status.setBackgroundColor(mxgui::red); + } + + tv_log_buf_dropped.setText(to_string(stats.statDroppedSamples)); + + if (stats.statDroppedSamples > 0) + { + tv_log_buf_dropped.setBackgroundColor(mxgui::red); + } + + tv_log_buf_failed.setText(to_string(stats.statWriteFailed) + " (" + + to_string(stats.statWriteError) + ")"); + + if (stats.statWriteError != 0) + { + tv_log_buf_failed.setBackgroundColor(mxgui::red); + } + + tv_log_buf_written.setText(to_string(stats.statBufferWritten)); + tv_log_buf_ttw.setText(to_string(stats.statWriteTime) + " ms"); + } + + void updateXbeeStatus(DataRateResult res_rcv, DataRateResult res_snd, + TxData txd, RxData rxd, Xbee::XbeeStatus xbee_status) + { + char str_buf[30]; + + tv_tx_num_pkt.setText( + to_string(txd.tx_success_counter + txd.tx_fail_counter)); + tv_tx_num_fail.setText(to_string(txd.tx_fail_counter)); + + snprintf(str_buf, 30, "%.1f pkt/s", res_snd.packets_per_second); + + tv_tx_pps.setText(str_buf); + tv_tx_tts.setText(to_string(txd.time_to_send) + " ms"); + + tv_tx_last_status.setText(to_string(xbee_status.last_tx_status)); + tv_tx_last_err.setText(to_string(xbee_status.last_tx_status_error)); + + tv_rx_num_pkt.setText(to_string(rxd.rcv_count)); + tv_rx_num_fail.setText(to_string(rxd.rcv_errors)); + // tv_rx_num_fail.setText(to_string(int_counter) + " " + + // to_string(GpioATTN::value())); + tv_rx_lost.setText(to_string(rxd.packets_lost)); + + tv_rx_RSSI.setText(to_string(rxd.RSSI) + " dB"); + + snprintf(str_buf, 30, "%.0f B/s", res_rcv.data_rate); + tv_rx_data_rate.setText(str_buf); + + snprintf(str_buf, 30, "%.1f pkt/s", res_rcv.packets_per_second); + tv_rx_pps.setText(str_buf); + + tv_rx_time_since_last_rx.setText(tickToHMS(miosix::getTick() - rxd.last_packet_timestamp)); + } + + VerticalLayout root{10}; + + TextView tv_cfg_tx_enabled{"Disabled"}; + TextView tv_cfg_pkt_size{"256 B"}; + TextView tv_cfg_snd_interval{"Cont"}; + TextView tv_cfg_freq_hop{"Enabled"}; + TextView tv_cfg_data_rate{"10 kbps"}; + + TextView tv_log_status{"SD ERR"}; + + TextView tv_log_buf_dropped{"0"}; + TextView tv_log_buf_failed{"0 (0)"}; + TextView tv_log_buf_written{"0"}; + TextView tv_log_buf_ttw{"0 ms"}; + + TextView tv_tx_num_pkt{"0"}; + TextView tv_tx_num_fail{"0"}; + TextView tv_tx_pps{"0 pkt/s"}; + TextView tv_tx_tts{"- ms"}; + TextView tv_tx_last_status{"0"}; + TextView tv_tx_last_err{"0"}; + + TextView tv_rx_num_pkt{"0"}; + TextView tv_rx_num_fail{"0"}; + TextView tv_rx_lost{"0"}; + TextView tv_rx_RSSI{"-40 dB"}; + TextView tv_rx_data_rate{"0 B/s"}; + TextView tv_rx_pps{"0 pkt/s"}; + TextView tv_rx_time_since_last_rx{"00:00:00"}; + + TextView btn_mark{"Mark Log (1)"}; + TextView btn_stop{"Stop"}; + +private: + TextView title{"Xbee Status"}; + GridLayout grid_title{1, 2}; + GridLayout grid_config{3, 4}; + GridLayout grid_buttons{1, 2}; + GridLayout grid_log_status{3, 4}; + GridLayout grid_data{8, 4}; + + TextView tv_cfg_txt_tx_enabled{"TX"}; + TextView tv_cfg_txt_pkt_size{"Pkt size"}; + TextView tv_cfg_txt_snd_interval{"Interv"}; + TextView tv_cfg_txt_freq_hop{"Freq hop"}; + TextView tv_cfg_txt_data_rate{"Data rate"}; + + TextView tv_log_title{"LOG"}; + TextView tv_log_txt_buf_dropped{"Buf drops"}; + TextView tv_log_txt_buf_failed{"Wrt fails"}; + TextView tv_log_txt_buf_written{"Wrt succ"}; + TextView tv_log_txt_buf_ttw{"TTW"}; + + TextView tv_tx_title{"TX"}; + + TextView tv_tx_txt_num_pkt{"Sent"}; + TextView tv_tx_txt_num_fail{"Fails"}; + TextView tv_tx_txt_pps{"PPS"}; + TextView tv_tx_txt_TTS{"TTS"}; + TextView tv_tx_txt_last_status{"Status"}; + TextView tv_tx_txt_last_err{"Last err"}; + + TextView tv_rx_title{"RX"}; + + TextView tv_rx_txt_num_pkt{"Recv"}; + TextView tv_rx_txt_num_fail{"Fails"}; + TextView tv_rx_txt_lost{"Lost"}; + TextView tv_rx_txt_RSSI{"RSSI"}; + TextView tv_rx_txt_data_rate{"DR"}; + TextView tv_rx_txt_pps{"PPS"}; + TextView tv_rx_txt_time_since_last_rx{"No RX dt"}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/XbeeGui.h b/src/tests/drivers/xbee/gui/XbeeGui.h new file mode 100644 index 0000000000000000000000000000000000000000..b24af1551bda6c358085cd6b312590a6621e9d09 --- /dev/null +++ b/src/tests/drivers/xbee/gui/XbeeGui.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <mxgui/display.h> + +#include <cstdint> +#include <functional> + +#include "ConfigScreen.h" +#include "StatusScreen.h" +#include "EnergyScanScreen.h" +#include "EndScreen.h" +#include "RespectScreen.h" +#include "utils/gui/ScreenManager.h" + +class XbeeGUI +{ +public: + enum Screens : uint8_t + { + SCREEN_CONFIG, + SCREEN_STATUS, + SCREEN_ENERGYSCAN, + SCREEN_END, + SCREEN_RESPECT + }; + + XbeeGUI() + : screen_manager(mxgui::DisplayManager::instance(), 4) + { + screen_manager.addScreen(SCREEN_CONFIG, &screen_config.root); + screen_manager.addScreen(SCREEN_STATUS, &screen_status.root); + screen_manager.addScreen(SCREEN_ENERGYSCAN, &screen_energy.root); + screen_manager.addScreen(SCREEN_END, &screen_end.root); + screen_manager.addScreen(SCREEN_RESPECT, &screen_respect.root); + + screen_manager.start(); + } + + ~XbeeGUI() + { + screen_manager.stop(); + } + + ScreenManager screen_manager; + + ConfigScreen screen_config{}; + StatusScreen screen_status{}; + EnergyScanScreen screen_energy{}; + EndScreen screen_end{}; + RespectScreen screen_respect{}; +}; \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/res/respect.cpp b/src/tests/drivers/xbee/gui/res/respect.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f32d2330f632a2cb03d1776bb267480a596fa5e2 --- /dev/null +++ b/src/tests/drivers/xbee/gui/res/respect.cpp @@ -0,0 +1,9635 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +//This file has been automatcally generated by pngconverter utility +//Please do not edit +#include "respect.h" + +using namespace mxgui; + +static const short int height=320; +static const short int width =240; + +static const unsigned short pixelData[]={ + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2145,6371,19049, + 29614,35921,38066,38034,35953,29614,21130,10597, + 2145,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,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,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,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,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,32,10597,33808,50712,61277,65503, + 65535,65535,65535,65535,65535,65535,65535,65535, + 54970,40147,21130,4258,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,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,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,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,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,10597,42260,63422,65535,65535,65535,65535, + 65503,65503,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,57051,31727,6371,32,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,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,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,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,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, + 29582,61277,65503,65535,65503,59164,42260,31727, + 23243,19017,16936,21162,27501,35953,48631,61277, + 63422,65535,65535,65535,65535,61277,35921,4258, + 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, + 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, + 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, + 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, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,4226,40179, + 65535,65535,61277,38034,14823,32,0,0, + 0,0,0,32,0,0,32,2145, + 19017,33840,57083,65535,65535,65535,65535,52857, + 14823,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,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,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,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,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,4226,44405,65503, + 63390,38034,6371,0,32,4226,14823,23275, + 27469,25388,21162,16904,10565,4258,32,32, + 0,0,2145,25388,57051,65535,65535,65535, + 61309,19049,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,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,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,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,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,42260,65503,57083, + 19017,2113,32,4258,33840,57083,65503,65503, + 65535,65535,65503,65503,63422,61277,50744,38066, + 23243,6339,32,32,4258,38066,65535,65535, + 65535,59196,10565,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,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,31727,65503,52857,12678, + 0,0,14791,52857,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65503,63390,44373,19049,2113,32,27501,63390, + 65535,65535,50744,2113,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,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,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,14823,63390,57051,8452,0, + 0,12710,59164,65535,65535,63390,63390,63422, + 65535,65535,65535,65535,65535,65503,65535,65535, + 65535,65503,65503,65503,48599,10565,0,27501, + 65503,65535,65503,25388,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,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,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,2145,54938,63422,16936,0,0, + 4258,54938,65535,63390,31695,12678,6371,12678, + 14823,19017,23243,27469,29582,31727,35953,40179, + 52825,63390,65535,65535,65503,57083,12678,32, + 40147,65535,65535,52857,2113,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,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,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,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,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,31727,65535,31695,32,0,0, + 35953,65535,63390,21162,0,0,0,0, + 0,0,0,32,0,0,0,32, + 0,12678,38066,63422,65535,65535,52825,4258, + 6371,59164,65535,65503,19049,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,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,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,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,0,0,0, + 0,0,0,0,0,0,0,0, + 0,10565,61309,54938,4226,0,0,14791, + 63422,65503,31695,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,19017,59196,65535,65535,27469, + 0,29582,65535,65535,42260,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,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,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,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,0,0,0, + 0,0,0,0,32,0,0,0, + 32,42292,65535,23243,0,0,2145,48599, + 65535,44405,2113,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,16904,61309,65535,52857, + 2113,6371,61277,65535,57051,2145,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,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,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,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,0,0, + 0,0,0,0,0,0,0,0, + 12710,63390,52857,2113,32,0,19049,63422, + 57083,8484,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,27501,65535,63422, + 12710,32,46486,65535,63422,12710,32,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,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,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,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,0, + 0,0,0,0,0,32,0,32, + 42260,65535,29582,0,0,2113,50712,65535, + 27501,0,32,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,50744,65503, + 33808,0,21130,65535,65535,25356,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,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,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,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,0,0, + 0,0,0,0,0,0,0,6371, + 65503,61277,8452,0,0,16936,65535,54970, + 4226,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,21130,65503, + 50712,0,6371,61309,65535,40179,0,32, + 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, + 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, + 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, + 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, + 0,0,0,0,32,0,0,33808, + 65535,44373,32,0,0,44405,65503,29582, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4226,57083, + 61309,4258,32,52857,65535,54970,32,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,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,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,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,0, + 0,0,0,0,0,0,2145,54970, + 65535,23275,0,0,12678,63422,61277,8452, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,42292, + 65535,16904,0,42292,65535,59196,4258,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,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,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,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,0, + 0,0,0,0,0,0,16936,65535, + 61277,6339,0,0,35953,65535,52825,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,27469, + 65535,27469,32,29582,65535,63422,12678,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,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,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,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,0, + 0,0,0,0,0,0,42260,65535, + 50712,0,0,6339,57083,65535,38034,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,14823, + 65535,31727,0,23243,65535,65535,19017,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,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,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,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,0, + 0,0,0,0,32,4258,59164,65535, + 33808,0,32,19049,65503,65535,29582,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,32,0,8484, + 63422,35953,0,16936,65535,65535,25388,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,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,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,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,0, + 0,0,0,0,0,16936,65503,65503, + 14823,0,0,40179,65535,65535,25388,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4258, + 57083,35921,0,12710,65535,65535,31695,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,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,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,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,0, + 0,0,0,0,32,35921,65535,59196, + 4226,0,4226,57083,65535,65535,27469,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2113, + 52857,31695,0,12678,65535,65535,38066,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,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,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,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,0, + 0,0,0,0,0,52857,65535,48599, + 0,0,12710,65535,65535,65535,33808,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,8452, + 61309,27501,0,10565,65535,65535,44373,32, + 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, + 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, + 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, + 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, + 0,0,0,0,8484,63390,65535,31727, + 0,32,31727,65535,65535,65535,44373,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 63390,21162,0,12678,65535,65503,44405,32, + 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, + 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, + 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, + 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, + 0,0,0,0,23275,65503,65503,19017, + 0,32,48631,65535,65535,65535,50712,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 63390,16936,0,14791,65535,65535,44373,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,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,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,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,0, + 0,0,0,32,40147,65535,63390,8452, + 0,4226,59196,65535,65535,65535,57083,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 61309,10565,0,16904,65535,65535,42292,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,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,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,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,0, + 0,0,0,2113,52857,65535,57083,2113, + 32,16904,65535,65535,65535,65535,59196,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 61309,6339,0,19017,65503,65535,42260,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,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,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,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,0, + 0,0,0,6339,63390,65535,46486,32, + 0,29582,65535,65535,65535,65535,61309,2145, + 0,0,0,0,0,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,19049, + 59196,2145,0,25356,65535,65535,38066,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,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,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,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,0, + 0,0,0,14823,65503,65535,35953,0, + 0,42260,65535,65535,65535,65535,61309,6371, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,29614, + 57051,2113,0,31695,65535,65535,33840,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,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,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,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,0, + 0,0,0,29582,65535,65535,25356,0, + 2113,54938,65535,65535,65503,65535,63390,8484, + 32,0,2113,4226,10597,19017,27469,31695, + 31695,25356,14823,8484,2113,32,0,32, + 0,0,0,0,0,0,2113,46486, + 50744,32,0,40147,65535,65535,27469,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,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,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,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,0, + 0,0,0,40147,65535,65503,16936,0, + 4258,61309,65535,65535,65535,65535,65503,23243, + 2145,16904,40147,59164,65503,65535,65535,65535, + 65535,65535,65503,63422,52857,31727,8484,2145, + 0,0,0,0,32,0,4258,57083, + 48599,0,32,46486,65535,65503,21162,32, + 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, + 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, + 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, + 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, + 0,0,32,48599,65535,63390,10565,0, + 10597,65503,65535,65535,65535,65535,65503,63422, + 57051,65503,65535,65535,65535,65535,65503,65535, + 65535,65535,65535,65535,65535,65535,63390,44373, + 16904,32,0,0,0,32,23275,65535, + 42292,32,2113,52825,65535,65503,16936,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,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,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,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,0, + 0,0,2145,54938,65535,61309,8452,0, + 21162,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,59164,48631,35921, + 29582,29582,38066,50712,59164,65535,65535,65535, + 63422,44405,12678,32,0,4258,54938,65535, + 35953,0,4226,54970,65535,63390,12678,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,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,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,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,0, + 0,0,4258,61277,65535,59196,4258,0, + 31727,63390,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,54970,23275,4258,0,32, + 0,0,32,32,6371,27469,54938,65535, + 65535,65503,61277,31727,10565,42260,65535,65535, + 29582,0,6371,61277,65535,59196,4258,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,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,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,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,0, + 0,32,8452,63390,65535,59164,2145,0, + 21130,48599,59164,63422,65535,65535,65535,65535, + 65535,65503,65535,52825,40179,33808,23275,14791, + 10565,6339,2113,0,0,0,4226,35953, + 65535,65535,65535,65535,63390,65535,65535,65535, + 23275,0,8484,63422,65535,57083,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,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,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,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,0,0, + 0,0,10565,65503,65535,52825,32,0, + 32,32,6339,12710,31695,46486,59196,63422, + 65503,65535,65535,65535,65535,65535,65535,65535, + 63422,59196,54970,48599,40147,29582,19017,14823, + 59196,65535,65535,65503,65535,65535,65535,65503, + 16936,0,14791,65535,65535,46486,0,32, + 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, + 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, + 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, + 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, + 0,0,12678,65535,65535,50744,32,0, + 32,0,0,32,0,0,4226,12678, + 25356,38066,50744,59196,65503,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65503, + 65503,65535,65535,65535,65535,65535,65535,63390, + 10565,32,23275,65535,65535,38066,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,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,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,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,0,0, + 0,0,14823,65535,65535,48631,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,4258,12710,23275,38066,48631, + 57083,63390,65503,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,63390,42292, + 32,0,31727,65535,65535,25388,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,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,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,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,0,0, + 0,0,16936,65535,65535,46518,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 4226,8484,16936,29582,42292,52857,61309,63422, + 65503,65503,65535,65535,65503,59164,31727,2145, + 0,32,38066,65535,65535,16936,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,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,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,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,0,0, + 0,0,16936,65535,65535,46486,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,12678, + 21130,27501,29614,25388,16904,2145,0,2113, + 0,0,48599,65535,63390,8484,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,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,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,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,0,0, + 0,0,16936,65535,65535,44373,32,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,32,0,32,0, + 0,32,50712,65535,61277,6371,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,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,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,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,0,0, + 0,0,16936,65535,65535,42260,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,32,0, + 0,2145,52857,65535,57083,2145,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,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,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,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,0,0, + 0,0,16936,65535,65535,40179,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,32, + 0,0,0,32,0,0,0,0, + 0,4226,57051,65535,54970,32,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,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,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,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,0,0, + 0,0,12710,65535,65535,42260,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,4258,59196,65503,48599,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,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,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,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,0,0,0, + 0,0,10597,65503,65535,42260,32,0, + 0,0,0,0,0,0,0,0, + 0,32,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,6371,61277,65535,48599,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,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,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,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,0,0,0, + 0,0,8484,63422,65535,42260,32,0, + 0,0,0,0,0,0,0,0, + 29582,27501,14791,6339,32,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,8452,63390,65535,48599,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,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,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,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,0,0,0, + 0,0,6371,63390,65535,42260,32,0, + 0,0,0,0,0,0,0,0, + 21162,59196,65535,61277,52825,42260,31695,29582, + 21162,19017,14791,12678,8484,6339,2145,2145, + 2145,2113,32,32,0,0,0,0, + 32,10565,63422,65535,48599,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,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,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,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,0,0,0, + 0,0,4226,59196,65535,44373,32,0, + 0,0,0,0,0,0,0,0, + 32,12710,52857,65503,65535,65535,65535,65535, + 65535,65535,65503,65503,65535,65503,65503,61277, + 59164,54970,54938,46518,16936,0,0,32, + 0,12678,65535,65535,46518,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,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,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,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,0,0,0, + 0,0,2145,54970,65535,44373,32,0, + 0,0,0,0,0,0,0,0, + 0,0,4258,50744,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 63422,63390,54970,42292,14823,0,0,0, + 0,14791,65535,65535,46518,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,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,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,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,0,0,0, + 0,0,32,48631,65535,42292,0,0, + 0,0,0,0,0,0,0,0, + 0,0,21130,54970,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65503,59164, + 29582,10597,2113,32,0,0,0,32, + 0,19017,65535,65535,46518,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,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,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,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,0,0,0, + 0,32,0,44405,65535,42292,0,0, + 0,0,0,0,0,0,0,0, + 16904,46518,65503,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65503, + 38066,8452,0,0,0,0,0,0, + 0,21162,65535,65503,46518,32,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,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,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,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,0,0, + 0,0,0,42292,65535,42292,0,0, + 0,0,0,0,0,0,32,2145, + 52857,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,57051,21130,0,0,0,0,0, + 0,23275,65535,65535,44405,32,0,32, + 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, + 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, + 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, + 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, + 0,0,0,42292,65535,42292,0,0, + 0,0,0,0,0,0,0,32, + 6339,31695,38066,40179,40147,40179,42292,44373, + 46486,46518,48631,50744,54938,57051,59164,59196, + 65503,65535,61309,35953,0,0,0,0, + 0,25388,65535,65535,44405,32,0,32, + 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, + 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, + 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, + 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, + 0,0,0,42292,65535,42292,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,32, + 0,0,32,32,32,2113,2113,2113, + 2113,4258,8452,6339,0,0,0,0, + 0,27501,65535,65535,44373,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,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,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,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,0,0,0, + 0,0,0,42292,65535,42292,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,0,0,2113,4226,6339, + 6339,6371,6371,6371,6371,6371,8452,6371, + 4258,0,0,0,0,0,0,0, + 0,29614,65535,65535,44373,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,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,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,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,0,0,0, + 0,0,32,46518,65535,40179,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,44373,61277, + 63390,61309,61277,61309,61309,61277,63390,54970, + 16904,0,0,0,0,0,0,0, + 0,33808,65535,65535,42292,2113,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,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,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,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,0,0, + 0,0,32,52825,65535,38034,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,23243, + 38066,48599,54938,54970,57051,48631,31727,6371, + 0,0,0,0,0,0,0,0, + 0,33840,65535,65535,42260,32,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,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,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,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,0,0, + 0,0,6339,61277,65535,33840,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,2113,2113,32,0,0,0, + 0,0,0,32,0,0,0,0, + 0,38034,65535,65535,40147,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,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,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,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,0,0,0, + 32,32,12710,65503,65535,31695,0,32, + 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,32,0,0,0,0,0, + 0,38066,65535,65503,38066,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,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,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,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,0,0,0, + 32,0,31695,65535,65535,27469,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,42260,65535,65535,38066,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,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,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,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,0,0,0, + 0,2113,52825,65535,65535,19049,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,44373,65535,65535,38034,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,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,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,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,0,0,0, + 0,10597,65535,65535,63390,10565,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,46486,65535,65535,33840,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,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,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,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,0,0,0, + 0,33840,65535,65535,61277,4226,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,48599,65535,65535,31727,0,0,32, + 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, + 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, + 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, + 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, + 2145,52857,65535,65535,50744,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, + 32,50744,65535,65535,27501,32,0,32, + 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, + 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, + 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, + 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, + 10565,63422,65535,65535,35921,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, + 32,52825,65535,65535,25356,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,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,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,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,0,0,0, + 29582,65535,65535,65535,19049,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, + 2113,54938,65535,65503,19017,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,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,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,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,0,0,0, + 44373,65535,65535,63390,10565,0,32,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, + 2113,57051,65535,65503,14823,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,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,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,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,32,0,0,4258, + 57083,65535,65535,59164,2113,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, + 2145,59196,65535,63390,6371,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,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,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,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,0,0,12678, + 63422,65535,65535,46486,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, + 4226,59196,65535,61277,4226,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,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,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,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,0,0,23275, + 65535,65535,65535,31695,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, + 6339,61309,65535,54938,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,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,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,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,0,0,32,38066, + 65535,65535,65503,19049,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, + 8484,63390,65535,48631,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,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,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,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,32,0,2113,52857, + 65503,65535,63390,8452,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, + 10565,65503,65535,40179,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,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,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,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,0,0,4258,61309, + 65535,65535,57083,4226,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, + 14791,65535,65535,27501,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,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,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,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,0,0,16936,65535, + 65535,65535,46518,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, + 21162,65535,65535,16936,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,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,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,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,0,0,29582,65535, + 65535,65535,35953,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, + 33808,65535,63422,12678,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,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,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,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, + 2113,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,46486,65535, + 65535,65535,25356,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,32,0,0,0, + 42292,65535,61277,6371,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,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,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,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,0,2145,52857,65535, + 65535,65535,16936,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,32,0,0,2113, + 50744,65535,59164,2113,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,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,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,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,4226,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,6339,61277,65535, + 65535,63390,8484,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,4258, + 57083,65535,54970,32,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,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,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,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,8452,57051,42292,14823,6371, + 4258,2145,0,0,0,0,0,0, + 0,0,0,0,0,8484,63422,65535, + 65535,61277,6339,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,6371, + 61309,65535,52825,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,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,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,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,33808,65535,65535,65535,61309, + 61277,59164,14823,0,0,0,0,0, + 0,0,0,0,0,19049,65535,65535, + 65535,57083,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,12678, + 65535,65535,44373,32,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,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,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,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,32,6371,59164,65535,65535,65535,65535, + 65535,65535,59196,10565,0,0,0,32, + 0,0,0,0,0,27469,65535,65535, + 65535,52825,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,21130, + 65535,65535,42292,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,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,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,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,32,38066,65535,65535,65535,65535,59196, + 54938,65503,65535,52857,6371,0,0,0, + 0,0,0,0,0,38034,65535,65535, + 65535,44373,32,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,33808, + 65535,65535,40179,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,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,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,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,27501,65503,65535,65503,65535,54938,10565, + 6339,54938,65535,65535,46518,4226,0,0, + 0,0,0,0,0,44405,65535,65535, + 65535,38066,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,42260, + 65535,65535,38066,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,0,0,0,2145, + 29614,63390,65535,65535,65535,65503,25356,32, + 0,21130,65535,65535,65503,35921,0,0, + 0,0,0,0,32,50712,65535,65535, + 65535,25388,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,32,50712, + 65535,65535,38034,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,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,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,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,8484,44405, + 65535,61277,57083,65503,65535,61309,6371,0, + 0,8452,63390,65535,65535,63390,16904,0, + 0,0,0,0,2113,54970,65535,65535, + 65535,21162,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,2113,52857, + 65535,65535,35953,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,32,31695,61277,65503, + 44373,6371,10597,65503,65535,46486,32,0, + 0,32,59164,65535,65535,65535,48599,2145, + 0,0,0,0,4258,61277,65535,65535, + 63422,14791,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,4226,59164, + 65535,65535,33840,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,32,4258,44405,65535,65503,40147, + 2145,32,27469,65535,65503,19049,0,0, + 0,0,50712,65535,65535,65503,63422,12710, + 0,0,0,0,8452,63390,65535,65535, + 63390,10565,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,4258,61309, + 65535,65535,31727,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,4258,48631,65535,65535,38066,32, + 32,10597,61309,65535,46518,32,0,32, + 0,32,38066,65535,65535,65535,65535,33808, + 32,0,0,0,12710,65535,65535,65535, + 61277,8484,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,6371,63390, + 65535,65535,31695,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2113,44405,65535,65503,44405,32,0, + 32,46486,65535,61277,10565,0,32,0, + 0,0,31695,65535,65535,65535,65535,50744, + 32,0,0,0,19017,65535,65535,65535, + 59196,4226,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,6371,61309, + 65535,65535,31695,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,29582,65535,65535,54970,6371,0,0, + 16904,63422,65503,27469,0,0,0,0, + 0,0,27469,65535,65535,65535,65535,61277, + 6339,0,0,0,23275,65535,65535,65535, + 54970,32,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,6339,61277, + 65535,65535,31695,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 10597,63390,65535,63390,14823,0,32,6339, + 54970,65535,44373,32,0,32,0,0, + 0,0,27469,65535,65535,65535,65535,65535, + 14791,32,0,0,27501,65535,65535,65535, + 50712,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,2145,4226,8452,10565,8484,4258, + 2145,32,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,59196, + 65535,65535,29614,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2113, + 46518,65535,65535,42260,32,0,8452,48599, + 65535,52857,4258,0,0,0,0,32, + 32,0,42260,65535,65535,65535,65535,65535, + 31727,0,0,0,35953,65535,65535,65535, + 42292,32,0,0,0,0,0,0, + 0,0,0,0,0,32,32,6371, + 27469,44373,54970,63390,63422,65503,65503,63422, + 59164,50744,40179,29582,8452,32,0,0, + 0,0,0,0,0,0,4226,57083, + 65535,65535,29582,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,32,21162, + 63422,65535,61277,10565,0,12678,52857,65535, + 61277,14791,0,0,0,0,0,0, + 2113,21162,63422,65535,65535,65535,65535,65535, + 46518,32,0,0,42292,65535,65535,65535, + 38034,0,0,0,0,0,0,0, + 0,0,0,32,0,6339,38034,61309, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,63390,40147,8484,0, + 0,0,0,0,0,0,2113,54970, + 65535,65535,29582,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,2145,54970, + 65535,65535,31695,0,19049,59196,65535,63390, + 21162,32,0,0,0,0,4258,21162, + 44405,63390,65535,65535,65535,65535,65535,65535, + 54938,2145,0,32,50744,65535,65535,65535, + 29582,0,0,0,0,0,0,0, + 0,0,32,32,21162,59164,65503,65535, + 65535,63390,61309,59164,52857,54970,57083,57083, + 63390,65503,65503,65503,65535,65535,59164,14823, + 0,0,0,0,0,0,32,52857, + 65535,65535,29582,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,31695,65535, + 65535,61309,23275,33840,63422,65535,61309,23275, + 32,0,0,0,8484,38066,61277,65503, + 65535,65535,65535,65535,65535,65535,65535,65535, + 61309,10565,32,2145,57051,65535,65535,65535, + 23275,0,32,0,0,0,0,0, + 0,32,32,27501,61277,52825,35953,25356, + 16904,6371,2113,2113,2113,2113,32,0, + 6339,12710,23275,35921,52825,63422,65503,61277, + 12710,0,0,0,32,0,0,50744, + 65535,65535,29582,32,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,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,10565,63390,65503, + 65535,65535,65535,65535,65535,59164,16936,0, + 0,0,4258,29582,61309,65503,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,23275,0,4258,61277,65535,65535,65503, + 19017,0,32,0,0,0,0,0, + 0,0,6371,21162,4258,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,8484,33808,52857, + 44405,2113,0,0,0,0,32,48631, + 65535,65535,35921,32,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,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,46486,65535,65535, + 65535,65535,65535,65535,50744,10565,0,0, + 0,21162,54970,65503,65503,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,48599,2145,21162,63422,65535,65535,65535, + 14791,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,32,32, + 16936,4258,32,32,0,0,0,48631, + 65535,65535,42260,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,0,2113,19049,63422,65535,65535, + 65535,65535,65535,46486,4226,0,32,12710, + 46518,65503,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65503,42260,61277,65535,65535,65535,63422, + 8452,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,32,0,0,0,0,48631, + 65535,65535,50744,32,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,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,4258,57051,65535,65535,65503, + 65535,65535,65535,16904,32,10565,40147,65535, + 65535,65503,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65503,65535,65535,65503,65535,65535,61277, + 6371,0,0,32,0,0,0,0, + 0,0,0,0,32,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,0,0,0,0,48631, + 65503,65535,59196,2145,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,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,33808,65535,65535,65535,65535, + 65535,65535,65535,44405,40179,61277,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,57051, + 2145,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,50744, + 65535,65535,65503,16936,32,0,0,0, + 0,0,0,32,0,0,0,0, + 0,0,32,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,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,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,10565,61277,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65503,65503,52857,38066,27469,21130,14791, + 12678,16904,23243,29582,42260,54970,65503,65535, + 65535,65535,65535,65535,65535,65535,65535,52825, + 32,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,32,52825, + 65535,65535,65535,40147,32,0,0,0, + 0,32,16936,35921,38034,31695,21130,10565, + 2145,32,32,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,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,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,2113,44405,65535,65535,65535,65535,65535, + 65535,65535,65503,65535,65535,65535,65535,65535, + 61309,42292,14791,4258,8452,6339,2113,0, + 32,0,0,0,0,32,16904,42292, + 63422,65535,65535,65535,65535,65535,65535,46486, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,2113,2113,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2113,52857, + 65535,65535,65535,59164,10565,8484,12710,19049, + 27469,40179,65535,65535,65535,65535,65535,63422, + 59196,46486,29614,16936,8484,4226,32,2113, + 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, + 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, + 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,21130,65503,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65503,48631, + 12710,2145,25388,57051,63422,61309,52857,38034, + 19017,4258,0,32,0,0,0,2113, + 10597,46518,65535,65535,65535,65535,65535,42292, + 0,0,0,0,0,0,0,0, + 0,0,0,0,29614,54970,50744,46486, + 38034,35921,31727,29614,27501,27469,23275,23243, + 19049,16904,10565,6371,0,0,0,0, + 0,0,0,0,0,0,2145,54938, + 65535,65535,65535,65535,63390,63422,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65503,65535,65535,63390,61277,54938,48599, + 33808,19017,2145,32,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,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,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, + 4258,54970,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,63422,65535,63390,33808,32, + 32,8452,46518,57051,63390,65535,65535,65503, + 65535,61277,42292,23243,4258,32,0,32, + 0,4258,46486,65535,65535,65535,65535,35953, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,8452,16936,23275, + 29614,33840,42260,46486,50744,54938,59164,61309, + 63422,63422,63390,63390,54970,42260,23275,10597, + 32,0,0,0,0,0,4226,59164, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65503,65535,65535, + 65535,65503,57083,31727,8452,32,0,32, + 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, + 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, + 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, + 35921,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,61309,19049,32,0, + 0,0,2145,12678,59196,65535,65535,65535, + 65535,65535,65535,65503,54970,16904,0,0, + 0,0,8452,57083,65535,65535,65535,29614, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,32,2113,2145,4258, + 10597,16904,25356,29614,40147,44373,50712,54938, + 38034,2113,0,0,0,0,6339,59196, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,63422,61277,59164,61277, + 61277,63422,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,61277,25388,32,0, + 32,32,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,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,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,12678, + 61309,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,59196,14823,0,32,0, + 0,14791,40147,61277,65535,65535,65535,65535, + 65535,65535,65535,52857,16936,2113,32,0, + 32,0,0,27501,65535,65535,65535,23275, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,32, + 0,0,0,0,0,0,32,2145, + 4226,0,0,0,0,0,8452,61309, + 65535,65535,65535,65535,65535,65535,65535,63390, + 50712,38034,21130,16904,16904,16904,12678,6371, + 4258,10565,19017,31695,44405,59164,65503,65535, + 65535,65535,65535,65535,65535,63422,44373,4258, + 0,32,32,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,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,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,2113,48631, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,63390,16936,0,0,0,4258, + 42292,63422,65535,65503,65535,65535,65503,65535, + 65535,65535,65535,65503,63390,50712,25388,4258, + 0,0,32,8484,63390,65535,65535,21162, + 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,32,32,32,0,0,8484,63390, + 65535,65535,65535,65535,65535,59196,33840,8452, + 2113,2145,42292,61277,63422,65503,63390,61309, + 54938,38034,23275,10565,2113,2145,12678,33808, + 48599,61277,65535,65535,65535,65535,65535,50744, + 8484,0,32,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,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,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,32,27501,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,63390,21130,0,0,0,0,35953, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,54938, + 6371,0,0,2113,48631,65535,65535,16936, + 0,0,0,0,0,0,0,0, + 0,32,0,0,0,0,32,0, + 0,32,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10597,65503, + 65535,65535,65535,65535,61309,16904,32,0, + 0,0,14823,54938,65535,65535,65535,65535, + 65503,65503,65503,65503,52857,27501,6371,32, + 0,6371,35953,61309,65535,65535,65535,65535, + 52857,6371,32,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,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,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,8484,61277,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,31695,0,0,32,0,0,19049, + 25356,25356,25356,25356,25356,25356,25356,23275, + 23275,23275,23275,25356,23275,23243,23275,23275, + 10597,0,0,0,38066,65535,65535,16904, + 0,0,0,0,0,0,0,0, + 6339,42292,35921,14791,2145,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,14791,65503, + 65535,65535,65535,65535,31695,0,0,0, + 0,0,0,4226,19017,42292,61309,65535, + 65535,65535,65535,65535,65535,65535,61309,46486, + 23243,4226,0,8452,44373,65503,65535,65535, + 65535,52857,8452,32,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2113,42292,65535,65535, + 65535,65535,65535,65503,65535,65535,65535,65535, + 46518,32,0,0,0,0,0,0, + 0,0,0,0,0,0,32,0, + 0,0,0,32,0,0,0,0, + 0,0,0,0,25356,65535,65535,14791, + 0,0,0,0,0,0,0,0, + 0,31695,65503,63422,59164,38034,16936,6339, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16936,65535, + 65535,65535,65535,59164,4226,0,0,32, + 0,32,2145,6371,8452,14791,33808,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65503,61277,35921,12710,2145,23243,61277,65535, + 65535,65535,50744,4258,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,21130,65535,65535,65535, + 65535,65535,61277,31727,65535,65535,65535,65535, + 12678,0,0,0,0,0,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,0, + 32,0,0,0,14791,65535,65535,12710, + 0,0,0,0,0,0,0,0, + 0,32,33808,65503,65535,65535,65503,61277, + 42292,19049,4226,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,19049,65535, + 65535,65535,65535,38066,0,0,0,0, + 10597,33808,50744,59196,63422,63422,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,63422, + 61277,63390,63390,61309,21130,0,16904,59164, + 65535,65535,63422,44373,32,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,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,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6371,57083,65503,65535,65535, + 65535,63422,27469,19017,65535,65503,65535,42260, + 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,6339,63390,65503,12678, + 0,0,0,0,0,0,0,0, + 0,0,2113,35921,65503,65535,65535,65535, + 65535,65503,59164,48599,27501,12710,4258,0, + 0,0,0,0,0,32,0,0, + 0,4226,10565,32,0,0,23243,65535, + 65535,65535,63422,16936,0,0,0,0, + 0,0,2145,10565,19049,33808,48599,54970, + 63422,65535,65535,65535,65535,65535,65535,65503, + 40179,12678,6371,8452,2145,0,32,14823, + 57083,65535,65535,63422,21130,0,0,0, + 0,0,0,0,0,0,0,32, + 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, + 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, + 0,0,0,0,0,0,0,0, + 0,0,32,40179,65535,65535,65535,65535, + 65535,44405,2113,21162,65535,65535,63422,12710, + 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,2113,57051,65503,10597, + 0,0,0,0,0,0,0,0, + 0,0,32,2113,27501,61277,65535,65535, + 65535,65535,65535,65503,65535,65503,59196,48631, + 38034,31695,27469,27501,29582,29582,31695,31727, + 44373,44373,8452,0,0,0,23275,65535, + 65535,65535,59196,4226,0,0,0,0, + 0,0,0,0,32,0,32,2145, + 8452,23243,40147,48599,57083,63422,65535,65535, + 65535,61277,33808,10597,0,0,0,0, + 14791,57083,65535,65535,50744,32,0,0, + 0,32,0,32,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,23243,65535,65535,65535,65535,65535, + 54970,8452,0,27501,65503,65535,50744,32, + 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,50744,63422,10565, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,12678,50744,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65503,65535,63422, + 33840,4226,0,0,0,0,25356,65535, + 65535,65535,44373,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,4226,12678,25356,38066, + 52825,63390,65503,63390,33840,32,0,0, + 0,10565,54938,65535,65503,16904,0,0, + 2113,19017,35921,38066,29582,10597,32,32, + 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, + 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, + 0,0,0,0,0,0,0,0, + 0,10565,59196,65535,65535,65503,65535,63422, + 16936,0,0,35921,65535,65535,29614,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,32,48599,65503,8484, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2145,27469, + 54938,65503,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,61277,42260,10597, + 32,0,0,0,0,0,25388,65535, + 65535,65535,27469,0,0,0,0,0, + 0,0,0,32,0,0,0,0, + 0,0,0,0,32,0,32,0, + 2113,6339,14823,27501,42260,19049,32,0, + 0,0,8452,52857,65535,44405,32,8452, + 46486,63422,65535,65535,65535,61277,44405,14823, + 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, + 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, + 0,0,0,0,0,0,0,0, + 4258,50712,65535,65535,65503,65535,65503,27501, + 0,0,0,44373,65535,63390,12678,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,32,46486,63422,10565, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2145,19017,44373,57083,63390,65503,65535,65503, + 65535,65503,59164,50744,27501,6371,32,0, + 0,0,0,0,0,0,23243,65535, + 65535,65535,19049,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,8484,57083,63390,14823,52825, + 65535,65535,65535,65535,65535,65535,65503,63422, + 44405,10565,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,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,0,0,32,0,0,32, + 33840,65535,65535,65535,65535,65535,42260,32, + 0,32,32,50744,65503,61309,2145,32, + 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,48599,63390,8452, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2145,8452,14823,21162,21162, + 16936,10597,4226,32,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,16936,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,19049,63422,61277,65535, + 65535,65535,65535,63422,61277,63422,65535,65535, + 65535,61277,33840,2113,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,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,0,0,32,19049, + 63390,65535,65535,65535,65535,52825,4258,0, + 0,32,4226,59196,65535,48599,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,32,52857,63390,6371, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,0,32,0,0,0,0, + 0,0,0,0,0,0,32,0, + 0,0,0,0,0,0,14823,65535, + 65535,65503,14791,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,32,32,32,50744,65535,65535, + 65535,65535,57083,19049,4226,8452,23275,50744, + 65535,65535,65503,52857,16936,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,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,0,10565,59164, + 65535,65535,65535,65535,59196,10597,0,0, + 0,0,8452,65503,65535,35953,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,2145,57051,63422,6371, + 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,12678,65535, + 65535,65503,12678,32,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,32,33840,65535,65535, + 65535,57083,10597,0,0,32,0,2113, + 25356,59196,65535,65535,63390,33840,4226,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,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, + 32,0,0,0,0,2113,48599,65535, + 65535,65535,65535,63390,21130,0,32,0, + 0,0,21130,65535,65535,27501,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,4258,61277,63390,6371, + 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,8484,63390, + 65535,63422,10597,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,19049,65535,65535, + 65503,16936,2145,16936,29614,29614,21162,10565, + 2145,10597,52825,65535,65535,65535,54938,14823, + 32,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,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,33840,65503,65535, + 65535,65535,65535,33808,2113,0,0,0, + 0,0,31695,65503,65535,23243,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,10565,63422,61309,6339, + 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,8452,61309, + 65535,63390,10565,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,14823,63422,65535, + 48631,32,29614,59164,65535,65535,65535,65503, + 52857,33840,23243,61309,65535,63422,65535,63390, + 31695,2145,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,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,32,0,0,19049,65503,65535,65535, + 65535,65503,46486,2113,0,0,0,0, + 0,0,42292,65503,63422,16904,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,16936,65535,61309,6339, + 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,6371,61277, + 65535,61309,8484,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,8484,61309,65535, + 33808,0,0,6371,31695,54938,65535,65535, + 65535,65535,65503,63422,65535,65535,65535,65535, + 65535,48599,8484,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,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,6371,59164,65535,65535,65535, + 65535,57083,6371,0,0,0,0,0, + 0,2145,48631,65535,63390,12678,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,25356,63422,61309,6371, + 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,6339,59196, + 65535,61277,8452,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,32,4226,59196,65535, + 21130,0,32,32,0,2113,21130,44405, + 61309,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,59164,21162,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,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,32,2113,44373,65535,65535,65535,65535, + 61277,16936,0,0,0,0,0,0, + 0,2113,54970,65535,63390,6339,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,33840,65535,61277,6339, + 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,32,6339,61277, + 65535,61309,8452,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,2145,59164,65503, + 10565,0,0,0,0,0,0,32, + 8484,31727,57051,65535,65535,65535,65535,65535, + 65535,65535,65535,65503,33808,2113,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,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,32,27469,63390,65503,65535,65535,65535, + 31695,0,0,0,0,0,0,0, + 0,4258,59196,65535,61277,2145,0,32, + 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,40179,65535,61277,4258, + 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,6371,61309, + 65535,61277,6371,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,2145,59164,61309, + 6339,0,0,0,0,0,0,32, + 4258,4226,8484,42292,65535,65535,65535,65535, + 65535,65535,65535,65535,65503,42260,2145,32, + 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, + 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, + 32,10597,61277,65535,65535,65535,65503,46486, + 2113,0,0,0,0,0,0,0, + 0,8452,63390,65535,52857,32,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,46518,65535,61277,4226, + 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,8484,63390, + 65535,59196,6339,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,2145,59164,57051, + 2145,0,32,0,32,0,32,4258, + 50744,61309,61309,63422,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,40147,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,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, + 2113,46486,65535,65535,65535,65535,59164,8484, + 0,0,32,0,0,0,0,0, + 0,23243,65503,65535,48599,2113,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,50744,65535,61309,6339, + 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,10565,63422, + 65535,59164,4258,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,2145,59164,52857, + 32,0,0,0,0,0,0,0, + 12678,52825,63422,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,63390,23243, + 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, + 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, + 23275,65535,65535,65535,65535,65503,21162,0, + 0,0,0,0,0,0,0,2113, + 12678,54970,65535,65535,42260,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,2113,52857,65535,61277,4258, + 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,12678,63422, + 65535,59164,4258,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,4258,59196,48599, + 0,0,0,0,0,0,0,0, + 0,2113,16904,29582,44373,54970,63390,65535, + 65535,65535,65535,65535,65535,65535,65535,57083, + 2113,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,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,4258, + 57083,65535,65535,65535,65535,44373,32,0, + 0,0,0,0,0,0,0,31727, + 65503,65535,65535,65535,33840,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,2113,52857,65535,61277,4258, + 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,12710,65503, + 65535,57083,4226,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,6339,61277,46486, + 0,0,0,0,0,0,0,0, + 0,0,32,0,0,32,10565,31727, + 54938,65503,65503,65535,65535,65535,65535,65503, + 21162,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,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,32,29582, + 65535,65535,65535,65535,63390,12678,0,0, + 0,0,0,0,0,0,0,12678, + 63422,65535,65535,65535,25388,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,2113,52857,65535,61277,4258, + 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,16904,65535, + 65535,57051,2145,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,8452,61309,44405, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,0, + 2145,25388,57083,65535,65535,65535,65535,65535, + 38066,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,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,32,2145,54970, + 65535,65535,65535,65535,42292,0,32,0, + 0,0,0,0,0,0,0,32, + 50712,65535,65535,65503,19049,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,2113,52857,65535,61277,4258, + 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,19017,65535, + 65535,54970,2113,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,8484,63390,44405, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,6371,48599,65503,65535,65535,65535, + 50744,32,32,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,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,19017,65535, + 65535,65535,65535,63422,14791,32,0,0, + 0,0,0,0,2113,19049,10565,2113, + 31695,65535,65535,65535,12678,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,2113,52857,65535,61277,2145, + 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,23243,65535, + 65535,54970,32,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,12678,63390,50712, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,2113,48599,65535,65535,65535, + 57083,2113,32,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,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,32,32,42292,65535, + 65535,65535,65535,57051,2145,0,0,0, + 0,0,0,0,4258,59196,63390,54970, + 50744,65535,65535,63390,6371,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,32,50744,65535,59196,2145, + 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,25356,65535, + 65535,52857,32,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,14791,65535,52857, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,8452,54938,65535,65535, + 61277,32,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,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,2113,59196,65535, + 65503,65535,65535,44373,32,0,0,0, + 0,0,0,0,32,44405,65535,65535, + 65503,65535,65535,59196,2145,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,32,48599,65535,59164,2145, + 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,29582,65535, + 65535,50744,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,19049,65535,54970, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,19049,65503,65535, + 65503,6339,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,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,12710,63390,65535, + 65535,65535,65535,33840,0,0,0,0, + 0,0,0,0,0,16936,65503,65535, + 65535,65535,65535,59164,2145,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,32,46518,65535,59164,2145, + 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,31727,65535, + 65535,50744,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,32,25356,65503,57051, + 2113,32,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,48599,65535, + 65535,10597,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,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,19049,65535,65535, + 65535,65535,65535,27469,0,0,0,0, + 0,0,0,0,0,2145,54938,65535, + 65535,65535,65535,54938,32,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,46486,65535,59164,2145, + 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,33840,65535, + 65535,48599,32,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,29614,65535,57051, + 2113,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,23275,65503, + 65503,16904,0,32,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,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,25356,65535,65535, + 65535,65535,65535,23275,0,0,0,0, + 0,0,0,0,0,0,27501,65503, + 65535,65535,65535,48631,32,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,44373,65535,59164,2113, + 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,38034,65535, + 65535,46486,32,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,35921,65535,57051, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8452,63390, + 65535,21162,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,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,27469,65535,65535, + 65535,65535,65535,25388,0,0,0,0, + 0,0,0,0,0,0,6339,59164, + 65535,65535,65535,42260,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,42260,65535,59164,2113, + 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,42260,65535, + 65535,44405,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,42260,65535,54970, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,2113,54970, + 65535,29582,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,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,25356,65535,65535, + 65535,65535,65535,31695,0,0,0,0, + 0,0,0,0,0,0,0,44373, + 65535,65535,65535,38034,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,40179,65535,59164,32, + 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,44405,65535, + 65535,44373,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,44373,65535,52857, + 32,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,44373, + 65535,33840,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,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,16936,65535,65535, + 65535,65535,65535,44405,32,0,0,0, + 0,0,0,0,0,0,0,21162, + 65503,65535,65535,29582,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,38066,65535,57083,32, + 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,32,46518,65535, + 65535,40147,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,50712,65535,50744, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,35953, + 65535,40147,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,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,10597,63422,65535, + 65535,65535,65535,54938,2145,0,0,32, + 0,0,0,0,0,32,0,8484, + 65535,65535,65503,21162,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,38066,65535,57083,32, + 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,32,50712,65535, + 65535,38066,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,52857,65535,46518, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,29614, + 65535,44373,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,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,32,4258,57051,65535, + 65535,65535,65535,65503,10565,0,32,0, + 0,0,0,0,0,0,0,4258, + 61277,65535,63422,16904,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,38066,65535,57083,32, + 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,2113,54938,65535, + 65535,35953,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,2113,57083,65535,40179, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,27469, + 65535,50712,32,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,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,32,0,33840,65535, + 65535,65535,65535,65535,33808,0,0,0, + 0,0,0,0,0,0,0,2145, + 59164,65535,63390,10597,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,38066,65535,57083,32, + 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,2113,57083,65535, + 65535,35921,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,4258,61277,65535,33840, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,27501, + 65535,54938,32,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,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,10597,61309, + 65535,65535,65535,65535,57083,2145,0,0, + 0,0,0,0,0,0,0,32, + 57051,65535,63390,4226,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,38034,65535,57051,32, + 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,4258,61309,65535, + 65503,33808,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,6339,61309,65535,27501, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,29582, + 65535,57083,2145,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,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,32,42260, + 65535,65535,65535,65503,65535,25356,0,0, + 0,0,0,0,0,0,0,2145, + 59164,65535,59164,32,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,38034,65535,57051,32, + 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,6339,61309,65535, + 65535,31695,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,10565,65503,65535,19049, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,35953, + 65535,61277,4226,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,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,10565, + 61277,65535,65535,65535,65535,54938,2145,0, + 0,0,0,0,0,0,0,4226, + 61309,65535,48599,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,38034,65535,57051,32, + 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,8452,63390,65535, + 65535,29614,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,14791,65503,65503,12678, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,46518, + 65535,63390,6339,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,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,32,0,0,32, + 27501,65535,65535,65535,65535,65535,27501,0, + 0,0,0,0,0,0,0,6339, + 65503,65535,40147,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,38034,65535,57051,32, + 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,8484,63390,65535, + 65535,29582,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,21162,65535,63390,6339, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,54938, + 65535,63390,8484,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,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, + 2113,46486,65535,65535,65535,65535,59196,8484, + 0,0,0,0,0,0,0,14823, + 65535,65535,29582,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,38034,65535,54970,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,8484,63422,65535, + 65535,27469,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,25388,65535,57083,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4226,61309, + 65535,63422,10597,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,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,6371,57083,65535,65535,65535,65535,46486, + 2145,0,0,0,0,0,32,29582, + 65535,65535,21130,32,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,38034,65535,54970,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,8484,63422,65535, + 65535,25388,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,29614,65535,54938,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,6371,63390, + 65535,65503,14791,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,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,32,19049,63422,65535,65535,65535,65535, + 27501,0,0,0,32,0,0,40179, + 65535,63390,10597,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,35953,65535,54970,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,8484,63422,65535, + 65535,23275,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,35921,65535,48631,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,12678,65503, + 65535,65503,19049,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,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,32,35953,63422,65535,65535,65535, + 61309,12678,0,0,0,0,32,50744, + 65535,61277,6339,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,35953,65535,54970,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,8484,63422,65535, + 65535,23243,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,40147,65535,46486,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16904,65535, + 65535,65503,23243,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,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,32,4226,52825,65535,65535,65535, + 65535,50712,4258,0,0,0,4258,59164, + 65535,52857,32,32,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,35921,65535,54970,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,6339,63390,65535, + 65535,21162,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,44405,65535,42292,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,29582,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,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,32,12678,61277,65535,65535, + 65535,65535,40147,2113,32,0,21130,65535, + 65535,44405,32,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,35953,65535,54938,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,4258,61309,65535, + 65535,19049,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,48599,65535,42292,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,31727,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,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,32,31695,63422,65535, + 65535,65535,65503,42292,16936,16904,54970,65535, + 65535,33840,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,38066,65535,52857,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,4226,61277,65535, + 65535,16936,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,50744,65535,42292,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,35953,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,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,4226,52825,65535, + 65535,65535,65535,65535,65535,65503,65535,65535, + 65535,27469,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,38066,65535,52857,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,2145,59196,65535, + 65503,16904,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,54938,65535,44373,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19049,65535, + 65535,65535,40179,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,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,16936,63422, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,16936,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,38066,65535,52857,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,32,57083,65535, + 65503,14791,0,32,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,2113,57083,65535,44373,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16904,65535, + 65535,65535,42260,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,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,32,0,32,44405, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65503,10565,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,42260,65535,52825,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,32,54938,65535, + 65503,14791,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,2145,59164,65535,44405,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,14791,65535, + 65535,65535,44373,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,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,10597, + 63390,65535,65503,65535,65535,65535,65535,65535, + 63390,6339,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,44405,65535,52825,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,50712,65535, + 65503,14791,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,4258,59164,65535,46486,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,12678,65535, + 65535,65535,46486,32,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,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,32,0,0, + 40179,65535,65535,65535,65535,65535,65535,65535, + 61277,6371,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,48599,65503,52825,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,46518,65535, + 63422,12710,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,6339,59196,65535,48599,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10597,65503, + 65535,65535,48599,32,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,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, + 10597,63390,65535,65535,65535,65535,65535,65535, + 61277,6339,0,32,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,4226,54938,65535,52825,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,44373,65535, + 63422,12678,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, + 32,0,0,6339,59196,65535,50712,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63422, + 65535,65535,50712,2113,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,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,44373,65535,65535,65535,65535,65535,65535, + 61309,4258,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,6339,61277,65535,50712,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,40179,65535, + 63422,12678,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,6371,61277,65535,50744,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63422, + 65535,65535,50712,2113,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,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,12678,63422,65535,65535,65535,65535,65535, + 63422,8452,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,12710,65535,65535,50712,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,35953,65535, + 63422,12678,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,8452,61309,65535,52857,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63422, + 65535,65535,50712,2113,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,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,32,48599,65503,65535,65535,65535,65535, + 65503,12710,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,21162,65535,65535,50744,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,33840,65535, + 65503,12710,32,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,8452,63390,65535,54938,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,8484,63422, + 65535,65535,50712,2113,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,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,19017,63422,65535,65535,65535,65503, + 65503,19049,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,38034,65535,65535,48631,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,31695,65535, + 63422,16936,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,8484,63390,65535,57083,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10565,65535, + 65535,65535,44405,32,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,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,2145,52857,65535,65535,65535,65535, + 65535,23243,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, + 32,0,32,52825,65535,65535,48631,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,29582,65535, + 65535,19049,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,10565,63422,65535,59196,4258, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10597,65503, + 65535,65535,40179,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,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,27469,65503,65535,65535,65535, + 65535,27469,0,32,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,4258,57051,65535,65535,48631,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,27469,65535, + 65535,25356,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,12678,65503,65535,63390,8484, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,19017,65503, + 65535,65535,31695,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,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,6339,59164,65535,65535,65535, + 65535,27501,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,10565,61309,65535,65535,48631,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,25388,65535, + 65535,31695,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,12710,65535,65535,63422,10597, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,27469,65535, + 65535,65535,21162,32,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,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,35953,65535,65535,65535, + 65535,29582,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,12678,65503,65535,65535,57051,32, + 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,23243,65535, + 65535,42260,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,32,14823,65503,65535,65535,16904, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,32,48599,65535, + 65535,63422,14791,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,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,8484,61309,65535,65535, + 65535,27501,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,16904,65535,65535,65535,61277,2145, + 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,23243,65535, + 65535,50744,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,16936,65503,65535,63422,21130, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,16904,65503,65535, + 65535,61309,4226,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,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,2113,52857,65535,65535, + 65535,25388,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,16904,65535,65535,65535,65503,12678, + 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,23243,65535, + 65535,59196,4258,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,25356,65535,65535,65535,27469, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,4258,50744,65535,65535, + 65535,63390,6339,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,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,32,48599,65535,65535, + 65535,25356,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,12678,65503,65535,65535,65535,23243, + 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,23243,65535, + 65535,63390,10597,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,35921,65535,65535,65535,33840, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,25356,65503,65503,65535, + 65535,65503,19017,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,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,32,48599,65535,65535, + 65535,19049,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,6339,59196,65535,65535,65535,35953, + 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,23243,65535, + 65535,65535,19049,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,52825,65535,65535,65535,44373, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,42292,65535,65535,65535, + 65535,65535,38066,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,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,2113,50744,65535,65535, + 65503,16936,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,42260,65535,65535,65535,38066, + 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,25388,65535, + 65535,65535,31727,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, + 32,0,4258,59196,65535,65535,65535,54970, + 2113,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,35953,65535,65535,65535, + 65535,65535,54970,2113,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,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,2145,54970,65535,65535, + 65503,14791,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,32,12678,63390,65535,63390,19049, + 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,29614,65535, + 65535,65535,44373,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, + 32,0,10597,63422,65535,65535,65535,65503, + 12678,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,12678,63390,65535,65535, + 65535,65535,63422,8484,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,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,4226,59164,65535,65535, + 63422,12678,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,33808,63422,29614,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,32,33808,65535, + 65535,65503,48631,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,14823,63390,65535,65535,65535,65535, + 25388,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,42260,65535,65535, + 65535,65535,65535,25356,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,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,4258,61309,65535,65535, + 63422,10597,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,2145,21162,32,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,23243,63390, + 65535,65535,42292,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,14791,63422,65535,65535,65535,65535, + 46518,32,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,10565,63390,65535, + 65535,65535,65535,40179,32,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,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,4258,61309,65535,65535, + 63422,10597,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,32,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,32,21162, + 59164,63390,19017,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,2145,52825,65535,65535,65535,65535, + 57083,4258,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,31695,65503, + 65535,65535,65535,54938,32,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,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,6371,63390,65535,65535, + 65503,12678,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,32,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,32, + 6339,12710,32,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,8452,40147,57083,61309,59164, + 31727,2145,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4226,50744, + 65503,65535,65535,59196,32,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,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,8452,63422,65535,65535, + 65503,12678,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,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,32, + 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,2145,6371,4226, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10565, + 61277,65535,65535,63422,32,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,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,6339,63422,65535,65535, + 65503,16936,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,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,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, + 38034,65535,65535,57083,32,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,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,4258,61309,65535,65535, + 65535,19049,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,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,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, + 19017,65535,65535,48631,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,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,4258,59196,65535,65535, + 65535,21162,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,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,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, + 21130,65535,65535,42260,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,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,4258,59164,65535,65535, + 65535,23243,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,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,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, + 33808,65535,65535,31695,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,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,2113,52857,65535,65535, + 65535,21162,32,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,32,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,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,0,0,0, + 0,0,0,0,0,0,32,32, + 48631,65535,65503,19017,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,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,32,48631,65535,65535, + 65535,21162,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,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,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,32,4258, + 61309,65535,63422,8484,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,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,40147,65535,65535, + 65535,23275,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,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,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,21162, + 65503,65535,57083,2145,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,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,29582,65503,65535, + 65535,25388,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,12710, + 2145,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,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,14823,25356,6371,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,40179, + 65535,65535,44373,32,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,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,16936,65503,65535, + 65503,27501,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,32,0,0,10597,54938, + 6339,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,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,12710,63390,59164,19049,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,59196, + 65535,65503,21130,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,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,8484,65503,65535, + 65535,31727,32,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,32,0,10565,54938,63390, + 8452,0,0,32,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2113,50744,65535,61309,14791,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,21130,65535, + 65535,59196,4258,32,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,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,2113,52857,65503, + 65535,40179,32,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, + 32,32,0,0,12710,57051,65535,61277, + 6339,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,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,27469,65535,65535,50744,2113,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,46486,65535, + 65535,42260,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,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,38034,65535, + 65535,48631,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,14791,59164,65535,65535,52857, + 32,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,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,4258,52825,65503,65503,19017,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,14823,65503,65535, + 63390,10565,32,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,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,19017,65535, + 65535,59164,4258,32,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,14823,59196,65535,65535,65535,35921, + 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, + 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,32,14823,65503,65535,44373,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,2145,50744,65535,65535, + 35921,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,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,4226,57083, + 65535,65503,16904,32,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, + 2113,8452,59164,65503,65535,65535,61277,6371, + 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, + 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,32,48599,65535,54970,2113, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,33840,65535,65535,52857, + 4226,0,32,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,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,40147, + 65535,65535,35921,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, + 32,40179,65535,65535,65535,65535,31695,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,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,31727,65535,59196,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,25388,65503,65535,57051,8484, + 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, + 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,12710, + 65503,65535,59164,6339,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, + 8452,63390,65535,65535,65535,54938,6339,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,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,27469,65535,59164,4226, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2145,27469,63390,65503,57051,14791,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,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,0, + 40147,65535,65503,31727,32,0,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 19049,65503,65535,65535,65503,25388,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,32,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,32,40147,65535,52825,32, + 0,0,0,0,0,32,32,0, + 0,0,0,0,0,32,0,32, + 8452,44373,65535,65535,52857,8484,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,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,0,0, + 6339,57083,65503,59164,6371,32,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, + 35921,65535,65535,65535,59164,4226,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,2113,10597,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,4258,57083,65535,33808,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4258,29614, + 59164,65535,65535,52825,6371,0,0,32, + 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, + 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, + 0,16936,63390,65535,44373,2113,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,32, + 50744,65535,65535,65535,40147,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,2113,42260,19017,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,23275,65535,61277,8452,0, + 0,0,0,0,0,0,0,32, + 0,0,32,4226,14823,35953,61277,65535, + 65535,65535,48599,6339,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,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,0,0,0,0, + 0,0,35921,65535,65503,23275,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,2145, + 59164,65535,65535,65535,25356,0,0,32, + 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,19049,61277,21162, + 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,44405,65503,29614,32,0, + 0,0,0,0,0,0,32,4226, + 14823,27469,46486,61277,65503,65535,65535,65535, + 65503,38066,2145,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,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,0,0,0,0,0, + 0,0,2113,48631,65535,59196,12710,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,6339, + 61277,65535,65535,63422,19017,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,32,52857,63390, + 23275,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,32,0,21162,33808,2113,0,32, + 0,0,0,0,2145,27469,50744,61277, + 65535,65535,65535,65535,65535,65535,65535,59164, + 25388,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,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,0,0,0,0,0,0,0, + 0,0,0,12678,61309,65535,54938,8452, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,6371, + 61277,65535,65535,65535,19049,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,25388,65503, + 61309,25356,32,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,32,0,2113,32,0,0,0, + 0,32,0,19017,54938,65503,65535,65535, + 65535,65535,65503,65535,65535,61309,42292,8484, + 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, + 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, + 0,0,0,0,0,0,0,0, + 0,0,0,0,27469,65503,65535,48631, + 4258,0,0,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,4258, + 59196,65535,65535,65535,23275,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,4258,59196, + 65535,63390,14823,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,32,32,0, + 0,32,29614,63422,65535,65503,65535,65535, + 65503,65503,61277,54938,35953,10565,0,0, + 0,0,32,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,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,0,0,0,0,0, + 32,0,0,0,2113,46486,65535,65535, + 48631,6339,32,32,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,0,0,4226, + 57083,65535,65535,65535,31695,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,32,38066, + 65535,65535,52857,2113,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, + 2145,40147,65535,65535,61277,42292,23275,14791, + 12710,10597,6339,32,0,0,0,0, + 0,0,32,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,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,0,0,0,0,0, + 0,0,0,0,0,6371,57051,65503, + 65535,52857,8484,32,0,0,32,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 46486,65535,65535,65535,42292,0,32,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,14791, + 65503,65535,65503,14823,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,32,0,0,4226, + 44373,65503,65503,42260,6371,32,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,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,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,14823,63390, + 65535,65535,59196,19017,0,0,32,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 23243,65535,65535,65535,48631,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,4226, + 59196,65535,65535,25356,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,4226,46486, + 65535,63422,33840,2113,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,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,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,27469, + 63422,65535,65535,63390,35921,6371,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 2145,54938,65535,65535,57051,2113,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,2145, + 57051,65535,65535,25388,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,2145,42292,65535, + 65535,44373,32,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,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,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,32,0,0, + 31727,65503,65535,65535,65535,57051,21162,2145, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,23243,63422,65535,61277,4258,32,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,2145, + 57083,65535,63422,16904,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,32,40179,65535,65535, + 57083,8452,0,32,0,0,0,32, + 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, + 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, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,29614,65503,65535,65535,65535,65535,50744, + 21162,2113,0,0,0,32,0,0, + 0,32,0,32,0,0,0,0, + 0,32,40147,65535,63422,6371,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,4226, + 59164,65503,57051,2145,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,31695,65535,65535,65535, + 25356,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,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,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,32,19049,59164,65535,65535,65535,65535, + 65503,52825,27469,8452,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,4258,48631,63390,6371,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,6339, + 61309,65503,25356,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,32,23275,63422,65535,65535,46486, + 2113,0,0,32,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,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,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,6339,38066,61277,65535,65535, + 65535,65535,65535,61277,46486,21130,4258,32, + 32,0,0,0,0,0,0,0, + 0,0,0,10597,52825,4226,0,32, + 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,4226, + 54938,33840,32,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,32,16936,61309,65535,65535,61277,8484, + 32,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,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,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,10597,38034,59196, + 65535,65535,65535,65535,65535,65535,61277,46518, + 27469,12710,2145,32,0,0,0,0, + 0,0,0,0,10565,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, + 4226,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,10565,57051,65535,65535,65503,23243,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,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,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,8484, + 31727,57083,65503,65535,65535,65535,65535,65535, + 65503,65503,57051,44373,19049,4258,0,0, + 32,32,32,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,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, + 6339,50744,65535,65535,65535,31727,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,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,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, + 32,4226,23243,50712,63390,65535,65535,65535, + 65535,65535,65535,65535,63422,52857,19049,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,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,4258, + 48599,65535,65535,65503,40147,2113,0,32, + 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, + 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, + 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,32,0,0,0, + 0,0,0,2113,14791,40179,61277,65535, + 65535,65535,65535,65535,65535,65503,65503,38034, + 4226,0,32,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,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,2145,42260, + 65503,65535,65503,46518,2145,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,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,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,32,6371,29614, + 52857,65503,65535,65535,65535,65535,65535,65503, + 48631,8452,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,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,32,4258,46486,65503, + 65535,65535,50744,6371,0,0,0,32, + 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, + 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, + 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, + 4226,19049,46486,63390,65535,65535,65535,65535, + 65535,52857,10565,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,32,8452,46518,65535,65535, + 65535,54970,8484,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,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,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,0,0,0,0,0, + 0,0,32,10565,33840,52857,65535,65535, + 65503,65535,54970,10597,0,32,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,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,32,14823,54938,65535,65535,65535, + 57083,10597,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,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,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,0,0,0,0,0,0, + 32,32,0,32,0,4226,21130,50712, + 65535,65535,65535,57051,12710,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,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,4258,35921,63390,65535,65535,65503,59164, + 14823,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,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,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,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,2113, + 29614,63422,65535,65535,54970,10597,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,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,4226, + 27469,54970,65535,65535,65535,65535,61309,19017, + 0,0,0,32,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,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,27469,65535,65535,65535,54970,12678,0, + 0,32,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,32,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,2113,10565,31695,59164, + 65535,65535,65535,65535,65535,61309,23275,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,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,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,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,38034,65535,65535,65535,57083,12678, + 32,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,2113,4226, + 4226,4258,8452,10597,12678,14791,12678,6371, + 6339,4258,2145,2113,2113,0,0,2113, + 2145,4258,16936,33808,52825,63390,65535,65535, + 65535,65535,65535,65535,63422,27469,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,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,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,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,8452,59164,65535,65535,65535,54938, + 8484,0,32,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,2145,10597,29582,40147,44405,52825,57083, + 61309,63422,65535,65535,65535,65535,65503,63422, + 63422,61277,54970,50744,48631,46518,46518,48631, + 57083,63390,65535,65535,65535,65503,65535,65535, + 65535,65535,65535,65503,29614,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,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,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,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,31727,65503,65535,65535,65535, + 54970,10565,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,32,6339, + 31695,54938,65503,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,63422,33840,32,32,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,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,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,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6371,57083,65503,65535,65503, + 65503,54970,10565,0,0,32,0,32, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,32,8452,35953,59196, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,63390,25388,32,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,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,27469,65535,65535,65535, + 65535,65535,57051,10597,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,6339,31727,59196,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65503, + 63390,59196,54970,52825,50744,48631,48599,48599, + 50712,54970,61309,65503,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65503, + 48631,12710,32,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,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,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,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,2145,48631,65535,65503, + 65535,65535,65535,57083,14791,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,2113,21162,54970,65503,65535,65535,65535, + 65535,65535,65535,65535,65503,54970,40179,21162, + 6371,2113,0,32,0,0,0,32, + 0,0,6371,19017,38034,50712,61309,65503, + 65535,65535,65535,65535,65503,61277,48599,19049, + 32,32,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,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,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,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,10565,59164,65535, + 65535,65503,65535,65535,61277,21130,32,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 16904,48599,65503,65535,65535,65535,65535,65535, + 65535,65535,63422,46486,16936,2145,0,32, + 0,32,0,0,32,0,0,0, + 0,0,0,0,0,32,6371,14823, + 25388,31695,31695,25388,16904,6371,32,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,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,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,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,16936,61309, + 65535,65535,65503,65535,65535,63390,35953,6371, + 0,0,32,0,32,0,0,0, + 0,0,0,0,2113,6371,25356,46518, + 65503,65535,65535,65535,65535,65535,65535,61309, + 50712,33808,10597,2113,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,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,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,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,0,0,0,0, + 0,0,0,0,0,0,32,21162, + 61309,65535,65535,65535,65535,65535,65535,61277, + 35921,14823,4258,32,32,0,0,32, + 2145,4258,10597,27469,46486,61309,65503,65503, + 65535,65535,65503,65535,65535,54970,25356,4258, + 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, + 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, + 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, + 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, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,0,0, + 21162,63390,65535,65535,65535,65535,63422,65535, + 65535,63390,59196,52857,48599,44405,44405,48599, + 52857,61277,63390,65503,65535,65535,65535,65535, + 65535,65535,65535,65503,50744,8452,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,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,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,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,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,21130,59164,65503,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,63422,61277,50744,10597,0,32,0, + 0,32,32,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,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,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,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,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 32,0,10597,50744,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,61277,46486, + 31695,19017,4258,2113,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,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,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,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,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,32,0,2145,29582,57083,63422,65535, + 65535,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,52857,25356,4258,2113, + 0,0,0,32,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,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,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,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,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,4258,27501,48631, + 61309,65503,65503,65535,65535,65535,65503,63422, + 63390,59196,44405,23243,2113,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,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,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,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,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,32, + 4258,16904,25388,33840,35953,35921,29614,23243, + 10597,4226,32,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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 +}; + +const basic_image<unsigned short> respect(height,width,pixelData); \ No newline at end of file diff --git a/src/tests/drivers/xbee/gui/res/respect.h b/src/tests/drivers/xbee/gui/res/respect.h new file mode 100644 index 0000000000000000000000000000000000000000..9d54b974112dd36f34169c5fa5af744c0fe80e25 --- /dev/null +++ b/src/tests/drivers/xbee/gui/res/respect.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +//This file has been automatcally generated by pngconverter utility +//Please do not edit +#ifndef RESPECT_H +#define RESPECT_H + +#include "mxgui/image.h" + +extern const mxgui::Image respect; + +#endif //RESPECT_H diff --git a/src/tests/drivers/xbee/logdecoder/.gitignore b/src/tests/drivers/xbee/logdecoder/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..8b193293c00f3d4483c23b0d5c2e705d0617e209 --- /dev/null +++ b/src/tests/drivers/xbee/logdecoder/.gitignore @@ -0,0 +1 @@ +logdecoder \ No newline at end of file diff --git a/src/tests/drivers/xbee/logdecoder/LogTypes.h b/src/tests/drivers/xbee/logdecoder/LogTypes.h new file mode 100644 index 0000000000000000000000000000000000000000..1db90d51c8c8d3b3149c5939d3c0dbf302dbe799 --- /dev/null +++ b/src/tests/drivers/xbee/logdecoder/LogTypes.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2015-2018 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#pragma once + +#include <fstream> +#include <iostream> + +#include "drivers/Xbee/APIFramesLog.h" +#include "drivers/Xbee/XbeeStatus.h" +#include "drivers/xbee/Mark.h" +#include "drivers/xbee/XbeeTestData.h" +#include "logger/Deserializer.h" +#include "logger/LogStats.h" + +// Serialized classes +using std::ofstream; + +template <typename T> +void print(T& t, ostream& os) +{ + t.print(os); +} + +template <typename T> +void registerType(Deserializer& ds) +{ + ds.registerType<T>(print<T>, T::header()); +} + +void registerTypes(Deserializer& ds) +{ + registerType<LogStats>(ds); + + registerType<Xbee::APIFrameLog>(ds); + registerType<Xbee::ATCommandFrameLog>(ds); + registerType<Xbee::ATCommandResponseFrameLog>(ds); + registerType<Xbee::ModemStatusFrameLog>(ds); + registerType<Xbee::TXRequestFrameLog>(ds); + registerType<Xbee::TXStatusFrameLog>(ds); + registerType<Xbee::RXPacketFrameLog>(ds); + registerType<Xbee::XbeeStatus>(ds); + + registerType<TxData>(ds); + registerType<RxData>(ds); + registerType<Mark>(ds); + registerType<XbeeConfig>(ds); + registerType<EnergyScanData>(ds); +} diff --git a/src/tests/drivers/xbee/logdecoder/Makefile b/src/tests/drivers/xbee/logdecoder/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..47258b629723d159885432ac1e485023f5d40002 --- /dev/null +++ b/src/tests/drivers/xbee/logdecoder/Makefile @@ -0,0 +1,13 @@ +BASE := ../../../../../ + +all: + g++ -std=c++17 -O2 -o logdecoder logdecoder.cpp \ + -DCOMPILE_FOR_X86 \ + $(BASE)libs/tscpp/stream.cpp \ + -I$(BASE)src/shared \ + -I$(BASE)src/tests \ + -I$(BASE)libs \ + -I$(BASE)libs/miosix-kernel/miosix + +clean: + rm logdecoder diff --git a/src/tests/drivers/xbee/logdecoder/logdecoder.cpp b/src/tests/drivers/xbee/logdecoder/logdecoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..28816fed14de875fc3c0a861db3f45c7823dc9aa --- /dev/null +++ b/src/tests/drivers/xbee/logdecoder/logdecoder.cpp @@ -0,0 +1,163 @@ +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Federico Terraneo + * + * 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. + */ + +/* + * This is a stub program for the program that will decode the logged data. + * Fill in the TODO to make it work. + */ + +#include <sys/stat.h> +#include <sys/types.h> +#include <tscpp/stream.h> + +#include <filesystem> +#include <fstream> +#include <iostream> +#include <stdexcept> +#include <string> +#include <vector> + +#include "LogTypes.h" + +using namespace std; +using namespace tscpp; + +namespace fs = filesystem; + +void showHelp(string cmdName) +{ + std::cerr << "Usage: " << cmdName + << " {-a [logs_diretory] | <log_file_path> | -h}" + << "Options:\n" + << "\t-h,--help\t\tShow help message\n" + << "\t-a,--all [dir=\".\"] Deserialize all logs in the provided directory\n" + << std::endl; +} + +vector<fs::path> listLogFiles(fs::path dir) +{ + vector<fs::path> out; + for (const auto& entry : fs::directory_iterator(dir)) + { + if (entry.exists() && entry.is_regular_file()) + { + if (entry.path().extension() == ".dat") + { + out.push_back(entry.path()); + } + } + } + return out; +} + +bool deserialize(fs::path log_path) +{ + cout << "Deserializing " << log_path << ".dat...\n"; + + // remove extension + log_path.replace_extension(""); + Deserializer d(log_path); + registerTypes(d); + + return d.deserialize(); +} + +bool deserializeAll(fs::path dir = ".") +{ + vector<fs::path> logs = listLogFiles(dir); + for (auto log : logs) + { + if (!deserialize(log.string())) + { + return false; + } + } + + return true; +} + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + showHelp(string(argv[0])); + return 1; + } + + bool success = false; + string arg1 = string(argv[1]); + if (arg1 == "-h" || arg1 == "--help") + { + showHelp(string(argv[0])); + return 0; + } + + if (arg1 == "-a" || arg1 == "--all") + { + fs::path dir = "."; + if (argc == 3) + { + string arg2 = string(argv[2]); + fs::directory_entry entry(arg2); + if (entry.exists() && entry.is_directory()) + { + dir = arg2; + } + else + { + cout << "Second argument after '-a' or '--all' must be a " + "directory\n"; + showHelp(string(argv[0])); + return 1; + } + } + cout << "Deserializing all logs...\n"; + success = deserializeAll(dir); + } + else if (arg1[0] == '-') + { + cerr << "Unknown option\n"; + return 1; + } + else + { + fs::directory_entry entry(arg1); + if (entry.exists() && entry.is_regular_file()) + { + success = deserialize(arg1); + } + else + { + showHelp(string(argv[0])); + return 1; + } + } + + if (success) + { + cout << "Deserialization completed successfully.\n"; + } + else + { + cout << "Deserialization ended with errors.\n"; + } +} diff --git a/src/tests/drivers/xbee/test-xbee-bidir.cpp b/src/tests/drivers/xbee/test-xbee-bidir.cpp new file mode 100644 index 0000000000000000000000000000000000000000..55bf6cb9b08ded7103ece1aa186ce87b8f86aae0 --- /dev/null +++ b/src/tests/drivers/xbee/test-xbee-bidir.cpp @@ -0,0 +1,266 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#ifndef RUN_SENDER +#define RUN_SENDER true +#endif +#ifndef RUN_RECEIVER +#define RUN_RECEIVER true +#endif + +#include <miosix.h> + +#include <cstdio> +#include <stdexcept> + +#include "ActiveObject.h" +#include "XbeeTransceiver.h" +#include "drivers/Xbee/APIFramesLog.h" +#include "drivers/Xbee/ATCommands.h" +#include "drivers/Xbee/Xbee.h" +#include "drivers/interrupt/external_interrupts.h" +#include "logger/Logger.h" + +using namespace miosix; + +#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X +#include "interfaces-impl/hwmapping.h" +using GpioMiso = miosix::interfaces::spi2::miso; +using GpioMosi = miosix::interfaces::spi2::mosi; +using GpioSck = miosix::interfaces::spi2::sck; + +using GpioCS = xbee::cs; +using GpioATTN = xbee::attn; +using GpioRST = xbee::reset; + +using GpioLedLog = Gpio<GPIOG_BASE, 2>; + +#define XBEE_SPI SPI2 +#else +using GpioMiso = Gpio<GPIOB_BASE, 4>; +using GpioMosi = Gpio<GPIOA_BASE, 7>; +using GpioSck = Gpio<GPIOA_BASE, 5>; + +using GpioCS = Gpio<GPIOC_BASE, 1>; +using GpioATTN = Gpio<GPIOE_BASE, 5>; +using GpioRST = Gpio<GPIOE_BASE, 6>; + +using GpioLedLog = Gpio<GPIOC_BASE, 13>; +#define XBEE_SPI SPI1 +#endif + +using GpioUserBtn = Gpio<GPIOA_BASE, 0>; + +Xbee::Xbee* xbee_driver = nullptr; +Logger& logger = Logger::instance(); + +#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X +void __attribute__((used)) EXTI10_IRQHandlerImpl() +#else +void __attribute__((used)) EXTI5_IRQHandlerImpl() +#endif +{ + if (xbee_driver != nullptr) + { + xbee_driver->handleATTNInterrupt(); + } +} + +int getUserBtnValue() +{ +#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X + return 0; +#else + return GpioUserBtn::value(); +#endif +} + +void configure() +{ +#ifndef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X + { + FastInterruptDisableLock dLock; + + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; + + // Set SPI pins to correct alternate mode + GpioSck::mode(Mode::ALTERNATE); + GpioMiso::mode(Mode::ALTERNATE); + GpioMosi::mode(Mode::ALTERNATE); + + GpioSck::alternateFunction(5); + GpioMiso::alternateFunction(5); + GpioMosi::alternateFunction(5); + + GpioATTN::mode(Mode::INPUT); + + GpioLedLog::mode(Mode::OUTPUT); + + GpioUserBtn::mode(Mode::INPUT_PULL_DOWN); + + GpioCS::mode(Mode::OUTPUT); + } + + GpioCS::high(); + GpioLedLog::low(); +#endif + +#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X + enableExternalInterrupt(GPIOF_BASE, 10, InterruptTrigger::FALLING_EDGE); +#else + enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE); +#endif +} + +void setupXbee(XbeeConfig config) +{ + if (xbee_driver) + { + if (config.data_rate_80k) + { + if (!Xbee::setDataRate(*xbee_driver, true)) + { + TRACE("[main] Error setting xbee_driver data rate!\n"); + } + } + + if (!config.freq_hop) + { + if (!Xbee::disableFrequencyHopping(*xbee_driver)) + { + TRACE("[main] Error disabling frequency hop!\n"); + } + } + } +} + +int main() +{ + XbeeConfig config; + config.data_rate_80k = true; + config.freq_hop = true; + + config.packet_size = 256; + config.send_interval = 333; + config.tx_enabled = RUN_SENDER; + config.timestamp = getTick(); + + configure(); + + int filenum; + try + { + filenum = logger.start(); + + printf("\nLog file opened! (%s)\n\n", + logger.getFileName(filenum).c_str()); + } + catch (const std::runtime_error& err) + { + GpioLedLog::high(); + printf("\n!!!!!!Error opening log file!!!!!!!\n\n"); + } + + logger.log(config); + + SPIBus spi_bus(XBEE_SPI); + SPIBusConfig cfg{}; + cfg.clock_div = SPIClockDivider::DIV16; + + GpioPin cs = GpioCS::getPin(); + GpioPin attn = GpioATTN::getPin(); + GpioPin rst = GpioRST::getPin(); + + xbee_driver = new Xbee::Xbee(spi_bus, cfg, cs, attn, rst); + + setupXbee(config); + + // RandSendInterval intv(333, 1500); + ConstSendInterval intv(config.send_interval); + XbeeTransceiver* trans = + new XbeeTransceiver(*xbee_driver, logger, intv, 256, 333); + if (!config.tx_enabled) + { + trans->disableSender(); + } + if (!RUN_RECEIVER) + { + trans->disableReceiver(); + } + trans->start(); + + while (getUserBtnValue() == 0) + { + long long loop_start = getTick(); + + DataRateResult res_rcv = trans->getReceiver().getDataRate(); + DataRateResult res_snd = trans->getSender().getDataRate(); + + TxData txd = trans->getSender().getTxData(); + RxData rxd = trans->getReceiver().getRxData(); + + logger.log(xbee_driver->getStatus()); + logger.log(logger.getLogStats()); + + long long tick = getTick(); + unsigned int h = tick / (1000 * 3600); + unsigned int m = (tick - h * 1000 * 3600) / (1000 * 60); + float s = (tick - h * 1000 * 3600 - m * 1000 * 60) / 1000.0f; + + printf("%02u:%02u:%06.3f\n", h, m, s); + if (RUN_SENDER) + { + printf("SND: int: %d, cnt: %d, tts: %u ms, pps: %.1f, fail: % d\n ", + txd.time_since_last_send, + txd.tx_success_counter + txd.tx_fail_counter, + txd.time_to_send, res_snd.packets_per_second, + txd.tx_fail_counter); + } + if (RUN_RECEIVER) + { + printf( + "RCV: cnt: %d, last_rx: %lld ms, RSSI: %d, dr: %.0f, pps: " + "%.1f," + " pl: %.0f%%, lcnt: %u, fail: %u\n", + rxd.rcv_count, rxd.last_packet_timestamp, rxd.RSSI, + res_rcv.data_rate, res_rcv.packets_per_second, + res_rcv.packet_loss * 100, rxd.packets_lost, rxd.rcv_errors); + } + printf("\n"); + + Thread::sleepUntil(loop_start + 1000); + } + + trans->stop(); + delete trans; + delete xbee_driver; + logger.stop(); + printf("Log closed.\n"); + + for (;;) + { + GpioLedLog::high(); + Thread::sleep(50); + GpioLedLog::low(); + Thread::sleep(5000); + } +} \ No newline at end of file diff --git a/src/tests/drivers/xbee/test-xbee-gui.cpp b/src/tests/drivers/xbee/test-xbee-gui.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d0ca706aee67690264bd80c63e3635e9c8780b76 --- /dev/null +++ b/src/tests/drivers/xbee/test-xbee-gui.cpp @@ -0,0 +1,367 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#include <miosix.h> +#include <mxgui/display.h> + +#include <array> +#include <cstdio> +#include <functional> +#include <stdexcept> + +#include "ActiveObject.h" +#include "Mark.h" +#include "XbeeTransceiver.h" +#include "drivers/Xbee/APIFramesLog.h" +#include "drivers/Xbee/ATCommands.h" +#include "drivers/Xbee/Xbee.h" +#include "drivers/interrupt/external_interrupts.h" +#include "gui/XbeeGui.h" +#include "logger/Logger.h" +#include "utils/ButtonHandler.h" + +using namespace miosix; +using namespace mxgui; +using namespace std::placeholders; + +using std::array; +using std::bind; +using std::to_string; + +/// Pin definitions +using GpioMiso = Gpio<GPIOB_BASE, 4>; +using GpioMosi = Gpio<GPIOA_BASE, 7>; +using GpioSck = Gpio<GPIOA_BASE, 5>; + +using GpioCS = Gpio<GPIOC_BASE, 1>; +using GpioATTN = Gpio<GPIOE_BASE, 5>; +using GpioRST = Gpio<GPIOE_BASE, 6>; + +using GpioUserBtn = Gpio<GPIOA_BASE, 0>; +using GpioLedLog = Gpio<GPIOC_BASE, 13>; + +// Forward dec +void onStartButtonClick(View* btn, Interaction action); +void onEnergyButtonClick(View* btn, Interaction action); +void onMarkButtonClick(View* btn, Interaction action); +void onStopButtonClick(View* btn, Interaction action); +void startTransceiver(XbeeConfig config); +void setupXbee(XbeeConfig config); +void configure(); + +// Global variables +Logger& logger = Logger::instance(); +Xbee::Xbee* xbee = nullptr; +ConstSendInterval snd_int{0}; +XbeeTransceiver* trans = nullptr; +XbeeGUI* gui; +ButtonHandler<GpioUserBtn>* btn_handler; + +unsigned int mark_counter = 1; + +/** + * @brief Activeobject to perform an energy detect scan as frequently as + * possible + */ +class EnergyScanner : public ActiveObject +{ +protected: + void run() override + { + while (!shouldStop()) + { + Xbee::ATCommandResponseFrame response; + + uint8_t duration = 0xFF; + + if (xbee->sendATCommand("ED", &response, &duration, 1, 1000) && + response.getCommandDataLength() == 30) + { + array<int, 30> scan; + + for (uint16_t i = 0; i < response.getCommandDataLength(); i++) + { + scan[i] = -(int)(*(response.getCommandDataPointer() + i)); + } + + gui->screen_energy.updateScan(scan); + + EnergyScanData data{getTick(), scan}; + logger.log(data); + } + } + } +} energy_scanner; + +int main() +{ + // Hardware + configure(); + + // SD + try + { + logger.start(); + printf("\nLog file opened! (%s)\n\n", + logger.getFileName(logger.getLogNumber()).c_str()); + } + catch (const std::runtime_error& err) + { + GpioLedLog::high(); + printf("\n!!!!!!Error opening log file!!!!!!!\n\n"); + } + + // XBee + SPIBus spi_bus(SPI1); + SPIBusConfig cfg{}; + cfg.clock_div = SPIClockDivider::DIV16; + + GpioPin cs = GpioCS::getPin(); + GpioPin attn = GpioATTN::getPin(); + GpioPin rst = GpioRST::getPin(); + + xbee = new Xbee::Xbee(spi_bus, cfg, cs, attn, rst); + + // GUI + gui = new XbeeGUI(); + + btn_handler = new ButtonHandler<GpioUserBtn>( + 0, bind(&ScreenManager::onButtonPress, &gui->screen_manager, _2)); + + btn_handler->start(); + + gui->screen_config.btn_start.addOnInteractionListener(onStartButtonClick); + gui->screen_config.btn_energy.addOnInteractionListener(onEnergyButtonClick); + + gui->screen_status.btn_stop.addOnInteractionListener(onStopButtonClick); + gui->screen_status.btn_mark.addOnInteractionListener(onMarkButtonClick); + + gui->screen_energy.btn_stop.addOnInteractionListener(onStopButtonClick); + gui->screen_energy.btn_mark.addOnInteractionListener(onMarkButtonClick); + gui->screen_energy.btn_reset.addOnInteractionListener( + [&](View* d, Interaction action) { + UNUSED(d); + if (action == Interaction::CLICK) + gui->screen_energy.resetStats(); + }); + + gui->screen_end.tv_f.addOnInteractionListener( + [&](View* d, Interaction action) { + UNUSED(d); + if (action == Interaction::CLICK) + gui->screen_manager.showScreen(XbeeGUI::SCREEN_RESPECT); + }); + + gui->screen_end.tv_reset.addOnInteractionListener( + [&](View* d, Interaction action) { + UNUSED(d); + if (action == Interaction::CLICK) + miosix::reboot(); + }); + + // Main loop: updates the information in the GUI + for (;;) + { + long long start = getTick(); + // Update display values + switch (gui->screen_manager.getScreen()) + { + case XbeeGUI::SCREEN_CONFIG: + gui->screen_config.updateLogStatus(logger); + break; + case XbeeGUI::SCREEN_STATUS: + if (trans && xbee) + { + gui->screen_status.updateXbeeStatus( + trans->getReceiver().getDataRate(), + trans->getSender().getDataRate(), + trans->getSender().getTxData(), + trans->getReceiver().getRxData(), xbee->getStatus()); + + logger.log(xbee->getStatus()); + } + + gui->screen_status.updateLogStatus(logger); + break; + case XbeeGUI::SCREEN_ENERGYSCAN: + gui->screen_energy.updateLogStatus(logger); + break; + default: + break; + } + + logger.log(logger.getLogStats()); + Thread::sleepUntil(start + 500); + } +} + +void onStartButtonClick(View* btn, Interaction action) +{ + UNUSED(btn); + if (action == Interaction::CLICK) + { + + XbeeConfig cfg = gui->screen_config.config; + cfg.timestamp = getTick(); + logger.log(cfg); + + gui->screen_config.btn_start.setText("Starting..."); + + gui->screen_status.updateConfig(cfg); + + setupXbee(cfg); + startTransceiver(cfg); + + // Show status screen + gui->screen_manager.showScreen(XbeeGUI::SCREEN_STATUS); + } +} + +void onStopButtonClick(View* btn, Interaction action) +{ + if (action == Interaction::LONG_CLICK) + { + TextView* tv_btn = dynamic_cast<TextView*>(btn); + + if (tv_btn) + { + tv_btn->setText("Stopping..."); + } + + if (trans) + { + trans->stop(); + } + + if (energy_scanner.isRunning()) + { + energy_scanner.stop(); + } + + logger.stop(); + + gui->screen_manager.showScreen(XbeeGUI::SCREEN_END); + } +} + +void onMarkButtonClick(View* btn, Interaction action) +{ + UNUSED(btn); + if (action == Interaction::CLICK) + { + Mark m{getTick(), mark_counter++}; + logger.log(m); + + TextView* tv_btn = dynamic_cast<TextView*>(btn); + if (tv_btn) + { + tv_btn->setText("Mark Log (" + to_string(mark_counter) + ")"); + } + } +} + +void onEnergyButtonClick(View* btn, Interaction action) +{ + UNUSED(btn); + if (action == Interaction::CLICK) + { + energy_scanner.start(); + gui->screen_manager.showScreen(XbeeGUI::SCREEN_ENERGYSCAN); + } +} + +void startTransceiver(XbeeConfig config) +{ + snd_int.interval = config.send_interval; + trans = new XbeeTransceiver(*xbee, logger, snd_int, config.packet_size, + config.send_interval); + + if (!config.tx_enabled) + { + trans->disableSender(); + } + + trans->start(); +} + +void setupXbee(XbeeConfig config) +{ + if (config.data_rate_80k) + { + if (!Xbee::setDataRate(*xbee, true)) + { + gui->screen_status.tv_cfg_data_rate.setBackgroundColor(mxgui::red); + TRACE("[main] Error setting xbee data rate!\n"); + } + } + + if (!config.freq_hop) + { + if (!Xbee::disableFrequencyHopping(*xbee)) + { + gui->screen_status.tv_cfg_freq_hop.setBackgroundColor(mxgui::red); + TRACE("[main] Error disabling frequency hop!\n"); + } + } +} + +void configure() +{ + { + FastInterruptDisableLock dLock; + + // Enable SPI5 and TIM5 peripheral clocks + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; + + // Set SPI pins to correct alternate mode + GpioSck::mode(Mode::ALTERNATE); + GpioMiso::mode(Mode::ALTERNATE); + GpioMosi::mode(Mode::ALTERNATE); + + GpioSck::alternateFunction(5); + GpioMiso::alternateFunction(5); + GpioMosi::alternateFunction(5); + + GpioATTN::mode(Mode::INPUT_PULL_UP); + + GpioLedLog::mode(Mode::OUTPUT); + GpioUserBtn::mode(Mode::INPUT_PULL_DOWN); + + // Set chip select pin to OUTPUT + GpioCS::mode(Mode::OUTPUT); + } + + // Chip select starts high (not asserted) + GpioCS::high(); + GpioLedLog::low(); + + // Enable rising-edge interrupt detection on PA2 + enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE); +} + +void __attribute__((used)) EXTI5_IRQHandlerImpl() +{ + if (xbee) + { + xbee->handleATTNInterrupt(); + } +} \ No newline at end of file diff --git a/src/shared/drivers/adc/InternalADCData.h b/src/tests/drivers/xbee/test-xbee-rcv.cpp similarity index 75% rename from src/shared/drivers/adc/InternalADCData.h rename to src/tests/drivers/xbee/test-xbee-rcv.cpp index 5c8bc5f2dd5aeec6052e4ad4b7ad28a213049389..eae22c0a43e919f6d051a1cde1ce4111e292f89e 100644 --- a/src/shared/drivers/adc/InternalADCData.h +++ b/src/tests/drivers/xbee/test-xbee-rcv.cpp @@ -1,36 +1,25 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta - * +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * * 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 + * 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. */ -#pragma once +#define RUN_SENDER false -#include <cstdint> - -struct InternalADCSample -{ - uint32_t timestamp; - - uint8_t adc; - uint8_t channel_id; - - uint16_t value; -}; \ No newline at end of file +#include "test-xbee-bidir.cpp" \ No newline at end of file diff --git a/src/tests/drivers/xbee/test-xbee-snd.cpp b/src/tests/drivers/xbee/test-xbee-snd.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9660ad67a2e75b7f38c9836e104f566d79979367 --- /dev/null +++ b/src/tests/drivers/xbee/test-xbee-snd.cpp @@ -0,0 +1,25 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + +#define RUN_RECEIVER false + +#include "test-xbee-bidir.cpp" diff --git a/src/tests/kalman/test-kalman-benchmark.cpp b/src/tests/kalman/test-kalman-benchmark.cpp index bd9ba8ad8b949e9187c3700e48ceff2e0becb5dc..76942c8a35204d9eb76921406fc93408a0c2b257 100644 --- a/src/tests/kalman/test-kalman-benchmark.cpp +++ b/src/tests/kalman/test-kalman-benchmark.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Mozzarelli + * Author: Luca Mozzarelli * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -23,14 +23,15 @@ // This prgram runs through a simulated flight and reports the apogee detection, // while measuring the time elapsed - // RESULT: Update operation 0.0319 on average #include <Common.h> #include <drivers/HardwareTimer.h> #include <kalman/Kalman.h> + #include <iostream> -#include "util/util.h" + #include "test-kalman-data.h" +#include "util/util.h" using namespace miosix; @@ -47,22 +48,25 @@ int main(int argc, char const* argv[]) // Setting pin mode for signaling ADA status { FastInterruptDisableLock dLock; - greenLed::mode(Mode::OUTPUT); - redLed::mode(Mode::OUTPUT); + //greenLed::mode(Mode::OUTPUT); + //redLed::mode(Mode::OUTPUT); + + RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; } // Timer for benchmarking purposes - HardwareTimer<uint32_t, 2>& timer = HardwareTimer<uint32_t, 2>::instance(); + HardwareTimer<uint32_t> timer{TIM5, TimerUtils::getPrescalerInputFrequency( + TimerUtils::InputClock::APB1)}; // Instanciate matrices - MatrixBase<float,3,3> P{0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1}; - MatrixBase<float,1,1> V2{10}; - MatrixBase<float,3,3> V1{0.01, 0, 0, 0, 0.01, 0, 0, 0, 0.01}; - MatrixBase<float,1,3> C{1, 0, 0}; - MatrixBase<float,3,3> A{1, 0, 0, 0, 1, 0, 0, 0, 1}; + MatrixBase<float, 3, 3> P{0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1}; + MatrixBase<float, 1, 1> V2{10}; + MatrixBase<float, 3, 3> V1{0.01, 0, 0, 0, 0.01, 0, 0, 0, 0.01}; + MatrixBase<float, 1, 3> C{1, 0, 0}; + MatrixBase<float, 3, 3> A{1, 0, 0, 0, 1, 0, 0, 0, 1}; // Instanciate filter object - Kalman<3,1> filter = Kalman<3,1>(A, C, V1, V2, P); + Kalman<3, 1> filter = Kalman<3, 1>(A, C, V1, V2, P); float last_time = 0.0; // Variable to save the time of the last sample float time; // Current time as read from csv file @@ -74,13 +78,16 @@ int main(int argc, char const* argv[]) uint32_t tick1; uint32_t tick2; + printf("%d %d \n", TIME.size(), INPUT.size()); + for (unsigned i = 0; i < TIME.size(); i++) { if (i == 0) { - filter.X(0,0) = INPUT[0]; + filter.X(0, 0) = INPUT[0]; continue; } + time = TIME[i]; T = time - last_time; @@ -88,25 +95,30 @@ int main(int argc, char const* argv[]) filter.A(0, 2) = 0.5 * T * T; filter.A(1, 2) = T; - MatrixBase<float,1,1> y{}; - y(0,0) = INPUT[i]; + MatrixBase<float, 1, 1> y{}; + y(0, 0) = INPUT[i]; tick1 = timer.tick(); + filter.update(y); + tick2 = timer.tick(); - printf("%f \n", timer.toMilliSeconds(tick2 - tick1)); + + printf("%d : %f \n", i, timer.toMilliSeconds(tick2 - tick1)); + // printf("%f, %f, %f;\n", filter.X(0,0), filter.X(1,0), filter.X(2,0)); // std::cout << MemoryProfiling::getCurrentFreeStack() << "\n"; last_time = time; - if (filter.X(1,0) < 0) + if (filter.X(1, 0) < 0) { - greenLed::high(); - redLed::low(); + printf("APOGEE DETECTED at iteration %d ! \n", i); + //greenLed::high(); + //redLed::low(); } else { - greenLed::low(); - redLed::high(); + //greenLed::low(); + //redLed::high(); } } timer.stop(); diff --git a/src/tests/kalman/test-kalman-data.h b/src/tests/kalman/test-kalman-data.h index 18720ca343360ef70868840e4397cfc039ac0c35..c11958d5e314707cd090fe8d73069abbd7675045 100644 --- a/src/tests/kalman/test-kalman-data.h +++ b/src/tests/kalman/test-kalman-data.h @@ -1,3 +1,25 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Mozzarelli + * + * 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. + */ + #pragma once #include <vector> @@ -86,7 +108,7 @@ static const std::vector<float> TIME = { 44, 44.05, 44.1, 44.15, 44.2, 44.25, 44.3, 44.35, 44.4, 44.45, 44.5, 44.55, 44.6, 44.65, 44.7, 44.75, 44.8, 44.85, 44.9, 44.95, 45, 45.05, 45.1, 45.15, 45.2, 45.25, 45.3, 45.35, 45.4, 45.45, 45.5, 45.55, 45.6, - 45.65, 45.7, 45.75, 45.8, 45.85, 45.9, 45.95, 46, 46.05, 46.1, 46.15, + 45.65, 45.7, 45.75, 45.8, 45.85, 45.9, 45.95, 46, 46.05, 46.1, 46.15, 46.2, 46.25, 46.3, 46.35, 46.4, 46.45, 46.5, 46.55, 46.6, 46.65, 46.7, 46.75, 46.8, 46.85, 46.9, 46.95, 47, 47.05, 47.1, 47.15, 47.2, 47.25, 47.3, 47.35, 47.4, 47.45, 47.5, 47.55, 47.6, 47.65, 47.7, 47.75, 47.8, @@ -178,7 +200,7 @@ static const std::vector<float> TIME = { 94.6, 94.65, 94.7, 94.75, 94.8, 94.85, 94.9, 94.95, 95, 95.05, 95.1, 95.15, 95.2, 95.25, 95.3, 95.35, 95.4, 95.45, 95.5, 95.55, 95.6, 95.65, 95.7, 95.75, 95.8, 95.85, 95.9, 95.95, 96, 96.05, 96.1, 96.15, 96.2, - 96.25, 96.3}; + 96.25, 96.3 }; // ------------------------ INPUT ------------------------ static const std::vector<float> INPUT = { @@ -294,7 +316,7 @@ static const std::vector<float> INPUT = { 10314.9, 10298.9, 10323, 10331, 10323, 10331, 10323, 10331, 10339, 10314.9, 10347.1, 10323, 10323, 10314.9, 10355.1, 10323, 10314.9, 10331, 10331, 10339, 10347.1, 10347.1, 10355.1, 10323, - 10347.1, 10314.9, 10347.1, 10363.2, 10347.1, 10339, 10355.1, 10363.2, + 10347.1, 10314.9, 10347.1, 10363.2, 10347.1, 10339, 10355.1, 10363.2, 10355.1, 10371.2, 10339, 10347.1, 10331, 10355.1, 10331, 10331, 10363.2, 10355.1, 10379.3, 10379.3, 10387.4, 10355.1, 10339, 10371.2, 10371.2, 10347.1, 10379.3, 10371.2, 10371.2, 10363.2, 10371.2, 10347.1, @@ -422,7 +444,7 @@ static const std::vector<float> INPUT = { 494.489, 478.164, 461.865, 453.725, 429.344, 423.934, 394.229, 375.369, 367.298, 348.488, 332.392, 310.97, 289.592, 284.255, 257.608, 246.969, 231.03, 212.466, 193.936, 186.004, 164.882, 146.436, 133.281, 114.89, - 104.396, 80.8236, 59.9147, 49.476, 31.2335, 10.424, -5.15584}; + 104.396, 80.8236, 59.9147, 49.476, 31.2335, 10.424, -5.15584 }; // ------------------------ STATE ------------------------ static const std::vector<float> STATE_1 = { diff --git a/src/tests/kalman/test-kalman-eigen-benchmark.cpp b/src/tests/kalman/test-kalman-eigen-benchmark.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d7bfa315c285628980404ec9c4b21e0e8f0efb95 --- /dev/null +++ b/src/tests/kalman/test-kalman-eigen-benchmark.cpp @@ -0,0 +1,142 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +// This prgram runs through a simulated flight and reports the apogee detection, +// while measuring the time elapsed + +#define EIGEN_RUNTIME_NO_MALLOC + +#include <Common.h> +#include <drivers/HardwareTimer.h> +#include <kalman/KalmanEigen.h> +#include <src/tests/kalman/test-kalman-data.h> + +#include <iostream> + +#include "math/SkyQuaternion.h" +#include "util/util.h" + +using namespace Eigen; +using namespace miosix; + +int main() +{ + // Setting pin mode for signaling ADA status + { + FastInterruptDisableLock dLock; + + RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; + } + + printf("RUNNING...\n"); + + // Timer for benchmarking purposes + HardwareTimer<uint32_t> timer{TIM5, TimerUtils::getPrescalerInputFrequency( + TimerUtils::InputClock::APB1)}; + + const int n = 3; + const int p = 1; + + Matrix<float, n, n> F = + (Matrix<float, n, n>(n, n) << 1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0) + .finished(); + // Output matrix + Matrix<float, p, n> H{1, 0, 0}; + + // Initial error covariance matrix + Matrix<float, n, n> P = (Matrix<float, n, n>(n, n) << 0.1, 0.0, 0.0, 0.0, + 0.1, 0.0, 0.0, 0.0, 0.1) + .finished(); + // Model variance matrix + Matrix<float, n, n> Q = (Matrix<float, n, n>(n, n) << 0.01, 0.0, 0.0, 0.0, + 0.01, 0.0, 0.0, 0.0, 0.01) + .finished(); + // Measurement variance + Matrix<float, p, p> R{10}; + + Matrix<float, n, 1> x0(INPUT[0], 0.0, 0.0); + + Matrix<float, p, 1> y(p); // vector with p elements (only one in this case) + + KalmanEigen<float, n, p>::KalmanConfig config; + config.F = F; + config.H = H; + config.Q = Q; + config.R = R; + config.P = P; + config.x = x0; + + KalmanEigen<float, n, p> filter(config); + + float last_time = 0.0; // Variable to save the time of the last sample + float time; // Current time as read from csv file + float T; // Time elapsed between last sample and current one + + timer.start(); + uint32_t tick1; + uint32_t tick2; + + printf("%d %d \n", TIME.size(), INPUT.size()); + + for (unsigned i = 1; i < TIME.size(); i++) + { + time = TIME[i]; + T = time - last_time; + + F(0, 1) = T; + F(0, 2) = 0.5 * T * T; + F(1, 2) = T; + + y(0) = INPUT[i]; + + tick1 = timer.tick(); + + filter.predict(F); + + if (!filter.correct(y)) + { + printf("Correction failed at iteration : %d \n", i); + } + + tick2 = timer.tick(); + + printf("%d : %f \n", i, timer.toMilliSeconds(tick2 - tick1)); + + // printf("%f, %f, %f;\n", filter.getState()(0), filter.getState()(1), + // filter.getState()(2)); + + // printf("%u \n", MemoryProfiling::getCurrentFreeStack()); + + last_time = time; + + if (filter.getState()(1) < 0) + { + printf("APOGEE DETECTED at iteration %d ! \n", i); + } + } + + timer.stop(); + + // printf("Total time %d \n", timer.interval()); + + return 0; +} diff --git a/src/tests/logger/test-logger.cpp b/src/tests/logger/test-logger.cpp index 36c3f55c9c1e1f6537567694caffbbe5d728547d..44d4e6684a6b45a1727d00e6d1702b475d2df97d 100644 --- a/src/tests/logger/test-logger.cpp +++ b/src/tests/logger/test-logger.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Terraneo Federico + * Author: Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/logger/test-logger.h b/src/tests/logger/test-logger.h index 5e6e33fff11d243fef8b048835626d47d1280620..dea913420cd21184015a718d585e14a03376048d 100644 --- a/src/tests/logger/test-logger.h +++ b/src/tests/logger/test-logger.h @@ -1,5 +1,5 @@ /* Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Terraneo Federico + * Author: Federico Terraneo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/misc/xbee-bitrate.cpp b/src/tests/misc/xbee-bitrate.cpp index 229fa358f1f54b48fc16f47d6aeee8b5aaf8b239..3bdeb4946ac8479b41c4e468cebc74b52fe0831a 100644 --- a/src/tests/misc/xbee-bitrate.cpp +++ b/src/tests/misc/xbee-bitrate.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 @@ -31,7 +30,7 @@ #include "drivers/Xbee/Xbee.h" #include "math/Stats.h" -#include <drivers/BusTemplate.h> +#include <drivers/spi/SPIDriver.h> using std::cin; using std::cout; @@ -43,20 +42,24 @@ static constexpr int PKT_NUM = 100; using namespace miosix; using namespace interfaces; -// SPI1 binding al sensore - // WARNING: If flashing on stm32f49 discovery board (with screen removed) use // SPI1 as the 2nd isnt working. -typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI2; // Creo la SPI2 -// typedef BusSPI<1, spi2::mosi, spi2::miso, spi2::sck> busSPI2; // Creo la -// SPI2 +// Discovery +SPIBus bus{SPI1}; +GpioPin cs = sensors::lsm6ds3h::cs::getPin(); +GpioPin attn = xbee::attn::getPin(); +GpioPin rst = xbee::reset::getPin(); + +// Death stack +// SPIBus bus{SPI2}; +// GpioPin cs = xbee::cs::getPin(); +// GpioPin attn = xbee::attn::getPin(); +// GpioPin rst = xbee::reset::getPin(); -// WARNING: Don't use xbee::cs on discovery board as it isn't working -typedef Xbee::Xbee<busSPI2, sensors::lsm6ds3h::cs, xbee::attn, xbee::reset> - Xbee_t; -Xbee_t xbee_transceiver; +Xbee::Xbee* xbee_transceiver; + void __attribute__((used)) EXTI10_IRQHandlerImpl() { Xbee::handleATTNInterrupt(); } void enableXbeeInterrupt() @@ -102,7 +105,7 @@ bool sendPacket(uint8_t size) } ++snd_cntr; - if (!xbee_transceiver.send(snd_buf, size)) + if (!xbee_transceiver->send(snd_buf, size)) { return false; } @@ -119,8 +122,9 @@ void resetXBee() int main() { enableXbeeInterrupt(); - busSPI2::init(); - xbee_transceiver.start(); + xbee_transceiver = new Xbee::Xbee(bus, cs, attn, rst); + + xbee_transceiver->start(); resetXBee(); printf("XBee bitrate measurement\n"); diff --git a/src/tests/misc/xbee-send-rcv.cpp b/src/tests/misc/xbee-send-rcv.cpp index a5fced8963c8e89aa73735b3e7a93b8a918813af..86a365b24d01117b5c6335b4c6d3ed89c909476b 100644 --- a/src/tests/misc/xbee-send-rcv.cpp +++ b/src/tests/misc/xbee-send-rcv.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,15 +13,17 @@ * * 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 + * 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. */ +#include <drivers/spi/SPIDriver.h> #include <interfaces-impl/hwmapping.h> #include <miosix.h> + #include <cstdio> #include <iostream> #include <string> @@ -31,33 +32,36 @@ #include "drivers/Xbee/Xbee.h" #include "math/Stats.h" -#include <drivers/BusTemplate.h> - using std::cin; using std::cout; using std::string; -using HwTimer = HardwareTimer<uint32_t, 2>; static const unsigned int PKT_SIZE = 128; using namespace miosix; using namespace interfaces; -// SPI1 binding al sensore - // WARNING: If flashing on stm32f49 discovery board (with screen removed) use // SPI1 as the 2nd isnt working. -// typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI2; // Creo la SPI2 -typedef BusSPI<2, spi2::mosi, spi2::miso, spi2::sck> busSPI2; // Creo la -// SPI2 +// Discovery +SPIBus bus{SPI1}; +GpioPin cs = sensors::lsm6ds3h::cs::getPin(); +GpioPin attn = xbee::attn::getPin(); +GpioPin rst = xbee::reset::getPin(); -// WARNING: Don't use xbee::cs on discovery board as it isn't working -typedef Xbee::Xbee<busSPI2, xbee::cs, xbee::attn, xbee::reset> - Xbee_t; +// Death stack +// SPIBus bus{SPI2}; +// GpioPin cs = xbee::cs::getPin(); +// GpioPin attn = xbee::attn::getPin(); +// GpioPin rst = xbee::reset::getPin(); -Xbee_t xbee_transceiver; -void __attribute__((used)) EXTI10_IRQHandlerImpl() { Xbee::handleATTNInterrupt(); } +Xbee::Xbee* xbee_transceiver; + +void __attribute__((used)) EXTI10_IRQHandlerImpl() +{ + Xbee::handleATTNInterrupt(); +} void enableXbeeInterrupt() { @@ -103,7 +107,7 @@ void send() c = 48; } - if (!xbee_transceiver.send(buf, PKT_SIZE)) + if (!xbee_transceiver->send(buf, PKT_SIZE)) { printf("[%d] Send error %d\n", (int)getTick(), ++fail); } @@ -124,7 +128,7 @@ void receive(void*) uint8_t buf[512]; for (;;) { - ssize_t len = xbee_transceiver.receive(buf, 512); + ssize_t len = xbee_transceiver->receive(buf, 512); if (len <= 0) { printf("Receive failed.\n"); @@ -168,12 +172,9 @@ int main() // reset(); - HwTimer& t = HwTimer::instance(); - t.setPrescaler(1024); - - busSPI2::init(); + xbee_transceiver = new Xbee::Xbee(bus, cs, attn, rst); - xbee_transceiver.start(); + xbee_transceiver->start(); // Send & receive Thread::create(receive, 2048); diff --git a/src/tests/misc/xbee-time-to-send.cpp b/src/tests/misc/xbee-time-to-send.cpp index 4e2d8dc7a45cc7eedb5f3395c67f6cfa2a169a21..87f44851af19af11178ba70899e0cb8da2f20c53 100644 --- a/src/tests/misc/xbee-time-to-send.cpp +++ b/src/tests/misc/xbee-time-to-send.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 @@ -44,19 +43,24 @@ static constexpr int PKTS_PER_SECOND = 4; using namespace miosix; using namespace interfaces; -// SPI1 binding al sensore - // WARNING: If flashing on stm32f49 discovery board (with screen removed) use // SPI1 as the 2nd isnt working. -// typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI2; // Creo la SPI2 -typedef BusSPI<2, spi2::mosi, spi2::miso, spi2::sck> busSPI2; +// Discovery +SPIBus bus{SPI1}; +GpioPin cs = sensors::lsm6ds3h::cs::getPin(); +GpioPin attn = xbee::attn::getPin(); +GpioPin rst = xbee::reset::getPin(); + +// Death stack +// SPIBus bus{SPI2}; +// GpioPin cs = xbee::cs::getPin(); +// GpioPin attn = xbee::attn::getPin(); +// GpioPin rst = xbee::reset::getPin(); + +Xbee::Xbee* xbee_transceiver; -// WARNING: Don't use xbee::cs on discovery board as it isn't working -typedef Xbee::Xbee<busSPI2, xbee::cs, xbee::attn, xbee::reset> - Xbee_t; -Xbee_t xbee_transceiver; void __attribute__((used)) EXTI10_IRQHandlerImpl() { Xbee::handleATTNInterrupt(); } void enableXbeeInterrupt() @@ -101,7 +105,7 @@ bool sendPacket(uint8_t size) } ++snd_cntr; - if (!xbee_transceiver.send(snd_buf, size)) + if (!xbee_transceiver->send(snd_buf, size)) { return false; } @@ -112,8 +116,10 @@ bool sendPacket(uint8_t size) int main() { enableXbeeInterrupt(); - busSPI2::init(); - xbee_transceiver.start(); + + xbee_transceiver = new Xbee::Xbee(bus, cs, attn, rst); + + xbee_transceiver->start(); printf("XBee time-to-send-measurement\n"); printf( diff --git a/src/tests/test-hsm.cpp b/src/tests/test-hsm.cpp index 8495f86f8f0bf4404f82d76a3c1aa37545e1d2fb..37ba77c5f42040e562b674e0a48927cfff0e0d28 100644 --- a/src/tests/test-hsm.cpp +++ b/src/tests/test-hsm.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2018 Skyward Experimental Rocketry - * Authors: Matteo Michele Piazzolla + * Author: Matteo Piazzolla * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 @@ -32,7 +32,7 @@ using namespace miosix; #define TOPIC_TEST 1 -#define CHECK_INIT() bool test_value = false +#define CHECK_INIT() bool test_value = false; (void)test_value #define CHECK_STATE(HSM, SIGNAL, STATE) do{ \ cout << "------------------------------" << endl; \ diff --git a/src/tests/test-interrupt.cpp b/src/tests/test-interrupt.cpp index 16d45b62a8cd2c25039e9a4e509c2a0cf549341f..ede46687b69e2998033d49de14017416e4910dc9 100644 --- a/src/tests/test-interrupt.cpp +++ b/src/tests/test-interrupt.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2018 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2018 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 @@ -23,6 +22,7 @@ #include <miosix.h> #include <stdio.h> +#include "drivers/interrupt/external_interrupts.h" using namespace miosix; @@ -33,16 +33,6 @@ bool itr = false; void __attribute__((used)) EXTI0_IRQHandlerImpl() { itr = true; - EXTI->PR |= EXTI_PR_PR0; -} - -// Attribute naked: Containing assembly code -void __attribute__((naked)) EXTI0_IRQHandler() -{ - saveContext(); - // bl _Z<length of function name><function name>v - asm volatile("bl _Z20EXTI0_IRQHandlerImplv"); - restoreContext(); } typedef Gpio<GPIOA_BASE, 0> user_button; @@ -51,43 +41,17 @@ int main() { user_button::mode(Mode::INPUT); - { - FastInterruptDisableLock l; - RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; - } - // Refer to the datasheet for a detailed description on the procedure and interrupt registers - - // Clear the mask on the wanted line - EXTI->IMR |= EXTI_IMR_MR0; - - // Trigger the interrupt on a falling edge - EXTI->FTSR |= EXTI_FTSR_TR0; - - // Trigger the interrupt on a rising edge - //EXTI->RTSR |= EXTI_RTSR_TR0; - - EXTI->PR |= EXTI_PR_PR0; // Reset pending register - - // Enable interrupt on PA0 in SYSCFG - SYSCFG->EXTICR[0] = 0; - - // Enable the interrput in the interrupt controller - NVIC_EnableIRQ(EXTI0_IRQn); - NVIC_SetPriority(EXTI0_IRQn, 15); + enableExternalInterrupt(GPIOA_BASE, 0, InterruptTrigger::RISING_EDGE, 15); + unsigned int counter = 0; while (true) { if (itr) { - printf("Interrupt received!\n"); + ++counter; + printf("%d Interrupt received!\n", counter); itr = false; - ledOn(); - Thread::sleep(200); - ledOff(); - Thread::sleep(200); - ledOn(); - Thread::sleep(200); - ledOff(); } + Thread::sleep(50); } } \ No newline at end of file diff --git a/src/tests/test-pinobserver.cpp b/src/tests/test-pinobserver.cpp index 42ce1d2636543cdda9a351b0cf6d12974f8caa02..1fc665a279ad88c9494a2c49435481f5b71ea76b 100644 --- a/src/tests/test-pinobserver.cpp +++ b/src/tests/test-pinobserver.cpp @@ -1,6 +1,5 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -14,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/test-rls.cpp b/src/tests/test-rls.cpp new file mode 100644 index 0000000000000000000000000000000000000000..052f55d57aad450e2fd74ff84deb824753fa52f2 --- /dev/null +++ b/src/tests/test-rls.cpp @@ -0,0 +1,88 @@ +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Mozzarelli + * + * 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. + */ + +/* +* This test simulates power control in an unknown resistor (i.e. thermal cutters ;) ) +* y(t) = phi(t)^T * theta(t) +* P = V^2/R +* +* y(t) = P(t) = V(t)*I(t) (power on the resistor) +* phi(t) = V(t)^2 (square of the voltage on the resistor) +* theta(t) = 1/R (inverse of the resistance) +*/ + +#include <libs/simple-template-matrix/matrix.h> +#include <rls/RLS.h> +#include <math.h> +#include <iostream> + + +int main() +{ + + float R = 0.3; // True resistance + float P_ref = 20; // Power setpoint + float V; // Voltage applied to the resistor + float I; // Current in the resistor + + float R0 = 0.5; // Inital guess of R + MatrixBase<float,1,1> theta0{1/R0}; + MatrixBase<float,1,1> V0{1}; // Confidence of the initial guess (variance) + float mu = 0.7; // Forgetting factor + float R_est; // Estimated R + + // Instantiate the RLS + RLS<1> rls{V0,theta0,mu}; + + MatrixBase<float,1,1> phi{0}; + MatrixBase<float,1,1> y{0}; + + for (unsigned i = 0; i < 1000; i++) + { + // Get the estimate of R + R_est = 1/(rls.getEstimate()(0,0)); + + // Compute the power on the resistor + V = sqrtf(P_ref*R_est); + + // Compute the mesured current + I = V/R; + + // Regressors vector + phi(0,0) = V*V; + + // Measurement vector + y(0,0) = V*I; + + // Update the filter + rls.update(y,phi); + + // std::cout << R << ", " << R_est << ", " << R-R_est << ", " << V*I << "\n"; + + // Start a slope on R + if (i > 500) + { + R = R+0.05; + } + } + +} \ No newline at end of file diff --git a/src/tests/test-sensormanager.cpp b/src/tests/test-sensormanager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a77c51e3e25e7442515ba0f814baaa0aa2ad1170 --- /dev/null +++ b/src/tests/test-sensormanager.cpp @@ -0,0 +1,280 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#include <assert.h> + +#include <functional> +#include <iostream> +#include <type_traits> + +#include "TimestampTimer.h" +#include "miosix.h" +#include "sensors/Sensor.h" +#include "sensors/SensorManager.h" +#include "utils/testutils/TestSensor.h" + +using namespace miosix; +using namespace TimestampTimer; + +// Data produced by the MySensor sensor. +struct MySensorData : public PressureData, public TemperatureData +{ + + MySensorData() : PressureData{0, 0.0}, TemperatureData{0, 0.0} {} + + MySensorData(float p, float t) + : PressureData{getTimestamp(), p}, TemperatureData{getTimestamp(), t} + { + } +}; + +// Specific Sensor implementation. +class MySensor : public Sensor<MySensorData> +{ +public: + virtual MySensorData sampleImpl() override + { + return MySensorData(rand() % 100, rand() % 100); + } + + bool init() override { return true; }; + bool selfTest() override { return true; }; +}; + +template <typename SensorData> +class MyPressureFilter : public Sensor<SensorData> +{ + static_assert( + checkIfProduces<Sensor<SensorData>, PressureData>::value, + "Template argument must be a sensor that produces pressure data."); + +public: + MyPressureFilter(Sensor<SensorData>* original_sensor, float offset) + : original_sensor(original_sensor), offset(offset) + { + } + + bool init() override { return true; }; + bool selfTest() override { return true; }; + + SensorData sampleImpl() { return SensorData(); } + + const SensorData& getLastSample() override + { + this->last_sample = original_sensor->getLastSample(); + this->last_sample.press += offset; + return this->last_sample; + } + +private: + Sensor<SensorData>* original_sensor; + float offset; +}; + +struct MySensorDataFIFO : public AccelerometerData, public GyroscopeData +{ + + MySensorDataFIFO() + : AccelerometerData{getTimestamp(), 0.0, 0.0, 0.0}, GyroscopeData{ + getTimestamp(), + 0.0, 0.0, 0.0} + { + } + + MySensorDataFIFO(AccelerometerData acc, GyroscopeData gyro) + : AccelerometerData{getTimestamp(), acc.accel_x, acc.accel_y, + acc.accel_z}, + GyroscopeData{getTimestamp(), gyro.gyro_x, gyro.gyro_y, gyro.gyro_z} + { + } +}; + +class MySensorFIFO : public SensorFIFO<MySensorDataFIFO, 20> +{ + uint32_t fifo_size = 20; + +public: + MySensorFIFO() {} + + // return last sample + virtual MySensorDataFIFO sampleImpl() override + { + for (uint32_t i = 0; i < fifo_size; i++) + { + AccelerometerData acc{getTimestamp(), 0.5, 0.5, 0.5}; + GyroscopeData gyro{getTimestamp(), 0.5, 0.5, 0.5}; + + last_fifo[i] = MySensorDataFIFO{acc, gyro}; + + TRACE("Accel : %llu %f %f %f \n", acc.accel_timestamp, acc.accel_x, + acc.accel_y, acc.accel_z); + TRACE("Gyro : %llu %f %f %f \n", gyro.gyro_timestamp, gyro.gyro_x, + gyro.gyro_y, gyro.gyro_z); + + Thread::sleep(5); + } + + last_fifo_level = fifo_size; + + return last_fifo[last_fifo_level - 1]; + } + + bool init() override { return true; }; + bool selfTest() override { return true; }; +}; + +template <typename FIFOData, uint32_t FifoSize> +class FIFOProxy : public SensorFIFO<FIFOData, FifoSize> +{ +public: + FIFOProxy(SensorFIFO<FIFOData, FifoSize>* sensor) : sensor(sensor) {} + + bool init() override { return true; }; + bool selfTest() override { return true; }; + + FIFOData sampleImpl() + { + index = 0; + return sensor->getFifoElement(index); + } + + const FIFOData& getLastSample() override + { + if (index < FifoSize - 1) + { + index++; + } + + TRACE("Index : %d \n", index); + + return sensor->getFifoElement(index); + } + +private: + SensorFIFO<FIFOData, FifoSize>* sensor; + uint32_t index = 0; +}; + +class FailingSensor : public Sensor<TestData> +{ + bool init() { return true; } + + bool selfTest() // always fail self-test + { + TRACE("Failed to init sensor FailingSensor!\n"); + return false; + } + + TestData sampleImpl() { return TestData{}; } +}; + +int main() +{ + srand(time(NULL)); + + enableTimestampTimer(); + + MySensor s1; + MySensor s2; + + MyPressureFilter<MySensorData> filter(&s1, 2.578f); + + FailingSensor failig_s; // must not be initialized and not sampled + + SensorManager sm( + {{/*Sensor=*/&s1, + {/*ID=*/"s1", + /*Freq=*/1000, + /*Callback=*/[]() { std::cout << "Callback s1!" << endl; }, + /*DMA=*/false, + /*Enabled=*/true}}, + {/*Sensor=*/&s2, + {/*ID=*/"s2", + /*Freq=*/1000, + /*Callback=*/[]() { std::cout << "Callback s2!" << endl; }, + /*DMA=*/false, + /*Enabled=*/true}}, + {/*Sensor=*/&filter, + {/*ID=*/"filter", + /*Freq=*/2000, + /*Callback=*/ + []() { std::cout << "Callback filter!" << endl; }, + /*DMA=*/false, + /*Enabled=*/true}}, + {/*Sensor=*/&failig_s, + {/*ID=*/"failing", + /*Freq=*/3000, + /*Callback=*/ + []() { std::cout << "Callback failing sensor!" << endl; }, + /*DMA=*/false, + /*Enabled=*/true}}}); + + sm.start(); + + Thread::sleep(1000); + + // TEST NORMAL SENSORS + + sm.disableSensor(&s1); + + Thread::sleep(1000); + + sm.enableSensor(&s1); + + for (int i = 0; i < 3; i++) + { + TRACE("S1 : %llu %f %llu %f \n", s1.getLastSample().press_timestamp, + s1.getLastSample().press, s1.getLastSample().temp_timestamp, + s1.getLastSample().temp); + TRACE("Filter : %llu %f \n", filter.getLastSample().press_timestamp, + filter.getLastSample().press); + + Thread::sleep(1000); + } + + // TEST SENSORS WITH FIFO + + const uint32_t fifo_size = 20; + + MySensorFIFO s; + + FIFOProxy<MySensorDataFIFO, fifo_size> fifo_proxy(&s); + + for (int i = 0; i < 3; i++) + { + s.sample(); + + MySensorDataFIFO data = fifo_proxy.getLastSample(); + + TRACE("AccelProxy : %llu %f %f %f \n", data.accel_timestamp, + data.accel_x, data.accel_y, data.accel_z); + TRACE("GyroProxy : %llu %f %f %f \n", data.gyro_timestamp, data.gyro_x, + data.gyro_y, data.gyro_z); + + Thread::sleep(1000); + } + + for (;;) + ; + + return 0; +} \ No newline at end of file diff --git a/src/tests/test-serial.cpp b/src/tests/test-serial.cpp index 744fc7a57bc4dc4b91e43f4a878eb535d4af0f20..35ca8e152c44b19c577169a65dcc2ce40a50ea9b 100644 --- a/src/tests/test-serial.cpp +++ b/src/tests/test-serial.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2015-2017 Skyward Experimental Rocketry - * Authors: Luca Erbetta <luca.erbetta@skywarder.eu> + * Author: Luca Erbetta * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -13,7 +13,7 @@ * * 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 + * 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 diff --git a/src/tests/test-taskscheduler.cpp b/src/tests/test-taskscheduler.cpp index cde3979a0114c9e5f86932c8a72912da0f69ca4a..6d8e877e0716b70c146344241b7dbe486bfe7d4f 100644 --- a/src/tests/test-taskscheduler.cpp +++ b/src/tests/test-taskscheduler.cpp @@ -1,20 +1,19 @@ -/* - * Copyright (c) 2019 Skyward Experimental Rocketry - * Authors: Luca Erbetta - * +/* Copyright (c) 2019 Skyward Experimental Rocketry + * Author: Luca Erbetta + * * 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 + * 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 diff --git a/src/tests/test-timestamp-timer.cpp b/src/tests/test-timestamp-timer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ea258f00cb02c18ccb07855ae95ab6d7dac8157e --- /dev/null +++ b/src/tests/test-timestamp-timer.cpp @@ -0,0 +1,39 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Conterio + * + * 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. + */ + +#include <miosix.h> +#include <stdio.h> +#include "TimestampTimer.h" + +using namespace miosix; + +int main() +{ + TimestampTimer::enableTimestampTimer(); + + while (true) + { + printf("%llu microseconds \n", TimestampTimer::getTimestamp()); + + Thread::sleep(1000); + } +} \ No newline at end of file diff --git a/src/tests/test-trace-logger.cpp b/src/tests/test-trace-logger.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0c391fa6e18c2ed1d15e32f6a9e1a2c55f1a58c1 --- /dev/null +++ b/src/tests/test-trace-logger.cpp @@ -0,0 +1,59 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Luca Erbetta + * + * 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. + */ + + +#include <diagnostic/PrintLogger.h> +#include <miosix.h> + +int main() +{ + Logging::startAsyncLogger(); + + PrintLogger logger = Logging::getLogger("root"); + PrintLogger log2 = logger.getChild("b.c"); + PrintLogger log3 = logger.getChild("a"); + PrintLogger log4 = Logging::getLogger("async"); + + // Logging::getStdOutLogSink().setFormatString("{ts} [{name}] {msg}\n"); + + PrintLogger logfile = Logging::getLogger("logfile"); + unique_ptr<LogSink> logfile_sink = std::make_unique<FileLogSinkBuffered>(); + logfile_sink->setLevel(LOGL_INFO); + Logging::addLogSink(logfile_sink); + + int async_ctr = 0; + for (;;) + { + LOG_ERR(logger, "This is a message {}", 52); + LOG_INFO(log2, "Ops, there was an {}!", "error"); + for (int i = 0; i < 4; i++) + { + LOG_INFO(log4, "This is an async log! {:d}", async_ctr++); + } + LOG_DEBUG(log3, "This is a verbose debug message {:.3f}", 1.234f); + LOG_CRIT(log3, "Float {:.1f} {:05.2f} {:f}", 1.234f, 1234.1234, + -9876.98765432f); + LOG_ERR(logger, "Simple log with no arguments"); + + miosix::Thread::sleep(1000); + } +}