├── CMakeLists.txt ├── Changelog.md ├── Findcpputils.cmake ├── Jenkinsfile ├── LICENSE ├── Makefile ├── Makefile.bench ├── Makefile.linux ├── Makefile.win32 ├── README.md ├── cmake_find ├── Finddoctest.cmake ├── Findfmt.cmake └── boilerplate.cmake ├── fmtbench ├── CMakeLists.txt ├── fmt_print-boost.cpp ├── fmt_print-fmt.cpp ├── fmt_print-fmt2.cpp ├── fmt_print-formatter.cpp ├── fmt_print-printf.cpp ├── fmt_print-std.cpp ├── fmt_string-boost.cpp ├── fmt_string-fmt.cpp ├── fmt_string-fmt2.cpp ├── fmt_string-formatter.cpp ├── fmt_string-printf.cpp └── fmt_string-std.cpp ├── include └── cpputils │ ├── HiresTimer.h │ ├── argparse.h │ ├── arrayview.h │ ├── b32-alphabet.h │ ├── b64-alphabet.h │ ├── base32encoder.h │ ├── base64encoder.h │ ├── crccalc.h │ ├── datapacking.h │ ├── fhandle.h │ ├── formatter.h │ ├── fslibrary.h │ ├── hexdumper.h │ ├── is_stream_insertable.h │ ├── lowpass_filter.h │ ├── minmax.h │ ├── mmem.h │ ├── mmfile.h │ ├── string-base.h │ ├── string-join.h │ ├── string-lineenum.h │ ├── string-parse.h │ ├── string-split.h │ ├── string-strip.h │ ├── stringconvert.h │ ├── stringlibrary.h │ ├── templateutils.h │ ├── timepoint.h │ ├── utfconvertor.h │ ├── utfcvutils.h │ ├── xmlnodetree.h │ └── xmlparser.h └── tests ├── CMakeLists.txt ├── test-argparse.cpp ├── test-arrayview.cpp ├── test-base32.cpp ├── test-base64.cpp ├── test-crccalc.cpp ├── test-datapack.cpp ├── test-fhandle.cpp ├── test-formatter.cpp ├── test-fslib.cpp ├── test-hexdump.cpp ├── test-lineenum.cpp ├── test-mmem.cpp ├── test-strconvert.cpp ├── test-strip.cpp ├── test-strlib.cpp ├── test-timepoint.cpp ├── test-xmlparse.cpp ├── test-xmltree.cpp ├── unittestframework.h └── unittests.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | project(cpputils) 3 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_find") 4 | 5 | include(boilerplate) 6 | 7 | add_library(cpputils INTERFACE) 8 | target_include_directories(cpputils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) 9 | target_compile_features(cpputils INTERFACE cxx_std_20) 10 | 11 | include(CTest) 12 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING OR BUILD_ALL_TESTS) 13 | add_subdirectory(tests) 14 | endif() 15 | 16 | if (OPT_BENCH) 17 | add_subdirectory(fmtbench) 18 | endif() 19 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## Fri Jun 2 14:56:14 CEST 2023 2 | * added minmax.h 3 | * several minor improvements 4 | * reorganising lib structure, now include path always has 'cpputils' 5 | 6 | ## Tue Nov 8 15:58:20 CET 2022 7 | * improved `packer_base` for `back_inserter`. 8 | * fhandle: fixed bug with return value of read/write of non-byte sized types 9 | * added ctest support 10 | * added cmake finders for doctest and fmt libraries. 11 | 12 | ## Fri May 27 22:45:46 CEST 2022 13 | * updated doctest from 2.3.6 to 2.4.8 14 | * updated catch from 2.11.1 to v2.13.9 15 | * added `OPT_ANALYZE` and `OPT_SANITIZE` for the gcc/clang builtin analyzers 16 | * fixed several stl-debug warnings. more exceptions in asn1parser 17 | * added option to enable STLDEBUGGING, targets for coverage processing 18 | * all my projects now use `USE_xxx` 19 | * base64encoder: added `base64_encode_unpadded` 20 | * solved problem with `operator<<(std::something)` output on some compilers. renamed 'debug' to 'windebug' to avoid name collisions. 21 | * added cmakeargs, same code in all makefiles for building msvc 22 | * added boost as depenency for the `fmt*boost.cpp` benchmarks 23 | * tests for mmem.h 24 | * makefile now only contains code for invoking cmake, the old makefile was renamed to Makefile.linux 25 | * fixed some incompatibilities with gcc and clang. moved `is_stream_insertable` to separate file. 26 | * base64: more tests 27 | * xmlparser and xmlnodetree 28 | * jenkins: added freebsd, stdlib variants 29 | * changed fmt bench to print and string variants 30 | * make/cmake: added COV, PROF, LIBCXX flags. llvm target 31 | * asn1parser: added gettlv(p,p) function 32 | * default to the cmake build 33 | * test-strlib.cpp: added hex2binary tests 34 | * test-argparse.cpp: added check for single arg 35 | * hex2binary now works on arrays as well 36 | * added 'data' method to `array_view` 37 | * change old makefile to c++20 38 | * cmake: added formatter benchmarks. for bench: fetch fmt library 39 | * several bugs fixed 40 | * more tests 41 | 42 | ## Thu Apr 8 09:59:13 CEST 2021 43 | * mmfile: better handling of ro/rw flags 44 | * added cmake build 45 | * more tests 46 | * alphabets for base32 and base64 coding 47 | * fhandle: write now takes PTR+size. added pread, pwrite. 48 | * formatter: added hex,octal output for 128-bit integers 49 | * added `string_to_signed` integer parsers. 50 | * mmfile no longer throws on an empty file. 51 | 52 | bugfixes: 53 | * formatter: reset to decimal in '%s' 54 | * mmem: forgot to copy 'length' field in the move constructor. 55 | * correct hex output of `array_view` 56 | 57 | ## Mon 14 Sep 2020 07:19:25 PM CEST 58 | * added a base32 encoder/decoder 59 | * fixed lineenum, now all tests succeed. 60 | 61 | ## Sat Aug 29 22:20:06 CEST 2020 62 | * string-lineenum.h: enumerate lines in a string 63 | * templateutils: is\_container, is\_stream\_insertable, is\_callable, is\_searchable. 64 | * formatter.h: fixed bug in %b handling 65 | * datapacking: added setbytes with first+last pair. 66 | * datapacking: added 24-bit support. added z-string support. 67 | * datapacking: improved container detection for makeXXpacker. setbytes now takes generic container. 68 | * asn1parser: added named constants for classes and tags 69 | * mappedfile: added mode flags 70 | * added filehandle support to 'fprint' 71 | * fhandle: added 'opening' constructor, which takes a filename and flags. 72 | * crc32 + crc16 code 73 | * more tests 74 | * more documentation. 75 | 76 | 77 | ## Wed Mar 4 12:02:54 CET 2020 78 | * fhandle: added operator bool 79 | * added simpler string conversion functions 80 | * fixed bugs in stringsplit 81 | * more tests 82 | 83 | ## Wed Jan 22 22:46:09 CET 2020 84 | 85 | * updated catch from v2.3.0 to v2.11.1 86 | * updated doctest from v1.2.0 to v2.3.6 87 | * split tests in one file per header, unittests now takes all files from tests directory 88 | * utfcvutils - ifdeffed clang specific code 89 | * mmem: noting problem with mapping block/char devices. 90 | * formatter: improved printing of vector, array. added printing of std::map, std::set. added int128 support. fixed '%+03d' formatting 91 | * fhandle: now keeps a sharedptr to the actual filehandle, so filehandles can be copied. added seek, tell, trunc, write, read methods 92 | * datapacking: added skip. simplified 'is_container' check. 93 | * unittests for fhandle 94 | * mmfile: wraps both a filehandle and mmap 95 | * copied hirestimer from itslib 96 | * added base64 en + decoding 97 | * unitests for base64 98 | * stringconvert: added some explanation 99 | * string-base: added 'beginswith' 100 | * fslibrary: added filetype filter. added dirent in return value 101 | * arrayview: added makerange(p, size) 102 | * argparse: added 'getfullarg' method, which returns the complete current argument 103 | * defaulting to doctest for unittest framework 104 | -------------------------------------------------------------------------------- /Findcpputils.cmake: -------------------------------------------------------------------------------- 1 | if (TARGET cpputils) 2 | return() 3 | endif() 4 | 5 | # NOTE: you can avoid downloading cpputils, by symlinking to a downloaded version here: 6 | find_path(CPPUTILS_DIR NAMES include/cpputils/string-lineenum.h PATHS ${CMAKE_SOURCE_DIR}/symlinks/cpputils) 7 | if(CPPUTILS_DIR STREQUAL "CPPUTILS_DIR-NOTFOUND") 8 | include(FetchContent) 9 | FetchContent_Populate(cpputils 10 | GIT_REPOSITORY https://github.com/nlitsme/cpputils) 11 | set(CPPUTILS_DIR ${cpputils_SOURCE_DIR}) 12 | else() 13 | set(cpputils_BINARY_DIR ${CMAKE_BINARY_DIR}/cpputils-build) 14 | endif() 15 | 16 | add_subdirectory(${CPPUTILS_DIR} ${cpputils_BINARY_DIR}) 17 | 18 | include(FindPackageHandleStandardArgs) 19 | find_package_handle_standard_args(cpputils REQUIRED_VARS CPPUTILS_DIR) 20 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { label "windows" } 3 | stages { 4 | stage("clean") { steps { sh '''git clean -dfx''' } } 5 | stage("windows-build") { 6 | steps { 7 | sh '''#!/bin/bash 8 | set -e 9 | . /c/local/msvcenv.sh 10 | make vc BOOST_ROOT=c:/local/boost_1_74_0 idasdk=c:/local/idasdk75 11 | ''' 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Willem Hengeveld 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # targets: 2 | # all - build all binaries using cmake 3 | # ninja - build all binaries using google-ninja 4 | # vc - build all binaries using cmake + msvc 5 | # clean - remove the build directory 6 | 7 | # Transform Makefile arguments to CMake args 8 | CMAKEARGS_LOCAL+=$(if $(GENERATOR),-G"$(GENERATOR)") 9 | CMAKEARGS_LOCAL+=$(if $(D),-DCMAKE_BUILD_TYPE=Debug,-DCMAKE_BUILD_TYPE=Release) 10 | CMAKEARGS_LOCAL+=$(if $(LOG),-DOPT_LOGGING=ON) 11 | CMAKEARGS_LOCAL+=$(if $(and $(LOG), $(D)),-DOPT_LOGGING_UDP=ON) 12 | CMAKEARGS_LOCAL+=$(if $(COV),-DOPT_COV=1) 13 | CMAKEARGS_LOCAL+=$(if $(SYM),-DOPT_SYMBOLS=1) 14 | CMAKEARGS_LOCAL+=$(if $(PROF),-DOPT_PROF=1) 15 | CMAKEARGS_LOCAL+=$(if $(LIBCXX),-DOPT_LIBCXX=1) 16 | CMAKEARGS_LOCAL+=$(if $(STLDEBUG),-DOPT_STL_DEBUGGING=1) 17 | CMAKEARGS_LOCAL+=$(if $(SANITIZE),-DOPT_ASAN=1) # backward compatibility 18 | CMAKEARGS_LOCAL+=$(if $(ASAN),-DOPT_ASAN=1) 19 | CMAKEARGS_LOCAL+=$(if $(TSAN),-DOPT_TSAN=1) 20 | CMAKEARGS_LOCAL+=$(if $(CLANGTIDY),-DOPT_CLANG_TIDY=1) 21 | CMAKEARGS_LOCAL+=$(if $(ANALYZE),-DOPT_ANALYZE=1) 22 | CMAKEARGS_LOCAL+=$(if $(TEST),-DBUILD_TESTING=1) 23 | CMAKEARGS_LOCAL+=$(if $(TOOLS),-DBUILD_TOOLS=1) 24 | CMAKEARGS_LOCAL+=$(if $(COMPCMD),-DOPT_COMPILE_COMMANDS=1) 25 | CMAKEARGS_LOCAL+=$(if $(BENCH),-DOPT_BENCH=1) 26 | 27 | # add user provided CMAKEARGS 28 | CMAKEARGS_LOCAL+=$(CMAKEARGS) 29 | 30 | CMAKE=cmake 31 | CTEST=ctest 32 | 33 | .DEFAULT_GOAL:=all 34 | .PHONY: all 35 | all: build 36 | 37 | 38 | # uses os default generator (if not GENERATOR is provided) 39 | .PHONY: generate 40 | generate: 41 | $(CMAKE) -B build $(CMAKEARGS_LOCAL) 42 | 43 | JOBSFLAG=$(filter -j%,$(MAKEFLAGS)) 44 | # only build without regenerating build system files (prevents overwrite of previous provided ARGS for generate) 45 | .PHONY: build 46 | build: generate 47 | $(CMAKE) --build build $(JOBSFLAG) $(if $(V),--verbose) --config $(if $(D),Debug,Release) 48 | 49 | # runs all tests 50 | ctest: TEST=1 51 | ctest: all 52 | $(CTEST) --verbose --test-dir build -C $(if $(D),Debug,Release) $(TESTARGS) 53 | 54 | llvm: export CC=clang 55 | llvm: export CXX=clang++ 56 | llvm: all 57 | 58 | 59 | SCANBUILD=$(firstword $(wildcard /usr/bin/scan-build*)) 60 | scan: CMAKE:=$(SCANBUILD) $(if $(CXX),--use-c++=$(CXX)) $(if $(CC),--use-cc=$(CC)) -o $$(pwd)/build/scanbuild $(CMAKE) 61 | scan: ctest 62 | 63 | vc: CMAKE:=cmake.exe 64 | vc: GENERATOR=Visual Studio 16 2019 65 | vc: CMAKEARGS+=$(VC_CMAKEARGS) 66 | vc: all 67 | 68 | nmake: CMAKE:=cmake.exe 69 | nmake: GENERATOR=NMake Makefiles 70 | nmake: CMAKEARGS+=$(VC_CMAKEARGS) 71 | nmake: all 72 | 73 | clean: 74 | $(RM) -r build CMakeFiles CMakeCache.txt CMakeOutput.log 75 | $(RM) $(wildcard *.gcov) 76 | 77 | COVOPTIONS+=-show-instantiation-summary 78 | #COVOPTIONS+=-show-functions 79 | COVOPTIONS+=-show-expansions 80 | COVOPTIONS+=-show-regions 81 | COVOPTIONS+=-show-line-counts-or-regions 82 | 83 | COVERAGEFILES=HiresTimer.h argparse.h arrayview.h asn1parser.h b32-alphabet.h b64-alphabet.h base32encoder.h base64encoder.h crccalc.h 84 | COVERAGEFILES+=datapacking.h fhandle.h formatter.h fslibrary.h hexdumper.h is_stream_insertable.h mmem.h mmfile.h xmlnodetree.h xmlparser.h 85 | COVERAGEFILES+=string-base.h string-join.h string-lineenum.h string-parse.h string-split.h string-strip.h stringconvert.h stringlibrary.h templateutils.h utfconvertor.h utfcvutils.h 86 | 87 | coverage: ctest 88 | llvm-profdata merge -o unittest.profdata default.profraw 89 | llvm-cov show ./build/unittests -instr-profile=unittest.profdata $(COVOPTIONS) $(COVERAGEFILES) 90 | 91 | gcov: ctest 92 | find build/CMakeFiles -name "*.gcda" | xargs rm -f 93 | ./build/unittests 94 | rm -f *.gcov 95 | # -H : human readable output 96 | # -p : preserve paths 97 | # -m : demangle names 98 | find build/CMakeFiles -name "*.gcda" | xargs gcov -p -m -H 99 | # outputs #home#itsme@workprj... files in the current directory 100 | # look for lines with '#####', this means: never executed. 101 | rm -f *"#build#"* *"#tests#"* "#usr#"* 102 | head -999999 *.gcov | filtercov 103 | 104 | -------------------------------------------------------------------------------- /Makefile.bench: -------------------------------------------------------------------------------- 1 | 2 | all: fmt_bench-boost fmt_bench-fmt fmt_bench-fmt2 fmt_bench-formatter fmt_bench-printf 3 | 4 | test: 5 | /usr/bin/time -l ./fmt_bench-boost | uniq 6 | /usr/bin/time -l ./fmt_bench-fmt | uniq 7 | /usr/bin/time -l ./fmt_bench-fmt2 | uniq 8 | /usr/bin/time -l ./fmt_bench-formatter | uniq 9 | /usr/bin/time -l ./fmt_bench-printf | uniq 10 | 11 | CFLAGS+=-std=c++20 12 | CFLAGS+=-I /usr/local/include -I . 13 | LDFLAGS+=-L/usr/local/lib 14 | 15 | %.o: %.cpp 16 | /usr/bin/time -l $(CXX) -c $(CFLAGS) -g $(if $(D),-O0,-O3) -Wall -o $@ $^ 17 | %: fmtbench/%.o 18 | $(CXX) -g $(LDFLAGS) -o $@ $^ $(ldflags_$(basename $@)) 19 | 20 | fmt_bench-boost: fmt_bench-boost.o 21 | fmt_bench-fmt: fmt_bench-fmt.o 22 | ldflags_fmt_bench-fmt=-lfmt 23 | fmt_bench-fmt2: fmt_bench-fmt2.o 24 | ldflags_fmt_bench-fmt2=-lfmt 25 | 26 | fmt_bench-formatter: fmt_bench-formatter.o 27 | fmt_bench-printf: fmt_bench-printf.o 28 | -------------------------------------------------------------------------------- /Makefile.linux: -------------------------------------------------------------------------------- 1 | # make parameters: 2 | # D=1 -- disable optimization, for easy debugging 3 | # COVERAGE=1 -- generate instrumented code 4 | # DOCTEST=1 -- use doctest instead of catch testing framework 5 | # 6 | # make targets: 7 | # all ( default ) - build unittests 8 | # clean - delete objects and executables 9 | # coverage - run coverage test 10 | # 11 | DOCTEST?=1 12 | CXXFLAGS=-g $(if $(D),-O0,-O3) -std=c++17 -Wall 13 | CXXFLAGS+=-I . 14 | CXXFLAGS+=-I /usr/local/include 15 | LDFLAGS=-g 16 | CDEFS?=$(if $(DOCTEST),-DUSE_DOCTEST,-DUSE_CATCH) 17 | all: cmake 18 | 19 | CMAKEARGS+=$(if $(D),-DCMAKE_BUILD_TYPE=Debug,-DCMAKE_BUILD_TYPE=Release) 20 | CMAKEARGS+=$(if $(COV),-DOPT_COV=1) 21 | CMAKEARGS+=$(if $(PROF),-DOPT_PROF=1) 22 | CMAKEARGS+=$(if $(LIBCXX),-DOPT_LIBCXX=1) 23 | 24 | CXXFLAGS+=$(if $(COVERAGE),-fprofile-instr-generate -fcoverage-mapping) 25 | LDFLAGS+=$(if $(COVERAGE),-fprofile-instr-generate -fcoverage-mapping) 26 | 27 | CXXFLAGS+=-DSUPPORT_POINTERS 28 | 29 | unittests: $(notdir $(subst .cpp,.o,$(wildcard tests/*.cpp))) 30 | $(CXX) $(LDFLAGS) -o $@ $^ 31 | 32 | %.o: tests/%.cpp 33 | $(CXX) $(CXXFLAGS) $(CDEFS) -c $^ -o $@ 34 | 35 | cmake: 36 | cmake -B build . $(CMAKEARGS) 37 | $(MAKE) -C build $(if $(V),VERBOSE=1) 38 | 39 | llvm: 40 | CC=clang CXX=clang++ cmake -B build . $(CMAKEARGS) 41 | $(MAKE) -C build $(if $(V),VERBOSE=1) 42 | 43 | 44 | vc: 45 | "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin/cmake.exe" -G"Visual Studio 16 2019" -B build . 46 | "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/MSBuild/Current/Bin/amd64/MSBuild.exe" build/cpputils.sln -t:Rebuild 47 | 48 | 49 | clean: 50 | $(RM) unittests $(wildcard *.o) $(wildcard *.profdata *.profraw) 51 | $(RM) -r build CMakeFiles CMakeCache.txt CMakeOutput.log 52 | 53 | # list of files checked for coverage 54 | COVERAGEFILES=argparse.h arrayview.h asn1parser.h datapacking.h fhandle.h formatter.h fslibrary.h hexdumper.h mmem.h stringconvert.h string-join.h stringlibrary.h utfconvertor.h string-base.h string-parse.h string-split.h string-strip.h base64encoder.h utfcvutils.h 55 | COVERAGEFILES+=HiresTimer.h asn1_presence.h asn1maker.h b32-alphabet.h b64-alphabet.h base32encoder.h certparser.h crccalc.h lowpass_filter.h mmfile.h string-lineenum.h templateutils.h xmlnodetree.h xmlparser.h 56 | 57 | COVOPTIONS+=-show-instantiation-summary 58 | #COVOPTIONS+=-show-functions 59 | COVOPTIONS+=-show-expansions 60 | COVOPTIONS+=-show-regions 61 | COVOPTIONS+=-show-line-counts-or-regions 62 | 63 | 64 | XCODETOOLCHAINS=$(shell xcode-select -p)/Toolchains/XcodeDefault.xctoolchain 65 | coverage: 66 | llvm-profdata merge -o unittest.profdata default.profraw 67 | llvm-cov show ./build/unittests -instr-profile=unittest.profdata $(COVOPTIONS) $(COVERAGEFILES) 68 | 69 | -------------------------------------------------------------------------------- /Makefile.win32: -------------------------------------------------------------------------------- 1 | DOCTEST?=1 2 | CXXFLAGS+=-Zi $(if $(D),/O0,/O2) -Wall -EHsc -DNOMINMAX 3 | CXXFLAGS+=-std:c++17 4 | CXXFLAGS+=-I . 5 | CXXFLAGS+=-utf-8 6 | LDFLAGS=-debug 7 | CDEFS?=$(if $(DOCTEST),-DUSE_DOCTEST,-DUSE_CATCH) 8 | CXX=cl.exe 9 | LD=cl.exe 10 | 11 | all: unittests.exe 12 | 13 | unittests.exe: $(filter-out test-makeasn1.obj test-fhandle.obj,$(notdir $(subst .cpp,.obj,$(wildcard tests/*.cpp)))) 14 | $(LD) $(LDFLAGS) -Fe:$@ $^ 15 | 16 | %.obj: tests/%.cpp 17 | $(CXX) $(CXXFLAGS) $(CDEFS) -c $^ -Fo:$@ 18 | 19 | 20 | clean: 21 | $(RM) unittests.exe $(wildcard *.obj) 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpputils 2 | various c++ utility classes 3 | 4 | * iostreams based hexdumper utility 5 | * string formatter using variadic templates instead of varargs, 6 | making use of iostreams for formatting. 7 | * utf-X string converter. 8 | * c++17 required. 9 | * a argc/argv argument parsing library: `argparse.h` 10 | * `arrayview.h` - provide an interface similar to std::stringview, or boost::span 11 | * `asn1parser: decodes BER encoded data 12 | * datapacking: big/little endian data extraction 13 | * fhandle: c++ wrapper for a POSIX file handle. 14 | * fslibrary: enumerates files recursively. 15 | * mmem: memory mapped files. 16 | * stringconvert: utf-N conversion tools. 17 | * stringlibrary: type independent string functions. 18 | * xmlparser: idea based on the python html.parser module. 19 | 20 | 21 | # usage: 22 | 23 | Several methods of including this project: 24 | * include this project using cmake `add\_subdirectory` 25 | * add the Findcpputils.cmake to your `CMAKE\_MODULE\_PATH`, and then use `find\_package(cpputils)` 26 | * add `-I cpputils/include` to your compiler commandline. 27 | 28 | How to use the include files: 29 | 30 | ``` 31 | #include 32 | 33 | int main(...) 34 | { 35 | print("%s\n", std::string("test")); 36 | } 37 | ``` 38 | 39 | ## hexdumper 40 | 41 | Using iostream to configure hexdump output: 42 | 43 | std::cout << Hex::hexstring << Hex::dumper(data, size) << "\n"; 44 | std::cout << Hex::offset(0x12000) << Hex::right << Hex::dumper(data, size) << "\n"; 45 | 46 | A more detailed description can be found [in this blog post](http://nlitsme.github.io/posts/hexdumper-for-c%2B%2B-iostreams/) 47 | 48 | 49 | ## formatter 50 | 51 | printf like formatting using a combination of variadic templates and iostreams. 52 | 53 | Example: 54 | 55 | std::cout << stringformat("%d %s %d", 1LL, std::string("test"), size_t(3)); 56 | 57 | You can stringify custom types by defining a suitable `operator<<(os, customtype)`. 58 | 59 | Compared to alternatives like fmtlib, boost::format, this implementation creates very small binaries. Performance is below that of fmtlib, but well about boost::format. 60 | 61 | The code is centered around the `StringFormatter` class. Several functions use this 62 | to provide formatting: 63 | * `std::string stringformat(const char *fmt, ...)` 64 | * print to a stl string 65 | * `QString qstringformat(const char *fmt, ...)` 66 | * print to a QT string 67 | * `fprint(FILE *out, const char*fmt, ...)` 68 | * print to a file 69 | * `print(const char*fmt, ...)` 70 | * print to stdout 71 | * `debug(const char*fmt, ...)` 72 | * print to windows debug log 73 | 74 | 75 | 76 | ### benchmarks 77 | 78 | Makefile.bench builds several small programs for comparing my formatter to several other 79 | similar libraries. 80 | 81 | 82 | ## stringconvert 83 | 84 | Convert C or C++ strings to C++ strings, between any of UTF-8, UTF-16, UTF-32, depending on the size of the character type. 85 | 86 | Example: 87 | 88 | auto utf8str = string::convert(L"test"); 89 | auto utf16str = string::convert(L"test"); 90 | 91 | The first line converts a wchar\_t string, which is either utf-16 or utf-32 encoded depending on the compiler, 92 | to a utf-8 string, the second line converts to utf-16. 93 | 94 | ## argparse 95 | 96 | Class for conveniently parsing commandline arguments. 97 | 98 | This example will parse: `cmd -apple -a 1234 first second -` 99 | 100 | ```c++ 101 | for (auto& arg : ArgParser(argc, argv)) 102 | switch (arg.option()) 103 | { 104 | case 'a': if (arg.match("-apple")) { 105 | /*...*/ 106 | } 107 | else { 108 | a_arg = arg.getint(); 109 | } 110 | break; 111 | case '-': if (arg.match("--verbose")) 112 | verbosity = 1; 113 | break; 114 | case 'v': verbosity = arg.count(); 115 | break; 116 | case 0: usestdin = true; 117 | break; 118 | case -1: switch(n++) 119 | { 120 | case 0: first = arg.getstr(); break; 121 | case 1: second = arg.getstr(); break; 122 | } 123 | } 124 | ``` 125 | 126 | ## stringlibrary 127 | 128 | Several utility functions for handling NUL terminated char and wchar strings, 129 | and std::strings: 130 | * stringcopy, stringlength, stringcompare, stringicompare, stringsplitter, (lr)strip 131 | 132 | Parsing integers from strings: 133 | * parseunsigned, parsesigned 134 | 135 | ## datapacking 136 | 137 | Classes for packing and unpacking fixed width numeric data, in either little or big-endian format. 138 | 139 | 140 | ## fhandle 141 | 142 | Exeption safe wrapper for posix filehandles. 143 | 144 | ## mmem 145 | 146 | class for using mem-mapped files. 147 | 148 | 149 | ## fslibrary 150 | 151 | A Recursive file iterator, which can be used from a ranged-for-loop. 152 | 153 | ## asn1parser 154 | 155 | Provides several methods of accessing items in an asn.1 BER encoded object. 156 | * `asn1tlv` is an object which decodes the Type + Length fields, and 157 | Provides the 'range' where the Data is located. 158 | * `enumtlvs` iterates over all BER objects found in the given range. 159 | * `traverse` extracts a specific part from a BER encoded object. 160 | 161 | 162 | ## unittests 163 | 164 | A Makefile is provided for building the unittests. These can be build 'normally', 165 | and also with options for code coverage testing. 166 | 167 | several targets exist: 168 | * `all`, the old method of building the tests 169 | * `cmake`, using cmake to resolve dependencies 170 | * `vc`, for building this on windows platforms with cmake + visualc++ 171 | 172 | 173 | ### todo 174 | 175 | * add support for hexdumping data from streams. 176 | * string alignment / width does not work correctly for unicode characters > 0x80. 177 | * `"% d"` : space-for-positive is not supported. 178 | * `"%\*d"` : width from argument list is not supported. 179 | * `"%+08d"` produces the wrong result: the sign will be after the padding, instead of in front. 180 | * `"%.8s"` string truncation does not work. 181 | * add support for different alphabets in base32/base64. 182 | * add linereader which takes either a filehandle, or a range 183 | * add read() which allocates it's return buffer. 184 | 185 | (C) 2016-2023 Willem Hengeveld 186 | -------------------------------------------------------------------------------- /cmake_find/Finddoctest.cmake: -------------------------------------------------------------------------------- 1 | list(APPEND CMAKE_MODULE_PATH "/usr/lib/cmake/doctest/") 2 | if (TARGET doctest) 3 | return() 4 | endif() 5 | if (TARGET doctest::doctest) 6 | return() 7 | endif() 8 | file(GLOB DOCTEST_DOCTEST_DIRS /usr/include /usr/local/include /usr/local/opt/doctest/include) 9 | find_path(DOCTEST_DOCTEST_DIR NAMES doctest/doctest.h PATHS ${DOCTEST_DOCTEST_DIRS}) 10 | if (DOCTEST_DOCTEST_DIR STREQUAL "DOCTEST_DOCTEST_DIR-NOTFOUND") 11 | include(FetchContent) 12 | FetchContent_Populate( 13 | doctest 14 | GIT_REPOSITORY https://github.com/doctest/doctest.git 15 | ) 16 | list(APPEND CMAKE_MODULE_PATH "${doctest_SOURCE_DIR}/scripts/cmake/") 17 | set(DOCTEST_DOCTEST_DIR ${doctest_SOURCE_DIR}) 18 | else() 19 | set(doctest_BINARY_DIR ${CMAKE_BINARY_DIR}/doctest-build) 20 | endif() 21 | 22 | add_library(doctest INTERFACE) 23 | target_include_directories(doctest INTERFACE ${DOCTEST_DOCTEST_DIR}) 24 | add_library(doctest::doctest ALIAS doctest) 25 | -------------------------------------------------------------------------------- /cmake_find/Findfmt.cmake: -------------------------------------------------------------------------------- 1 | file(GLOB FMTLIB_DIRS ../../*/fmt /usr/include /usr/local/include /usr/local/opt/doctest/include) 2 | find_path(FMTLIB_PATH NAMES include/fmt/printf.h PATHS ${FMTLIB_DIRS}) 3 | if (NOT FMTLIB_PATH STREQUAL "FMTLIB_PATH-NOTFOUND") 4 | add_library(fmtlib INTERFACE) 5 | target_include_directories(fmtlib INTERFACE ${FMTLIB_PATH}/include) 6 | target_compile_definitions(fmtlib INTERFACE FMT_HEADER_ONLY) 7 | 8 | add_library(fmt::fmt ALIAS fmtlib) 9 | else() 10 | include(FetchContent) 11 | FetchContent_Declare(fmt 12 | GIT_REPOSITORY https://github.com/fmtlib/fmt.git 13 | GIT_TAG master 14 | ) 15 | FetchContent_MakeAvailable(fmt) 16 | endif() 17 | 18 | -------------------------------------------------------------------------------- /cmake_find/boilerplate.cmake: -------------------------------------------------------------------------------- 1 | option(OPT_STL_DEBUGGING "Build with STL debugging" OFF) 2 | option(OPT_PROF "Build for profiling" OFF) 3 | option(OPT_COV "Build for code coverage" OFF) 4 | option(OPT_LIBCXX "Build with libcxx" OFF) 5 | option(OPT_MODULES "use c++20 modules" OFF) 6 | option(OPT_ANALYZE "add -fanalyzer" OFF) 7 | option(OPT_SYMBOLS "With symbols" OFF) 8 | option(OPT_SANITIZE "With -fsanitize" OFF) 9 | option(OPT_TSAN "With thread sanitizer" OFF) 10 | option(OPT_ASAN "With address sanitizer" OFF) 11 | option(OPT_CLANG_TIDY "With clang-tidy checks" OFF) 12 | option(OPT_COMPILE_COMMANDS "Generate compile_commands.json" OFF) 13 | option(OPT_INSTALL_HEADERS "Export header files for INSTALL target" OFF) 14 | option(OPT_DISABLE_CMAKE_SANITY_CHECK "Disable CMake call sanity checks (ex: OpenWrt)" OFF) 15 | option(OPT_DISABLE_DEVEL_INSTALL "Disable all development install targets (ex: Win NSIS installer)" OFF) 16 | 17 | if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") 18 | set(LINUX TRUE) 19 | elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 20 | set(DARWIN TRUE) 21 | if (${CMAKE_OSX_SYSROOT} MATCHES "/iPhoneOS.platform") 22 | set(IPHONE TRUE) 23 | elseif (${CMAKE_OSX_SYSROOT} MATCHES "/iPhoneSimulator.platform") 24 | set(IPHONESIM TRUE) 25 | elseif (${CMAKE_OSX_SYSROOT} MATCHES "/MacOSX.platform") 26 | set(MACOS TRUE) 27 | else() 28 | message(FATAL_ERROR "Unsupported apple platform") 29 | endif() 30 | elseif (${CMAKE_SYSTEM_NAME} MATCHES "iOS") 31 | set(DARWIN TRUE) 32 | if (${CMAKE_OSX_SYSROOT} MATCHES "/iPhoneOS.platform") 33 | set(IPHONE TRUE) 34 | elseif (${CMAKE_OSX_SYSROOT} MATCHES "/iPhoneSimulator.platform") 35 | set(IPHONESIM TRUE) 36 | endif() 37 | elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") 38 | set(FREEBSD TRUE) 39 | endif() 40 | 41 | if (NOT OPT_DISABLE_CMAKE_SANITY_CHECK) 42 | # checking if we are called in the correct way: 43 | # with a -B argument. and without a cache file in the source directory. 44 | if (CMAKE_CACHEFILE_DIR STREQUAL "${CMAKE_SOURCE_DIR}") 45 | message(FATAL_ERROR "\nUnexpected CMakeCache.txt file in the source directory. Please remove it.") 46 | return() 47 | endif() 48 | 49 | if (EXISTS ${CMAKE_BINARY_DIR}/CMakeLists.txt) 50 | message(FATAL_ERROR "\nRun cmake with an explicit -B buildpath") 51 | return() 52 | endif() 53 | endif() 54 | 55 | if (OPT_ANALYZE) 56 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 57 | # see https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Static-Analyzer-Options.html#index-analyzer 58 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fanalyzer") 59 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 60 | # https://clang.llvm.org/docs/UsersManual.html 61 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --analyze") 62 | endif() 63 | endif() 64 | 65 | if (OPT_ASAN AND OPT_TSAN) 66 | message(FATAL_ERROR "Only one sanitizer can be active at a time") 67 | elseif (OPT_ASAN) 68 | # https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Instrumentation-Options.html#index-fsanitize_003daddress 69 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") 70 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") 71 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") 72 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=dataflow") 73 | elseif(OPT_TSAN) 74 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") 75 | endif() 76 | 77 | if (OPT_CLANG_TIDY) 78 | # clang-tidy supports a range of different checks. For a list of all available 79 | # checks, check the clang-tidy website: 80 | # https://clang.llvm.org/extra/clang-tidy/checks/list.html 81 | # To enable only certain checks, we disable all of them first and then select 82 | # - clang-analyzer-* => Clang Static Analyzer 83 | # - bugprone-* => bug-prone code constructs (except bugprone-easily-swappable-parameters, bugprone-suspicious-include) 84 | # - cert-* => CERT Secure Coding Guidelines 85 | # - concurrency-* => General concurrency checks 86 | # - performance-* => General performance checks 87 | # - portability-* => General portability checks 88 | set(CLANG_TIDY_CHECKS "clang-analyzer-*,bugprone-*,-bugprone-easily-swappable-parameters,-bugprone-suspicious-include,cert-*,concurrency-*,performance-*,portability=*") 89 | set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--extra-arg-before=-std=c++${CMAKE_CXX_STANDARD};-checks=-*,${CLANG_TIDY_CHECKS}") 90 | endif() 91 | 92 | if (OPT_LIBCXX) 93 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 94 | endif() 95 | 96 | if (OPT_STL_DEBUGGING) 97 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_DEBUG") 98 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_DEBUG_LEVEL=1") 99 | endif() 100 | 101 | if (OPT_PROF) 102 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg ") 103 | endif() 104 | 105 | if (OPT_SYMBOLS) 106 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g ") 107 | endif() 108 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 109 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++11-narrowing") 110 | endif() 111 | 112 | if (OPT_COV) 113 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 114 | message(STATUS "gcc code coverage") 115 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftest-coverage -fprofile-arcs ") 116 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -ftest-coverage -fprofile-arcs ") 117 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 118 | message(STATUS "llvm code coverage") 119 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping -fdebug-info-for-profiling") 120 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mllvm -inline-threshold=100000") 121 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-instr-generate -fcoverage-mapping") 122 | else() 123 | message(STATUS "don't know how to add code coverage for ${CMAKE_CXX_COMPILER_ID }") 124 | endif() 125 | endif() 126 | if(OPT_STATIC) 127 | set(LIBSTYLE STATIC) 128 | set(CMAKE_POSITION_INDEPENDENT_CODE True) 129 | else() 130 | set(LIBSTYLE SHARED) 131 | endif() 132 | 133 | if (OPT_COMPILE_COMMANDS) 134 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 135 | endif() 136 | 137 | if (OPT_DISABLE_DEVEL_INSTALL) 138 | set(MAY_EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL) 139 | endif() 140 | 141 | 142 | # Project wide warning/error settings 143 | if(MSVC) 144 | # /W0 suppresses all warnings 145 | # /W1 displays level 1 (severe) warnings (default in command line) 146 | # /W2 displays level 1 and level 2 (significant) warnings. 147 | # /W3 displays level 1, level 2, and level 3 (production quality) warnings (default in IDE) 148 | # /W4 displays level 1, level 2, and level 3 warnings, and all level 4 (informational) warnings that aren't off by default 149 | add_compile_options(/W1) 150 | else() 151 | # Exclude the following ones for now: 152 | # -Wunused-parameter: we have delegate classes with stub methods (with unused parameters) 153 | # -Wempty-body: occurs in release builds as there are if-cases which only contain a logmsg expression 154 | # -Wunused-variable, -Wunused-value: occurs in release builds for parameters of a logmsg expression 155 | add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-empty-body -Wno-unused-value -Wno-unused-variable) 156 | endif() 157 | 158 | if(MSVC) 159 | # /MP = multithreaded build 160 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") 161 | # /utf-8 = utf8 source and execution 162 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") 163 | # NOBITMAP - avoid error in mmreg.h 164 | # NOMINMAX - remove 'max()' macro from global namespace 165 | # NOGDI - ... 166 | add_definitions(-DNOMINMAX -DNOGDI -DNOBITMAP -DWIN32_LEAN_AND_MEAN) 167 | add_definitions(-DWIN32) 168 | add_definitions(-D__STDC_WANT_SECURE_LIB__=1) 169 | 170 | # Executables need to resolve path to dlls (RPATH is not available on Windows). This could be done 171 | # either by using PATH env. variable or keeping dlls alongside with executables 172 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin) 173 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin) 174 | endif() 175 | if (OPT_MODULES) 176 | if (CMAKE_COMPILER_IS_GNUCXX) 177 | set(CMAKE_CXX_FLAGS -fmodules-ts) 178 | else() 179 | set(CMAKE_CXX_FLAGS -fmodules -fbuiltin-module-map) 180 | endif() 181 | endif() 182 | 183 | -------------------------------------------------------------------------------- /fmtbench/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(fmt REQUIRED) 2 | find_package(Boost REQUIRED COMPONENTS system) 3 | add_executable(fmt_string-boost fmt_string-boost.cpp) 4 | target_link_libraries(fmt_string-boost cpputils Boost::headers) 5 | 6 | add_executable(fmt_string-fmt fmt_string-fmt.cpp) 7 | target_link_libraries(fmt_string-fmt cpputils fmt::fmt) 8 | add_executable(fmt_string-fmt2 fmt_string-fmt2.cpp) 9 | target_link_libraries(fmt_string-fmt2 cpputils fmt::fmt) 10 | add_executable(fmt_string-formatter fmt_string-formatter.cpp) 11 | target_link_libraries(fmt_string-formatter cpputils) 12 | add_executable(fmt_string-printf fmt_string-printf.cpp) 13 | #add_executable(fmt_string-std fmt_string-std.cpp) 14 | 15 | 16 | add_executable(fmt_print-boost fmt_print-boost.cpp) 17 | target_link_libraries(fmt_print-boost cpputils Boost::headers) 18 | 19 | add_executable(fmt_print-fmt fmt_print-fmt.cpp) 20 | target_link_libraries(fmt_print-fmt cpputils fmt::fmt) 21 | add_executable(fmt_print-fmt2 fmt_print-fmt2.cpp) 22 | target_link_libraries(fmt_print-fmt2 cpputils fmt::fmt) 23 | add_executable(fmt_print-formatter fmt_print-formatter.cpp) 24 | target_link_libraries(fmt_print-formatter cpputils) 25 | add_executable(fmt_print-printf fmt_print-printf.cpp) 26 | #add_executable(fmt_print-std fmt_print-std.cpp) 27 | 28 | -------------------------------------------------------------------------------- /fmtbench/fmt_print-boost.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | const long maxIter = 2000000L; 7 | for(long i = 0; i < maxIter; ++i) 8 | std::cout << boost::format("%0.10f:%04d:%+g:%s:%p:%c:%%\n") % 9 | 1.234 % 42 % 3.13 % "str" % (void*)1000 % (char)'X'; 10 | return 0; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /fmtbench/fmt_print-fmt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) 4 | { 5 | const long maxIter = 2000000L; 6 | for(long i = 0; i < maxIter; ++i) 7 | fmt::printf("%0.10f:%04d:%+g:%s:%p:%c:%%\n", 8 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 9 | return 0; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /fmtbench/fmt_print-fmt2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) 4 | { 5 | const long maxIter = 2000000L; 6 | for(long i = 0; i < maxIter; ++i) 7 | fmt::print("{:.10f}:{:04}:{:+g}:{}:{}:{}:%\n", 8 | 1.234, 42, 3.13, "str", (void*)1000, (char)'X'); 9 | return 0; 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /fmtbench/fmt_print-formatter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) 4 | { 5 | const long maxIter = 2000000L; 6 | for(long i = 0; i < maxIter; ++i) 7 | print("%0.10f:%04d:%+g:%s:%p:%c:%%\n", 8 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /fmtbench/fmt_print-printf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) 4 | { 5 | const long maxIter = 2000000L; 6 | for(long i = 0; i < maxIter; ++i) 7 | printf("%0.10f:%04d:%+g:%s:%p:%c:%%\n", 8 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 9 | return 0; 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /fmtbench/fmt_print-std.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | const long maxIter = 2000000L; 7 | for(long i = 0; i < maxIter; ++i) 8 | std::cout << std::format("{:0.10f}:{:04d}:{:+g}:{}:{:p}:{:c}:%\n", 9 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 10 | return 0; 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /fmtbench/fmt_string-boost.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | const long maxIter = 2000000L; 7 | int total = 0; 8 | for(long i = 0; i < maxIter; ++i) { 9 | auto f = boost::format("%0.10f:%04d:%+g:%s:%p:%c:%%\n") % 10 | 1.234 % 42 % 3.13 % "str" % (void*)1000 % (char)'X'; 11 | total += f.str().size(); 12 | } 13 | std::cout << "total = " << total << "\n"; 14 | return 0; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /fmtbench/fmt_string-fmt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | const long maxIter = 2000000L; 7 | int total = 0; 8 | for(long i = 0; i < maxIter; ++i) { 9 | std::string res = fmt::format("%0.10f:%04d:%+g:%s:%p:%c:%%\n", 10 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 11 | total += res.size(); 12 | } 13 | std::cout << "total = " << total << "\n"; 14 | return 0; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /fmtbench/fmt_string-fmt2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | const long maxIter = 2000000L; 7 | int total = 0; 8 | for(long i = 0; i < maxIter; ++i) { 9 | std::string res = fmt::format("{:.10f}:{:04}:{:+g}:{}:{}:{}:%\n", 10 | 1.234, 42, 3.13, "str", (void*)1000, (char)'X'); 11 | total += res.size(); 12 | } 13 | std::cout << "total = " << total << "\n"; 14 | return 0; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /fmtbench/fmt_string-formatter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | const long maxIter = 2000000L; 7 | int total = 0; 8 | for(long i = 0; i < maxIter; ++i) { 9 | std::string res = stringformat("%0.10f:%04d:%+g:%s:%p:%c:%%\n", 10 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 11 | total += res.size(); 12 | } 13 | std::cout << "total = " << total << "\n"; 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /fmtbench/fmt_string-printf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) 4 | { 5 | const long maxIter = 2000000L; 6 | char buf[256]; 7 | int total = 0; 8 | for(long i = 0; i < maxIter; ++i) { 9 | total += sprintf(buf, "%0.10f:%04d:%+g:%s:%p:%c:%%\n", 10 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 11 | } 12 | printf("total: %d\n", total); 13 | return 0; 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /fmtbench/fmt_string-std.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | const long maxIter = 2000000L; 7 | int total = 0; 8 | for(long i = 0; i < maxIter; ++i) { 9 | std::string res = std::format("{:0.10f}:{:04d}:{:+g}:{}:{:p}:{:c}:%\n", 10 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 11 | total += res.size(); 12 | } 13 | std::cout << "total = " << total << "\n"; 14 | return 0; 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /include/cpputils/HiresTimer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // HiresTimer provides a usec resolution timer on both POSIX and win32 platforms 6 | #ifdef _WIN32 7 | #include 8 | class HiresTimer { 9 | DWORD _tick; 10 | public: 11 | HiresTimer() 12 | { 13 | reset(); 14 | } 15 | int64_t lap() 16 | { 17 | DWORD now= GetTickCount(); 18 | int64_t tdiff= now-_tick; 19 | _tick= now; 20 | return tdiff*1000; 21 | } 22 | void reset() 23 | { 24 | _tick= GetTickCount(); 25 | } 26 | 27 | // in usec 28 | int64_t elapsed() const 29 | { 30 | return (int64_t)1000*msecelapsed(); 31 | } 32 | 33 | // in msec 34 | int32_t msecelapsed() const 35 | { 36 | DWORD now= GetTickCount(); 37 | int64_t tdiff= now-_tick; 38 | // note: this fails for delays more than a few weeks 39 | return (int32_t)tdiff; 40 | } 41 | static void usleep(int64_t t) 42 | { 43 | // note: this fails for delays more than a few weeks 44 | Sleep((DWORD)(t/1000)); 45 | } 46 | uint64_t getstamp() const 47 | { 48 | return uint64_t(1000)*_tick; 49 | } 50 | static uint64_t stamp() 51 | { 52 | return HiresTimer().getstamp(); 53 | } 54 | static uint32_t msecstamp() 55 | { 56 | return (uint32_t)(HiresTimer().getstamp()/1000); 57 | } 58 | 59 | static uint32_t unixstamp() 60 | { 61 | const uint64_t EPOCH_BIAS= 116444736000000000i64; 62 | const uint64_t HUNDREDNS = 10000000; 63 | SYSTEMTIME sTime; 64 | GetSystemTime(&sTime); 65 | 66 | FILETIME fTime; 67 | ULARGE_INTEGER ll; 68 | //time_t tt=0; 69 | 70 | if ( !SystemTimeToFileTime( &sTime, &fTime ) ) 71 | return 0; 72 | 73 | ll.LowPart= fTime.dwLowDateTime; 74 | ll.HighPart= fTime.dwHighDateTime; 75 | return (uint32_t)((ll.QuadPart - EPOCH_BIAS)/HUNDREDNS); 76 | 77 | } 78 | }; 79 | #else 80 | #include 81 | #include 82 | #include // for usleep 83 | class HiresTimer { 84 | struct timeval _tv; 85 | public: 86 | HiresTimer() 87 | { 88 | reset(); 89 | } 90 | int64_t lap() 91 | { 92 | struct timeval now; 93 | gettimeofday(&now, 0); 94 | int64_t tdiff= ((int64_t)now.tv_sec-_tv.tv_sec)*1000000LL+((int64_t)now.tv_usec-_tv.tv_usec); 95 | _tv= now; 96 | return tdiff; 97 | } 98 | void reset() 99 | { 100 | gettimeofday(&_tv, 0); 101 | } 102 | int64_t elapsed() const 103 | { 104 | struct timeval now; 105 | gettimeofday(&now, 0); 106 | int64_t tdiff= ((int64_t)now.tv_sec-_tv.tv_sec)*1000000LL+((int64_t)now.tv_usec-_tv.tv_usec); 107 | return tdiff; 108 | } 109 | int32_t msecelapsed() const 110 | { 111 | return elapsed()/1000; 112 | } 113 | static void usleep(int64_t t) 114 | { 115 | ::usleep(t); 116 | } 117 | uint64_t getstamp() const 118 | { 119 | return _tv.tv_sec*1000000LL+_tv.tv_usec; 120 | } 121 | static uint64_t stamp() 122 | { 123 | return HiresTimer().getstamp(); 124 | } 125 | static uint32_t msecstamp() 126 | { 127 | return HiresTimer().getstamp()/1000; 128 | } 129 | static uint32_t unixstamp() 130 | { 131 | return time(0); 132 | } 133 | }; 134 | #endif 135 | 136 | -------------------------------------------------------------------------------- /include/cpputils/argparse.h: -------------------------------------------------------------------------------- 1 | /* (C) 2003 XDA Developers itsme@xs4all.nl 2 | * 3 | * $Header$ 4 | */ 5 | #ifndef __ARGS_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | /* 15 | 16 | // cmd -apple -a 1234 first second - 17 | 18 | for (auto& arg : ArgParser(argc, argv)) 19 | switch (arg.option()) 20 | { 21 | case 'a': 22 | if (arg.match("-apple")) { 23 | 24 | } 25 | else { 26 | a_arg = arg.getint(); 27 | } 28 | break; 29 | case '-': 30 | if (arg.match("--long")) 31 | a_long = true; 32 | break; 33 | case 0: 34 | usestdin = true; 35 | break; 36 | case -1: 37 | switch(n++) 38 | { 39 | case 0: first = arg.getstr(); break; 40 | case 1: second = arg.getstr(); break; 41 | } 42 | } 43 | 44 | */ 45 | 46 | // TODO - support floating point numbers 47 | // TODO - support options with multiple arguments. 48 | 49 | class ArgParser { 50 | struct ArgIterator { 51 | const char **argv; // argument list 52 | int argc; // nr of argument strings 53 | int i; // current argument 54 | const char *p; // position in current argument 55 | const char *pend; // end of current argument 56 | 57 | bool longmatch; 58 | bool isoption; 59 | 60 | void setcurrent() 61 | { 62 | longmatch = false; 63 | isoption = false; 64 | if (i==argc) { 65 | p = pend = nullptr; 66 | return; 67 | } 68 | p = argv[i]; 69 | pend = p + stringlength(p); 70 | } 71 | 72 | ArgIterator(const char **argv, int i, int argc) 73 | : argv(argv), argc(argc), i(i) 74 | { 75 | setcurrent(); 76 | } 77 | 78 | friend bool operator!=(const ArgIterator& lhs, const ArgIterator& rhs) 79 | { 80 | return !(lhs.i==rhs.i && lhs.p == rhs.p); 81 | } 82 | 83 | ArgIterator& operator*() { return *this; } 84 | ArgIterator& operator++() 85 | { 86 | if (i==argc) 87 | // calller should throw when nescesary. 88 | return *this; 89 | 90 | i++; 91 | setcurrent(); 92 | 93 | return *this; 94 | } 95 | 96 | /* 97 | * get single option character 98 | * returns -1 for non option arguments 99 | * returns 0 for single dash '-' 100 | */ 101 | int option() 102 | { 103 | if (i==argc) 104 | throw std::range_error("arg out of range"); 105 | if (*p=='-') { 106 | // a single '-' by itself is not considered an option, 107 | // so 'getstr()' will return "-" 108 | isoption = p[1]!=0; 109 | return p[1]; 110 | } 111 | isoption = false; 112 | return -1; 113 | } 114 | 115 | /* 116 | * matches long option names 117 | * the name should include the leading option dash(es) 118 | * 119 | * like: match("--verbose") 120 | * 121 | * the caller must make sure the matching order does not 122 | * lead to ambiguities. 123 | * 124 | * When the name matches, the current ptr will point after the match. 125 | */ 126 | bool match(const char*name) 127 | { 128 | if (i==argc) 129 | throw std::range_error("arg out of range"); 130 | auto namelen = stringlength(name); 131 | if (namelen > stringlength(p)) 132 | return false; 133 | if (std::equal(name, name+namelen, p)) { 134 | p += namelen; 135 | longmatch = true; 136 | return true; 137 | } 138 | return false; 139 | } 140 | 141 | /* 142 | * checks for '--', usually used to end option processing 143 | */ 144 | bool optionterminator() 145 | { 146 | if (i==argc) 147 | throw std::range_error("arg out of range"); 148 | 149 | if (p+2!=pend) 150 | return false; 151 | 152 | return p[0]=='-' && p[1]=='-'; 153 | } 154 | 155 | /* 156 | * gets the next argument string 157 | * 158 | * long arguments can have a '=' after the option name 159 | * 160 | * --number=1234 161 | * 162 | */ 163 | const char *getstr() 164 | { 165 | if (i==argc) 166 | throw std::range_error("arg out of range"); 167 | if (isoption && !longmatch) { 168 | // skips '-' + optionchar 169 | p += 2; 170 | if (*p==0) { 171 | // -opt argument 172 | operator++(); 173 | } 174 | 175 | // -opt 176 | } 177 | else if (longmatch) { 178 | // long match need not match the full word. 179 | p = std::find(p, pend, '='); 180 | if (p!=pend) { 181 | // --longopt '=' argument 182 | return p+1; 183 | } 184 | 185 | // --longopt argument 186 | operator++(); 187 | } 188 | 189 | // check again if we are out of options 190 | if (i==argc) 191 | throw std::range_error("arg out of range"); 192 | return p; 193 | } 194 | const char*getfullarg() 195 | { 196 | return argv[i]; 197 | } 198 | /* 199 | * return count for a repeated option: 200 | * 201 | * -vvv -> returns 3 202 | * 203 | */ 204 | int count() 205 | { 206 | if (i==argc) 207 | throw std::range_error("arg out of range"); 208 | if (longmatch) 209 | throw std::logic_error("can't count a --long option"); 210 | char opt = p[1]; 211 | const char *q = p+1; 212 | 213 | while (q!=pend && *q==opt) 214 | ++q; 215 | 216 | if (q!=pend) 217 | throw std::logic_error("counted options must all be equal"); 218 | 219 | return pend - p - 1; 220 | } 221 | 222 | /* 223 | * gets the integer after the current option. 224 | * either in the same string, or in the next full argument. 225 | */ 226 | int64_t getint() 227 | { 228 | getstr(); // ignoring result, we use 'p' directly. 229 | 230 | auto res = parsesigned(p, pend, 0); 231 | if (res.second != pend) 232 | throw std::logic_error("characters trailing number"); 233 | return res.first; 234 | } 235 | uint64_t getuint() 236 | { 237 | getstr(); // ignoring result, we use 'p' directly. 238 | 239 | auto res = parseunsigned(p, pend, 0); 240 | if (res.second != pend) 241 | throw std::logic_error("characters trailing number"); 242 | return res.first; 243 | } 244 | double getdouble() 245 | { 246 | getstr(); // ignoring result, we use 'p' directly. 247 | 248 | // note: can't use std::from_chars, since that is not implemented in libc++ yet. 249 | 250 | char *numend; 251 | double res = strtod(p, &numend); 252 | if (numend != pend) 253 | throw std::logic_error("characters trailing number"); 254 | return res; 255 | } 256 | 257 | }; 258 | 259 | int argc; 260 | const char **argv; 261 | public: 262 | ArgParser(int argc, const char**argv) 263 | : argc(argc), argv(argv) 264 | { 265 | } 266 | 267 | 268 | ArgParser(int argc, char**argv) 269 | : argc(argc), argv(const_cast(argv)) 270 | { 271 | } 272 | 273 | auto begin() { return ArgIterator(argv, 1, argc); } 274 | auto end() { return ArgIterator(argv, argc, argc); } 275 | }; 276 | #define __ARGS_H__ 277 | #endif 278 | -------------------------------------------------------------------------------- /include/cpputils/arrayview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | /* 5 | * like string_view or span, but takes an iterator-pair. 6 | * 7 | * Note that array_view can also take streampos as 'P', and therefore I did not 8 | * add operator[] and value_type. 9 | 10 | * this is probably obsolete now that is part of all standard libs. 11 | */ 12 | template 13 | class array_view { 14 | P first, last; 15 | public: 16 | array_view() 17 | { 18 | } 19 | 20 | array_view(P first, P last) 21 | : first(first), last(last) 22 | { 23 | } 24 | array_view(P first, typename std::iterator_traits

::difference_type size) 25 | : first(first), last(first) 26 | { 27 | std::advance(last, size); 28 | } 29 | 30 | P begin() { return first; } 31 | P end() { return last; } 32 | 33 | P data() { return &*first; } 34 | 35 | auto& front() const { return *first; } 36 | auto& back() const { return *(last-1); } 37 | 38 | const P begin() const { return first; } 39 | const P end() const { return last; } 40 | 41 | size_t size() const { return last-first; } 42 | 43 | bool operator==( const array_view

& rhs ) const { 44 | return first==rhs.first && last==rhs.last; 45 | } 46 | }; 47 | 48 | 49 | // note: for 'range' i can use either 'std::span' ( c++20 ), or std::string_view ( c++17 ) 50 | // or std::array_view (c++20) 51 | template 52 | using range = array_view

; 53 | 54 | template 55 | auto makerange(P first, P last) 56 | { 57 | return range

(first, last); 58 | } 59 | template 60 | auto makerange(P first, size_t size) 61 | { 62 | return range

(first, size); 63 | } 64 | 65 | template 66 | auto makerange(V & v) 67 | { 68 | return range(v.begin(), v.end()); 69 | } 70 | 71 | template 72 | auto makerange(const V & v) 73 | { 74 | return range(v.begin(), v.end()); 75 | } 76 | 77 | template 78 | bool equalrange(const R& r, const S& s) 79 | { 80 | return r.size()==s.size() && std::equal(std::begin(r), std::end(r), std::begin(s)); 81 | } 82 | 83 | /* 84 | * helper for printing hexdump of an array_view. 85 | */ 86 | template 87 | std::ostream& operator<<(std::ostream&os, const array_view

& buf) 88 | { 89 | auto fillchar = os.fill(); 90 | std::ios state(NULL); state.copyfmt(os); 91 | 92 | bool first= true; 93 | for (const auto& b : buf) { 94 | os.copyfmt(state); 95 | if (!first && fillchar) os << fillchar; 96 | os << std::right; 97 | os << std::setfill('0'); 98 | os << std::setw(2); 99 | os << std::hex << unsigned(b); 100 | first= false; 101 | } 102 | return os; 103 | } 104 | -------------------------------------------------------------------------------- /include/cpputils/b32-alphabet.h: -------------------------------------------------------------------------------- 1 | struct StandardBase32 { 2 | static char code2char(int code) 3 | { 4 | static const char*chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; 5 | return chars[code]; 6 | } 7 | static int char2code(char ch) 8 | { 9 | // -1 : invalid char 10 | // -2 : padding 11 | // -3 : whitespace 12 | 13 | static const int codes[] = { 14 | // 1 2 3 4 5 6 7 8 9 a b c d e f 15 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-3,-3,-1,-3,-3,-1,-1, // 0 16 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 1 17 | -3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 2 18 | -1,-1,26,27,28,29,30,31,-1,-1,-1,-1,-1,-2,-1,-1, // 3 19 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, // 4 20 | 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, // 5 21 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, // 4 22 | 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, // 5 23 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 8 24 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 9 25 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // a 26 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // b 27 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // c 28 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // d 29 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // e 30 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // f 31 | }; 32 | return codes[ch]; 33 | } 34 | 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /include/cpputils/b64-alphabet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct StandardBase64 { 5 | static char code2char(int code) 6 | { 7 | static const char*chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 8 | return chars[code]; 9 | } 10 | static int char2code(char ch) 11 | { 12 | // -1 : invalid char 13 | // -2 : padding 14 | // -3 : whitespace 15 | 16 | static const int codes[] = { 17 | // 1 2 3 4 5 6 7 8 9 a b c d e f 18 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-3,-3,-1,-3,-3,-1,-1, // 0 19 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 1 20 | -3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, // 2 21 | 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1, // 3 22 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, // 4 23 | 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, // 5 24 | -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, // 6 25 | 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, // 7 26 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 8 27 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 9 28 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // a 29 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // b 30 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // c 31 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // d 32 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // e 33 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // f 34 | }; 35 | return codes[(uint8_t)ch]; 36 | } 37 | }; 38 | 39 | struct UrlSafeBase64 { 40 | static char code2char(int code) 41 | { 42 | static const char*chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="; 43 | return chars[code]; 44 | } 45 | static char char2code(char ch) 46 | { 47 | static const int codes[] = { 48 | // 1 2 3 4 5 6 7 8 9 a b c d e f 49 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-3,-3,-1,-3,-3,-1,-1, // 0 50 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 1 51 | -3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1, // 2 52 | 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1, // 3 53 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, // 4 54 | 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,63, // 5 55 | -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, // 6 56 | 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, // 7 57 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 8 58 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 9 59 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // a 60 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // b 61 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // c 62 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // d 63 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // e 64 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // f 65 | }; 66 | return codes[(uint8_t)ch]; 67 | } 68 | }; 69 | 70 | -------------------------------------------------------------------------------- /include/cpputils/base32encoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // encode a 5 byte chunk into 8 characters 10 | template 11 | P base32_encode_chunk(P chunk, P last, S enc) 12 | { 13 | static const char*b32ToChar= "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; 14 | 15 | P p = chunk; 16 | auto b = *p++; 17 | 18 | int c[8] = { (b&0xF8)>>3, (b&0x07)<<2, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }; 19 | 20 | if (p < last) { 21 | b = *p++; 22 | c[1] |= (b&0xc0)>>6; 23 | c[2] = (b&0x3e)>>1; 24 | c[3] = (b&1)<<4; 25 | 26 | if (p < last) { 27 | b = *p++; 28 | c[3] |= (b&0xf0)>>4; 29 | c[4] = (b&0x0f)<<1; 30 | if (p < last) { 31 | b = *p++; 32 | c[4] |= (b&0x80)>>7; 33 | c[5] = (b&0x7c)>>2; 34 | c[6] = (b&0x03)<<3; 35 | if (p < last) { 36 | b = *p++; 37 | c[6] |= (b&0xe0)>>5; 38 | c[7] = (b&0x1f); 39 | } 40 | } 41 | } 42 | } 43 | 44 | for (int i=0 ; i<8 ; i++) 45 | *enc++= b32ToChar[c[i]]; 46 | 47 | return p; 48 | } 49 | template 50 | std::tuple base32_encode(P ifirst, P ilast, S ofirst, S olast) 51 | { 52 | P p = ifirst; 53 | S o = ofirst; 54 | while (p < ilast && o+8 <= olast) 55 | { 56 | p = base32_encode_chunk(p, ilast, o); 57 | o += 8; 58 | } 59 | return { p, o }; 60 | } 61 | 62 | // -1 : invalid b32 63 | // -2 : b32 terminator 64 | // -3 : whitespace 65 | // >=0: valid b32 66 | static const int char2b32[]= { 67 | // 1 2 3 4 5 6 7 8 9 a b c d e f 68 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-3,-3,-1,-3,-3,-1,-1, // 0 69 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 1 70 | -3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 2 71 | -1,-1,26,27,28,29,30,31,-1,-1,-1,-1,-1,-2,-1,-1, // 3 72 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, // 4 73 | 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, // 5 74 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, // 4 75 | 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, // 5 76 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 8 77 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 9 78 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // a 79 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // b 80 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // c 81 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // d 82 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // e 83 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // f 84 | }; 85 | template 86 | std::tuple base32_decode(S ifirst, S ilast, P ofirst, P olast) 87 | { 88 | S p = ifirst; 89 | P o = ofirst; 90 | 91 | uint8_t b = 0; 92 | int i = 0; 93 | while (p < ilast && o < olast) 94 | { 95 | auto c = *p++; 96 | int cv = char2b32[(uint8_t)c]; 97 | if (cv==-2) { 98 | // '=' is always a proper base32 ending 99 | i = 0; 100 | break; 101 | } 102 | else if (cv==-3) { 103 | // skip whitespace 104 | continue; 105 | } 106 | else if (cv==-1) { 107 | // invalid character. 108 | return { p, o, false }; 109 | } 110 | 111 | // 00000111 11222223 33334444 45555566 66677777 112 | switch(i) 113 | { 114 | case 0: b = cv<<3; i=1; break; 115 | case 1: b |= cv>>2; *o++ = b; b = cv<<6; i=2; break; 116 | case 2: b |= cv<<1; i=3; break; 117 | case 3: b |= cv>>4; *o++ = b; b = cv<<4; i=4; break; 118 | case 4: b |= cv>>1; *o++ = b; b = cv<<7; i=5; break; 119 | case 5: b |= cv<<2; i=6; break; 120 | case 6: b |= cv>>3; *o++ = b; b = cv<<5; i=7; break; 121 | case 7: b |= cv; *o++ = b; i = 0; break; 122 | } 123 | } 124 | // skip trailing '=' 125 | while (p < ilast && *p == '=') 126 | p++; 127 | 128 | return { p, o, i==0 }; 129 | } 130 | 131 | template 132 | std::vector base32_decode(const S& txt) 133 | { 134 | std::vector data(((txt.size()+7)/8)*5); 135 | auto [p, o, ok] = base32_decode(txt.begin(), txt.end(), data.begin(), data.end()); 136 | if (!ok) 137 | throw std::runtime_error("base32_decode"); 138 | // todo: check if all data was decoded. 139 | data.erase(o, data.end()); 140 | return data; 141 | } 142 | 143 | template 144 | std::string base32_encode(const P& data) 145 | { 146 | std::string txt(((data.size()+4)/5)*8, char(0)); 147 | auto [p, o] = base32_encode(data.begin(), data.end(), txt.begin(), txt.end()); 148 | // todo: check if all data was encoded. 149 | txt.erase(o, txt.end()); 150 | return txt; 151 | } 152 | 153 | -------------------------------------------------------------------------------- /include/cpputils/base64encoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | /* 10 | Functions for base64 encoding and decoding. 11 | 12 | most convenient interface: 13 | 14 | std::string base64_encode(const P& data) 15 | std::vector base64_decode(const S& txt) 16 | 17 | Where the encode function takes any kind of vector/array, and the 18 | decode function takes any kind of string 19 | 20 | 21 | When you want functions which don't allocate anything 22 | the following two are useful: 23 | 24 | std::tuple base64_encode(P ifirst, P ilast, S ofirst, S olast) 25 | std::tuple base64_decode(S ifirst, S ilast, P ofirst, P olast) 26 | 27 | these take iterator(or pointer) pairs for the input and output, 28 | and return the last used in and output iterators. 29 | The decoder also returns a bool, indicating if the input string contained all valid base64. 30 | 31 | */ 32 | 33 | /* 34 | * encode a 3 byte chunk into 4 characters 35 | */ 36 | template 37 | auto base64_encode_chunk(P chunk, P last, S enc, bool nopadding) 38 | { 39 | // note: facebook, youtube use a modified version with tr "+/" "-_" 40 | P p = chunk; 41 | auto b = *p++; 42 | 43 | int c0= b>>2; 44 | int c1= (b&3)<<4; 45 | int c2= 0x40; 46 | int c3= 0x40; 47 | 48 | if (p < last) { 49 | b = *p++; 50 | c1 |= (b&0xf0)>>4; 51 | c2 = (b&0x0f)<<2; 52 | 53 | if (p < last) { 54 | b = *p++; 55 | c2 |= (b&0xc0)>>6; 56 | c3 = b&0x3f; 57 | } 58 | } 59 | 60 | *enc++= ALPHABET::code2char(c0); 61 | *enc++= ALPHABET::code2char(c1); 62 | if (!(nopadding && c2==0x40)) *enc++= ALPHABET::code2char(c2); 63 | if (!(nopadding && c3==0x40)) *enc++= ALPHABET::code2char(c3); 64 | 65 | return std::make_tuple(p, enc); 66 | } 67 | /* 68 | * returns a tuple with: 69 | * - the next unused input pointer 'p' 70 | * - the next unused output pointer 'o' 71 | * 72 | * When p!=ilast then there was not enough space in the output buffer 73 | * to encode all the data. 74 | */ 75 | template 76 | std::tuple base64_encode(P ifirst, P ilast, S ofirst, S olast, bool nopadding=false) 77 | { 78 | P p = ifirst; 79 | S o = ofirst; 80 | while (p < ilast && o+4 <= olast) 81 | { 82 | auto [newp, newo] = base64_encode_chunk(p, ilast, o, nopadding); 83 | p = newp; 84 | o = newo; 85 | } 86 | return { p, o }; 87 | } 88 | 89 | /* 90 | returns a tuple with: 91 | - where the decoding stopped 92 | - where the next unused output ptr. 93 | - success flag. 94 | */ 95 | template 96 | std::tuple base64_decode(S ifirst, S ilast, P ofirst, P olast) 97 | { 98 | S p = ifirst; 99 | P o = ofirst; 100 | 101 | uint8_t b = 0; 102 | int i = 0; 103 | while (p < ilast && o < olast) 104 | { 105 | auto c = *p++; 106 | int cv = ALPHABET::char2code(c); 107 | if (cv==-2) { 108 | // '=' always ends a base64 encoding 109 | break; 110 | } 111 | else if (cv==-3) { 112 | // skip whitespace 113 | continue; 114 | } 115 | else if (cv==-1) { 116 | // invalid character. 117 | return { p, o, false }; 118 | } 119 | 120 | switch(i) 121 | { 122 | case 0: b = cv<<2; i=1; break; 123 | case 1: b |= cv>>4; *o++ = b; b = cv<<4; i=2; break; 124 | case 2: b |= cv>>2; *o++ = b; b = cv<<6; i=3; break; 125 | case 3: b |= cv; *o++ = b; i = 0; break; 126 | } 127 | } 128 | // skip trailing '=' 129 | while (p < ilast && *p == '=') 130 | p++; 131 | 132 | return { p, o, i!=1 }; 133 | } 134 | 135 | template 136 | std::vector base64_decode(P first, P last) 137 | { 138 | std::vector data(((std::distance(first, last)+3)/4)*3); 139 | auto [p, o, ok] = base64_decode(first, last, data.begin(), data.end()); 140 | if (!ok) 141 | throw std::runtime_error("base64_decode"); 142 | // todo: check if all data was decoded. 143 | // -- problem: since decode will return when all bytes in the output are used. 144 | // there still may be unprocessed space chars left. 145 | // I could work around this by making the data vector one larger then needed. 146 | data.erase(o, data.end()); 147 | return data; 148 | } 149 | 150 | template 151 | std::vector base64_decode(const S& txt) 152 | { 153 | return base64_decode(txt.begin(), txt.end()); 154 | } 155 | 156 | template 157 | std::string base64_encode(const P& data) 158 | { 159 | std::string txt(((data.size()+2)/3)*4, char(0)); 160 | auto [p, o] = base64_encode(data.begin(), data.end(), txt.begin(), txt.end(), /*nopadding*/false); 161 | // check if all data was encoded. 162 | if (p != data.end()) 163 | throw std::runtime_error("base64_encode"); 164 | txt.erase(o, txt.end()); 165 | return txt; 166 | } 167 | 168 | template 169 | std::string base64_encode_unpadded(const P& data) 170 | { 171 | std::string txt(((data.size()+2)/3)*4, char(0)); 172 | auto [p, o] = base64_encode(data.begin(), data.end(), txt.begin(), txt.end(), /*nopadding*/true); 173 | // check if all data was encoded. 174 | if (p != data.end()) 175 | throw std::runtime_error("base64_encode_unpadded"); 176 | txt.erase(o, txt.end()); 177 | return txt; 178 | } 179 | -------------------------------------------------------------------------------- /include/cpputils/crccalc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | template 6 | class CrcCalc { 7 | std::array table; 8 | INT polymult(uint8_t b) 9 | { 10 | INT result = 0; 11 | INT factor = poly; 12 | for (int i = 0 ; i < 8 ; i++) 13 | { 14 | if (b & 0x80) 15 | result ^= factor; 16 | b <<= 1; 17 | bool bit = factor&1; 18 | factor >>= 1; 19 | if (bit) 20 | factor ^= poly; 21 | } 22 | return result; 23 | } 24 | void calc_table() 25 | { 26 | for (int i = 0 ; i < 256 ; i++) 27 | table[i] = polymult(i); 28 | } 29 | 30 | public: 31 | CrcCalc() 32 | { 33 | calc_table(); 34 | } 35 | INT add(INT crc, uint8_t b) const 36 | { 37 | return table[(crc^b)&0xFF] ^ (crc>>8); 38 | } 39 | INT add(INT crc, const uint8_t *p, int size) const 40 | { 41 | while (size--) 42 | crc = add(crc, *p++); 43 | return crc; 44 | } 45 | 46 | INT calc(const uint8_t *p, int size) const 47 | { 48 | constexpr INT mask = ((((INT)1<<(nbits-1))-1)<<1) | 1; 49 | return add(mask, p, size) ^ mask; 50 | } 51 | }; 52 | 53 | 54 | template 55 | uint16_t crc16(uint16_t crc, P ptr, size_t size) 56 | { 57 | static CrcCalc CRC; 58 | return CRC.add(crc, ptr, size); 59 | } 60 | template 61 | uint16_t crc16(P ptr, size_t size) 62 | { 63 | return crc16(~0, ptr, size) ^ (~0); 64 | } 65 | template 66 | uint16_t crc16(const V& v) 67 | { 68 | return crc16(&v[0], v.size()); 69 | } 70 | 71 | 72 | template 73 | uint32_t crc32(uint32_t crc, P ptr, size_t size) 74 | { 75 | static CrcCalc CRC; 76 | return CRC.add(crc, ptr, size); 77 | } 78 | template 79 | uint32_t crc32(P ptr, size_t size) 80 | { 81 | return crc32(~0, ptr, size) ^ (~0); 82 | } 83 | template 84 | uint32_t crc32(const V& v) 85 | { 86 | return crc32(&v[0], v.size()); 87 | } 88 | -------------------------------------------------------------------------------- /include/cpputils/fhandle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef _WIN32 9 | #include 10 | #include 11 | #endif 12 | #ifndef _WIN32 13 | #include 14 | 15 | #ifdef __MACH__ 16 | #include 17 | #endif 18 | #ifdef __linux__ 19 | #include 20 | #endif 21 | 22 | #include 23 | #include 24 | #endif 25 | #include 26 | 27 | // wrap posix file handle in a c++ class, 28 | // so it will be closed when it leaves the current scope. 29 | // This is most useful for making code exception safe. 30 | // 31 | // `filehandle` can be assigned to, copied, deleted safely. 32 | 33 | struct filehandle { 34 | 35 | // the 'ptr' class owns the filehandle, 36 | // the 'filehandle' has a shared_ptr to the 'ptr', 37 | // this makes sure the object can be copied as needed. 38 | struct ptr { 39 | int fh = -1; 40 | ptr(int fh) 41 | : fh(fh) 42 | { 43 | } 44 | ~ptr() { close(); } 45 | 46 | bool empty() const { return fh==-1; } 47 | 48 | void close() 49 | { 50 | if (fh==-1) 51 | return; 52 | if (-1 == ::close(fh)) { 53 | fh = -1; 54 | throw std::system_error(errno, std::generic_category(), "close"); 55 | } 56 | fh = -1; 57 | } 58 | }; 59 | std::shared_ptr _p; 60 | 61 | 62 | filehandle() { } 63 | 64 | // the copy constructor is implemented in terms of the assignment operator. 65 | filehandle(const filehandle& fh) 66 | { 67 | *this = fh; 68 | } 69 | 70 | // construct with `int` is implemented using `assign int`. 71 | filehandle(int fh) 72 | { 73 | *this = fh; 74 | } 75 | 76 | // construct with path + flags opens a filesystem file. 77 | filehandle(const std::string& filename, int openflags = O_RDONLY, int mode=0666) 78 | { 79 | open(filename, openflags, mode); 80 | } 81 | 82 | void open(const std::string& filename, int openflags = O_RDONLY, int mode=0666) 83 | { 84 | *this = ::open(filename.c_str(), openflags, mode); 85 | } 86 | 87 | // discards and optionally closes any handle currently retained, 88 | // before creating a new wrapped filehandle. 89 | filehandle& operator=(int fh) 90 | { 91 | if (fh==-1) 92 | throw std::system_error(errno, std::generic_category()); 93 | _p = std::make_shared(fh); 94 | return *this; 95 | } 96 | // copies the wrapped handle from the passed handle. 97 | filehandle& operator=(const filehandle& fh) 98 | { 99 | _p = fh._p; 100 | return *this; 101 | } 102 | bool empty() const { return !_p || _p->empty(); } 103 | 104 | // boolean-context checks if the filehandle has been set. 105 | operator bool () const { return !empty(); } 106 | 107 | 108 | // int-context returns the filehandle, so it can be passed to posix functions. 109 | operator int () const { return fh(); } 110 | int fh() const { 111 | if (!_p) throw std::runtime_error("no filehandle set"); 112 | return _p->fh; 113 | } 114 | 115 | // explicitly close the filehandle. 116 | // note: other copies will note an exception when using the handle. 117 | void close() { 118 | if (!_p) throw std::runtime_error("no filehandle set"); 119 | _p->close(); 120 | } 121 | 122 | // for regular-files, returns the filesize, 123 | // for block-devices, returns the device size. 124 | uint64_t size() 125 | { 126 | struct stat st; 127 | if (fstat(fh(), &st)) 128 | throw std::system_error(errno, std::generic_category(), "fstat"); 129 | 130 | if (st.st_mode&S_IFREG) 131 | return st.st_size; 132 | #ifndef _WIN32 133 | else if (st.st_mode&S_IFBLK) { 134 | #ifdef DKIOCGETBLOCKCOUNT 135 | uint64_t bkcount; 136 | uint32_t bksize; 137 | if (-1==ioctl(fh(), DKIOCGETBLOCKCOUNT, &bkcount)) 138 | throw std::system_error(errno, std::generic_category(), "ioctl(DKIOCGETBLOCKCOUNT)"); 139 | if (-1==ioctl(fh(), DKIOCGETBLOCKSIZE, &bksize)) 140 | throw std::system_error(errno, std::generic_category(), "ioctl(DKIOCGETBLOCKSIZE)"); 141 | return bkcount*bksize; 142 | #elif defined(BLKGETSIZE64) 143 | uint64_t devsize; 144 | if (-1==ioctl(fh(), BLKGETSIZE64, &devsize)) 145 | throw std::system_error(errno, std::generic_category(), "ioctl(BLKGETSIZE64)"); 146 | return devsize; 147 | #else 148 | throw std::runtime_error("size not implemented for block dev"); 149 | #endif 150 | } 151 | #endif 152 | else { 153 | // pipe or socket 154 | throw std::runtime_error("size not implemented for pipe or socket"); 155 | } 156 | } 157 | 158 | // note: this changes the process-global, OS maintained file pointer. 159 | uint64_t seek(int64_t pos, int whence=SEEK_SET) 160 | { 161 | auto rc = ::lseek(fh(), pos, whence); 162 | if (rc == -1) 163 | throw std::system_error(errno, std::generic_category(), "lseek"); 164 | return rc; 165 | } 166 | // note: this returns the process-global, OS maintained file pointer. 167 | uint64_t tell() 168 | { 169 | return seek(0, SEEK_CUR); 170 | } 171 | 172 | // modify the file's size. 173 | void trunc(uint64_t pos) 174 | { 175 | #ifndef _WIN32 176 | if (-1==::ftruncate(fh(), pos)) 177 | throw std::system_error(errno, std::generic_category(), "truncate"); 178 | #else 179 | HANDLE msfh = (HANDLE)_get_osfhandle(fh()); 180 | LARGE_INTEGER cur; 181 | // save filepos 182 | LARGE_INTEGER liZero; liZero.QuadPart = 0; 183 | if (!SetFilePointerEx(msfh, liZero, &cur, FILE_CURRENT)) 184 | throw std::system_error(errno, std::generic_category(), "SetFilePointerEx"); 185 | 186 | // change file size 187 | LARGE_INTEGER liPos; liPos.QuadPart = pos; 188 | if (!SetFilePointerEx(msfh, liPos, NULL, FILE_BEGIN)) 189 | throw std::system_error(errno, std::generic_category(), "SetFilePointerEx"); 190 | if (!SetEndOfFile(msfh)) 191 | throw std::system_error(errno, std::generic_category(), "SetEndOfFile"); 192 | 193 | // restore filepos 194 | if (!SetFilePointerEx(msfh, cur, NULL, FILE_BEGIN)) 195 | throw std::system_error(errno, std::generic_category(), "SetFilePointerEx"); 196 | #endif 197 | } 198 | 199 | // ============================= write ============================= 200 | // write (PTR ptr, size_t count) -> size_t 201 | // write (const RANGE& r) -> size_t 202 | // write (PTR first, PTR last) -> size_t 203 | // pwrite (uint64_t ofs, PTR ptr, size_t count) -> size_t 204 | 205 | template 206 | auto write(PTR ptr, size_t count) 207 | { 208 | auto rc = ::write(fh(), ptr, count*sizeof(*ptr)); 209 | if (rc == -1) 210 | throw std::system_error(errno, std::generic_category(), "write"); 211 | 212 | return rc/sizeof(*ptr); 213 | } 214 | 215 | template 216 | auto write(const RANGE& r) 217 | { 218 | return write(&*r.begin(), r.size()); 219 | } 220 | template 221 | auto write(PTR first, PTR last) 222 | { 223 | return write(first, std::distance(first, last)); 224 | } 225 | 226 | // Write without using the process-global filepointer. 227 | // Use this when writing to different locations in the file from 228 | // different threads. 229 | // The user should still make sure it controls who is writing where. 230 | template 231 | auto pwrite(uint64_t ofs, PTR ptr, size_t count) 232 | { 233 | auto rc = ::pwrite(fh(), ptr, count*sizeof(*ptr), ofs); 234 | if (rc == -1) 235 | throw std::system_error(errno, std::generic_category(), "pwrite"); 236 | 237 | return rc/sizeof(*ptr); 238 | } 239 | 240 | 241 | // ============================= read ============================= 242 | // - read (PTR ptr, size_t count) -> size_t 243 | // - read (PTR first, PTR last) -> size_t 244 | // - read (INT count) -> bytevector 245 | // - read (RANGE r) -> size_t 246 | // - readspan(std::span r) -> std::span 247 | // - pread (uint64_t ofs, PTR ptr, size_t count) -> size_t 248 | 249 | template 250 | size_t read(PTR ptr, size_t count) 251 | { 252 | auto rc = ::read(fh(), ptr, count*sizeof(*ptr)); 253 | if (rc == -1) 254 | throw std::system_error(errno, std::generic_category(), "read"); 255 | 256 | return rc/sizeof(*ptr); 257 | } 258 | 259 | template 260 | size_t read(PTR first, PTR last) 261 | { 262 | return read(first, std::distance(first, last)); 263 | } 264 | // template 265 | // std::enable_if_t, size_t> read(RANGE r) 266 | // { 267 | // return read(&*r.begin(), r.size()); 268 | // } 269 | template 270 | std::enable_if_t, std::vector> read(INT count) 271 | { 272 | std::vector data(count); 273 | auto rc = read(&data[0], data.size()); 274 | data.resize(rc); 275 | 276 | return data; 277 | } 278 | 279 | #if __cplusplus > 201703L 280 | // returns a span for the actually read bytes. 281 | std::span readspan(std::span r) 282 | { 283 | size_t n = read(r.data(), r.size()); 284 | return r.first(n); 285 | } 286 | std::span readspan(std::span r) 287 | { 288 | size_t n = read(r.data(), r.size()); 289 | return r.first(n); 290 | } 291 | template 292 | std::span readspan(std::span r) 293 | { 294 | size_t n = read(r.data(), r.size()); 295 | return r.first(n); 296 | } 297 | 298 | #endif 299 | // Read without using the process-global filepointer. 300 | // Use this when reading from different locations in the file from 301 | // different threads. 302 | template 303 | size_t pread(uint64_t ofs, PTR ptr, size_t count) 304 | { 305 | auto rc = ::pread(fh(), ptr, count*sizeof(*ptr), ofs); 306 | if (rc == -1) 307 | throw std::system_error(errno, std::generic_category(), "pread"); 308 | 309 | return rc/sizeof(*ptr); 310 | } 311 | 312 | }; 313 | -------------------------------------------------------------------------------- /include/cpputils/is_stream_insertable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // problem with clang: the operator<< functions must be declared -before- 3 | // including this file. with gcc the declaration order does not matter. 4 | 5 | /** test if T can be inserted in a std::ostream */ 6 | template 7 | class is_stream_insertable { 8 | template 9 | static auto test(int) 10 | -> decltype(std::declval() << std::declval(), std::true_type()); 11 | 12 | template 13 | static auto test(...)->std::false_type; 14 | 15 | public: 16 | static const bool value = decltype(test(0))::value; 17 | }; 18 | template 19 | constexpr bool is_stream_insertable_v = is_stream_insertable::value; 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /include/cpputils/lowpass_filter.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // see /home/itsme/mac/myprj/geotracker/plain-filter.py 4 | 5 | class LowPassFilter { 6 | std::vector _h; 7 | double sinc(double x) { 8 | if (x==0) 9 | return 1; 10 | return std::sin(M_PI*x)/(M_PI*x); 11 | } 12 | 13 | /* 14 | a * b 15 | 0*2 16 | 0*1 + 1*2 17 | 0*0 + 1*1 + 2*2 18 | 1*0 + 2*1 + 3*2 19 | 2*0 + 3*1 + 4*2 20 | 3*0 + 4*1 + 5*2 21 | 4*0 + 5*1 + 6*2 22 | 5*0 + 6*1 + 7*2 23 | 6*0 + 7*1 + 8*2 24 | 7*0 + 8*1 + 9*2 25 | 8*0 + 9*1 26 | 9*0 27 | 28 | 29 | 30 | 0*2 31 | 0*1 + 1*2 32 | 0*0 + 1*1 + 2*2 33 | 1*0 + 2*1 + 3*2 34 | 2*0 + 3*1 + 4*2 35 | 3*0 + 4*1 + 5*2 36 | 4*0 + 5*1 + 6*2 37 | 5*0 + 6*1 + 7*2 38 | 6*0 + 7*1 + 8*2 39 | 7*0 + 8*1 + 9*2 40 | 8*0 + 9*1 41 | 9*0 42 | */ 43 | auto convolve(const std::vector& a, const std::vector& b) 44 | { 45 | std::vector res; 46 | 47 | for (auto j = b.begin()+b.size()/2 ; j > b.begin() ; --j) 48 | { 49 | double sum = 0; 50 | auto jj = j; 51 | for (auto i = a.begin() ; i < a.end() && jj < b.end() ; ++i, ++jj) 52 | sum += *i * *jj; 53 | res.push_back(sum); 54 | } 55 | for (auto i = a.begin() ; i < a.end() ; ++i) 56 | { 57 | double sum = 0; 58 | auto ii = i; 59 | for (auto j = b.begin() ; j < b.end() && ii < a.end() ; ++j, ++ii) 60 | sum += *ii * *j; 61 | res.push_back(sum); 62 | } 63 | 64 | return res; 65 | } 66 | LowPassFilter(double fc, double b) 67 | { 68 | int N = std::ceil(4/b); 69 | if (N%2 == 0) 70 | ++N; 71 | 72 | double sumh = 0; 73 | for (int n = 0 ; n < N ; ++n) { 74 | double h = sinc(2*fc*(n-(N-1)/2)); 75 | double w = 0.42 - 0.5 * std::cos(2*M_PI*n/(N-1)) + 0.08*std::cos(4*M_PI*n/(N-1)); 76 | 77 | _h.push_back( h*w ); 78 | 79 | sumh += _h.back(); 80 | } 81 | for (auto & hv : _h) 82 | hv /= sumh; 83 | } 84 | 85 | auto calc(const std::vector& vec) 86 | { 87 | return convolve(vec, _h); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /include/cpputils/minmax.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /* 6 | 7 | This file provides fixedmin and fixedmax, both support differently typed arguments. 8 | 9 | This solves the problem where you get a compiler error for saying this: 10 | 11 | std::max(v.size(), 123) 12 | or 13 | std::max(p - q, v.size()) 14 | 15 | This would give you a compiler error: 16 | 17 | error: no matching function for call to 'min(int, unsigned int)' 18 | note: deduced conflicting types for parameter 'const _Tp' ('int' and 'unsigned int') 19 | 20 | You could pretend the error does not exist, and specify an explicit type, like this: 21 | 22 | std::min(-1, 2U) 23 | 24 | This will even result in incorrect code, and return '2U', without the compiler warning for it. 25 | So that is even worse. 26 | 27 | 28 | So you would always need to pay close attention to the types of the values you are comparing. 29 | 30 | A better solution: 31 | 32 | fixedmin(-1, 2U) -> -1 33 | 34 | See https://godbolt.org/z/ohzc7jGPY for an example. 35 | 36 | This table shows how the resulting type is determined: 37 | 38 | A B min max 39 | A A -- samesize(A,B) && samesignedness(A,B) 40 | s8 s8 s8 s8 41 | u8 u8 u8 u8 42 | s16 s16 s16 s16 43 | u16 u16 u16 u16 44 | 45 | B B -- largest_is_signed(A,B) 46 | s8 s16 s16 s16 47 | u8 s16 s16 s16 48 | A A -- largest_is_signed(A,B) 49 | s16 s8 s16 s16 50 | s16 u8 s16 s16 51 | 52 | signedof(A,B) unsignedof(A,B) -- sizeof(A)==sizeof(B) && different_signedness(A,B) 53 | s8 u8 s8 u8 54 | u8 s8 s8 u8 55 | s16 u16 s16 u16 56 | u16 s16 s16 u16 57 | 58 | smallest(A,B) largest(A,B) -- largest_is_unsigned(A,B) 59 | s8 u16 s8 u16 60 | u16 s8 s8 u16 61 | u8 u16 u8 u16 62 | u16 u8 u8 u16 63 | 64 | NOTE: 65 | gcc just says 'parse error' on fn==fn 66 | clang is more informative: a space is required between a right angle bracket and an equals sign. 67 | 68 | */ 69 | template 70 | using maxresult_t = 71 | std::conditional_t< (std::is_signed_v == std::is_signed_v && sizeof(L)==sizeof(R)), 72 | L, // both the same 73 | std::conditional_t< (std::is_signed_v && sizeof(L) && sizeof(L)>sizeof(R)), // largest type is signed 76 | L, // max = largest type 77 | std::conditional_t< (std::is_signed_v != std::is_signed_v && sizeof(L)==sizeof(R)), // different-signedness 78 | std::make_unsigned_t, // use unsigned type 79 | std::conditional_t< (std::is_unsigned_v && sizeof(L) && sizeof(L)>sizeof(R)), // largest is unsigned 82 | L, // use largest 83 | void // never happens 84 | > 85 | > 86 | > 87 | > 88 | > 89 | >; 90 | 91 | template 92 | constexpr bool equal_v = std::is_signed_v == std::is_signed_v; // && sizeof(L)==sizeof(R); 93 | 94 | template 95 | using minresult_t = 96 | std::conditional_t< (std::is_signed_v == std::is_signed_v && sizeof(L)==sizeof(R)), 97 | L, // both the same 98 | std::conditional_t< (std::is_signed_v && sizeof(L) && sizeof(L)>sizeof(R)), // largest type is signed 101 | L, // min = largest type 102 | std::conditional_t< (std::is_signed_v != std::is_signed_v && sizeof(L)==sizeof(R)), // different-signedness 103 | std::make_signed_t, // use signed type 104 | std::conditional_t< (std::is_unsigned_v && sizeof(L) && sizeof(L)>sizeof(R)), // largest is unsigned 107 | R, // use smallest 108 | void // never happens 109 | > 110 | > 111 | > 112 | > 113 | > 114 | >; 115 | 116 | template 117 | minresult_t fixedmin(T t, U u) 118 | { 119 | if (std::cmp_less(t, u)) 120 | return t; 121 | else 122 | return u; 123 | } 124 | 125 | template 126 | maxresult_t fixedmax(T t, U u) 127 | { 128 | if (std::cmp_less(u, t)) 129 | return t; 130 | else 131 | return u; 132 | } 133 | 134 | -------------------------------------------------------------------------------- /include/cpputils/mmem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // class wrappong a memmapped object 3 | 4 | #ifndef _WIN32 5 | #include 6 | #include 7 | #include 8 | #endif 9 | #ifdef _WIN32 10 | #include 11 | #include 12 | #include // get_osfhandle 13 | enum { 14 | PROT_READ = 1, 15 | PROT_WRITE = 2, 16 | }; 17 | 18 | #endif 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #ifdef __ANDROID_API__ 25 | extern "C" void* __mmap2(void*, size_t, int, int, int, size_t); 26 | #define mymmap __mmap2 27 | #define mmapoffset(x) ((x)>>12) 28 | #else 29 | #define mymmap mmap 30 | #define mmapoffset(x) (x) 31 | #endif 32 | 33 | #define dbgprint(...) 34 | 35 | /* 36 | * NOTE: problem on macosx: you can't mmap a block or char device. 37 | * 38 | * see https://stackoverflow.com/questions/24520474/mmap-a-block-device-on-mac-os-x 39 | * 40 | * the problem is in the mmap kernel driver: 41 | https://opensource.apple.com/source/xnu/xnu-2422.1.72/bsd/kern/kern_mman.c 42 | 43 | if (vp->v_type != VREG && vp->v_type != VCHR) { 44 | (void)vnode_put(vp); 45 | error = EINVAL; 46 | goto bad; 47 | } 48 | ... 49 | if (vp->v_type == VCHR || vp->v_type == VSTR) { 50 | (void)vnode_put(vp); 51 | error = ENODEV; 52 | goto bad; 53 | } 54 | 55 | */ 56 | 57 | 58 | // class for creating a memory mapped file. 59 | // TODO: create internal sharedptr like I did in filehandle. 60 | struct mappedmem { 61 | uint8_t *pmem; 62 | uint64_t phys_length; 63 | uint64_t dataofs; 64 | uint64_t length; 65 | 66 | static uint64_t round_up(uint64_t ofs, uint64_t base) 67 | { 68 | return ((ofs-1)|(base-1))+1; 69 | } 70 | static uint64_t round_down(uint64_t ofs, uint64_t base) 71 | { 72 | return ofs & ~(base-1); 73 | } 74 | 75 | 76 | mappedmem(mappedmem&& mm) 77 | { 78 | pmem = mm.pmem; 79 | phys_length = mm.phys_length; 80 | dataofs = mm.dataofs; 81 | length = mm.length; 82 | 83 | mm.pmem= nullptr; 84 | } 85 | #ifndef _WIN32 86 | // maps offsets start..end from file. 87 | mappedmem(int f, uint64_t start, uint64_t end, int mmapmode= PROT_READ|PROT_WRITE) 88 | { 89 | uint64_t pagesize= std::max(0x1000, (int)sysconf(_SC_PAGE_SIZE)); 90 | 91 | uint64_t phys_start= round_down(start, pagesize); 92 | uint64_t phys_end= round_up(end, pagesize); 93 | 94 | phys_length= phys_end - phys_start; 95 | length = end - start; 96 | dataofs= start - phys_start; 97 | 98 | if (phys_length==0) { 99 | pmem = NULL; 100 | return; 101 | } 102 | pmem= (uint8_t*)mymmap(NULL, phys_length, mmapmode, MAP_SHARED, f, mmapoffset(phys_start)); 103 | if (pmem==MAP_FAILED) 104 | throw std::system_error(errno, std::generic_category(), "mmap"); 105 | } 106 | 107 | // todo: add madvise support 108 | 109 | ~mappedmem() 110 | { 111 | if (!pmem) 112 | return; 113 | 114 | if (munmap(pmem, phys_length)) 115 | perror("munmap"); 116 | } 117 | #endif 118 | #ifdef _WIN32 119 | HANDLE m; 120 | int xlatmode2Protect(int mmode) 121 | { 122 | int r = 0; 123 | switch(mmode&(PROT_READ|PROT_WRITE)) 124 | { 125 | case PROT_READ|PROT_WRITE: r |= PAGE_READWRITE; break; 126 | case PROT_READ: r |= PAGE_READONLY; break; 127 | } 128 | return r; 129 | } 130 | int xlatmode2Access(int mmode) 131 | { 132 | int r = 0; 133 | if (mmode&PROT_READ) 134 | r |= FILE_MAP_READ; 135 | if (mmode&PROT_WRITE) 136 | r |= FILE_MAP_WRITE; 137 | return r; 138 | } 139 | static uint64_t getpagesize() 140 | { 141 | SYSTEM_INFO si; 142 | GetSystemInfo(&si); 143 | return si.dwAllocationGranularity; 144 | } 145 | 146 | mappedmem(int f, uint64_t start, uint64_t end, int mmapmode= PROT_READ|PROT_WRITE) 147 | : m(INVALID_HANDLE_VALUE), pmem(NULL) 148 | { 149 | uint64_t pagesize= std::max(0x1000, (int)getpagesize()); 150 | 151 | uint64_t phys_start= round_down(start, pagesize); 152 | uint64_t phys_end= round_up(end, pagesize); 153 | 154 | phys_length= phys_end - phys_start; 155 | length = end - start; 156 | dataofs= start - phys_start; 157 | 158 | if (phys_length==0) { 159 | pmem = NULL; 160 | return; 161 | } 162 | 163 | HANDLE fh=INVALID_HANDLE_VALUE;// note: both indicator value for anon-mapping, and error from get_osfhandle 164 | if (f!=-1) { 165 | fh = (HANDLE)_get_osfhandle(f); 166 | if (fh==INVALID_HANDLE_VALUE) 167 | throw std::system_error(errno, std::generic_category(), "invalid filehandle"); 168 | } 169 | m = CreateFileMapping(fh, NULL, xlatmode2Protect(mmapmode), (phys_end-phys_start)>>32, (phys_end-phys_start)&0xFFFFFFFF, NULL); 170 | if (m==NULL) 171 | throw std::system_error(errno, std::generic_category(), "CreateFileMapping"); 172 | pmem = (uint8_t*)MapViewOfFile(m, xlatmode2Access(mmapmode), phys_start>>32, phys_start&0xFFFFFFFF, phys_end-phys_start); 173 | if (pmem==NULL) 174 | throw std::system_error(errno, std::generic_category(), "MapViewOfFile"); 175 | } 176 | ~mappedmem() 177 | { 178 | if (pmem) 179 | if (!UnmapViewOfFile(pmem)) 180 | dbgprint("Error in UnmapViewOfFile: %08x\n", GetLastError()); 181 | if (m) 182 | if (!CloseHandle(m)) 183 | dbgprint("Error in CloseHandle: %08x\n", GetLastError()); 184 | } 185 | #endif 186 | 187 | uint8_t *data() { return pmem+dataofs; } 188 | const uint8_t *data() const { return pmem+dataofs; } 189 | 190 | uint8_t *begin() { return data(); } 191 | uint8_t *end() { return data() + length; } 192 | const uint8_t *begin() const { return data(); } 193 | const uint8_t *end() const { return data() + length; } 194 | size_t size() 195 | { 196 | return length; 197 | } 198 | uint8_t& operator[](size_t ix) { return data()[ix]; } 199 | 200 | // returns true when addresses stayed the same. 201 | bool resize(uint64_t newsize) 202 | { 203 | #ifndef MREMAP_MAYMOVE 204 | // freebsd does not have mremap 205 | throw std::runtime_error("mmap.resize not supported"); 206 | #else 207 | uint64_t pagesize= std::max(0x1000, (int)sysconf(_SC_PAGE_SIZE)); 208 | uint64_t new_physlength = round_up(dataofs+newsize, pagesize); 209 | 210 | uint8_t *newaddr = (uint8_t *)mremap(pmem, phys_length, new_physlength, MREMAP_MAYMOVE); 211 | if (newaddr==MAP_FAILED) 212 | throw std::system_error(errno, std::generic_category(), "mremap"); 213 | 214 | bool havemoved = newaddr!=pmem; 215 | 216 | pmem = newaddr; 217 | phys_length = new_physlength; 218 | // dataofs should remain the same 219 | length = newsize; 220 | 221 | return !havemoved; 222 | #endif 223 | } 224 | }; 225 | 226 | -------------------------------------------------------------------------------- /include/cpputils/mmfile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | /* 9 | retain both the filehandle and the mmap 10 | 11 | Note: this object may be quite unnescesary, since the mmap manual states: 12 | After the mmap() call has returned, the file descriptor, fd, can be closed 13 | immediately without invalidating the mapping. 14 | 15 | */ 16 | // todo: make this copyable. 17 | class mappedfile { 18 | filehandle _f; 19 | mappedmem _m; 20 | public: 21 | mappedfile(filehandle fh, int mmapmode) 22 | : _f(fh), 23 | _m(_f, 0, _f.size(), mmapmode) 24 | { 25 | } 26 | 27 | mappedfile(const std::string& filename, int openflags = O_RDONLY, int mode=0666) 28 | : mappedfile(open(filename.c_str(), openflags, mode), openflags==O_RDONLY ? PROT_READ : (PROT_READ|PROT_WRITE)) 29 | { 30 | } 31 | mappedfile(int fh, int mmapmode = PROT_READ) 32 | : mappedfile(filehandle{fh}, mmapmode) 33 | { 34 | } 35 | auto file() { return _f; } 36 | 37 | 38 | auto size() { return _m.size(); } 39 | auto begin() { return _m.begin(); } 40 | auto end() { return _m.end(); } 41 | 42 | // return true if pointers did not move. 43 | bool resize(uint64_t newsize) 44 | { 45 | _f.trunc(newsize); 46 | return _m.resize(newsize); 47 | } 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /include/cpputils/string-base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * Returns the byte length of a NUL terminated string. 8 | */ 9 | template 10 | size_t stringlength(const T *str) 11 | { 12 | size_t len=0; 13 | while (*str++) 14 | len++; 15 | return len; 16 | } 17 | 18 | /* 19 | * case sensitive compare of two NUL terminated strings. 20 | */ 21 | template 22 | int stringcompare(const TA *a, const TB *b) 23 | { 24 | while (*a && *b && *a == *b) 25 | { 26 | a++; 27 | b++; 28 | } 29 | if (*a<*b) 30 | return -1; 31 | if (*a>*b) 32 | return 1; 33 | return 0; 34 | } 35 | 36 | /* 37 | * Copy a NUL terminated string. 38 | * Returns a pointer to the last element copied. 39 | */ 40 | template 41 | TA *stringcopy(TA *a, const TB *b) 42 | { 43 | while ((*a++ = *b++)!=0) 44 | ; 45 | return a-1; 46 | } 47 | 48 | /* 49 | * case insensitive character compare 50 | */ 51 | template 52 | int charicompare(TA a, TB b) 53 | { 54 | a=(TA)tolower(a); 55 | b=(TB)tolower(b); 56 | if (ab) return 1; 58 | return 0; 59 | } 60 | /* 61 | * case sensitive character compare 62 | */ 63 | template 64 | int charcompare(TA a, TB b) 65 | { 66 | if (ab) return 1; 68 | return 0; 69 | } 70 | /* 71 | * case insensitive compare of two NUL terminated strings. 72 | */ 73 | template 74 | int stringicompare(const PA* a, const PB* b) 75 | { 76 | while (*a && *b && charicompare(*a, *b)==0) 77 | { 78 | a++; 79 | b++; 80 | } 81 | return charicompare(*a, *b); 82 | } 83 | 84 | /* 85 | * case insensitive compare of two stl string types. 86 | * 87 | * arguments can be anything which has begin and end, like: 88 | * vector, array, string, string_view, span, etc. 89 | */ 90 | template 91 | int stringicompare(const TA& a, const TB& b) 92 | { 93 | auto pa= std::begin(a); 94 | auto pa_end= std::end(a); 95 | 96 | auto pb= std::begin(b); 97 | auto pb_end= std::end(b); 98 | 99 | while (pa!=pa_end && pb!=pb_end && charicompare(*pa, *pb)==0) 100 | { 101 | pa++; 102 | pb++; 103 | } 104 | 105 | if (pa==pa_end && pb==pb_end) 106 | return 0; 107 | if (pa==pa_end) 108 | return -1; 109 | if (pb==pb_end) 110 | return 1; 111 | return charicompare(*pa, *pb); 112 | } 113 | /* 114 | * case sensitive compare of two stl string types. 115 | * 116 | * arguments can be anything which has begin and end, like: 117 | * vector, array, string, string_view, span, etc. 118 | */ 119 | template 120 | int stringcompare(const TA& a, const TB& b) 121 | { 122 | auto pa= std::begin(a); 123 | auto pa_end= std::end(a); 124 | 125 | auto pb= std::begin(b); 126 | auto pb_end= std::end(b); 127 | 128 | while (pa!=pa_end && pb!=pb_end && *pa == *pb) 129 | { 130 | pa++; 131 | pb++; 132 | } 133 | 134 | if (pa==pa_end && pb==pb_end) 135 | return 0; 136 | if (pa==pa_end) 137 | return -1; 138 | if (pb==pb_end) 139 | return 1; 140 | return charcompare(*pa, *pb); 141 | } 142 | 143 | /* returns true when sequence 'v' starts with the same elements as 'w' */ 144 | template 145 | bool beginswith(const T& v, const U& w) 146 | { 147 | return v.size()>=w.size() && std::equal(w.begin(), w.end(), v.begin()); 148 | //return std::size(v)>=std::size(w) && std::equal(std::begin(w), std::end(w), std::begin(v)); 149 | } 150 | 151 | -------------------------------------------------------------------------------- /include/cpputils/string-join.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | auto stringjoin(const S& sep, const V& v) 5 | { 6 | S result; 7 | for (auto & s : v) 8 | { 9 | if (!result.empty()) 10 | result += sep; 11 | result += s; 12 | } 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /include/cpputils/string-lineenum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | /* 6 | * 7 | * enumerate lines in a byte / char range like this: 8 | * 9 | * for (auto line : lineenumerator(range)) 10 | * { 11 | * ... 12 | * } 13 | * 14 | * note that this is kind of a duplicate of stringsplitter 15 | */ 16 | 17 | // todo: fix problem where last non-lf terminated section is not 18 | // returned as a part. 19 | template 20 | struct lineenumerator { 21 | struct iter { 22 | PTR p, q, last; 23 | 24 | iter(PTR first, PTR last) 25 | : p(first), q(first), last(last) 26 | { 27 | q = std::find(p, last, '\n'); 28 | } 29 | auto operator*() 30 | { 31 | return std::string(p, q); 32 | } 33 | iter& operator++() 34 | { 35 | p = (q 64 | struct iter { 65 | PTR p, q, last; 66 | 67 | iter(PTR first, PTR last) 68 | : p(first), q(first), last(last) 69 | { 70 | q = std::find(p, last, '\n'); 71 | } 72 | auto operator*() 73 | { 74 | return std::span(p, q); 75 | } 76 | iter& operator++() 77 | { 78 | p = (q text; 91 | 92 | template 93 | lineenumerator(std::span text) 94 | : text((const char*)text.data(), (const char*)text.data()+text.size()) 95 | { 96 | } 97 | iter begin() { return iter(text.begin(), text.end()); } 98 | iter end() { return iter(text.end(), text.end()); } 99 | }; 100 | */ 101 | -------------------------------------------------------------------------------- /include/cpputils/string-parse.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* 10 | * converts strings to numbers. 11 | * 12 | * Four ways to specify a string: 13 | * * a pair of pointers or iterators 14 | * * a pointer to a NUL terminated string 15 | * * a pointer and a size 16 | * * a container supporting std::begin and std::end 17 | * 18 | * `parseunsigned` and `parsesigned` return both the result, and the 19 | * position where parsing stopped. 20 | * 21 | * `string_to_unsigned` returns only the result, and throws when 22 | * parsing stopped before the end of the string. 23 | * 24 | * `hex2binary` converts a bound string of hex values to a vector of those values. 25 | * 26 | * Two variants: 27 | * * convert a bound string to a bound value range, 28 | * returning the number of converted values. 29 | * * taking a string, returning a specified vector type. 30 | * 31 | */ 32 | 33 | 34 | /* 35 | * convert a ascii digit to a number, for any base up to 36. 36 | * 37 | * digits are taken to be: 0..9, a..z 38 | * or 0..9, A..Z 39 | */ 40 | template 41 | int char2nyble(T c) 42 | { 43 | if (c<'0') return -1; 44 | if (c<='9') return c-'0'; 45 | if (c<'A') return -1; 46 | if (c<='Z') return 10+c-'A'; 47 | if (c<'a') return -1; 48 | if (c<='z') return 10+c-'a'; 49 | return -1; 50 | } 51 | 52 | /* 53 | parses a string bound by a pointer pair. 54 | 55 | failure is indicated by returning 'first'. 56 | */ 57 | template 58 | std::pair parseunsigned(P first, P last, int base) 59 | { 60 | int state = 0; // 3 = foundbase, 2 = invalid, 1 = found '0', 0 = initial state. 61 | int digits = 0; 62 | uint64_t num = 0; 63 | auto p = first; 64 | while (p, 0b, 0x 77 | state = 1; 78 | } 79 | else { 80 | // invalid 81 | state = 2; 82 | break; 83 | } 84 | } 85 | else if (state==1) { 86 | if (*p == 'x') { 87 | base = 16; 88 | state = 3; 89 | } 90 | else if (*p == 'b') { 91 | base = 2; 92 | state = 3; 93 | } 94 | else if (n==-1) { 95 | // just a single '0' followed by a non digit 96 | // -> return '0' 97 | break; 98 | } 99 | else if (0<=n && n<=7) { 100 | base = 8; 101 | state = 3; 102 | num = n; // this is the first real digit 103 | digits = 1; 104 | } 105 | else if (n>=8) { 106 | // invalid octal digit 107 | state = 2; 108 | break; 109 | } 110 | } 111 | else { 112 | // invalid 113 | state = 2; 114 | break; 115 | } 116 | } 117 | else { 118 | if (n<0 || n>=base) { 119 | // end of number 120 | break; 121 | } 122 | num *= base; 123 | num += n; 124 | digits++; 125 | } 126 | 127 | ++p; 128 | } 129 | if (state==2) // invalid 130 | return std::make_pair(0, first); 131 | 132 | if (state==3 && digits==0) { 133 | // "0x" and "0b" are invalid numbers, they expect digits. 134 | return std::make_pair(0, first); 135 | } 136 | return std::make_pair(num, p); 137 | } 138 | 139 | /* parses a string bound by a pointer pair. */ 140 | template 141 | std::pair parsesigned(P first, P last, int base) 142 | { 143 | auto p = first; 144 | bool negative = false; 145 | if (first==last) 146 | return std::make_pair(0, first); 147 | if (*p == '-') { 148 | negative = true; 149 | ++p; 150 | } 151 | auto res = parseunsigned(p, last, base); 152 | if (res.second == p) 153 | return std::make_pair(0, first); 154 | 155 | uint64_t num = negative ? -res.first : res.first; 156 | 157 | return std::make_pair(num, res.second); 158 | } 159 | 160 | /* parses a NUL terminated string */ 161 | template 162 | std::pair parseunsigned(const T* str, int base) 163 | { 164 | return parseunsigned(str, str+stringlength(str), base); 165 | } 166 | 167 | 168 | /* parses a NUL terminated string */ 169 | template 170 | std::pair parsesigned(const T* str, int base) 171 | { 172 | return parsesigned(str, str+stringlength(str), base); 173 | } 174 | 175 | /* parses a container type string */ 176 | template 177 | std::pair parseunsigned(const T& str, int base) 178 | { 179 | return parseunsigned(std::begin(str), std::end(str), base); 180 | } 181 | 182 | /* parses a container type string */ 183 | template 184 | std::pair parsesigned(const T& str, int base) 185 | { 186 | return parsesigned(std::begin(str), std::end(str), base); 187 | } 188 | 189 | template 190 | size_t hex2binary(P1 strfirst, P1 strlast, P2 first, P2 last) 191 | { 192 | typedef typename std::iterator_traits::value_type value_type; 193 | int shift = 0; 194 | value_type word=0; 195 | auto o= first; 196 | for (auto i= strfirst ; i!=strlast && o!=last ; i++) 197 | { 198 | int n= char2nyble(*i); 199 | if (n>=0 && n<16) { 200 | if (shift==0) { 201 | word= value_type(n); 202 | shift += 4; 203 | } 204 | else { 205 | word <<= 4; 206 | word |= n; 207 | shift += 4; 208 | } 209 | if (shift==sizeof(word)*8) { 210 | *o++ = word; 211 | 212 | shift = 0; 213 | } 214 | } 215 | } 216 | return o-first; 217 | } 218 | 219 | template 220 | auto hex2binary(const S& hexstr) 221 | { 222 | if constexpr (std::is_pointer_v || std::is_array_v) { 223 | auto len = stringlength(hexstr); 224 | V v(len / 2); 225 | size_t n = hex2binary(hexstr, hexstr+len, v.begin(), v.end()); 226 | 227 | // todo: when 'V' is an std::array, don't resize. 228 | v.resize(n); 229 | 230 | return v; 231 | } 232 | else { 233 | V v(hexstr.size() / 2); 234 | size_t n = hex2binary(hexstr.begin(), hexstr.end(), v.begin(), v.end()); 235 | 236 | // todo: when 'V' is an std::array, don't resize. 237 | v.resize(n); 238 | 239 | return v; 240 | } 241 | } 242 | template 243 | auto hex2binary(const P first, const P last) 244 | { 245 | V v(std::distance(first, last) / 2); 246 | size_t n = hex2binary(first, last, v.begin(), v.end()); 247 | v.resize(n); 248 | 249 | return v; 250 | } 251 | 252 | /* parses a string bound by a pointer pair. */ 253 | template 254 | uint64_t string_to_unsigned(P first, P last, int base) 255 | { 256 | auto [ value, end ] = parseunsigned(first, last, base); 257 | if (last != end) 258 | throw std::runtime_error("parsing error"); 259 | return value; 260 | } 261 | 262 | /* parses a NUL terminated string */ 263 | template 264 | uint64_t string_to_unsigned(const T* str, int base) 265 | { 266 | auto [ value, end ] = parseunsigned(str, base); 267 | if (*end) 268 | throw std::runtime_error("parsing error"); 269 | return value; 270 | } 271 | 272 | /* parses a container type string */ 273 | template 274 | uint64_t string_to_unsigned(const T& str, int base) 275 | { 276 | auto [ value, end ] = parseunsigned(str, base); 277 | if (std::end(str) != end) 278 | throw std::runtime_error("parsing error"); 279 | return value; 280 | } 281 | 282 | 283 | /* parses a string bound by a pointer pair. */ 284 | template 285 | int64_t string_to_signed(P first, P last, int base) 286 | { 287 | auto [ value, end ] = parsesigned(first, last, base); 288 | if (last != end) 289 | throw std::runtime_error("parsing error"); 290 | return value; 291 | } 292 | 293 | /* parses a NUL terminated string */ 294 | template 295 | int64_t string_to_signed(const T* str, int base) 296 | { 297 | auto [ value, end ] = parsesigned(str, base); 298 | if (*end) 299 | throw std::runtime_error("parsing error"); 300 | return value; 301 | } 302 | 303 | /* parses a container type string */ 304 | template 305 | int64_t string_to_signed(const T& str, int base) 306 | { 307 | auto [ value, end ] = parsesigned(str, base); 308 | if (std::end(str) != end) 309 | throw std::runtime_error("parsing error"); 310 | return value; 311 | } 312 | 313 | // todo: parse_double 314 | -------------------------------------------------------------------------------- /include/cpputils/string-split.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* 8 | * S is a std::string, or std::string_view 9 | */ 10 | template 11 | struct stringsplitter { 12 | S _str; 13 | S _sep; 14 | 15 | /* 16 | // separator, which splits according to items in, and not in the set. 17 | struct set { 18 | const S& sep; 19 | 20 | set(const S& sep) 21 | : sep(sep) 22 | { 23 | } 24 | auto findnext(S::const_iterator first, S::const_iterator last) 25 | { 26 | } 27 | auto findend(S::const_iterator first, S::const_iterator last) 28 | { 29 | } 30 | 31 | }; 32 | // separator, which splits according to substrings. 33 | struct string { 34 | const S& sep; 35 | 36 | string(const S& sep) 37 | : sep(sep) 38 | { 39 | } 40 | auto findnext(S::const_iterator first, S::const_iterator last) 41 | { 42 | } 43 | auto findend(S::const_iterator first, S::const_iterator last) 44 | { 45 | } 46 | 47 | 48 | }; 49 | */ 50 | struct iter { 51 | typename S::const_iterator p; 52 | typename S::const_iterator q; 53 | typename S::const_iterator last; 54 | const S& sep; 55 | 56 | // TODO: change interface to 'sep' such that 'sep' decides whether to do 57 | // a find_first_of or a search. 58 | // and to treat sequences of separators as one, or as separating empty elements. 59 | iter(typename S::const_iterator first, typename S::const_iterator last, const S& sep) 60 | : p(first), q(first), last(last), sep(sep) 61 | { 62 | } 63 | // updates 'q', to point to the first 'sep' 64 | S operator*() 65 | { 66 | //q = std::find_first_of(p, last, std::begin(sep), std::end(sep)); 67 | q = std::find_if(p, last, [this](auto c) { return std::find(std::begin(sep), std::end(sep), c) != std::end(sep); }); 68 | 69 | //q = std::search(p, last, std::begin(sep), std::end(sep)); 70 | 71 | // workaround for lack of iterator variant of stringview 72 | if constexpr (std::is_same_v) { 73 | return S(&*p, q-p); 74 | } 75 | else { 76 | return S(p, q); 77 | } 78 | } 79 | // updates 'p', to point to the first 'sep' 80 | iter& operator++() 81 | { 82 | p = std::find_if(q, last, [this](auto c) { return std::find(std::begin(sep), std::end(sep), c) == std::end(sep); }); 83 | //p = q + std::size(ssep) 84 | return *this; 85 | } 86 | friend bool operator!=(const iter& lhs, const iter& rhs) 87 | { 88 | return lhs.p!=rhs.p || lhs.last!=rhs.last; 89 | } 90 | }; 91 | 92 | // TODO: support two different string types 93 | stringsplitter(const S& str, const S& sep) 94 | : _str(str), _sep(sep) 95 | { 96 | } 97 | iter begin() const 98 | { 99 | return iter(std::begin(_str), std::end(_str), _sep); 100 | } 101 | iter end() const 102 | { 103 | return iter(std::end(_str), std::end(_str), _sep); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /include/cpputils/string-strip.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | template 13 | bool is_in(const SEQUENCE& trims, CHAR c) 14 | { 15 | if constexpr (is_searchable_v) 16 | return trims.find(c) != trims.end(); 17 | return std::find(std::begin(trims), std::end(trims), c) != std::end(trims); 18 | } 19 | template 20 | bool is_in(const char*trims, CHAR c) 21 | { 22 | return std::strchr(trims, c) != NULL; 23 | } 24 | template 25 | bool is_in(char trims, CHAR c) 26 | { 27 | return trims == c; 28 | } 29 | 30 | 31 | 32 | // strip algorithms taking first, last pointer pairs. 33 | // 34 | //note: i used to have identical names for the predicate variants, 35 | //and select the function by using: 36 | // std::enable_if_t, P> 37 | // std::enable_if_t, P> 38 | // 39 | // where 'std::is_void_v' means: 'is_container' 40 | 41 | 42 | template 43 | P rstrip_if(P first, P last, PRED pred) 44 | { 45 | auto i = last; 46 | 47 | while (i > first) 48 | { 49 | i--; 50 | if (!pred(*i)) 51 | return i+1; 52 | } 53 | return first; 54 | } 55 | 56 | template 57 | P rstrip(P first, P last, const S& trims) 58 | { 59 | return rstrip_if(first, last, [&](auto c) { return is_in(trims, c); }); 60 | } 61 | 62 | 63 | template 64 | P lstrip_if(P first, P last, PRED pred) 65 | { 66 | auto i = first; 67 | 68 | while (i < last) 69 | { 70 | if (!pred(*i)) 71 | return i; 72 | i++; 73 | } 74 | return last; 75 | } 76 | 77 | template 78 | P lstrip(P first, P last, const S& trims) 79 | { 80 | return lstrip_f(first, last, [&](auto c) { return is_in(trims, c); }); 81 | } 82 | 83 | template 84 | std::pair strip_if(P first, P last, PRED pred) 85 | { 86 | auto l = lstrip_if(first, last, pred); 87 | auto r = rstrip_if(first, last, pred); 88 | return {l, r}; 89 | } 90 | 91 | template 92 | std::pair strip(P first, P last, const S& trims) 93 | { 94 | return strip_if(first, last, [&](auto c) { return is_in(trims, c); }); 95 | } 96 | 97 | 98 | template 99 | T makeresult(P first, P last) 100 | { 101 | if constexpr (std::is_same_v || std::is_same_v) 102 | return {first, typename T::size_type(last-first)}; 103 | else 104 | return {first, last}; 105 | } 106 | 107 | // adaptors for stl types 108 | 109 | template 110 | T rstrip_if(const T& str, PRED pred) 111 | { 112 | return makeresult(std::begin(str), rstrip_if(std::begin(str), std::end(str), pred)); 113 | } 114 | 115 | template 116 | T rstrip(const T& str, const S& trims) 117 | { 118 | return rstrip_if(str, [&](auto c) { return is_in(trims, c); }); 119 | } 120 | 121 | 122 | template 123 | T lstrip_if(const T& str, PRED pred) 124 | { 125 | return makeresult(lstrip_if(std::begin(str), std::end(str), pred), std::end(str)); 126 | } 127 | 128 | template 129 | T lstrip(const T& str, const S& trims) 130 | { 131 | return lstrip_if(str, [&](auto c) { return is_in(trims, c); }); 132 | } 133 | 134 | template 135 | T strip_if(const T& str, PRED pred) 136 | { 137 | auto l = lstrip_if(std::begin(str), std::end(str), pred); 138 | auto r = rstrip_if(std::begin(str), std::end(str), pred); 139 | 140 | return makeresult(l, r); 141 | } 142 | 143 | template 144 | T strip(const T& str, const S& trims) 145 | { 146 | return strip_if(str, [&](auto c) { return is_in(trims, c); }); 147 | } 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /include/cpputils/stringconvert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Collection of functions for converting strings between the various UTF-XX encodings. 4 | * 5 | * The encoding is based on the character size, char, uint8_t and other 8-bit values are 6 | * assumed to be UTF-8 encoded, 7 | * short, uint16_t, and other 16-bit values, in UTF-16. 8 | * long, uint32_t and other 32-bit values in UTF-32. 9 | * 10 | * The endianness / byte order is always the compiler's and platform's native order. 11 | * 12 | * the conversion result is always a suitable std::string variant. 13 | * 14 | * 15 | * Usage: 16 | * 17 | * std::string utf8 = "utf8 text"; 18 | * char u8[8]; 19 | * 20 | * auto utf16 = string::convert>(utf8); 21 | * auto u16a = string::convert(u8, 8); 22 | * auto u16b = string::convert(u8, u8+8); 23 | * 24 | * 25 | * (C) 2016 Willem Hengeveld 26 | * 27 | */ 28 | #include 29 | #include 30 | 31 | namespace string { 32 | 33 | namespace z { 34 | ///////////////////////////////////////////////////////// 35 | // z-string utilities, for c style NUL terminated strings 36 | 37 | ///////////////////////////////////////////////////////// 38 | // string::z::length is basically a templated version of the libc strlen / wcslen functions. 39 | template 40 | size_t length(const T *str) 41 | { 42 | size_t len=0; 43 | while (*str++) 44 | len++; 45 | return len; 46 | } 47 | 48 | } 49 | 50 | 51 | /** 52 | * should take any kind of container: string, vector, array or stringview 53 | */ 54 | template 55 | inline auto convert(const SRC& src) 56 | { 57 | using SRCCHAR = typename SRC::value_type; 58 | 59 | std::basic_string dst(utfconvertor::maxsize(std::size(src)), DSTCHAR(0)); 60 | 61 | auto [ sused, dused ] = utfconvertor::convert(std::begin(src), std::end(src), std::begin(dst), std::end(dst)); 62 | 63 | dst.erase(dused, dst.end()); 64 | return dst; 65 | } 66 | 67 | /** 68 | * takes a NUL terminated C type string. 69 | */ 70 | template 71 | inline auto convert(const SRC* src) 72 | { 73 | auto l = z::length(src); 74 | std::basic_string dst(utfconvertor::maxsize(l), DSTCHAR(0)); 75 | 76 | auto [ sused, dused ] = utfconvertor::convert(src, src + l, std::begin(dst), std::end(dst)); 77 | 78 | dst.erase(dused, dst.end()); 79 | return dst; 80 | } 81 | /** 82 | * takes a pointer and a length 83 | */ 84 | template 85 | inline auto convert(const SRC* src, size_t length) 86 | { 87 | std::basic_string dst(utfconvertor::maxsize(length), DSTCHAR(0)); 88 | 89 | auto [ sused, dused ] = utfconvertor::convert(src, src + length, std::begin(dst), std::end(dst)); 90 | 91 | dst.erase(dused, dst.end()); 92 | return dst; 93 | } 94 | 95 | /** 96 | * takes iterator or pointer pair. 97 | */ 98 | template 99 | inline auto convert(SRCPTR first, SRCPTR last) 100 | { 101 | using SRCCHAR = typename std::iterator_traits::value_type; 102 | 103 | std::basic_string dst(utfconvertor::maxsize(std::distance(first, last)), DSTCHAR(0)); 104 | 105 | auto [ sused, dused ] = utfconvertor::convert(first, last, std::begin(dst), std::end(dst)); 106 | 107 | dst.erase(dused, dst.end()); 108 | return dst; 109 | } 110 | 111 | #ifdef _WIN32 112 | #ifdef __cplusplus_winrt 113 | ///////////////////////////////////////////////////////// 114 | // convert C# platform string 115 | template 116 | inline std::basic_string convert(Platform::String^ps) 117 | { 118 | std::basic_string src; 119 | for (wchar_t wc : ps) 120 | src += wc; 121 | return string::convert(src); 122 | } 123 | #endif 124 | #endif 125 | 126 | } // namespace 127 | -------------------------------------------------------------------------------- /include/cpputils/stringlibrary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | 15 | -------------------------------------------------------------------------------- /include/cpputils/templateutils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #if __cplusplus > 201703L 4 | #include 5 | #endif 6 | #include 7 | #include 8 | #include 9 | 10 | #define HAVE_db59c0d370a4d148_TEMPLATEUTILS 11 | 12 | /** test if T is a container type */ 13 | template 14 | struct is_container : std::false_type {}; 15 | 16 | template 17 | struct is_container > : std::true_type {}; 18 | template 19 | struct is_container > : std::true_type {}; 20 | #if __cplusplus > 201703L 21 | template 22 | struct is_container > : std::true_type {}; 23 | template 24 | struct is_container > : std::true_type {}; 25 | #endif 26 | template 27 | struct is_container > : std::true_type {}; 28 | template 29 | struct is_container > : std::true_type {}; 30 | 31 | template 32 | struct is_container > : std::true_type {}; 33 | template 34 | struct is_container > : std::true_type {}; 35 | 36 | // note: gcc will not match this template when N is specified as 'int' 37 | template 38 | struct is_container > : std::true_type {}; 39 | template 40 | struct is_container > : std::true_type {}; 41 | 42 | template 43 | constexpr bool is_container_v = is_container::value; 44 | 45 | 46 | template 47 | struct is_callable_impl { 48 | private: 49 | typedef char(&yes)[1]; 50 | typedef char(&no)[2]; 51 | 52 | struct Fallback { void operator()(); }; 53 | struct Derived : T, Fallback { }; 54 | 55 | template struct Check; 56 | 57 | template 58 | static yes test(...); 59 | 60 | template 61 | static no test(Check*); 62 | 63 | public: 64 | static const bool value = sizeof(test(0)) == sizeof(yes); 65 | }; 66 | template 67 | struct is_callable 68 | : std::conditional< 69 | std::is_class::value, 70 | is_callable_impl, 71 | std::false_type 72 | >::type 73 | { }; 74 | template constexpr bool is_callable_v = is_callable::value; 75 | 76 | 77 | template 78 | struct enable_if_type { typedef R type; }; 79 | 80 | template 81 | struct is_searchable : std::false_type {}; 82 | template 83 | struct is_searchable::type> : std::true_type 84 | {}; 85 | template constexpr bool is_searchable_v = is_searchable::value; 86 | 87 | 88 | -------------------------------------------------------------------------------- /include/cpputils/timepoint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifndef _WIN32 8 | #include 9 | #endif 10 | 11 | template 12 | class timedelta_cls { 13 | int64_t _usec; 14 | public: 15 | timedelta_cls() 16 | : _usec(0) 17 | { 18 | } 19 | timedelta_cls(int64_t usec) 20 | : _usec(usec) 21 | { 22 | } 23 | 24 | int64_t totalusec() const { return _usec; } 25 | int64_t totalmsec() const { return _usec/1000; } 26 | int64_t totalseconds() const { return _usec/1000/1000; } 27 | int64_t totalminutes() const { return _usec/1000/1000/60; } 28 | int64_t totalhours() const { return _usec/1000/1000/60/60; } 29 | int64_t totaldays() const { return _usec/1000/1000/60/60/24; } 30 | 31 | int usec() const { return _usec % 1000000; } 32 | int msec() const { return (_usec/1000) % 1000; } 33 | int seconds() const { return (_usec/1000000) % 60; } 34 | int minutes() const { return (_usec/1000000/60) % 60; } 35 | int hours() const { return (_usec/1000000/60/60) % 24; } 36 | 37 | bool iszero() const { return _usec == 0; } 38 | 39 | timedelta_cls& operator+=(timedelta_cls rhs) 40 | { 41 | _usec += rhs.totalusec(); 42 | return *this; 43 | } 44 | timedelta_cls& operator-=(timedelta_cls rhs) 45 | { 46 | _usec -= rhs.totalusec(); 47 | return *this; 48 | } 49 | friend std::ostream& operator<<(std::ostream& os, const timedelta_cls& dt) 50 | { 51 | return os << dt.totalseconds() << "." << std::setw(6) << std::setfill('0') << dt.usec(); 52 | } 53 | friend timedelta_cls operator-(timedelta_cls lhs, timedelta_cls rhs) 54 | { 55 | return timedelta_cls(lhs.totalusec() - rhs.totalusec()); 56 | } 57 | friend timedelta_cls operator+(timedelta_cls lhs, timedelta_cls rhs) 58 | { 59 | return timedelta_cls(lhs.totalusec() + rhs.totalusec()); 60 | } 61 | 62 | friend bool operator==(timedelta_cls lhs, timedelta_cls rhs) { return lhs.totalusec() == rhs.totalusec(); } 63 | friend bool operator!=(timedelta_cls lhs, timedelta_cls rhs) { return !(lhs==rhs); } 64 | friend bool operator<(timedelta_cls lhs, timedelta_cls rhs) { return lhs.totalusec() < rhs.totalusec(); } 65 | friend bool operator>=(timedelta_cls lhs, timedelta_cls rhs) { return lhs.totalusec() >= rhs.totalusec(); } 66 | friend bool operator>(timedelta_cls lhs, timedelta_cls rhs) { return lhs.totalusec() > rhs.totalusec(); } 67 | friend bool operator<=(timedelta_cls lhs, timedelta_cls rhs) { return lhs.totalusec() <= rhs.totalusec(); } 68 | 69 | }; 70 | 71 | template 72 | class timepoint_cls { 73 | #ifdef _WIN32 74 | FILETIME _tv; 75 | #else 76 | struct timeval _tv; 77 | #endif 78 | public: 79 | timepoint_cls() 80 | { 81 | #ifdef _WIN32 82 | _tv.dwLowDateTime = 0; 83 | _tv.dwHighDateTime = 0; 84 | #else 85 | _tv.tv_sec = 0; 86 | _tv.tv_usec = 0; 87 | #endif 88 | } 89 | timepoint_cls(const timepoint_cls& tp) 90 | { 91 | *this = tp; 92 | } 93 | auto& operator=(const timepoint_cls& tp) 94 | { 95 | _tv = tp._tv; 96 | return *this; 97 | } 98 | void setnow() 99 | { 100 | #ifdef _WIN32 101 | GetSystemTimePreciseAsFileTime(&_tv); 102 | #else 103 | gettimeofday(&_tv, 0); 104 | #endif 105 | } 106 | static timepoint_cls now() 107 | { 108 | timepoint_cls t; 109 | t.setnow(); 110 | 111 | return t; 112 | } 113 | timepoint_cls(int64_t epochusec) 114 | { 115 | set_unixepochusec(epochusec); 116 | } 117 | void set_unixepochusec(uint64_t epochusec) 118 | { 119 | #ifdef _WIN32 120 | // seconds since 1601-01-01 00:00:00 121 | // ft = (time_t * 10000000) + 116444736000000000 122 | // ft = (usec * 10) + 116444736000 123 | uint64_t ft = (epochusec * 10) + 116444736000; 124 | _tv.dwLowDateTime = (DWORD)ft; 125 | _tv.dwHighDateTime = DWORD(ft>>32); 126 | #else 127 | // seconds since 1970-01-01 00:00:00 128 | _tv.tv_sec = epochusec / 1000000; 129 | _tv.tv_usec = epochusec % 1000000; 130 | #endif 131 | } 132 | #ifdef _WIN32 133 | uint64_t msvcepochusec() const 134 | { 135 | return ( uint64_t(_tv.dwHighDateTime) << 32 ) | _tv.dwLowDateTime; 136 | } 137 | uint64_t unixepochusec() const 138 | { 139 | return msvcepochusec()/10 - 11644473600; 140 | } 141 | bool empty() const { return _tv.dwHighDateTime == 0; } 142 | 143 | operator const FILETIME&() const 144 | { 145 | return _tv; 146 | } 147 | #else 148 | uint64_t msvcepochusec() const 149 | { 150 | return (unixepochusec() * 10) + 116444736000; 151 | } 152 | uint64_t unixepochusec() const 153 | { 154 | return uint64_t(_tv.tv_sec)*1000000 + _tv.tv_usec; 155 | } 156 | 157 | bool empty() const { return _tv.tv_sec == 0; } 158 | 159 | operator const struct timeval&() const 160 | { 161 | return _tv; 162 | } 163 | #endif 164 | auto time() const { return unixepochusec()/1000000; } 165 | auto usec() const { return unixepochusec()%1000000; } 166 | 167 | timepoint_cls& operator+=(timedelta_cls rhs) 168 | { 169 | set_unixepochusec(unixepochusec() + rhs.totalusec()); 170 | 171 | return *this; 172 | } 173 | timepoint_cls& operator-=(timedelta_cls rhs) 174 | { 175 | set_unixepochusec(unixepochusec() - rhs.totalusec()); 176 | 177 | return *this; 178 | } 179 | 180 | 181 | operator double() const 182 | { 183 | return unixepochusec() * 0.000001; 184 | } 185 | operator int64_t() const 186 | { 187 | return unixepochusec(); 188 | } 189 | std::string iso() const 190 | { 191 | struct tm tm; 192 | time_t t = this->time(); 193 | if (NULL == localtime_r(&t, &tm)) 194 | throw std::system_error(errno, std::generic_category(), "localtime"); 195 | 196 | return stringformat("%04d-%02d-%02dT%02d:%02d:%02d.%06d%+03d:%02d", 197 | tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, 198 | tm.tm_hour, tm.tm_min, tm.tm_sec, 199 | this->usec(), 200 | #ifdef _WIN32 201 | 0, 0 202 | #else 203 | tm.tm_gmtoff/3600, (tm.tm_gmtoff/60)%60 204 | #endif 205 | ); 206 | } 207 | std::string isoutc() const 208 | { 209 | struct tm tm; 210 | time_t t = this->time(); 211 | if (NULL == gmtime_r(&t, &tm)) 212 | throw std::system_error(errno, std::generic_category(), "gmtime"); 213 | 214 | return stringformat("%04d-%02d-%02dT%02d:%02d:%02d.%06dZ", 215 | tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, 216 | tm.tm_hour, tm.tm_min, tm.tm_sec, 217 | this->usec()); 218 | } 219 | friend std::ostream& operator<<(std::ostream& os, const timepoint_cls& t) 220 | { 221 | return os << t.time() << "." << std::setw(6) << std::setfill('0') << t.usec(); 222 | } 223 | friend timedelta_cls operator-(const timepoint_cls& lhs, const timepoint_cls& rhs) 224 | { 225 | return timedelta_cls((int64_t)lhs - (int64_t)rhs); 226 | } 227 | friend timepoint_cls operator+(timepoint_cls lhs, const timedelta_cls rhs) 228 | { 229 | lhs += rhs; 230 | return lhs; 231 | } 232 | friend timepoint_cls operator-(timepoint_cls lhs, const timedelta_cls rhs) 233 | { 234 | lhs -= rhs; 235 | return lhs; 236 | } 237 | friend timepoint_cls operator+(const timedelta_cls lhs, timepoint_cls rhs) 238 | { 239 | return rhs+lhs; 240 | } 241 | 242 | friend bool operator==(const timepoint_cls& lhs, const timepoint_cls& rhs) { return int64_t(lhs) == int64_t(rhs); } 243 | friend bool operator!=(const timepoint_cls& lhs, const timepoint_cls& rhs) { return !(lhs==rhs); } 244 | friend bool operator<(const timepoint_cls& lhs, const timepoint_cls& rhs) { return int64_t(lhs) < int64_t(rhs); } 245 | friend bool operator>=(const timepoint_cls& lhs, const timepoint_cls& rhs) { return int64_t(lhs) >= int64_t(rhs); } 246 | friend bool operator>(const timepoint_cls& lhs, const timepoint_cls& rhs) { return int64_t(lhs) > int64_t(rhs); } 247 | friend bool operator<=(const timepoint_cls& lhs, const timepoint_cls& rhs) { return int64_t(lhs) <= int64_t(rhs); } 248 | 249 | 250 | }; 251 | 252 | 253 | using timepoint = timepoint_cls<>; 254 | using timedelta = timedelta_cls<>; 255 | 256 | -------------------------------------------------------------------------------- /include/cpputils/utfconvertor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace { 7 | 8 | /* anonymous namespace for translating char types to types defined by utfcvutils.h */ 9 | 10 | template struct chartypes { }; 11 | template<> struct chartypes<1> { typedef utf8char_t CHAR; }; 12 | template<> struct chartypes<2> { typedef utf16char_t CHAR; }; 13 | template<> struct chartypes<4> { typedef utf32char_t CHAR; }; 14 | 15 | template struct unsignedforsize { }; 16 | template<> struct unsignedforsize<1> { typedef uint8_t type; }; 17 | template<> struct unsignedforsize<2> { typedef uint16_t type; }; 18 | template<> struct unsignedforsize<4> { typedef uint32_t type; }; 19 | template<> struct unsignedforsize<8> { typedef uint64_t type; }; 20 | 21 | /* basically stringcopy, taking care of terminating NUL, returning used items */ 22 | template 23 | auto identityconvert(S src, S send, D dst, D dend) 24 | { 25 | while (src 34 | const typename chartypes::CHAR* char_cast(const T *p) 35 | { 36 | typedef const typename chartypes::CHAR* result_type; 37 | return reinterpret_cast(p); 38 | } 39 | template 40 | typename chartypes::CHAR* char_cast(T *p) 41 | { 42 | typedef typename chartypes::CHAR* result_type; 43 | return reinterpret_cast(p); 44 | } 45 | 46 | /* cast_to_char converts to the correct char type, given the number of bytes 'TO' needed in the type */ 47 | template 48 | const typename chartypes::CHAR* cast_to_char(const T *p) 49 | { 50 | typedef const typename chartypes::CHAR* result_type; 51 | return reinterpret_cast(p); 52 | } 53 | 54 | /* utfconvertor template converts from utf to utf encoding 55 | * 56 | * it has two static functions: 57 | * convert : converts utf codepoints from in to utf codepoints in 58 | * max codeunits are written to including the terminating NUL. 59 | * The number of codeunits used from is returned. 60 | * maxsize : gives a quick calculation of the maximum possible number of codeunits required 61 | * for converting utf codeunits. 62 | */ 63 | template 64 | struct utfconvertor { 65 | // [ sused, dused ] convert(*src, *dst, maxsize); 66 | // size_t maxsize(size_t from) 67 | }; 68 | 69 | template<> 70 | struct utfconvertor<1,2> { enum { FROM=1, TO=2 }; 71 | template 72 | static auto convert(S src, S send, D dst, D dend) { return utf8toutf16(src, send, dst, dend); } 73 | template 74 | static auto countneeded(S src, S end) { return utf8toutf16needed(src, end); } 75 | 76 | static size_t maxsize(size_t from) { return from; } 77 | }; 78 | template<> 79 | struct utfconvertor<2,1> { enum { FROM=2, TO=1 }; 80 | template 81 | static auto convert(S src, S send, D dst, D dend) { return utf16toutf8(src, send, dst, dend); } 82 | template 83 | static auto countneeded(S src, S end) { return utf16toutf8needed(src, end); } 84 | 85 | // for values between 0x800 and 0xffff you need 3 bytes in utf-8, and only 2 in utf-16 86 | static size_t maxsize(size_t from) { return 3*from; } 87 | }; 88 | 89 | template<> 90 | struct utfconvertor<1,4> { enum { FROM=1, TO=4 }; 91 | template 92 | static auto convert(S src, S send, D dst, D dend) { return utf8toutf32(src, send, dst, dend); } 93 | template 94 | static auto countneeded(S src, S end) { return utf8toutf32needed(src, end); } 95 | 96 | // values below 0x80 require 4 times more size in utf-32 97 | static size_t maxsize(size_t from) { return from; } 98 | }; 99 | template<> 100 | struct utfconvertor<4,1> { enum { FROM=4, TO=1 }; 101 | template 102 | static auto convert(S src, S send, D dst, D dend) { return utf32toutf8(src, send, dst, dend); } 103 | template 104 | static auto countneeded(S src, S end) { return utf32toutf8needed(src, end); } 105 | 106 | // values above 0x10000 require 4 utf-8 bytes 107 | static size_t maxsize(size_t from) { return 4*from; } 108 | }; 109 | template<> 110 | struct utfconvertor<4,2> { enum { FROM=4, TO=2 }; 111 | template 112 | static auto convert(S src, S send, D dst, D dend) { return utf32toutf16(src, send, dst, dend); } 113 | template 114 | static auto countneeded(S src, S end) { return utf32toutf16needed(src, end); } 115 | 116 | // values above 0x10000 require 2 utf-16 bytes 117 | static size_t maxsize(size_t from) { return 2*from; } 118 | }; 119 | template<> 120 | struct utfconvertor<2,4> { enum { FROM=2, TO=4 }; 121 | template 122 | static auto convert(S src, S send, D dst, D dend) { return utf16toutf32(src, send, dst, dend); } 123 | template 124 | static auto countneeded(S src, S end) { return utf16toutf32needed(src, end); } 125 | 126 | static size_t maxsize(size_t from) { return from; } 127 | }; 128 | template<> 129 | struct utfconvertor<1,1> { enum { FROM=1, TO=1 }; 130 | template 131 | static auto convert(S src, S send, D dst, D dend) { return identityconvert(src, send, dst, dend); } 132 | template 133 | static auto countneeded(S src, S end) { return std::distance(src, end); } 134 | 135 | static size_t maxsize(size_t from) { return from; } 136 | }; 137 | template<> 138 | struct utfconvertor<2,2> { enum { FROM=2, TO=2 }; 139 | template 140 | static auto convert(S src, S send, D dst, D dend) { return identityconvert(src, send, dst, dend); } 141 | template 142 | static auto countneeded(S src, S end) { return std::distance(src, end); } 143 | 144 | static size_t maxsize(size_t from) { return from; } 145 | }; 146 | template<> 147 | struct utfconvertor<4,4> { enum { FROM=4, TO=4 }; 148 | template 149 | static auto convert(S src, S send, D dst, D dend) { return identityconvert(src, send, dst, dend); } 150 | template 151 | static auto countneeded(S src, S end) { return std::distance(src, end); } 152 | 153 | static size_t maxsize(size_t from) { return from; } 154 | }; 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /include/cpputils/xmlnodetree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct XmlNode { 7 | using ptr = std::shared_ptr; 8 | using XmlAttr = XmlParser::attribute_list; 9 | 10 | std::string tag; 11 | XmlAttr attrs; 12 | std::string data; 13 | std::vector children; 14 | 15 | 16 | XmlNode(const std::string& tag, const XmlAttr& attrs) 17 | : tag(tag), attrs(attrs) 18 | { 19 | } 20 | XmlNode(const std::string& data) 21 | : data(data) 22 | { 23 | } 24 | 25 | static auto make(const std::string& tag, const XmlAttr& attrs) 26 | { 27 | return std::make_shared(tag, attrs); 28 | } 29 | static auto makedata(const std::string& data) 30 | { 31 | return std::make_shared(data); 32 | } 33 | 34 | void addchild(ptr child) 35 | { 36 | children.push_back(child); 37 | } 38 | void adddata(const std::string& moredata) 39 | { 40 | data += moredata; 41 | } 42 | 43 | std::string find_attr(const std::string& key) const 44 | { 45 | for (auto& kv : attrs) 46 | if (kv.first == key) 47 | return kv.second; 48 | return {}; 49 | } 50 | bool has_attr(const std::string& key) const 51 | { 52 | for (auto& kv : attrs) 53 | if (kv.first == key) 54 | return true; 55 | return false; 56 | } 57 | std::string asstring() const 58 | { 59 | return stringformat("tag:%s, %d at, %d ch, %d data", tag, attrs.size(), children.size(), data.size()); 60 | } 61 | template 62 | void recurse(FN fn) 63 | { 64 | for (auto c : children) 65 | fn(c); 66 | } 67 | 68 | std::string asxml() const 69 | { 70 | if (data.empty() && children.empty()) 71 | return stringformat("<%s%s/>", tag, attrstring()); 72 | else 73 | return stringformat("<%s%s>%s%s", tag, attrstring(), data, childstring(), tag); 74 | } 75 | std::string attrstring() const 76 | { 77 | std::string res; 78 | for (auto a : attrs) 79 | res += stringformat(" %s=\"%s\"", a.first, a.second); 80 | return res; 81 | } 82 | std::string childstring() const 83 | { 84 | std::string res; 85 | for (auto c : children) 86 | res += c->asxml(); 87 | return res; 88 | } 89 | }; 90 | 91 | class XmlNodeTree : public XmlParser { 92 | using XmlAttr = XmlParser::attribute_list; 93 | 94 | std::vector _stack; 95 | std::vector _roots; 96 | 97 | public: 98 | // interface for xmlparser 99 | void handle_starttag(std::string tag, const XmlAttr& attrs) override 100 | { 101 | auto item = XmlNode::make(tag, attrs); 102 | _stack.push_back(item); 103 | } 104 | void handle_endtag(std::string tag) override 105 | { 106 | auto item = _stack.back(); 107 | if (item->tag != tag) 108 | throw std::runtime_error("tag mismatch"); 109 | _stack.pop_back(); 110 | if (_stack.empty()) 111 | _roots.push_back(item); 112 | else 113 | _stack.back()->addchild(item); 114 | } 115 | void handle_data(const char*first, const char*last) override 116 | { 117 | auto sv = std::string(first, last-first); 118 | if (_stack.empty()) 119 | _roots.push_back(XmlNode::makedata(sv)); 120 | else 121 | _stack.back()->adddata(sv); 122 | } 123 | 124 | 125 | // interface for completed tree 126 | XmlNode::ptr root() const 127 | { 128 | XmlNode::ptr res; 129 | int nroots = 0; 130 | for (auto n : _roots) 131 | if (!n->tag.empty()) { 132 | if (!res) 133 | res = n; 134 | nroots++; 135 | } 136 | if (nroots>1) 137 | print("warn: found %d roots\n", nroots); 138 | 139 | return res; 140 | } 141 | bool validate() const 142 | { 143 | int nroots = 0; 144 | for (auto n : _roots) 145 | if (!n->tag.empty()) 146 | nroots++; 147 | return _stack.empty() && nroots==1; 148 | } 149 | void dump() const 150 | { 151 | if (!_stack.empty()) { 152 | print("== stack\n"); 153 | for (auto p : _stack) 154 | print("%s\n", p->asstring()); 155 | } 156 | if (!_roots.empty()) { 157 | print("== roots\n"); 158 | for (auto p : _roots) 159 | print("%s\n", p->asstring()); 160 | } 161 | print("--\n"); 162 | } 163 | void dumpxml() const 164 | { 165 | if (!_stack.empty()) { 166 | print("== stack\n"); 167 | for (auto p : _stack) 168 | print("* %s\n", p->asxml()); 169 | } 170 | if (!_roots.empty()) { 171 | print("== roots\n"); 172 | for (auto p : _roots) 173 | print("* %s\n", p->asxml()); 174 | } 175 | print("--\n"); 176 | } 177 | }; 178 | 179 | 180 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(doctest REQUIRED) 2 | 3 | file(GLOB UnittestSrc *.cpp) 4 | if (WIN32) 5 | # skippoing these tests on windows. 6 | list(REMOVE_ITEM UnittestSrc test-fhandle.cpp) 7 | list(REMOVE_ITEM UnittestSrc test-mmem.cpp) 8 | endif() 9 | 10 | # disable work-in-progress 11 | list(REMOVE_ITEM UnittestSrc test-makeasn1.cpp) 12 | 13 | add_executable(cpputils_unittests ${UnittestSrc}) 14 | set_property(TARGET cpputils_unittests PROPERTY OUTPUT_NAME unittests) 15 | target_link_libraries(cpputils_unittests cpputils doctest::doctest) 16 | target_compile_definitions(cpputils_unittests PRIVATE USE_DOCTEST) 17 | 18 | include(CTest) 19 | include(doctest OPTIONAL RESULT_VARIABLE res) 20 | if (res STREQUAL NOTFOUND) 21 | add_test(NAME CpputilsTest COMMAND cpputils_unittests) 22 | else() 23 | doctest_discover_tests(cpputils_unittests) 24 | endif() 25 | -------------------------------------------------------------------------------- /tests/test-argparse.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | #include 4 | #include 5 | 6 | TEST_CASE("argparse") { 7 | SECTION("test") { 8 | const char*argv[] = { 9 | "pgmname", // not counted in the option list 10 | "-a", "-123", // mask 0x0001, counted in nargs, value checked 11 | "--bigword", // mask 0x0200, counted in nbig 12 | "-b123", // mask 0x0002, counted in nargs 13 | "-pear", "0x1234", // mask 0x0004, counted in nargs, value checked 14 | "-vvv", // mask 0x0400, counted in nargs, multiplicity checked 15 | "-xxy", // mask 0x4000, counted in nargs, failed multiplicity checked 16 | "-pTEST", // mask 0x0008, counted in nargs, value checked 17 | "--apple=test", // mask 0x0010, counted in nargs, value checked 18 | "--equal==test", // mask 0x2000, counted in nargs, value checked 19 | "-cx", // mask 0x8000, counted in nargs 20 | "--bigword", // mask 0x0200, counted in nbig 21 | "--long", "999", // mask 0x0020, counted in nargs, value checked 22 | "firstfile", // mask 0x0080, counted in nfiles, value checked 23 | "secondfile", // mask 0x0100, counted in nfiles, value checked, 24 | "-", // mask 0x0040, counted in nstdin 25 | "--", // mask 0x1000, counted in nrends 26 | "moreargs", "-v", "-" // mask 0x0800, counted in nextra 27 | }; 28 | int argc = sizeof(argv)/sizeof(*argv); 29 | int nfiles = 0; 30 | int nextra = 0; 31 | int argmask = 0; 32 | int nargs = 0; 33 | int nbig = 0; 34 | int nstdin = 0; 35 | int nrends = 0; 36 | for (auto& arg : ArgParser(argc, argv)) 37 | if (nrends) { 38 | nextra ++; 39 | argmask |= 0x0800; 40 | } 41 | else switch(arg.option()) 42 | { 43 | case 'a': 44 | CHECK( arg.getint() == -123 ); 45 | argmask |= 0x0001; 46 | nargs ++; 47 | break; 48 | 49 | case 'b': 50 | CHECK( arg.getuint() == 123 ); 51 | argmask |= 0x0002; 52 | nargs ++; 53 | break; 54 | case 'c': 55 | CHECK( strcmp(arg.getstr(), "x") == 0 ); 56 | argmask |= 0x8000; 57 | nargs ++; 58 | break; 59 | 60 | case 'p': 61 | if (arg.match("-pear")) { 62 | CHECK( arg.getint() == 0x1234 ); 63 | argmask |= 0x0004; 64 | nargs ++; 65 | } 66 | else { 67 | CHECK( std::string(arg.getstr()) == "TEST" ); 68 | argmask |= 0x0008; 69 | nargs ++; 70 | } 71 | break; 72 | case 'v': 73 | CHECK( arg.count() == 3 ); 74 | argmask |= 0x0400; 75 | nargs ++; 76 | break; 77 | case 'x': 78 | CHECK_THROWS( arg.count() ); // counted options must all be equal 79 | argmask |= 0x4000; 80 | nargs ++; 81 | break; 82 | 83 | case '-': 84 | if (arg.match("--big")) { 85 | nbig++; 86 | CHECK_THROWS( arg.count() ); // can't count a long option 87 | argmask |= 0x0200; 88 | } 89 | else if (arg.match("--bigword")) { 90 | // --big should match before --bigword 91 | CHECK( false ); 92 | } 93 | else if (arg.match("--unused")) { 94 | CHECK( false ); 95 | } 96 | else if (arg.match("--apple")) { 97 | CHECK( std::string(arg.getstr()) == "test" ); 98 | argmask |= 0x0010; 99 | nargs ++; 100 | } 101 | else if (arg.match("--equal")) { 102 | CHECK( std::string(arg.getstr()) == "=test" ); 103 | argmask |= 0x2000; 104 | nargs ++; 105 | } 106 | else if (arg.match("--long")) { 107 | CHECK( arg.getint() == 999 ); 108 | argmask |= 0x0020; 109 | nargs ++; 110 | } 111 | else if (arg.optionterminator()) { 112 | nrends ++; 113 | argmask |= 0x1000; 114 | } 115 | else { 116 | INFO( "unexpected long option" ); 117 | CHECK( false ); 118 | } 119 | break; 120 | case 0: 121 | CHECK( std::string(arg.getstr()) == "-" ); 122 | nstdin ++; 123 | argmask |= 0x0040; 124 | break; 125 | case -1: 126 | switch(nfiles++) 127 | { 128 | case 0: 129 | CHECK( std::string(arg.getstr()) == "firstfile" ); 130 | argmask |= 0x0080; 131 | break; 132 | case 1: 133 | CHECK( std::string(arg.getstr()) == "secondfile" ); 134 | argmask |= 0x0100; 135 | break; 136 | default: 137 | INFO( "expected only two non options args" ); 138 | CHECK( false ); 139 | } 140 | break; 141 | default: 142 | INFO( "unexpected option" ); 143 | CHECK( false ); 144 | } 145 | CHECK( nstdin == 1 ); 146 | CHECK( nrends == 1 ); 147 | CHECK( nfiles == 2 ); 148 | CHECK( nextra == 3 ); 149 | CHECK( nargs == 10 ); 150 | CHECK( nbig == 2 ); 151 | CHECK( argmask == 0xFFFF ); 152 | } 153 | SECTION("testerrors") { 154 | const char*argv[] = { 155 | "pgmname", // not counted in the option list 156 | "-a" }; 157 | int argc = sizeof(argv)/sizeof(*argv); 158 | int argmask = 0; 159 | int nargs = 0; 160 | for (auto& arg : ArgParser(argc, argv)) 161 | switch(arg.option()) 162 | { 163 | case 'a': 164 | CHECK_THROWS( arg.getint() ); 165 | argmask |= 0x0001; 166 | nargs ++; 167 | break; 168 | default: 169 | INFO( "unexpected option" ); 170 | CHECK( false ); 171 | } 172 | CHECK( nargs == 1 ); 173 | CHECK( argmask == 0x0001 ); 174 | } 175 | 176 | } 177 | 178 | -------------------------------------------------------------------------------- /tests/test-arrayview.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | TEST_CASE("av") { 8 | CHECK( stringformat("%b", array_view((const uint8_t*)"te\n\tst", 6)) == "74 65 0a 09 73 74" ); 9 | CHECK( stringformat("%+b", array_view((const uint8_t*)"te\n\tst", 6)) == "74 65 0a 09 73 74" ); 10 | CHECK( stringformat("%-b", array_view((const uint8_t*)"te\n\tst", 6)) == "74 65 0a 09 73 74" ); 11 | 12 | CHECK( stringformat("%b", array_view("te\n\tst", 6)) == "74 65 0a 09 73 74" ); 13 | CHECK( stringformat("%-b", array_view("te\n\tst", 6)) == "74 65 0a 09 73 74" ); 14 | } 15 | -------------------------------------------------------------------------------- /tests/test-base32.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | TEST_CASE("base32") { 9 | enum { E=1, D=2, FD=4 }; 10 | 11 | struct testent { 12 | std::vector data; 13 | int flags; 14 | std::string txt; 15 | }; 16 | 17 | std::vector testcases = { 18 | { { }, E|D, "" }, 19 | { { }, D, "\n" }, 20 | { { }, D, "\t" }, 21 | { { }, FD, "." }, 22 | { { }, D, "\t" }, 23 | { { }, D, "=" }, 24 | { { }, D, "==" }, 25 | { { }, D, "===" }, 26 | 27 | // from rfc4648 28 | { { 0x66 }, E|D, "MY======" }, 29 | { { 0x66,0x6f }, E|D, "MZXQ====" }, 30 | { { 0x66,0x6f,0x6f }, E|D, "MZXW6===" }, 31 | { { 0x66,0x6f,0x6f,0x62 }, E|D, "MZXW6YQ=" }, 32 | { { 0x66,0x6f,0x6f,0x62,0x61 }, E|D, "MZXW6YTB" }, 33 | { { 0x66,0x6f,0x6f,0x62,0x61,0x72 }, E|D, "MZXW6YTBOI======" }, 34 | }; 35 | SECTION("cases") { 36 | for (auto& ent : testcases) { 37 | if (ent.flags&E) { 38 | auto e = base32_encode(ent.data); 39 | CHECK(e == ent.txt); 40 | } 41 | if (ent.flags&D) { 42 | auto d = base32_decode(ent.txt); 43 | CHECK(d == ent.data); 44 | } 45 | if (ent.flags&FD) { 46 | CHECK_THROWS(base32_decode(ent.txt)); 47 | } 48 | } 49 | } 50 | SECTION("types") { 51 | std::array a = { 0, 1, 2 }; 52 | CHECK( base32_encode(a) == "AAAQE===" ); 53 | 54 | std::vector v = { 0, 1, 2 }; 55 | CHECK( base32_encode(v) == "AAAQE===" ); 56 | 57 | std::basic_string bs = { 0, 1, 2 }; 58 | CHECK( base32_encode(bs) == "AAAQE===" ); 59 | CHECK( base32_encode((std::basic_string_view)bs ) == "AAAQE===" ); 60 | 61 | std::vector iv = { 0, 1, 2 }; 62 | CHECK( base32_encode(iv) == "AAAQE===" ); 63 | 64 | std::string txt = "AAAQE==="; 65 | CHECK( base32_decode(txt) == std::vector{0, 1, 2} ); 66 | CHECK( base32_decode((std::string_view)txt) == std::vector{0, 1, 2} ); 67 | 68 | std::basic_string itxt = { 'A', 'A', 'A', 'Q', 'E', '=', '=', '=' }; 69 | 70 | CHECK( base32_decode(itxt) == std::vector{0, 1, 2} ); 71 | CHECK( base32_decode((std::basic_string_view)itxt) == std::vector{0, 1, 2} ); 72 | 73 | std::basic_string btxt = { 'A', 'A', 'A', 'Q', 'E', '=', '=', '=' }; 74 | CHECK( base32_decode(btxt) == std::vector{0, 1, 2} ); 75 | CHECK( base32_decode((std::basic_string_view)btxt) == std::vector{0, 1, 2} ); 76 | } 77 | SECTION("invalid") { 78 | std::string txt = "x"; 79 | 80 | for (int i = 0 ; i < 256 ; i++) 81 | { 82 | if (i == '\t' || i == '\n' || i == '\r' || i == 0xc || i == ' ' 83 | || (i>='2' && i<='7') 84 | || (i>='a' && i<='z') 85 | || (i>='A' && i<='Z') 86 | || i=='=') 87 | continue; 88 | txt[0] = i; 89 | 90 | INFO("checking char " << i); 91 | CHECK_THROWS( base32_decode(txt) ); 92 | } 93 | } 94 | } 95 | 96 | 97 | -------------------------------------------------------------------------------- /tests/test-crccalc.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | #include 3 | 4 | // include twice to detect proper header behaviour 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | uint32_t crc24_1(P ptr, size_t size) 11 | { 12 | static CrcCalc CRC; 13 | return CRC.add(0xb704ce, ptr, size); 14 | } 15 | template 16 | uint32_t crc24_2(P ptr, size_t size) 17 | { 18 | static CrcCalc CRC; 19 | return CRC.add(0xb704ce, ptr, size); 20 | } 21 | 22 | TEST_SUITE("crc16") { 23 | TEST_CASE("checkcrc") { 24 | std::vector pkt0{0x00}; 25 | std::vector pkt1{0x13, 0x00, 0x7e}; 26 | 27 | CHECK( crc16(pkt0) == 0xf078 ); 28 | CHECK( crc16(pkt1) == 0x36c4 ); 29 | 30 | CHECK( crc32(pkt0) == 0xd202ef8d ); 31 | CHECK( crc32(pkt1) == 0x569c9800 ); 32 | } 33 | TEST_CASE("crccalc") { 34 | CrcCalc mycrc16; 35 | CrcCalc mycrc32; 36 | 37 | std::vector pkt0{0x00}; 38 | std::vector pkt1{0x13, 0x00, 0x7e}; 39 | 40 | CHECK( mycrc16.calc(&pkt0[0], pkt0.size()) == 0xf078 ); 41 | CHECK( mycrc16.calc(&pkt1[0], pkt1.size()) == 0x36c4 ); 42 | 43 | CHECK( mycrc32.calc(&pkt0[0], pkt0.size()) == 0xd202ef8d ); 44 | CHECK( mycrc32.calc(&pkt1[0], pkt1.size()) == 0x569c9800 ); 45 | 46 | } 47 | TEST_CASE("crc24") { 48 | std::vector v22(33, 0x22); 49 | std::vector v33(22, 0x33); 50 | CHECK( crc16(v22.data(), v22.size()) == 0x73b9 ); // x-25 51 | CHECK( crc32(v22.data(), v22.size()) == 0xee89f6e5 ); // crc-32 52 | CHECK( crc24_1(v22.data(), v22.size()) == 0x04E4F7B ); 53 | CHECK( crc24_2(v22.data(), v22.size()) == 0x0F95BF2 ); 54 | 55 | CHECK( crc16(v33.data(), v33.size()) == 0x9ba7 ); 56 | CHECK( crc32(v33.data(), v33.size()) == 0x70e447b9 ); 57 | CHECK( crc24_1(v33.data(), v33.size()) == 0x17F224B ); 58 | CHECK( crc24_2(v33.data(), v33.size()) == 0x0A38C2A ); 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /tests/test-datapack.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | #include 4 | #include 5 | TEST_CASE("packer") { 6 | SECTION("limits") { 7 | SECTION("empty") { 8 | std::vector data; 9 | 10 | unpacker p(data.begin(), data.end()); 11 | 12 | CHECK_THROWS(p.get8()); 13 | CHECK_THROWS(p.get32le()); 14 | CHECK_THROWS(p.getstr(1)); 15 | CHECK_THROWS(p.getbytes(1)); 16 | CHECK(p.getstr(0) == std::string()); 17 | CHECK(p.getbytes(0) == std::vector()); 18 | 19 | packer q(data.begin(), data.end()); 20 | 21 | CHECK_THROWS(q.set8(0)); 22 | CHECK_THROWS(q.set32le(0)); 23 | CHECK_THROWS(q.setstr("x")); 24 | CHECK_THROWS(q.setbytes(std::vector{0})); 25 | CHECK_NOTHROW(q.setstr("")); 26 | CHECK_NOTHROW(q.setbytes(std::vector{})); 27 | 28 | } 29 | SECTION("onebyte_8") { 30 | std::vector data(1); 31 | 32 | unpacker p(data.begin(), data.end()); 33 | 34 | CHECK(p.get8() == 0); 35 | CHECK_THROWS(p.get8()); 36 | } 37 | SECTION("onebyte_16") { 38 | std::vector data(1); 39 | 40 | unpacker p(data.begin(), data.end()); 41 | 42 | CHECK_THROWS(p.get16le()); 43 | } 44 | SECTION("onebyte_str") { 45 | std::vector data(1); 46 | 47 | unpacker p(data.begin(), data.end()); 48 | 49 | CHECK(p.getbytes(1) == std::vector{0x00}); 50 | } 51 | } 52 | 53 | SECTION("set/getiter") { 54 | std::vector data(36); 55 | 56 | packer p(data.begin(), data.end()); 57 | 58 | p.set32le(0x11223344); 59 | p.set16be(0x5566); 60 | p.set32be(0x11223344); 61 | p.set16le(0x5566); 62 | p.set8(0x88); 63 | p.set64be(0x123456789abcdef0LL); 64 | p.set64le(0x123456789abcdef0LL); 65 | p.setstr("test123"); 66 | CHECK_THROWS(p.set8(0)); 67 | 68 | // check consistency 69 | unpacker q(data.begin(), data.end()); 70 | 71 | CHECK(q.get32le() == 0x11223344); 72 | CHECK(q.get16be() == 0x5566); 73 | CHECK(q.get32be() == 0x11223344); 74 | CHECK(q.get16le() == 0x5566); 75 | CHECK(q.get8() == 0x88); 76 | CHECK(q.get64be() == 0x123456789abcdef0LL); 77 | CHECK(q.get64le() == 0x123456789abcdef0LL); 78 | CHECK(q.getstr(7) == "test123"); 79 | CHECK_THROWS(q.get8()); 80 | 81 | // check be / le 82 | unpacker r(data.begin(), data.end()); 83 | 84 | CHECK(r.get32be() == 0x44332211); 85 | CHECK(r.get16le() == 0x6655); 86 | CHECK(r.get32le() == 0x44332211); 87 | CHECK(r.get16be() == 0x6655); 88 | CHECK(r.get8() == 0x88); 89 | CHECK(r.get64le() == 0xf0debc9a78563412LL); 90 | CHECK(r.get64be() == 0xf0debc9a78563412LL); 91 | std::string str = "test123"; 92 | CHECK(std::equal(str.begin(), str.end(), r.getdata(7))); 93 | CHECK_THROWS(r.get8()); 94 | } 95 | SECTION("set/getptr") { 96 | std::vector data(29); 97 | 98 | packer p(&data[0], &data[0]+data.size()); 99 | 100 | p.set32le(0x11223344); 101 | p.set16be(0x5566); 102 | p.set32be(0x11223344); 103 | p.set16le(0x5566); 104 | p.set8(0x88); 105 | p.set64be(0x123456789abcdef0LL); 106 | p.set64le(0x123456789abcdef0LL); 107 | CHECK_THROWS(p.set8(0)); 108 | 109 | // check consistency 110 | unpacker q(&data[0], &data[0]+data.size()); 111 | 112 | CHECK(q.get32le() == 0x11223344); 113 | CHECK(q.get16be() == 0x5566); 114 | CHECK(q.get32be() == 0x11223344); 115 | CHECK(q.get16le() == 0x5566); 116 | CHECK(q.get8() == 0x88); 117 | CHECK(q.get64be() == 0x123456789abcdef0LL); 118 | CHECK(q.get64le() == 0x123456789abcdef0LL); 119 | CHECK_THROWS(q.get8()); 120 | 121 | // check be / le 122 | unpacker r(&data[0], &data[0]+data.size()); 123 | 124 | CHECK(r.get32be() == 0x44332211); 125 | CHECK(r.get16le() == 0x6655); 126 | CHECK(r.get32le() == 0x44332211); 127 | CHECK(r.get16be() == 0x6655); 128 | CHECK(r.get8() == 0x88); 129 | CHECK(r.get64le() == 0xf0debc9a78563412LL); 130 | CHECK(r.get64be() == 0xf0debc9a78563412LL); 131 | CHECK_THROWS(r.get8()); 132 | } 133 | SECTION("set/getstr") { 134 | std::vector data(8); 135 | 136 | packer p(&data[0], &data[0]+data.size()); 137 | 138 | p.set32le(0x41424344); 139 | p.setstr("abcd"); 140 | 141 | unpacker q(&data[0], &data[0]+data.size()); 142 | CHECK(q.getstr(4) == "DCBA"); 143 | CHECK(q.get32le() == 0x64636261); 144 | } 145 | 146 | SECTION("backinsert") { 147 | std::vector data; 148 | 149 | packer p(std::back_inserter(data), std::back_inserter(data)); 150 | 151 | p.set32le(0x11223344); 152 | p.set16be(0x5566); 153 | p.set32be(0x11223344); 154 | p.set16le(0x5566); 155 | 156 | CHECK(data.size() == 12); 157 | 158 | // check consistency 159 | unpacker q(data.begin(), data.end()); 160 | 161 | CHECK(q.get32le() == 0x11223344); 162 | CHECK(q.get16be() == 0x5566); 163 | CHECK(q.get32be() == 0x11223344); 164 | CHECK(q.get16le() == 0x5566); 165 | CHECK_THROWS(q.get8()); 166 | } 167 | } 168 | 169 | -------------------------------------------------------------------------------- /tests/test-fhandle.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | // TODO: this test just crashes directly on windows. 10 | TEST_CASE("filehandle") { 11 | #ifndef _WIN32 12 | signal(SIGPIPE, SIG_IGN); 13 | #endif 14 | SECTION("construct") { 15 | 16 | // check invalid fh 17 | CHECK_THROWS( filehandle{-1} ); 18 | 19 | // will throw on close 20 | #ifndef _WIN32 21 | // not checking on windows, there an invalid filehandle is fatal. 22 | CHECK_THROWS( filehandle{99}.close() ); 23 | #endif 24 | 25 | // check default constructor 26 | CHECK( filehandle{}.empty() ); 27 | CHECK_THROWS( filehandle{}.fh() ); 28 | 29 | // copy constructor 30 | CHECK( filehandle{filehandle{}}.empty() ); 31 | #ifndef _WIN32 32 | // not checking on windows, there an invalid filehandle is fatal. 33 | CHECK_THROWS( filehandle{filehandle{99}}.close() ); 34 | 35 | filehandle f; 36 | filehandle g; 37 | 38 | // check assignment 39 | CHECK_NOTHROW( g = f ); 40 | CHECK_THROWS( f = -1 ); 41 | CHECK_NOTHROW( f = 99 ); 42 | 43 | CHECK( f.fh() == 99 ); 44 | CHECK( 99 == f.fh() ); 45 | 46 | CHECK_NOTHROW( g = f ); 47 | CHECK( 99 == g.fh() ); 48 | 49 | CHECK_THROWS( f.close() ); // because 99 is not a valid fd. 50 | CHECK_NOTHROW( g.close() ); // now g is also already closed 51 | #endif 52 | } 53 | #ifndef _WIN32 54 | // TODO - create tests which work on windows as well. 55 | SECTION("pair") { 56 | filehandle f; 57 | filehandle g; 58 | 59 | int fpair[2]; 60 | ::pipe(fpair); 61 | 62 | f = fpair[0]; 63 | g = fpair[1]; 64 | 65 | CHECK( g.write("abc", 3) == 3 ); 66 | 67 | CHECK( f.read(3) == std::vector{ 'a', 'b', 'c' } ); 68 | 69 | g.close(); 70 | CHECK( f.read(3).empty() ); 71 | } 72 | SECTION("closereader") { 73 | 74 | int fpair[2]; 75 | ::pipe(fpair); 76 | 77 | filehandle f = fpair[0]; 78 | 79 | if (1) { 80 | filehandle g = fpair[1]; 81 | 82 | // data can be written and read 83 | CHECK( g.write("abc", 3) == 3 ); 84 | CHECK( f.read(3) == std::vector{ 'a', 'b', 'c' } ); 85 | 86 | // pipe is unidirectional 87 | #ifndef __FreeBSD__ 88 | // todo - why does this not throw on freebsd? 89 | CHECK_THROWS( f.write("abc", 3) ); 90 | CHECK_THROWS( g.read(3) ); 91 | #endif 92 | // leaving scope closes 'g' 93 | } 94 | 95 | if (1) { 96 | // 'g' is now already closed 97 | filehandle g = fpair[1]; 98 | 99 | // data can no longer be written and read 100 | CHECK_THROWS( g.write("abc", 3) ); // writing fails, because g is closed. 101 | // reading returns EOF / empty 102 | CHECK( f.read(3).empty() ); 103 | 104 | // will throw since fh is already closed. 105 | // need to explicitly close, to avoid an exception in 106 | // the destructor when leaving scope. 107 | CHECK_THROWS( g.close() ); 108 | } 109 | } 110 | SECTION("closewriter") { 111 | 112 | int fpair[2]; 113 | ::pipe(fpair); 114 | 115 | filehandle g = fpair[1]; 116 | 117 | if (1) { 118 | filehandle f = fpair[0]; 119 | 120 | // data can be written and read 121 | CHECK( g.write("abc", 3) == 3 ); 122 | CHECK( f.read(3) == std::vector{ 'a', 'b', 'c' } ); 123 | 124 | // pipe is unidirectional 125 | #ifndef __FreeBSD__ 126 | // todo - why does this not throw on freebsd? 127 | CHECK_THROWS( f.write("abc", 3) ); 128 | CHECK_THROWS( g.read(3) ); 129 | #endif 130 | // leaving scope closes 'f' 131 | } 132 | 133 | if (1) { 134 | // 'f' is now already closed 135 | filehandle f = fpair[0]; 136 | 137 | // data can no longer be written and read 138 | CHECK_THROWS( g.write("abc", 3) ); // writing will sigpipe 139 | 140 | CHECK_THROWS( f.read(3) ); // should fail because f is closed. 141 | 142 | // will throw since fh is already closed. 143 | // need to explicitly close, to avoid an exception in 144 | // the destructor when leaving scope. 145 | CHECK_THROWS( f.close() ); 146 | } 147 | } 148 | SECTION("copyfh") { 149 | 150 | int fpair[2]; 151 | ::pipe(fpair); 152 | 153 | filehandle f = fpair[0]; 154 | filehandle h = fpair[1]; 155 | 156 | if (1) { 157 | // 'g' is a copy of 'h' 158 | filehandle g = h; 159 | 160 | // data can be written and read 161 | CHECK( g.write("abc", 3) == 3 ); 162 | CHECK( f.read(3) == std::vector{ 'a', 'b', 'c' } ); 163 | 164 | // leaving scope does not close 'g', since it is a copy of 'h'. 165 | } 166 | 167 | if (1) { 168 | // 'g' is still open 169 | filehandle g = h; 170 | 171 | // data can still be written 172 | CHECK( g.write("abc", 3) == 3 ); 173 | CHECK( f.read(3) == std::vector{ 'a', 'b', 'c' } ); 174 | } 175 | 176 | } 177 | SECTION("overwritewriter") { 178 | int fpair[2]; 179 | ::pipe(fpair); 180 | 181 | filehandle f = fpair[0]; 182 | filehandle g = fpair[1]; 183 | 184 | int fpair2[2]; 185 | ::pipe(fpair2); 186 | 187 | // overwrite 'g', closing the read end of the pipe. 188 | g = fpair2[1]; 189 | 190 | // reading returns EOF 191 | CHECK( f.read(3).empty() ); 192 | } 193 | SECTION("overwritereader") { 194 | int fpair[2]; 195 | ::pipe(fpair); 196 | 197 | filehandle f = fpair[0]; 198 | filehandle g = fpair[1]; 199 | 200 | int fpair2[2]; 201 | ::pipe(fpair2); 202 | 203 | // overwrite 'f', closing the read end of the pipe. 204 | f = fpair2[0]; 205 | 206 | // reading returns EOF 207 | CHECK_THROWS( g.write("abc", 3) ); // writing fails, because f is closed. 208 | } 209 | #endif 210 | // TODO 211 | // - read(first,last) 212 | // - read(first,size) 213 | // - read(vector) 214 | // - read(span) 215 | // - read(range) 216 | // 217 | } 218 | -------------------------------------------------------------------------------- /tests/test-fslib.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | #include 3 | #include 4 | #include 5 | 6 | TEST_CASE("fileenum") { 7 | int n = 0; 8 | for (auto [p, e] : fileenumerator(".")) 9 | { 10 | //print("p:%s\n\te:%s\n", p, e); 11 | n++; 12 | } 13 | CHECK(n>0); 14 | } 15 | -------------------------------------------------------------------------------- /tests/test-hexdump.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | TEST_CASE("hexdumper") { 10 | SECTION("hex") { 11 | std::stringstream buf; 12 | buf << Hex::hexstring << std::setfill(' ') << Hex::dumper("abcde", 5); 13 | CHECK( buf.str() == "61 62 63 64 65" ); 14 | } 15 | SECTION("left") { 16 | std::stringstream buf; 17 | buf << Hex::singleline << std::left << Hex::dumper("abcde", 5); 18 | CHECK( buf.str() == "61 62 63 64 65" ); 19 | } 20 | SECTION("right") { 21 | std::stringstream buf; 22 | buf << Hex::singleline << std::showpos << Hex::dumper("abcde", 5); 23 | CHECK( buf.str() == "abcde" ); 24 | } 25 | SECTION("bin") { 26 | std::stringstream buf; 27 | buf << Hex::bin << std::left << Hex::dumper(std::vector{123}); 28 | 29 | CHECK( buf.str() == "01111011" ); 30 | } 31 | SECTION("hexstring") { 32 | std::stringstream buf; 33 | buf << Hex::hexstring << Hex::dumper("abcde", 5); 34 | CHECK( buf.str() == "6162636465" ); 35 | } 36 | SECTION("case") { 37 | std::stringstream buf; 38 | SECTION("lower") { 39 | buf << Hex::hexstring << std::nouppercase << Hex::dumper("jklmn", 5); 40 | CHECK( buf.str() == "6a6b6c6d6e" ); 41 | } 42 | SECTION("upper") { 43 | buf << Hex::hexstring << std::uppercase << Hex::dumper("jklmn", 5); 44 | CHECK( buf.str() == "6A6B6C6D6E" ); 45 | } 46 | } 47 | SECTION("numberbase") { 48 | std::stringstream buf; 49 | SECTION("hex") { 50 | buf << Hex::singleline << std::left << std::hex << Hex::dumper("abcde", 5); 51 | CHECK( buf.str() == "61 62 63 64 65" ); 52 | } 53 | SECTION("dec") { 54 | buf << Hex::singleline << std::left << std::dec << Hex::dumper("abcde", 5); 55 | CHECK( buf.str() == "97 98 99 100 101" ); 56 | } 57 | SECTION("oct") { 58 | buf << Hex::singleline << std::left << std::oct << Hex::dumper("abcde", 5); 59 | CHECK( buf.str() == "141 142 143 144 145" ); 60 | } 61 | SECTION("bin") { 62 | buf << Hex::singleline << std::left << Hex::bin << Hex::dumper("abcde", 5); 63 | CHECK( buf.str() == "01100001 01100010 01100011 01100100 01100101" ); 64 | } 65 | SECTION("hex.showbase") { 66 | buf << Hex::singleline << std::left << std::hex << std::showbase << Hex::dumper("abcde", 5); 67 | CHECK( buf.str() == "0x61 0x62 0x63 0x64 0x65" ); 68 | } 69 | SECTION("dec.showbase") { 70 | buf << Hex::singleline << std::left << std::dec << std::showbase << Hex::dumper("abcde", 5); 71 | CHECK( buf.str() == "97 98 99 100 101" ); 72 | } 73 | SECTION("oct.showbase") { 74 | buf << Hex::singleline << std::left << std::oct << std::showbase << Hex::dumper("abcde", 5); 75 | CHECK( buf.str() == "0141 0142 0143 0144 0145" ); 76 | } 77 | SECTION("bin.showbase") { 78 | buf << Hex::singleline << std::left << Hex::bin << std::showbase << Hex::dumper("abcde", 5); 79 | CHECK( buf.str() == "0b01100001 0b01100010 0b01100011 0b01100100 0b01100101" ); 80 | } 81 | 82 | } 83 | SECTION("ascstring") { 84 | std::stringstream buf; 85 | buf << Hex::ascstring << Hex::dumper("abcde\r\n", 7); 86 | CHECK( buf.str() == "abcde.." ); 87 | } 88 | 89 | SECTION("ascstring") { 90 | std::vector bv; 91 | std::string asc; 92 | for (int i=0 ; i<256 ; i++) { 93 | bv.push_back(i); 94 | asc.push_back( (i<32) || (i>=0x7f) ? '.' : char(i) ); 95 | } 96 | 97 | std::stringstream buf; 98 | buf << Hex::ascstring << Hex::dumper(bv); 99 | 100 | CHECK( buf.str() == asc ); 101 | } 102 | SECTION("singleline") { 103 | std::stringstream buf; 104 | buf << Hex::singleline << Hex::dumper("abcde", 5); 105 | CHECK( buf.str() == "61 62 63 64 65 abcde" ); 106 | } 107 | SECTION("multiline") { 108 | std::stringstream buf; 109 | buf << Hex::multiline << Hex::dumper("0123456789abcdefghijklmnopq", 27); 110 | CHECK( buf.str() == 111 | "30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66 0123456789abcdef\n" 112 | "67 68 69 6a 6b 6c 6d 6e 6f 70 71 ghijklmnopq \n"); 113 | } 114 | SECTION("offset") { 115 | std::stringstream buf; 116 | buf << Hex::offset(0xa000) << Hex::multiline << Hex::dumper("0123456789abcdefghijklmnopq", 27); 117 | CHECK( buf.str() == 118 | "0000a000: 30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66 0123456789abcdef\n" 119 | "0000a010: 67 68 69 6a 6b 6c 6d 6e 6f 70 71 ghijklmnopq \n"); 120 | } 121 | SECTION("step") { 122 | // this test currently fails 123 | std::stringstream buf; 124 | buf << Hex::offset(0xa000) << Hex::step(3) << std::setw(2) << Hex::dumper("0123456789abcdefghijklmnopq", 27); 125 | CHECK( buf.str() == 126 | "0000a000: 30 31 01\n" 127 | "0000a003: 33 34 34\n" 128 | "0000a006: 36 37 67\n" 129 | "0000a009: 39 61 9a\n" 130 | "0000a00c: 63 64 cd\n" 131 | "0000a00f: 66 67 fg\n" 132 | "0000a012: 69 6a ij\n" 133 | "0000a015: 6c 6d lm\n" 134 | "0000a018: 6f 70 op\n"); 135 | } 136 | SECTION("summarize") { 137 | // this test currently fails 138 | std::stringstream buf; 139 | SECTION("plain summary") { 140 | buf << Hex::offset(0) << std::setw(2) << Hex::dumper("012323232323cdefghghghghopq", 27); 141 | CHECK( buf.str() == 142 | "00000000: 30 31 01\n" 143 | "00000002: 32 33 23\n" 144 | "* [ 0x4 lines ]\n" 145 | "0000000c: 63 64 cd\n" 146 | "0000000e: 65 66 ef\n" 147 | "00000010: 67 68 gh\n" 148 | "* [ 0x3 lines ]\n" 149 | "00000018: 6f 70 op\n" 150 | "0000001a: 71 q \n" 151 | ); 152 | } 153 | SECTION("summary,th=3") { 154 | buf << Hex::offset(0) << std::setw(2) << Hex::summarize_threshold(3) << Hex::dumper("012323232323cdefghghghghopq", 27); 155 | CHECK( buf.str() == 156 | "00000000: 30 31 01\n" 157 | "00000002: 32 33 23\n" 158 | "* [ 0x4 lines ]\n" 159 | "0000000c: 63 64 cd\n" 160 | "0000000e: 65 66 ef\n" 161 | "00000010: 67 68 gh\n" 162 | "00000012: 67 68 gh\n" 163 | "00000014: 67 68 gh\n" 164 | "00000016: 67 68 gh\n" 165 | "00000018: 6f 70 op\n" 166 | "0000001a: 71 q \n" 167 | ); 168 | } 169 | SECTION("noskip") { 170 | buf << Hex::offset(0) << std::setw(2) << std::noskipws << Hex::dumper("012323232323cdefghghghghopq", 27); 171 | CHECK( buf.str() == 172 | "00000000: 30 31 01\n" 173 | "00000002: 32 33 23\n" 174 | "00000004: 32 33 23\n" 175 | "00000006: 32 33 23\n" 176 | "00000008: 32 33 23\n" 177 | "0000000a: 32 33 23\n" 178 | "0000000c: 63 64 cd\n" 179 | "0000000e: 65 66 ef\n" 180 | "00000010: 67 68 gh\n" 181 | "00000012: 67 68 gh\n" 182 | "00000014: 67 68 gh\n" 183 | "00000016: 67 68 gh\n" 184 | "00000018: 6f 70 op\n" 185 | "0000001a: 71 q \n" 186 | ); 187 | } 188 | 189 | } 190 | SECTION("values") { 191 | std::stringstream buf; 192 | SECTION("byte") { 193 | buf << Hex::singleline << std::left << Hex::dumper(std::vector{0, 0x55, 127, 128, 0xAA, 255}); 194 | CHECK( buf.str() == "00 55 7f 80 aa ff" ); 195 | } 196 | SECTION("char") { 197 | buf << Hex::singleline << std::left << Hex::dumper(std::vector{0, 0x55, 127, (char)-128, (char)-0x56, (char)-1}); 198 | CHECK( buf.str() == "00 55 7f 80 aa ff" ); 199 | } 200 | SECTION("hword") { 201 | buf << Hex::singleline << std::left << Hex::dumper(std::vector{0, 127, 128, 255, 0x5555, 0x7fff, 0x8000, 0xAAAA, 0xFFFF}); 202 | CHECK( buf.str() == "0000 007f 0080 00ff 5555 7fff 8000 aaaa ffff"); 203 | } 204 | SECTION("short") { 205 | buf << Hex::singleline << std::left << Hex::dumper(std::vector{0, 127, 128, 255, 0x5555, 0x7fff, -0x8000, -0x5556, -1}); 206 | CHECK( buf.str() == "0000 007f 0080 00ff 5555 7fff 8000 aaaa ffff"); 207 | } 208 | SECTION("dword") { 209 | buf << Hex::singleline << std::left << Hex::dumper(std::vector{0, 127, 128, 255, 0x7fff, 0x8000, 0xFFFF, 0x55555555, 0x7FFFFFFF, 0x80000000, 0xAAAAAAAA, 0xFFFFFFFF}); 210 | CHECK( buf.str() == "00000000 0000007f 00000080 000000ff 00007fff 00008000 0000ffff 55555555 7fffffff 80000000 aaaaaaaa ffffffff" ); 211 | } 212 | SECTION("long") { 213 | buf << Hex::singleline << std::left << Hex::dumper(std::vector{0, 127, 128, 255, 0x7fff, 0x8000, 0xFFFF, 0x55555555, 0x7FFFFFFF, -0x7fffffff-1, -0x55555556, -1}); 214 | CHECK( buf.str() == "00000000 0000007f 00000080 000000ff 00007fff 00008000 0000ffff 55555555 7fffffff 80000000 aaaaaaaa ffffffff" ); 215 | } 216 | SECTION("longlong") { 217 | buf << Hex::singleline << std::left << Hex::dumper(std::vector{0, 127, 128, 255, 0x7fff, 0x8000, 0xFFFF, 0x55555555, 0x7FFFFFFF, -0x7fffffff-1, -0x55555556, -1, 0x5555555555555555, -0x5555555555555556, 0x7FFFFFFFFFFFFFFF, -0x7FFFFFFFFFFFFFFF-1}); 218 | CHECK( buf.str() == "0000000000000000 000000000000007f 0000000000000080 00000000000000ff 0000000000007fff 0000000000008000 000000000000ffff 0000000055555555 000000007fffffff ffffffff80000000 ffffffffaaaaaaaa ffffffffffffffff 5555555555555555 aaaaaaaaaaaaaaaa 7fffffffffffffff 8000000000000000" ); 219 | } 220 | SECTION("ulonglong") { 221 | buf << Hex::singleline << std::left << Hex::dumper(std::vector{0, 127, 128, 255, 0x7fff, 0x8000, 0xFFFF, 0x55555555, 0x7FFFFFFF, 0x80000000, 0xAAAAAAAA, 0xFFFFFFFF, 0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0x7FFFFFFFFFFFFFFF, 0x8000000000000000, 0xFFFFFFFFFFFFFFFF}); 222 | CHECK( buf.str() == "0000000000000000 000000000000007f 0000000000000080 00000000000000ff 0000000000007fff 0000000000008000 000000000000ffff 0000000055555555 000000007fffffff 0000000080000000 00000000aaaaaaaa 00000000ffffffff 5555555555555555 aaaaaaaaaaaaaaaa 7fffffffffffffff 8000000000000000 ffffffffffffffff" ); 223 | } 224 | } 225 | } 226 | 227 | -------------------------------------------------------------------------------- /tests/test-lineenum.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | #include 4 | #include 5 | 6 | TEST_CASE("lineenum") { 7 | std::string text = "first\nsecond\nthird"; 8 | int i=0; 9 | for (auto part : lineenumerator(text)) { 10 | switch(i++) 11 | { 12 | case 0: CHECK(part == "first"); break; 13 | case 1: CHECK(part == "second"); break; 14 | case 2: CHECK(part == "third"); break; 15 | } 16 | } 17 | CHECK( i==3 ); // --> 2 : fail to return last section. 18 | } 19 | TEST_CASE("lineenum_empty") { 20 | std::string text = ""; 21 | int i=0; 22 | // verify that an empty string does have no lines. 23 | for (auto part : lineenumerator(text)) 24 | { 25 | CHECK_FALSE(i++==0); 26 | CHECK_FALSE(part.empty()); 27 | } 28 | CHECK(i==0); 29 | } 30 | TEST_CASE("lineenum_nolf") { 31 | std::string text = "test"; 32 | int i=0; 33 | // verify that an string without lf is handled as one line. 34 | for (auto part : lineenumerator(text)) { 35 | CHECK(i++==0); 36 | CHECK( part == "test" ); 37 | } 38 | CHECK(i==1); // --> 0 : fail to return last section. 39 | } 40 | TEST_CASE("lineenum_lf") { 41 | std::string text = "test\n"; 42 | int i=0; 43 | // verify that an string with an lf is handled as one line. 44 | for (auto part : lineenumerator(text)) { 45 | CHECK(i++==0); 46 | CHECK( part == "test" ); 47 | } 48 | CHECK(i==1); 49 | } 50 | TEST_CASE("lineenum_onlylf") { 51 | std::string text = "\n"; 52 | int i=0; 53 | // verify that an string with only an lf is handled as one empty line. 54 | for (auto part : lineenumerator(text)) { 55 | CHECK(i++==0); 56 | CHECK( part == "" ); 57 | } 58 | CHECK(i==1); 59 | } 60 | TEST_CASE("lineenum_lflf") { 61 | std::string text = "\n\n"; 62 | int i=0; 63 | // verify that an string with only an lf is handled as one empty line. 64 | for (auto part : lineenumerator(text)) { 65 | CHECK(i++<2); 66 | CHECK( part == "" ); 67 | } 68 | CHECK(i==2); 69 | } 70 | // test\n\n 71 | // \ntest 72 | // \n\ntest 73 | // \ntest\n 74 | 75 | TEST_CASE("lineenumref") { 76 | std::string text = "first\nsecond\nthird"; 77 | int i=0; 78 | for (const auto& part : lineenumerator(text)) { 79 | switch(i++) 80 | { 81 | case 0: CHECK(part == "first"); break; 82 | case 1: CHECK(part == "second"); break; 83 | case 2: CHECK(part == "third"); break; 84 | } 85 | } 86 | } 87 | #if 0 88 | TEST_CASE("lineenumw") { 89 | std::wstring text = L"first\nsecond\nthird"; 90 | int i=0; 91 | for (auto part : lineenumerator(text)) { 92 | switch(i++) 93 | { 94 | case 0: CHECK(part == L"first"); break; 95 | case 1: CHECK(part == L"second"); break; 96 | case 2: CHECK(part == L"third"); break; 97 | } 98 | } 99 | } 100 | 101 | TEST_CASE("lineenumwref") { 102 | std::wstring text = L"first\nsecond\nthird"; 103 | int i=0; 104 | for (const auto& part : lineenumerator(text)) { 105 | switch(i++) 106 | { 107 | case 0: CHECK(part == L"first"); break; 108 | case 1: CHECK(part == L"second"); break; 109 | case 2: CHECK(part == L"third"); break; 110 | } 111 | } 112 | } 113 | TEST_CASE("lineenumvector") { 114 | std::vector text = { 'a', '\n', 'b', '\n', 'c' }; 115 | int i=0; 116 | for (auto part : lineenumerator(text)) { 117 | switch(i++) 118 | { 119 | case 0: CHECK(part == "a"); break; 120 | case 1: CHECK(part == "b"); break; 121 | case 2: CHECK(part == "c"); break; 122 | } 123 | } 124 | } 125 | #endif 126 | 127 | -------------------------------------------------------------------------------- /tests/test-mmem.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #if defined(__MACH__) || defined(_WIN32) 15 | // simulate the linux memfd_create function 16 | int memfd_create(const char *name, int flags) 17 | { 18 | char uname[L_tmpnam]; 19 | if (std::tmpnam(uname)) { 20 | int f = open(uname, O_CREAT|O_RDWR, 0666); 21 | std::remove(uname); 22 | return f; 23 | } 24 | return -1; 25 | } 26 | #endif 27 | 28 | TEST_CASE("mmem0") { 29 | 30 | // create a virtual file 31 | #ifdef _WIN32 32 | int f = -1; 33 | #else 34 | filehandle f(memfd_create("test.dat", 0)); 35 | #endif 36 | 37 | // check that we can create a mapping from an empty file. 38 | 39 | mappedmem m(f, 0, 0x1000000); 40 | 41 | CHECK(m.size() == 0x1000000); 42 | } 43 | 44 | TEST_CASE("mmem") { 45 | 46 | std::vector rnddata(256); 47 | for (auto &b : rnddata) 48 | b = std::rand(); 49 | 50 | // create a virtual file 51 | filehandle f(memfd_create("test.dat", 0)); 52 | f.trunc(0x1234); 53 | 54 | mappedmem m(f, 0, 0x1000000); 55 | 56 | auto p0 = m.begin(); 57 | 58 | // init with some random data 59 | std::copy(rnddata.begin(), rnddata.end(), p0); 60 | 61 | // resize 62 | #ifndef _WIN32 63 | // note: on win32 you can't trunc a file which has a mapping. 64 | f.trunc(0x12340); 65 | #ifdef MREMAP_MAYMOVE 66 | m.resize(0x12340); 67 | #endif 68 | #endif 69 | 70 | auto p1 = m.begin(); 71 | 72 | // check that ptr and contents stayed the same 73 | CHECK( p0 == p1 ); 74 | CHECK( std::equal(rnddata.begin(), rnddata.end(), p0) ); 75 | 76 | #ifndef _WIN32 77 | // resize again 78 | f.trunc(0x1234); 79 | #ifdef MREMAP_MAYMOVE 80 | m.resize(0x1234); 81 | #endif 82 | #endif 83 | 84 | auto p2 = m.begin(); 85 | 86 | // check that ptr and contents stayed the same 87 | CHECK( p0 == p2 ); 88 | CHECK( std::equal(rnddata.begin(), rnddata.end(), p0) ); 89 | 90 | 91 | } 92 | 93 | 94 | TEST_CASE("mmem2") { 95 | 96 | std::vector rnddata(512); 97 | for (auto &b : rnddata) 98 | b = std::rand(); 99 | 100 | // create a virtual file 101 | filehandle f1(memfd_create("test.dat", 0)); 102 | f1.trunc(0x1234); 103 | 104 | mappedmem m1(f1, 0, 0x1000000000); 105 | auto p0 = m1.begin(); 106 | 107 | // init with some random data 108 | std::copy(rnddata.begin(), rnddata.begin()+256, p0); 109 | 110 | 111 | // create a virtual file 112 | filehandle f2(memfd_create("test2.dat", 0)); 113 | f2.trunc(0x1234); 114 | 115 | mappedmem m2(f2, 0, 0x1000000); 116 | auto q0 = m2.begin(); 117 | 118 | // init with some random data 119 | std::copy(rnddata.begin()+256, rnddata.end(), q0); 120 | 121 | // resize 122 | #ifndef _WIN32 123 | f1.trunc(0x10000000); 124 | #ifdef MREMAP_MAYMOVE 125 | m1.resize(0x10000000); 126 | #endif 127 | #endif 128 | auto p1 = m1.begin(); 129 | 130 | // check that ptr and contents stayed the same 131 | CHECK( p0 == p1 ); 132 | CHECK( std::equal(rnddata.begin(), rnddata.begin()+256, p0) ); 133 | 134 | // resize again 135 | #ifndef _WIN32 136 | f1.trunc(0x100000000); 137 | #ifdef MREMAP_MAYMOVE 138 | m1.resize(0x100000000); 139 | #endif 140 | #endif 141 | 142 | auto p2 = m1.begin(); 143 | 144 | // check that ptr and contents stayed the same 145 | CHECK( p0 == p2 ); 146 | CHECK( std::equal(rnddata.begin(), rnddata.begin()+256, p0) ); 147 | } 148 | 149 | -------------------------------------------------------------------------------- /tests/test-strip.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | #include 4 | #include 5 | 6 | // ... somehow doctest needs an explicit include of iostream here. 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | template 14 | auto makeset(const V& v) 15 | { 16 | std::set s; 17 | for (auto & x : v) 18 | s.insert(x); 19 | return s; 20 | } 21 | TEST_CASE("strip") { 22 | 23 | CHECK( rstrip(std::string("abcdef"), std::string("ef")) == std::string("abcd") ); 24 | CHECK( rstrip(std::string("abcdef"), std::string("ab")) == std::string("abcdef") ); 25 | CHECK( rstrip(std::string("abcdef"), std::string("")) == std::string("abcdef") ); 26 | 27 | CHECK( lstrip(std::string("abcdef"), std::string("ef")) == std::string("abcdef") ); 28 | 29 | CHECK( lstrip(std::string("abcdef"), "ef") == std::string("abcdef") ); 30 | CHECK( rstrip(std::string("abcdef"), 'f') == std::string("abcde") ); 31 | 32 | CHECK( lstrip(std::string("abcdef"), std::string("ab")) == std::string("cdef") ); 33 | 34 | CHECK( rstrip(std::string("abcdef"), makeset(std::string("ef"))) == std::string("abcd") ); 35 | CHECK( lstrip(std::string("abcdef"), makeset(std::string("ab"))) == std::string("cdef") ); 36 | 37 | std::vector v = { 'a', 'b', 'c', 'd', 'e', 'f' }; 38 | CHECK( rstrip(v.begin(), v.end(), std::string("ef")) == v.begin()+4 ); 39 | 40 | std::string txt = "xyzabcdefghi"; 41 | 42 | #ifndef _WIN32 43 | // TODO - why won't this compile with msvc? 44 | CHECK( rstrip( std::string_view(&txt[3], 6), "ef") == std::string_view(&txt[3], 4) ); 45 | CHECK( strip( std::string_view(&txt[3], 6), "abef") == std::string_view(&txt[5], 2) ); 46 | #endif 47 | CHECK( strip( std::string("abcdef"), "abef") == std::string("cd") ); 48 | 49 | std::wstring wtxt = L"xyzabcdefghi"; 50 | 51 | #ifndef _WIN32 52 | CHECK( rstrip( std::wstring_view(&wtxt[3], 6), "ef") == std::wstring_view(&wtxt[3], 4) ); 53 | CHECK( strip( std::wstring_view(&wtxt[3], 6), "abef") == std::wstring_view(&wtxt[5], 2) ); 54 | #endif 55 | CHECK( strip( std::wstring(L"abcdef"), "abef") == std::wstring(L"cd") ); 56 | 57 | CHECK( rstrip(std::string("abc"), std::string("bc")) == std::string("a") ); 58 | CHECK( rstrip(std::string("abc"), std::string("b")) == std::string("abc") ); 59 | CHECK( rstrip(std::string("abc"), std::string("")) == std::string("abc") ); 60 | CHECK( rstrip(std::string("ab"), std::string("b")) == std::string("a") ); 61 | CHECK( rstrip(std::string("a"), std::string("b")) == std::string("a") ); 62 | CHECK( rstrip(std::string("ab"), std::string("ab")) == std::string("") ); 63 | CHECK( rstrip(std::string("a"), std::string("ab")) == std::string("") ); 64 | CHECK( rstrip(std::string(""), std::string("ab")) == std::string("") ); 65 | CHECK( rstrip(std::string(""), std::string("a")) == std::string("") ); 66 | CHECK( rstrip(std::string(""), std::string("")) == std::string("") ); 67 | 68 | CHECK( lstrip(std::string("fedcba"), std::string("fe")) == std::string("dcba") ); 69 | CHECK( lstrip(std::string("fedcba"), std::string("ba")) == std::string("fedcba") ); 70 | CHECK( lstrip(std::string("fedcba"), std::string("")) == std::string("fedcba") ); 71 | 72 | CHECK( lstrip(std::string("cba"), std::string("cb")) == std::string("a") ); 73 | CHECK( lstrip(std::string("cba"), std::string("b")) == std::string("cba") ); 74 | CHECK( lstrip(std::string("cba"), std::string("")) == std::string("cba") ); 75 | CHECK( lstrip(std::string("ba"), std::string("b")) == std::string("a") ); 76 | CHECK( lstrip(std::string("a"), std::string("b")) == std::string("a") ); 77 | CHECK( lstrip(std::string("ba"), std::string("ba")) == std::string("") ); 78 | CHECK( lstrip(std::string("a"), std::string("ba")) == std::string("") ); 79 | CHECK( lstrip(std::string(""), std::string("ba")) == std::string("") ); 80 | CHECK( lstrip(std::string(""), std::string("a")) == std::string("") ); 81 | CHECK( lstrip(std::string(""), std::string("")) == std::string("") ); 82 | 83 | 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /tests/test-timepoint.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | // include twice to detect proper header behaviour 4 | #include 5 | #include 6 | 7 | TEST_SUITE("time") { 8 | TEST_CASE("delta") { 9 | CHECK( timedelta{1234567}.totalusec() == 1234567 ); 10 | CHECK( timedelta{1234567}.usec() == 234567 ); 11 | CHECK( timedelta{1234567}.msec() == 234 ); 12 | CHECK( timedelta{61234567}.seconds() == 1 ); 13 | CHECK( timedelta{1234567}.totalmsec() == 1234 ); 14 | CHECK( timedelta{1234567}.totalseconds() == 1 ); 15 | } 16 | TEST_CASE("point") { 17 | CHECK( timepoint{} == timepoint{} ); 18 | CHECK( timepoint{}.empty() ); 19 | CHECK( !timepoint::now().empty() ); 20 | CHECK( !timepoint{123131299313213}.empty() ); 21 | CHECK( timepoint{123456789123456} == timepoint{123456789123456} ); 22 | CHECK( timepoint{123456789123456}.time() == 123456789 ); 23 | CHECK( timepoint{123456789123456}.usec() == 123456 ); 24 | CHECK( (double)timepoint{123456789123456} == 123456789.123456 ); 25 | CHECK( (int64_t)timepoint{123456789123456} == 123456789123456 ); 26 | } 27 | TEST_CASE("arithmetic") { 28 | CHECK( timepoint{123456789123456} + timedelta{0} == timepoint{123456789123456} ); 29 | CHECK( timepoint{123456789123456} - timedelta{0} == timepoint{123456789123456} ); 30 | CHECK( timedelta{0} + timepoint{123456789123456} == timepoint{123456789123456} ); 31 | 32 | CHECK( timepoint{123456789123456} + timedelta{321} == timepoint{123456789123777} ); 33 | CHECK( timepoint{123456789123456} - timedelta{123} == timepoint{123456789123333} ); 34 | CHECK( timedelta{321} + timepoint{123456789123456} == timepoint{123456789123777} ); 35 | 36 | CHECK( timepoint{123456789123456} - timepoint{123456789000000} == timedelta{123456} ); 37 | CHECK( timedelta{123456} - timedelta{123} == timedelta{123333} ); 38 | CHECK( timedelta{123456} + timedelta{321} == timedelta{123777} ); 39 | } 40 | TEST_CASE("streams") { 41 | CHECK( stringformat("%s", timepoint{123456789123456}) == "123456789.123456" ); 42 | CHECK( stringformat("%s", timepoint{123456788999999}) == "123456788.999999" ); 43 | CHECK( stringformat("%s", timepoint{123456789000000}) == "123456789.000000" ); 44 | CHECK( stringformat("%s", timepoint{123456789000001}) == "123456789.000001" ); 45 | CHECK( stringformat("%s", timedelta{123}) == "0.000123" ); 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /tests/test-xmlparse.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | // include twice to detect proper header behaviour 4 | #include 5 | #include 6 | 7 | using namespace std::string_literals; 8 | 9 | class MyXml : public XmlParser { 10 | public: 11 | enum { START, END, ELEM, DECL, CMT, DATA }; 12 | std::vector> parsed; 13 | 14 | void handle_starttag(std::string tag, const attribute_list& attrs) override { parsed.emplace_back(START, tag, attrs); } 15 | void handle_endtag(std::string tag) override { parsed.emplace_back(END, tag, attribute_list{}); } 16 | void handle_element(std::string tag, const attribute_list& attrs) override { parsed.emplace_back(ELEM, tag, attrs); } 17 | void handle_xml_decl(std::string tag, const attribute_list& attrs) override { parsed.emplace_back(DECL, tag, attrs); } 18 | void handle_comment(const char*first, const char*last) override { parsed.emplace_back(CMT, std::string(first, last), attribute_list{}); } 19 | void handle_data(const char*first, const char*last) override { parsed.emplace_back(DATA, std::string(first, last), attribute_list{}); } 20 | 21 | #if 0 22 | auto mk(int type, const std::string& tag) 23 | { 24 | return decltype(parsed){std::make_tuple(type, tag, attribute_list{})}; 25 | } 26 | auto mk(int type0, const std::string& tag0, int type1, const std::string& tag1) 27 | { 28 | return decltype(parsed){std::make_tuple(type0, tag0, attribute_list{}), std::make_tuple(type1, tag1, attribute_list{})}; 29 | } 30 | #endif 31 | 32 | /* create a vector of { type, tag, [] } */ 33 | template 34 | auto mk(ARGS&&...args) 35 | { 36 | decltype(parsed) v; 37 | if constexpr (sizeof...(ARGS) > 0) 38 | add(v, std::forward(args)...); 39 | return v; 40 | } 41 | template 42 | void add(decltype(parsed)& v, int type, const std::string& tag, ARGS&&...args) 43 | { 44 | v.emplace_back(std::make_tuple(type, tag, attribute_list{})); 45 | if constexpr (sizeof...(ARGS) > 0) 46 | add(v, std::forward(args)...); 47 | } 48 | 49 | /* create a vector of { type, tag, attrlist } */ 50 | template 51 | auto mkx(ARGS&&...args) 52 | { 53 | decltype(parsed) v; 54 | if constexpr (sizeof...(ARGS) > 0) 55 | addx(v, std::forward(args)...); 56 | return v; 57 | } 58 | 59 | template 60 | void addx(decltype(parsed)& v, int type, const std::string& tag, const attribute_list& attrs, ARGS&&...args) 61 | { 62 | v.emplace_back(std::make_tuple(type, tag, attrs)); 63 | if constexpr (sizeof...(ARGS) > 0) 64 | addx(v, std::forward(args)...); 65 | } 66 | 67 | /* create a attributelist of [ { k, v }, ... ] */ 68 | template 69 | auto mkattr(ARGS&&...args) 70 | { 71 | attribute_list v; 72 | if constexpr (sizeof...(ARGS) > 0) 73 | addattr(v, std::forward(args)...); 74 | return v; 75 | } 76 | template 77 | void addattr(attribute_list& v, const std::string& key, const std::string& val, ARGS&&...args) 78 | { 79 | v.emplace_back(key, val); 80 | if constexpr (sizeof...(ARGS) > 0) 81 | addattr(v, std::forward(args)...); 82 | } 83 | 84 | }; 85 | #ifdef USE_CATCH 86 | std::ostream& operator<<(std::ostream& os, const std::pair& item) 87 | { 88 | return os << '(' << item.first << ", " << item.second << ')'; 89 | } 90 | std::ostream& operator<<(std::ostream& os, const std::vector>& item) 91 | { 92 | os << '['; 93 | bool first = true; 94 | for (auto & ent : item) 95 | { 96 | if (!first) 97 | os << ", "; 98 | os << ent; 99 | first = false; 100 | } 101 | os << ']'; 102 | return os; 103 | } 104 | 105 | std::ostream& operator<<(std::ostream& os, const std::tuple>>& item) 106 | { 107 | return os << '{' << std::get<0>(item) << ", " << std::get<1>(item) << ", " << std::get<1>(item) << '}'; 108 | } 109 | #endif 110 | TEST_SUITE("xml") { 111 | TEST_CASE("construct_xml") { 112 | XmlParser tst; 113 | 114 | CHECK(true); 115 | } 116 | TEST_CASE("testchars1") { 117 | XmlParser tst; 118 | 119 | CHECK_NOTHROW(tst.parse(""s)); 120 | CHECK_NOTHROW(tst.parse("."s)); 121 | CHECK_NOTHROW(tst.parse(" "s)); 122 | CHECK_NOTHROW(tst.parse(""s)); 123 | } 124 | TEST_CASE("testchars") { 125 | for (int c = 0 ; c < 256 ; c++) { 126 | XmlParser tst; 127 | char buf[2] = { (char)c, 0 }; 128 | if (c=='<') 129 | CHECK_THROWS(tst.parse(buf, buf+1)); 130 | else 131 | CHECK_NOTHROW(tst.parse(buf, buf+1)); 132 | } 133 | } 134 | TEST_CASE("test2chars") { 135 | for (int a = 0 ; a < 256 ; a++) { 136 | for (int b = 0 ; b < 256 ; b++) { 137 | XmlParser tst; 138 | char buf[3] = { (char)a, (char)b, 0 }; 139 | INFO("char = " << a << ',' << b); 140 | if (b=='<') 141 | CHECK_THROWS(tst.parse(buf, buf+2)); 142 | else if (a=='<' && "!?/="s.find(b)==std::string::npos) 143 | CHECK_THROWS(tst.parse(buf, buf+2)); 144 | else 145 | CHECK_NOTHROW(tst.parse(buf, buf+2)); 146 | } 147 | } 148 | } 149 | 150 | TEST_CASE("tstfail") { 151 | XmlParser tst; 152 | CHECK_NOTHROW(tst.parse(""s)); 153 | CHECK_NOTHROW(tst.parse(""s)); 154 | CHECK_NOTHROW(tst.parse(""s)); 155 | CHECK_NOTHROW(tst.parse(""s)); 156 | CHECK_NOTHROW(tst.parse(""s)); 157 | CHECK_NOTHROW(tst.parse(""s)); 158 | CHECK_NOTHROW(tst.parse(""s)); 159 | CHECK_NOTHROW(tst.parse(""s)); 160 | CHECK_NOTHROW(tst.parse(" "s)); 161 | CHECK_NOTHROW(tst.parse(""s)); 162 | CHECK_NOTHROW(tst.parse(""s)); 163 | CHECK_NOTHROW(tst.parse(""s)); 164 | CHECK_NOTHROW(tst.parse(""s)); 165 | 166 | // todo: > 167 | // <=> 168 | } 169 | TEST_CASE("validate") { 170 | MyXml tst; 171 | 172 | tst.parse(""s); 173 | tst.parse(""s); 174 | CHECK(tst.parsed == tst.mk(MyXml::START, "a", MyXml::END, "a")); 175 | tst.parsed.clear(); 176 | 177 | tst.parse(""s); 178 | CHECK(tst.parsed == tst.mk(MyXml::ELEM, "a")); 179 | tst.parsed.clear(); 180 | 181 | tst.parse("< a/>"s); 182 | CHECK(tst.parsed == tst.mk(MyXml::ELEM, "a")); 183 | tst.parsed.clear(); 184 | 185 | tst.parse(""s); 186 | CHECK(tst.parsed == tst.mk(MyXml::ELEM, "a")); 187 | tst.parsed.clear(); 188 | 189 | 190 | tst.parse("< a />"s); 191 | CHECK(tst.parsed == tst.mk(MyXml::ELEM, "a")); 192 | tst.parsed.clear(); 193 | 194 | tst.parse(""s); 195 | CHECK(tst.parsed == tst.mkx(MyXml::ELEM, "a", tst.mkattr("k", "v", "l", "w"))); 196 | tst.parsed.clear(); 197 | 198 | } 199 | }; 200 | -------------------------------------------------------------------------------- /tests/test-xmltree.cpp: -------------------------------------------------------------------------------- 1 | #include "unittestframework.h" 2 | 3 | #include 4 | #include 5 | 6 | TEST_CASE("xmlnode") { 7 | XmlNode x("tag", {}); 8 | 9 | CHECK(x.tag == "tag"); 10 | CHECK(x.attrs.empty()); 11 | CHECK(x.data.empty()); 12 | CHECK(x.children.empty()); 13 | 14 | XmlNode y("data"); 15 | 16 | CHECK(y.tag.empty()); 17 | CHECK(y.attrs.empty()); 18 | CHECK(y.data == "data"); 19 | CHECK(y.children.empty()); 20 | 21 | auto z = XmlNode::make("tag", {}); 22 | CHECK(z->tag == "tag"); 23 | CHECK(z->attrs.empty()); 24 | CHECK(z->data.empty()); 25 | CHECK(z->children.empty()); 26 | 27 | auto w = XmlNode::makedata("data"); 28 | CHECK(w->tag.empty()); 29 | CHECK(w->attrs.empty()); 30 | CHECK(w->data == "data"); 31 | CHECK(w->children.empty()); 32 | 33 | 34 | x.addchild(z); 35 | x.adddata("more"); 36 | CHECK(x.find_attr("TEST").empty()); 37 | CHECK(!x.has_attr("TEST")); 38 | 39 | CHECK(x.tag == "tag"); 40 | CHECK(x.attrs.empty()); 41 | CHECK(x.data == "more"); 42 | CHECK(x.children.size()==1); 43 | } 44 | TEST_CASE("xmltree") { 45 | XmlNodeTree tree; 46 | using namespace std::string_literals; 47 | tree.parse("abc213"s); 48 | 49 | CHECK(tree.validate()); 50 | CHECK(tree.root()); 51 | CHECK(tree.root()->tag == "plist"); 52 | } 53 | TEST_CASE("xmltree-2") { // with extra whitespace 54 | XmlNodeTree tree; 55 | using namespace std::string_literals; 56 | tree.parse(" \nabc213\n"s); 57 | 58 | CHECK(tree.validate()); 59 | CHECK(tree.root()); 60 | CHECK(tree.root()->tag == "plist"); 61 | } 62 | TEST_CASE("xmltree-3") { // with two roots. 63 | XmlNodeTree tree; 64 | using namespace std::string_literals; 65 | tree.parse(" \n\n"s); 66 | 67 | CHECK(!tree.validate()); 68 | } 69 | TEST_CASE("xmltree-4") { // with mismatched tags 70 | XmlNodeTree tree; 71 | using namespace std::string_literals; 72 | CHECK_THROWS(tree.parse(" \n\n"s)); 73 | } 74 | TEST_CASE("xmltree-5") { // unclosed tag. 75 | XmlNodeTree tree; 76 | using namespace std::string_literals; 77 | tree.parse(" \n\n"s); 78 | 79 | CHECK(!tree.validate()); 80 | } 81 | TEST_CASE("xmltree-6") { // empty string 82 | XmlNodeTree tree; 83 | using namespace std::string_literals; 84 | tree.parse(""s); 85 | 86 | CHECK(!tree.validate()); 87 | } 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /tests/unittestframework.h: -------------------------------------------------------------------------------- 1 | /* 2 | * catch is available from: https://github.com/catchorg/Catch2 3 | * doctest is available from https://github.com/onqtam/doctest 4 | * 5 | * 6 | * Doctest is almost a drop-in replacement for Catch. 7 | * Though Catch has a few more features, and works without any restrictions, 8 | * doctest has much faster compilation times, our 44 unittests build 9 | * takes about 13 minutes to build with catch, or about 3.5 minutes when 10 | * using doctest. 11 | * 12 | */ 13 | #if !defined(USE_CATCH) && !defined(USE_DOCTEST) 14 | #define USE_DOCTEST 15 | #endif 16 | 17 | #ifdef USE_CATCH 18 | #ifdef UNITTESTMAIN 19 | #define CATCH_CONFIG_MAIN 20 | #endif 21 | //#define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER 22 | #define CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS 23 | #if __has_include() 24 | #include 25 | #elif __has_include("single_include/catch.hpp") 26 | #include "single_include/catch.hpp" 27 | #elif __has_include("contrib/catch.hpp") 28 | #include "contrib/catch.hpp" 29 | #else 30 | #error "Could not find catch.hpp" 31 | #endif 32 | 33 | #define SKIPTEST , "[!hide]" 34 | 35 | // doctest has suites, catch doesn't. 36 | #define TEST_SUITE(x) namespace 37 | 38 | #elif defined(USE_DOCTEST) 39 | #ifdef UNITTESTMAIN 40 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 41 | #endif 42 | #include 43 | #if __has_include() 44 | #include 45 | #elif __has_include("single_include/doctest.h") 46 | #include "single_include/doctest.h" 47 | #elif __has_include("contrib/doctest.h") 48 | #include "contrib/doctest.h" 49 | #else 50 | #error "Could not find doctest.h" 51 | #endif 52 | #define SECTION(...) SUBCASE(__VA_ARGS__) 53 | #define SKIPTEST * doctest::skip(true) 54 | 55 | #define CHECK_THAT(a, b) 56 | #else 57 | #error define either USE_CATCH or USE_DOCTEST 58 | #endif 59 | 60 | #define IN_UNITTEST 1 61 | -------------------------------------------------------------------------------- /tests/unittests.cpp: -------------------------------------------------------------------------------- 1 | #define UNITTESTMAIN 2 | #include "unittestframework.h" 3 | 4 | // including all here again, so we will catch linking errors. 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | --------------------------------------------------------------------------------