#ifndef _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ #define _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ // // Rapid YAML - a library to parse and emit YAML, and do it fast. // // https://github.com/biojppm/rapidyaml // // DO NOT EDIT. This file is generated automatically. // This is an amalgamated single-header version of the library. // // INSTRUCTIONS: // // - Include at will in any header of your project. Because the // amalgamated header file is large, to speed up compilation of // your project, protect the include with its include guard // `_RYML_SINGLE_HEADER_AMALGAMATED_HPP_`, ie like this: // ``` // #ifndef _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ // #include // #endif // ``` // // - In one (and only one) of your project source files, #define // RYML_SINGLE_HDR_DEFINE_NOW and then include this header. This will enable // the function and class definitions in the header file. // // - To compile into a shared library, define the preprocessor symbol // RYML_SHARED before including the header. This will take care of // symbol export/import. // // //******************************************************************************** //-------------------------------------------------------------------------------- // LICENSE.txt // https://github.com/biojppm/rapidyaml/LICENSE.txt //-------------------------------------------------------------------------------- //******************************************************************************** // Copyright (c) 2018, Joao Paulo Magalhaes // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // // shared library: export when defining #if defined(RYML_SHARED) && defined(RYML_SINGLE_HDR_DEFINE_NOW) && !defined(RYML_EXPORTS) #define RYML_EXPORTS #endif // propagate defines to c4core #if defined(RYML_SINGLE_HDR_DEFINE_NOW) && !defined(C4CORE_SINGLE_HDR_DEFINE_NOW) #define C4CORE_SINGLE_HDR_DEFINE_NOW #endif #if defined(RYML_EXPORTS) && !defined(C4CORE_EXPORTS) #define C4CORE_EXPORTS #endif #if defined(RYML_SHARED) && !defined(C4CORE_SHARED) #define C4CORE_SHARED #endif // workaround for include removal while amalgamating // resulting in missing in arm-none-eabi-g++ // https://github.com/biojppm/rapidyaml/issues/193 #include //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/c4core_all.hpp // https://github.com/biojppm/rapidyaml/src/c4/c4core_all.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4CORE_SINGLE_HEADER_AMALGAMATED_HPP_ #define _C4CORE_SINGLE_HEADER_AMALGAMATED_HPP_ // // c4core - C++ utilities // // https://github.com/biojppm/c4core // // DO NOT EDIT. This file is generated automatically. // This is an amalgamated single-header version of the library. // // INSTRUCTIONS: // - Include at will in any header of your project // - In one (and only one) of your project source files, // #define C4CORE_SINGLE_HDR_DEFINE_NOW and then include this header. // This will enable the function and class definitions in // the header file. // - To compile into a shared library, just define the // preprocessor symbol C4CORE_SHARED . This will take // care of symbol export/import. // //******************************************************************************** //-------------------------------------------------------------------------------- // LICENSE.txt // https://github.com/biojppm/c4core/LICENSE.txt //-------------------------------------------------------------------------------- //******************************************************************************** // Copyright (c) 2018, Joao Paulo Magalhaes // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // // shared library: export when defining #if defined(C4CORE_SHARED) && defined(C4CORE_SINGLE_HDR_DEFINE_NOW) && !defined(C4CORE_EXPORTS) #define C4CORE_EXPORTS #endif //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/export.hpp // https://github.com/biojppm/c4core/src/c4/export.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef C4_EXPORT_HPP_ #define C4_EXPORT_HPP_ #ifdef _WIN32 #ifdef C4CORE_SHARED #ifdef C4CORE_EXPORTS #define C4CORE_EXPORT __declspec(dllexport) #else #define C4CORE_EXPORT __declspec(dllimport) #endif #else #define C4CORE_EXPORT #endif #else #define C4CORE_EXPORT #endif #endif /* C4CORE_EXPORT_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/export.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/version.hpp // https://github.com/biojppm/c4core/src/c4/version.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_VERSION_HPP_ #define _C4_VERSION_HPP_ /** @file version.hpp */ #define C4CORE_VERSION "0.2.6" #define C4CORE_VERSION_MAJOR 0 #define C4CORE_VERSION_MINOR 2 #define C4CORE_VERSION_PATCH 6 // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/export.hpp //#include #if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) #error "amalgamate: file c4/export.hpp must have been included at this point" #endif /* C4_EXPORT_HPP_ */ namespace c4 { C4CORE_EXPORT const char* version(); C4CORE_EXPORT int version_major(); C4CORE_EXPORT int version_minor(); C4CORE_EXPORT int version_patch(); } // namespace c4 #endif /* _C4_VERSION_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/version.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/preprocessor.hpp // https://github.com/biojppm/c4core/src/c4/preprocessor.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_PREPROCESSOR_HPP_ #define _C4_PREPROCESSOR_HPP_ /** @file preprocessor.hpp Contains basic macros and preprocessor utilities. * @ingroup basic_headers */ #ifdef __clang__ /* NOTE: using , ## __VA_ARGS__ to deal with zero-args calls to * variadic macros is not portable, but works in clang, gcc, msvc, icc. * clang requires switching off compiler warnings for pedantic mode. * @see http://stackoverflow.com/questions/32047685/variadic-macro-without-arguments */ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" // warning: token pasting of ',' and __VA_ARGS__ is a GNU extension #elif defined(__GNUC__) /* GCC also issues a warning for zero-args calls to variadic macros. * This warning is switched on with -pedantic and apparently there is no * easy way to turn it off as with clang. But marking this as a system * header works. * @see https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html * @see http://stackoverflow.com/questions/35587137/ */ # pragma GCC system_header #endif #define C4_WIDEN(str) L"" str #define C4_COUNTOF(arr) (sizeof(arr)/sizeof((arr)[0])) #define C4_EXPAND(arg) arg /** useful in some macro calls with template arguments */ #define C4_COMMA , /** useful in some macro calls with template arguments * @see C4_COMMA */ #define C4_COMMA_X C4_COMMA /** expand and quote */ #define C4_XQUOTE(arg) _C4_XQUOTE(arg) #define _C4_XQUOTE(arg) C4_QUOTE(arg) #define C4_QUOTE(arg) #arg /** expand and concatenate */ #define C4_XCAT(arg1, arg2) _C4_XCAT(arg1, arg2) #define _C4_XCAT(arg1, arg2) C4_CAT(arg1, arg2) #define C4_CAT(arg1, arg2) arg1##arg2 #define C4_VERSION_CAT(major, minor, patch) ((major)*10000 + (minor)*100 + (patch)) /** A preprocessor foreach. Spectacular trick taken from: * http://stackoverflow.com/a/1872506/5875572 * The first argument is for a macro receiving a single argument, * which will be called with every subsequent argument. There is * currently a limit of 32 arguments, and at least 1 must be provided. * Example: @code{.cpp} struct Example { int a; int b; int c; }; // define a one-arg macro to be called #define PRN_STRUCT_OFFSETS(field) PRN_STRUCT_OFFSETS_(Example, field) #define PRN_STRUCT_OFFSETS_(structure, field) printf(C4_XQUOTE(structure) ":" C4_XQUOTE(field)" - offset=%zu\n", offsetof(structure, field)); // now call the macro for a, b and c C4_FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c); @endcode */ #define C4_FOR_EACH(what, ...) C4_FOR_EACH_SEP(what, ;, __VA_ARGS__) /** same as C4_FOR_EACH(), but use a custom separator between statements. * If a comma is needed as the separator, use the C4_COMMA macro. * @see C4_FOR_EACH * @see C4_COMMA */ #define C4_FOR_EACH_SEP(what, sep, ...) _C4_FOR_EACH_(_C4_FOR_EACH_NARG(__VA_ARGS__), what, sep, __VA_ARGS__) /// @cond dev #define _C4_FOR_EACH_01(what, sep, x) what(x) sep #define _C4_FOR_EACH_02(what, sep, x, ...) what(x) sep _C4_FOR_EACH_01(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_03(what, sep, x, ...) what(x) sep _C4_FOR_EACH_02(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_04(what, sep, x, ...) what(x) sep _C4_FOR_EACH_03(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_05(what, sep, x, ...) what(x) sep _C4_FOR_EACH_04(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_06(what, sep, x, ...) what(x) sep _C4_FOR_EACH_05(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_07(what, sep, x, ...) what(x) sep _C4_FOR_EACH_06(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_08(what, sep, x, ...) what(x) sep _C4_FOR_EACH_07(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_09(what, sep, x, ...) what(x) sep _C4_FOR_EACH_08(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_10(what, sep, x, ...) what(x) sep _C4_FOR_EACH_09(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_11(what, sep, x, ...) what(x) sep _C4_FOR_EACH_10(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_12(what, sep, x, ...) what(x) sep _C4_FOR_EACH_11(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_13(what, sep, x, ...) what(x) sep _C4_FOR_EACH_12(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_14(what, sep, x, ...) what(x) sep _C4_FOR_EACH_13(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_15(what, sep, x, ...) what(x) sep _C4_FOR_EACH_14(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_16(what, sep, x, ...) what(x) sep _C4_FOR_EACH_15(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_17(what, sep, x, ...) what(x) sep _C4_FOR_EACH_16(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_18(what, sep, x, ...) what(x) sep _C4_FOR_EACH_17(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_19(what, sep, x, ...) what(x) sep _C4_FOR_EACH_18(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_20(what, sep, x, ...) what(x) sep _C4_FOR_EACH_19(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_21(what, sep, x, ...) what(x) sep _C4_FOR_EACH_20(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_22(what, sep, x, ...) what(x) sep _C4_FOR_EACH_21(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_23(what, sep, x, ...) what(x) sep _C4_FOR_EACH_22(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_24(what, sep, x, ...) what(x) sep _C4_FOR_EACH_23(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_25(what, sep, x, ...) what(x) sep _C4_FOR_EACH_24(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_26(what, sep, x, ...) what(x) sep _C4_FOR_EACH_25(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_27(what, sep, x, ...) what(x) sep _C4_FOR_EACH_26(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_28(what, sep, x, ...) what(x) sep _C4_FOR_EACH_27(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_29(what, sep, x, ...) what(x) sep _C4_FOR_EACH_28(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_30(what, sep, x, ...) what(x) sep _C4_FOR_EACH_29(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_31(what, sep, x, ...) what(x) sep _C4_FOR_EACH_30(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_32(what, sep, x, ...) what(x) sep _C4_FOR_EACH_31(what, sep, __VA_ARGS__) #define _C4_FOR_EACH_NARG(...) _C4_FOR_EACH_NARG_(__VA_ARGS__, _C4_FOR_EACH_RSEQ_N()) #define _C4_FOR_EACH_NARG_(...) _C4_FOR_EACH_ARG_N(__VA_ARGS__) #define _C4_FOR_EACH_ARG_N(_01, _02, _03, _04, _05, _06, _07, _08, _09, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, N, ...) N #define _C4_FOR_EACH_RSEQ_N() 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01 #define _C4_FOR_EACH_(N, what, sep, ...) C4_XCAT(_C4_FOR_EACH_, N)(what, sep, __VA_ARGS__) /// @endcond #ifdef __clang__ # pragma clang diagnostic pop #endif #endif /* _C4_PREPROCESSOR_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/preprocessor.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/platform.hpp // https://github.com/biojppm/c4core/src/c4/platform.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_PLATFORM_HPP_ #define _C4_PLATFORM_HPP_ /** @file platform.hpp Provides platform information macros * @ingroup basic_headers */ // see also https://sourceforge.net/p/predef/wiki/OperatingSystems/ #if defined(_WIN64) # define C4_WIN # define C4_WIN64 #elif defined(_WIN32) # define C4_WIN # define C4_WIN32 #elif defined(__ANDROID__) # define C4_ANDROID #elif defined(__APPLE__) # include "TargetConditionals.h" # if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR # define C4_IOS # elif TARGET_OS_MAC || TARGET_OS_OSX # define C4_MACOS # else # error "Unknown Apple platform" # endif #elif defined(__linux__) || defined(__linux) # define C4_UNIX # define C4_LINUX #elif defined(__unix__) || defined(__unix) # define C4_UNIX #elif defined(__arm__) || defined(__aarch64__) # define C4_ARM #elif defined(__xtensa__) || defined(__XTENSA__) # define C4_XTENSA #elif defined(SWIG) # define C4_SWIG #else # error "unknown platform" #endif #if defined(__posix) || defined(C4_UNIX) || defined(C4_LINUX) # define C4_POSIX #endif #endif /* _C4_PLATFORM_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/platform.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/cpu.hpp // https://github.com/biojppm/c4core/src/c4/cpu.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_CPU_HPP_ #define _C4_CPU_HPP_ /** @file cpu.hpp Provides processor information macros * @ingroup basic_headers */ // see also https://sourceforge.net/p/predef/wiki/Architectures/ // see also https://sourceforge.net/p/predef/wiki/Endianness/ // see also https://github.com/googlesamples/android-ndk/blob/android-mk/hello-jni/jni/hello-jni.c // see also http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qprocessordetection.h #ifdef __ORDER_LITTLE_ENDIAN__ # define _C4EL __ORDER_LITTLE_ENDIAN__ #else # define _C4EL 1234 #endif #ifdef __ORDER_BIG_ENDIAN__ # define _C4EB __ORDER_BIG_ENDIAN__ #else # define _C4EB 4321 #endif // mixed byte order (eg, PowerPC or ia64) #define _C4EM 1111 // NOLINT // NOTE: to find defined macros in a platform, // g++ -dM -E - = 8) \ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 8) # define C4_CPU_ARMV8 # elif defined(__ARM_ARCH_7__) || defined(_ARM_ARCH_7) \ || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) \ || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) \ || defined(__ARM_ARCH_7EM__) \ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 7) \ || (defined(_M_ARM) && _M_ARM >= 7) # define C4_CPU_ARMV7 # elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) \ || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6ZK__) \ || defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_6KZ__) \ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 6) # define C4_CPU_ARMV6 # elif (defined(__ARM_ARCH) && __ARM_ARCH == 5) \ || defined(__ARM_ARCH_5TEJ__) \ || defined(__ARM_ARCH_5TE__) \ || defined(__ARM_ARCH_5T__) \ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 5) # define C4_CPU_ARMV5 # elif (defined(__ARM_ARCH) && __ARM_ARCH == 4) \ || defined(__ARM_ARCH_4T__) \ || defined(__ARM_ARCH_4__) \ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 4) # define C4_CPU_ARMV4 # else # error "unknown CPU architecture: ARM" # endif # endif # if defined(__ARMEL__) || defined(__LITTLE_ENDIAN__) || defined(__AARCH64EL__) \ || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) \ || defined(_MSC_VER) // winarm64 does not provide any of the above macros, // but advises little-endianess: // https://docs.microsoft.com/en-us/cpp/build/overview-of-arm-abi-conventions?view=msvc-170 // So if it is visual studio compiling, we'll assume little endian. # define C4_BYTE_ORDER _C4EL # elif defined(__ARMEB__) || defined(__BIG_ENDIAN__) || defined(__AARCH64EB__) \ || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) # define C4_BYTE_ORDER _C4EB # elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_PDP_ENDIAN__) # define C4_BYTE_ORDER _C4EM # else # error "unknown endianness" # endif #elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) # define C4_CPU_IA64 # define C4_WORDSIZE 8 # define C4_BYTE_ORDER _C4EM // itanium is bi-endian - check byte order below #elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \ || defined(_M_MPPC) || defined(_M_PPC) # if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) # define C4_CPU_PPC64 # define C4_WORDSIZE 8 # else # define C4_CPU_PPC # define C4_WORDSIZE 4 # endif # define C4_BYTE_ORDER _C4EM // ppc is bi-endian - check byte order below #elif defined(__s390x__) || defined(__zarch__) || defined(__SYSC_ZARCH_) # define C4_CPU_S390_X # define C4_WORDSIZE 8 # define C4_BYTE_ORDER _C4EB #elif defined(__xtensa__) || defined(__XTENSA__) # define C4_CPU_XTENSA # define C4_WORDSIZE 4 // not sure about this... # if defined(__XTENSA_EL__) || defined(__xtensa_el__) # define C4_BYTE_ORDER _C4EL # else # define C4_BYTE_ORDER _C4EB # endif #elif defined(__riscv) # if __riscv_xlen == 64 # define C4_CPU_RISCV64 # define C4_WORDSIZE 8 # else # define C4_CPU_RISCV32 # define C4_WORDSIZE 4 # endif # define C4_BYTE_ORDER _C4EL #elif defined(__EMSCRIPTEN__) # define C4_BYTE_ORDER _C4EL # define C4_WORDSIZE 4 #elif defined(__loongarch__) # if defined(__loongarch64) # define C4_CPU_LOONGARCH64 # define C4_WORDSIZE 8 # else # define C4_CPU_LOONGARCH # define C4_WORDSIZE 4 # endif # define C4_BYTE_ORDER _C4EL #elif defined(__mips__) || defined(_mips) || defined(mips) # if defined(__mips) # if __mips == 64 # define C4_CPU_MIPS64 # define C4_WORDSIZE 8 # elif __mips == 32 # define C4_CPU_MIPS32 # define C4_WORDSIZE 4 # endif # elif defined(__arch64__) || (defined(__SIZE_WIDTH__) && __SIZE_WIDTH__ == 64) || (defined(__LP64__) && __LP64__) # define C4_CPU_MIPS64 # define C4_WORDSIZE 8 # elif defined(__arch32__) || (defined(__SIZE_WIDTH__) && __SIZE_WIDTH__ == 32) || (defined(__LP32__) && __LP32__) # define C4_CPU_MIPS32 # define C4_WORDSIZE 4 # else # error "unknown mips architecture" # endif # if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ # define C4_BYTE_ORDER _C4EB # elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define C4_BYTE_ORDER _C4EL # else # error "unknown mips endianness" # endif #elif defined(__sparc__) || defined(__sparc) || defined(sparc) # if defined(__arch64__) || (defined(__SIZE_WIDTH__) && __SIZE_WIDTH__ == 64) || (defined(__LP64__) && __LP64__) # define C4_CPU_SPARC64 # define C4_WORDSIZE 8 # elif defined(__arch32__) || (defined(__SIZE_WIDTH__) && __SIZE_WIDTH__ == 32) || (defined(__LP32__) && __LP32__) # define C4_CPU_SPARC32 # define C4_WORDSIZE 4 # else # error "unknown sparc architecture" # endif # define C4_BYTE_ORDER _C4EB #elif defined(SWIG) # error "please define CPU architecture macros when compiling with swig" #else # error "unknown CPU architecture" #endif #define C4_LITTLE_ENDIAN (C4_BYTE_ORDER == _C4EL) #define C4_BIG_ENDIAN (C4_BYTE_ORDER == _C4EB) #define C4_MIXED_ENDIAN (C4_BYTE_ORDER == _C4EM) #endif /* _C4_CPU_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/cpu.hpp) // (amalgamate) these includes are needed to work around // conditional includes in the gcc4.8 shim #include #include #include //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/gcc-4.8.hpp // https://github.com/biojppm/c4core/src/c4/gcc-4.8.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_GCC_4_8_HPP_ #define _C4_GCC_4_8_HPP_ #if __GNUC__ == 4 && __GNUC_MINOR__ >= 8 /* STL polyfills for old GNU compilers */ _Pragma("GCC diagnostic ignored \"-Wshadow\"") _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") #if __cplusplus //included above: //#include //included above: //#include namespace std { template struct is_trivially_copyable : public integral_constant::value && __has_trivial_destructor(_Tp) && (__has_trivial_constructor(_Tp) || __has_trivial_copy(_Tp) || __has_trivial_assign(_Tp))> { }; template using is_trivially_copy_constructible = has_trivial_copy_constructor<_Tp>; template using is_trivially_default_constructible = has_trivial_default_constructor<_Tp>; template using is_trivially_copy_assignable = has_trivial_copy_assign<_Tp>; /* not supported */ template struct is_trivially_move_constructible : false_type { }; /* not supported */ template struct is_trivially_move_assignable : false_type { }; inline void *align(size_t __align, size_t __size, void*& __ptr, size_t& __space) noexcept { if (__space < __size) return nullptr; const auto __intptr = reinterpret_cast(__ptr); const auto __aligned = (__intptr - 1u + __align) & -__align; const auto __diff = __aligned - __intptr; if (__diff > (__space - __size)) return nullptr; else { __space -= __diff; return __ptr = reinterpret_cast(__aligned); } } #if __GNUC__ == 4 && __GNUC_MINOR__ == 8 typedef long double max_align_t ; #endif } #else // __cplusplus //included above: //#include // see https://sourceware.org/bugzilla/show_bug.cgi?id=25399 (ubuntu gcc-4.8) #define memset(s, c, count) __builtin_memset(s, c, count) #endif // __cplusplus #endif // __GNUC__ == 4 && __GNUC_MINOR__ >= 8 #endif // _C4_GCC_4_8_HPP_ // (end https://github.com/biojppm/c4core/src/c4/gcc-4.8.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/compiler.hpp // https://github.com/biojppm/c4core/src/c4/compiler.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_COMPILER_HPP_ #define _C4_COMPILER_HPP_ /** @file compiler.hpp Provides compiler information macros * @ingroup basic_headers */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/platform.hpp //#include "c4/platform.hpp" #if !defined(C4_PLATFORM_HPP_) && !defined(_C4_PLATFORM_HPP_) #error "amalgamate: file c4/platform.hpp must have been included at this point" #endif /* C4_PLATFORM_HPP_ */ // Compilers: // C4_MSVC // Visual Studio 2022: MSVC++ 17, 1930 // Visual Studio 2019: MSVC++ 16, 1920 // Visual Studio 2017: MSVC++ 15 // Visual Studio 2015: MSVC++ 14 // Visual Studio 2013: MSVC++ 13 // Visual Studio 2013: MSVC++ 12 // Visual Studio 2012: MSVC++ 11 // Visual Studio 2010: MSVC++ 10 // Visual Studio 2008: MSVC++ 09 // Visual Studio 2005: MSVC++ 08 // C4_CLANG // C4_GCC // C4_ICC (intel compiler) /** @see http://sourceforge.net/p/predef/wiki/Compilers/ for a list of compiler identifier macros */ /** @see https://msdn.microsoft.com/en-us/library/b0084kay.aspx for VS2013 predefined macros */ #if defined(_MSC_VER) && !defined(__clang__) # define C4_MSVC # define C4_MSVC_VERSION_2022 17 # define C4_MSVC_VERSION_2019 16 # define C4_MSVC_VERSION_2017 15 # define C4_MSVC_VERSION_2015 14 # define C4_MSVC_VERSION_2013 12 # define C4_MSVC_VERSION_2012 11 # if _MSC_VER >= 1930 # define C4_MSVC_VERSION C4_MSVC_VERSION_2022 // visual studio 2022 # define C4_MSVC_2022 # elif _MSC_VER >= 1920 # define C4_MSVC_VERSION C4_MSVC_VERSION_2019 // visual studio 2019 # define C4_MSVC_2019 # elif _MSC_VER >= 1910 # define C4_MSVC_VERSION C4_MSVC_VERSION_2017 // visual studio 2017 # define C4_MSVC_2017 # elif _MSC_VER == 1900 # define C4_MSVC_VERSION C4_MSVC_VERSION_2015 // visual studio 2015 # define C4_MSVC_2015 # elif _MSC_VER == 1800 # error "MSVC version not supported" # define C4_MSVC_VERSION C4_MSVC_VERSION_2013 // visual studio 2013 # define C4_MSVC_2013 # elif _MSC_VER == 1700 # error "MSVC version not supported" # define C4_MSVC_VERSION C4_MSVC_VERSION_2012 // visual studio 2012 # define C4_MSVC_2012 # elif _MSC_VER == 1600 # error "MSVC version not supported" # define C4_MSVC_VERSION 10 // visual studio 2010 # define C4_MSVC_2010 # elif _MSC_VER == 1500 # error "MSVC version not supported" # define C4_MSVC_VERSION 09 // visual studio 2008 # define C4_MSVC_2008 # elif _MSC_VER == 1400 # error "MSVC version not supported" # define C4_MSVC_VERSION 08 // visual studio 2005 # define C4_MSVC_2005 # else # error "MSVC version not supported" # endif // _MSC_VER #else # define C4_MSVC_VERSION 0 // visual studio not present # define C4_GCC_LIKE # ifdef __INTEL_COMPILER // check ICC before checking GCC, as ICC defines __GNUC__ too # define C4_ICC # define C4_ICC_VERSION __INTEL_COMPILER # elif defined(__APPLE_CC__) # define C4_XCODE # if defined(__clang__) # define C4_CLANG # ifndef __apple_build_version__ # define C4_CLANG_VERSION C4_VERSION_ENCODED(__clang_major__, __clang_minor__, __clang_patchlevel__) # else # define C4_CLANG_VERSION __apple_build_version__ # endif # else # define C4_XCODE_VERSION __APPLE_CC__ # endif # elif defined(__clang__) # define C4_CLANG # ifndef __apple_build_version__ # define C4_CLANG_VERSION C4_VERSION_ENCODED(__clang_major__, __clang_minor__, __clang_patchlevel__) # else # define C4_CLANG_VERSION __apple_build_version__ # endif # elif defined(__GNUC__) # ifdef __MINGW32__ # define C4_MINGW # endif # define C4_GCC # if defined(__GNUC_PATCHLEVEL__) # define C4_GCC_VERSION C4_VERSION_ENCODED(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) # else # define C4_GCC_VERSION C4_VERSION_ENCODED(__GNUC__, __GNUC_MINOR__, 0) # endif # if __GNUC__ < 5 # if __GNUC__ == 4 && __GNUC_MINOR__ >= 8 // provided by cmake sub-project // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/gcc-4.8.hpp //# include "c4/gcc-4.8.hpp" #if !defined(C4_GCC_4_8_HPP_) && !defined(_C4_GCC_4_8_HPP_) #error "amalgamate: file c4/gcc-4.8.hpp must have been included at this point" #endif /* C4_GCC_4_8_HPP_ */ # else // we do not support GCC < 4.8: // * misses std::is_trivially_copyable // * misses std::align // * -Wshadow has false positives when a local function parameter has the same name as a method # error "GCC < 4.8 is not supported" # endif # endif # endif #endif // defined(C4_WIN) && defined(_MSC_VER) #endif /* _C4_COMPILER_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/compiler.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/language.hpp // https://github.com/biojppm/c4core/src/c4/language.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_LANGUAGE_HPP_ #define _C4_LANGUAGE_HPP_ /** @file language.hpp Provides language standard information macros and * compiler agnostic utility macros: namespace facilities, function attributes, * variable attributes, etc. * @ingroup basic_headers */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/preprocessor.hpp //#include "c4/preprocessor.hpp" #if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) #error "amalgamate: file c4/preprocessor.hpp must have been included at this point" #endif /* C4_PREPROCESSOR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/compiler.hpp //#include "c4/compiler.hpp" #if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) #error "amalgamate: file c4/compiler.hpp must have been included at this point" #endif /* C4_COMPILER_HPP_ */ /* Detect C++ standard. * @see http://stackoverflow.com/a/7132549/5875572 */ #ifndef C4_CPP # if defined(_MSC_VER) && !defined(__clang__) # if _MSC_VER >= 1910 // >VS2015: VS2017, VS2019, VS2022 # if (!defined(_MSVC_LANG)) # error _MSVC not defined # endif # if _MSVC_LANG >= 201705L # define C4_CPP 20 # define C4_CPP20 # elif _MSVC_LANG == 201703L # define C4_CPP 17 # define C4_CPP17 # elif _MSVC_LANG >= 201402L # define C4_CPP 14 # define C4_CPP14 # elif _MSVC_LANG >= 201103L # define C4_CPP 11 # define C4_CPP11 # else # error C++ lesser than C++11 not supported # endif # else # if _MSC_VER == 1900 # define C4_CPP 14 // VS2015 is c++14 https://devblogs.microsoft.com/cppblog/c111417-features-in-vs-2015-rtm/ # define C4_CPP14 # elif _MSC_VER == 1800 // VS2013 # define C4_CPP 11 # define C4_CPP11 # else # error C++ lesser than C++11 not supported # endif # endif # elif defined(__INTEL_COMPILER) // https://software.intel.com/en-us/node/524490 # ifdef __INTEL_CXX20_MODE__ // not sure about this # define C4_CPP 20 # define C4_CPP20 # elif defined __INTEL_CXX17_MODE__ // not sure about this # define C4_CPP 17 # define C4_CPP17 # elif defined __INTEL_CXX14_MODE__ // not sure about this # define C4_CPP 14 # define C4_CPP14 # elif defined __INTEL_CXX11_MODE__ # define C4_CPP 11 # define C4_CPP11 # else # error C++ lesser than C++11 not supported # endif # else # ifndef __cplusplus # error __cplusplus is not defined? # endif # if __cplusplus == 1 # error cannot handle __cplusplus==1 # elif __cplusplus >= 201709L # define C4_CPP 20 # define C4_CPP20 # elif __cplusplus >= 201703L # define C4_CPP 17 # define C4_CPP17 # elif __cplusplus >= 201402L # define C4_CPP 14 # define C4_CPP14 # elif __cplusplus >= 201103L # define C4_CPP 11 # define C4_CPP11 # elif __cplusplus >= 199711L # error C++ lesser than C++11 not supported # endif # endif #else # ifdef C4_CPP == 20 # define C4_CPP20 # elif C4_CPP == 17 # define C4_CPP17 # elif C4_CPP == 14 # define C4_CPP14 # elif C4_CPP == 11 # define C4_CPP11 # elif C4_CPP == 98 # define C4_CPP98 # error C++ lesser than C++11 not supported # else # error C4_CPP must be one of 20, 17, 14, 11, 98 # endif #endif #ifdef C4_CPP20 # define C4_CPP17 # define C4_CPP14 # define C4_CPP11 #elif defined(C4_CPP17) # define C4_CPP14 # define C4_CPP11 #elif defined(C4_CPP14) # define C4_CPP11 #endif /** lifted from this answer: http://stackoverflow.com/a/20170989/5875572 */ #if defined(_MSC_VER) && !defined(__clang__) # if _MSC_VER < 1900 # define C4_CONSTEXPR11 # define C4_CONSTEXPR14 # elif _MSC_VER < 2000 # define C4_CONSTEXPR11 constexpr # define C4_CONSTEXPR14 # else # define C4_CONSTEXPR11 constexpr # define C4_CONSTEXPR14 constexpr # endif #else # if __cplusplus < 201103 # define C4_CONSTEXPR11 # define C4_CONSTEXPR14 # elif __cplusplus == 201103 # define C4_CONSTEXPR11 constexpr # define C4_CONSTEXPR14 # else # define C4_CONSTEXPR11 constexpr # define C4_CONSTEXPR14 constexpr # endif #endif // _MSC_VER #if C4_CPP < 17 #define C4_IF_CONSTEXPR #define C4_INLINE_CONSTEXPR constexpr #else #define C4_IF_CONSTEXPR constexpr #define C4_INLINE_CONSTEXPR inline constexpr #endif #if defined(_MSC_VER) && !defined(__clang__) # if (defined(_CPPUNWIND) && (_CPPUNWIND == 1)) # define C4_EXCEPTIONS # endif #else # if defined(__EXCEPTIONS) || defined(__cpp_exceptions) # define C4_EXCEPTIONS # endif #endif #ifdef C4_EXCEPTIONS # define C4_IF_EXCEPTIONS_(exc_code, setjmp_code) exc_code # define C4_IF_EXCEPTIONS(exc_code, setjmp_code) do { exc_code } while(0) #else # define C4_IF_EXCEPTIONS_(exc_code, setjmp_code) setjmp_code # define C4_IF_EXCEPTIONS(exc_code, setjmp_code) do { setjmp_code } while(0) #endif #if defined(_MSC_VER) && !defined(__clang__) # if defined(_CPPRTTI) # define C4_RTTI # endif #else # if defined(__GXX_RTTI) # define C4_RTTI # endif #endif #ifdef C4_RTTI # define C4_IF_RTTI_(code_rtti, code_no_rtti) code_rtti # define C4_IF_RTTI(code_rtti, code_no_rtti) do { code_rtti } while(0) #else # define C4_IF_RTTI_(code_rtti, code_no_rtti) code_no_rtti # define C4_IF_RTTI(code_rtti, code_no_rtti) do { code_no_rtti } while(0) #endif //------------------------------------------------------------ #define _C4_BEGIN_NAMESPACE(ns) namespace ns { #define _C4_END_NAMESPACE(ns) } // MSVC cant handle the C4_FOR_EACH macro... need to fix this //#define C4_BEGIN_NAMESPACE(...) C4_FOR_EACH_SEP(_C4_BEGIN_NAMESPACE, , __VA_ARGS__) //#define C4_END_NAMESPACE(...) C4_FOR_EACH_SEP(_C4_END_NAMESPACE, , __VA_ARGS__) #define C4_BEGIN_NAMESPACE(ns) namespace ns { #define C4_END_NAMESPACE(ns) } #define C4_BEGIN_HIDDEN_NAMESPACE namespace /*hidden*/ { #define C4_END_HIDDEN_NAMESPACE } /* namespace hidden */ //------------------------------------------------------------ #ifndef C4_API # if defined(_MSC_VER) && !defined(__clang__) # if defined(C4_EXPORT) # define C4_API __declspec(dllexport) # elif defined(C4_IMPORT) # define C4_API __declspec(dllimport) # else # define C4_API # endif # else # define C4_API # endif #endif #if defined(_MSC_VER) && !defined(__clang__) # define C4_RESTRICT __restrict # define C4_RESTRICT_FN __declspec(restrict) # define C4_NO_INLINE __declspec(noinline) # define C4_ALWAYS_INLINE inline __forceinline /** these are not available in VS AFAIK */ # define C4_CONST # define C4_PURE # define C4_FLATTEN # define C4_HOT /** @todo */ # define C4_COLD /** @todo */ # define C4_ASSUME(...) __assume(__VA_ARGS__) # define C4_EXPECT(x, y) x /** @todo */ # define C4_LIKELY(x) x # define C4_UNLIKELY(x) x # define C4_UNREACHABLE() _c4_msvc_unreachable() # define C4_ATTR_FORMAT(...) /** */ # define C4_NORETURN [[noreturn]] # if _MSC_VER >= 1700 // VS2012 # define C4_NODISCARD _Check_return_ # else # define C4_NODISCARD # endif [[noreturn]] __forceinline void _c4_msvc_unreachable() { __assume(false); } ///< https://stackoverflow.com/questions/60802864/emulating-gccs-builtin-unreachable-in-visual-studio # define C4_UNREACHABLE_AFTER_ERR() /* */ #else ///< @todo assuming gcc-like compiler. check it is actually so. /** for function attributes in GCC, * @see https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes */ /** for __builtin functions in GCC, * @see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html */ # define C4_RESTRICT __restrict__ # define C4_RESTRICT_FN __attribute__((restrict)) # define C4_NO_INLINE __attribute__((noinline)) # define C4_ALWAYS_INLINE inline __attribute__((always_inline)) # define C4_CONST __attribute__((const)) # define C4_PURE __attribute__((pure)) /** force inlining of every callee function */ # define C4_FLATTEN __atribute__((flatten)) /** mark a function as hot, ie as having a visible impact in CPU time * thus making it more likely to inline, etc * @see http://stackoverflow.com/questions/15028990/semantics-of-gcc-hot-attribute */ # define C4_HOT __attribute__((hot)) /** mark a function as cold, ie as NOT having a visible impact in CPU time * @see http://stackoverflow.com/questions/15028990/semantics-of-gcc-hot-attribute */ # define C4_COLD __attribute__((cold)) # define C4_EXPECT(x, y) __builtin_expect(x, y) ///< @see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html # define C4_LIKELY(x) __builtin_expect(x, 1) # define C4_UNLIKELY(x) __builtin_expect(x, 0) # define C4_UNREACHABLE() __builtin_unreachable() # define C4_ATTR_FORMAT(...) //__attribute__((format (__VA_ARGS__))) ///< @see https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes # define C4_NORETURN __attribute__((noreturn)) # define C4_NODISCARD __attribute__((warn_unused_result)) # define C4_UNREACHABLE_AFTER_ERR() C4_UNREACHABLE() // C4_ASSUME // see https://stackoverflow.com/questions/63493968/reproducing-clangs-builtin-assume-for-gcc // preferred option: C++ standard attribute # ifdef __has_cpp_attribute # if __has_cpp_attribute(assume) >= 202207L # define C4_ASSUME(...) [[assume(__VA_ARGS__)]] # endif # endif // first fallback: compiler intrinsics/attributes for assumptions # ifndef C4_ASSUME # if defined(__clang__) # define C4_ASSUME(...) __builtin_assume(__VA_ARGS__) # elif defined(__GNUC__) # if __GNUC__ >= 13 # define C4_ASSUME(...) __attribute__((__assume__(__VA_ARGS__))) # endif # endif # endif // second fallback: possibly evaluating uses of unreachable() // Set this to 1 if you want to allow assumptions to possibly evaluate. # ifndef C4_ASSUME_ALLOW_EVAL # define C4_ASSUME_ALLOW_EVAL 0 # endif # if !defined(C4_ASSUME) && (C4_ASSUME_ALLOW_EVAL) # define C4_ASSUME(...) do { if (!bool(__VA_ARGS__)) C4_UNREACHABLE(); ) while(0) # endif // last fallback: define macro as doing nothing # ifndef C4_ASSUME # define C4_ASSUME(...) # endif #endif #if C4_CPP >= 14 # define C4_DEPRECATED(msg) [[deprecated(msg)]] #else # if defined(_MSC_VER) # define C4_DEPRECATED(msg) __declspec(deprecated(msg)) # else // defined(__GNUC__) || defined(__clang__) # define C4_DEPRECATED(msg) __attribute__((deprecated(msg))) # endif #endif #ifdef _MSC_VER # define C4_FUNC __FUNCTION__ # define C4_PRETTY_FUNC __FUNCSIG__ #else /// @todo assuming gcc-like compiler. check it is actually so. # define C4_FUNC __FUNCTION__ # define C4_PRETTY_FUNC __PRETTY_FUNCTION__ #endif /** prevent compiler warnings about a specific var being unused */ #define C4_UNUSED(var) (void)var #if C4_CPP >= 17 #define C4_STATIC_ASSERT(cond) static_assert(cond) #else #define C4_STATIC_ASSERT(cond) static_assert((cond), #cond) #endif #define C4_STATIC_ASSERT_MSG(cond, msg) static_assert((cond), #cond ": " msg) /** @def C4_DONT_OPTIMIZE idea lifted from GoogleBenchmark. * @see https://github.com/google/benchmark/blob/master/include/benchmark/benchmark_api.h */ namespace c4 { namespace detail { #ifdef __GNUC__ # define C4_DONT_OPTIMIZE(var) c4::detail::dont_optimize(var) template< class T > C4_ALWAYS_INLINE void dont_optimize(T const& value) { asm volatile("" : : "g"(value) : "memory"); } // NOLINT #else # define C4_DONT_OPTIMIZE(var) c4::detail::use_char_pointer(reinterpret_cast< const char* >(&var)) void use_char_pointer(char const volatile*); #endif } // namespace detail } // namespace c4 /** @def C4_KEEP_EMPTY_LOOP prevent an empty loop from being optimized out. * @see http://stackoverflow.com/a/7084193/5875572 */ #if defined(_MSC_VER) && !defined(__clang__) # define C4_KEEP_EMPTY_LOOP { char c; C4_DONT_OPTIMIZE(c); } #else # define C4_KEEP_EMPTY_LOOP { asm(""); } #endif /** @def C4_VA_LIST_REUSE_MUST_COPY * @todo I strongly suspect that this is actually only in UNIX platforms. revisit this. */ #ifdef __GNUC__ # define C4_VA_LIST_REUSE_MUST_COPY #endif #endif /* _C4_LANGUAGE_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/language.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/types.hpp // https://github.com/biojppm/c4core/src/c4/types.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_TYPES_HPP_ #define _C4_TYPES_HPP_ //included above: //#include #include //included above: //#include #if __cplusplus >= 201103L #include // for integer_sequence and friends #endif // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/preprocessor.hpp //#include "c4/preprocessor.hpp" #if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) #error "amalgamate: file c4/preprocessor.hpp must have been included at this point" #endif /* C4_PREPROCESSOR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/language.hpp //#include "c4/language.hpp" #if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) #error "amalgamate: file c4/language.hpp must have been included at this point" #endif /* C4_LANGUAGE_HPP_ */ /** @file types.hpp basic types, and utility macros and traits for types. * @ingroup basic_headers */ /** @defgroup types Type utilities */ // NOLINTBEGIN(bugprone-macro-parentheses) namespace c4 { /** @defgroup intrinsic_types Intrinsic types * @ingroup types * @{ */ using cbyte = const char; /**< a constant byte */ using byte = char; /**< a mutable byte */ using i8 = int8_t; using i16 = int16_t; using i32 = int32_t; using i64 = int64_t; using u8 = uint8_t; using u16 = uint16_t; using u32 = uint32_t; using u64 = uint64_t; using f32 = float; using f64 = double; using ssize_t = typename std::make_signed::type; /** @} */ //-------------------------------------------------- /** @defgroup utility_types Utility types * @ingroup types * @{ */ // some tag types #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic push #if __GNUC__ >= 6 #pragma GCC diagnostic ignored "-Wunused-const-variable" #endif #endif /** a tag type for initializing the containers with variadic arguments a la * initializer_list, minus the initializer_list overload problems. */ struct aggregate_t {}; /** @see aggregate_t */ constexpr const aggregate_t aggregate{}; /** a tag type for specifying the initial capacity of allocatable contiguous storage */ struct with_capacity_t {}; /** @see with_capacity_t */ constexpr const with_capacity_t with_capacity{}; /** a tag type for disambiguating template parameter packs in variadic template overloads */ struct varargs_t {}; /** @see with_capacity_t */ constexpr const varargs_t varargs{}; #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic pop #endif //-------------------------------------------------- /** whether a value should be used in place of a const-reference in argument passing. */ template struct cref_uses_val { enum { value = ( std::is_scalar::value || ( #if C4_CPP >= 20 (std::is_trivially_copyable::value && std::is_standard_layout::value) #else std::is_pod::value #endif && sizeof(T) <= sizeof(size_t))) }; }; /** utility macro to override the default behaviour for c4::fastcref @see fastcref */ #define C4_CREF_USES_VAL(T) \ template<> \ struct cref_uses_val \ { \ enum { value = true }; \ }; /** Whether to use pass-by-value or pass-by-const-reference in a function argument * or return type. */ template using fastcref = typename std::conditional::value, T, T const&>::type; //-------------------------------------------------- /** Just what its name says. Useful sometimes as a default empty policy class. */ struct EmptyStruct // NOLINT { template EmptyStruct(T && ...){} // NOLINT }; /** Just what its name says. Useful sometimes as a default policy class to * be inherited from. */ struct EmptyStructVirtual // NOLINT { virtual ~EmptyStructVirtual() = default; template EmptyStructVirtual(T && ...){} // NOLINT }; /** */ template struct inheritfrom : public T {}; //-------------------------------------------------- // Utilities to make a class obey size restrictions (eg, min size or size multiple of). // DirectX usually makes this restriction with uniform buffers. // This is also useful for padding to prevent false-sharing. /** how many bytes must be added to size such that the result is at least minsize? */ C4_ALWAYS_INLINE constexpr size_t min_remainder(size_t size, size_t minsize) noexcept { return size < minsize ? minsize-size : 0; } /** how many bytes must be added to size such that the result is a multiple of multipleof? */ C4_ALWAYS_INLINE constexpr size_t mult_remainder(size_t size, size_t multipleof) noexcept { return (((size % multipleof) != 0) ? (multipleof-(size % multipleof)) : 0); } /* force the following class to be tightly packed. */ #pragma pack(push, 1) /** pad a class with more bytes at the end. * @see http://stackoverflow.com/questions/21092415/force-c-structure-to-pack-tightly */ template struct Padded : public T { using T::T; using T::operator=; Padded(T const& val) : T(val) {} Padded(T && val) : T(std::forward(val)) {} // NOLINT char ___c4padspace___[BytesToPadAtEnd]; }; #pragma pack(pop) /** When the padding argument is 0, we cannot declare the char[] array. */ template struct Padded : public T { using T::T; using T::operator=; Padded(T const& val) : T(val) {} Padded(T && val) : T(std::forward(val)) {} // NOLINT }; /** make T have a size which is at least Min bytes */ template using MinSized = Padded; /** make T have a size which is a multiple of Mult bytes */ template using MultSized = Padded; /** make T have a size which is simultaneously: * -bigger or equal than Min * -a multiple of Mult */ template using MinMultSized = MultSized, Mult>; /** make T be suitable for use as a uniform buffer. (at least with DirectX). */ template using UbufSized = MinMultSized; //----------------------------------------------------------------------------- #define C4_NO_COPY_CTOR(ty) ty(ty const&) = delete #define C4_NO_MOVE_CTOR(ty) ty(ty &&) = delete #define C4_NO_COPY_ASSIGN(ty) ty& operator=(ty const&) = delete #define C4_NO_MOVE_ASSIGN(ty) ty& operator=(ty &&) = delete #define C4_DEFAULT_COPY_CTOR(ty) ty(ty const&) noexcept = default #define C4_DEFAULT_MOVE_CTOR(ty) ty(ty &&) noexcept = default #define C4_DEFAULT_COPY_ASSIGN(ty) ty& operator=(ty const&) noexcept = default #define C4_DEFAULT_MOVE_ASSIGN(ty) ty& operator=(ty &&) noexcept = default #define C4_NO_COPY_OR_MOVE_CTOR(ty) \ C4_NO_COPY_CTOR(ty); \ C4_NO_MOVE_CTOR(ty) #define C4_NO_COPY_OR_MOVE_ASSIGN(ty) \ C4_NO_COPY_ASSIGN(ty); \ C4_NO_MOVE_ASSIGN(ty) #define C4_NO_COPY_OR_MOVE(ty) \ C4_NO_COPY_OR_MOVE_CTOR(ty); \ C4_NO_COPY_OR_MOVE_ASSIGN(ty) #define C4_DEFAULT_COPY_AND_MOVE_CTOR(ty) \ C4_DEFAULT_COPY_CTOR(ty); \ C4_DEFAULT_MOVE_CTOR(ty) #define C4_DEFAULT_COPY_AND_MOVE_ASSIGN(ty) \ C4_DEFAULT_COPY_ASSIGN(ty); \ C4_DEFAULT_MOVE_ASSIGN(ty) #define C4_DEFAULT_COPY_AND_MOVE(ty) \ C4_DEFAULT_COPY_AND_MOVE_CTOR(ty); \ C4_DEFAULT_COPY_AND_MOVE_ASSIGN(ty) /** @see https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable */ #define C4_MUST_BE_TRIVIAL_COPY(ty) \ static_assert(std::is_trivially_copyable::value, #ty " must be trivially copyable") /** @} */ //----------------------------------------------------------------------------- /** @defgroup traits_types Type traits utilities * @ingroup types * @{ */ // http://stackoverflow.com/questions/10821380/is-t-an-instance-of-a-template-in-c template class X, typename T> struct is_instance_of_tpl : std::false_type {}; template class X, typename... Y> struct is_instance_of_tpl> : std::true_type {}; //----------------------------------------------------------------------------- /** SFINAE. use this macro to enable a template function overload based on a compile-time condition. @code // define an overload for a non-pod type template::value)> void foo() { std::cout << "pod type\n"; } // define an overload for a non-pod type template::value)> void foo() { std::cout << "nonpod type\n"; } struct non_pod { non_pod() : name("asdfkjhasdkjh") {} const char *name; }; int main() { foo(); // prints "pod type" foo(); // prints "nonpod type" } @endcode */ #define C4_REQUIRE_T(cond) typename std::enable_if::type* = nullptr /** enable_if for a return type * @see C4_REQUIRE_T */ #define C4_REQUIRE_R(cond, type_) typename std::enable_if::type //----------------------------------------------------------------------------- /** define a traits class reporting whether a type provides a member typedef */ #define C4_DEFINE_HAS_TYPEDEF(member_typedef) \ template \ struct has_##stype \ { \ private: \ \ typedef char yes; \ typedef struct { char array[2]; } no; \ \ template \ static yes _test(typename C::member_typedef*); \ \ template \ static no _test(...); \ \ public: \ \ enum { value = (sizeof(_test(0)) == sizeof(yes)) }; \ \ } /** @} */ //----------------------------------------------------------------------------- /** @defgroup type_declarations Type declaration utilities * @ingroup types * @{ */ #define _c4_DEFINE_ARRAY_TYPES_WITHOUT_ITERATOR(T, I) \ \ using size_type = I; \ using ssize_type = typename std::make_signed::type; \ using difference_type = typename std::make_signed::type; \ \ using value_type = T; \ using pointer = T*; \ using const_pointer = T const*; \ using reference = T&; \ using const_reference = T const& #define _c4_DEFINE_TUPLE_ARRAY_TYPES_WITHOUT_ITERATOR(interior_types, I) \ \ using size_type = I; \ using ssize_type = typename std::make_signed::type; \ using difference_type = typename std::make_signed::type; \ \ template using value_type = typename std::tuple_element< n, std::tuple>::type; \ template using pointer = value_type*; \ template using const_pointer = value_type const*; \ template using reference = value_type&; \ template using const_reference = value_type const& #define _c4_DEFINE_ARRAY_TYPES(T, I) \ \ _c4_DEFINE_ARRAY_TYPES_WITHOUT_ITERATOR(T, I); \ \ using iterator = T*; \ using const_iterator = T const*; \ using reverse_iterator = std::reverse_iterator; \ using const_reverse_iterator = std::reverse_iterator #define _c4_DEFINE_TUPLE_ARRAY_TYPES(interior_types, I) \ \ _c4_DEFINE_TUPLE_ARRAY_TYPES_WITHOUT_ITERATOR(interior_types, I); \ \ template using iterator = value_type*; \ template using const_iterator = value_type const*; \ template using reverse_iterator = std::reverse_iterator< value_type*>; \ template using const_reverse_iterator = std::reverse_iterator< value_type const*> /** @} */ //----------------------------------------------------------------------------- /** @defgroup compatility_utilities Backport implementation of some Modern C++ utilities * @ingroup types * @{ */ //----------------------------------------------------------------------------- // index_sequence and friends are available only for C++14 and later. // A C++11 implementation is provided here. // This implementation was copied over from clang. // see http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 #if __cplusplus > 201103L using std::integer_sequence; using std::index_sequence; using std::make_integer_sequence; using std::make_index_sequence; using std::index_sequence_for; #else /** C++11 implementation of integer sequence * @see https://en.cppreference.com/w/cpp/utility/integer_sequence * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ template struct integer_sequence { static_assert(std::is_integral<_Tp>::value, "std::integer_sequence can only be instantiated with an integral type" ); using value_type = _Tp; static constexpr size_t size() noexcept { return sizeof...(_Ip); } }; /** C++11 implementation of index sequence * @see https://en.cppreference.com/w/cpp/utility/integer_sequence * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ template using index_sequence = integer_sequence; /** @cond DONT_DOCUMENT_THIS */ namespace __detail { template struct __repeat; template struct __repeat, _Extra...> { using type = integer_sequence<_Tp, _Np..., sizeof...(_Np) + _Np..., 2 * sizeof...(_Np) + _Np..., 3 * sizeof...(_Np) + _Np..., 4 * sizeof...(_Np) + _Np..., 5 * sizeof...(_Np) + _Np..., 6 * sizeof...(_Np) + _Np..., 7 * sizeof...(_Np) + _Np..., _Extra...>; }; template struct __parity; template struct __make : __parity<_Np % 8>::template __pmake<_Np> {}; template<> struct __make<0> { using type = integer_sequence; }; template<> struct __make<1> { using type = integer_sequence; }; template<> struct __make<2> { using type = integer_sequence; }; template<> struct __make<3> { using type = integer_sequence; }; template<> struct __make<4> { using type = integer_sequence; }; template<> struct __make<5> { using type = integer_sequence; }; template<> struct __make<6> { using type = integer_sequence; }; template<> struct __make<7> { using type = integer_sequence; }; template<> struct __parity<0> { template struct __pmake : __repeat::type> {}; }; template<> struct __parity<1> { template struct __pmake : __repeat::type, _Np - 1> {}; }; template<> struct __parity<2> { template struct __pmake : __repeat::type, _Np - 2, _Np - 1> {}; }; template<> struct __parity<3> { template struct __pmake : __repeat::type, _Np - 3, _Np - 2, _Np - 1> {}; }; template<> struct __parity<4> { template struct __pmake : __repeat::type, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; template<> struct __parity<5> { template struct __pmake : __repeat::type, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; template<> struct __parity<6> { template struct __pmake : __repeat::type, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; template<> struct __parity<7> { template struct __pmake : __repeat::type, _Np - 7, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; template struct __convert { template struct __result; template<_Tp ..._Np> struct __result> { using type = integer_sequence<_Up, _Np...>; }; }; template struct __convert<_Tp, _Tp> { template struct __result { using type = _Up; }; }; template using __make_integer_sequence_unchecked = typename __detail::__convert::template __result::type>::type; template struct __make_integer_sequence { static_assert(std::is_integral<_Tp>::value, "std::make_integer_sequence can only be instantiated with an integral type" ); static_assert(0 <= _Ep, "std::make_integer_sequence input shall not be negative"); typedef __make_integer_sequence_unchecked<_Tp, _Ep> type; }; } // namespace __detail /** @endcond */ /** C++11 implementation of index sequence * @see https://en.cppreference.com/w/cpp/utility/integer_sequence * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ template using make_integer_sequence = typename __detail::__make_integer_sequence<_Tp, _Np>::type; /** C++11 implementation of index sequence * @see https://en.cppreference.com/w/cpp/utility/integer_sequence * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ template using make_index_sequence = make_integer_sequence; /** C++11 implementation of index sequence * @see https://en.cppreference.com/w/cpp/utility/integer_sequence * @see taken from clang: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/utility?revision=211563&view=markup#l687 */ template using index_sequence_for = make_index_sequence; #endif /** @} */ } // namespace c4 // NOLINTEND(bugprone-macro-parentheses) #endif /* _C4_TYPES_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/types.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/config.hpp // https://github.com/biojppm/c4core/src/c4/config.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_CONFIG_HPP_ #define _C4_CONFIG_HPP_ /** @defgroup basic_headers Basic headers * @brief Headers providing basic macros, platform+cpu+compiler information, * C++ facilities and basic typedefs. */ /** @file config.hpp Contains configuration defines and includes the basic_headers. * @ingroup basic_headers */ //#define C4_DEBUG #define C4_ERROR_SHOWS_FILELINE //#define C4_ERROR_SHOWS_FUNC //#define C4_ERROR_THROWS_EXCEPTION //#define C4_NO_ALLOC_DEFAULTS //#define C4_REDEFINE_CPPNEW #ifndef C4_SIZE_TYPE # define C4_SIZE_TYPE size_t #endif #ifndef C4_STR_SIZE_TYPE # define C4_STR_SIZE_TYPE C4_SIZE_TYPE #endif #ifndef C4_TIME_TYPE # define C4_TIME_TYPE double #endif // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/export.hpp //#include "c4/export.hpp" #if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) #error "amalgamate: file c4/export.hpp must have been included at this point" #endif /* C4_EXPORT_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/preprocessor.hpp //#include "c4/preprocessor.hpp" #if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) #error "amalgamate: file c4/preprocessor.hpp must have been included at this point" #endif /* C4_PREPROCESSOR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/platform.hpp //#include "c4/platform.hpp" #if !defined(C4_PLATFORM_HPP_) && !defined(_C4_PLATFORM_HPP_) #error "amalgamate: file c4/platform.hpp must have been included at this point" #endif /* C4_PLATFORM_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/cpu.hpp //#include "c4/cpu.hpp" #if !defined(C4_CPU_HPP_) && !defined(_C4_CPU_HPP_) #error "amalgamate: file c4/cpu.hpp must have been included at this point" #endif /* C4_CPU_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/compiler.hpp //#include "c4/compiler.hpp" #if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) #error "amalgamate: file c4/compiler.hpp must have been included at this point" #endif /* C4_COMPILER_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/language.hpp //#include "c4/language.hpp" #if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) #error "amalgamate: file c4/language.hpp must have been included at this point" #endif /* C4_LANGUAGE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/types.hpp //#include "c4/types.hpp" #if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) #error "amalgamate: file c4/types.hpp must have been included at this point" #endif /* C4_TYPES_HPP_ */ #endif // _C4_CONFIG_HPP_ // (end https://github.com/biojppm/c4core/src/c4/config.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/ext/debugbreak/debugbreak.h // https://github.com/biojppm/c4core/src/c4/ext/debugbreak/debugbreak.h //-------------------------------------------------------------------------------- //******************************************************************************** /* Copyright (c) 2011-2021, Scott Tsai * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef DEBUG_BREAK_H #define DEBUG_BREAK_H #ifdef _MSC_VER #define debug_break __debugbreak #else #ifdef __cplusplus extern "C" { #endif #define DEBUG_BREAK_USE_TRAP_INSTRUCTION 1 #define DEBUG_BREAK_USE_BUILTIN_TRAP 2 #define DEBUG_BREAK_USE_SIGTRAP 3 #define DEBUG_BREAK_USE_BUILTIN_DEBUGTRAP 4 #if defined(__i386__) || defined(__x86_64__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __inline__ static void trap_instruction(void) { __asm__ volatile("int $0x03"); } #elif defined(__thumb__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION /* FIXME: handle __THUMB_INTERWORK__ */ __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'arm-linux-tdep.c' in GDB source. * Both instruction sequences below work. */ #if 1 /* 'eabi_linux_thumb_le_breakpoint' */ __asm__ volatile(".inst 0xde01"); #else /* 'eabi_linux_thumb2_le_breakpoint' */ __asm__ volatile(".inst.w 0xf7f0a000"); #endif /* Known problem: * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB. * 'step' would keep getting stuck on the same instruction. * * Workaround: use the new GDB commands 'debugbreak-step' and * 'debugbreak-continue' that become available * after you source the script from GDB: * * $ gdb -x debugbreak-gdb.py <... USUAL ARGUMENTS ...> * * 'debugbreak-step' would jump over the breakpoint instruction with * roughly equivalent of: * (gdb) set $instruction_len = 2 * (gdb) tbreak *($pc + $instruction_len) * (gdb) jump *($pc + $instruction_len) */ } #elif defined(__arm__) && !defined(__thumb__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'arm-linux-tdep.c' in GDB source, * 'eabi_linux_arm_le_breakpoint' */ __asm__ volatile(".inst 0xe7f001f0"); /* Known problem: * Same problem and workaround as Thumb mode */ } #elif defined(__aarch64__) && defined(__APPLE__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_BUILTIN_DEBUGTRAP #elif defined(__aarch64__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'aarch64-tdep.c' in GDB source, * 'aarch64_default_breakpoint' */ __asm__ volatile(".inst 0xd4200000"); } #elif defined(__powerpc__) /* PPC 32 or 64-bit, big or little endian */ #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'rs6000-tdep.c' in GDB source, * 'rs6000_breakpoint' */ __asm__ volatile(".4byte 0x7d821008"); /* Known problem: * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB. * 'step' stuck on the same instruction ("twge r2,r2"). * * The workaround is the same as ARM Thumb mode: use debugbreak-gdb.py * or manually jump over the instruction. */ } #elif defined(__riscv) /* RISC-V 32 or 64-bit, whether the "C" extension * for compressed, 16-bit instructions are supported or not */ #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'riscv-tdep.c' in GDB source, * 'riscv_sw_breakpoint_from_kind' */ __asm__ volatile(".4byte 0x00100073"); } #else #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_SIGTRAP #endif #ifndef DEBUG_BREAK_IMPL #error "debugbreak.h is not supported on this target" #elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void debug_break(void) { trap_instruction(); } #elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BUILTIN_DEBUGTRAP __attribute__((always_inline)) __inline__ static void debug_break(void) { __builtin_debugtrap(); } #elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BUILTIN_TRAP __attribute__((always_inline)) __inline__ static void debug_break(void) { __builtin_trap(); } #elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_SIGTRAP #include __attribute__((always_inline)) __inline__ static void debug_break(void) { raise(SIGTRAP); } #else #error "invalid DEBUG_BREAK_IMPL value" #endif #ifdef __cplusplus } #endif #endif /* ifdef _MSC_VER */ #endif /* ifndef DEBUG_BREAK_H */ // (end https://github.com/biojppm/c4core/src/c4/ext/debugbreak/debugbreak.h) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/error.hpp // https://github.com/biojppm/c4core/src/c4/error.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_ERROR_HPP_ #define _C4_ERROR_HPP_ /** @file error.hpp Facilities for error reporting and runtime assertions. */ /** @defgroup error_checking Error checking */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/config.hpp //#include "c4/config.hpp" #if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) #error "amalgamate: file c4/config.hpp must have been included at this point" #endif /* C4_CONFIG_HPP_ */ #ifdef _DOXYGEN_ /** if this is defined and exceptions are enabled, then calls to C4_ERROR() * will throw an exception * @ingroup error_checking */ # define C4_EXCEPTIONS_ENABLED /** if this is defined and exceptions are enabled, then calls to C4_ERROR() * will throw an exception * @see C4_EXCEPTIONS_ENABLED * @ingroup error_checking */ # define C4_ERROR_THROWS_EXCEPTION /** evaluates to noexcept when C4_ERROR might be called and * exceptions are disabled. Otherwise, defaults to nothing. * @ingroup error_checking */ # define C4_NOEXCEPT #endif // _DOXYGEN_ #if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION) # define C4_NOEXCEPT #else # define C4_NOEXCEPT noexcept #endif namespace c4 { namespace detail { struct fail_type__ {}; } // detail } // c4 #define C4_STATIC_ERROR(dummy_type, errmsg) \ static_assert(std::is_same::value, errmsg) //----------------------------------------------------------------------------- #define C4_ASSERT_SAME_TYPE(ty1, ty2) \ C4_STATIC_ASSERT(std::is_same::value) #define C4_ASSERT_DIFF_TYPE(ty1, ty2) \ C4_STATIC_ASSERT( ! std::is_same::value) //----------------------------------------------------------------------------- #ifdef _DOXYGEN_ /** utility macro that triggers a breakpoint when * the debugger is attached and NDEBUG is not defined. * @ingroup error_checking */ # define C4_DEBUG_BREAK() #endif // _DOXYGEN_ #if defined(NDEBUG) || defined(C4_NO_DEBUG_BREAK) # define C4_DEBUG_BREAK() #else # ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wundef" # if !defined(__APPLE_CC__) # if __clang_major__ >= 10 # pragma clang diagnostic ignored "-Wgnu-inline-cpp-without-extern" // debugbreak/debugbreak.h:50:16: error: 'gnu_inline' attribute without 'extern' in C++ treated as externally available, this changed in Clang 10 [-Werror,-Wgnu-inline-cpp-without-extern] # endif # else # if __clang_major__ >= 13 # pragma clang diagnostic ignored "-Wgnu-inline-cpp-without-extern" // debugbreak/debugbreak.h:50:16: error: 'gnu_inline' attribute without 'extern' in C++ treated as externally available, this changed in Clang 10 [-Werror,-Wgnu-inline-cpp-without-extern] # endif # endif # elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wundef" # endif // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/ext/debugbreak/debugbreak.h //# include #if !defined(DEBUG_BREAK_H) && !defined(_DEBUG_BREAK_H) #error "amalgamate: file c4/ext/debugbreak/debugbreak.h must have been included at this point" #endif /* DEBUG_BREAK_H */ # define C4_DEBUG_BREAK() if(c4::is_debugger_attached()) { ::debug_break(); } # ifdef __clang__ # pragma clang diagnostic pop # elif defined(__GNUC__) # pragma GCC diagnostic pop # endif #endif namespace c4 { C4CORE_EXPORT bool is_debugger_attached(); } // namespace c4 //----------------------------------------------------------------------------- #ifdef __clang__ /* NOTE: using , ## __VA_ARGS__ to deal with zero-args calls to * variadic macros is not portable, but works in clang, gcc, msvc, icc. * clang requires switching off compiler warnings for pedantic mode. * @see http://stackoverflow.com/questions/32047685/variadic-macro-without-arguments */ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" // warning: token pasting of ',' and __VA_ARGS__ is a GNU extension #elif defined(__GNUC__) /* GCC also issues a warning for zero-args calls to variadic macros. * This warning is switched on with -pedantic and apparently there is no * easy way to turn it off as with clang. But marking this as a system * header works. * @see https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html * @see http://stackoverflow.com/questions/35587137/ */ # pragma GCC system_header #endif //----------------------------------------------------------------------------- namespace c4 { typedef enum : uint32_t { /** when an error happens and the debugger is attached, call C4_DEBUG_BREAK(). * Without effect otherwise. */ ON_ERROR_DEBUGBREAK = 0x01u << 0u, /** when an error happens log a message. */ ON_ERROR_LOG = 0x01u << 1u, /** when an error happens invoke a callback if it was set with * set_error_callback(). */ ON_ERROR_CALLBACK = 0x01u << 2u, /** when an error happens call std::terminate(). */ ON_ERROR_ABORT = 0x01u << 3u, /** when an error happens and exceptions are enabled throw an exception. * Without effect otherwise. */ ON_ERROR_THROW = 0x01u << 4u, /** the default flags. */ ON_ERROR_DEFAULTS = ON_ERROR_DEBUGBREAK|ON_ERROR_LOG|ON_ERROR_CALLBACK|ON_ERROR_ABORT } ErrorFlags_e; using error_flags = uint32_t; C4CORE_EXPORT void set_error_flags(error_flags f); C4CORE_EXPORT error_flags get_error_flags(); using error_callback_type = void (*)(const char* msg, size_t msg_size); C4CORE_EXPORT void set_error_callback(error_callback_type cb); C4CORE_EXPORT error_callback_type get_error_callback(); //----------------------------------------------------------------------------- /** RAII class controling the error settings inside a scope. */ struct ScopedErrorSettings // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) { error_flags m_flags; error_callback_type m_callback; explicit ScopedErrorSettings(error_callback_type cb) : m_flags(get_error_flags()), m_callback(get_error_callback()) { set_error_callback(cb); } explicit ScopedErrorSettings(error_flags flags) : m_flags(get_error_flags()), m_callback(get_error_callback()) { set_error_flags(flags); } explicit ScopedErrorSettings(error_flags flags, error_callback_type cb) : m_flags(get_error_flags()), m_callback(get_error_callback()) { set_error_flags(flags); set_error_callback(cb); } ~ScopedErrorSettings() { set_error_flags(m_flags); set_error_callback(m_callback); } }; //----------------------------------------------------------------------------- /** source location */ struct srcloc; // watchout: for VS the [[noreturn]] needs to come before other annotations like C4CORE_EXPORT [[noreturn]] C4CORE_EXPORT void handle_error(srcloc s, const char *fmt, ...); C4CORE_EXPORT void handle_warning(srcloc s, const char *fmt, ...); # define C4_ERROR(msg, ...) \ do { \ if(c4::get_error_flags() & c4::ON_ERROR_DEBUGBREAK) \ { \ C4_DEBUG_BREAK() \ } \ c4::handle_error(C4_SRCLOC(), msg, ## __VA_ARGS__); \ } while(0) # define C4_WARNING(msg, ...) \ c4::handle_warning(C4_SRCLOC(), msg, ## __VA_ARGS__) #if defined(C4_ERROR_SHOWS_FILELINE) && defined(C4_ERROR_SHOWS_FUNC) struct srcloc { const char *file = ""; const char *func = ""; int line = 0; }; #define C4_SRCLOC() c4::srcloc{__FILE__, C4_PRETTY_FUNC, __LINE__} #elif defined(C4_ERROR_SHOWS_FILELINE) struct srcloc { const char *file; int line; }; #define C4_SRCLOC() c4::srcloc{__FILE__, __LINE__} #elif ! defined(C4_ERROR_SHOWS_FUNC) struct srcloc { }; #define C4_SRCLOC() c4::srcloc() #else # error not implemented #endif //----------------------------------------------------------------------------- // assertions // Doxygen needs this so that only one definition counts #ifdef _DOXYGEN_ /** Explicitly enables assertions, independently of NDEBUG status. * This is meant to allow enabling assertions even when NDEBUG is defined. * Defaults to undefined. * @ingroup error_checking */ # define C4_USE_ASSERT /** assert that a condition is true; this is turned off when NDEBUG * is defined and C4_USE_ASSERT is not true. * @ingroup error_checking */ # define C4_ASSERT /** same as C4_ASSERT(), additionally prints a printf-formatted message * @ingroup error_checking */ # define C4_ASSERT_MSG /** evaluates to C4_NOEXCEPT when C4_XASSERT is disabled; otherwise, defaults * to noexcept * @ingroup error_checking */ # define C4_NOEXCEPT_A #endif // _DOXYGEN_ #ifndef C4_USE_ASSERT # ifdef NDEBUG # define C4_USE_ASSERT 0 # else # define C4_USE_ASSERT 1 # endif #endif #if C4_USE_ASSERT # define C4_ASSERT(cond) C4_CHECK(cond) # define C4_ASSERT_MSG(cond, /*fmt, */...) C4_CHECK_MSG(cond, ## __VA_ARGS__) # define C4_ASSERT_IF(predicate, cond) if(predicate) { C4_ASSERT(cond); } # define C4_NOEXCEPT_A C4_NOEXCEPT #else # define C4_ASSERT(cond) # define C4_ASSERT_MSG(cond, /*fmt, */...) # define C4_ASSERT_IF(predicate, cond) # define C4_NOEXCEPT_A noexcept #endif //----------------------------------------------------------------------------- // extreme assertions // Doxygen needs this so that only one definition counts #ifdef _DOXYGEN_ /** Explicitly enables extreme assertions; this is meant to allow enabling * assertions even when NDEBUG is defined. Defaults to undefined. * @ingroup error_checking */ # define C4_USE_XASSERT /** extreme assertion: can be switched off independently of * the regular assertion; use for example for bounds checking in hot code. * Turned on only when C4_USE_XASSERT is defined * @ingroup error_checking */ # define C4_XASSERT /** same as C4_XASSERT(), and additionally prints a printf-formatted message * @ingroup error_checking */ # define C4_XASSERT_MSG /** evaluates to C4_NOEXCEPT when C4_XASSERT is disabled; otherwise, defaults to noexcept * @ingroup error_checking */ # define C4_NOEXCEPT_X #endif // _DOXYGEN_ #ifndef C4_USE_XASSERT # define C4_USE_XASSERT C4_USE_ASSERT #endif #if C4_USE_XASSERT # define C4_XASSERT(cond) C4_CHECK(cond) # define C4_XASSERT_MSG(cond, /*fmt, */...) C4_CHECK_MSG(cond, ## __VA_ARGS__) # define C4_XASSERT_IF(predicate, cond) if(predicate) { C4_XASSERT(cond); } # define C4_NOEXCEPT_X C4_NOEXCEPT #else # define C4_XASSERT(cond) # define C4_XASSERT_MSG(cond, /*fmt, */...) # define C4_XASSERT_IF(predicate, cond) # define C4_NOEXCEPT_X noexcept #endif //----------------------------------------------------------------------------- // checks: never switched-off /** Check that a condition is true, or raise an error when not * true. Unlike C4_ASSERT(), this check is not disabled in non-debug * builds. * @see C4_ASSERT * @ingroup error_checking * * @todo add constexpr-compatible compile-time assert: * https://akrzemi1.wordpress.com/2017/05/18/asserts-in-constexpr-functions/ */ #define C4_CHECK(cond) \ do { \ if(C4_UNLIKELY(!(cond))) \ { \ C4_ERROR("check failed: %s", #cond); \ } \ } while(0) /** like C4_CHECK(), and additionally log a printf-style message. * @see C4_CHECK * @ingroup error_checking */ #define C4_CHECK_MSG(cond, fmt, ...) \ do { \ if(C4_UNLIKELY(!(cond))) \ { \ C4_ERROR("check failed: " #cond "\n" fmt, ## __VA_ARGS__); \ } \ } while(0) //----------------------------------------------------------------------------- // Common error conditions #define C4_NOT_IMPLEMENTED() C4_ERROR("NOT IMPLEMENTED") #define C4_NOT_IMPLEMENTED_MSG(/*msg, */...) C4_ERROR("NOT IMPLEMENTED: " __VA_ARGS__) #define C4_NOT_IMPLEMENTED_IF(condition) do { if(C4_UNLIKELY(condition)) { C4_ERROR("NOT IMPLEMENTED"); } } while(0) #define C4_NOT_IMPLEMENTED_IF_MSG(condition, /*msg, */...) do { if(C4_UNLIKELY(condition)) { C4_ERROR("NOT IMPLEMENTED: " __VA_ARGS__); } } while(0) #define C4_NEVER_REACH() do { C4_ERROR("never reach this point"); C4_UNREACHABLE(); } while(0) #define C4_NEVER_REACH_MSG(/*msg, */...) do { C4_ERROR("never reach this point: " __VA_ARGS__); C4_UNREACHABLE(); } while(0) //----------------------------------------------------------------------------- // helpers for warning suppression // idea adapted from https://github.com/onqtam/doctest/ // TODO: add C4_MESSAGE() https://stackoverflow.com/questions/18252351/custom-preprocessor-macro-for-a-conditional-pragma-message-xxx?rq=1 #ifdef C4_MSVC #define C4_SUPPRESS_WARNING_MSVC_PUSH __pragma(warning(push)) #define C4_SUPPRESS_WARNING_MSVC(w) __pragma(warning(disable : w)) #define C4_SUPPRESS_WARNING_MSVC_POP __pragma(warning(pop)) #else // C4_MSVC #define C4_SUPPRESS_WARNING_MSVC_PUSH #define C4_SUPPRESS_WARNING_MSVC(w) #define C4_SUPPRESS_WARNING_MSVC_POP #endif // C4_MSVC #ifdef C4_CLANG #define C4_PRAGMA_TO_STR(x) _Pragma(#x) #define C4_SUPPRESS_WARNING_CLANG_PUSH _Pragma("clang diagnostic push") #define C4_SUPPRESS_WARNING_CLANG(w) C4_PRAGMA_TO_STR(clang diagnostic ignored w) #define C4_SUPPRESS_WARNING_CLANG_POP _Pragma("clang diagnostic pop") #else // C4_CLANG #define C4_SUPPRESS_WARNING_CLANG_PUSH #define C4_SUPPRESS_WARNING_CLANG(w) #define C4_SUPPRESS_WARNING_CLANG_POP #endif // C4_CLANG #ifdef C4_GCC #define C4_PRAGMA_TO_STR(x) _Pragma(#x) #define C4_SUPPRESS_WARNING_GCC_PUSH _Pragma("GCC diagnostic push") #define C4_SUPPRESS_WARNING_GCC(w) C4_PRAGMA_TO_STR(GCC diagnostic ignored w) #define C4_SUPPRESS_WARNING_GCC_POP _Pragma("GCC diagnostic pop") #else // C4_GCC #define C4_SUPPRESS_WARNING_GCC_PUSH #define C4_SUPPRESS_WARNING_GCC(w) #define C4_SUPPRESS_WARNING_GCC_POP #endif // C4_GCC #define C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(w) \ C4_SUPPRESS_WARNING_MSVC_PUSH \ C4_SUPPRESS_WARNING_MSVC(w) #define C4_SUPPRESS_WARNING_CLANG_WITH_PUSH(w) \ C4_SUPPRESS_WARNING_CLANG_PUSH \ C4_SUPPRESS_WARNING_CLANG(w) #define C4_SUPPRESS_WARNING_GCC_WITH_PUSH(w) \ C4_SUPPRESS_WARNING_GCC_PUSH \ C4_SUPPRESS_WARNING_GCC(w) #define C4_SUPPRESS_WARNING_GCC_CLANG_PUSH \ C4_SUPPRESS_WARNING_GCC_PUSH \ C4_SUPPRESS_WARNING_CLANG_PUSH #define C4_SUPPRESS_WARNING_GCC_CLANG(w) \ C4_SUPPRESS_WARNING_GCC(w) \ C4_SUPPRESS_WARNING_CLANG(w) #define C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH(w) \ C4_SUPPRESS_WARNING_GCC_WITH_PUSH(w) \ C4_SUPPRESS_WARNING_CLANG_WITH_PUSH(w) #define C4_SUPPRESS_WARNING_GCC_CLANG_POP \ C4_SUPPRESS_WARNING_GCC_POP \ C4_SUPPRESS_WARNING_CLANG_POP } // namespace c4 #ifdef __clang__ # pragma clang diagnostic pop #endif #endif /* _C4_ERROR_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/error.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/memory_util.hpp // https://github.com/biojppm/c4core/src/c4/memory_util.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_MEMORY_UTIL_HPP_ #define _C4_MEMORY_UTIL_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/config.hpp //#include "c4/config.hpp" #if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) #error "amalgamate: file c4/config.hpp must have been included at this point" #endif /* C4_CONFIG_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/compiler.hpp //#include "c4/compiler.hpp" #if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) #error "amalgamate: file c4/compiler.hpp must have been included at this point" #endif /* C4_COMPILER_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/cpu.hpp //#include "c4/cpu.hpp" #if !defined(C4_CPU_HPP_) && !defined(_C4_CPU_HPP_) #error "amalgamate: file c4/cpu.hpp must have been included at this point" #endif /* C4_CPU_HPP_ */ #ifdef C4_MSVC #include #endif //included above: //#include #if (defined(__GNUC__) && __GNUC__ >= 10) || defined(__has_builtin) #define _C4_USE_LSB_INTRINSIC(which) __has_builtin(which) #define _C4_USE_MSB_INTRINSIC(which) __has_builtin(which) #elif defined(C4_MSVC) #define _C4_USE_LSB_INTRINSIC(which) true #define _C4_USE_MSB_INTRINSIC(which) true #else // let's try our luck #define _C4_USE_LSB_INTRINSIC(which) true #define _C4_USE_MSB_INTRINSIC(which) true #endif /** @file memory_util.hpp Some memory utilities. */ // NOLINTBEGIN(google-runtime-int) namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") /** set the given memory to zero */ C4_ALWAYS_INLINE void mem_zero(void* mem, size_t num_bytes) { memset(mem, 0, num_bytes); } /** set the given memory to zero */ template C4_ALWAYS_INLINE void mem_zero(T* mem, size_t num_elms) { memset(mem, 0, sizeof(T) * num_elms); } /** set the given memory to zero */ template C4_ALWAYS_INLINE void mem_zero(T* mem) { memset(mem, 0, sizeof(T)); } C4_ALWAYS_INLINE C4_CONST bool mem_overlaps(void const* a, void const* b, size_t sza, size_t szb) { // thanks @timwynants return (((const char*)b + szb) > a && b < ((const char*)a+sza)); } void mem_repeat(void* dest, void const* pattern, size_t pattern_size, size_t num_times); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template C4_ALWAYS_INLINE C4_CONST bool is_aligned(T *ptr, uintptr_t alignment=alignof(T)) { return (uintptr_t(ptr) & (alignment - uintptr_t(1))) == uintptr_t(0); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // least significant bit /** @name msb Compute the least significant bit * @note the input value must be nonzero * @note the input type must be unsigned */ /** @{ */ // https://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear #define _c4_lsb_fallback \ unsigned c = 0; \ v = (v ^ (v - 1)) >> 1; /* Set v's trailing 0s to 1s and zero rest */ \ for(; v; ++c) \ v >>= 1; \ return (unsigned) c // u8 template C4_CONSTEXPR14 auto lsb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_LSB_INTRINSIC(__builtin_ctz) // upcast to use the intrinsic, it's cheaper. #ifdef C4_MSVC #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanForward(&bit, (unsigned long)v); return bit; #else _c4_lsb_fallback; #endif #else return (unsigned)__builtin_ctz((unsigned)v); #endif #else _c4_lsb_fallback; #endif } // u16 template C4_CONSTEXPR14 auto lsb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_LSB_INTRINSIC(__builtin_ctz) // upcast to use the intrinsic, it's cheaper. // Then remember that the upcast makes it to 31bits #ifdef C4_MSVC #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanForward(&bit, (unsigned long)v); return bit; #else _c4_lsb_fallback; #endif #else return (unsigned)__builtin_ctz((unsigned)v); #endif #else _c4_lsb_fallback; #endif } // u32 template C4_CONSTEXPR14 auto lsb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_LSB_INTRINSIC(__builtin_ctz) #ifdef C4_MSVC #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanForward(&bit, v); return bit; #else _c4_lsb_fallback; #endif #else return (unsigned)__builtin_ctz((unsigned)v); #endif #else _c4_lsb_fallback; #endif } // u64 in 64bits template C4_CONSTEXPR14 auto lsb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_LSB_INTRINSIC(__builtin_ctzl) #if defined(C4_MSVC) #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanForward64(&bit, v); return bit; #else _c4_lsb_fallback; #endif #else return (unsigned)__builtin_ctzl((unsigned long)v); #endif #else _c4_lsb_fallback; #endif } // u64 in 32bits template C4_CONSTEXPR14 auto lsb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_LSB_INTRINSIC(__builtin_ctzll) #if defined(C4_MSVC) #if !defined(C4_CPU_X86) && !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanForward64(&bit, v); return bit; #else _c4_lsb_fallback; #endif #else return (unsigned)__builtin_ctzll((unsigned long long)v); #endif #else _c4_lsb_fallback; #endif } #undef _c4_lsb_fallback /** @} */ namespace detail { template struct _lsb11; template struct _lsb11 { enum : unsigned { num = _lsb11>1), num_bits+I(1), (((val>>1)&I(1))!=I(0))>::num }; }; template struct _lsb11 { enum : unsigned { num = num_bits }; }; } // namespace detail /** TMP version of lsb(); this needs to be implemented with template * meta-programming because C++11 cannot use a constexpr function with * local variables * @see lsb */ template struct lsb11 { static_assert(number != 0, "lsb: number must be nonzero"); enum : unsigned { value = detail::_lsb11::num}; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // most significant bit /** @name msb Compute the most significant bit * @note the input value must be nonzero * @note the input type must be unsigned */ /** @{ */ #define _c4_msb8_fallback \ unsigned n = 0; \ if(v & I(0xf0)) v >>= 4, n |= I(4); \ if(v & I(0x0c)) v >>= 2, n |= I(2); \ if(v & I(0x02)) v >>= 1, n |= I(1); \ return n #define _c4_msb16_fallback \ unsigned n = 0; \ if(v & I(0xff00)) v >>= 8, n |= I(8); \ if(v & I(0x00f0)) v >>= 4, n |= I(4); \ if(v & I(0x000c)) v >>= 2, n |= I(2); \ if(v & I(0x0002)) v >>= 1, n |= I(1); \ return n #define _c4_msb32_fallback \ unsigned n = 0; \ if(v & I(0xffff0000)) v >>= 16, n |= 16; \ if(v & I(0x0000ff00)) v >>= 8, n |= 8; \ if(v & I(0x000000f0)) v >>= 4, n |= 4; \ if(v & I(0x0000000c)) v >>= 2, n |= 2; \ if(v & I(0x00000002)) v >>= 1, n |= 1; \ return n #define _c4_msb64_fallback \ unsigned n = 0; \ if(v & I(0xffffffff00000000)) v >>= 32, n |= I(32); \ if(v & I(0x00000000ffff0000)) v >>= 16, n |= I(16); \ if(v & I(0x000000000000ff00)) v >>= 8, n |= I(8); \ if(v & I(0x00000000000000f0)) v >>= 4, n |= I(4); \ if(v & I(0x000000000000000c)) v >>= 2, n |= I(2); \ if(v & I(0x0000000000000002)) v >>= 1, n |= I(1); \ return n // u8 template C4_CONSTEXPR14 auto msb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_MSB_INTRINSIC(__builtin_clz) // upcast to use the intrinsic, it's cheaper. // Then remember that the upcast makes it to 31bits #ifdef C4_MSVC #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanReverse(&bit, (unsigned long)v); return bit; #else _c4_msb8_fallback; #endif #else return 31u - (unsigned)__builtin_clz((unsigned)v); #endif #else _c4_msb8_fallback; #endif } // u16 template C4_CONSTEXPR14 auto msb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_MSB_INTRINSIC(__builtin_clz) // upcast to use the intrinsic, it's cheaper. // Then remember that the upcast makes it to 31bits #ifdef C4_MSVC #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanReverse(&bit, (unsigned long)v); return bit; #else _c4_msb16_fallback; #endif #else return 31u - (unsigned)__builtin_clz((unsigned)v); #endif #else _c4_msb16_fallback; #endif } // u32 template C4_CONSTEXPR14 auto msb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_MSB_INTRINSIC(__builtin_clz) #ifdef C4_MSVC #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanReverse(&bit, v); return bit; #else _c4_msb32_fallback; #endif #else return 31u - (unsigned)__builtin_clz((unsigned)v); #endif #else _c4_msb32_fallback; #endif } // u64 in 64bits template C4_CONSTEXPR14 auto msb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_MSB_INTRINSIC(__builtin_clzl) #ifdef C4_MSVC #if !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanReverse64(&bit, v); return bit; #else _c4_msb64_fallback; #endif #else return 63u - (unsigned)__builtin_clzl((unsigned long)v); #endif #else _c4_msb64_fallback; #endif } // u64 in 32bits template C4_CONSTEXPR14 auto msb(I v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(v != 0); #if _C4_USE_MSB_INTRINSIC(__builtin_clzll) #ifdef C4_MSVC #if !defined(C4_CPU_X86) && !defined(C4_CPU_ARM64) && !defined(C4_CPU_ARM) unsigned long bit; _BitScanReverse64(&bit, v); return bit; #else _c4_msb64_fallback; #endif #else return 63u - (unsigned)__builtin_clzll((unsigned long long)v); #endif #else _c4_msb64_fallback; #endif } #undef _c4_msb8_fallback #undef _c4_msb16_fallback #undef _c4_msb32_fallback #undef _c4_msb64_fallback /** @} */ namespace detail { template struct _msb11; template struct _msb11< I, val, num_bits, false> { enum : unsigned { num = _msb11>1), num_bits+I(1), ((val>>1)==I(0))>::num }; }; template struct _msb11 { static_assert(val == 0, "bad implementation"); enum : unsigned { num = (unsigned)(num_bits-1) }; }; } // namespace detail /** TMP version of msb(); this needs to be implemented with template * meta-programming because C++11 cannot use a constexpr function with * local variables * @see msb */ template struct msb11 { enum : unsigned { value = detail::_msb11::num }; }; #undef _C4_USE_LSB_INTRINSIC #undef _C4_USE_MSB_INTRINSIC //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // there is an implicit conversion below; it happens when E or B are // narrower than int, and thus any operation will upcast the result to // int, and then downcast to assign C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wconversion") /** integer power; this function is constexpr-14 because of the local * variables */ template C4_CONSTEXPR14 C4_CONST auto ipow(B base, E exponent) noexcept -> typename std::enable_if::value, B>::type { C4_STATIC_ASSERT(std::is_integral::value); B r = B(1); if(exponent >= 0) { for(E e = 0; e < exponent; ++e) r *= base; } else { exponent *= E(-1); for(E e = 0; e < exponent; ++e) r /= base; } return r; } /** integer power; this function is constexpr-14 because of the local * variables */ template C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type { C4_STATIC_ASSERT(std::is_integral::value); B r = B(1); if(exponent >= 0) { for(E e = 0; e < exponent; ++e) r *= base; } else { exponent *= E(-1); for(E e = 0; e < exponent; ++e) r /= base; } return r; } /** integer power; this function is constexpr-14 because of the local * variables */ template C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type { C4_STATIC_ASSERT(std::is_integral::value); B r = B(1); B bbase = B(base); if(exponent >= 0) { for(E e = 0; e < exponent; ++e) r *= bbase; } else { exponent *= E(-1); for(E e = 0; e < exponent; ++e) r /= bbase; } return r; } /** integer power; this function is constexpr-14 because of the local * variables */ template C4_CONSTEXPR14 C4_CONST auto ipow(B base, E exponent) noexcept -> typename std::enable_if::value, B>::type { C4_STATIC_ASSERT(std::is_integral::value); B r = B(1); for(E e = 0; e < exponent; ++e) r *= base; return r; } /** integer power; this function is constexpr-14 because of the local * variables */ template C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type { C4_STATIC_ASSERT(std::is_integral::value); B r = B(1); for(E e = 0; e < exponent; ++e) r *= base; return r; } /** integer power; this function is constexpr-14 because of the local * variables */ template C4_CONSTEXPR14 C4_CONST auto ipow(E exponent) noexcept -> typename std::enable_if::value, B>::type { C4_STATIC_ASSERT(std::is_integral::value); B r = B(1); B bbase = B(base); for(E e = 0; e < exponent; ++e) r *= bbase; return r; } C4_SUPPRESS_WARNING_GCC_CLANG_POP //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** return a mask with all bits set [first_bit,last_bit[; this function * is constexpr-14 because of the local variables */ template C4_CONSTEXPR14 I contiguous_mask(I first_bit, I last_bit) { I r = 0; for(I i = first_bit; i < last_bit; ++i) { r |= (I(1) << i); } return r; } namespace detail { template struct _ctgmsk11; template struct _ctgmsk11< I, val, first, last, true> { enum : I { value = _ctgmsk11::value }; }; template struct _ctgmsk11< I, val, first, last, false> { enum : I { value = val }; }; } // namespace detail /** TMP version of contiguous_mask(); this needs to be implemented with template * meta-programming because C++11 cannot use a constexpr function with * local variables * @see contiguous_mask */ template struct contiguous_mask11 { enum : I { value = detail::_ctgmsk11::value }; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** use Empty Base Class Optimization to reduce the size of a pair of * potentially empty types*/ namespace detail { typedef enum { tpc_same, tpc_same_empty, tpc_both_empty, tpc_first_empty, tpc_second_empty, tpc_general } TightPairCase_e; template constexpr TightPairCase_e tpc_which_case() { return std::is_same::value ? std::is_empty::value ? tpc_same_empty : tpc_same : std::is_empty::value && std::is_empty::value ? tpc_both_empty : std::is_empty::value ? tpc_first_empty : std::is_empty::value ? tpc_second_empty : tpc_general ; } template struct tight_pair { private: First m_first; Second m_second; public: using first_type = First; using second_type = Second; tight_pair() : m_first(), m_second() {} tight_pair(First const& f, Second const& s) : m_first(f), m_second(s) {} C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return m_first; } C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return m_first; } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; } }; template struct tight_pair : public First { static_assert(std::is_same::value, "bad implementation"); using first_type = First; using second_type = Second; tight_pair() : First() {} tight_pair(First const& f, Second const& /*s*/) : First(f) {} C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return reinterpret_cast(*this); } // NOLINT C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return reinterpret_cast(*this); } // NOLINT }; template struct tight_pair : public First, public Second { using first_type = First; using second_type = Second; tight_pair() : First(), Second() {} tight_pair(First const& f, Second const& s) : First(f), Second(s) {} C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return static_cast(*this); } }; template struct tight_pair : public First { Second m_second; using first_type = First; using second_type = Second; tight_pair() : First() {} tight_pair(First const& f, Second const& s) : First(f), m_second(s) {} C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; } }; template struct tight_pair : public First { Second m_second; using first_type = First; using second_type = Second; tight_pair() : First(), m_second() {} tight_pair(First const& f, Second const& s) : First(f), m_second(s) {} C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; } }; template struct tight_pair : public Second { First m_first; using first_type = First; using second_type = Second; tight_pair() : Second(), m_first() {} tight_pair(First const& f, Second const& s) : Second(s), m_first(f) {} C4_ALWAYS_INLINE C4_CONSTEXPR14 First & first () { return m_first; } C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return m_first; } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return static_cast(*this); } C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return static_cast(*this); } }; } // namespace detail template using tight_pair = detail::tight_pair()>; C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 // NOLINTEND(google-runtime-int) #endif /* _C4_MEMORY_UTIL_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/memory_util.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/memory_resource.hpp // https://github.com/biojppm/c4core/src/c4/memory_resource.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_MEMORY_RESOURCE_HPP_ #define _C4_MEMORY_RESOURCE_HPP_ /** @file memory_resource.hpp Provides facilities to allocate typeless * memory, via the memory resource model consecrated with C++17. */ /** @defgroup memory memory utilities */ /** @defgroup raw_memory_alloc Raw memory allocation * @ingroup memory */ /** @defgroup memory_resources Memory resources * @ingroup memory */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/config.hpp //#include "c4/config.hpp" #if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) #error "amalgamate: file c4/config.hpp must have been included at this point" #endif /* C4_CONFIG_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ namespace c4 { // need these forward decls here struct MemoryResource; struct MemoryResourceMalloc; struct MemoryResourceStack; MemoryResourceMalloc* get_memory_resource_malloc(); MemoryResourceStack* get_memory_resource_stack(); namespace detail { MemoryResource*& get_memory_resource(); } // c-style allocation --------------------------------------------------------- // this API provides aligned allocation functions. // These functions forward the call to a user-modifiable function. // aligned allocation. /** Aligned allocation. Merely calls the current get_aalloc() function. * @see get_aalloc() * @ingroup raw_memory_alloc */ void* aalloc(size_t sz, size_t alignment); /** Aligned free. Merely calls the current get_afree() function. * @see get_afree() * @ingroup raw_memory_alloc */ void afree(void* ptr); /** Aligned reallocation. Merely calls the current get_arealloc() function. * @see get_arealloc() * @ingroup raw_memory_alloc */ void* arealloc(void* ptr, size_t oldsz, size_t newsz, size_t alignment); // allocation setup facilities. /** Function pointer type for aligned allocation * @see set_aalloc() * @ingroup raw_memory_alloc */ using aalloc_pfn = void* (*)(size_t size, size_t alignment); /** Function pointer type for aligned deallocation * @see set_afree() * @ingroup raw_memory_alloc */ using afree_pfn = void (*)(void *ptr); /** Function pointer type for aligned reallocation * @see set_arealloc() * @ingroup raw_memory_alloc */ using arealloc_pfn = void* (*)(void *ptr, size_t oldsz, size_t newsz, size_t alignment); // allocation function pointer setters/getters /** Set the global aligned allocation function. * @see aalloc() * @see get_aalloc() * @ingroup raw_memory_alloc */ void set_aalloc(aalloc_pfn fn); /** Set the global aligned deallocation function. * @see afree() * @see get_afree() * @ingroup raw_memory_alloc */ void set_afree(afree_pfn fn); /** Set the global aligned reallocation function. * @see arealloc() * @see get_arealloc() * @ingroup raw_memory_alloc */ void set_arealloc(arealloc_pfn fn); /** Get the global aligned reallocation function. * @see arealloc() * @ingroup raw_memory_alloc */ aalloc_pfn get_aalloc(); /** Get the global aligned deallocation function. * @see afree() * @ingroup raw_memory_alloc */ afree_pfn get_afree(); /** Get the global aligned reallocation function. * @see arealloc() * @ingroup raw_memory_alloc */ arealloc_pfn get_arealloc(); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // c++-style allocation ------------------------------------------------------- /** C++17-style memory_resource base class. See http://en.cppreference.com/w/cpp/experimental/memory_resource * @ingroup memory_resources */ struct MemoryResource // NOLINT(*-member-functions) { const char *name = nullptr; virtual ~MemoryResource() = default; void* allocate(size_t sz, size_t alignment=alignof(max_align_t), void *hint=nullptr) { void *mem = this->do_allocate(sz, alignment, hint); C4_CHECK_MSG(mem != nullptr, "could not allocate %lu bytes", sz); return mem; } void* reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment=alignof(max_align_t)) { void *mem = this->do_reallocate(ptr, oldsz, newsz, alignment); C4_CHECK_MSG(mem != nullptr, "could not reallocate from %lu to %lu bytes", oldsz, newsz); return mem; } void deallocate(void* ptr, size_t sz, size_t alignment=alignof(max_align_t)) { this->do_deallocate(ptr, sz, alignment); } protected: virtual void* do_allocate(size_t sz, size_t alignment, void* hint) = 0; virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) = 0; virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) = 0; }; /** get the current global memory resource. To avoid static initialization * order problems, this is implemented using a function call to ensure * that it is available when first used. * @ingroup memory_resources */ C4_ALWAYS_INLINE MemoryResource* get_memory_resource() { return detail::get_memory_resource(); } /** set the global memory resource * @ingroup memory_resources */ C4_ALWAYS_INLINE void set_memory_resource(MemoryResource* mr) { C4_ASSERT(mr != nullptr); detail::get_memory_resource() = mr; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A c4::aalloc-based memory resource. Thread-safe if the implementation * called by c4::aalloc() is safe. * @ingroup memory_resources */ struct MemoryResourceMalloc : public MemoryResource // NOLINT(*-member-functions) { MemoryResourceMalloc() { name = "malloc"; } protected: void* do_allocate(size_t sz, size_t alignment, void *hint) override { C4_UNUSED(hint); return c4::aalloc(sz, alignment); } void do_deallocate(void* ptr, size_t sz, size_t alignment) override { C4_UNUSED(sz); C4_UNUSED(alignment); c4::afree(ptr); } void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override { return c4::arealloc(ptr, oldsz, newsz, alignment); } }; /** returns a malloc-based memory resource * @ingroup memory_resources */ C4_ALWAYS_INLINE MemoryResourceMalloc* get_memory_resource_malloc() { /** @todo use a nifty counter: * https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter */ static MemoryResourceMalloc mr; return &mr; } namespace detail { C4_ALWAYS_INLINE MemoryResource* & get_memory_resource() { /** @todo use a nifty counter: * https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter */ thread_local static MemoryResource* mr = get_memory_resource_malloc(); return mr; } } // namespace detail //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- namespace detail { /** Allows a memory resource to obtain its memory from another memory resource. * @ingroup memory_resources */ struct DerivedMemoryResource : public MemoryResource { public: DerivedMemoryResource(MemoryResource *mr_=nullptr) : m_local(mr_ ? mr_ : get_memory_resource()) {} private: MemoryResource *m_local; protected: void* do_allocate(size_t sz, size_t alignment, void* hint) override { return m_local->allocate(sz, alignment, hint); } void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override { return m_local->reallocate(ptr, oldsz, newsz, alignment); } void do_deallocate(void* ptr, size_t sz, size_t alignment) override { m_local->deallocate(ptr, sz, alignment); } }; /** Provides common facilities for memory resource consisting of a single memory block * @ingroup memory_resources */ struct _MemoryResourceSingleChunk : public DerivedMemoryResource { C4_NO_COPY_OR_MOVE(_MemoryResourceSingleChunk); using impl_type = DerivedMemoryResource; public: _MemoryResourceSingleChunk(MemoryResource *impl=nullptr) : DerivedMemoryResource(impl) { name = "linear_malloc"; } /** initialize with owned memory, allocated from the given (or the global) memory resource */ _MemoryResourceSingleChunk(size_t sz, MemoryResource *impl=nullptr) : _MemoryResourceSingleChunk(impl) { acquire(sz); } /** initialize with borrowed memory */ _MemoryResourceSingleChunk(void *mem, size_t sz) : _MemoryResourceSingleChunk() { acquire(mem, sz); } ~_MemoryResourceSingleChunk() override { release(); } public: void const* mem() const { return m_mem; } size_t capacity() const { return m_size; } size_t size() const { return m_pos; } size_t slack() const { C4_ASSERT(m_size >= m_pos); return m_size - m_pos; } public: char *m_mem{nullptr}; size_t m_size{0}; size_t m_pos{0}; bool m_owner; public: /** set the internal pointer to the beginning of the linear buffer */ void clear() { m_pos = 0; } /** initialize with owned memory, allocated from the global memory resource */ void acquire(size_t sz); /** initialize with borrowed memory */ void acquire(void *mem, size_t sz); /** release the memory */ void release(); }; } // namespace detail //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** provides a linear memory resource. Allocates incrementally from a linear * buffer, without ever deallocating. Deallocations are a no-op, and the * memory is freed only when the resource is release()d. The memory used by * this object can be either owned or borrowed. When borrowed, no calls to * malloc/free take place. * * @ingroup memory_resources */ struct MemoryResourceLinear : public detail::_MemoryResourceSingleChunk // NOLINT(*-member-functions) { C4_NO_COPY_OR_MOVE(MemoryResourceLinear); public: using detail::_MemoryResourceSingleChunk::_MemoryResourceSingleChunk; protected: void* do_allocate(size_t sz, size_t alignment, void *hint) override; void do_deallocate(void* ptr, size_t sz, size_t alignment) override; void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** provides a stack-type malloc-based memory resource. * @ingroup memory_resources */ struct MemoryResourceStack : public detail::_MemoryResourceSingleChunk // NOLINT(*-member-functions) { C4_NO_COPY_OR_MOVE(MemoryResourceStack); public: using detail::_MemoryResourceSingleChunk::_MemoryResourceSingleChunk; protected: void* do_allocate(size_t sz, size_t alignment, void *hint) override; void do_deallocate(void* ptr, size_t sz, size_t alignment) override; void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** provides a linear array-based memory resource. * @see MemoryResourceLinear * @ingroup memory_resources */ template struct MemoryResourceLinearArr : public MemoryResourceLinear { C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4324) // structure was padded due to alignment specifier alignas(alignof(max_align_t)) char m_arr[N]; C4_SUPPRESS_WARNING_MSVC_POP MemoryResourceLinearArr() : MemoryResourceLinear(m_arr, N) { name = "linear_arr"; } // NOLINT }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- struct AllocationCounts { struct Item { ssize_t allocs; ssize_t size; void add(size_t sz) { ++allocs; size += static_cast(sz); } void rem(size_t sz) { --allocs; size -= static_cast(sz); } Item max(Item const& that) const { Item r(*this); r.allocs = r.allocs > that.allocs ? r.allocs : that.allocs; r.size = r.size > that.size ? r.size : that.size; return r; } }; Item curr = {0, 0}; Item total = {0, 0}; Item max = {0, 0}; void clear_counts() { curr = {0, 0}; total = {0, 0}; max = {0, 0}; } void update(AllocationCounts const& that) { curr.allocs += that.curr.allocs; curr.size += that.curr.size; total.allocs += that.total.allocs; total.size += that.total.size; max.allocs += that.max.allocs; max.size += that.max.size; } void add_counts(void* ptr, size_t sz) { if(ptr == nullptr) return; curr.add(sz); total.add(sz); max = max.max(curr); } void rem_counts(void *ptr, size_t sz) { if(ptr == nullptr) return; curr.rem(sz); } AllocationCounts operator- (AllocationCounts const& that) const { AllocationCounts r(*this); r.curr.allocs -= that.curr.allocs; r.curr.size -= that.curr.size; r.total.allocs -= that.total.allocs; r.total.size -= that.total.size; r.max.allocs -= that.max.allocs; r.max.size -= that.max.size; return r; } AllocationCounts operator+ (AllocationCounts const& that) const { AllocationCounts r(*this); r.curr.allocs += that.curr.allocs; r.curr.size += that.curr.size; r.total.allocs += that.total.allocs; r.total.size += that.total.size; r.max.allocs += that.max.allocs; r.max.size += that.max.size; return r; } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** a MemoryResource which latches onto another MemoryResource * and counts allocations and sizes. * @ingroup memory_resources */ class MemoryResourceCounts : public MemoryResource { public: MemoryResourceCounts() : m_resource(get_memory_resource()) { C4_ASSERT(m_resource != this); name = "MemoryResourceCounts"; } MemoryResourceCounts(MemoryResource *res) : m_resource(res) { C4_ASSERT(m_resource != this); name = "MemoryResourceCounts"; } MemoryResource *resource() { return m_resource; } AllocationCounts const& counts() const { return m_counts; } protected: MemoryResource *m_resource; // NOLINT AllocationCounts m_counts; // NOLINT protected: void* do_allocate(size_t sz, size_t alignment, void * /*hint*/) override { void *ptr = m_resource->allocate(sz, alignment); m_counts.add_counts(ptr, sz); return ptr; } void do_deallocate(void* ptr, size_t sz, size_t alignment) override { m_counts.rem_counts(ptr, sz); m_resource->deallocate(ptr, sz, alignment); } void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override { m_counts.rem_counts(ptr, oldsz); void* nptr = m_resource->reallocate(ptr, oldsz, newsz, alignment); m_counts.add_counts(nptr, newsz); return nptr; } }; //----------------------------------------------------------------------------- /** RAII class which binds a memory resource with a scope duration. * @ingroup memory_resources */ struct ScopedMemoryResource // NOLINT(*-member-functions) { MemoryResource *m_original; ScopedMemoryResource(MemoryResource *r) : m_original(get_memory_resource()) { set_memory_resource(r); } ~ScopedMemoryResource() { set_memory_resource(m_original); } }; //----------------------------------------------------------------------------- /** RAII class which counts allocations and frees inside a scope. Can * optionally set also the memory resource to be used. * @ingroup memory_resources */ struct ScopedMemoryResourceCounts // NOLINT(*-member-functions) { MemoryResourceCounts mr; ScopedMemoryResourceCounts() : mr() { set_memory_resource(&mr); } ScopedMemoryResourceCounts(MemoryResource *m) : mr(m) { set_memory_resource(&mr); } ~ScopedMemoryResourceCounts() { set_memory_resource(mr.resource()); } }; } // namespace c4 #endif /* _C4_MEMORY_RESOURCE_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/memory_resource.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/ctor_dtor.hpp // https://github.com/biojppm/c4core/src/c4/ctor_dtor.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_CTOR_DTOR_HPP_ #define _C4_CTOR_DTOR_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/preprocessor.hpp //#include "c4/preprocessor.hpp" #if !defined(C4_PREPROCESSOR_HPP_) && !defined(_C4_PREPROCESSOR_HPP_) #error "amalgamate: file c4/preprocessor.hpp must have been included at this point" #endif /* C4_PREPROCESSOR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/language.hpp //#include "c4/language.hpp" #if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) #error "amalgamate: file c4/language.hpp must have been included at this point" #endif /* C4_LANGUAGE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/memory_util.hpp //#include "c4/memory_util.hpp" #if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) #error "amalgamate: file c4/memory_util.hpp must have been included at this point" #endif /* C4_MEMORY_UTIL_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ //included above: //#include //included above: //#include // std::forward /** @file ctor_dtor.hpp object construction and destruction facilities. * Some of these are not yet available in C++11. */ namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") /** default-construct an object, trivial version */ template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type construct(U *ptr) noexcept { memset(ptr, 0, sizeof(U)); } /** default-construct an object, non-trivial version */ template C4_ALWAYS_INLINE typename std ::enable_if< ! std::is_trivially_default_constructible::value, void>::type construct(U* ptr) noexcept { new ((void*)ptr) U(); } /** default-construct n objects, trivial version */ template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type construct_n(U* ptr, I n) noexcept { memset(ptr, 0, n * sizeof(U)); } /** default-construct n objects, non-trivial version */ template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_default_constructible::value, void>::type construct_n(U* ptr, I n) noexcept { for(I i = 0; i < n; ++i) { new ((void*)(ptr + i)) U(); } } #ifdef __clang__ # pragma clang diagnostic push #elif defined(__GNUC__) # pragma GCC diagnostic push # if __GNUC__ >= 6 # pragma GCC diagnostic ignored "-Wnull-dereference" # endif #endif template inline void construct(U* ptr, Args&&... args) { new ((void*)ptr) U(std::forward(args)...); } template inline void construct_n(U* ptr, I n, Args&&... args) // NOLINT { for(I i = 0; i < n; ++i) { new ((void*)(ptr + i)) U(args...); } } #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif //----------------------------------------------------------------------------- // copy-construct template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type copy_construct(U* dst, U const* src) noexcept { C4_ASSERT(dst != src); memcpy(dst, src, sizeof(U)); } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_constructible::value, void>::type copy_construct(U* dst, U const* src) { C4_ASSERT(dst != src); new ((void*)dst) U(*src); } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type copy_construct_n(U* dst, U const* src, I n) noexcept { C4_ASSERT(dst != src); memcpy(dst, src, n * sizeof(U)); } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_constructible::value, void>::type copy_construct_n(U* dst, U const* src, I n) { C4_ASSERT(dst != src); for(I i = 0; i < n; ++i) { new ((void*)(dst + i)) U(*(src + i)); } } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type copy_construct(U* dst, U src) noexcept // pass by value for scalar types { *dst = src; } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type copy_construct(U* dst, U const& src) // pass by reference for non-scalar types { C4_ASSERT(dst != &src); new ((void*)dst) U(src); } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type copy_construct_n(U* dst, U src, I n) noexcept // pass by value for scalar types { for(I i = 0; i < n; ++i) { dst[i] = src; } } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type copy_construct_n(U* dst, U const& src, I n) // pass by reference for non-scalar types { C4_ASSERT(dst != &src); for(I i = 0; i < n; ++i) { new ((void*)(dst + i)) U(src); } } template C4_ALWAYS_INLINE void copy_construct(U (&dst)[N], U const (&src)[N]) noexcept { copy_construct_n(dst, src, N); } //----------------------------------------------------------------------------- // copy-assign template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type copy_assign(U* dst, U const* src) noexcept { C4_ASSERT(dst != src); memcpy(dst, src, sizeof(U)); } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_assignable::value, void>::type copy_assign(U* dst, U const* src) noexcept { C4_ASSERT(dst != src); *dst = *src; } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type copy_assign_n(U* dst, U const* src, I n) noexcept { C4_ASSERT(dst != src); memcpy(dst, src, n * sizeof(U)); } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_assignable::value, void>::type copy_assign_n(U* dst, U const* src, I n) noexcept { C4_ASSERT(dst != src); for(I i = 0; i < n; ++i) { dst[i] = src[i]; } } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type copy_assign(U* dst, U src) noexcept // pass by value for scalar types { *dst = src; } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type copy_assign(U* dst, U const& src) noexcept // pass by reference for non-scalar types { C4_ASSERT(dst != &src); *dst = src; } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type copy_assign_n(U* dst, U src, I n) noexcept // pass by value for scalar types { for(I i = 0; i < n; ++i) { dst[i] = src; } } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar::value, void>::type copy_assign_n(U* dst, U const& src, I n) noexcept // pass by reference for non-scalar types { C4_ASSERT(dst != &src); for(I i = 0; i < n; ++i) { dst[i] = src; } } template C4_ALWAYS_INLINE void copy_assign(U (&dst)[N], U const (&src)[N]) noexcept { copy_assign_n(dst, src, N); } //----------------------------------------------------------------------------- // move-construct template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type move_construct(U* dst, U* src) noexcept { C4_ASSERT(dst != src); memcpy(dst, src, sizeof(U)); } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type move_construct(U* dst, U* src) noexcept { C4_ASSERT(dst != src); new ((void*)dst) U(std::move(*src)); } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type move_construct_n(U* dst, U* src, I n) noexcept { C4_ASSERT(dst != src); memcpy(dst, src, n * sizeof(U)); } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type move_construct_n(U* dst, U* src, I n) noexcept { C4_ASSERT(dst != src); for(I i = 0; i < n; ++i) { new ((void*)(dst + i)) U(std::move(src[i])); } } //----------------------------------------------------------------------------- // move-assign template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type move_assign(U* dst, U* src) noexcept { C4_ASSERT(dst != src); memcpy(dst, src, sizeof(U)); } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_assignable::value, void>::type move_assign(U* dst, U* src) noexcept { C4_ASSERT(dst != src); *dst = std::move(*src); } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type move_assign_n(U* dst, U* src, I n) noexcept { C4_ASSERT(dst != src); memcpy(dst, src, n * sizeof(U)); } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_assignable::value, void>::type move_assign_n(U* dst, U* src, I n) noexcept { C4_ASSERT(dst != src); for(I i = 0; i < n; ++i) { *(dst + i) = std::move(*(src + i)); } } //----------------------------------------------------------------------------- // destroy template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type destroy(U* ptr) noexcept { C4_UNUSED(ptr); // nothing to do } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_destructible::value, void>::type destroy(U* ptr) noexcept { ptr->~U(); } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type destroy_n(U* ptr, I n) noexcept { C4_UNUSED(ptr); C4_UNUSED(n); // nothing to do } template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_destructible::value, void>::type destroy_n(U* ptr, I n) noexcept { for(I i = 0; i C4_ALWAYS_INLINE typename std::enable_if::value, void>::type make_room(U *buf, I bufsz, I room) C4_NOEXCEPT_A { C4_ASSERT(bufsz >= 0 && room >= 0); if(room >= bufsz) { memcpy (buf + room, buf, bufsz * sizeof(U)); } else { memmove(buf + room, buf, bufsz * sizeof(U)); } } /** makes room at the beginning of buf, which has a current size of bufsz */ template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type make_room(U *buf, I bufsz, I room) C4_NOEXCEPT_A { C4_ASSERT(bufsz >= 0 && room >= 0); if(room >= bufsz) { for(I i = 0; i < bufsz; ++i) { new ((void*)(buf + (i + room))) U(std::move(buf[i])); } } else { for(I i = 0; i < bufsz; ++i) { I w = bufsz-1 - i; // do a backwards loop new ((void*)(buf + (w + room))) U(std::move(buf[w])); } } } /** make room to the right of pos */ template C4_ALWAYS_INLINE void make_room(U *buf, I bufsz, I currsz, I pos, I room) { C4_ASSERT(pos >= 0 && pos <= currsz); C4_ASSERT(currsz <= bufsz); C4_ASSERT(room + currsz <= bufsz); C4_UNUSED(bufsz); make_room(buf + pos, currsz - pos, room); } /** make room to the right of pos, copying to the beginning of a different buffer */ template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type make_room(U *dst, U const* src, I srcsz, I room, I pos) C4_NOEXCEPT_A { C4_ASSERT(srcsz >= 0 && room >= 0 && pos >= 0); C4_ASSERT(pos < srcsz || (pos == 0 && srcsz == 0)); memcpy(dst , src , pos * sizeof(U)); memcpy(dst + room + pos, src + pos, (srcsz - pos) * sizeof(U)); } /** make room to the right of pos, copying to the beginning of a different buffer */ template C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type make_room(U *dst, U const* src, I srcsz, I room, I pos) { C4_ASSERT(srcsz >= 0 && room >= 0 && pos >= 0); C4_ASSERT(pos < srcsz || (pos == 0 && srcsz == 0)); for(I i = 0; i < pos; ++i) { new ((void*)(dst + i)) U(std::move(src[i])); } src += pos; dst += room + pos; for(I i = 0, e = srcsz - pos; i < e; ++i) { new ((void*)(dst + i)) U(std::move(src[i])); } } template C4_ALWAYS_INLINE void make_room ( U * dst, I dstsz, U const* src, I srcsz, I room, I pos ) { C4_ASSERT(pos >= 0 && pos < srcsz || (srcsz == 0 && pos == 0)); C4_ASSERT(pos >= 0 && pos < dstsz || (dstsz == 0 && pos == 0)); C4_ASSERT(srcsz+room <= dstsz); C4_UNUSED(dstsz); make_room(dst, src, srcsz, room, pos); } //----------------------------------------------------------------------------- /** destroy room at the beginning of buf, which has a current size of n */ template C4_ALWAYS_INLINE typename std::enable_if::value || (std::is_standard_layout::value && std::is_trivial::value), void>::type destroy_room(U *buf, I n, I room) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && room >= 0); C4_ASSERT(room <= n); if(room < n) { memmove(buf, buf + room, (n - room) * sizeof(U)); } else { // nothing to do - no need to destroy scalar types } } /** destroy room at the beginning of buf, which has a current size of n */ template C4_ALWAYS_INLINE typename std::enable_if< ! (std::is_scalar::value || (std::is_standard_layout::value && std::is_trivial::value)), void>::type destroy_room(U *buf, I n, I room) { C4_ASSERT(n >= 0 && room >= 0); C4_ASSERT(room <= n); if(room < n) { for(I i = 0, e = n - room; i < e; ++i) { buf[i] = std::move(buf[i + room]); } } else { for(I i = 0; i < n; ++i) { buf[i].~U(); } } } /** destroy room to the right of pos, copying to a different buffer */ template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type destroy_room(U *dst, U const* src, I n, I room, I pos) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && room >= 0 && pos >= 0); C4_ASSERT(pos C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible::value, void>::type destroy_room(U *dst, U const* src, I n, I room, I pos) { C4_ASSERT(n >= 0 && room >= 0 && pos >= 0); C4_ASSERT(pos < n); C4_ASSERT(pos + room <= n); for(I i = 0; i < pos; ++i) { new ((void*)(dst + i)) U(std::move(src[i])); } src += room + pos; dst += pos; for(I i = 0, e = n - pos - room; i < e; ++i) { new ((void*)(dst + i)) U(std::move(src[i])); } } C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 #endif /* _C4_CTOR_DTOR_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/ctor_dtor.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/allocator.hpp // https://github.com/biojppm/c4core/src/c4/allocator.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_ALLOCATOR_HPP_ #define _C4_ALLOCATOR_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/memory_resource.hpp //#include "c4/memory_resource.hpp" #if !defined(C4_MEMORY_RESOURCE_HPP_) && !defined(_C4_MEMORY_RESOURCE_HPP_) #error "amalgamate: file c4/memory_resource.hpp must have been included at this point" #endif /* C4_MEMORY_RESOURCE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/ctor_dtor.hpp //#include "c4/ctor_dtor.hpp" #if !defined(C4_CTOR_DTOR_HPP_) && !defined(_C4_CTOR_DTOR_HPP_) #error "amalgamate: file c4/ctor_dtor.hpp must have been included at this point" #endif /* C4_CTOR_DTOR_HPP_ */ #include // std::allocator_traits //included above: //#include /** @file allocator.hpp Contains classes to make typeful allocations (note * that memory resources are typeless) */ /** @defgroup mem_res_providers Memory resource providers * @brief Policy classes which provide a memory resource for * use in an allocator. * @ingroup memory */ /** @defgroup allocators Allocators * @brief Lightweight classes that act as handles to specific memory * resources and provide typeful memory. * @ingroup memory */ namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") namespace detail { template inline size_t size_for (size_t num_objs) noexcept { return num_objs * sizeof(T); } template< > inline size_t size_for(size_t num_objs) noexcept { return num_objs; } } // namespace detail //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** provides a per-allocator memory resource * @ingroup mem_res_providers */ class MemRes { public: MemRes() : m_resource(get_memory_resource()) {} MemRes(MemoryResource* r) noexcept : m_resource(r ? r : get_memory_resource()) {} MemoryResource* resource() const { return m_resource; } private: MemoryResource* m_resource; }; /** the allocators using this will default to the global memory resource * @ingroup mem_res_providers */ class MemResGlobal { public: MemResGlobal() = default; MemResGlobal(MemoryResource* r) noexcept { C4_UNUSED(r); C4_ASSERT(r == get_memory_resource()); } static MemoryResource* resource() { return get_memory_resource(); } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- namespace detail { template struct _AllocatorUtil; template struct has_no_alloc : public std::integral_constant::value) && std::is_constructible::value> {}; // std::uses_allocator_v && std::is_constructible // ie can construct(std::allocator_arg_t, MemoryResource*, Args...) template struct has_alloc_arg : public std::integral_constant::value && std::is_constructible::value> {}; // std::uses_allocator && std::is_constructible // ie, can construct(Args..., MemoryResource*) template struct has_alloc : public std::integral_constant::value && std::is_constructible::value> {}; } // namespace detail template struct detail::_AllocatorUtil : public MemRes { using MemRes::MemRes; /** for construct: * @see http://en.cppreference.com/w/cpp/experimental/polymorphic_allocator/construct */ // 1. types with no allocators template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type construct(U *ptr, Args &&...args) { c4::construct(ptr, std::forward(args)...); } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type construct_n(U* ptr, I n, Args&&... args) { c4::construct_n(ptr, n, std::forward(args)...); } // 2. types using allocators (ie, containers) // 2.1. can construct(std::allocator_arg_t, MemoryResource*, Args...) template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type construct(U* ptr, Args&&... args) { c4::construct(ptr, std::allocator_arg, this->resource(), std::forward(args)...); } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type construct_n(U* ptr, I n, Args&&... args) { c4::construct_n(ptr, n, std::allocator_arg, this->resource(), std::forward(args)...); } // 2.2. can construct(Args..., MemoryResource*) template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type construct(U* ptr, Args&&... args) { c4::construct(ptr, std::forward(args)..., this->resource()); } template C4_ALWAYS_INLINE typename std::enable_if::value, void>::type construct_n(U* ptr, I n, Args&&... args) { c4::construct_n(ptr, n, std::forward(args)..., this->resource()); } template static C4_ALWAYS_INLINE void destroy(U* ptr) { c4::destroy(ptr); } template static C4_ALWAYS_INLINE void destroy_n(U* ptr, I n) { c4::destroy_n(ptr, n); } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** An allocator is simply a proxy to a memory resource. * @param T * @param MemResProvider * @ingroup allocators */ template class Allocator : public detail::_AllocatorUtil // NOLINT(*-member-functions) { public: using impl_type = detail::_AllocatorUtil; using value_type = T; using pointer = T*; using const_pointer = T const*; using reference = T&; using const_reference = T const&; using size_type = size_t; using difference_type = std::ptrdiff_t; using propagate_on_container_move_assigment = std::true_type; public: template bool operator== (Allocator const& that) const { return this->resource() == that.resource(); } template bool operator!= (Allocator const& that) const { return this->resource() != that.resource(); } public: template friend class Allocator; template struct rebind { using other = Allocator; }; template typename rebind::other rebound() { return typename rebind::other(*this); } public: using impl_type::impl_type; Allocator() : impl_type() {} // VS demands this template Allocator(Allocator const& that) : impl_type(that.resource()) {} Allocator(Allocator const&) = default; Allocator(Allocator &&) = default; Allocator& operator= (Allocator const&) = default; // why? @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator Allocator& operator= (Allocator &&) = default; /** returns a default-constructed polymorphic allocator object * @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator/select_on_container_copy_construction */ Allocator select_on_container_copy_construct() const { return Allocator(*this); } T* allocate(size_t num_objs, size_t alignment=alignof(T)) { C4_ASSERT(this->resource() != nullptr); C4_ASSERT(alignment >= alignof(T)); void* vmem = this->resource()->allocate(detail::size_for(num_objs), alignment); T* mem = static_cast(vmem); return mem; } void deallocate(T * ptr, size_t num_objs, size_t alignment=alignof(T)) { C4_ASSERT(this->resource() != nullptr); C4_ASSERT(alignment>= alignof(T)); this->resource()->deallocate(ptr, detail::size_for(num_objs), alignment); } T* reallocate(T* ptr, size_t oldnum, size_t newnum, size_t alignment=alignof(T)) { C4_ASSERT(this->resource() != nullptr); C4_ASSERT(alignment >= alignof(T)); void* vmem = this->resource()->reallocate(ptr, detail::size_for(oldnum), detail::size_for(newnum), alignment); T* mem = static_cast(vmem); return mem; } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @ingroup allocators */ template class SmallAllocator : public detail::_AllocatorUtil // NOLINT(*-member-functions) { static_assert(Alignment >= alignof(T), "invalid alignment"); using impl_type = detail::_AllocatorUtil; alignas(Alignment) char m_arr[N * sizeof(T)]; size_t m_num{0}; public: using value_type = T; using pointer = T*; using const_pointer = T const*; using reference = T&; using const_reference = T const&; using size_type = size_t; using difference_type = std::ptrdiff_t; using propagate_on_container_move_assigment = std::true_type; template bool operator== (SmallAllocator const&) const { return false; } template bool operator!= (SmallAllocator const&) const { return true; } public: template friend class SmallAllocator; template struct rebind { using other = SmallAllocator; }; template typename rebind::other rebound() { return typename rebind::other(*this); } public: using impl_type::impl_type; SmallAllocator() : impl_type() {} // VS demands this template SmallAllocator(SmallAllocator const& that) : impl_type(that.resource()) { C4_ASSERT(that.m_num == 0); } SmallAllocator(SmallAllocator const&) = default; SmallAllocator(SmallAllocator &&) = default; SmallAllocator& operator= (SmallAllocator const&) = default; // WTF? why? @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator SmallAllocator& operator= (SmallAllocator &&) = default; /** returns a default-constructed polymorphic allocator object * @see http://en.cppreference.com/w/cpp/memory/polymorphic_allocator/select_on_container_copy_construction */ SmallAllocator select_on_container_copy_construct() const { return SmallAllocator(*this); } T* allocate(size_t num_objs, size_t alignment=Alignment) { C4_ASSERT(this->resource() != nullptr); C4_ASSERT(alignment >= alignof(T)); void *vmem; if(m_num + num_objs <= N) { vmem = (m_arr + m_num * sizeof(T)); } else { vmem = this->resource()->allocate(num_objs * sizeof(T), alignment); } m_num += num_objs; T *mem = static_cast(vmem); return mem; } void deallocate(T * ptr, size_t num_objs, size_t alignment=Alignment) { C4_ASSERT(m_num >= num_objs); m_num -= num_objs; if((char*)ptr >= m_arr && (char*)ptr < m_arr + (N * sizeof(T))) { return; } C4_ASSERT(this->resource() != nullptr); C4_ASSERT(alignment >= alignof(T)); this->resource()->deallocate(ptr, num_objs * sizeof(T), alignment); } T* reallocate(T * ptr, size_t oldnum, size_t newnum, size_t alignment=Alignment) { C4_ASSERT(this->resource() != nullptr); C4_ASSERT(alignment >= alignof(T)); if(oldnum <= N && newnum <= N) { return m_arr; } else if(oldnum <= N && newnum > N) { return allocate(newnum, alignment); } else if(oldnum > N && newnum <= N) { deallocate(ptr, oldnum, alignment); return m_arr; } void* vmem = this->resource()->reallocate(ptr, oldnum * sizeof(T), newnum * sizeof(T), alignment); T* mem = static_cast(vmem); return mem; } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** An allocator making use of the global memory resource. * @ingroup allocators */ template using allocator = Allocator; /** An allocator with a per-instance memory resource * @ingroup allocators */ template using allocator_mr = Allocator; /** @ingroup allocators */ template using small_allocator = SmallAllocator; /** @ingroup allocators */ template using small_allocator_mr = SmallAllocator; C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 #endif /* _C4_ALLOCATOR_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/allocator.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/char_traits.hpp // https://github.com/biojppm/c4core/src/c4/char_traits.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_CHAR_TRAITS_HPP_ #define _C4_CHAR_TRAITS_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/config.hpp //#include "c4/config.hpp" #if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) #error "amalgamate: file c4/config.hpp must have been included at this point" #endif /* C4_CONFIG_HPP_ */ #include // needed because of std::char_traits #include #include namespace c4 { C4_ALWAYS_INLINE bool isspace(char c) { return std::isspace(c) != 0; } C4_ALWAYS_INLINE bool isspace(wchar_t c) { return std::iswspace(static_cast(c)) != 0; } //----------------------------------------------------------------------------- template struct char_traits; template<> struct char_traits : public std::char_traits { constexpr static const char whitespace_chars[] = " \f\n\r\t\v"; constexpr static const size_t num_whitespace_chars = sizeof(whitespace_chars) - 1; }; template<> struct char_traits : public std::char_traits { constexpr static const wchar_t whitespace_chars[] = L" \f\n\r\t\v"; constexpr static const size_t num_whitespace_chars = sizeof(whitespace_chars) - 1; }; //----------------------------------------------------------------------------- namespace detail { template struct needed_chars; template<> struct needed_chars { template C4_ALWAYS_INLINE constexpr static SizeType for_bytes(SizeType num_bytes) { return num_bytes; } }; template<> struct needed_chars { template C4_ALWAYS_INLINE constexpr static SizeType for_bytes(SizeType num_bytes) { // wchar_t is not necessarily 2 bytes. return (num_bytes / static_cast(sizeof(wchar_t))) + ((num_bytes & static_cast(SizeType(sizeof(wchar_t)) - SizeType(1))) != 0); } }; } // namespace detail /** get the number of C characters needed to store a number of bytes */ template C4_ALWAYS_INLINE constexpr SizeType num_needed_chars(SizeType num_bytes) { return detail::needed_chars::for_bytes(num_bytes); } //----------------------------------------------------------------------------- /** get the given text string as either char or wchar_t according to the given type */ #define C4_TXTTY(txt, type) \ /* is there a smarter way to do this? */\ c4::detail::literal_as::get(txt, C4_WIDEN(txt)) namespace detail { template struct literal_as; template<> struct literal_as { C4_ALWAYS_INLINE static constexpr const char* get(const char* str, const wchar_t *) { return str; } }; template<> struct literal_as { C4_ALWAYS_INLINE static constexpr const wchar_t* get(const char*, const wchar_t *wstr) { return wstr; } }; } // namespace detail } // namespace c4 #endif /* _C4_CHAR_TRAITS_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/char_traits.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/hash.hpp // https://github.com/biojppm/c4core/src/c4/hash.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_HASH_HPP_ #define _C4_HASH_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/config.hpp //#include "c4/config.hpp" #if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) #error "amalgamate: file c4/config.hpp must have been included at this point" #endif /* C4_CONFIG_HPP_ */ #include /** @file hash.hpp */ /** @defgroup hash Hash utils * @see http://aras-p.info/blog/2016/08/02/Hash-Functions-all-the-way-down/ */ namespace c4 { namespace detail { /** @internal * @ingroup hash * @see this was taken a great answer in stackoverflow: * https://stackoverflow.com/a/34597785/5875572 * @see http://aras-p.info/blog/2016/08/02/Hash-Functions-all-the-way-down/ */ template class basic_fnv1a final { static_assert(std::is_unsigned::value, "need unsigned integer"); public: using result_type = ResultT; private: result_type state_ {}; public: C4_CONSTEXPR14 basic_fnv1a() noexcept : state_ {OffsetBasis} {} C4_CONSTEXPR14 void update(const void *const data, const size_t size) noexcept { auto const* cdata = static_cast(data); auto acc = this->state_; for(size_t i = 0; i < size; ++i) { const auto next = size_t(cdata[i]); acc = (acc ^ next) * Prime; } this->state_ = acc; } C4_CONSTEXPR14 result_type digest() const noexcept { return this->state_; } }; using fnv1a_32 = basic_fnv1a; using fnv1a_64 = basic_fnv1a; template struct fnv1a; template<> struct fnv1a<32> { using type = fnv1a_32; }; template<> struct fnv1a<64> { using type = fnv1a_64; }; } // namespace detail /** @ingroup hash */ template using fnv1a_t = typename detail::fnv1a::type; /** @ingroup hash */ C4_CONSTEXPR14 inline size_t hash_bytes(const void *const data, const size_t size) noexcept { fnv1a_t fn{}; fn.update(data, size); return fn.digest(); } /** * @overload hash_bytes * @ingroup hash */ template C4_CONSTEXPR14 inline size_t hash_bytes(const char (&str)[N]) noexcept { fnv1a_t fn{}; fn.update(str, N); return fn.digest(); } } // namespace c4 #endif // _C4_HASH_HPP_ // (end https://github.com/biojppm/c4core/src/c4/hash.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/szconv.hpp // https://github.com/biojppm/c4core/src/c4/szconv.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_SZCONV_HPP_ #define _C4_SZCONV_HPP_ /** @file szconv.hpp utilities to deal safely with narrowing conversions */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/config.hpp //#include "c4/config.hpp" #if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) #error "amalgamate: file c4/config.hpp must have been included at this point" #endif /* C4_CONFIG_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ #include namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") /** @todo this would be so much easier with calls to numeric_limits::max()... */ template struct is_narrower_size : std::conditional < (std::is_signed::value == std::is_signed::value) ? (sizeof(SizeOut) < sizeof(SizeIn)) : ( (sizeof(SizeOut) < sizeof(SizeIn)) || ( (sizeof(SizeOut) == sizeof(SizeIn)) && (std::is_signed::value && std::is_unsigned::value) ) ), std::true_type, std::false_type >::type { static_assert(std::is_integral::value, "must be integral type"); static_assert(std::is_integral::value, "must be integral type"); }; /** when SizeOut is wider than SizeIn, assignment can occur without reservations */ template C4_ALWAYS_INLINE typename std::enable_if< ! is_narrower_size::value, SizeOut>::type szconv(SizeIn sz) noexcept { return static_cast(sz); } /** when SizeOut is narrower than SizeIn, narrowing will occur, so we check * for overflow. Note that this check is done only if C4_XASSERT is enabled. * @see C4_XASSERT */ template C4_ALWAYS_INLINE typename std::enable_if::value, SizeOut>::type szconv(SizeIn sz) { C4_XASSERT(sz >= 0); C4_XASSERT_MSG((SizeIn)sz <= (SizeIn)std::numeric_limits::max(), "size conversion overflow: in=%zu", (size_t)sz); SizeOut szo = static_cast(sz); return szo; } C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 #endif /* _C4_SZCONV_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/szconv.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/blob.hpp // https://github.com/biojppm/c4core/src/c4/blob.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_BLOB_HPP_ #define _C4_BLOB_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/types.hpp //#include "c4/types.hpp" #if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) #error "amalgamate: file c4/types.hpp must have been included at this point" #endif /* C4_TYPES_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ /** @file blob.hpp Mutable and immutable binary data blobs. */ namespace c4 { template struct blob_; namespace detail { template struct is_blob_type : std::integral_constant {}; template struct is_blob_type> : std::integral_constant {}; template struct is_blob_value_type : std::integral_constant::value || std::is_trivially_copyable::value)> {}; } // namespace // NOLINTBEGIN(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) template struct blob_ { static_assert(std::is_same::value || std::is_same::value, "must be either byte or cbyte"); static_assert(sizeof(T) == 1u, "must be either byte or cbyte"); public: T * buf; size_t len; public: C4_ALWAYS_INLINE blob_() noexcept = default; C4_ALWAYS_INLINE blob_(blob_ const& that) noexcept = default; C4_ALWAYS_INLINE blob_(blob_ && that) noexcept = default; C4_ALWAYS_INLINE blob_& operator=(blob_ && that) noexcept = default; C4_ALWAYS_INLINE blob_& operator=(blob_ const& that) noexcept = default; template::value && std::is_same::type, T>::value, U>::type> C4_ALWAYS_INLINE blob_(blob_ const& that) noexcept : buf(that.buf), len(that.len) {} // NOLINT template::value && std::is_same::type, T>::value, U>::type> C4_ALWAYS_INLINE blob_(blob_ && that) noexcept : buf(that.buf), len(that.len) {} // NOLINT template::value && std::is_same::type, T>::value, U>::type> C4_ALWAYS_INLINE blob_& operator=(blob_ && that) noexcept { buf = that.buf; len = that.len; } // NOLINT template::value && std::is_same::type, T>::value, U>::type> C4_ALWAYS_INLINE blob_& operator=(blob_ const& that) noexcept { buf = that.buf; len = that.len; } // NOLINT C4_ALWAYS_INLINE blob_(void *ptr, size_t n) noexcept : buf(reinterpret_cast(ptr)), len(n) {} // NOLINT C4_ALWAYS_INLINE blob_(void const *ptr, size_t n) noexcept : buf(reinterpret_cast(ptr)), len(n) {} // NOLINT #define _C4_REQUIRE_BLOBTYPE(ty) class=typename std::enable_if<((!detail::is_blob_type::value) && (detail::is_blob_value_type::value)), T>::type template C4_ALWAYS_INLINE blob_(U &var) noexcept : buf(reinterpret_cast(&var)), len(sizeof(U)) {} // NOLINT template C4_ALWAYS_INLINE blob_(U *ptr, size_t n) noexcept : buf(reinterpret_cast(ptr)), len(sizeof(U) * n) { C4_ASSERT(is_aligned(ptr)); } // NOLINT template C4_ALWAYS_INLINE blob_& operator= (U &var) noexcept { buf = reinterpret_cast(&var); len = sizeof(U); return *this; } // NOLINT template C4_ALWAYS_INLINE blob_(U (&arr)[N]) noexcept : buf(reinterpret_cast(arr)), len(sizeof(U) * N) {} // NOLINT template C4_ALWAYS_INLINE blob_& operator= (U (&arr)[N]) noexcept { buf = reinterpret_cast(arr); len = sizeof(U) * N; return *this; } // NOLINT #undef _C4_REQUIRE_BLOBTYPE }; // NOLINTEND(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) /** an immutable binary blob */ using cblob = blob_; /** a mutable binary blob */ using blob = blob_< byte>; C4_MUST_BE_TRIVIAL_COPY(blob); C4_MUST_BE_TRIVIAL_COPY(cblob); } // namespace c4 #endif // _C4_BLOB_HPP_ // (end https://github.com/biojppm/c4core/src/c4/blob.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/substr_fwd.hpp // https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_SUBSTR_FWD_HPP_ #define _C4_SUBSTR_FWD_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/export.hpp //#include "c4/export.hpp" #if !defined(C4_EXPORT_HPP_) && !defined(_C4_EXPORT_HPP_) #error "amalgamate: file c4/export.hpp must have been included at this point" #endif /* C4_EXPORT_HPP_ */ namespace c4 { #ifndef DOXYGEN template struct basic_substring; using csubstr = C4CORE_EXPORT basic_substring; using substr = C4CORE_EXPORT basic_substring; #endif // !DOXYGEN } // namespace c4 #endif /* _C4_SUBSTR_FWD_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/substr.hpp // https://github.com/biojppm/c4core/src/c4/substr.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_SUBSTR_HPP_ #define _C4_SUBSTR_HPP_ /** @file substr.hpp read+write string views */ //included above: //#include //included above: //#include //included above: //#include // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/config.hpp //#include "c4/config.hpp" #if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) #error "amalgamate: file c4/config.hpp must have been included at this point" #endif /* C4_CONFIG_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp //#include "c4/substr_fwd.hpp" #if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) #error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" #endif /* C4_SUBSTR_FWD_HPP_ */ #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wtype-limits" // disable warnings on size_t>=0, used heavily in assertions below. These assertions are a preparation step for providing the index type as a template parameter. # pragma GCC diagnostic ignored "-Wuseless-cast" # pragma GCC diagnostic ignored "-Wold-style-cast" #endif namespace c4 { /** @defgroup doc_substr Substring: read/write string views * @{ */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ namespace detail { template static inline void _do_reverse(C *C4_RESTRICT first, C *C4_RESTRICT last) { while(last > first) { C tmp = *last; *last-- = *first; *first++ = tmp; } } } // namespace detail /** @endcond */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ // utility macros to deuglify SFINAE code; undefined after the class. // https://stackoverflow.com/questions/43051882/how-to-disable-a-class-member-funrtion-for-certain-template-types #define C4_REQUIRE_RW(ret_type) \ template \ typename std::enable_if< ! std::is_const::value, ret_type>::type /** @endcond */ /** a non-owning string-view, consisting of a character pointer * and a length. * * @note The pointer is explicitly restricted. * * @see a [quickstart * sample](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#ga43e253da0692c13967019446809c1113) * in rapidyaml's documentation. */ template struct C4CORE_EXPORT basic_substring // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) { public: /** a restricted pointer to the first character of the substring */ C * C4_RESTRICT str; /** the length of the substring */ size_t len; public: /** @name Types */ /** @{ */ using CC = typename std::add_const::type; //!< CC=const char using NCC_ = typename std::remove_const::type; //!< NCC_=non const char using ro_substr = basic_substring; using rw_substr = basic_substring; using char_type = C; using size_type = size_t; using iterator = C*; using const_iterator = CC*; enum : size_t { npos = (size_t)-1, NONE = (size_t)-1 }; /// convert automatically to substring of const C template C4_ALWAYS_INLINE operator typename std::enable_if::value, ro_substr const&>::type () const noexcept { return *(ro_substr const*)this; // don't call the str+len ctor because it does a check } /** @} */ public: /** @name Default construction and assignment */ /** @{ */ C4_ALWAYS_INLINE constexpr basic_substring() noexcept : str(), len() {} C4_ALWAYS_INLINE basic_substring(basic_substring const&) noexcept = default; C4_ALWAYS_INLINE basic_substring(basic_substring &&) noexcept = default; C4_ALWAYS_INLINE basic_substring(std::nullptr_t) noexcept : str(nullptr), len(0) {} C4_ALWAYS_INLINE basic_substring& operator= (basic_substring const&) noexcept = default; C4_ALWAYS_INLINE basic_substring& operator= (basic_substring &&) noexcept = default; C4_ALWAYS_INLINE basic_substring& operator= (std::nullptr_t) noexcept { str = nullptr; len = 0; return *this; } C4_ALWAYS_INLINE void clear() noexcept { str = nullptr; len = 0; } /** @} */ public: /** @name Construction and assignment from characters with the same type */ /** @{ */ /** Construct from an array. * @warning the input string need not be zero terminated, but the * length is taken as if the string was zero terminated */ template C4_ALWAYS_INLINE constexpr basic_substring(C (&s_)[N]) noexcept : str(s_), len(N-1) {} /** Construct from a pointer and length. * @warning the input string need not be zero terminated. */ C4_ALWAYS_INLINE basic_substring(C *s_, size_t len_) noexcept : str(s_), len(len_) { C4_ASSERT(str || !len_); } /** Construct from two pointers. * @warning the end pointer MUST BE larger than or equal to the begin pointer * @warning the input string need not be zero terminated */ C4_ALWAYS_INLINE basic_substring(C *beg_, C *end_) noexcept : str(beg_), len(static_cast(end_ - beg_)) { C4_ASSERT(end_ >= beg_); } /** Construct from a C-string (zero-terminated string) * @warning the input string MUST BE zero terminated. * @warning will call strlen() * @note this overload uses SFINAE to prevent it from overriding the array ctor * @see For a more detailed explanation on why the plain overloads cannot * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ template::value || std::is_same::value, int>::type=0> C4_ALWAYS_INLINE basic_substring(U s_) noexcept : str(s_), len(s_ ? strlen(s_) : 0) {} /** Assign from an array. * @warning the input string need not be zero terminated, but the * length is taken as if the string was zero terminated */ template C4_ALWAYS_INLINE void assign(C (&s_)[N]) noexcept { str = (s_); len = (N-1); } /** Assign from a pointer and length. * @warning the input string need not be zero terminated. */ C4_ALWAYS_INLINE void assign(C *s_, size_t len_) noexcept { str = s_; len = len_; C4_ASSERT(str || !len_); } /** Assign from two pointers. * @warning the end pointer MUST BE larger than or equal to the begin pointer * @warning the input string need not be zero terminated. */ C4_ALWAYS_INLINE void assign(C *beg_, C *end_) noexcept { C4_ASSERT(end_ >= beg_); str = (beg_); len = static_cast(end_ - beg_); } /** Assign from a C-string (zero-terminated string) * @warning the input string must be zero terminated. * @warning will call strlen() * @note this overload uses SFINAE to prevent it from overriding the array ctor * @see For a more detailed explanation on why the plain overloads cannot * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ template::value || std::is_same::value, int>::type=0> C4_ALWAYS_INLINE void assign(U s_) noexcept { str = (s_); len = (s_ ? strlen(s_) : 0); } /** Assign from an array. * @warning the input string need not be zero terminated. */ template C4_ALWAYS_INLINE basic_substring& operator= (C (&s_)[N]) noexcept { str = (s_); len = (N-1); return *this; } /** Assign from a C-string (zero-terminated string) * @warning the input string MUST BE zero terminated. * @warning will call strlen() * @note this overload uses SFINAE to prevent it from overriding the array ctor * @see For a more detailed explanation on why the plain overloads cannot * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ template::value || std::is_same::value, int>::type=0> C4_ALWAYS_INLINE basic_substring& operator= (U s_) noexcept { str = s_; len = s_ ? strlen(s_) : 0; return *this; } /** @} */ public: /** @name Standard accessor methods */ /** @{ */ C4_ALWAYS_INLINE C4_PURE bool has_str() const noexcept { return ! empty() && str[0] != C(0); } C4_ALWAYS_INLINE C4_PURE bool empty() const noexcept { return (len == 0 || str == nullptr); } C4_ALWAYS_INLINE C4_PURE bool not_empty() const noexcept { return (len != 0 && str != nullptr); } C4_ALWAYS_INLINE C4_PURE size_t size() const noexcept { return len; } C4_ALWAYS_INLINE C4_PURE iterator begin() noexcept { return str; } C4_ALWAYS_INLINE C4_PURE iterator end () noexcept { return str + len; } C4_ALWAYS_INLINE C4_PURE const_iterator begin() const noexcept { return str; } C4_ALWAYS_INLINE C4_PURE const_iterator end () const noexcept { return str + len; } C4_ALWAYS_INLINE C4_PURE C * data() noexcept { return str; } C4_ALWAYS_INLINE C4_PURE C const* data() const noexcept { return str; } C4_ALWAYS_INLINE C4_PURE C & operator[] (size_t i) noexcept { C4_ASSERT(i >= 0 && i < len); return str[i]; } C4_ALWAYS_INLINE C4_PURE C const& operator[] (size_t i) const noexcept { C4_ASSERT(i >= 0 && i < len); return str[i]; } C4_ALWAYS_INLINE C4_PURE C & front() noexcept { C4_ASSERT(len > 0 && str != nullptr); return *str; } C4_ALWAYS_INLINE C4_PURE C const& front() const noexcept { C4_ASSERT(len > 0 && str != nullptr); return *str; } C4_ALWAYS_INLINE C4_PURE C & back() noexcept { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); } C4_ALWAYS_INLINE C4_PURE C const& back() const noexcept { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); } /** @} */ public: /** @name Comparison methods */ /** @{ */ C4_PURE int compare(C const c) const noexcept { C4_XASSERT((str != nullptr) || len == 0); if(C4_LIKELY(str != nullptr && len > 0)) return (*str != c) ? *str - c : (static_cast(len) - 1); else return -1; } C4_PURE int compare(const char *C4_RESTRICT that, size_t sz) const noexcept { C4_XASSERT(that || sz == 0); C4_XASSERT(str || len == 0); if(C4_LIKELY(str && that)) { { const size_t min = len < sz ? len : sz; for(size_t i = 0; i < min; ++i) if(str[i] != that[i]) return str[i] < that[i] ? -1 : 1; } if(len < sz) return -1; else if(len == sz) return 0; else return 1; } else if(len == sz) { C4_XASSERT(len == 0 && sz == 0); return 0; } return len < sz ? -1 : 1; } C4_ALWAYS_INLINE C4_PURE int compare(ro_substr const that) const noexcept { return this->compare(that.str, that.len); } C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return str == nullptr; } C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return str != nullptr; } C4_ALWAYS_INLINE C4_PURE bool operator== (C const c) const noexcept { return this->compare(c) == 0; } C4_ALWAYS_INLINE C4_PURE bool operator!= (C const c) const noexcept { return this->compare(c) != 0; } C4_ALWAYS_INLINE C4_PURE bool operator< (C const c) const noexcept { return this->compare(c) < 0; } C4_ALWAYS_INLINE C4_PURE bool operator> (C const c) const noexcept { return this->compare(c) > 0; } C4_ALWAYS_INLINE C4_PURE bool operator<= (C const c) const noexcept { return this->compare(c) <= 0; } C4_ALWAYS_INLINE C4_PURE bool operator>= (C const c) const noexcept { return this->compare(c) >= 0; } template C4_ALWAYS_INLINE C4_PURE bool operator== (basic_substring const that) const noexcept { return this->compare(that) == 0; } template C4_ALWAYS_INLINE C4_PURE bool operator!= (basic_substring const that) const noexcept { return this->compare(that) != 0; } template C4_ALWAYS_INLINE C4_PURE bool operator< (basic_substring const that) const noexcept { return this->compare(that) < 0; } template C4_ALWAYS_INLINE C4_PURE bool operator> (basic_substring const that) const noexcept { return this->compare(that) > 0; } template C4_ALWAYS_INLINE C4_PURE bool operator<= (basic_substring const that) const noexcept { return this->compare(that) <= 0; } template C4_ALWAYS_INLINE C4_PURE bool operator>= (basic_substring const that) const noexcept { return this->compare(that) >= 0; } template C4_ALWAYS_INLINE C4_PURE bool operator== (const char (&that)[N]) const noexcept { return this->compare(that, N-1) == 0; } template C4_ALWAYS_INLINE C4_PURE bool operator!= (const char (&that)[N]) const noexcept { return this->compare(that, N-1) != 0; } template C4_ALWAYS_INLINE C4_PURE bool operator< (const char (&that)[N]) const noexcept { return this->compare(that, N-1) < 0; } template C4_ALWAYS_INLINE C4_PURE bool operator> (const char (&that)[N]) const noexcept { return this->compare(that, N-1) > 0; } template C4_ALWAYS_INLINE C4_PURE bool operator<= (const char (&that)[N]) const noexcept { return this->compare(that, N-1) <= 0; } template C4_ALWAYS_INLINE C4_PURE bool operator>= (const char (&that)[N]) const noexcept { return this->compare(that, N-1) >= 0; } /** @} */ public: /** @name Sub-selection methods */ /** @{ */ /** true if *this is a substring of that (ie, from the same buffer) */ C4_ALWAYS_INLINE C4_PURE bool is_sub(ro_substr const that) const noexcept { return that.is_super(*this); } /** true if that is a substring of *this (ie, from the same buffer) */ C4_ALWAYS_INLINE C4_PURE bool is_super(ro_substr const that) const noexcept { if(C4_LIKELY(len > 0)) return that.str >= str && that.str+that.len <= str+len; else return that.len == 0 && that.str == str && str != nullptr; } /** true if there is overlap of at least one element between that and *this */ C4_ALWAYS_INLINE C4_PURE bool overlaps(ro_substr const that) const noexcept { // thanks @timwynants return that.str+that.len > str && that.str < str+len; } public: /** return [first,len[ */ C4_ALWAYS_INLINE C4_PURE basic_substring sub(size_t first) const noexcept { C4_ASSERT(first >= 0 && first <= len); return basic_substring(str + first, len - first); } /** return [first,first+num[. If num==npos, return [first,len[ */ C4_ALWAYS_INLINE C4_PURE basic_substring sub(size_t first, size_t num) const noexcept { C4_ASSERT(first >= 0 && first <= len); C4_ASSERT((num >= 0 && num <= len) || (num == npos)); size_t rnum = num != npos ? num : len - first; C4_ASSERT((first >= 0 && first + rnum <= len) || (num == 0)); return basic_substring(str + first, rnum); } /** return [first,last[. If last==npos, return [first,len[ */ C4_ALWAYS_INLINE C4_PURE basic_substring range(size_t first, size_t last=npos) const noexcept { C4_ASSERT(first >= 0 && first <= len); last = last != npos ? last : len; C4_ASSERT(first <= last); C4_ASSERT(last >= 0 && last <= len); return basic_substring(str + first, last - first); } /** return the first @p num elements: [0,num[*/ C4_ALWAYS_INLINE C4_PURE basic_substring first(size_t num) const noexcept { C4_ASSERT(num <= len || num == npos); return basic_substring(str, num != npos ? num : len); } /** return the last @p num elements: [len-num,len[*/ C4_ALWAYS_INLINE C4_PURE basic_substring last(size_t num) const noexcept { C4_ASSERT(num <= len || num == npos); return num != npos ? basic_substring(str + len - num, num) : *this; } /** offset from the ends: return [left,len-right[ ; ie, trim a number of characters from the left and right. This is equivalent to python's negative list indices. */ C4_ALWAYS_INLINE C4_PURE basic_substring offs(size_t left, size_t right) const noexcept { C4_ASSERT(left >= 0 && left <= len); C4_ASSERT(right >= 0 && right <= len); C4_ASSERT(left <= len - right + 1); return basic_substring(str + left, len - right - left); } /** return [0, pos[ . Same as .first(pos), but provided for compatibility with .right_of() */ C4_ALWAYS_INLINE C4_PURE basic_substring left_of(size_t pos) const noexcept { C4_ASSERT(pos <= len || pos == npos); return (pos != npos) ? basic_substring(str, pos) : *this; } /** return [0, pos+include_pos[ . Same as .first(pos+1), but provided for compatibility with .right_of() */ C4_ALWAYS_INLINE C4_PURE basic_substring left_of(size_t pos, bool include_pos) const noexcept { C4_ASSERT(pos <= len || pos == npos); return (pos != npos) ? basic_substring(str, pos+include_pos) : *this; } /** return [pos+1, len[ */ C4_ALWAYS_INLINE C4_PURE basic_substring right_of(size_t pos) const noexcept { C4_ASSERT(pos <= len || pos == npos); return (pos != npos) ? basic_substring(str + (pos + 1), len - (pos + 1)) : basic_substring(str + len, size_t(0)); } /** return [pos+!include_pos, len[ */ C4_ALWAYS_INLINE C4_PURE basic_substring right_of(size_t pos, bool include_pos) const noexcept { C4_ASSERT(pos <= len || pos == npos); return (pos != npos) ? basic_substring(str + (pos + !include_pos), len - (pos + !include_pos)) : basic_substring(str + len, size_t(0)); } public: /** given @p subs a substring of the current string, get the * portion of the current string to the left of it */ C4_ALWAYS_INLINE C4_PURE basic_substring left_of(ro_substr const subs) const noexcept { C4_ASSERT(is_super(subs) || subs.empty()); auto ssb = subs.begin(); auto b = begin(); auto e = end(); if(ssb >= b && ssb <= e) return sub(0, static_cast(ssb - b)); else return sub(0, 0); } /** given @p subs a substring of the current string, get the * portion of the current string to the right of it */ C4_ALWAYS_INLINE C4_PURE basic_substring right_of(ro_substr const subs) const noexcept { C4_ASSERT(is_super(subs) || subs.empty()); auto sse = subs.end(); auto b = begin(); auto e = end(); if(sse >= b && sse <= e) return sub(static_cast(sse - b), static_cast(e - sse)); else return sub(0, 0); } /** @} */ public: /** @name Removing characters (trim()) / patterns (strip()) from the tips of the string */ /** @{ */ /** trim left */ basic_substring triml(const C c) const { if( ! empty()) { size_t pos = first_not_of(c); if(pos != npos) return sub(pos); } return sub(0, 0); } /** trim left ANY of the characters. * @see stripl() to remove a pattern from the left */ basic_substring triml(ro_substr chars) const { if( ! empty()) { size_t pos = first_not_of(chars); if(pos != npos) return sub(pos); } return sub(0, 0); } /** trim the character c from the right */ basic_substring trimr(const C c) const { if( ! empty()) { size_t pos = last_not_of(c, npos); if(pos != npos) return sub(0, pos+1); } return sub(0, 0); } /** trim right ANY of the characters * @see stripr() to remove a pattern from the right */ basic_substring trimr(ro_substr chars) const { if( ! empty()) { size_t pos = last_not_of(chars, npos); if(pos != npos) return sub(0, pos+1); } return sub(0, 0); } /** trim the character c left and right */ basic_substring trim(const C c) const { return triml(c).trimr(c); } /** trim left and right ANY of the characters * @see strip() to remove a pattern from the left and right */ basic_substring trim(ro_substr const chars) const { return triml(chars).trimr(chars); } /** remove a pattern from the left * @see triml() to remove characters*/ basic_substring stripl(ro_substr pattern) const { if( ! begins_with(pattern)) return *this; return sub(pattern.len < len ? pattern.len : len); } /** remove a pattern from the right * @see trimr() to remove characters*/ basic_substring stripr(ro_substr pattern) const { if( ! ends_with(pattern)) return *this; return left_of(len - (pattern.len < len ? pattern.len : len)); } /** @} */ public: /** @name Lookup methods */ /** @{ */ size_t find(const C c, size_t start_pos=0) const { return first_of(c, start_pos); } size_t find(ro_substr pattern, size_t start_pos=0) const { C4_ASSERT(start_pos == npos || (start_pos >= 0 && start_pos <= len)); if(len < pattern.len) return npos; for(size_t i = start_pos, e = len - pattern.len + 1; i < e; ++i) { bool gotit = true; for(size_t j = 0; j < pattern.len; ++j) { C4_ASSERT(i + j < len); if(str[i + j] != pattern.str[j]) { gotit = false; break; } } if(gotit) { return i; } } return npos; } public: /** count the number of occurrences of c */ size_t count(const C c, size_t pos=0) const { C4_ASSERT(pos >= 0 && pos <= len); size_t num = 0; pos = find(c, pos); while(pos != npos) { ++num; pos = find(c, pos + 1); } return num; } /** count the number of occurrences of s */ size_t count(ro_substr c, size_t pos=0) const { C4_ASSERT(pos >= 0 && pos <= len); size_t num = 0; pos = find(c, pos); while(pos != npos) { ++num; pos = find(c, pos + c.len); } return num; } /** get the substr consisting of the first occurrence of @p c after @p pos, or an empty substr if none occurs */ basic_substring select(const C c, size_t pos=0) const { pos = find(c, pos); return pos != npos ? sub(pos, 1) : basic_substring(); } /** get the substr consisting of the first occurrence of @p pattern after @p pos, or an empty substr if none occurs */ basic_substring select(ro_substr pattern, size_t pos=0) const { pos = find(pattern, pos); return pos != npos ? sub(pos, pattern.len) : basic_substring(); } public: struct first_of_any_result { size_t which; size_t pos; operator bool() const { return which != NONE && pos != npos; } }; first_of_any_result first_of_any(ro_substr s0, ro_substr s1) const { ro_substr s[2] = {s0, s1}; return first_of_any_iter(&s[0], &s[0] + 2); } first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2) const { ro_substr s[3] = {s0, s1, s2}; return first_of_any_iter(&s[0], &s[0] + 3); } first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2, ro_substr s3) const { ro_substr s[4] = {s0, s1, s2, s3}; return first_of_any_iter(&s[0], &s[0] + 4); } first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2, ro_substr s3, ro_substr s4) const { ro_substr s[5] = {s0, s1, s2, s3, s4}; return first_of_any_iter(&s[0], &s[0] + 5); } template first_of_any_result first_of_any_iter(It first_span, It last_span) const { for(size_t i = 0; i < len; ++i) { size_t curr = 0; for(It it = first_span; it != last_span; ++curr, ++it) { auto const& chars = *it; if((i + chars.len) > len) continue; bool gotit = true; for(size_t j = 0; j < chars.len; ++j) { C4_ASSERT(i + j < len); if(str[i + j] != chars[j]) { gotit = false; break; } } if(gotit) { return {curr, i}; } } } return {NONE, npos}; } public: /** true if the first character of the string is @p c */ bool begins_with(const C c) const { return len > 0 ? str[0] == c : false; } /** true if the first @p num characters of the string are @p c */ bool begins_with(const C c, size_t num) const { if(len < num) { return false; } for(size_t i = 0; i < num; ++i) { if(str[i] != c) { return false; } } return true; } /** true if the string begins with the given @p pattern */ bool begins_with(ro_substr pattern) const { if(len < pattern.len) { return false; } for(size_t i = 0; i < pattern.len; ++i) { if(str[i] != pattern[i]) { return false; } } return true; } /** true if the first character of the string is any of the given @p chars */ bool begins_with_any(ro_substr chars) const { if(len == 0) { return false; } for(size_t i = 0; i < chars.len; ++i) { if(str[0] == chars.str[i]) { return true; } } return false; } /** true if the last character of the string is @p c */ bool ends_with(const C c) const { return len > 0 ? str[len-1] == c : false; } /** true if the last @p num characters of the string are @p c */ bool ends_with(const C c, size_t num) const { if(len < num) { return false; } for(size_t i = len - num; i < len; ++i) { if(str[i] != c) { return false; } } return true; } /** true if the string ends with the given @p pattern */ bool ends_with(ro_substr pattern) const { if(len < pattern.len) { return false; } for(size_t i = 0, s = len-pattern.len; i < pattern.len; ++i) { if(str[s+i] != pattern[i]) { return false; } } return true; } /** true if the last character of the string is any of the given @p chars */ bool ends_with_any(ro_substr chars) const { if(len == 0) { return false; } for(size_t i = 0; i < chars.len; ++i) { if(str[len - 1] == chars[i]) { return true; } } return false; } public: /** @return the first position where c is found in the string, or npos if none is found */ size_t first_of(const C c, size_t start=0) const { C4_ASSERT(start == npos || (start >= 0 && start <= len)); for(size_t i = start; i < len; ++i) { if(str[i] == c) return i; } return npos; } /** @return the last position where c is found in the string, or npos if none is found */ size_t last_of(const C c, size_t start=npos) const { C4_ASSERT(start == npos || (start >= 0 && start <= len)); if(start == npos) start = len; for(size_t i = start-1; i != size_t(-1); --i) { if(str[i] == c) return i; } return npos; } /** @return the first position where ANY of the chars is found in the string, or npos if none is found */ size_t first_of(ro_substr chars, size_t start=0) const { C4_ASSERT(start == npos || (start >= 0 && start <= len)); for(size_t i = start; i < len; ++i) { for(size_t j = 0; j < chars.len; ++j) { if(str[i] == chars[j]) return i; } } return npos; } /** @return the last position where ANY of the chars is found in the string, or npos if none is found */ size_t last_of(ro_substr chars, size_t start=npos) const { C4_ASSERT(start == npos || (start >= 0 && start <= len)); if(start == npos) start = len; for(size_t i = start-1; i != size_t(-1); --i) { for(size_t j = 0; j < chars.len; ++j) { if(str[i] == chars[j]) return i; } } return npos; } public: size_t first_not_of(const C c) const { for(size_t i = 0; i < len; ++i) { if(str[i] != c) return i; } return npos; } size_t first_not_of(const C c, size_t start) const { C4_ASSERT((start >= 0 && start <= len) || (start == len && len == 0)); for(size_t i = start; i < len; ++i) { if(str[i] != c) return i; } return npos; } size_t last_not_of(const C c) const { for(size_t i = len-1; i != size_t(-1); --i) { if(str[i] != c) return i; } return npos; } size_t last_not_of(const C c, size_t start) const { C4_ASSERT(start == npos || (start >= 0 && start <= len)); if(start == npos) start = len; for(size_t i = start-1; i != size_t(-1); --i) { if(str[i] != c) return i; } return npos; } size_t first_not_of(ro_substr chars) const { for(size_t i = 0; i < len; ++i) { bool gotit = true; for(size_t j = 0; j < chars.len; ++j) { if(str[i] == chars.str[j]) { gotit = false; break; } } if(gotit) { return i; } } return npos; } size_t first_not_of(ro_substr chars, size_t start) const { C4_ASSERT((start >= 0 && start <= len) || (start == len && len == 0)); for(size_t i = start; i < len; ++i) { bool gotit = true; for(size_t j = 0; j < chars.len; ++j) { if(str[i] == chars.str[j]) { gotit = false; break; } } if(gotit) { return i; } } return npos; } size_t last_not_of(ro_substr chars) const { for(size_t i = len-1; i != size_t(-1); --i) { bool gotit = true; for(size_t j = 0; j < chars.len; ++j) { if(str[i] == chars.str[j]) { gotit = false; break; } } if(gotit) { return i; } } return npos; } size_t last_not_of(ro_substr chars, size_t start) const { C4_ASSERT(start == npos || (start >= 0 && start <= len)); if(start == npos) start = len; for(size_t i = start-1; i != size_t(-1); --i) { bool gotit = true; for(size_t j = 0; j < chars.len; ++j) { if(str[i] == chars.str[j]) { gotit = false; break; } } if(gotit) { return i; } } return npos; } /** @} */ public: /** @name Range lookup methods */ /** @{ */ /** get the range delimited by an open-close pair of characters. * @note There must be no nested pairs. * @note No checks for escapes are performed. */ basic_substring pair_range(CC open, CC close) const { size_t b = find(open); if(b == npos) return basic_substring(); size_t e = find(close, b+1); if(e == npos) return basic_substring(); basic_substring ret = range(b, e+1); C4_ASSERT(ret.sub(1).find(open) == npos); return ret; } /** get the range delimited by a single open-close character (eg, quotes). * @note The open-close character can be escaped. */ basic_substring pair_range_esc(CC open_close, CC escape=CC('\\')) { size_t b = find(open_close); if(b == npos) return basic_substring(); for(size_t i = b+1; i < len; ++i) { CC c = str[i]; if(c == open_close) { if(str[i-1] != escape) { return range(b, i+1); } } } return basic_substring(); } /** get the range delimited by an open-close pair of characters, * with possibly nested occurrences. No checks for escapes are * performed. */ basic_substring pair_range_nested(CC open, CC close) const { size_t b = find(open); if(b == npos) return basic_substring(); size_t e, curr = b+1, count = 0; const char both[] = {open, close, '\0'}; while((e = first_of(both, curr)) != npos) { if(str[e] == open) { ++count; curr = e+1; } else if(str[e] == close) { if(count == 0) return range(b, e+1); --count; curr = e+1; } } return basic_substring(); } basic_substring unquoted() const { constexpr const C dq('"'), sq('\''); if(len >= 2 && (str[len - 2] != C('\\')) && ((begins_with(sq) && ends_with(sq)) || (begins_with(dq) && ends_with(dq)))) { return range(1, len -1); } return *this; } /** @} */ public: /** @name Number-matching query methods */ /** @{ */ /** @return true if the substring contents are a floating-point or integer number. * @note any leading or trailing whitespace will return false. */ bool is_number() const { if(empty() || (first_non_empty_span().empty())) return false; if(first_uint_span() == *this) return true; if(first_int_span() == *this) return true; if(first_real_span() == *this) return true; return false; } /** @return true if the substring contents are a real number. * @note any leading or trailing whitespace will return false. */ bool is_real() const { if(empty() || (first_non_empty_span().empty())) return false; if(first_real_span() == *this) return true; return false; } /** @return true if the substring contents are an integer number. * @note any leading or trailing whitespace will return false. */ bool is_integer() const { if(empty() || (first_non_empty_span().empty())) return false; if(first_uint_span() == *this) return true; if(first_int_span() == *this) return true; return false; } /** @return true if the substring contents are an unsigned integer number. * @note any leading or trailing whitespace will return false. */ bool is_unsigned_integer() const { if(empty() || (first_non_empty_span().empty())) return false; if(first_uint_span() == *this) return true; return false; } /** get the first span consisting exclusively of non-empty characters */ basic_substring first_non_empty_span() const { constexpr const ro_substr empty_chars(" \n\r\t"); size_t pos = first_not_of(empty_chars); if(pos == npos) return first(0); auto ret = sub(pos); pos = ret.first_of(empty_chars); return ret.first(pos); } /** get the first span which can be interpreted as an unsigned integer */ basic_substring first_uint_span() const { basic_substring ne = first_non_empty_span(); if(ne.empty()) return ne; if(ne.str[0] == '-') return first(0); size_t skip_start = size_t(ne.str[0] == '+'); return ne._first_integral_span(skip_start); } /** get the first span which can be interpreted as a signed integer */ basic_substring first_int_span() const { basic_substring ne = first_non_empty_span(); if(ne.empty()) return ne; size_t skip_start = size_t(ne.str[0] == '+' || ne.str[0] == '-'); return ne._first_integral_span(skip_start); } basic_substring _first_integral_span(size_t skip_start) const { C4_ASSERT(!empty()); if(skip_start == len) return first(0); C4_ASSERT(skip_start < len); if(len >= skip_start + 3) { if(str[skip_start] != '0') { for(size_t i = skip_start; i < len; ++i) { char c = str[i]; if(c < '0' || c > '9') return i > skip_start && _is_delim_char(c) ? first(i) : first(0); } } else { char next = str[skip_start + 1]; if(next == 'x' || next == 'X') { skip_start += 2; for(size_t i = skip_start; i < len; ++i) { const char c = str[i]; if( ! _is_hex_char(c)) return i > skip_start && _is_delim_char(c) ? first(i) : first(0); } return *this; } else if(next == 'b' || next == 'B') { skip_start += 2; for(size_t i = skip_start; i < len; ++i) { const char c = str[i]; if(c != '0' && c != '1') return i > skip_start && _is_delim_char(c) ? first(i) : first(0); } return *this; } else if(next == 'o' || next == 'O') { skip_start += 2; for(size_t i = skip_start; i < len; ++i) { const char c = str[i]; if(c < '0' || c > '7') return i > skip_start && _is_delim_char(c) ? first(i) : first(0); } return *this; } } } // must be a decimal, or it is not a an number for(size_t i = skip_start; i < len; ++i) { const char c = str[i]; if(c < '0' || c > '9') return i > skip_start && _is_delim_char(c) ? first(i) : first(0); } return *this; } /** get the first span which can be interpreted as a real (floating-point) number */ basic_substring first_real_span() const { basic_substring ne = first_non_empty_span(); if(ne.empty()) return ne; const size_t skip_start = (ne.str[0] == '+' || ne.str[0] == '-'); C4_ASSERT(skip_start == 0 || skip_start == 1); // if we have at least three digits after the leading sign, it // can be decimal, or hex, or bin or oct. Ex: // non-decimal: 0x0, 0b0, 0o0 // decimal: 1.0, 10., 1e1, 100, inf, nan, infinity if(ne.len >= skip_start+3) { // if it does not have leading 0, it must be decimal, or it is not a real if(ne.str[skip_start] != '0') { if(ne.str[skip_start] == 'i') // is it infinity or inf? { basic_substring word = ne._word_follows(skip_start + 1, "nfinity"); if(word.len) return word; return ne._word_follows(skip_start + 1, "nf"); } else if(ne.str[skip_start] == 'n') // is it nan? { return ne._word_follows(skip_start + 1, "an"); } else // must be a decimal, or it is not a real { return ne._first_real_span_dec(skip_start); } } else // starts with 0. is it 0x, 0b or 0o? { const char next = ne.str[skip_start + 1]; // hexadecimal if(next == 'x' || next == 'X') return ne._first_real_span_hex(skip_start + 2); // binary else if(next == 'b' || next == 'B') return ne._first_real_span_bin(skip_start + 2); // octal else if(next == 'o' || next == 'O') return ne._first_real_span_oct(skip_start + 2); // none of the above. may still be a decimal. else return ne._first_real_span_dec(skip_start); // do not skip the 0. } } // less than 3 chars after the leading sign. It is either a // decimal or it is not a real. (cannot be any of 0x0, etc). return ne._first_real_span_dec(skip_start); } /** true if the character is a delimiter character *at the end* */ static constexpr C4_ALWAYS_INLINE C4_CONST bool _is_delim_char(char c) noexcept { return c == ' ' || c == '\n' || c == ']' || c == ')' || c == '}' || c == ',' || c == ';' || c == '\r' || c == '\t' || c == '\0'; } /** true if the character is in [0-9a-fA-F] */ static constexpr C4_ALWAYS_INLINE C4_CONST bool _is_hex_char(char c) noexcept { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } C4_NO_INLINE C4_PURE basic_substring _word_follows(size_t pos, csubstr word) const noexcept { size_t posend = pos + word.len; if(len >= posend && sub(pos, word.len) == word) if(len == posend || _is_delim_char(str[posend])) return first(posend); return first(0); } // this function is declared inside the class to avoid a VS error with __declspec(dllimport) C4_NO_INLINE C4_PURE basic_substring _first_real_span_dec(size_t pos) const noexcept { bool intchars = false; bool fracchars = false; bool powchars; // integral part for( ; pos < len; ++pos) { const char c = str[pos]; if(c >= '0' && c <= '9') { intchars = true; } else if(c == '.') { ++pos; goto fractional_part_dec; // NOLINT } else if(c == 'e' || c == 'E') { ++pos; goto power_part_dec; // NOLINT } else if(_is_delim_char(c)) { return intchars ? first(pos) : first(0); } else { return first(0); } } // no . or p were found; this is either an integral number // or not a number at all return intchars ? *this : first(0); fractional_part_dec: C4_ASSERT(pos > 0); C4_ASSERT(str[pos - 1] == '.'); for( ; pos < len; ++pos) { const char c = str[pos]; if(c >= '0' && c <= '9') { fracchars = true; } else if(c == 'e' || c == 'E') { ++pos; goto power_part_dec; // NOLINT } else if(_is_delim_char(c)) { return intchars || fracchars ? first(pos) : first(0); } else { return first(0); } } return intchars || fracchars ? *this : first(0); power_part_dec: C4_ASSERT(pos > 0); C4_ASSERT(str[pos - 1] == 'e' || str[pos - 1] == 'E'); // either digits, or +, or - are expected here, followed by more digits. if((len == pos) || ((!intchars) && (!fracchars))) return first(0); if(str[pos] == '-' || str[pos] == '+') ++pos; // skip the sign powchars = false; for( ; pos < len; ++pos) { const char c = str[pos]; if(c >= '0' && c <= '9') powchars = true; else if(powchars && _is_delim_char(c)) return first(pos); else return first(0); } return powchars ? *this : first(0); } // this function is declared inside the class to avoid a VS error with __declspec(dllimport) C4_NO_INLINE C4_PURE basic_substring _first_real_span_hex(size_t pos) const noexcept { bool intchars = false; bool fracchars = false; bool powchars; // integral part for( ; pos < len; ++pos) { const char c = str[pos]; if(_is_hex_char(c)) { intchars = true; } else if(c == '.') { ++pos; goto fractional_part_hex; // NOLINT } else if(c == 'p' || c == 'P') { ++pos; goto power_part_hex; // NOLINT } else if(_is_delim_char(c)) { return intchars ? first(pos) : first(0); } else { return first(0); } } // no . or p were found; this is either an integral number // or not a number at all return intchars ? *this : first(0); fractional_part_hex: C4_ASSERT(pos > 0); C4_ASSERT(str[pos - 1] == '.'); for( ; pos < len; ++pos) { const char c = str[pos]; if(_is_hex_char(c)) { fracchars = true; } else if(c == 'p' || c == 'P') { ++pos; goto power_part_hex; // NOLINT } else if(_is_delim_char(c)) { return intchars || fracchars ? first(pos) : first(0); } else { return first(0); } } return intchars || fracchars ? *this : first(0); power_part_hex: C4_ASSERT(pos > 0); C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P'); // either a + or a - is expected here, followed by more chars. // also, using (pos+1) in this check will cause an early // return when no more chars follow the sign. if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars))) return first(0); ++pos; // this was the sign. // ... so the (pos+1) ensures that we enter the loop and // hence that there exist chars in the power part powchars = false; for( ; pos < len; ++pos) { const char c = str[pos]; if(c >= '0' && c <= '9') powchars = true; else if(powchars && _is_delim_char(c)) return first(pos); else return first(0); } return *this; } // this function is declared inside the class to avoid a VS error with __declspec(dllimport) C4_NO_INLINE C4_PURE basic_substring _first_real_span_bin(size_t pos) const noexcept { bool intchars = false; bool fracchars = false; bool powchars; // integral part for( ; pos < len; ++pos) { const char c = str[pos]; if(c == '0' || c == '1') { intchars = true; } else if(c == '.') { ++pos; goto fractional_part_bin; // NOLINT } else if(c == 'p' || c == 'P') { ++pos; goto power_part_bin; // NOLINT } else if(_is_delim_char(c)) { return intchars ? first(pos) : first(0); } else { return first(0); } } // no . or p were found; this is either an integral number // or not a number at all return intchars ? *this : first(0); fractional_part_bin: C4_ASSERT(pos > 0); C4_ASSERT(str[pos - 1] == '.'); for( ; pos < len; ++pos) { const char c = str[pos]; if(c == '0' || c == '1') { fracchars = true; } else if(c == 'p' || c == 'P') { ++pos; goto power_part_bin; // NOLINT } else if(_is_delim_char(c)) { return intchars || fracchars ? first(pos) : first(0); } else { return first(0); } } return intchars || fracchars ? *this : first(0); power_part_bin: C4_ASSERT(pos > 0); C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P'); // either a + or a - is expected here, followed by more chars. // also, using (pos+1) in this check will cause an early // return when no more chars follow the sign. if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars))) return first(0); ++pos; // this was the sign. // ... so the (pos+1) ensures that we enter the loop and // hence that there exist chars in the power part powchars = false; for( ; pos < len; ++pos) { const char c = str[pos]; if(c >= '0' && c <= '9') powchars = true; else if(powchars && _is_delim_char(c)) return first(pos); else return first(0); } return *this; } // this function is declared inside the class to avoid a VS error with __declspec(dllimport) C4_NO_INLINE C4_PURE basic_substring _first_real_span_oct(size_t pos) const noexcept { bool intchars = false; bool fracchars = false; bool powchars; // integral part for( ; pos < len; ++pos) { const char c = str[pos]; if(c >= '0' && c <= '7') { intchars = true; } else if(c == '.') { ++pos; goto fractional_part_oct; // NOLINT } else if(c == 'p' || c == 'P') { ++pos; goto power_part_oct; // NOLINT } else if(_is_delim_char(c)) { return intchars ? first(pos) : first(0); } else { return first(0); } } // no . or p were found; this is either an integral number // or not a number at all return intchars ? *this : first(0); fractional_part_oct: C4_ASSERT(pos > 0); C4_ASSERT(str[pos - 1] == '.'); for( ; pos < len; ++pos) { const char c = str[pos]; if(c >= '0' && c <= '7') { fracchars = true; } else if(c == 'p' || c == 'P') { ++pos; goto power_part_oct; // NOLINT } else if(_is_delim_char(c)) { return intchars || fracchars ? first(pos) : first(0); } else { return first(0); } } return intchars || fracchars ? *this : first(0); power_part_oct: C4_ASSERT(pos > 0); C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P'); // either a + or a - is expected here, followed by more chars. // also, using (pos+1) in this check will cause an early // return when no more chars follow the sign. if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars))) return first(0); ++pos; // this was the sign. // ... so the (pos+1) ensures that we enter the loop and // hence that there exist chars in the power part powchars = false; for( ; pos < len; ++pos) { const char c = str[pos]; if(c >= '0' && c <= '9') powchars = true; else if(powchars && _is_delim_char(c)) return first(pos); else return first(0); } return *this; } /** @} */ public: /** @name Splitting methods */ /** @{ */ /** returns true if the string has not been exhausted yet, meaning * it's ok to call next_split() again. When no instance of sep * exists in the string, returns the full string. When the input * is an empty string, the output string is the empty string. */ bool next_split(C sep, size_t *C4_RESTRICT start_pos, basic_substring *C4_RESTRICT out) const { if(C4_LIKELY(*start_pos < len)) { for(size_t i = *start_pos; i < len; i++) { if(str[i] == sep) { out->assign(str + *start_pos, i - *start_pos); *start_pos = i+1; return true; } } out->assign(str + *start_pos, len - *start_pos); *start_pos = len + 1; return true; } else { bool valid = len > 0 && (*start_pos == len); if(valid && str && str[len-1] == sep) { out->assign(str + len, size_t(0)); // the cast is needed to prevent overload ambiguity } else { out->assign(str + len + 1, size_t(0)); // the cast is needed to prevent overload ambiguity } *start_pos = len + 1; return valid; } } private: struct split_proxy_impl { struct split_iterator_impl { split_proxy_impl const* m_proxy; basic_substring m_str; size_t m_pos; NCC_ m_sep; split_iterator_impl(split_proxy_impl const* proxy, size_t pos, C sep) : m_proxy(proxy), m_pos(pos), m_sep(sep) { _tick(); } void _tick() { m_proxy->m_str.next_split(m_sep, &m_pos, &m_str); } split_iterator_impl& operator++ () { _tick(); return *this; } split_iterator_impl operator++ (int) { split_iterator_impl it = *this; _tick(); return it; } // NOLINT basic_substring& operator* () { return m_str; } basic_substring* operator-> () { return &m_str; } bool operator!= (split_iterator_impl const& that) const { return !(this->operator==(that)); } bool operator== (split_iterator_impl const& that) const { C4_XASSERT((m_sep == that.m_sep) && "cannot compare split iterators with different separators"); if(m_str.size() != that.m_str.size()) return false; if(m_str.data() != that.m_str.data()) return false; return m_pos == that.m_pos; } }; basic_substring m_str; size_t m_start_pos; C m_sep; split_proxy_impl(basic_substring str_, size_t start_pos, C sep) : m_str(str_), m_start_pos(start_pos), m_sep(sep) { } split_iterator_impl begin() const { auto it = split_iterator_impl(this, m_start_pos, m_sep); return it; } split_iterator_impl end() const { size_t pos = m_str.size() + 1; auto it = split_iterator_impl(this, pos, m_sep); return it; } }; public: using split_proxy = split_proxy_impl; /** a view into the splits */ split_proxy split(C sep, size_t start_pos=0) const { C4_XASSERT((start_pos >= 0 && start_pos < len) || empty()); auto ss = sub(0, len); auto it = split_proxy(ss, start_pos, sep); return it; } public: /** pop right: return the first split from the right. Use * gpop_left() to get the reciprocal part. */ basic_substring pop_right(C sep=C('/'), bool skip_empty=false) const { if(C4_LIKELY(len > 1)) { auto pos = last_of(sep); if(pos != npos) { if(pos + 1 < len) // does not end with sep { return sub(pos + 1); // return from sep to end } else // the string ends with sep { if( ! skip_empty) { return sub(pos + 1, 0); } auto ppos = last_not_of(sep); // skip repeated seps if(ppos == npos) // the string is all made of seps { return sub(0, 0); } // find the previous sep auto pos0 = last_of(sep, ppos); if(pos0 == npos) // only the last sep exists { return sub(0); // return the full string (because skip_empty is true) } ++pos0; return sub(pos0); } } else // no sep was found, return the full string { return *this; } } else if(len == 1) { if(begins_with(sep)) { return sub(0, 0); } return *this; } else // an empty string { return basic_substring(); } } /** return the first split from the left. Use gpop_right() to get * the reciprocal part. */ basic_substring pop_left(C sep = C('/'), bool skip_empty=false) const { if(C4_LIKELY(len > 1)) { auto pos = first_of(sep); if(pos != npos) { if(pos > 0) // does not start with sep { return sub(0, pos); // return everything up to it } else // the string starts with sep { if( ! skip_empty) { return sub(0, 0); } auto ppos = first_not_of(sep); // skip repeated seps if(ppos == npos) // the string is all made of seps { return sub(0, 0); } // find the next sep auto pos0 = first_of(sep, ppos); if(pos0 == npos) // only the first sep exists { return sub(0); // return the full string (because skip_empty is true) } C4_XASSERT(pos0 > 0); // return everything up to the second sep return sub(0, pos0); } } else // no sep was found, return the full string { return sub(0); } } else if(len == 1) { if(begins_with(sep)) { return sub(0, 0); } return sub(0); } else // an empty string { return basic_substring(); } } public: /** greedy pop left. eg, csubstr("a/b/c").gpop_left('/')="c" */ basic_substring gpop_left(C sep = C('/'), bool skip_empty=false) const { auto ss = pop_right(sep, skip_empty); ss = left_of(ss); if(ss.find(sep) != npos) { if(ss.ends_with(sep)) { if(skip_empty) { ss = ss.trimr(sep); } else { ss = ss.sub(0, ss.len-1); // safe to subtract because ends_with(sep) is true } } } return ss; } /** greedy pop right. eg, csubstr("a/b/c").gpop_right('/')="a" */ basic_substring gpop_right(C sep = C('/'), bool skip_empty=false) const { auto ss = pop_left(sep, skip_empty); ss = right_of(ss); if(ss.find(sep) != npos) { if(ss.begins_with(sep)) { if(skip_empty) { ss = ss.triml(sep); } else { ss = ss.sub(1); } } } return ss; } /** @} */ public: /** @name Path-like manipulation methods */ /** @{ */ basic_substring basename(C sep=C('/')) const { auto ss = pop_right(sep, /*skip_empty*/true); ss = ss.trimr(sep); return ss; } basic_substring dirname(C sep=C('/')) const { auto ss = basename(sep); ss = ss.empty() ? *this : left_of(ss); return ss; } C4_ALWAYS_INLINE basic_substring name_wo_extshort() const { return gpop_left('.'); } C4_ALWAYS_INLINE basic_substring name_wo_extlong() const { return pop_left('.'); } C4_ALWAYS_INLINE basic_substring extshort() const { return pop_right('.'); } C4_ALWAYS_INLINE basic_substring extlong() const { return gpop_right('.'); } /** @} */ public: /** @name Content-modification methods (only for non-const C) */ /** @{ */ /** convert the string to upper-case * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(void) toupper() { for(size_t i = 0; i < len; ++i) { str[i] = static_cast(::toupper(str[i])); } } /** convert the string to lower-case * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(void) tolower() { for(size_t i = 0; i < len; ++i) { str[i] = static_cast(::tolower(str[i])); } } public: /** fill the entire contents with the given @p val * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(void) fill(C val) { for(size_t i = 0; i < len; ++i) { str[i] = val; } } public: /** set the current substring to a copy of the given csubstr * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(void) copy_from(ro_substr that, size_t ifirst=0, size_t num=npos) { C4_ASSERT(ifirst >= 0 && ifirst <= len); num = num != npos ? num : len - ifirst; num = num < that.len ? num : that.len; C4_ASSERT(ifirst + num >= 0 && ifirst + num <= len); // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(num) memcpy(str + sizeof(C) * ifirst, that.str, sizeof(C) * num); } public: /** reverse in place * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(void) reverse() { if(len == 0) return; detail::_do_reverse(str, str + len - 1); } /** revert a subpart in place * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(void) reverse_sub(size_t ifirst, size_t num) { C4_ASSERT(ifirst >= 0 && ifirst <= len); C4_ASSERT(ifirst + num >= 0 && ifirst + num <= len); if(num == 0) return; detail::_do_reverse(str + ifirst, str + ifirst + num - 1); } /** revert a range in place * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(void) reverse_range(size_t ifirst, size_t ilast) { C4_ASSERT(ifirst >= 0 && ifirst <= len); C4_ASSERT(ilast >= 0 && ilast <= len); if(ifirst == ilast) return; detail::_do_reverse(str + ifirst, str + ilast - 1); } public: /** erase part of the string. eg, with char s[] = "0123456789", * substr(s).erase(3, 2) = "01256789", and s is now "01245678989" * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(basic_substring) erase(size_t pos, size_t num) { C4_ASSERT(pos >= 0 && pos+num <= len); size_t num_to_move = len - pos - num; memmove(str + pos, str + pos + num, sizeof(C) * num_to_move); return basic_substring{str, len - num}; } /** @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(basic_substring) erase_range(size_t first, size_t last) { C4_ASSERT(first <= last); return erase(first, static_cast(last-first)); // NOLINT } /** erase a part of the string. * @note @p sub must be a substring of this string * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(basic_substring) erase(ro_substr sub) { C4_ASSERT(is_super(sub)); C4_ASSERT(sub.str >= str); return erase(static_cast(sub.str - str), sub.len); } public: /** replace every occurrence of character @p value with the character @p repl * @return the number of characters that were replaced * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(size_t) replace(C value, C repl, size_t pos=0) { C4_ASSERT((pos >= 0 && pos <= len) || pos == npos); size_t did_it = 0; while((pos = find(value, pos)) != npos) { str[pos++] = repl; ++did_it; } return did_it; } /** replace every occurrence of each character in @p value with * the character @p repl. * @return the number of characters that were replaced * @note this method requires that the string memory is writeable and is SFINAEd out for const C */ C4_REQUIRE_RW(size_t) replace(ro_substr chars, C repl, size_t pos=0) { C4_ASSERT((pos >= 0 && pos <= len) || pos == npos); size_t did_it = 0; while((pos = first_of(chars, pos)) != npos) { str[pos++] = repl; ++did_it; } return did_it; } /** replace @p pattern with @p repl, and write the result into * @p dst. pattern and repl don't need equal sizes. * * @return the required size for dst. No overflow occurs if * dst.len is smaller than the required size; this can be used to * determine the required size for an existing container. */ size_t replace_all(rw_substr dst, ro_substr pattern, ro_substr repl, size_t pos=0) const { C4_ASSERT( ! pattern.empty()); //!< @todo relax this precondition C4_ASSERT( ! this ->overlaps(dst)); //!< @todo relax this precondition C4_ASSERT( ! pattern.overlaps(dst)); C4_ASSERT( ! repl .overlaps(dst)); C4_ASSERT((pos >= 0 && pos <= len) || pos == npos); C4_SUPPRESS_WARNING_GCC_PUSH C4_SUPPRESS_WARNING_GCC("-Warray-bounds") // gcc11 has a false positive here #if (!defined(__clang__)) && (defined(__GNUC__) && (__GNUC__ >= 7)) C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc11 has a false positive here #endif #define _c4append(first, last) \ { \ C4_ASSERT((last) >= (first)); \ size_t num = static_cast((last) - (first)); \ if(num > 0 && sz + num <= dst.len) \ { \ memcpy(dst.str + sz, first, num * sizeof(C)); \ } \ sz += num; \ } size_t sz = 0; size_t b = pos; _c4append(str, str + pos); do { size_t e = find(pattern, b); if(e == npos) { _c4append(str + b, str + len); break; } _c4append(str + b, str + e); _c4append(repl.begin(), repl.end()); b = e + pattern.size(); } while(b < len && b != npos); return sz; #undef _c4append C4_SUPPRESS_WARNING_GCC_POP } /** @} */ }; // template class basic_substring #undef C4_REQUIRE_RW //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_substr_adapters substr adapters * * to_substr() and to_csubstr() is used in generic code like * format(), and allow adding construction of substrings from new * types like containers. * @{ */ /** neutral version for use in generic code */ C4_ALWAYS_INLINE substr to_substr(substr s) noexcept { return s; } /** neutral version for use in generic code */ C4_ALWAYS_INLINE csubstr to_csubstr(substr s) noexcept { return s; } /** neutral version for use in generic code */ C4_ALWAYS_INLINE csubstr to_csubstr(csubstr s) noexcept { return s; } template C4_ALWAYS_INLINE substr to_substr(char (&s)[N]) noexcept { substr ss(s, N-1); return ss; } template C4_ALWAYS_INLINE csubstr to_csubstr(const char (&s)[N]) noexcept { csubstr ss(s, N-1); return ss; } /** @note this overload uses SFINAE to prevent it from overriding the array overload * @see For a more detailed explanation on why the plain overloads cannot * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ template C4_ALWAYS_INLINE typename std::enable_if::value, substr>::type to_substr(U s) noexcept { substr ss(s); return ss; } /** @note this overload uses SFINAE to prevent it from overriding the array overload * @see For a more detailed explanation on why the plain overloads cannot * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */ template C4_ALWAYS_INLINE typename std::enable_if::value || std::is_same::value, csubstr>::type to_csubstr(U s) noexcept { csubstr ss(s); return ss; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_substr_cmp substr comparison operators * @{ */ template inline bool operator== (const char (&s)[N], basic_substring const that) noexcept { return that.compare(s, N-1) == 0; } template inline bool operator!= (const char (&s)[N], basic_substring const that) noexcept { return that.compare(s, N-1) != 0; } template inline bool operator< (const char (&s)[N], basic_substring const that) noexcept { return that.compare(s, N-1) > 0; } template inline bool operator> (const char (&s)[N], basic_substring const that) noexcept { return that.compare(s, N-1) < 0; } template inline bool operator<= (const char (&s)[N], basic_substring const that) noexcept { return that.compare(s, N-1) >= 0; } template inline bool operator>= (const char (&s)[N], basic_substring const that) noexcept { return that.compare(s, N-1) <= 0; } template inline bool operator== (const char c, basic_substring const that) noexcept { return that.compare(c) == 0; } template inline bool operator!= (const char c, basic_substring const that) noexcept { return that.compare(c) != 0; } template inline bool operator< (const char c, basic_substring const that) noexcept { return that.compare(c) > 0; } template inline bool operator> (const char c, basic_substring const that) noexcept { return that.compare(c) < 0; } template inline bool operator<= (const char c, basic_substring const that) noexcept { return that.compare(c) >= 0; } template inline bool operator>= (const char c, basic_substring const that) noexcept { return that.compare(c) <= 0; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /* C4_SUBSTR_NO_OSTREAM_LSHIFT doctest does not deal well with * template operator<< * @see https://github.com/onqtam/doctest/pull/431 */ #ifndef C4_SUBSTR_NO_OSTREAM_LSHIFT #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wsign-conversion" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wsign-conversion" #endif /** output the string to a stream */ template inline OStream& operator<< (OStream& os, basic_substring s) { os.write(s.str, s.len); return os; } // this causes ambiguity ///** this is used by google test */ //template //inline void PrintTo(basic_substring s, OStream* os) //{ // os->write(s.str, s.len); //} #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif // !C4_SUBSTR_NO_OSTREAM_LSHIFT /** @} */ } // namespace c4 #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif /* _C4_SUBSTR_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/substr.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/ext/fast_float.hpp // https://github.com/biojppm/c4core/src/c4/ext/fast_float.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_EXT_FAST_FLOAT_HPP_ #define _C4_EXT_FAST_FLOAT_HPP_ #if defined(_MSC_VER) && !defined(__clang__) # pragma warning(push) # pragma warning(disable: 4365) // '=': conversion from 'const _Ty' to 'fast_float::limb', signed/unsigned mismatch # pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe #elif defined(__clang__) || defined(__APPLE_CC__) || defined(_LIBCPP_VERSION) # pragma clang diagnostic push # if (defined(__clang_major__) && (__clang_major__ >= 9)) || defined(__APPLE_CC__) # pragma clang diagnostic ignored "-Wfortify-source" # endif # pragma clang diagnostic ignored "-Wshift-count-overflow" # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wnarrowing" # pragma GCC diagnostic ignored "-Wconversion" # pragma GCC diagnostic ignored "-Wsign-conversion" # pragma GCC diagnostic ignored "-Wuseless-cast" # pragma GCC diagnostic ignored "-Wold-style-cast" # pragma GCC diagnostic ignored "-Warray-bounds" # if __GNUC__ >= 5 # pragma GCC diagnostic ignored "-Wshift-count-overflow" # endif #endif // fast_float by Daniel Lemire // fast_float by João Paulo Magalhaes // // // with contributions from Eugene Golushkov // with contributions from Maksim Kita // with contributions from Marcin Wojdyr // with contributions from Neal Richardson // with contributions from Tim Paine // with contributions from Fabio Pellacini // with contributions from Lénárd Szolnoki // with contributions from Jan Pharago // with contributions from Maya Warrier // with contributions from Taha Khokhar // with contributions from Anders Dalvander // // // MIT License Notice // // MIT License // // Copyright (c) 2021 The fast_float authors // // Permission is hereby granted, free of charge, to any // person obtaining a copy of this software and associated // documentation files (the "Software"), to deal in the // Software without restriction, including without // limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software // is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice // shall be included in all copies or substantial portions // of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // #ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H #define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H #ifdef __has_include #if __has_include() #include #endif #endif // Testing for https://wg21.link/N3652, adopted in C++14 #if defined(__cpp_constexpr) && __cpp_constexpr >= 201304 #define FASTFLOAT_CONSTEXPR14 constexpr #else #define FASTFLOAT_CONSTEXPR14 #endif #if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L #define FASTFLOAT_HAS_BIT_CAST 1 #else #define FASTFLOAT_HAS_BIT_CAST 0 #endif #if defined(__cpp_lib_is_constant_evaluated) && \ __cpp_lib_is_constant_evaluated >= 201811L #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 #else #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 #endif // Testing for relevant C++20 constexpr library features #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \ defined(__cpp_lib_constexpr_algorithms) && \ __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ #define FASTFLOAT_CONSTEXPR20 constexpr #define FASTFLOAT_IS_CONSTEXPR 1 #else #define FASTFLOAT_CONSTEXPR20 #define FASTFLOAT_IS_CONSTEXPR 0 #endif #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) #define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0 #else #define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1 #endif #endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H #ifndef FASTFLOAT_FLOAT_COMMON_H #define FASTFLOAT_FLOAT_COMMON_H #include //included above: //#include #include //included above: //#include //included above: //#include //included above: //#include #include #ifdef __has_include #if __has_include() && (__cplusplus > 202002L || (defined(_MSVC_LANG) && (_MSVC_LANG > 202002L))) #include #endif #endif #define FASTFLOAT_VERSION_MAJOR 8 #define FASTFLOAT_VERSION_MINOR 0 #define FASTFLOAT_VERSION_PATCH 0 #define FASTFLOAT_STRINGIZE_IMPL(x) #x #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) #define FASTFLOAT_VERSION_STR \ FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MAJOR) \ "." FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MINOR) "." FASTFLOAT_STRINGIZE( \ FASTFLOAT_VERSION_PATCH) #define FASTFLOAT_VERSION \ (FASTFLOAT_VERSION_MAJOR * 10000 + FASTFLOAT_VERSION_MINOR * 100 + \ FASTFLOAT_VERSION_PATCH) namespace fast_float { enum class chars_format : uint64_t; namespace detail { constexpr chars_format basic_json_fmt = chars_format(1 << 5); constexpr chars_format basic_fortran_fmt = chars_format(1 << 6); } // namespace detail enum class chars_format : uint64_t { scientific = 1 << 0, fixed = 1 << 2, hex = 1 << 3, no_infnan = 1 << 4, // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 json = uint64_t(detail::basic_json_fmt) | fixed | scientific | no_infnan, // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. json_or_infnan = uint64_t(detail::basic_json_fmt) | fixed | scientific, fortran = uint64_t(detail::basic_fortran_fmt) | fixed | scientific, general = fixed | scientific, allow_leading_plus = 1 << 7, skip_white_space = 1 << 8, }; template struct from_chars_result_t { UC const *ptr; std::errc ec; }; using from_chars_result = from_chars_result_t; template struct parse_options_t { constexpr explicit parse_options_t(chars_format fmt = chars_format::general, UC dot = UC('.'), int b = 10) : format(fmt), decimal_point(dot), base(b) {} /** Which number formats are accepted */ chars_format format; /** The character used as decimal point */ UC decimal_point; /** The base used for integers */ int base; }; using parse_options = parse_options_t; } // namespace fast_float #if FASTFLOAT_HAS_BIT_CAST #include #endif #if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) || \ defined(__MINGW64__) || defined(__s390x__) || \ (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ defined(__PPC64LE__)) || \ defined(__loongarch64)) #define FASTFLOAT_64BIT 1 #elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ defined(__arm__) || defined(_M_ARM) || defined(__ppc__) || \ defined(__MINGW32__) || defined(__EMSCRIPTEN__)) #define FASTFLOAT_32BIT 1 #else // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. // We can never tell the register width, but the SIZE_MAX is a good // approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max // portability. #if SIZE_MAX == 0xffff #error Unknown platform (16-bit, unsupported) #elif SIZE_MAX == 0xffffffff #define FASTFLOAT_32BIT 1 #elif SIZE_MAX == 0xffffffffffffffff #define FASTFLOAT_64BIT 1 #else #error Unknown platform (not 32-bit, not 64-bit?) #endif #endif #if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) || \ (defined(_M_ARM64) && !defined(__MINGW32__)) //included above: //#include #endif #if defined(_MSC_VER) && !defined(__clang__) #define FASTFLOAT_VISUAL_STUDIO 1 #endif #if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ #define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #elif defined _WIN32 #define FASTFLOAT_IS_BIG_ENDIAN 0 #else #if defined(__APPLE__) || defined(__FreeBSD__) #include #elif defined(sun) || defined(__sun) #include #elif defined(__MVS__) #include #else #ifdef __has_include #if __has_include() #include #endif //__has_include() #endif //__has_include #endif # #ifndef __BYTE_ORDER__ // safe choice #define FASTFLOAT_IS_BIG_ENDIAN 0 #endif # #ifndef __ORDER_LITTLE_ENDIAN__ // safe choice #define FASTFLOAT_IS_BIG_ENDIAN 0 #endif # #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define FASTFLOAT_IS_BIG_ENDIAN 0 #else #define FASTFLOAT_IS_BIG_ENDIAN 1 #endif #endif #if defined(__SSE2__) || (defined(FASTFLOAT_VISUAL_STUDIO) && \ (defined(_M_AMD64) || defined(_M_X64) || \ (defined(_M_IX86_FP) && _M_IX86_FP == 2))) #define FASTFLOAT_SSE2 1 #endif #if defined(__aarch64__) || defined(_M_ARM64) #define FASTFLOAT_NEON 1 #endif #if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON) #define FASTFLOAT_HAS_SIMD 1 #endif #if defined(__GNUC__) // disable -Wcast-align=strict (GCC only) #define FASTFLOAT_SIMD_DISABLE_WARNINGS \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wcast-align\"") #else #define FASTFLOAT_SIMD_DISABLE_WARNINGS #endif #if defined(__GNUC__) #define FASTFLOAT_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") #else #define FASTFLOAT_SIMD_RESTORE_WARNINGS #endif #ifdef FASTFLOAT_VISUAL_STUDIO #define fastfloat_really_inline __forceinline #else #define fastfloat_really_inline inline __attribute__((always_inline)) #endif #ifndef FASTFLOAT_ASSERT #define FASTFLOAT_ASSERT(x) \ { ((void)(x)); } #endif #ifndef FASTFLOAT_DEBUG_ASSERT #define FASTFLOAT_DEBUG_ASSERT(x) \ { ((void)(x)); } #endif // rust style `try!()` macro, or `?` operator #define FASTFLOAT_TRY(x) \ { \ if (!(x)) \ return false; \ } #define FASTFLOAT_ENABLE_IF(...) \ typename std::enable_if<(__VA_ARGS__), int>::type namespace fast_float { fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED return std::is_constant_evaluated(); #else return false; #endif } template struct is_supported_float_type : std::integral_constant< bool, std::is_same::value || std::is_same::value #ifdef __STDCPP_FLOAT64_T__ || std::is_same::value #endif #ifdef __STDCPP_FLOAT32_T__ || std::is_same::value #endif #ifdef __STDCPP_FLOAT16_T__ || std::is_same::value #endif #ifdef __STDCPP_BFLOAT16_T__ || std::is_same::value #endif > { }; template using equiv_uint_t = typename std::conditional< sizeof(T) == 1, uint8_t, typename std::conditional< sizeof(T) == 2, uint16_t, typename std::conditional::type>::type>::type; template struct is_supported_integer_type : std::is_integral {}; template struct is_supported_char_type : std::integral_constant::value || std::is_same::value || std::is_same::value || std::is_same::value #ifdef __cpp_char8_t || std::is_same::value #endif > { }; // Compares two ASCII strings in a case insensitive manner. template inline FASTFLOAT_CONSTEXPR14 bool fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, size_t length) { for (size_t i = 0; i < length; ++i) { UC const actual = actual_mixedcase[i]; if ((actual < 256 ? actual | 32 : actual) != expected_lowercase[i]) { return false; } } return true; } #ifndef FLT_EVAL_METHOD #error "FLT_EVAL_METHOD should be defined, please include cfloat." #endif // a pointer and a length to a contiguous block of memory template struct span { T const *ptr; size_t length; constexpr span(T const *_ptr, size_t _length) : ptr(_ptr), length(_length) {} constexpr span() : ptr(nullptr), length(0) {} constexpr size_t len() const noexcept { return length; } FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return ptr[index]; } }; struct value128 { uint64_t low; uint64_t high; constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} constexpr value128() : low(0), high(0) {} }; /* Helper C++14 constexpr generic implementation of leading_zeroes */ fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { if (input_num & uint64_t(0xffffffff00000000)) { input_num >>= 32; last_bit |= 32; } if (input_num & uint64_t(0xffff0000)) { input_num >>= 16; last_bit |= 16; } if (input_num & uint64_t(0xff00)) { input_num >>= 8; last_bit |= 8; } if (input_num & uint64_t(0xf0)) { input_num >>= 4; last_bit |= 4; } if (input_num & uint64_t(0xc)) { input_num >>= 2; last_bit |= 2; } if (input_num & uint64_t(0x2)) { /* input_num >>= 1; */ last_bit |= 1; } return 63 - last_bit; } /* result might be undefined when input_num is zero */ fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int leading_zeroes(uint64_t input_num) { assert(input_num > 0); if (cpp20_and_in_constexpr()) { return leading_zeroes_generic(input_num); } #ifdef FASTFLOAT_VISUAL_STUDIO #if defined(_M_X64) || defined(_M_ARM64) unsigned long leading_zero = 0; // Search the mask data from most significant bit (MSB) // to least significant bit (LSB) for a set bit (1). _BitScanReverse64(&leading_zero, input_num); return (int)(63 - leading_zero); #else return leading_zeroes_generic(input_num); #endif #else return __builtin_clzll(input_num); #endif } // slow emulation routine for 32-bit fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { return x * (uint64_t)y; } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); uint64_t adbc_carry = (uint64_t)(adbc < ad); uint64_t lo = bd + (adbc << 32); *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + (adbc_carry << 32) + (uint64_t)(lo < bd); return lo; } #ifdef FASTFLOAT_32BIT // slow emulation routine for 32-bit #if !defined(__MINGW64__) fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { return umul128_generic(ab, cd, hi); } #endif // !__MINGW64__ #endif // FASTFLOAT_32BIT // compute 64-bit a*b fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 full_multiplication(uint64_t a, uint64_t b) { if (cpp20_and_in_constexpr()) { value128 answer; answer.low = umul128_generic(a, b, &answer.high); return answer; } value128 answer; #if defined(_M_ARM64) && !defined(__MINGW32__) // ARM64 has native support for 64-bit multiplications, no need to emulate // But MinGW on ARM64 doesn't have native support for 64-bit multiplications answer.high = __umulh(a, b); answer.low = a * b; #elif defined(FASTFLOAT_32BIT) || \ (defined(_WIN64) && !defined(__clang__) && !defined(_M_ARM64)) answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 #elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) __uint128_t r = ((__uint128_t)a) * b; answer.low = uint64_t(r); answer.high = uint64_t(r >> 64); #else answer.low = umul128_generic(a, b, &answer.high); #endif return answer; } struct adjusted_mantissa { uint64_t mantissa{0}; int32_t power2{0}; // a negative value indicates an invalid result adjusted_mantissa() = default; constexpr bool operator==(adjusted_mantissa const &o) const { return mantissa == o.mantissa && power2 == o.power2; } constexpr bool operator!=(adjusted_mantissa const &o) const { return mantissa != o.mantissa || power2 != o.power2; } }; // Bias so we can get the real exponent with an invalid adjusted_mantissa. constexpr static int32_t invalid_am_bias = -0x8000; // used for binary_format_lookup_tables::max_mantissa constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; template struct binary_format_lookup_tables; template struct binary_format : binary_format_lookup_tables { using equiv_uint = equiv_uint_t; static constexpr int mantissa_explicit_bits(); static constexpr int minimum_exponent(); static constexpr int infinite_power(); static constexpr int sign_index(); static constexpr int min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST static constexpr int max_exponent_fast_path(); static constexpr int max_exponent_round_to_even(); static constexpr int min_exponent_round_to_even(); static constexpr uint64_t max_mantissa_fast_path(int64_t power); static constexpr uint64_t max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST static constexpr int largest_power_of_ten(); static constexpr int smallest_power_of_ten(); static constexpr T exact_power_of_ten(int64_t power); static constexpr size_t max_digits(); static constexpr equiv_uint exponent_mask(); static constexpr equiv_uint mantissa_mask(); static constexpr equiv_uint hidden_bit_mask(); }; template struct binary_format_lookup_tables { static constexpr double powers_of_ten[] = { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; // Largest integer value v so that (5**index * v) <= 1<<53. // 0x20000000000000 == 1 << 53 static constexpr uint64_t max_mantissa[] = { 0x20000000000000, 0x20000000000000 / 5, 0x20000000000000 / (5 * 5), 0x20000000000000 / (5 * 5 * 5), 0x20000000000000 / (5 * 5 * 5 * 5), 0x20000000000000 / (constant_55555), 0x20000000000000 / (constant_55555 * 5), 0x20000000000000 / (constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * 5 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555), 0x20000000000000 / (constant_55555 * constant_55555 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5)}; }; #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE template constexpr double binary_format_lookup_tables::powers_of_ten[]; template constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; #endif template struct binary_format_lookup_tables { static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; // Largest integer value v so that (5**index * v) <= 1<<24. // 0x1000000 == 1<<24 static constexpr uint64_t max_mantissa[] = { 0x1000000, 0x1000000 / 5, 0x1000000 / (5 * 5), 0x1000000 / (5 * 5 * 5), 0x1000000 / (5 * 5 * 5 * 5), 0x1000000 / (constant_55555), 0x1000000 / (constant_55555 * 5), 0x1000000 / (constant_55555 * 5 * 5), 0x1000000 / (constant_55555 * 5 * 5 * 5), 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), 0x1000000 / (constant_55555 * constant_55555), 0x1000000 / (constant_55555 * constant_55555 * 5)}; }; #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE template constexpr float binary_format_lookup_tables::powers_of_ten[]; template constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; #endif template <> inline constexpr int binary_format::min_exponent_fast_path() { #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return 0; #else return -22; #endif } template <> inline constexpr int binary_format::min_exponent_fast_path() { #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return 0; #else return -10; #endif } template <> inline constexpr int binary_format::mantissa_explicit_bits() { return 52; } template <> inline constexpr int binary_format::mantissa_explicit_bits() { return 23; } template <> inline constexpr int binary_format::max_exponent_round_to_even() { return 23; } template <> inline constexpr int binary_format::max_exponent_round_to_even() { return 10; } template <> inline constexpr int binary_format::min_exponent_round_to_even() { return -4; } template <> inline constexpr int binary_format::min_exponent_round_to_even() { return -17; } template <> inline constexpr int binary_format::minimum_exponent() { return -1023; } template <> inline constexpr int binary_format::minimum_exponent() { return -127; } template <> inline constexpr int binary_format::infinite_power() { return 0x7FF; } template <> inline constexpr int binary_format::infinite_power() { return 0xFF; } template <> inline constexpr int binary_format::sign_index() { return 63; } template <> inline constexpr int binary_format::sign_index() { return 31; } template <> inline constexpr int binary_format::max_exponent_fast_path() { return 22; } template <> inline constexpr int binary_format::max_exponent_fast_path() { return 10; } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { return uint64_t(2) << mantissa_explicit_bits(); } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { return uint64_t(2) << mantissa_explicit_bits(); } // credit: Jakub Jelínek #ifdef __STDCPP_FLOAT16_T__ template struct binary_format_lookup_tables { static constexpr std::float16_t powers_of_ten[] = {1e0f16, 1e1f16, 1e2f16, 1e3f16, 1e4f16}; // Largest integer value v so that (5**index * v) <= 1<<11. // 0x800 == 1<<11 static constexpr uint64_t max_mantissa[] = {0x800, 0x800 / 5, 0x800 / (5 * 5), 0x800 / (5 * 5 * 5), 0x800 / (5 * 5 * 5 * 5), 0x800 / (constant_55555)}; }; #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE template constexpr std::float16_t binary_format_lookup_tables::powers_of_ten[]; template constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; #endif template <> inline constexpr std::float16_t binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)powers_of_ten[0], powers_of_ten[power]; } template <> inline constexpr binary_format::equiv_uint binary_format::exponent_mask() { return 0x7C00; } template <> inline constexpr binary_format::equiv_uint binary_format::mantissa_mask() { return 0x03FF; } template <> inline constexpr binary_format::equiv_uint binary_format::hidden_bit_mask() { return 0x0400; } template <> inline constexpr int binary_format::max_exponent_fast_path() { return 4; } template <> inline constexpr int binary_format::mantissa_explicit_bits() { return 10; } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { return uint64_t(2) << mantissa_explicit_bits(); } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { // caller is responsible to ensure that // power >= 0 && power <= 4 // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)max_mantissa[0], max_mantissa[power]; } template <> inline constexpr int binary_format::min_exponent_fast_path() { return 0; } template <> inline constexpr int binary_format::max_exponent_round_to_even() { return 5; } template <> inline constexpr int binary_format::min_exponent_round_to_even() { return -22; } template <> inline constexpr int binary_format::minimum_exponent() { return -15; } template <> inline constexpr int binary_format::infinite_power() { return 0x1F; } template <> inline constexpr int binary_format::sign_index() { return 15; } template <> inline constexpr int binary_format::largest_power_of_ten() { return 4; } template <> inline constexpr int binary_format::smallest_power_of_ten() { return -27; } template <> inline constexpr size_t binary_format::max_digits() { return 22; } #endif // __STDCPP_FLOAT16_T__ // credit: Jakub Jelínek #ifdef __STDCPP_BFLOAT16_T__ template struct binary_format_lookup_tables { static constexpr std::bfloat16_t powers_of_ten[] = {1e0bf16, 1e1bf16, 1e2bf16, 1e3bf16}; // Largest integer value v so that (5**index * v) <= 1<<8. // 0x100 == 1<<8 static constexpr uint64_t max_mantissa[] = {0x100, 0x100 / 5, 0x100 / (5 * 5), 0x100 / (5 * 5 * 5), 0x100 / (5 * 5 * 5 * 5)}; }; #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE template constexpr std::bfloat16_t binary_format_lookup_tables::powers_of_ten[]; template constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; #endif template <> inline constexpr std::bfloat16_t binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)powers_of_ten[0], powers_of_ten[power]; } template <> inline constexpr int binary_format::max_exponent_fast_path() { return 3; } template <> inline constexpr binary_format::equiv_uint binary_format::exponent_mask() { return 0x7F80; } template <> inline constexpr binary_format::equiv_uint binary_format::mantissa_mask() { return 0x007F; } template <> inline constexpr binary_format::equiv_uint binary_format::hidden_bit_mask() { return 0x0080; } template <> inline constexpr int binary_format::mantissa_explicit_bits() { return 7; } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { return uint64_t(2) << mantissa_explicit_bits(); } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { // caller is responsible to ensure that // power >= 0 && power <= 3 // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)max_mantissa[0], max_mantissa[power]; } template <> inline constexpr int binary_format::min_exponent_fast_path() { return 0; } template <> inline constexpr int binary_format::max_exponent_round_to_even() { return 3; } template <> inline constexpr int binary_format::min_exponent_round_to_even() { return -24; } template <> inline constexpr int binary_format::minimum_exponent() { return -127; } template <> inline constexpr int binary_format::infinite_power() { return 0xFF; } template <> inline constexpr int binary_format::sign_index() { return 15; } template <> inline constexpr int binary_format::largest_power_of_ten() { return 38; } template <> inline constexpr int binary_format::smallest_power_of_ten() { return -60; } template <> inline constexpr size_t binary_format::max_digits() { return 98; } #endif // __STDCPP_BFLOAT16_T__ template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { // caller is responsible to ensure that // power >= 0 && power <= 22 // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)max_mantissa[0], max_mantissa[power]; } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { // caller is responsible to ensure that // power >= 0 && power <= 10 // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)max_mantissa[0], max_mantissa[power]; } template <> inline constexpr double binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)powers_of_ten[0], powers_of_ten[power]; } template <> inline constexpr float binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)powers_of_ten[0], powers_of_ten[power]; } template <> inline constexpr int binary_format::largest_power_of_ten() { return 308; } template <> inline constexpr int binary_format::largest_power_of_ten() { return 38; } template <> inline constexpr int binary_format::smallest_power_of_ten() { return -342; } template <> inline constexpr int binary_format::smallest_power_of_ten() { return -64; } template <> inline constexpr size_t binary_format::max_digits() { return 769; } template <> inline constexpr size_t binary_format::max_digits() { return 114; } template <> inline constexpr binary_format::equiv_uint binary_format::exponent_mask() { return 0x7F800000; } template <> inline constexpr binary_format::equiv_uint binary_format::exponent_mask() { return 0x7FF0000000000000; } template <> inline constexpr binary_format::equiv_uint binary_format::mantissa_mask() { return 0x007FFFFF; } template <> inline constexpr binary_format::equiv_uint binary_format::mantissa_mask() { return 0x000FFFFFFFFFFFFF; } template <> inline constexpr binary_format::equiv_uint binary_format::hidden_bit_mask() { return 0x00800000; } template <> inline constexpr binary_format::equiv_uint binary_format::hidden_bit_mask() { return 0x0010000000000000; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void to_float(bool negative, adjusted_mantissa am, T &value) { using equiv_uint = equiv_uint_t; equiv_uint word = equiv_uint(am.mantissa); word = equiv_uint(word | equiv_uint(am.power2) << binary_format::mantissa_explicit_bits()); word = equiv_uint(word | equiv_uint(negative) << binary_format::sign_index()); #if FASTFLOAT_HAS_BIT_CAST value = std::bit_cast(word); #else ::memcpy(&value, &word, sizeof(T)); #endif } template struct space_lut { static constexpr bool value[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; }; #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE template constexpr bool space_lut::value[]; #endif template constexpr bool is_space(UC c) { return c < 256 && space_lut<>::value[uint8_t(c)]; } template static constexpr uint64_t int_cmp_zeros() { static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), "Unsupported character size"); return (sizeof(UC) == 1) ? 0x3030303030303030 : (sizeof(UC) == 2) ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | uint64_t(UC('0')) << 16 | UC('0')) : (uint64_t(UC('0')) << 32 | UC('0')); } template static constexpr int int_cmp_len() { return sizeof(uint64_t) / sizeof(UC); } template constexpr UC const *str_const_nan(); template <> constexpr char const *str_const_nan() { return "nan"; } template <> constexpr wchar_t const *str_const_nan() { return L"nan"; } template <> constexpr char16_t const *str_const_nan() { return u"nan"; } template <> constexpr char32_t const *str_const_nan() { return U"nan"; } #ifdef __cpp_char8_t template <> constexpr char8_t const *str_const_nan() { return u8"nan"; } #endif template constexpr UC const *str_const_inf(); template <> constexpr char const *str_const_inf() { return "infinity"; } template <> constexpr wchar_t const *str_const_inf() { return L"infinity"; } template <> constexpr char16_t const *str_const_inf() { return u"infinity"; } template <> constexpr char32_t const *str_const_inf() { return U"infinity"; } #ifdef __cpp_char8_t template <> constexpr char8_t const *str_const_inf() { return u8"infinity"; } #endif template struct int_luts { static constexpr uint8_t chdigit[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; static constexpr size_t maxdigits_u64[] = { 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; static constexpr uint64_t min_safe_u64[] = { 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, 7450580596923828125, 4738381338321616896, 3909821048582988049, 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, 5559917313492231481, 2218611106740436992, 8650415919381337933, 2177953337809371136, 6568408355712890625, 1152921504606846976, 2862423051509815793, 6746640616477458432, 15181127029874798299ull, 1638400000000000000, 3243919932521508681, 6221821273427820544, 11592836324538749809ull, 876488338465357824, 1490116119384765625, 2481152873203736576, 4052555153018976267, 6502111422497947648, 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, 1152921504606846976, 1667889514952984961, 2386420683693101056, 3379220508056640625, 4738381338321616896}; }; #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE template constexpr uint8_t int_luts::chdigit[]; template constexpr size_t int_luts::maxdigits_u64[]; template constexpr uint64_t int_luts::min_safe_u64[]; #endif template fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { return int_luts<>::chdigit[static_cast(c)]; } fastfloat_really_inline constexpr size_t max_digits_u64(int base) { return int_luts<>::maxdigits_u64[base - 2]; } // If a u64 is exactly max_digits_u64() in length, this is // the value below which it has definitely overflowed. fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) { return int_luts<>::min_safe_u64[base - 2]; } static_assert(std::is_same, uint64_t>::value, "equiv_uint should be uint64_t for double"); static_assert(std::numeric_limits::is_iec559, "double must fulfill the requirements of IEC 559 (IEEE 754)"); static_assert(std::is_same, uint32_t>::value, "equiv_uint should be uint32_t for float"); static_assert(std::numeric_limits::is_iec559, "float must fulfill the requirements of IEC 559 (IEEE 754)"); #ifdef __STDCPP_FLOAT64_T__ static_assert(std::is_same, uint64_t>::value, "equiv_uint should be uint64_t for std::float64_t"); static_assert( std::numeric_limits::is_iec559, "std::float64_t must fulfill the requirements of IEC 559 (IEEE 754)"); #endif // __STDCPP_FLOAT64_T__ #ifdef __STDCPP_FLOAT32_T__ static_assert(std::is_same, uint32_t>::value, "equiv_uint should be uint32_t for std::float32_t"); static_assert( std::numeric_limits::is_iec559, "std::float32_t must fulfill the requirements of IEC 559 (IEEE 754)"); #endif // __STDCPP_FLOAT32_T__ #ifdef __STDCPP_FLOAT16_T__ static_assert( std::is_same::equiv_uint, uint16_t>::value, "equiv_uint should be uint16_t for std::float16_t"); static_assert( std::numeric_limits::is_iec559, "std::float16_t must fulfill the requirements of IEC 559 (IEEE 754)"); #endif // __STDCPP_FLOAT16_T__ #ifdef __STDCPP_BFLOAT16_T__ static_assert( std::is_same::equiv_uint, uint16_t>::value, "equiv_uint should be uint16_t for std::bfloat16_t"); static_assert( std::numeric_limits::is_iec559, "std::bfloat16_t must fulfill the requirements of IEC 559 (IEEE 754)"); #endif // __STDCPP_BFLOAT16_T__ constexpr chars_format operator~(chars_format rhs) noexcept { using int_type = std::underlying_type::type; return static_cast(~static_cast(rhs)); } constexpr chars_format operator&(chars_format lhs, chars_format rhs) noexcept { using int_type = std::underlying_type::type; return static_cast(static_cast(lhs) & static_cast(rhs)); } constexpr chars_format operator|(chars_format lhs, chars_format rhs) noexcept { using int_type = std::underlying_type::type; return static_cast(static_cast(lhs) | static_cast(rhs)); } constexpr chars_format operator^(chars_format lhs, chars_format rhs) noexcept { using int_type = std::underlying_type::type; return static_cast(static_cast(lhs) ^ static_cast(rhs)); } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & operator&=(chars_format &lhs, chars_format rhs) noexcept { return lhs = (lhs & rhs); } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & operator|=(chars_format &lhs, chars_format rhs) noexcept { return lhs = (lhs | rhs); } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & operator^=(chars_format &lhs, chars_format rhs) noexcept { return lhs = (lhs ^ rhs); } namespace detail { // adjust for deprecated feature macros constexpr chars_format adjust_for_feature_macros(chars_format fmt) { return fmt #ifdef FASTFLOAT_ALLOWS_LEADING_PLUS | chars_format::allow_leading_plus #endif #ifdef FASTFLOAT_SKIP_WHITE_SPACE | chars_format::skip_white_space #endif ; } } // namespace detail } // namespace fast_float #endif #ifndef FASTFLOAT_FAST_FLOAT_H #define FASTFLOAT_FAST_FLOAT_H namespace fast_float { /** * This function parses the character sequence [first,last) for a number. It * parses floating-point numbers expecting a locale-indepent format equivalent * to what is used by std::strtod in the default ("C") locale. The resulting * floating-point value is the closest floating-point values (using either float * or double), using the "round to even" convention for values that would * otherwise fall right in-between two values. That is, we provide exact parsing * according to the IEEE standard. * * Given a successful parse, the pointer (`ptr`) in the returned value is set to * point right after the parsed number, and the `value` referenced is set to the * parsed value. In case of error, the returned `ec` contains a representative * error, otherwise the default (`std::errc()`) value is stored. * * The implementation does not throw and does not allocate memory (e.g., with * `new` or `malloc`). * * Like the C++17 standard, the `fast_float::from_chars` functions take an * optional last argument of the type `fast_float::chars_format`. It is a bitset * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & * fast_float::chars_format::scientific` are set to determine whether we allow * the fixed point and scientific notation respectively. The default is * `fast_float::chars_format::general` which allows both `fixed` and * `scientific`. */ template ::value)> FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, chars_format fmt = chars_format::general) noexcept; /** * Like from_chars, but accepts an `options` argument to govern number parsing. * Both for floating-point types and integer types. */ template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(UC const *first, UC const *last, T &value, parse_options_t options) noexcept; /** * from_chars for integer types. */ template ::value)> FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; } // namespace fast_float #endif // FASTFLOAT_FAST_FLOAT_H #ifndef FASTFLOAT_ASCII_NUMBER_H #define FASTFLOAT_ASCII_NUMBER_H //included above: //#include //included above: //#include //included above: //#include #include //included above: //#include //included above: //#include #ifdef FASTFLOAT_SSE2 #include #endif #ifdef FASTFLOAT_NEON #include #endif namespace fast_float { template fastfloat_really_inline constexpr bool has_simd_opt() { #ifdef FASTFLOAT_HAS_SIMD return std::is_same::value; #else return false; #endif } // Next function can be micro-optimized, but compilers are entirely // able to optimize it well. template fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { return !(c > UC('9') || c < UC('0')); } fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; } // Read 8 UC into a u64. Truncates UC if not char. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t read8_to_u64(UC const *chars) { if (cpp20_and_in_constexpr() || !std::is_same::value) { uint64_t val = 0; for (int i = 0; i < 8; ++i) { val |= uint64_t(uint8_t(*chars)) << (i * 8); ++chars; } return val; } uint64_t val; ::memcpy(&val, chars, sizeof(uint64_t)); #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. val = byteswap(val); #endif return val; } #ifdef FASTFLOAT_SSE2 fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { FASTFLOAT_SIMD_DISABLE_WARNINGS __m128i const packed = _mm_packus_epi16(data, data); #ifdef FASTFLOAT_64BIT return uint64_t(_mm_cvtsi128_si64(packed)); #else uint64_t value; // Visual Studio + older versions of GCC don't support _mm_storeu_si64 _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed); return value; #endif FASTFLOAT_SIMD_RESTORE_WARNINGS } fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { FASTFLOAT_SIMD_DISABLE_WARNINGS return simd_read8_to_u64( _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars))); FASTFLOAT_SIMD_RESTORE_WARNINGS } #elif defined(FASTFLOAT_NEON) fastfloat_really_inline uint64_t simd_read8_to_u64(uint16x8_t const data) { FASTFLOAT_SIMD_DISABLE_WARNINGS uint8x8_t utf8_packed = vmovn_u16(data); return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); FASTFLOAT_SIMD_RESTORE_WARNINGS } fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { FASTFLOAT_SIMD_DISABLE_WARNINGS return simd_read8_to_u64( vld1q_u16(reinterpret_cast(chars))); FASTFLOAT_SIMD_RESTORE_WARNINGS } #endif // FASTFLOAT_SSE2 // MSVC SFINAE is broken pre-VS2017 #if defined(_MSC_VER) && _MSC_VER <= 1900 template #else template ()) = 0> #endif // dummy for compile uint64_t simd_read8_to_u64(UC const *) { return 0; } // credit @aqrit fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t parse_eight_digits_unrolled(uint64_t val) { uint64_t const mask = 0x000000FF000000FF; uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) val -= 0x3030303030303030; val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; return uint32_t(val); } // Call this if chars are definitely 8 digits. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t parse_eight_digits_unrolled(UC const *chars) noexcept { if (cpp20_and_in_constexpr() || !has_simd_opt()) { return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay } return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); } // credit @aqrit fastfloat_really_inline constexpr bool is_made_of_eight_digits_fast(uint64_t val) noexcept { return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & 0x8080808080808080)); } #ifdef FASTFLOAT_HAS_SIMD // Call this if chars might not be 8 digits. // Using this style (instead of is_made_of_eight_digits_fast() then // parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool simd_parse_if_eight_digits_unrolled(char16_t const *chars, uint64_t &i) noexcept { if (cpp20_and_in_constexpr()) { return false; } #ifdef FASTFLOAT_SSE2 FASTFLOAT_SIMD_DISABLE_WARNINGS __m128i const data = _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)); // (x - '0') <= 9 // http://0x80.pl/articles/simd-parsing-int-sequences.html __m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); __m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); if (_mm_movemask_epi8(t1) == 0) { i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); return true; } else return false; FASTFLOAT_SIMD_RESTORE_WARNINGS #elif defined(FASTFLOAT_NEON) FASTFLOAT_SIMD_DISABLE_WARNINGS uint16x8_t const data = vld1q_u16(reinterpret_cast(chars)); // (x - '0') <= 9 // http://0x80.pl/articles/simd-parsing-int-sequences.html uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0')); uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); if (vminvq_u16(mask) == 0xFFFF) { i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); return true; } else return false; FASTFLOAT_SIMD_RESTORE_WARNINGS #else (void)chars; (void)i; return false; #endif // FASTFLOAT_SSE2 } #endif // FASTFLOAT_HAS_SIMD // MSVC SFINAE is broken pre-VS2017 #if defined(_MSC_VER) && _MSC_VER <= 1900 template #else template ()) = 0> #endif // dummy for compile bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) { return 0; } template ::value) = 0> fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void loop_parse_if_eight_digits(UC const *&p, UC const *const pend, uint64_t &i) { if (!has_simd_opt()) { return; } while ((std::distance(p, pend) >= 8) && simd_parse_if_eight_digits_unrolled( p, i)) { // in rare cases, this will overflow, but that's ok p += 8; } } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void loop_parse_if_eight_digits(char const *&p, char const *const pend, uint64_t &i) { // optimizes better than parse_if_eight_digits_unrolled() for UC = char. while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(read8_to_u64(p))) { i = i * 100000000 + parse_eight_digits_unrolled(read8_to_u64( p)); // in rare cases, this will overflow, but that's ok p += 8; } } enum class parse_error { no_error, // [JSON-only] The minus sign must be followed by an integer. missing_integer_after_sign, // A sign must be followed by an integer or dot. missing_integer_or_dot_after_sign, // [JSON-only] The integer part must not have leading zeros. leading_zeros_in_integer_part, // [JSON-only] The integer part must have at least one digit. no_digits_in_integer_part, // [JSON-only] If there is a decimal point, there must be digits in the // fractional part. no_digits_in_fractional_part, // The mantissa must have at least one digit. no_digits_in_mantissa, // Scientific notation requires an exponential part. missing_exponential_part, }; template struct parsed_number_string_t { int64_t exponent{0}; uint64_t mantissa{0}; UC const *lastmatch{nullptr}; bool negative{false}; bool valid{false}; bool too_many_digits{false}; // contains the range of the significant digits span integer{}; // non-nullable span fraction{}; // nullable parse_error error{parse_error::no_error}; }; using byte_span = span; using parsed_number_string = parsed_number_string_t; template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t report_parse_error(UC const *p, parse_error error) { parsed_number_string_t answer; answer.valid = false; answer.lastmatch = p; answer.error = error; return answer; } // Assuming that you use no more than 19 digits, this will // parse an ASCII string. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t parse_number_string(UC const *p, UC const *pend, parse_options_t options) noexcept { chars_format const fmt = detail::adjust_for_feature_macros(options.format); UC const decimal_point = options.decimal_point; parsed_number_string_t answer; answer.valid = false; answer.too_many_digits = false; // assume p < pend, so dereference without checks; answer.negative = (*p == UC('-')); // C++17 20.19.3.(7.1) explicitly forbids '+' sign here if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) && !uint64_t(fmt & detail::basic_json_fmt) && *p == UC('+'))) { ++p; if (p == pend) { return report_parse_error( p, parse_error::missing_integer_or_dot_after_sign); } if (uint64_t(fmt & detail::basic_json_fmt)) { if (!is_integer(*p)) { // a sign must be followed by an integer return report_parse_error(p, parse_error::missing_integer_after_sign); } } else { if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot return report_parse_error( p, parse_error::missing_integer_or_dot_after_sign); } } } UC const *const start_digits = p; uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) while ((p != pend) && is_integer(*p)) { // a multiplication by 10 is cheaper than an arbitrary integer // multiplication i = 10 * i + uint64_t(*p - UC('0')); // might overflow, we will handle the overflow later ++p; } UC const *const end_of_integer_part = p; int64_t digit_count = int64_t(end_of_integer_part - start_digits); answer.integer = span(start_digits, size_t(digit_count)); if (uint64_t(fmt & detail::basic_json_fmt)) { // at least 1 digit in integer part, without leading zeros if (digit_count == 0) { return report_parse_error(p, parse_error::no_digits_in_integer_part); } if ((start_digits[0] == UC('0') && digit_count > 1)) { return report_parse_error(start_digits, parse_error::leading_zeros_in_integer_part); } } int64_t exponent = 0; bool const has_decimal_point = (p != pend) && (*p == decimal_point); if (has_decimal_point) { ++p; UC const *before = p; // can occur at most twice without overflowing, but let it occur more, since // for integers with many digits, digit parsing is the primary bottleneck. loop_parse_if_eight_digits(p, pend, i); while ((p != pend) && is_integer(*p)) { uint8_t digit = uint8_t(*p - UC('0')); ++p; i = i * 10 + digit; // in rare cases, this will overflow, but that's ok } exponent = before - p; answer.fraction = span(before, size_t(p - before)); digit_count -= exponent; } if (uint64_t(fmt & detail::basic_json_fmt)) { // at least 1 digit in fractional part if (has_decimal_point && exponent == 0) { return report_parse_error(p, parse_error::no_digits_in_fractional_part); } } else if (digit_count == 0) { // we must have encountered at least one integer! return report_parse_error(p, parse_error::no_digits_in_mantissa); } int64_t exp_number = 0; // explicit exponential part if ((uint64_t(fmt & chars_format::scientific) && (p != pend) && ((UC('e') == *p) || (UC('E') == *p))) || (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) && ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || (UC('D') == *p)))) { UC const *location_of_e = p; if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || (UC('D') == *p)) { ++p; } bool neg_exp = false; if ((p != pend) && (UC('-') == *p)) { neg_exp = true; ++p; } else if ((p != pend) && (UC('+') == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) ++p; } if ((p == pend) || !is_integer(*p)) { if (!uint64_t(fmt & chars_format::fixed)) { // The exponential part is invalid for scientific notation, so it must // be a trailing token for fixed notation. However, fixed notation is // disabled, so report a scientific notation error. return report_parse_error(p, parse_error::missing_exponential_part); } // Otherwise, we will be ignoring the 'e'. p = location_of_e; } else { while ((p != pend) && is_integer(*p)) { uint8_t digit = uint8_t(*p - UC('0')); if (exp_number < 0x10000000) { exp_number = 10 * exp_number + digit; } ++p; } if (neg_exp) { exp_number = -exp_number; } exponent += exp_number; } } else { // If it scientific and not fixed, we have to bail out. if (uint64_t(fmt & chars_format::scientific) && !uint64_t(fmt & chars_format::fixed)) { return report_parse_error(p, parse_error::missing_exponential_part); } } answer.lastmatch = p; answer.valid = true; // If we frequently had to deal with long strings of digits, // we could extend our code by using a 128-bit integer instead // of a 64-bit integer. However, this is uncommon. // // We can deal with up to 19 digits. if (digit_count > 19) { // this is uncommon // It is possible that the integer had an overflow. // We have to handle the case where we have 0.0000somenumber. // We need to be mindful of the case where we only have zeroes... // E.g., 0.000000000...000. UC const *start = start_digits; while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { if (*start == UC('0')) { digit_count--; } start++; } if (digit_count > 19) { answer.too_many_digits = true; // Let us start again, this time, avoiding overflows. // We don't need to check if is_integer, since we use the // pre-tokenized spans from above. i = 0; p = answer.integer.ptr; UC const *int_end = p + answer.integer.len(); uint64_t const minimal_nineteen_digit_integer{1000000000000000000}; while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { i = i * 10 + uint64_t(*p - UC('0')); ++p; } if (i >= minimal_nineteen_digit_integer) { // We have a big integers exponent = end_of_integer_part - p + exp_number; } else { // We have a value with a fractional component. p = answer.fraction.ptr; UC const *frac_end = p + answer.fraction.len(); while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { i = i * 10 + uint64_t(*p - UC('0')); ++p; } exponent = answer.fraction.ptr - p + exp_number; } // We have now corrected both exponent and i, to a truncated value } } answer.exponent = exponent; answer.mantissa = i; return answer; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t parse_int_string(UC const *p, UC const *pend, T &value, parse_options_t options) { chars_format const fmt = detail::adjust_for_feature_macros(options.format); int const base = options.base; from_chars_result_t answer; UC const *const first = p; bool const negative = (*p == UC('-')); #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(push) #pragma warning(disable : 4127) #endif if (!std::is_signed::value && negative) { #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(pop) #endif answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) && (*p == UC('+')))) { ++p; } UC const *const start_num = p; while (p != pend && *p == UC('0')) { ++p; } bool const has_leading_zeros = p > start_num; UC const *const start_digits = p; uint64_t i = 0; if (base == 10) { loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible } while (p != pend) { uint8_t digit = ch_to_digit(*p); if (digit >= base) { break; } i = uint64_t(base) * i + digit; // might overflow, check this later p++; } size_t digit_count = size_t(p - start_digits); if (digit_count == 0) { if (has_leading_zeros) { value = 0; answer.ec = std::errc(); answer.ptr = p; } else { answer.ec = std::errc::invalid_argument; answer.ptr = first; } return answer; } answer.ptr = p; // check u64 overflow size_t max_digits = max_digits_u64(base); if (digit_count > max_digits) { answer.ec = std::errc::result_out_of_range; return answer; } // this check can be eliminated for all other types, but they will all require // a max_digits(base) equivalent if (digit_count == max_digits && i < min_safe_u64(base)) { answer.ec = std::errc::result_out_of_range; return answer; } // check other types overflow if (!std::is_same::value) { if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { answer.ec = std::errc::result_out_of_range; return answer; } } if (negative) { #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(push) #pragma warning(disable : 4146) #endif // this weird workaround is required because: // - converting unsigned to signed when its value is greater than signed max // is UB pre-C++23. // - reinterpret_casting (~i + 1) would work, but it is not constexpr // this is always optimized into a neg instruction (note: T is an integer // type) value = T(-std::numeric_limits::max() - T(i - uint64_t(std::numeric_limits::max()))); #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(pop) #endif } else { value = T(i); } answer.ec = std::errc(); return answer; } } // namespace fast_float #endif #ifndef FASTFLOAT_FAST_TABLE_H #define FASTFLOAT_FAST_TABLE_H //included above: //#include namespace fast_float { /** * When mapping numbers from decimal to binary, * we go from w * 10^q to m * 2^p but we have * 10^q = 5^q * 2^q, so effectively * we are trying to match * w * 2^q * 5^q to m * 2^p. Thus the powers of two * are not a concern since they can be represented * exactly using the binary notation, only the powers of five * affect the binary significand. */ /** * The smallest non-zero float (binary64) is 2^-1074. * We take as input numbers of the form w x 10^q where w < 2^64. * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. * However, we have that * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. * Thus it is possible for a number of the form w * 10^-342 where * w is a 64-bit value to be a non-zero floating-point number. ********* * Any number of form w * 10^309 where w>= 1 is going to be * infinite in binary64 so we never need to worry about powers * of 5 greater than 308. */ template struct powers_template { constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); // Powers of five from 5^-342 all the way to 5^308 rounded toward one. constexpr static uint64_t power_of_five_128[number_of_entries] = { 0xeef453d6923bd65a, 0x113faa2906a13b3f, 0x9558b4661b6565f8, 0x4ac7ca59a424c507, 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, 0x91d8a02bb6c10594, 0x79071b9b8a4be869, 0xb64ec836a47146f9, 0x9748e2826cdee284, 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, 0xb208ef855c969f4f, 0xbdbd2d335e51a935, 0xde8b2b66b3bc4723, 0xad2c788035e61382, 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, 0xa9c98d8ccb009506, 0x680efdaf511f18c2, 0xd43bf0effdc0ba48, 0x212bd1b2566def2, 0x84a57695fe98746d, 0x14bb630f7604b57, 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, 0xcf42894a5dce35ea, 0x52064cac828675b9, 0x818995ce7aa0e1b2, 0x7343efebd1940993, 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, 0xca66fa129f9b60a6, 0xd41a26e077774ef6, 0xfd00b897478238d0, 0x8920b098955522b4, 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, 0xc5a890362fddbc62, 0xeb2189f734aa831d, 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, 0x9a6bb0aa55653b2d, 0x47b233c92125366e, 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, 0xf148440a256e2c76, 0xc00670ea43ca250d, 0x96cd2a865764dbca, 0x380406926a5e5728, 0xbc807527ed3e12bc, 0xc605083704f5ecf2, 0xeba09271e88d976b, 0xf7864a44c633682e, 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, 0xb8157268fdae9e4c, 0x5960ea05bad82964, 0xe61acf033d1a45df, 0x6fb92487298e33bd, 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, 0xe0b62e2929aba83c, 0x331acdabfe94de87, 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, 0x892731ac9faf056e, 0xbe311c083a225cd2, 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, 0xd64d3d9db981787d, 0x92cbbccdad5b108, 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, 0xa76c582338ed2621, 0xaf2af2b80af6f24e, 0xd1476e2c07286faa, 0x1af5af660db4aee1, 0x82cca4db847945ca, 0x50d98d9fc890ed4d, 0xa37fce126597973c, 0xe50ff107bab528a0, 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, 0x9faacf3df73609b1, 0x77b191618c54e9ac, 0xc795830d75038c1d, 0xd59df5b9ef6a2417, 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, 0x9becce62836ac577, 0x4ee367f9430aec32, 0xc2e801fb244576d5, 0x229c41f793cda73f, 0xf3a20279ed56d48a, 0x6b43527578c1110f, 0x9845418c345644d6, 0x830a13896b78aaa9, 0xbe5691ef416bd60c, 0x23cc986bc656d553, 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, 0x91376c36d99995be, 0x23100809b9c21fa1, 0xb58547448ffffb2d, 0xabd40a0c2832a78a, 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, 0xdd95317f31c7fa1d, 0x40405643d711d583, 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, 0xad1c8eab5ee43b66, 0xda3243650005eecf, 0xd863b256369d4a40, 0x90bed43e40076a82, 0x873e4f75e2224e68, 0x5a7744a6e804a291, 0xa90de3535aaae202, 0x711515d0a205cb36, 0xd3515c2831559a83, 0xd5a5b44ca873e03, 0x8412d9991ed58091, 0xe858790afe9486c2, 0xa5178fff668ae0b6, 0x626e974dbe39a872, 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, 0xc987434744ac874e, 0xa327ffb266b56220, 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, 0xc4ce17b399107c22, 0xcb550fb4384d21d3, 0xf6019da07f549b2b, 0x7e2a53a146606a48, 0x99c102844f94e0fb, 0x2eda7444cbfc426d, 0xc0314325637a1939, 0xfa911155fefb5308, 0xf03d93eebc589f88, 0x793555ab7eba27ca, 0x96267c7535b763b5, 0x4bc1558b2f3458de, 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, 0xea9c227723ee8bcb, 0x465e15a979c1cadc, 0x92a1958a7675175f, 0xbfacd89ec191ec9, 0xb749faed14125d36, 0xcef980ec671f667b, 0xe51c79a85916f484, 0x82b7e12780e7401a, 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, 0xdfbdcece67006ac9, 0x67a791e093e1d49a, 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, 0xaecc49914078536d, 0x58fae9f773886e18, 0xda7f5bf590966848, 0xaf39a475506a899e, 0x888f99797a5e012d, 0x6d8406c952429603, 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, 0xd0601d8efc57b08b, 0xf13b94daf124da26, 0x823c12795db6ce57, 0x76c53d08d6b70858, 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, 0xf867241c8cc6d4c0, 0xc30163d203c94b62, 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, 0xc21094364dfb5636, 0x985915fc12f542e4, 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, 0xbd8430bd08277231, 0x50c6ff782a838353, 0xece53cec4a314ebd, 0xa4f8bf5635246428, 0x940f4613ae5ed136, 0x871b7795e136be99, 0xb913179899f68584, 0x28e2557b59846e3f, 0xe757dd7ec07426e5, 0x331aeada2fe589cf, 0x9096ea6f3848984f, 0x3ff0d2c85def7621, 0xb4bca50b065abe63, 0xfed077a756b53a9, 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, 0xb080392cc4349dec, 0xbd8d794d96aacfb3, 0xdca04777f541c567, 0xecf0d7a0fc5583a0, 0x89e42caaf9491b60, 0xf41686c49db57244, 0xac5d37d5b79b6239, 0x311c2875c522ced5, 0xd77485cb25823ac7, 0x7d633293366b828b, 0x86a8d39ef77164bc, 0xae5dff9c02033197, 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, 0xd267caa862a12d66, 0xd072df63c324fd7b, 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, 0xa46116538d0deb78, 0x52d9be85f074e608, 0xcd795be870516656, 0x67902e276c921f8b, 0x806bd9714632dff6, 0xba1cd8a3db53b6, 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, 0xfad2a4b13d1b5d6c, 0x796b805720085f81, 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, 0xf4f1b4d515acb93b, 0xee92fb5515482d44, 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, 0xef340a98172aace4, 0x86fb897116c87c34, 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, 0xbae0a846d2195712, 0x8974836059cca109, 0xe998d258869facd7, 0x2bd1a438703fc94b, 0x91ff83775423cc06, 0x7b6306a34627ddcf, 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, 0x8e938662882af53e, 0x547eb47b7282ee9c, 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, 0xae0b158b4738705e, 0x9624ab50b148d445, 0xd98ddaee19068c76, 0x3badd624dd9b0957, 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, 0xd47487cc8470652b, 0x7647c3200069671f, 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, 0xa5fb0a17c777cf09, 0xf468107100525890, 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, 0x81ac1fe293d599bf, 0xc6f14cd848405530, 0xa21727db38cb002f, 0xb8ada00e5a506a7c, 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, 0xfd442e4688bd304a, 0x908f4a166d1da663, 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, 0xf7549530e188c128, 0xd12bee59e68ef47c, 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, 0xc13a148e3032d6e7, 0xe36a52363c1faf01, 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, 0xebdf661791d60f56, 0x111b495b3464ad21, 0x936b9fcebb25c995, 0xcab10dd900beec34, 0xb84687c269ef3bfb, 0x3d5d514f40eea742, 0xe65829b3046b0afa, 0xcb4a5a3112a5112, 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, 0xb3f4e093db73a093, 0x59ed216765690f56, 0xe0f218b8d25088b8, 0x306869c13ec3532c, 0x8c974f7383725573, 0x1e414218c73a13fb, 0xafbd2350644eeacf, 0xe5d1929ef90898fa, 0xdbac6c247d62a583, 0xdf45f746b74abf39, 0x894bc396ce5da772, 0x6b8bba8c328eb783, 0xab9eb47c81f5114f, 0x66ea92f3f326564, 0xd686619ba27255a2, 0xc80a537b0efefebd, 0x8613fd0145877585, 0xbd06742ce95f5f36, 0xa798fc4196e952e7, 0x2c48113823b73704, 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, 0x82ef85133de648c4, 0x9a984d73dbe722fb, 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, 0xcc963fee10b7d1b3, 0x318df905079926a8, 0xffbbcfe994e5c61f, 0xfdf17746497f7052, 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, 0x9c1661a651213e2d, 0x6bea10ca65c084e, 0xc31bfa0fe5698db8, 0x486e494fcff30a62, 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, 0xbe89523386091465, 0xf6bbb397f1135823, 0xee2ba6c0678b597f, 0x746aa07ded582e2c, 0x94db483840b717ef, 0xa8c2a44eb4571cdc, 0xba121a4650e4ddeb, 0x92f34d62616ce413, 0xe896a0d7e51e1566, 0x77b020baf9c81d17, 0x915e2486ef32cd60, 0xace1474dc1d122e, 0xb5b5ada8aaff80b8, 0xd819992132456ba, 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, 0xd89d64d57a607744, 0xe871c7bf077ba8b7, 0x87625f056c7c4a8b, 0x11471cd764ad4972, 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, 0xd389b47879823479, 0x4aff1d108d4ec2c3, 0x843610cb4bf160cb, 0xcedf722a585139ba, 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, 0xce947a3da6a9273e, 0x733d226229feea32, 0x811ccc668829b887, 0x806357d5a3f525f, 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, 0xc5029163f384a931, 0xa9e795e65d4df11, 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, 0x99ea0196163fa42e, 0x504bced1bf8e4e45, 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, 0xf07da27a82c37088, 0x5d767327bb4e5a4c, 0x964e858c91ba2655, 0x3a6a07f8d510f86f, 0xbbe226efb628afea, 0x890489f70a55368b, 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, 0xe55990879ddcaabd, 0xcc420a6a101d0515, 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, 0xb32df8e9f3546564, 0x47939822dc96abf9, 0xdff9772470297ebd, 0x59787e2b93bc56f7, 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, 0xaefae51477a06b03, 0xede622920b6b23f1, 0xdab99e59958885c4, 0xe95fab368e45eced, 0x88b402f7fd75539b, 0x11dbcb0218ebb414, 0xaae103b5fcd2a881, 0xd652bdc29f26a119, 0xd59944a37c0752a2, 0x4be76d3346f0495f, 0x857fcae62d8493a5, 0x6f70a4400c562ddb, 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, 0x825ecc24c873782f, 0x8ed400668c0c28c8, 0xa2f67f2dfa90563b, 0x728900802f0f32fa, 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, 0xfea126b7d78186bc, 0xe2f610c84987bfa8, 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, 0xc6ede63fa05d3143, 0x91503d1c79720dbb, 0xf8a95fcf88747d94, 0x75a44c6397ce912a, 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, 0xc24452da229b021b, 0xfbe85badce996168, 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, 0x97c560ba6b0919a5, 0xdccd879fc967d41a, 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, 0xed246723473e3813, 0x290123e9aab23b68, 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, 0xb94470938fa89bce, 0xf808e40e8d5b3e69, 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, 0x8d590723948a535f, 0x579c487e5a38ad0e, 0xb0af48ec79ace837, 0x2d835a9df0c6d851, 0xdcdb1b2798182244, 0xf8e431456cf88e65, 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, 0xa87fea27a539e9a5, 0x3f2398d747b36224, 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, 0x83a3eeeef9153e89, 0x1953cf68300424ac, 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, 0xcdb02555653131b6, 0x3792f412cb06794d, 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, 0xc8de047564d20a8b, 0xf245825a5a445275, 0xfb158592be068d2e, 0xeed6e2f0f0d56712, 0x9ced737bb6c4183d, 0x55464dd69685606b, 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, 0xf53304714d9265df, 0xd53dd99f4b3066a8, 0x993fe2c6d07b7fab, 0xe546a8038efe4029, 0xbf8fdb78849a5f96, 0xde98520472bdd033, 0xef73d256a5c0f77c, 0x963e66858f6d4440, 0x95a8637627989aad, 0xdde7001379a44aa8, 0xbb127c53b17ec159, 0x5560c018580d5d52, 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, 0x9226712162ab070d, 0xcab3961304ca70e8, 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, 0xb267ed1940f1c61c, 0x55f038b237591ed3, 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, 0xae397d8aa96c1b77, 0xabec975e0a0d081a, 0xd9c7dced53c72255, 0x96e7bd358c904a21, 0x881cea14545c7575, 0x7e50d64177da2e54, 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, 0xcfb11ead453994ba, 0x67de18eda5814af2, 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, 0xa2425ff75e14fc31, 0xa1258379a94d028d, 0xcad2f7f5359a3b3e, 0x96ee45813a04330, 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, 0x9e74d1b791e07e48, 0x775ea264cf55347e, 0xc612062576589dda, 0x95364afe032a819e, 0xf79687aed3eec551, 0x3a83ddbd83f52205, 0x9abe14cd44753b52, 0xc4926a9672793543, 0xc16d9a0095928a27, 0x75b7053c0f178294, 0xf1c90080baf72cb1, 0x5324c68b12dd6339, 0x971da05074da7bee, 0xd3f6fc16ebca5e04, 0xbce5086492111aea, 0x88f4bb1ca6bcf585, 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, 0x9392ee8e921d5d07, 0x3aff322e62439fd0, 0xb877aa3236a4b449, 0x9befeb9fad487c3, 0xe69594bec44de15b, 0x4c2ebe687989a9b4, 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, 0xb424dc35095cd80f, 0x538484c19ef38c95, 0xe12e13424bb40e13, 0x2865a5f206b06fba, 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, 0xafebff0bcb24aafe, 0xf78f69a51539d749, 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, 0x89705f4136b4a597, 0x31680a88f8953031, 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, 0xa7c5ac471b478423, 0xfcf80dc33721d54, 0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x83126e978d4fdf3b, 0x645a1cac083126ea, 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, 0xcccccccccccccccc, 0xcccccccccccccccd, 0x8000000000000000, 0x0, 0xa000000000000000, 0x0, 0xc800000000000000, 0x0, 0xfa00000000000000, 0x0, 0x9c40000000000000, 0x0, 0xc350000000000000, 0x0, 0xf424000000000000, 0x0, 0x9896800000000000, 0x0, 0xbebc200000000000, 0x0, 0xee6b280000000000, 0x0, 0x9502f90000000000, 0x0, 0xba43b74000000000, 0x0, 0xe8d4a51000000000, 0x0, 0x9184e72a00000000, 0x0, 0xb5e620f480000000, 0x0, 0xe35fa931a0000000, 0x0, 0x8e1bc9bf04000000, 0x0, 0xb1a2bc2ec5000000, 0x0, 0xde0b6b3a76400000, 0x0, 0x8ac7230489e80000, 0x0, 0xad78ebc5ac620000, 0x0, 0xd8d726b7177a8000, 0x0, 0x878678326eac9000, 0x0, 0xa968163f0a57b400, 0x0, 0xd3c21bcecceda100, 0x0, 0x84595161401484a0, 0x0, 0xa56fa5b99019a5c8, 0x0, 0xcecb8f27f4200f3a, 0x0, 0x813f3978f8940984, 0x4000000000000000, 0xa18f07d736b90be5, 0x5000000000000000, 0xc9f2c9cd04674ede, 0xa400000000000000, 0xfc6f7c4045812296, 0x4d00000000000000, 0x9dc5ada82b70b59d, 0xf020000000000000, 0xc5371912364ce305, 0x6c28000000000000, 0xf684df56c3e01bc6, 0xc732000000000000, 0x9a130b963a6c115c, 0x3c7f400000000000, 0xc097ce7bc90715b3, 0x4b9f100000000000, 0xf0bdc21abb48db20, 0x1e86d40000000000, 0x96769950b50d88f4, 0x1314448000000000, 0xbc143fa4e250eb31, 0x17d955a000000000, 0xeb194f8e1ae525fd, 0x5dcfab0800000000, 0x92efd1b8d0cf37be, 0x5aa1cae500000000, 0xb7abc627050305ad, 0xf14a3d9e40000000, 0xe596b7b0c643c719, 0x6d9ccd05d0000000, 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, 0xb35dbf821ae4f38b, 0xdda2802c8a800000, 0xe0352f62a19e306e, 0xd50b2037ad200000, 0x8c213d9da502de45, 0x4526f422cc340000, 0xaf298d050e4395d6, 0x9670b12b7f410000, 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, 0xab0e93b6efee0053, 0x8eea0d047a457a00, 0xd5d238a4abe98068, 0x72a4904598d6d880, 0x85a36366eb71f041, 0x47a6da2b7f864750, 0xa70c3c40a64e6c51, 0x999090b65f67d924, 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, 0x82818f1281ed449f, 0xbff8f10e7a8921a4, 0xa321f2d7226895c7, 0xaff72d52192b6a0d, 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, 0xfee50b7025c36a08, 0x2f236d04753d5b4, 0x9f4f2726179a2245, 0x1d762422c946590, 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, 0xf316271c7fc3908a, 0x8bef464e3945ef7a, 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, 0xe7d34c64a9c85d44, 0x60dbbca87196b616, 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, 0xb51d13aea4a488dd, 0x6babab6398bdbe41, 0xe264589a4dcdab14, 0xc696963c7eed2dd1, 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, 0xdd15fe86affad912, 0x49ef0eb713f39ebe, 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, 0xacb92ed9397bf996, 0x49c2c37f07965404, 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, 0xd2d80db02aabd62b, 0xf50a3fa490c30190, 0x83c7088e1aab65db, 0x792667c6da79e0fa, 0xa4b8cab1a1563f52, 0x577001b891185938, 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, 0x80b05e5ac60b6178, 0x544f8158315b05b4, 0xa0dc75f1778e39d6, 0x696361ae3db1c721, 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, 0xfb5878494ace3a5f, 0x4ab48a04065c723, 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, 0xf5746577930d6500, 0xca8f44ec7ee36479, 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, 0x95d04aee3b80ece5, 0xbba1f1d158724a12, 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, 0xea1575143cf97226, 0xf52d09d71a3293bd, 0x924d692ca61be758, 0x593c2626705f9c56, 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, 0xe498f455c38b997a, 0xb6dfb9c0f956447, 0x8edf98b59a373fec, 0x4724bd4189bd5eac, 0xb2977ee300c50fe7, 0x58edec91ec2cb657, 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, 0x8b865b215899f46c, 0xbd79e0d20082ee74, 0xae67f1e9aec07187, 0xecd8590680a3aa11, 0xda01ee641a708de9, 0xe80e6f4820cc9495, 0x884134fe908658b2, 0x3109058d147fdcdd, 0xaa51823e34a7eede, 0xbd4b46f0599fd415, 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, 0x850fadc09923329e, 0x3e2cf6bc604ddb0, 0xa6539930bf6bff45, 0x84db8346b786151c, 0xcfe87f7cef46ff16, 0xe612641865679a63, 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, 0xa26da3999aef7749, 0xe3be5e330f38f09d, 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, 0xc646d63501a1511d, 0xb281e1fd541501b8, 0xf7d88bc24209a565, 0x1f225a7ca91a4226, 0x9ae757596946075f, 0x3375788de9b06958, 0xc1a12d2fc3978937, 0x52d6b1641c83ae, 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, 0x9745eb4d50ce6332, 0xf840b7ba963646e0, 0xbd176620a501fbff, 0xb650e5a93bc3d898, 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, 0x93ba47c980e98cdf, 0xc66f336c36b10137, 0xb8a8d9bbe123f017, 0xb80b0047445d4184, 0xe6d3102ad96cec1d, 0xa60dc059157491e5, 0x9043ea1ac7e41392, 0x87c89837ad68db2f, 0xb454e4a179dd1877, 0x29babe4598c311fb, 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, 0xdc21a1171d42645d, 0x76707543f4fa1f73, 0x899504ae72497eba, 0x6a06494a791c53a8, 0xabfa45da0edbde69, 0x487db9d17636892, 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, 0xa7f26836f282b732, 0x8e6cac7768d7141e, 0xd1ef0244af2364ff, 0x3207d795430cd926, 0x8335616aed761f1f, 0x7f44e6bd49e807b8, 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, 0xcd036837130890a1, 0x36dba887c37a8c0f, 0x802221226be55a64, 0xc2494954da2c9789, 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, 0xc83553c5c8965d3d, 0x6f92829494e5acc7, 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, 0x9c69a97284b578d7, 0xff2a760414536efb, 0xc38413cf25e2d70d, 0xfef5138519684aba, 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, 0x98bf2f79d5993802, 0xef2f773ffbd97a61, 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, 0x952ab45cfa97a0b2, 0xdd945a747bf26183, 0xba756174393d88df, 0x94f971119aeef9e4, 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, 0x91abb422ccb812ee, 0xac62e055c10ab33a, 0xb616a12b7fe617aa, 0x577b986b314d6009, 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, 0x8e41ade9fbebc27d, 0x14588f13be847307, 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, 0x8aec23d680043bee, 0x25de7bb9480d5854, 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, 0xd910f7ff28069da4, 0x1b2ba1518094da04, 0x87aa9aff79042286, 0x90fb44d2f05d0842, 0xa99541bf57452b28, 0x353a1607ac744a53, 0xd3fa922f2d1675f2, 0x42889b8997915ce8, 0x847c9b5d7c2e09b7, 0x69956135febada11, 0xa59bc234db398c25, 0x43fab9837e699095, 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, 0x8161afb94b44f57d, 0x1d1be0eebac278f5, 0xa1ba1ba79e1632dc, 0x6462d92a69731732, 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, 0xfcb2cb35e702af78, 0x5cda735244c3d43e, 0x9defbf01b061adab, 0x3a0888136afa64a7, 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, 0xf6c69a72a3989f5b, 0x8aad549e57273d45, 0x9a3c2087a63f6399, 0x36ac54e2f678864b, 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, 0x969eb7c47859e743, 0x9f644ae5a4b1b325, 0xbc4665b596706114, 0x873d5d9f0dde1fee, 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, 0x8fa475791a569d10, 0xf96e017d694487bc, 0xb38d92d760ec4455, 0x37c981dcc395a9ac, 0xe070f78d3927556a, 0x85bbe253f47b1417, 0x8c469ab843b89562, 0x93956d7478ccec8e, 0xaf58416654a6babb, 0x387ac8d1970027b2, 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, 0x88fcf317f22241e2, 0x441fece3bdf81f03, 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, 0x85c7056562757456, 0xf6872d5667844e49, 0xa738c6bebb12d16c, 0xb428f8ac016561db, 0xd106f86e69d785c7, 0xe13336d701beba52, 0x82a45b450226b39c, 0xecc0024661173473, 0xa34d721642b06084, 0x27f002d7f95d0190, 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, 0xff290242c83396ce, 0x7e67047175a15271, 0x9f79a169bd203e41, 0xf0062c6e984d386, 0xc75809c42c684dd1, 0x52c07b78a3e60868, 0xf92e0c3537826145, 0xa7709a56ccdf8a82, 0x9bbcc7a142b17ccb, 0x88a66076400bb691, 0xc2abf989935ddbfe, 0x6acff893d00ea435, 0xf356f7ebf83552fe, 0x583f6b8c4124d43, 0x98165af37b2153de, 0xc3727a337a8b704a, 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, 0xeda2ee1c7064130c, 0x1162def06f79df73, 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, 0x8da471a9de737e24, 0x5ceaecfed289e5d2, 0xb10d8e1456105dad, 0x7425a83e872c5f47, 0xdd50f1996b947518, 0xd12f124e28f77719, 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, 0x8714a775e3e95c78, 0x65acfaec34810a71, 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, 0xd31045a8341ca07c, 0x1ede48111209a050, 0x83ea2b892091e44d, 0x934aed0aab460432, 0xa4e4b66b68b65d60, 0xf81da84d5617853f, 0xce1de40642e3f4b9, 0x36251260ab9d668e, 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, 0xa1075a24e4421730, 0xb24cf65b8612f81f, 0xc94930ae1d529cfc, 0xdee033f26797b627, 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, 0x9d412e0806e88aa5, 0x8e1f289560ee864e, 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, 0xea53df5fd18d5513, 0x84c86189216dc5ed, 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, 0xe4d5e82392a40515, 0xfabaf3feaa5334a, 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, 0xb2c71d5bca9023f8, 0x743e20e9ef511012, 0xdf78e4b2bd342cf6, 0x914da9246b255416, 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, 0xae9672aba3d0c320, 0xa184ac2473b529b1, 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, 0x8865899617fb1871, 0x7e2fa67c7a658892, 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, 0xd51ea6fa85785631, 0x552a74227f3ea565, 0x8533285c936b35de, 0xd53a88958f87275f, 0xa67ff273b8460356, 0x8a892abaf368f137, 0xd01fef10a657842c, 0x2d2b7569b0432d85, 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, 0xcb3f2f7642717713, 0x241c70a936219a73, 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, 0x9ec95d1463e8a506, 0xf4363804324a40aa, 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, 0x976e41088617ca01, 0xd5be0503e085d813, 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, 0x93e1ab8252f33b45, 0xcabb90e5c942b503, 0xb8da1662e7b00a17, 0x3d6a751f3b936243, 0xe7109bfba19c0c9d, 0xcc512670a783ad4, 0x906a617d450187e2, 0x27fb2b80668b24c5, 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, 0xe1a63853bbd26451, 0x5e7873f8a0396973, 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, 0xac2820d9623bf429, 0x546345fa9fbdcd44, 0xd732290fbacaf133, 0xa97c177947ad4095, 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, 0xa81f301449ee8c70, 0x5c68f256bfff5a74, 0xd226fc195c6a2f8c, 0x73832eec6fff3111, 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, 0xa0555e361951c366, 0xd7e105bcc332621f, 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, 0xfa856334878fc150, 0xb14f98f6f0feb951, 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, 0xc3b8358109e84f07, 0xa862f80ec4700c8, 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, 0xeeea5d5004981478, 0x1858ccfce06cac74, 0x95527a5202df0ccb, 0xf37801e0c43ebc8, 0xbaa718e68396cffd, 0xd30560258f54e6ba, 0xe950df20247c83fd, 0x47c6b82ef32a2069, 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, 0xb6472e511c81471d, 0xe0133fe4adf8e952, 0xe3d8f9e563a198e5, 0x58180fddd97723a6, 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648, }; }; #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE template constexpr uint64_t powers_template::power_of_five_128[number_of_entries]; #endif using powers = powers_template<>; } // namespace fast_float #endif #ifndef FASTFLOAT_DECIMAL_TO_BINARY_H #define FASTFLOAT_DECIMAL_TO_BINARY_H //included above: //#include #include #include //included above: //#include #include //included above: //#include namespace fast_float { // This will compute or rather approximate w * 5**q and return a pair of 64-bit // words approximating the result, with the "high" part corresponding to the // most significant bits and the low part corresponding to the least significant // bits. // template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 compute_product_approximation(int64_t q, uint64_t w) { int const index = 2 * int(q - powers::smallest_power_of_five); // For small values of q, e.g., q in [0,27], the answer is always exact // because The line value128 firstproduct = full_multiplication(w, // power_of_five_128[index]); gives the exact answer. value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); constexpr uint64_t precision_mask = (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) : uint64_t(0xFFFFFFFFFFFFFFFF); if ((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) // regarding the second product, we only need secondproduct.high, but our // expectation is that the compiler will optimize this extra work away if // needed. value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); firstproduct.low += secondproduct.high; if (secondproduct.high > firstproduct.low) { firstproduct.high++; } } return firstproduct; } namespace detail { /** * For q in (0,350), we have that * f = (((152170 + 65536) * q ) >> 16); * is equal to * floor(p) + q * where * p = log(5**q)/log(2) = q * log(5)/log(2) * * For negative values of q in (-400,0), we have that * f = (((152170 + 65536) * q ) >> 16); * is equal to * -ceil(p) + q * where * p = log(5**-q)/log(2) = -q * log(5)/log(2) */ constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { return (((152170 + 65536) * q) >> 16) + 63; } } // namespace detail // create an adjusted mantissa, biased by the invalid power2 // for significant digits already multiplied by 10 ** q. template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { int hilz = int(w >> 63) ^ 1; adjusted_mantissa answer; answer.mantissa = w << hilz; int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); return answer; } // w * 10 ** q, without rounding the representation up. // the power2 in the exponent will be adjusted by invalid_am_bias. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { int lz = leading_zeroes(w); w <<= lz; value128 product = compute_product_approximation(q, w); return compute_error_scaled(q, product.high, lz); } // Computers w * 10 ** q. // The returned value should be a valid number that simply needs to be // packed. However, in some very rare cases, the computation will fail. In such // cases, we return an adjusted_mantissa with a negative power of 2: the caller // should recompute in such cases. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { adjusted_mantissa answer; if ((w == 0) || (q < binary::smallest_power_of_ten())) { answer.power2 = 0; answer.mantissa = 0; // result should be zero return answer; } if (q > binary::largest_power_of_ten()) { // we want to get infinity: answer.power2 = binary::infinite_power(); answer.mantissa = 0; return answer; } // At this point in time q is in [powers::smallest_power_of_five, // powers::largest_power_of_five]. // We want the most significant bit of i to be 1. Shift if needed. int lz = leading_zeroes(w); w <<= lz; // The required precision is binary::mantissa_explicit_bits() + 3 because // 1. We need the implicit bit // 2. We need an extra bit for rounding purposes // 3. We might lose a bit due to the "upperbit" routine (result too small, // requiring a shift) value128 product = compute_product_approximation(q, w); // The computed 'product' is always sufficient. // Mathematical proof: // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to // appear) See script/mushtak_lemire.py // The "compute_product_approximation" function can be slightly slower than a // branchless approach: value128 product = compute_product(q, w); but in // practice, we can win big with the compute_product_approximation if its // additional branch is easily predicted. Which is best is data specific. int upperbit = int(product.high >> 63); int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; answer.mantissa = product.high >> shift; answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); if (answer.power2 <= 0) { // we have a subnormal? // Here have that answer.power2 <= 0 so -answer.power2 >= 0 if (-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you // have a zero for sure. answer.power2 = 0; answer.mantissa = 0; // result should be zero return answer; } // next line is safe because -answer.power2 + 1 < 64 answer.mantissa >>= -answer.power2 + 1; // Thankfully, we can't have both "round-to-even" and subnormals because // "round-to-even" only occurs for powers close to 0 in the 32-bit and // and 64-bit case (with no more than 19 digits). answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa >>= 1; // There is a weird scenario where we don't have a subnormal but just. // Suppose we start with 2.2250738585072013e-308, we end up // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer // subnormal, but we can only know this after rounding. // So we only declare a subnormal if we are smaller than the threshold. answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; return answer; } // usually, we round *up*, but if we fall right in between and and we have an // even basis, we need to round down // We are only concerned with the cases where 5**q fits in single 64-bit word. if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && ((answer.mantissa & 3) == 1)) { // we may fall between two floats! // To be in-between two floats we need that in doing // answer.mantissa = product.high >> (upperbit + 64 - // binary::mantissa_explicit_bits() - 3); // ... we dropped out only zeroes. But if this happened, then we can go // back!!! if ((answer.mantissa << shift) == product.high) { answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up } } answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa >>= 1; if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); answer.power2++; // undo previous addition } answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); if (answer.power2 >= binary::infinite_power()) { // infinity answer.power2 = binary::infinite_power(); answer.mantissa = 0; } return answer; } } // namespace fast_float #endif #ifndef FASTFLOAT_BIGINT_H #define FASTFLOAT_BIGINT_H #include //included above: //#include //included above: //#include //included above: //#include namespace fast_float { // the limb width: we want efficient multiplication of double the bits in // limb, or for 64-bit limbs, at least 64-bit multiplication where we can // extract the high and low parts efficiently. this is every 64-bit // architecture except for sparc, which emulates 128-bit multiplication. // we might have platforms where `CHAR_BIT` is not 8, so let's avoid // doing `8 * sizeof(limb)`. #if defined(FASTFLOAT_64BIT) && !defined(__sparc) #define FASTFLOAT_64BIT_LIMB 1 typedef uint64_t limb; constexpr size_t limb_bits = 64; #else #define FASTFLOAT_32BIT_LIMB typedef uint32_t limb; constexpr size_t limb_bits = 32; #endif typedef span limb_span; // number of bits in a bigint. this needs to be at least the number // of bits required to store the largest bigint, which is // `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or // ~3600 bits, so we round to 4000. constexpr size_t bigint_bits = 4000; constexpr size_t bigint_limbs = bigint_bits / limb_bits; // vector-like type that is allocated on the stack. the entire // buffer is pre-allocated, and only the length changes. template struct stackvec { limb data[size]; // we never need more than 150 limbs uint16_t length{0}; stackvec() = default; stackvec(stackvec const &) = delete; stackvec &operator=(stackvec const &) = delete; stackvec(stackvec &&) = delete; stackvec &operator=(stackvec &&other) = delete; // create stack vector from existing limb span. FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { FASTFLOAT_ASSERT(try_extend(s)); } FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return data[index]; } FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return data[index]; } // index from the end of the container FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); size_t rindex = length - index - 1; return data[rindex]; } // set the length, without bounds checking. FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { length = uint16_t(len); } constexpr size_t len() const noexcept { return length; } constexpr bool is_empty() const noexcept { return length == 0; } constexpr size_t capacity() const noexcept { return size; } // append item to vector, without bounds checking FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { data[length] = value; length++; } // append item to vector, returning if item was added FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { if (len() < capacity()) { push_unchecked(value); return true; } else { return false; } } // add items to the vector, from a span, without bounds checking FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { limb *ptr = data + length; std::copy_n(s.ptr, s.len(), ptr); set_len(len() + s.len()); } // try to add items to the vector, returning if items were added FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { if (len() + s.len() <= capacity()) { extend_unchecked(s); return true; } else { return false; } } // resize the vector, without bounds checking // if the new size is longer than the vector, assign value to each // appended item. FASTFLOAT_CONSTEXPR20 void resize_unchecked(size_t new_len, limb value) noexcept { if (new_len > len()) { size_t count = new_len - len(); limb *first = data + len(); limb *last = first + count; ::std::fill(first, last, value); set_len(new_len); } else { set_len(new_len); } } // try to resize the vector, returning if the vector was resized. FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { if (new_len > capacity()) { return false; } else { resize_unchecked(new_len, value); return true; } } // check if any limbs are non-zero after the given index. // this needs to be done in reverse order, since the index // is relative to the most significant limbs. FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { while (index < len()) { if (rindex(index) != 0) { return true; } index++; } return false; } // normalize the big integer, so most-significant zero limbs are removed. FASTFLOAT_CONSTEXPR14 void normalize() noexcept { while (len() > 0 && rindex(0) == 0) { length--; } } }; fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t empty_hi64(bool &truncated) noexcept { truncated = false; return 0; } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint64_hi64(uint64_t r0, bool &truncated) noexcept { truncated = false; int shl = leading_zeroes(r0); return r0 << shl; } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept { int shl = leading_zeroes(r0); if (shl == 0) { truncated = r1 != 0; return r0; } else { int shr = 64 - shl; truncated = (r1 << shl) != 0; return (r0 << shl) | (r1 >> shr); } } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint32_hi64(uint32_t r0, bool &truncated) noexcept { return uint64_hi64(r0, truncated); } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept { uint64_t x0 = r0; uint64_t x1 = r1; return uint64_hi64((x0 << 32) | x1, truncated); } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept { uint64_t x0 = r0; uint64_t x1 = r1; uint64_t x2 = r2; return uint64_hi64(x0, (x1 << 32) | x2, truncated); } // add two small integers, checking for overflow. // we want an efficient operation. for msvc, where // we don't have built-in intrinsics, this is still // pretty fast. fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb scalar_add(limb x, limb y, bool &overflow) noexcept { limb z; // gcc and clang #if defined(__has_builtin) #if __has_builtin(__builtin_add_overflow) if (!cpp20_and_in_constexpr()) { overflow = __builtin_add_overflow(x, y, &z); return z; } #endif #endif // generic, this still optimizes correctly on MSVC. z = x + y; overflow = z < x; return z; } // multiply two small integers, getting both the high and low bits. fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb scalar_mul(limb x, limb y, limb &carry) noexcept { #ifdef FASTFLOAT_64BIT_LIMB #if defined(__SIZEOF_INT128__) // GCC and clang both define it as an extension. __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); carry = limb(z >> limb_bits); return limb(z); #else // fallback, no native 128-bit integer multiplication with carry. // on msvc, this optimizes identically, somehow. value128 z = full_multiplication(x, y); bool overflow; z.low = scalar_add(z.low, carry, overflow); z.high += uint64_t(overflow); // cannot overflow carry = z.high; return z.low; #endif #else uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); carry = limb(z >> limb_bits); return limb(z); #endif } // add scalar value to bigint starting from offset. // used in grade school multiplication template inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec &vec, limb y, size_t start) noexcept { size_t index = start; limb carry = y; bool overflow; while (carry != 0 && index < vec.len()) { vec[index] = scalar_add(vec[index], carry, overflow); carry = limb(overflow); index += 1; } if (carry != 0) { FASTFLOAT_TRY(vec.try_push(carry)); } return true; } // add scalar value to bigint. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool small_add(stackvec &vec, limb y) noexcept { return small_add_from(vec, y, 0); } // multiply bigint by scalar value. template inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec &vec, limb y) noexcept { limb carry = 0; for (size_t index = 0; index < vec.len(); index++) { vec[index] = scalar_mul(vec[index], y, carry); } if (carry != 0) { FASTFLOAT_TRY(vec.try_push(carry)); } return true; } // add bigint to bigint starting from index. // used in grade school multiplication template FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y, size_t start) noexcept { // the effective x buffer is from `xstart..x.len()`, so exit early // if we can't get that current range. if (x.len() < start || y.len() > x.len() - start) { FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); } bool carry = false; for (size_t index = 0; index < y.len(); index++) { limb xi = x[index + start]; limb yi = y[index]; bool c1 = false; bool c2 = false; xi = scalar_add(xi, yi, c1); if (carry) { xi = scalar_add(xi, 1, c2); } x[index + start] = xi; carry = c1 | c2; } // handle overflow if (carry) { FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); } return true; } // add bigint to bigint. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y) noexcept { return large_add_from(x, y, 0); } // grade-school multiplication algorithm template FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { limb_span xs = limb_span(x.data, x.len()); stackvec z(xs); limb_span zs = limb_span(z.data, z.len()); if (y.len() != 0) { limb y0 = y[0]; FASTFLOAT_TRY(small_mul(x, y0)); for (size_t index = 1; index < y.len(); index++) { limb yi = y[index]; stackvec zi; if (yi != 0) { // re-use the same buffer throughout zi.set_len(0); FASTFLOAT_TRY(zi.try_extend(zs)); FASTFLOAT_TRY(small_mul(zi, yi)); limb_span zis = limb_span(zi.data, zi.len()); FASTFLOAT_TRY(large_add_from(x, zis, index)); } } } x.normalize(); return true; } // grade-school multiplication algorithm template FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec &x, limb_span y) noexcept { if (y.len() == 1) { FASTFLOAT_TRY(small_mul(x, y[0])); } else { FASTFLOAT_TRY(long_mul(x, y)); } return true; } template struct pow5_tables { static constexpr uint32_t large_step = 135; static constexpr uint64_t small_power_of_5[] = { 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, 2384185791015625UL, 11920928955078125UL, 59604644775390625UL, 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, }; #ifdef FASTFLOAT_64BIT_LIMB constexpr static limb large_power_of_5[] = { 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, 10482974169319127550UL, 198276706040285095UL}; #else constexpr static limb large_power_of_5[] = { 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; #endif }; #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE template constexpr uint32_t pow5_tables::large_step; template constexpr uint64_t pow5_tables::small_power_of_5[]; template constexpr limb pow5_tables::large_power_of_5[]; #endif // big integer type. implements a small subset of big integer // arithmetic, using simple algorithms since asymptotically // faster algorithms are slower for a small number of limbs. // all operations assume the big-integer is normalized. struct bigint : pow5_tables<> { // storage of the limbs, in little-endian order. stackvec vec; FASTFLOAT_CONSTEXPR20 bigint() : vec() {} bigint(bigint const &) = delete; bigint &operator=(bigint const &) = delete; bigint(bigint &&) = delete; bigint &operator=(bigint &&other) = delete; FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() { #ifdef FASTFLOAT_64BIT_LIMB vec.push_unchecked(value); #else vec.push_unchecked(uint32_t(value)); vec.push_unchecked(uint32_t(value >> 32)); #endif vec.normalize(); } // get the high 64 bits from the vector, and if bits were truncated. // this is to get the significant digits for the float. FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept { #ifdef FASTFLOAT_64BIT_LIMB if (vec.len() == 0) { return empty_hi64(truncated); } else if (vec.len() == 1) { return uint64_hi64(vec.rindex(0), truncated); } else { uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); truncated |= vec.nonzero(2); return result; } #else if (vec.len() == 0) { return empty_hi64(truncated); } else if (vec.len() == 1) { return uint32_hi64(vec.rindex(0), truncated); } else if (vec.len() == 2) { return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); } else { uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); truncated |= vec.nonzero(3); return result; } #endif } // compare two big integers, returning the large value. // assumes both are normalized. if the return value is // negative, other is larger, if the return value is // positive, this is larger, otherwise they are equal. // the limbs are stored in little-endian order, so we // must compare the limbs in ever order. FASTFLOAT_CONSTEXPR20 int compare(bigint const &other) const noexcept { if (vec.len() > other.vec.len()) { return 1; } else if (vec.len() < other.vec.len()) { return -1; } else { for (size_t index = vec.len(); index > 0; index--) { limb xi = vec[index - 1]; limb yi = other.vec[index - 1]; if (xi > yi) { return 1; } else if (xi < yi) { return -1; } } return 0; } } // shift left each limb n bits, carrying over to the new limb // returns true if we were able to shift all the digits. FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { // Internally, for each item, we shift left by n, and add the previous // right shifted limb-bits. // For example, we transform (for u8) shifted left 2, to: // b10100100 b01000010 // b10 b10010001 b00001000 FASTFLOAT_DEBUG_ASSERT(n != 0); FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); size_t shl = n; size_t shr = limb_bits - shl; limb prev = 0; for (size_t index = 0; index < vec.len(); index++) { limb xi = vec[index]; vec[index] = (xi << shl) | (prev >> shr); prev = xi; } limb carry = prev >> shr; if (carry != 0) { return vec.try_push(carry); } return true; } // move the limbs left by `n` limbs. FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { FASTFLOAT_DEBUG_ASSERT(n != 0); if (n + vec.len() > vec.capacity()) { return false; } else if (!vec.is_empty()) { // move limbs limb *dst = vec.data + n; limb const *src = vec.data; std::copy_backward(src, src + vec.len(), dst + vec.len()); // fill in empty limbs limb *first = vec.data; limb *last = first + n; ::std::fill(first, last, 0); vec.set_len(n + vec.len()); return true; } else { return true; } } // move the limbs left by `n` bits. FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { size_t rem = n % limb_bits; size_t div = n / limb_bits; if (rem != 0) { FASTFLOAT_TRY(shl_bits(rem)); } if (div != 0) { FASTFLOAT_TRY(shl_limbs(div)); } return true; } // get the number of leading zeros in the bigint. FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { if (vec.is_empty()) { return 0; } else { #ifdef FASTFLOAT_64BIT_LIMB return leading_zeroes(vec.rindex(0)); #else // no use defining a specialized leading_zeroes for a 32-bit type. uint64_t r0 = vec.rindex(0); return leading_zeroes(r0 << 32); #endif } } // get the number of bits in the bigint. FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { int lz = ctlz(); return int(limb_bits * vec.len()) - lz; } FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); } FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); } // multiply as if by 2 raised to a power. FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); } // multiply as if by 5 raised to a power. FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { // multiply by a power of 5 size_t large_length = sizeof(large_power_of_5) / sizeof(limb); limb_span large = limb_span(large_power_of_5, large_length); while (exp >= large_step) { FASTFLOAT_TRY(large_mul(vec, large)); exp -= large_step; } #ifdef FASTFLOAT_64BIT_LIMB uint32_t small_step = 27; limb max_native = 7450580596923828125UL; #else uint32_t small_step = 13; limb max_native = 1220703125U; #endif while (exp >= small_step) { FASTFLOAT_TRY(small_mul(vec, max_native)); exp -= small_step; } if (exp != 0) { // Work around clang bug https://godbolt.org/z/zedh7rrhc // This is similar to https://github.com/llvm/llvm-project/issues/47746, // except the workaround described there don't work here FASTFLOAT_TRY(small_mul( vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))); } return true; } // multiply as if by 10 raised to a power. FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { FASTFLOAT_TRY(pow5(exp)); return pow2(exp); } }; } // namespace fast_float #endif #ifndef FASTFLOAT_DIGIT_COMPARISON_H #define FASTFLOAT_DIGIT_COMPARISON_H //included above: //#include //included above: //#include //included above: //#include //included above: //#include namespace fast_float { // 1e0 to 1e19 constexpr static uint64_t powers_of_ten_uint64[] = {1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, 1000000000000000000UL, 10000000000000000000UL}; // calculate the exponent, in scientific notation, of the number. // this algorithm is not even close to optimized, but it has no practical // effect on performance: in order to have a faster algorithm, we'd need // to slow down performance for faster algorithms, and this is still fast. template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t scientific_exponent(parsed_number_string_t &num) noexcept { uint64_t mantissa = num.mantissa; int32_t exponent = int32_t(num.exponent); while (mantissa >= 10000) { mantissa /= 10000; exponent += 4; } while (mantissa >= 100) { mantissa /= 100; exponent += 2; } while (mantissa >= 10) { mantissa /= 10; exponent += 1; } return exponent; } // this converts a native floating-point number to an extended-precision float. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa to_extended(T value) noexcept { using equiv_uint = equiv_uint_t; constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); adjusted_mantissa am; int32_t bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); equiv_uint bits; #if FASTFLOAT_HAS_BIT_CAST bits = std::bit_cast(value); #else ::memcpy(&bits, &value, sizeof(T)); #endif if ((bits & exponent_mask) == 0) { // denormal am.power2 = 1 - bias; am.mantissa = bits & mantissa_mask; } else { // normal am.power2 = int32_t((bits & exponent_mask) >> binary_format::mantissa_explicit_bits()); am.power2 -= bias; am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; } return am; } // get the extended precision value of the halfway point between b and b+u. // we are given a native float that represents b, so we need to adjust it // halfway between b and b+u. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa to_extended_halfway(T value) noexcept { adjusted_mantissa am = to_extended(value); am.mantissa <<= 1; am.mantissa += 1; am.power2 -= 1; return am; } // round an extended-precision float to the nearest machine float. template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, callback cb) noexcept { int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; if (-am.power2 >= mantissa_shift) { // have a denormal float int32_t shift = -am.power2 + 1; cb(am, std::min(shift, 64)); // check for round-up: if rounding-nearest carried us to the hidden bit. am.power2 = (am.mantissa < (uint64_t(1) << binary_format::mantissa_explicit_bits())) ? 0 : 1; return; } // have a normal float, use the default shift. cb(am, mantissa_shift); // check for carry if (am.mantissa >= (uint64_t(2) << binary_format::mantissa_explicit_bits())) { am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); am.power2++; } // check for infinite: we could have carried to an infinite power am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); if (am.power2 >= binary_format::infinite_power()) { am.power2 = binary_format::infinite_power(); am.mantissa = 0; } } template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, callback cb) noexcept { uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); uint64_t truncated_bits = am.mantissa & mask; bool is_above = truncated_bits > halfway; bool is_halfway = truncated_bits == halfway; // shift digits into position if (shift == 64) { am.mantissa = 0; } else { am.mantissa >>= shift; } am.power2 += shift; bool is_odd = (am.mantissa & 1) == 1; am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round_down(adjusted_mantissa &am, int32_t shift) noexcept { if (shift == 64) { am.mantissa = 0; } else { am.mantissa >>= shift; } am.power2 += shift; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void skip_zeros(UC const *&first, UC const *last) noexcept { uint64_t val; while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len()) { ::memcpy(&val, first, sizeof(uint64_t)); if (val != int_cmp_zeros()) { break; } first += int_cmp_len(); } while (first != last) { if (*first != UC('0')) { break; } first++; } } // determine if any non-zero digits were truncated. // all characters must be valid digits. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool is_truncated(UC const *first, UC const *last) noexcept { // do 8-bit optimizations, can just compare to 8 literal 0s. uint64_t val; while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len()) { ::memcpy(&val, first, sizeof(uint64_t)); if (val != int_cmp_zeros()) { return true; } first += int_cmp_len(); } while (first != last) { if (*first != UC('0')) { return true; } ++first; } return false; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool is_truncated(span s) noexcept { return is_truncated(s.ptr, s.ptr + s.len()); } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void parse_eight_digits(UC const *&p, limb &value, size_t &counter, size_t &count) noexcept { value = value * 100000000 + parse_eight_digits_unrolled(p); p += 8; counter += 8; count += 8; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void parse_one_digit(UC const *&p, limb &value, size_t &counter, size_t &count) noexcept { value = value * 10 + limb(*p - UC('0')); p++; counter++; count++; } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void add_native(bigint &big, limb power, limb value) noexcept { big.mul(power); big.add(value); } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void round_up_bigint(bigint &big, size_t &count) noexcept { // need to round-up the digits, but need to avoid rounding // ....9999 to ...10000, which could cause a false halfway point. add_native(big, 10, 1); count++; } // parse the significant digits into a big integer template inline FASTFLOAT_CONSTEXPR20 void parse_mantissa(bigint &result, parsed_number_string_t &num, size_t max_digits, size_t &digits) noexcept { // try to minimize the number of big integer and scalar multiplication. // therefore, try to parse 8 digits at a time, and multiply by the largest // scalar value (9 or 19 digits) for each step. size_t counter = 0; digits = 0; limb value = 0; #ifdef FASTFLOAT_64BIT_LIMB size_t step = 19; #else size_t step = 9; #endif // process all integer digits. UC const *p = num.integer.ptr; UC const *pend = p + num.integer.len(); skip_zeros(p, pend); // process all digits, in increments of step per loop while (p != pend) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { parse_eight_digits(p, value, counter, digits); } while (counter < step && p != pend && digits < max_digits) { parse_one_digit(p, value, counter, digits); } if (digits == max_digits) { // add the temporary value, then check if we've truncated any digits add_native(result, limb(powers_of_ten_uint64[counter]), value); bool truncated = is_truncated(p, pend); if (num.fraction.ptr != nullptr) { truncated |= is_truncated(num.fraction); } if (truncated) { round_up_bigint(result, digits); } return; } else { add_native(result, limb(powers_of_ten_uint64[counter]), value); counter = 0; value = 0; } } // add our fraction digits, if they're available. if (num.fraction.ptr != nullptr) { p = num.fraction.ptr; pend = p + num.fraction.len(); if (digits == 0) { skip_zeros(p, pend); } // process all digits, in increments of step per loop while (p != pend) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { parse_eight_digits(p, value, counter, digits); } while (counter < step && p != pend && digits < max_digits) { parse_one_digit(p, value, counter, digits); } if (digits == max_digits) { // add the temporary value, then check if we've truncated any digits add_native(result, limb(powers_of_ten_uint64[counter]), value); bool truncated = is_truncated(p, pend); if (truncated) { round_up_bigint(result, digits); } return; } else { add_native(result, limb(powers_of_ten_uint64[counter]), value); counter = 0; value = 0; } } } if (counter != 0) { add_native(result, limb(powers_of_ten_uint64[counter]), value); } } template inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); adjusted_mantissa answer; bool truncated; answer.mantissa = bigmant.hi64(truncated); int bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); answer.power2 = bigmant.bit_length() - 64 + bias; round(answer, [truncated](adjusted_mantissa &a, int32_t shift) { round_nearest_tie_even( a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { return is_above || (is_halfway && truncated) || (is_odd && is_halfway); }); }); return answer; } // the scaling here is quite simple: we have, for the real digits `m * 10^e`, // and for the theoretical digits `n * 2^f`. Since `e` is always negative, // to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. // we then need to scale by `2^(f- e)`, and then the two significant digits // are of the same magnitude. template inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { bigint &real_digits = bigmant; int32_t real_exp = exponent; // get the value of `b`, rounded down, and get a bigint representation of b+h adjusted_mantissa am_b = am; // gcc7 buf: use a lambda to remove the noexcept qualifier bug with // -Wnoexcept-type. round(am_b, [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); T b; to_float(false, am_b, b); adjusted_mantissa theor = to_extended_halfway(b); bigint theor_digits(theor.mantissa); int32_t theor_exp = theor.power2; // scale real digits and theor digits to be same power. int32_t pow2_exp = theor_exp - real_exp; uint32_t pow5_exp = uint32_t(-real_exp); if (pow5_exp != 0) { FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); } if (pow2_exp > 0) { FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); } else if (pow2_exp < 0) { FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); } // compare digits, and use it to director rounding int ord = real_digits.compare(theor_digits); adjusted_mantissa answer = am; round(answer, [ord](adjusted_mantissa &a, int32_t shift) { round_nearest_tie_even( a, shift, [ord](bool is_odd, bool _, bool __) -> bool { (void)_; // not needed, since we've done our comparison (void)__; // not needed, since we've done our comparison if (ord > 0) { return true; } else if (ord < 0) { return false; } else { return is_odd; } }); }); return answer; } // parse the significant digits as a big integer to unambiguously round the // the significant digits. here, we are trying to determine how to round // an extended float representation close to `b+h`, halfway between `b` // (the float rounded-down) and `b+u`, the next positive float. this // algorithm is always correct, and uses one of two approaches. when // the exponent is positive relative to the significant digits (such as // 1234), we create a big-integer representation, get the high 64-bits, // determine if any lower bits are truncated, and use that to direct // rounding. in case of a negative exponent relative to the significant // digits (such as 1.2345), we create a theoretical representation of // `b` as a big-integer type, scaled to the same binary exponent as // the actual digits. we then compare the big integer representations // of both, and use that to direct rounding. template inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa digit_comp(parsed_number_string_t &num, adjusted_mantissa am) noexcept { // remove the invalid exponent bias am.power2 -= invalid_am_bias; int32_t sci_exp = scientific_exponent(num); size_t max_digits = binary_format::max_digits(); size_t digits = 0; bigint bigmant; parse_mantissa(bigmant, num, max_digits, digits); // can't underflow, since digits is at most max_digits. int32_t exponent = sci_exp + 1 - int32_t(digits); if (exponent >= 0) { return positive_digit_comp(bigmant, exponent); } else { return negative_digit_comp(bigmant, am, exponent); } } } // namespace fast_float #endif #ifndef FASTFLOAT_PARSE_NUMBER_H #define FASTFLOAT_PARSE_NUMBER_H //included above: //#include //included above: //#include //included above: //#include //included above: //#include namespace fast_float { namespace detail { /** * Special case +inf, -inf, nan, infinity, -infinity. * The case comparisons could be made much faster given that we know that the * strings a null-free and fixed. **/ template from_chars_result_t FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last, T &value, chars_format fmt) noexcept { from_chars_result_t answer{}; answer.ptr = first; answer.ec = std::errc(); // be optimistic // assume first < last, so dereference without checks; bool const minusSign = (*first == UC('-')); // C++17 20.19.3.(7.1) explicitly forbids '+' sign here if ((*first == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) && (*first == UC('+')))) { ++first; } if (last - first >= 3) { if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { answer.ptr = (first += 3); value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). if (first != last && *first == UC('(')) { for (UC const *ptr = first + 1; ptr != last; ++ptr) { if (*ptr == UC(')')) { answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) break; } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || (UC('A') <= *ptr && *ptr <= UC('Z')) || (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) break; // forbidden char, not nan(n-char-seq-opt) } } return answer; } if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { answer.ptr = first + 8; } else { answer.ptr = first + 3; } value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); return answer; } } answer.ec = std::errc::invalid_argument; return answer; } /** * Returns true if the floating-pointing rounding mode is to 'nearest'. * It is the default on most system. This function is meant to be inexpensive. * Credit : @mwalcott3 */ fastfloat_really_inline bool rounds_to_nearest() noexcept { // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return false; #endif // See // A fast function to check your floating-point rounding mode // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ // // This function is meant to be equivalent to : // prior: #include // return fegetround() == FE_TONEAREST; // However, it is expected to be much faster than the fegetround() // function call. // // The volatile keyword prevents the compiler from computing the function // at compile-time. // There might be other ways to prevent compile-time optimizations (e.g., // asm). The value does not need to be std::numeric_limits::min(), any // small value so that 1 + x should round to 1 would do (after accounting for // excess precision, as in 387 instructions). static float volatile fmin = std::numeric_limits::min(); float fmini = fmin; // we copy it so that it gets loaded at most once. // // Explanation: // Only when fegetround() == FE_TONEAREST do we have that // fmin + 1.0f == 1.0f - fmin. // // FE_UPWARD: // fmin + 1.0f > 1 // 1.0f - fmin == 1 // // FE_DOWNWARD or FE_TOWARDZERO: // fmin + 1.0f == 1 // 1.0f - fmin < 1 // // Note: This may fail to be accurate if fast-math has been // enabled, as rounding conventions may not apply. #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(push) // todo: is there a VS warning? // see // https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 #elif defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfloat-equal" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif return (fmini + 1.0f == 1.0f - fmini); #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(pop) #elif defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif } } // namespace detail template struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { return from_chars_advanced(first, last, value, options); } }; #ifdef __STDCPP_FLOAT32_T__ template <> struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, std::float32_t &value, parse_options_t options) noexcept { // if std::float32_t is defined, and we are in C++23 mode; macro set for // float32; set value to float due to equivalence between float and // float32_t float val; auto ret = from_chars_advanced(first, last, val, options); value = val; return ret; } }; #endif #ifdef __STDCPP_FLOAT64_T__ template <> struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, std::float64_t &value, parse_options_t options) noexcept { // if std::float64_t is defined, and we are in C++23 mode; macro set for // float64; set value as double due to equivalence between double and // float64_t double val; auto ret = from_chars_advanced(first, last, val, options); value = val; return ret; } }; #endif template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, chars_format fmt /*= chars_format::general*/) noexcept { return from_chars_caller::call(first, last, value, parse_options_t(fmt)); } /** * This function overload takes parsed_number_string_t structure that is created * and populated either by from_chars_advanced function taking chars range and * parsing options or other parsing custom function implemented by user. */ template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { static_assert(is_supported_float_type::value, "only some floating-point types are supported"); static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); from_chars_result_t answer; answer.ec = std::errc(); // be optimistic answer.ptr = pns.lastmatch; // The implementation of the Clinger's fast path is convoluted because // we want round-to-nearest in all cases, irrespective of the rounding mode // selected on the thread. // We proceed optimistically, assuming that detail::rounds_to_nearest() // returns true. if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && !pns.too_many_digits) { // Unfortunately, the conventional Clinger's fast path is only possible // when the system rounds to the nearest float. // // We expect the next branch to almost always be selected. // We could check it first (before the previous branch), but // there might be performance advantages at having the check // be last. if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { // We have that fegetround() == FE_TONEAREST. // Next is Clinger's fast path. if (pns.mantissa <= binary_format::max_mantissa_fast_path()) { value = T(pns.mantissa); if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } else { value = value * binary_format::exact_power_of_ten(pns.exponent); } if (pns.negative) { value = -value; } return answer; } } else { // We do not have that fegetround() == FE_TONEAREST. // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's // proposal if (pns.exponent >= 0 && pns.mantissa <= binary_format::max_mantissa_fast_path(pns.exponent)) { #if defined(__clang__) || defined(FASTFLOAT_32BIT) // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD if (pns.mantissa == 0) { value = pns.negative ? T(-0.) : T(0.); return answer; } #endif value = T(pns.mantissa) * binary_format::exact_power_of_ten(pns.exponent); if (pns.negative) { value = -value; } return answer; } } } adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); if (pns.too_many_digits && am.power2 >= 0) { if (am != compute_float>(pns.exponent, pns.mantissa + 1)) { am = compute_error>(pns.exponent, pns.mantissa); } } // If we called compute_float>(pns.exponent, pns.mantissa) // and we have an invalid power (am.power2 < 0), then we need to go the long // way around again. This is very uncommon. if (am.power2 < 0) { am = digit_comp(pns, am); } to_float(pns.negative, am, value); // Test for over/underflow. if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format::infinite_power()) { answer.ec = std::errc::result_out_of_range; } return answer; } template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_float_advanced(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { static_assert(is_supported_float_type::value, "only some floating-point types are supported"); static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); chars_format const fmt = detail::adjust_for_feature_macros(options.format); from_chars_result_t answer; if (uint64_t(fmt & chars_format::skip_white_space)) { while ((first != last) && fast_float::is_space(*first)) { first++; } } if (first == last) { answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } parsed_number_string_t pns = parse_number_string(first, last, options); if (!pns.valid) { if (uint64_t(fmt & chars_format::no_infnan)) { answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } else { return detail::parse_infnan(first, last, value, fmt); } } // call overload that takes parsed_number_string_t directly. return from_chars_advanced(pns, value); } template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, int base) noexcept { static_assert(is_supported_integer_type::value, "only integer types are supported"); static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); parse_options_t options; options.base = base; return from_chars_advanced(first, last, value, options); } template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_int_advanced(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { static_assert(is_supported_integer_type::value, "only integer types are supported"); static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); chars_format const fmt = detail::adjust_for_feature_macros(options.format); int const base = options.base; from_chars_result_t answer; if (uint64_t(fmt & chars_format::skip_white_space)) { while ((first != last) && fast_float::is_space(*first)) { first++; } } if (first == last || base < 2 || base > 36) { answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } return parse_int_string(first, last, value, options); } template struct from_chars_advanced_caller { static_assert(TypeIx > 0, "unsupported type"); }; template <> struct from_chars_advanced_caller<1> { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { return from_chars_float_advanced(first, last, value, options); } }; template <> struct from_chars_advanced_caller<2> { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { return from_chars_int_advanced(first, last, value, options); } }; template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { return from_chars_advanced_caller< size_t(is_supported_float_type::value) + 2 * size_t(is_supported_integer_type::value)>::call(first, last, value, options); } } // namespace fast_float #endif #ifdef _MSC_VER # pragma warning(pop) #elif defined(__clang__) || defined(__APPLE_CC__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif // _C4_EXT_FAST_FLOAT_HPP_ // (end https://github.com/biojppm/c4core/src/c4/ext/fast_float.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/std/vector_fwd.hpp // https://github.com/biojppm/c4core/src/c4/std/vector_fwd.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_STD_VECTOR_FWD_HPP_ #define _C4_STD_VECTOR_FWD_HPP_ /** @file vector_fwd.hpp */ //included above: //#include // NOLINTBEGIN(cert-dcl58-cpp) // forward declarations for std::vector #if defined(__GLIBCXX__) || defined(__GLIBCPP__) || defined(_MSC_VER) #if defined(_MSC_VER) __pragma(warning(push)) __pragma(warning(disable : 4643)) #endif namespace std { template class allocator; #ifdef _GLIBCXX_DEBUG inline namespace __debug { template class vector; } #else template class vector; #endif } // namespace std #if defined(_MSC_VER) __pragma(warning(pop)) #endif #elif defined(_LIBCPP_ABI_NAMESPACE) namespace std { inline namespace _LIBCPP_ABI_NAMESPACE { template class allocator; template class vector; } // namespace _LIBCPP_ABI_NAMESPACE } // namespace std #else #error "unknown standard library" #endif #ifndef C4CORE_SINGLE_HEADER // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp //#include "c4/substr_fwd.hpp" #if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) #error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" #endif /* C4_SUBSTR_FWD_HPP_ */ #endif namespace c4 { template c4::substr to_substr(std::vector &vec); template c4::csubstr to_csubstr(std::vector const& vec); template bool operator!= (c4::csubstr ss, std::vector const& s); template bool operator== (c4::csubstr ss, std::vector const& s); template bool operator>= (c4::csubstr ss, std::vector const& s); template bool operator> (c4::csubstr ss, std::vector const& s); template bool operator<= (c4::csubstr ss, std::vector const& s); template bool operator< (c4::csubstr ss, std::vector const& s); template bool operator!= (std::vector const& s, c4::csubstr ss); template bool operator== (std::vector const& s, c4::csubstr ss); template bool operator>= (std::vector const& s, c4::csubstr ss); template bool operator> (std::vector const& s, c4::csubstr ss); template bool operator<= (std::vector const& s, c4::csubstr ss); template bool operator< (std::vector const& s, c4::csubstr ss); template size_t to_chars(c4::substr buf, std::vector const& s); template bool from_chars(c4::csubstr buf, std::vector * s); } // namespace c4 // NOLINTEND(cert-dcl58-cpp) #endif // _C4_STD_VECTOR_FWD_HPP_ // (end https://github.com/biojppm/c4core/src/c4/std/vector_fwd.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/std/string_fwd.hpp // https://github.com/biojppm/c4core/src/c4/std/string_fwd.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_STD_STRING_FWD_HPP_ #define _C4_STD_STRING_FWD_HPP_ /** @file string_fwd.hpp */ #ifndef DOXYGEN #ifndef C4CORE_SINGLE_HEADER // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp //#include "c4/substr_fwd.hpp" #if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) #error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" #endif /* C4_SUBSTR_FWD_HPP_ */ #endif //included above: //#include // forward declarations for std::string #if defined(__GLIBCXX__) || defined(__GLIBCPP__) #include // use the fwd header in glibcxx #elif defined(_LIBCPP_VERSION) || defined(__APPLE_CC__) #include // use the fwd header in stdlibc++ #elif defined(_MSC_VER) // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ //! @todo is there a fwd header in msvc? namespace std { C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4643) // Forward declaring 'char_traits' in namespace std is not permitted by the C++ Standard. template struct char_traits; template class allocator; template class basic_string; using string = basic_string, allocator>; C4_SUPPRESS_WARNING_MSVC_POP } /* namespace std */ #else #error "unknown standard library" #endif namespace c4 { c4::substr to_substr(std::string &s) noexcept; c4::csubstr to_csubstr(std::string const& s) noexcept; bool operator== (c4::csubstr ss, std::string const& s); bool operator!= (c4::csubstr ss, std::string const& s); bool operator>= (c4::csubstr ss, std::string const& s); bool operator> (c4::csubstr ss, std::string const& s); bool operator<= (c4::csubstr ss, std::string const& s); bool operator< (c4::csubstr ss, std::string const& s); bool operator== (std::string const& s, c4::csubstr ss); bool operator!= (std::string const& s, c4::csubstr ss); bool operator>= (std::string const& s, c4::csubstr ss); bool operator> (std::string const& s, c4::csubstr ss); bool operator<= (std::string const& s, c4::csubstr ss); bool operator< (std::string const& s, c4::csubstr ss); size_t to_chars(c4::substr buf, std::string const& s); bool from_chars(c4::csubstr buf, std::string * s); } // namespace c4 #endif // DOXYGEN #endif // _C4_STD_STRING_FWD_HPP_ // (end https://github.com/biojppm/c4core/src/c4/std/string_fwd.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/std/std_fwd.hpp // https://github.com/biojppm/c4core/src/c4/std/std_fwd.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_STD_STD_FWD_HPP_ #define _C4_STD_STD_FWD_HPP_ /** @file std_fwd.hpp includes all c4-std interop fwd files */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/std/vector_fwd.hpp //#include "c4/std/vector_fwd.hpp" #if !defined(C4_STD_VECTOR_FWD_HPP_) && !defined(_C4_STD_VECTOR_FWD_HPP_) #error "amalgamate: file c4/std/vector_fwd.hpp must have been included at this point" #endif /* C4_STD_VECTOR_FWD_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/std/string_fwd.hpp //#include "c4/std/string_fwd.hpp" #if !defined(C4_STD_STRING_FWD_HPP_) && !defined(_C4_STD_STRING_FWD_HPP_) #error "amalgamate: file c4/std/string_fwd.hpp must have been included at this point" #endif /* C4_STD_STRING_FWD_HPP_ */ //#include "c4/std/tuple_fwd.hpp" #endif // _C4_STD_STD_FWD_HPP_ // (end https://github.com/biojppm/c4core/src/c4/std/std_fwd.hpp) // (amalgamate) this include is needed to work around // conditional includes in charconv.hpp #if (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)) || (__cplusplus >= 201703L) #include #endif //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/charconv.hpp // https://github.com/biojppm/c4core/src/c4/charconv.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_CHARCONV_HPP_ #define _C4_CHARCONV_HPP_ /** @file charconv.hpp Lightweight generic type-safe wrappers for * converting individual values to/from strings. */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/language.hpp //#include "c4/language.hpp" #if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) #error "amalgamate: file c4/language.hpp must have been included at this point" #endif /* C4_LANGUAGE_HPP_ */ //included above: //#include //included above: //#include //included above: //#include //included above: //#include //included above: //#include // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/config.hpp //#include "c4/config.hpp" #if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) #error "amalgamate: file c4/config.hpp must have been included at this point" #endif /* C4_CONFIG_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/substr.hpp //#include "c4/substr.hpp" #if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) #error "amalgamate: file c4/substr.hpp must have been included at this point" #endif /* C4_SUBSTR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/std/std_fwd.hpp //#include "c4/std/std_fwd.hpp" #if !defined(C4_STD_STD_FWD_HPP_) && !defined(_C4_STD_STD_FWD_HPP_) #error "amalgamate: file c4/std/std_fwd.hpp must have been included at this point" #endif /* C4_STD_STD_FWD_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/memory_util.hpp //#include "c4/memory_util.hpp" #if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) #error "amalgamate: file c4/memory_util.hpp must have been included at this point" #endif /* C4_MEMORY_UTIL_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/szconv.hpp //#include "c4/szconv.hpp" #if !defined(C4_SZCONV_HPP_) && !defined(_C4_SZCONV_HPP_) #error "amalgamate: file c4/szconv.hpp must have been included at this point" #endif /* C4_SZCONV_HPP_ */ #ifndef C4CORE_NO_FAST_FLOAT # if (C4_CPP >= 17) # if defined(_MSC_VER) # if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros //included above: //# include # define C4CORE_HAVE_STD_TOCHARS 1 # define C4CORE_HAVE_STD_FROMCHARS 0 // prefer fast_float with MSVC # define C4CORE_HAVE_FAST_FLOAT 1 # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 1 # endif # else # if __has_include() //included above: //# include # if defined(__cpp_lib_to_chars) # define C4CORE_HAVE_STD_TOCHARS 1 # define C4CORE_HAVE_STD_FROMCHARS 0 // glibc uses fast_float internally # define C4CORE_HAVE_FAST_FLOAT 1 # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 1 # endif # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 1 # endif # endif # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 1 # endif # if C4CORE_HAVE_FAST_FLOAT // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/ext/fast_float.hpp //# include "c4/ext/fast_float.hpp" #if !defined(C4_EXT_FAST_FLOAT_HPP_) && !defined(_C4_EXT_FAST_FLOAT_HPP_) #error "amalgamate: file c4/ext/fast_float.hpp must have been included at this point" #endif /* C4_EXT_FAST_FLOAT_HPP_ */ # endif #elif (C4_CPP >= 17) # define C4CORE_HAVE_FAST_FLOAT 0 # if defined(_MSC_VER) # if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros //included above: //# include # define C4CORE_HAVE_STD_TOCHARS 1 # define C4CORE_HAVE_STD_FROMCHARS 1 # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # endif # else # if __has_include() //included above: //# include # if defined(__cpp_lib_to_chars) # define C4CORE_HAVE_STD_TOCHARS 1 # define C4CORE_HAVE_STD_FROMCHARS 1 // glibc uses fast_float internally # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # endif # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # endif # endif #else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 0 #endif #if !C4CORE_HAVE_STD_FROMCHARS #include #endif #if defined(_MSC_VER) && !defined(__clang__) # pragma warning(push) # pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe # if C4_MSVC_VERSION != C4_MSVC_VERSION_2017 # pragma warning(disable: 4800) //'int': forcing value to bool 'true' or 'false' (performance warning) # endif #elif defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" # pragma clang diagnostic ignored "-Wformat-nonliteral" # pragma clang diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wformat-nonliteral" # pragma GCC diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision # pragma GCC diagnostic ignored "-Wuseless-cast" # pragma GCC diagnostic ignored "-Wold-style-cast" #endif #if defined(__clang__) #define C4_NO_UBSAN_IOVRFLW __attribute__((no_sanitize("signed-integer-overflow"))) #elif defined(__GNUC__) #if __GNUC__ > 7 #define C4_NO_UBSAN_IOVRFLW __attribute__((no_sanitize("signed-integer-overflow"))) #else #define C4_NO_UBSAN_IOVRFLW #endif #else #define C4_NO_UBSAN_IOVRFLW #endif // NOLINTBEGIN(hicpp-signed-bitwise) namespace c4 { /** @defgroup doc_charconv Charconv utilities * * Lightweight, very fast generic type-safe wrappers for converting * individual values to/from strings. These are the main generic * functions: * - @ref doc_to_chars and its alias @ref xtoa(): implemented by calling @ref itoa() / @ref utoa() / @ref ftoa() / @ref dtoa() (or generically @ref xtoa()) * - @ref doc_from_chars and its alias @ref atox(): implemented by calling @ref atoi() / @ref atou() / @ref atof() / @ref atod() (or generically @ref atox()) * - @ref to_chars_sub() * - @ref from_chars_first() * - @ref xtoa() and @ref atox() are implemented in terms of @ref write_dec() / @ref read_dec() et al (see @ref doc_write / @ref doc_read()) * * And also some modest brag is in order: these functions are really * fast: faster even than C++17 `std::to_chars()` and * `std::to_chars()`, and many dozens of times faster than the * iostream abominations. * * For example, here are some benchmark comparisons for @ref * doc_from_chars (link leads to the main project README, where these * results are shown more systematically). * * * *
atox,int64_t
g++12, linux Visual Studio 2019 *
\image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-atox-mega_bytes_per_second-i64.png \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-atox-mega_bytes_per_second-i64.png *
* * * *
xtoa,int64_t
g++12, linux Visual Studio 2019 *
\image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png *
* * To parse floating point, c4core uses * [fastfloat](https://github.com/fastfloat/fast_float), which is * extremely fast, by an even larger factor: * * * *
atox,float
g++12, linux Visual Studio 2019 *
\image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-atof-mega_bytes_per_second-float.png \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-atof-mega_bytes_per_second-float.png *
* * @{ */ #if C4CORE_HAVE_STD_TOCHARS /** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ typedef enum : std::underlying_type::type { /** print the real number in floating point format (like %f) */ FTOA_FLOAT = static_cast::type>(std::chars_format::fixed), /** print the real number in scientific format (like %e) */ FTOA_SCIENT = static_cast::type>(std::chars_format::scientific), /** print the real number in flexible format (like %g) */ FTOA_FLEX = static_cast::type>(std::chars_format::general), /** print the real number in hexadecimal format (like %a) */ FTOA_HEXA = static_cast::type>(std::chars_format::hex), } RealFormat_e; #else /** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ typedef enum : char { /** print the real number in floating point format (like %f) */ FTOA_FLOAT = 'f', /** print the real number in scientific format (like %e) */ FTOA_SCIENT = 'e', /** print the real number in flexible format (like %g) */ FTOA_FLEX = 'g', /** print the real number in hexadecimal format (like %a) */ FTOA_HEXA = 'a', } RealFormat_e; #endif /** @cond dev */ /** in some platforms, int,unsigned int * are not any of int8_t...int64_t and * long,unsigned long are not any of uint8_t...uint64_t */ template struct is_fixed_length { enum : bool { /** true if T is one of the fixed length signed types */ value_i = (std::is_integral::value && (std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value)), /** true if T is one of the fixed length unsigned types */ value_u = (std::is_integral::value && (std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value)), /** true if T is one of the fixed length signed or unsigned types */ value = value_i || value_u }; }; /** @endcond */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- #if defined(_MSC_VER) && !defined(__clang__) # pragma warning(push) #elif defined(__clang__) # pragma clang diagnostic push #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wconversion" # if __GNUC__ >= 6 # pragma GCC diagnostic ignored "-Wnull-dereference" # endif #endif /** @cond dev */ namespace detail { /* python command to get the values below: def dec(v): return str(v) for bits in (8, 16, 32, 64): imin, imax, umax = (-(1 << (bits - 1))), (1 << (bits - 1)) - 1, (1 << bits) - 1 for vname, v in (("imin", imin), ("imax", imax), ("umax", umax)): for f in (bin, oct, dec, hex): print(f"{bits}b: {vname}={v} {f.__name__}: len={len(f(v)):2d}: {v} {f(v)}") */ // do not use the type as the template argument because in some // platforms long!=int32 and long!=int64. Just use the numbytes // which is more generic and spares lengthy SFINAE code. template struct charconv_digits_; template using charconv_digits = charconv_digits_::value>; template<> struct charconv_digits_<1u, true> // int8_t { enum : size_t { maxdigits_bin = 1 + 2 + 8, // -128==-0b10000000 maxdigits_oct = 1 + 2 + 3, // -128==-0o200 maxdigits_dec = 1 + 3, // -128 maxdigits_hex = 1 + 2 + 2, // -128==-0x80 maxdigits_bin_nopfx = 8, // -128==-0b10000000 maxdigits_oct_nopfx = 3, // -128==-0o200 maxdigits_dec_nopfx = 3, // -128 maxdigits_hex_nopfx = 2, // -128==-0x80 }; // min values without sign! static constexpr csubstr min_value_dec() noexcept { return csubstr("128"); } static constexpr csubstr min_value_hex() noexcept { return csubstr("80"); } static constexpr csubstr min_value_oct() noexcept { return csubstr("200"); } static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000"); } static constexpr csubstr max_value_dec() noexcept { return csubstr("127"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '1')); } }; template<> struct charconv_digits_<1u, false> // uint8_t { enum : size_t { maxdigits_bin = 2 + 8, // 255 0b11111111 maxdigits_oct = 2 + 3, // 255 0o377 maxdigits_dec = 3, // 255 maxdigits_hex = 2 + 2, // 255 0xff maxdigits_bin_nopfx = 8, // 255 0b11111111 maxdigits_oct_nopfx = 3, // 255 0o377 maxdigits_dec_nopfx = 3, // 255 maxdigits_hex_nopfx = 2, // 255 0xff }; static constexpr csubstr max_value_dec() noexcept { return csubstr("255"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '3')); } }; template<> struct charconv_digits_<2u, true> // int16_t { enum : size_t { maxdigits_bin = 1 + 2 + 16, // -32768 -0b1000000000000000 maxdigits_oct = 1 + 2 + 6, // -32768 -0o100000 maxdigits_dec = 1 + 5, // -32768 -32768 maxdigits_hex = 1 + 2 + 4, // -32768 -0x8000 maxdigits_bin_nopfx = 16, // -32768 -0b1000000000000000 maxdigits_oct_nopfx = 6, // -32768 -0o100000 maxdigits_dec_nopfx = 5, // -32768 -32768 maxdigits_hex_nopfx = 4, // -32768 -0x8000 }; // min values without sign! static constexpr csubstr min_value_dec() noexcept { return csubstr("32768"); } static constexpr csubstr min_value_hex() noexcept { return csubstr("8000"); } static constexpr csubstr min_value_oct() noexcept { return csubstr("100000"); } static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000"); } static constexpr csubstr max_value_dec() noexcept { return csubstr("32767"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6)); } }; template<> struct charconv_digits_<2u, false> // uint16_t { enum : size_t { maxdigits_bin = 2 + 16, // 65535 0b1111111111111111 maxdigits_oct = 2 + 6, // 65535 0o177777 maxdigits_dec = 6, // 65535 65535 maxdigits_hex = 2 + 4, // 65535 0xffff maxdigits_bin_nopfx = 16, // 65535 0b1111111111111111 maxdigits_oct_nopfx = 6, // 65535 0o177777 maxdigits_dec_nopfx = 6, // 65535 65535 maxdigits_hex_nopfx = 4, // 65535 0xffff }; static constexpr csubstr max_value_dec() noexcept { return csubstr("65535"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6) || (str.len == 6 && str[0] <= '1')); } }; template<> struct charconv_digits_<4u, true> // int32_t { enum : size_t { maxdigits_bin = 1 + 2 + 32, // len=35: -2147483648 -0b10000000000000000000000000000000 maxdigits_oct = 1 + 2 + 11, // len=14: -2147483648 -0o20000000000 maxdigits_dec = 1 + 10, // len=11: -2147483648 -2147483648 maxdigits_hex = 1 + 2 + 8, // len=11: -2147483648 -0x80000000 maxdigits_bin_nopfx = 32, // len=35: -2147483648 -0b10000000000000000000000000000000 maxdigits_oct_nopfx = 11, // len=14: -2147483648 -0o20000000000 maxdigits_dec_nopfx = 10, // len=11: -2147483648 -2147483648 maxdigits_hex_nopfx = 8, // len=11: -2147483648 -0x80000000 }; // min values without sign! static constexpr csubstr min_value_dec() noexcept { return csubstr("2147483648"); } static constexpr csubstr min_value_hex() noexcept { return csubstr("80000000"); } static constexpr csubstr min_value_oct() noexcept { return csubstr("20000000000"); } static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000000000000000000000000000"); } static constexpr csubstr max_value_dec() noexcept { return csubstr("2147483647"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '1')); } }; template<> struct charconv_digits_<4u, false> // uint32_t { enum : size_t { maxdigits_bin = 2 + 32, // len=34: 4294967295 0b11111111111111111111111111111111 maxdigits_oct = 2 + 11, // len=13: 4294967295 0o37777777777 maxdigits_dec = 10, // len=10: 4294967295 4294967295 maxdigits_hex = 2 + 8, // len=10: 4294967295 0xffffffff maxdigits_bin_nopfx = 32, // len=34: 4294967295 0b11111111111111111111111111111111 maxdigits_oct_nopfx = 11, // len=13: 4294967295 0o37777777777 maxdigits_dec_nopfx = 10, // len=10: 4294967295 4294967295 maxdigits_hex_nopfx = 8, // len=10: 4294967295 0xffffffff }; static constexpr csubstr max_value_dec() noexcept { return csubstr("4294967295"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '3')); } }; template<> struct charconv_digits_<8u, true> // int64_t { enum : size_t { maxdigits_bin = 1 + 2 + 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000 maxdigits_oct = 1 + 2 + 22, // len=25: -9223372036854775808 -0o1000000000000000000000 maxdigits_dec = 1 + 19, // len=20: -9223372036854775808 -9223372036854775808 maxdigits_hex = 1 + 2 + 16, // len=19: -9223372036854775808 -0x8000000000000000 maxdigits_bin_nopfx = 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000 maxdigits_oct_nopfx = 22, // len=25: -9223372036854775808 -0o1000000000000000000000 maxdigits_dec_nopfx = 19, // len=20: -9223372036854775808 -9223372036854775808 maxdigits_hex_nopfx = 16, // len=19: -9223372036854775808 -0x8000000000000000 }; static constexpr csubstr min_value_dec() noexcept { return csubstr("9223372036854775808"); } static constexpr csubstr min_value_hex() noexcept { return csubstr("8000000000000000"); } static constexpr csubstr min_value_oct() noexcept { return csubstr("1000000000000000000000"); } static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000000000000000000000000000000000000000000000000000"); } static constexpr csubstr max_value_dec() noexcept { return csubstr("9223372036854775807"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22)); } }; template<> struct charconv_digits_<8u, false> // uint64_t { enum : size_t { maxdigits_bin = 2 + 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111 maxdigits_oct = 2 + 22, // len=24: 18446744073709551615 0o1777777777777777777777 maxdigits_dec = 20, // len=20: 18446744073709551615 18446744073709551615 maxdigits_hex = 2 + 16, // len=18: 18446744073709551615 0xffffffffffffffff maxdigits_bin_nopfx = 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111 maxdigits_oct_nopfx = 22, // len=24: 18446744073709551615 0o1777777777777777777777 maxdigits_dec_nopfx = 20, // len=20: 18446744073709551615 18446744073709551615 maxdigits_hex_nopfx = 16, // len=18: 18446744073709551615 0xffffffffffffffff }; static constexpr csubstr max_value_dec() noexcept { return csubstr("18446744073709551615"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22) || (str.len == 22 && str[0] <= '1')); } }; } // namespace detail // Helper macros, undefined below #define _c4append(c) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = static_cast(c); } else { ++pos; } } #define _c4appendhex(i) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = hexchars[i]; } else { ++pos; } } /** @endcond */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_digits Get number of digits * * @note At first sight this code may look heavily branchy and * therefore inefficient. However, measurements revealed this to be * the fastest among the alternatives. * * @see https://github.com/biojppm/c4core/pull/77 * * @{ */ /** decimal digits for 8 bit integers */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE auto digits_dec(T v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return ((v >= 100) ? 3u : ((v >= 10) ? 2u : 1u)); } /** decimal digits for 16 bit integers */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE auto digits_dec(T v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return ((v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); } /** decimal digits for 32 bit integers */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE auto digits_dec(T v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return ((v >= 1000000000) ? 10u : (v >= 100000000) ? 9u : (v >= 10000000) ? 8u : (v >= 1000000) ? 7u : (v >= 100000) ? 6u : (v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); } /** decimal digits for 64 bit integers */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE auto digits_dec(T v) noexcept -> typename std::enable_if::type { // thanks @fargies!!! // https://github.com/biojppm/c4core/pull/77#issuecomment-1063753568 C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); if(v >= 1000000000) // 10 { if(v >= 100000000000000) // 15 [15-20] range { if(v >= 100000000000000000) // 18 (15 + (20 - 15) / 2) { if((typename std::make_unsigned::type)v >= 10000000000000000000u) // 20 return 20u; else return (v >= 1000000000000000000) ? 19u : 18u; } else if(v >= 10000000000000000) // 17 return 17u; else return(v >= 1000000000000000) ? 16u : 15u; } else if(v >= 1000000000000) // 13 return (v >= 10000000000000) ? 14u : 13u; else if(v >= 100000000000) // 12 return 12; else return(v >= 10000000000) ? 11u : 10u; } else if(v >= 10000) // 5 [5-9] range { if(v >= 10000000) // 8 return (v >= 100000000) ? 9u : 8u; else if(v >= 1000000) // 7 return 7; else return (v >= 100000) ? 6u : 5u; } else if(v >= 100) return (v >= 1000) ? 4u : 3u; else return (v >= 10) ? 2u : 1u; } /** return the number of digits required to encode an hexadecimal number. */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_hex(T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return v ? 1u + (msb((typename std::make_unsigned::type)v) >> 2u) : 1u; } /** return the number of digits required to encode a binary number. */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_bin(T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return v ? 1u + msb((typename std::make_unsigned::type)v) : 1u; } /** return the number of digits required to encode an octal number. */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_oct(T v_) noexcept { // TODO: is there a better way? C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v_ >= 0); using U = typename std::conditional::type>::type; U v = (U) v_; // safe because we require v_ >= 0 // NOLINT uint32_t __n = 1; enum : U { __b2 = 64u, __b3 = 64u * 8u, __b4 = 64u * 8u * 8u, }; while(true) { if(v < 8u) return __n; else if(v < __b2) return __n + 1; else if(v < __b3) return __n + 2; else if(v < __b4) return __n + 3; v /= (U) __b4; __n += 4; } } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ namespace detail { C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef"; C4_INLINE_CONSTEXPR const char digits0099[] = "0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" "4041424344454647484950515253545556575859" "6061626364656667686970717273747576777879" "8081828384858687888990919293949596979899"; } // namespace detail /** @endcond */ C4_SUPPRESS_WARNING_GCC_PUSH C4_SUPPRESS_WARNING_GCC("-Warray-bounds") // gcc has false positives here #if (defined(__GNUC__) && (__GNUC__ >= 7)) C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has false positives here #endif /** @defgroup doc_write_unchecked Write with known number of digits * * Writes a value without checking the buffer length with regards to * the required number of digits to encode the value. It is the * responsibility of the caller to ensure that the provided number of * digits is enough to write the given value. Notwithstanding the * name, assertions are liberally performed, so this code is safe. * * @{ */ template C4_HOT C4_ALWAYS_INLINE void write_dec_unchecked(substr buf, T v, unsigned digits_v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); C4_ASSERT(buf.len >= digits_v); C4_XASSERT(digits_v == digits_dec(v)); // in bm_xtoa: checkoncelog_singlediv_write2 while(v >= T(100)) { T quo = v; quo /= T(100); const auto num = (v - quo * T(100)) << 1u; // NOLINT v = quo; buf.str[--digits_v] = detail::digits0099[num + 1]; buf.str[--digits_v] = detail::digits0099[num]; } if(v >= T(10)) { C4_ASSERT(digits_v == 2); const auto num = v << 1u; buf.str[1] = detail::digits0099[num + 1]; buf.str[0] = detail::digits0099[num]; } else { C4_ASSERT(digits_v == 1); buf.str[0] = (char)('0' + v); } } template C4_HOT C4_ALWAYS_INLINE void write_hex_unchecked(substr buf, T v, unsigned digits_v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); C4_ASSERT(buf.len >= digits_v); C4_XASSERT(digits_v == digits_hex(v)); do { buf.str[--digits_v] = detail::hexchars[v & T(15)]; v >>= 4; } while(v); C4_ASSERT(digits_v == 0); } template C4_HOT C4_ALWAYS_INLINE void write_oct_unchecked(substr buf, T v, unsigned digits_v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); C4_ASSERT(buf.len >= digits_v); C4_XASSERT(digits_v == digits_oct(v)); do { buf.str[--digits_v] = (char)('0' + (v & T(7))); v >>= 3; } while(v); C4_ASSERT(digits_v == 0); } template C4_HOT C4_ALWAYS_INLINE void write_bin_unchecked(substr buf, T v, unsigned digits_v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); C4_ASSERT(buf.len >= digits_v); C4_XASSERT(digits_v == digits_bin(v)); do { buf.str[--digits_v] = (char)('0' + (v & T(1))); v >>= 1; } while(v); C4_ASSERT(digits_v == 0); } /** @} */ // write_unchecked //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_write Write a value * * Writes a value without checking the buffer length * decimal number -- but asserting. * * @{ */ /** write an integer to a string in decimal format. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the required size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t write_dec(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); unsigned digits = digits_dec(v); if(C4_LIKELY(buf.len >= digits)) write_dec_unchecked(buf, v, digits); return digits; } /** write an integer to a string in hexadecimal format. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not prefix with 0x * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the required size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t write_hex(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); unsigned digits = digits_hex(v); if(C4_LIKELY(buf.len >= digits)) write_hex_unchecked(buf, v, digits); return digits; } /** write an integer to a string in octal format. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not prefix with 0o * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the required size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t write_oct(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); unsigned digits = digits_oct(v); if(C4_LIKELY(buf.len >= digits)) write_oct_unchecked(buf, v, digits); return digits; } /** write an integer to a string in binary format. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not prefix with 0b * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the required size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t write_bin(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); unsigned digits = digits_bin(v); C4_ASSERT(digits > 0); if(C4_LIKELY(buf.len >= digits)) write_bin_unchecked(buf, v, digits); return digits; } /** @cond dev */ namespace detail { template using NumberWriter = size_t (*)(substr, U); template writer> size_t write_num_digits(substr buf, T v, size_t num_digits) noexcept { C4_STATIC_ASSERT(std::is_integral::value); const size_t ret = writer(buf, v); if(ret >= num_digits) return ret; else if(ret >= buf.len || num_digits > buf.len) return num_digits; C4_ASSERT(num_digits >= ret); const size_t delta = static_cast(num_digits - ret); // NOLINT C4_ASSERT(ret + delta <= buf.len); if(ret) memmove(buf.str + delta, buf.str, ret); if(delta) memset(buf.str, '0', delta); return num_digits; } } // namespace detail /** @endcond */ /** same as c4::write_dec(), but pad with zeroes on the left * such that the resulting string is @p num_digits wide. * If the given number is requires more than num_digits, then the number prevails. */ template C4_ALWAYS_INLINE size_t write_dec(substr buf, T val, size_t num_digits) noexcept { return detail::write_num_digits>(buf, val, num_digits); } /** same as c4::write_hex(), but pad with zeroes on the left * such that the resulting string is @p num_digits wide. * If the given number is requires more than num_digits, then the number prevails. */ template C4_ALWAYS_INLINE size_t write_hex(substr buf, T val, size_t num_digits) noexcept { return detail::write_num_digits>(buf, val, num_digits); } /** same as c4::write_bin(), but pad with zeroes on the left * such that the resulting string is @p num_digits wide. * If the given number is requires more than num_digits, then the number prevails. */ template C4_ALWAYS_INLINE size_t write_bin(substr buf, T val, size_t num_digits) noexcept { return detail::write_num_digits>(buf, val, num_digits); } /** same as c4::write_oct(), but pad with zeroes on the left * such that the resulting string is @p num_digits wide. * If the given number is requires more than num_digits, then the number prevails. */ template C4_ALWAYS_INLINE size_t write_oct(substr buf, T val, size_t num_digits) noexcept { return detail::write_num_digits>(buf, val, num_digits); } /** @} */ // write C4_SUPPRESS_WARNING_GCC_POP //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C4_SUPPRESS_WARNING_MSVC_PUSH C4_SUPPRESS_WARNING_MSVC(4365) // '=': conversion from 'int' to 'I', signed/unsigned mismatch /** @defgroup doc_read Read a value * * @{ */ /** read a decimal integer from a string. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note The string must be trimmed. Whitespace is not accepted. * @note the string must not be empty * @note there is no check for overflow; the value wraps around * in a way similar to the standard C/C++ overflow behavior. * For example, `read_dec("128", &val)` returns true * and val will be set to 0 because 127 is the max i8 value. * @see overflows() to find out if a number string overflows a type range * @return true if the conversion was successful (no overflow check) */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool read_dec(csubstr s, I *C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(!s.empty()); *v = 0; for(char c : s) { if(C4_UNLIKELY(c < '0' || c > '9')) return false; *v = (*v) * I(10) + (I(c) - I('0')); } return true; } /** read an hexadecimal integer from a string. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not accept leading 0x or 0X * @note the string must not be empty * @note the string must be trimmed. Whitespace is not accepted. * @note there is no check for overflow; the value wraps around * in a way similar to the standard C/C++ overflow behavior. * For example, `read_hex("80", &val)` returns true * and val will be set to 0 because 7f is the max i8 value. * @see overflows() to find out if a number string overflows a type range * @return true if the conversion was successful (no overflow check) */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool read_hex(csubstr s, I *C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(!s.empty()); *v = 0; for(char c : s) { I cv; if(c >= '0' && c <= '9') cv = I(c) - I('0'); else if(c >= 'a' && c <= 'f') cv = I(10) + (I(c) - I('a')); else if(c >= 'A' && c <= 'F') cv = I(10) + (I(c) - I('A')); else return false; *v = (*v) * I(16) + cv; } return true; } /** read a binary integer from a string. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not accept leading 0b or 0B * @note the string must not be empty * @note the string must be trimmed. Whitespace is not accepted. * @note there is no check for overflow; the value wraps around * in a way similar to the standard C/C++ overflow behavior. * For example, `read_bin("10000000", &val)` returns true * and val will be set to 0 because 1111111 is the max i8 value. * @see overflows() to find out if a number string overflows a type range * @return true if the conversion was successful (no overflow check) */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool read_bin(csubstr s, I *C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(!s.empty()); *v = 0; for(char c : s) { *v <<= 1; if(c == '1') *v |= 1; else if(c != '0') return false; } return true; } /** read an octal integer from a string. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not accept leading 0o or 0O * @note the string must not be empty * @note the string must be trimmed. Whitespace is not accepted. * @note there is no check for overflow; the value wraps around * in a way similar to the standard C/C++ overflow behavior. * For example, `read_oct("200", &val)` returns true * and val will be set to 0 because 177 is the max i8 value. * @see overflows() to find out if a number string overflows a type range * @return true if the conversion was successful (no overflow check) */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool read_oct(csubstr s, I *C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(!s.empty()); *v = 0; for(char c : s) { if(C4_UNLIKELY(c < '0' || c > '7')) return false; *v = (*v) * I(8) + (I(c) - I('0')); } return true; } /** @} */ C4_SUPPRESS_WARNING_MSVC_POP //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wswitch-default") /** @cond dev */ namespace detail { inline size_t _itoa2buf(substr buf, size_t pos, csubstr val) noexcept { C4_ASSERT(pos < buf.len); C4_ASSERT(pos + val.len <= buf.len); C4_ASSERT(val.len > 0); memcpy(buf.str + pos, val.str, val.len); return pos + val.len; } inline size_t _itoa2bufwithdigits(substr buf, size_t pos, size_t num_digits, csubstr val) noexcept { num_digits = num_digits > val.len ? num_digits - val.len : 0; C4_ASSERT(num_digits + val.len <= buf.len); for(size_t i = 0; i < num_digits; ++i) _c4append('0'); return detail::_itoa2buf(buf, pos, val); } template C4_NO_INLINE size_t _itoadec2buf(substr buf) noexcept { using digits_type = detail::charconv_digits; if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) return digits_type::maxdigits_dec; buf.str[0] = '-'; return detail::_itoa2buf(buf, 1, digits_type::min_value_dec()); } template C4_NO_INLINE size_t _itoa2buf(substr buf, I radix) noexcept { using digits_type = detail::charconv_digits; size_t pos = 0; if(C4_LIKELY(buf.len > 0)) buf.str[pos++] = '-'; switch(radix) // NOLINT(hicpp-multiway-paths-covered) { case I(10): if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) return digits_type::maxdigits_dec; pos =_itoa2buf(buf, pos, digits_type::min_value_dec()); break; case I(16): if(C4_UNLIKELY(buf.len < digits_type::maxdigits_hex)) return digits_type::maxdigits_hex; buf.str[pos++] = '0'; buf.str[pos++] = 'x'; pos = _itoa2buf(buf, pos, digits_type::min_value_hex()); break; case I( 2): if(C4_UNLIKELY(buf.len < digits_type::maxdigits_bin)) return digits_type::maxdigits_bin; buf.str[pos++] = '0'; buf.str[pos++] = 'b'; pos = _itoa2buf(buf, pos, digits_type::min_value_bin()); break; case I( 8): if(C4_UNLIKELY(buf.len < digits_type::maxdigits_oct)) return digits_type::maxdigits_oct; buf.str[pos++] = '0'; buf.str[pos++] = 'o'; pos = _itoa2buf(buf, pos, digits_type::min_value_oct()); break; } return pos; } template C4_NO_INLINE size_t _itoa2buf(substr buf, I radix, size_t num_digits) noexcept { using digits_type = detail::charconv_digits; size_t pos = 0; size_t needed_digits = 0; if(C4_LIKELY(buf.len > 0)) buf.str[pos++] = '-'; switch(radix) // NOLINT(hicpp-multiway-paths-covered) { case I(10): // add 1 to account for - needed_digits = num_digits+1 > digits_type::maxdigits_dec ? num_digits+1 : digits_type::maxdigits_dec; if(C4_UNLIKELY(buf.len < needed_digits)) return needed_digits; pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_dec()); break; case I(16): // add 3 to account for -0x needed_digits = num_digits+3 > digits_type::maxdigits_hex ? num_digits+3 : digits_type::maxdigits_hex; if(C4_UNLIKELY(buf.len < needed_digits)) return needed_digits; buf.str[pos++] = '0'; buf.str[pos++] = 'x'; pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_hex()); break; case I(2): // add 3 to account for -0b needed_digits = num_digits+3 > digits_type::maxdigits_bin ? num_digits+3 : digits_type::maxdigits_bin; if(C4_UNLIKELY(buf.len < needed_digits)) return needed_digits; C4_ASSERT(buf.len >= digits_type::maxdigits_bin); buf.str[pos++] = '0'; buf.str[pos++] = 'b'; pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_bin()); break; case I(8): // add 3 to account for -0o needed_digits = num_digits+3 > digits_type::maxdigits_oct ? num_digits+3 : digits_type::maxdigits_oct; if(C4_UNLIKELY(buf.len < needed_digits)) return needed_digits; C4_ASSERT(buf.len >= digits_type::maxdigits_oct); buf.str[pos++] = '0'; buf.str[pos++] = 'o'; pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_oct()); break; } return pos; } } // namespace detail /** @endcond */ /** @defgroup doc_itoa itoa: signed to chars * * @{ */ /** convert an integral signed decimal to a string. * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t itoa(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_signed::value); if(v >= T(0)) { // write_dec() checks the buffer size, so no need to check here return write_dec(buf, v); } // when T is the min value (eg i8: -128), negating it // will overflow, so treat the min as a special case if(C4_LIKELY(v != std::numeric_limits::min())) { v = -v; unsigned digits = digits_dec(v); if(C4_LIKELY(buf.len >= digits + 1u)) { buf.str[0] = '-'; write_dec_unchecked(buf.sub(1), v, digits); } return digits + 1u; } return detail::_itoadec2buf(buf); } /** convert an integral signed integer to a string, using a specific * radix. The radix must be 2, 8, 10 or 16. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix) noexcept { C4_STATIC_ASSERT(std::is_signed::value); C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); C4_SUPPRESS_WARNING_GCC_PUSH #if (defined(__GNUC__) && (__GNUC__ >= 7)) C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here #endif // when T is the min value (eg i8: -128), negating it // will overflow, so treat the min as a special case if(C4_LIKELY(v != std::numeric_limits::min())) { unsigned pos = 0; if(v < 0) { v = -v; if(C4_LIKELY(buf.len > 0)) buf.str[pos] = '-'; ++pos; } unsigned digits = 0; switch(radix) // NOLINT(hicpp-multiway-paths-covered) { case T(10): digits = digits_dec(v); if(C4_LIKELY(buf.len >= pos + digits)) write_dec_unchecked(buf.sub(pos), v, digits); break; case T(16): digits = digits_hex(v); if(C4_LIKELY(buf.len >= pos + 2u + digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'x'; write_hex_unchecked(buf.sub(pos + 2), v, digits); } digits += 2u; break; case T(2): digits = digits_bin(v); if(C4_LIKELY(buf.len >= pos + 2u + digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'b'; write_bin_unchecked(buf.sub(pos + 2), v, digits); } digits += 2u; break; case T(8): digits = digits_oct(v); if(C4_LIKELY(buf.len >= pos + 2u + digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'o'; write_oct_unchecked(buf.sub(pos + 2), v, digits); } digits += 2u; break; } return pos + digits; } C4_SUPPRESS_WARNING_GCC_POP // when T is the min value (eg i8: -128), negating it // will overflow return detail::_itoa2buf(buf, radix); } /** same as c4::itoa(), but pad with zeroes on the left such that the * resulting string is @p num_digits wide, not accounting for radix * prefix (0x,0o,0b). The @p radix must be 2, 8, 10 or 16. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix, size_t num_digits) noexcept { C4_STATIC_ASSERT(std::is_signed::value); C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); C4_SUPPRESS_WARNING_GCC_PUSH #if (defined(__GNUC__) && (__GNUC__ >= 7)) C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here #endif // when T is the min value (eg i8: -128), negating it // will overflow, so treat the min as a special case if(C4_LIKELY(v != std::numeric_limits::min())) { unsigned pos = 0; if(v < 0) { v = -v; if(C4_LIKELY(buf.len > 0)) buf.str[pos] = '-'; ++pos; } unsigned total_digits = 0; switch(radix) // NOLINT(hicpp-multiway-paths-covered) { case T(10): total_digits = digits_dec(v); total_digits = pos + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) write_dec(buf.sub(pos), v, num_digits); break; case T(16): total_digits = digits_hex(v); total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'x'; write_hex(buf.sub(pos + 2), v, num_digits); } break; case T(2): total_digits = digits_bin(v); total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'b'; write_bin(buf.sub(pos + 2), v, num_digits); } break; case T(8): total_digits = digits_oct(v); total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'o'; write_oct(buf.sub(pos + 2), v, num_digits); } break; } return total_digits; } C4_SUPPRESS_WARNING_GCC_POP // when T is the min value (eg i8: -128), negating it // will overflow return detail::_itoa2buf(buf, radix, num_digits); } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_utoa utoa: unsigned to chars * * @{ */ /** convert an integral unsigned decimal to a string. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t utoa(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_unsigned::value); // write_dec() does the buffer length check, so no need to check here return write_dec(buf, v); } /** convert an integral unsigned integer to a string, using a specific * radix. The radix must be 2, 8, 10 or 16. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix) noexcept { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); unsigned digits = 0; switch(radix) // NOLINT(hicpp-multiway-paths-covered) { case T(10): digits = digits_dec(v); if(C4_LIKELY(buf.len >= digits)) write_dec_unchecked(buf, v, digits); break; case T(16): digits = digits_hex(v); if(C4_LIKELY(buf.len >= digits+2u)) { buf.str[0] = '0'; buf.str[1] = 'x'; write_hex_unchecked(buf.sub(2), v, digits); } digits += 2u; break; case T(2): digits = digits_bin(v); if(C4_LIKELY(buf.len >= digits+2u)) { buf.str[0] = '0'; buf.str[1] = 'b'; write_bin_unchecked(buf.sub(2), v, digits); } digits += 2u; break; case T(8): digits = digits_oct(v); if(C4_LIKELY(buf.len >= digits+2u)) { buf.str[0] = '0'; buf.str[1] = 'o'; write_oct_unchecked(buf.sub(2), v, digits); } digits += 2u; break; } return digits; } /** same as c4::utoa(), but pad with zeroes on the left such that the * resulting string is @p num_digits wide. The @p radix must be 2, * 8, 10 or 16. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix, size_t num_digits) noexcept { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); unsigned total_digits = 0; switch(radix) // NOLINT(hicpp-multiway-paths-covered) { case T(10): total_digits = digits_dec(v); total_digits = (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) write_dec(buf, v, num_digits); break; case T(16): total_digits = digits_hex(v); total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[0] = '0'; buf.str[1] = 'x'; write_hex(buf.sub(2), v, num_digits); } break; case T(2): total_digits = digits_bin(v); total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[0] = '0'; buf.str[1] = 'b'; write_bin(buf.sub(2), v, num_digits); } break; case T(8): total_digits = digits_oct(v); total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[0] = '0'; buf.str[1] = 'o'; write_oct(buf.sub(2), v, num_digits); } break; } return total_digits; } C4_SUPPRESS_WARNING_GCC_POP /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_atoi atoi: chars to signed * * @{ */ /** Convert a trimmed string to a signed integral value. The input * string can be formatted as decimal, binary (prefix 0b or 0B), octal * (prefix 0o or 0O) or hexadecimal (prefix 0x or 0X). Strings with * leading zeroes are considered as decimal and not octal (unlike the * C/C++ convention). Every character in the input string is read for * the conversion; the input string must not contain any leading or * trailing whitespace. * * @return true if the conversion was successful. * * @note a positive sign is not accepted. ie, the string must not * start with '+' * * @note overflow is not detected: the return status is true even if * the conversion would return a value outside of the type's range, in * which case the result will wrap around the type's range. This is * similar to native behavior. See @ref doc_overflows and @ref * doc_overflow_checked for overflow checking utilities. * * @see atoi_first() if the string is not trimmed to the value to read. */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool atoi(csubstr str, T * C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_STATIC_ASSERT(std::is_signed::value); if(C4_UNLIKELY(str.len == 0)) return false; C4_ASSERT(str.str[0] != '+'); T sign = 1; size_t start = 0; if(str.str[0] == '-') { if(C4_UNLIKELY(str.len == ++start)) return false; sign = -1; } bool parsed_ok = true; if(str.str[start] != '0') // this should be the common case, so put it first { parsed_ok = read_dec(str.sub(start), v); } else if(str.len > start + 1) { // starts with 0: is it 0x, 0o, 0b? const char pfx = str.str[start + 1]; if(pfx == 'x' || pfx == 'X') parsed_ok = str.len > start + 2 && read_hex(str.sub(start + 2), v); else if(pfx == 'b' || pfx == 'B') parsed_ok = str.len > start + 2 && read_bin(str.sub(start + 2), v); else if(pfx == 'o' || pfx == 'O') parsed_ok = str.len > start + 2 && read_oct(str.sub(start + 2), v); else parsed_ok = read_dec(str.sub(start + 1), v); } else { parsed_ok = read_dec(str.sub(start), v); } if(C4_LIKELY(parsed_ok)) *v *= sign; return parsed_ok; } /** Select the next range of characters in the string that can be parsed * as a signed integral value, and convert it using atoi(). Leading * whitespace (space, newline, tabs) is skipped. * @return the number of characters read for conversion, or csubstr::npos if the conversion failed * @see atoi() if the string is already trimmed to the value to read. * @see csubstr::first_int_span() */ template C4_ALWAYS_INLINE size_t atoi_first(csubstr str, T * C4_RESTRICT v) { csubstr trimmed = str.first_int_span(); if(trimmed.len == 0) return csubstr::npos; if(atoi(trimmed, v)) return static_cast(trimmed.end() - str.begin()); return csubstr::npos; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_atou atou: chars to unsigned * * @{ */ /** Convert a trimmed string to an unsigned integral value. The string can be * formatted as decimal, binary (prefix 0b or 0B), octal (prefix 0o or 0O) * or hexadecimal (prefix 0x or 0X). Every character in the input string is read * for the conversion; it must not contain any leading or trailing whitespace. * * @return true if the conversion was successful. * * @note overflow is not detected: the return status is true even if * the conversion would return a value outside of the type's range, in * which case the result will wrap around the type's range. See @ref * doc_overflows and @ref doc_overflow_checked for overflow checking * utilities. * * @note If the string has a minus character, the return status * will be false. * * @see atou_first() if the string is not trimmed to the value to read. */ template bool atou(csubstr str, T * C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); if(C4_UNLIKELY(str.len == 0 || str.front() == '-')) return false; bool parsed_ok = true; if(str.str[0] != '0') { parsed_ok = read_dec(str, v); } else { if(str.len > 1) { const char pfx = str.str[1]; if(pfx == 'x' || pfx == 'X') parsed_ok = str.len > 2 && read_hex(str.sub(2), v); else if(pfx == 'b' || pfx == 'B') parsed_ok = str.len > 2 && read_bin(str.sub(2), v); else if(pfx == 'o' || pfx == 'O') parsed_ok = str.len > 2 && read_oct(str.sub(2), v); else parsed_ok = read_dec(str, v); } else { *v = 0; // we know the first character is 0 } } return parsed_ok; } /** Select the next range of characters in the string that can be parsed * as an unsigned integral value, and convert it using atou(). Leading * whitespace (space, newline, tabs) is skipped. * @return the number of characters read for conversion, or csubstr::npos if the conversion faileds * @see atou() if the string is already trimmed to the value to read. * @see csubstr::first_uint_span() */ template C4_ALWAYS_INLINE size_t atou_first(csubstr str, T *v) { csubstr trimmed = str.first_uint_span(); if(trimmed.len == 0) return csubstr::npos; if(atou(trimmed, v)) return static_cast(trimmed.end() - str.begin()); return csubstr::npos; } /** @} */ #if defined(_MSC_VER) && !defined(__clang__) # pragma warning(pop) #elif defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ namespace detail { inline bool check_overflow(csubstr str, csubstr limit) noexcept { if(str.len != limit.len) return str.len > limit.len; for(size_t i = 0; i < limit.len; ++i) { if(str[i] < limit[i]) return false; else if(str[i] > limit[i]) return true; } return false; } } // namespace detail /** @endcond */ /** @defgroup doc_overflows overflows: does a number string overflow a type * * @{ */ /** Test if the following string would overflow when converted to * associated integral types; this function is dispatched with SFINAE * to handle differently signed and unsigned types. * @return true if number will overflow, false if it fits (or doesn't parse) * @see doc_overflow_checked for format specifiers to enforce no-overflow reads */ template auto overflows(csubstr str) noexcept -> typename std::enable_if::value, bool>::type { C4_STATIC_ASSERT(std::is_integral::value); if(C4_UNLIKELY(str.len == 0)) { return false; } else if(str.str[0] == '0') { if (str.len == 1) return false; switch (str.str[1]) { case 'x': case 'X': { size_t fno = str.first_not_of('0', 2); if (fno == csubstr::npos) return false; return !(str.len <= fno + (sizeof(T) * 2)); } case 'b': case 'B': { size_t fno = str.first_not_of('0', 2); if (fno == csubstr::npos) return false; return !(str.len <= fno +(sizeof(T) * 8)); } case 'o': case 'O': { size_t fno = str.first_not_of('0', 2); if(fno == csubstr::npos) return false; return detail::charconv_digits::is_oct_overflow(str.sub(fno)); } default: { size_t fno = str.first_not_of('0', 1); if(fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::max_value_dec()); } } } else if(C4_UNLIKELY(str[0] == '-')) { return true; } else { return detail::check_overflow(str, detail::charconv_digits::max_value_dec()); } } /** Test if the following string would overflow when converted to * associated integral types; this function is dispatched with SFINAE * to handle differently signed and unsigned types. * * @return true if number will overflow, false if it fits (or doesn't parse) * @see doc_overflow_checked for format specifiers to enforce no-overflow reads */ template auto overflows(csubstr str) noexcept -> typename std::enable_if::value, bool>::type { C4_STATIC_ASSERT(std::is_integral::value); if(C4_UNLIKELY(str.len == 0)) return false; if(str.str[0] == '-') { if(str.str[1] == '0') { if(str.len == 2) return false; switch(str.str[2]) { case 'x': case 'X': { size_t fno = str.first_not_of('0', 3); if (fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_hex()); } case 'b': case 'B': { size_t fno = str.first_not_of('0', 3); if (fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_bin()); } case 'o': case 'O': { size_t fno = str.first_not_of('0', 3); if(fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_oct()); } default: { size_t fno = str.first_not_of('0', 2); if(fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_dec()); } } } else { return detail::check_overflow(str.sub(1), detail::charconv_digits::min_value_dec()); } } else if(str.str[0] == '0') { if (str.len == 1) return false; switch(str.str[1]) { case 'x': case 'X': { size_t fno = str.first_not_of('0', 2); if (fno == csubstr::npos) return false; const size_t len = str.len - fno; return !((len < sizeof (T) * 2) || (len == sizeof(T) * 2 && str[fno] <= '7')); } case 'b': case 'B': { size_t fno = str.first_not_of('0', 2); if (fno == csubstr::npos) return false; return !(str.len <= fno + (sizeof(T) * 8 - 1)); } case 'o': case 'O': { size_t fno = str.first_not_of('0', 2); if(fno == csubstr::npos) return false; return detail::charconv_digits::is_oct_overflow(str.sub(fno)); } default: { size_t fno = str.first_not_of('0', 1); if(fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::max_value_dec()); } } } else { return detail::check_overflow(str, detail::charconv_digits::max_value_dec()); } } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ namespace detail { #if (!C4CORE_HAVE_STD_FROMCHARS) /** @see http://www.exploringbinary.com/ for many good examples on float-str conversion */ template void get_real_format_str(char (& C4_RESTRICT fmt)[N], int precision, RealFormat_e formatting, const char* length_modifier="") { int iret; if(precision == -1) iret = snprintf(fmt, sizeof(fmt), "%%%s%c", length_modifier, formatting); else if(precision == 0) iret = snprintf(fmt, sizeof(fmt), "%%.%s%c", length_modifier, formatting); else iret = snprintf(fmt, sizeof(fmt), "%%.%d%s%c", precision, length_modifier, formatting); C4_ASSERT(iret >= 2 && size_t(iret) < sizeof(fmt)); C4_UNUSED(iret); } /** @todo we're depending on snprintf()/sscanf() for converting to/from * floating point numbers. Apparently, this increases the binary size * by a considerable amount. There are some lightweight printf * implementations: * * @see http://www.sparetimelabs.com/tinyprintf/tinyprintf.php (BSD) * @see https://github.com/weiss/c99-snprintf * @see https://github.com/nothings/stb/blob/master/stb_sprintf.h * @see http://www.exploringbinary.com/ * @see https://blog.benoitblanchon.fr/lightweight-float-to-string/ * @see http://www.ryanjuckett.com/programming/printing-floating-point-numbers/ */ template size_t print_one(substr str, const char* full_fmt, T v) { #ifdef _MSC_VER /** use _snprintf() to prevent early termination of the output * for writing the null character at the last position * @see https://msdn.microsoft.com/en-us/library/2ts7cx93.aspx */ int iret = _snprintf(str.str, str.len, full_fmt, v); if(iret < 0) { /* when buf.len is not enough, VS returns a negative value. * so call it again with a negative value for getting an * actual length of the string */ iret = snprintf(nullptr, 0, full_fmt, v); C4_ASSERT(iret > 0); } size_t ret = (size_t) iret; return ret; #else int iret = snprintf(str.str, str.len, full_fmt, v); C4_ASSERT(iret >= 0); size_t ret = (size_t) iret; if(ret >= str.len) ++ret; /* snprintf() reserves the last character to write \0 */ return ret; #endif } #endif // (!C4CORE_HAVE_STD_FROMCHARS) #if (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT) /** scans a string using the given type format, while at the same time * allowing non-null-terminated strings AND guaranteeing that the given * string length is strictly respected, so that no buffer overflows * might occur. */ template inline size_t scan_one(csubstr str, const char *type_fmt, T *v) { /* snscanf() is absolutely needed here as we must be sure that * str.len is strictly respected, because substr is * generally not null-terminated. * * Alas, there is no snscanf(). * * So we fake it by using a dynamic format with an explicit * field size set to the length of the given span. * This trick is taken from: * https://stackoverflow.com/a/18368910/5875572 */ /* this is the actual format we'll use for scanning */ char fmt[16]; /* write the length into it. Eg "%12f". * Also, get the number of characters read from the string. * So the final format ends up as "%12f%n"*/ int iret = std::snprintf(fmt, sizeof(fmt), "%%" "%zu" "%s" "%%n", str.len, type_fmt); /* no nasty surprises, please! */ C4_ASSERT(iret >= 0 && size_t(iret) < C4_COUNTOF(fmt)); /* now we scan with confidence that the span length is respected */ int num_chars; iret = std::sscanf(str.str, fmt, v, &num_chars); /* scanf returns the number of successful conversions */ if(iret != 1) return csubstr::npos; C4_ASSERT(num_chars >= 0); return (size_t)(num_chars); } #endif // (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT) #if C4CORE_HAVE_STD_TOCHARS template C4_ALWAYS_INLINE size_t rtoa(substr buf, T v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept { std::to_chars_result result; size_t pos = 0; if(formatting == FTOA_HEXA) { if(buf.len > size_t(2)) { buf.str[0] = '0'; buf.str[1] = 'x'; } pos += size_t(2); } if(precision == -1) result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting); else result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting, precision); if(result.ec == std::errc()) { // all good, no errors. C4_ASSERT(result.ptr >= buf.str); ptrdiff_t delta = result.ptr - buf.str; return static_cast(delta); } C4_ASSERT(result.ec == std::errc::value_too_large); // This is unfortunate. // // When the result can't fit in the given buffer, // std::to_chars() returns the end pointer it was originally // given, which is useless because here we would like to know // _exactly_ how many characters the buffer must have to fit // the result. // // So we take the pessimistic view, and assume as many digits // as could ever be required: size_t ret = static_cast(std::numeric_limits::max_digits10); return ret > buf.len ? ret : buf.len + 1; } #endif // C4CORE_HAVE_STD_TOCHARS #if C4CORE_HAVE_FAST_FLOAT template C4_ALWAYS_INLINE bool scan_rhex(csubstr s, T *C4_RESTRICT val) noexcept { C4_ASSERT(s.len > 0); C4_ASSERT(s.str[0] != '-'); C4_ASSERT(s.str[0] != '+'); C4_ASSERT(!s.begins_with("0x")); C4_ASSERT(!s.begins_with("0X")); size_t pos = 0; // integer part for( ; pos < s.len; ++pos) { const char c = s.str[pos]; if(c >= '0' && c <= '9') *val = *val * T(16) + T(c - '0'); else if(c >= 'a' && c <= 'f') *val = *val * T(16) + T(c - 'a'); else if(c >= 'A' && c <= 'F') *val = *val * T(16) + T(c - 'A'); else if(c == '.') { ++pos; break; // follow on to mantissa } else if(c == 'p' || c == 'P') { ++pos; goto power; // no mantissa given, jump to power // NOLINT } else { return false; } } // mantissa { // 0.0625 == 1/16 == value of first digit after the comma for(T digit = T(0.0625); pos < s.len; ++pos, digit /= T(16)) // NOLINT { const char c = s.str[pos]; if(c >= '0' && c <= '9') *val += digit * T(c - '0'); else if(c >= 'a' && c <= 'f') *val += digit * T(c - 'a'); else if(c >= 'A' && c <= 'F') *val += digit * T(c - 'A'); else if(c == 'p' || c == 'P') { ++pos; goto power; // mantissa finished, jump to power // NOLINT } else { return false; } } } return true; power: if(C4_LIKELY(pos < s.len)) { if(s.str[pos] == '+') // atoi() cannot handle a leading '+' ++pos; if(C4_LIKELY(pos < s.len)) { int16_t powval = {}; if(C4_LIKELY(atoi(s.sub(pos), &powval))) { *val *= ipow(powval); return true; } } } return false; } #endif } // namespace detail /** @endcond */ #undef _c4appendhex #undef _c4append /** @defgroup doc_ftoa ftoa: float32 to chars * * @{ */ /** Convert a single-precision real number to string. The string will * in general be NOT null-terminated. For FTOA_FLEX, \p precision is * the number of significand digits. Otherwise \p precision is the * number of decimals. It is safe to call this function with an empty * or too-small buffer. * * @return the size of the buffer needed to write the number */ C4_ALWAYS_INLINE size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept { #if C4CORE_HAVE_STD_TOCHARS return detail::rtoa(str, v, precision, formatting); #else char fmt[16]; detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/""); return detail::print_one(str, fmt, v); #endif } /** @} */ /** @defgroup doc_dtoa dtoa: float64 to chars * * @{ */ /** Convert a double-precision real number to string. The string will * in general be NOT null-terminated. For FTOA_FLEX, \p precision is * the number of significand digits. Otherwise \p precision is the * number of decimals. It is safe to call this function with an empty * or too-small buffer. * * @return the size of the buffer needed to write the number */ C4_ALWAYS_INLINE size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept { #if C4CORE_HAVE_STD_TOCHARS return detail::rtoa(str, v, precision, formatting); #else char fmt[16]; detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"l"); return detail::print_one(str, fmt, v); #endif } /** @} */ /** @defgroup doc_atof atof: chars to float32 * * @{ */ /** Convert a string to a single precision real number. * The input string must be trimmed to the value, ie * no leading or trailing whitespace can be present. * @return true iff the conversion succeeded * @see atof_first() if the string is not trimmed */ C4_ALWAYS_INLINE bool atof(csubstr str, float * C4_RESTRICT v) noexcept { C4_ASSERT(str.len > 0); C4_ASSERT(str.triml(" \r\t\n").len == str.len); #if C4CORE_HAVE_FAST_FLOAT // fastfloat cannot parse hexadecimal floats bool isneg = (str.str[0] == '-'); csubstr rem = str.sub(isneg || str.str[0] == '+'); if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) { fast_float::from_chars_result result; result = fast_float::from_chars(str.str, str.str + str.len, *v); return result.ec == std::errc(); } else if(detail::scan_rhex(rem.sub(2), v)) { *v *= isneg ? -1.f : 1.f; return true; } return false; #elif C4CORE_HAVE_STD_FROMCHARS std::from_chars_result result; result = std::from_chars(str.str, str.str + str.len, *v); return result.ec == std::errc(); #else csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) return detail::scan_one(str, "f", v) != csubstr::npos; else return detail::scan_one(str, "a", v) != csubstr::npos; #endif } /** Convert a string to a single precision real number. * Leading whitespace is skipped until valid characters are found. * @return the number of characters read from the string, or npos if * conversion was not successful or if the string was empty */ inline size_t atof_first(csubstr str, float * C4_RESTRICT v) noexcept { csubstr trimmed = str.first_real_span(); if(trimmed.len == 0) return csubstr::npos; if(atof(trimmed, v)) return static_cast(trimmed.end() - str.begin()); return csubstr::npos; } /** @} */ /** @defgroup doc_atod atod: chars to float64 * * @{ */ /** Convert a string to a double precision real number. * The input string must be trimmed to the value, ie * no leading or trailing whitespace can be present. * @return true iff the conversion succeeded * @see atod_first() if the string is not trimmed */ C4_ALWAYS_INLINE bool atod(csubstr str, double * C4_RESTRICT v) noexcept { C4_ASSERT(str.len > 0); C4_ASSERT(str.triml(" \r\t\n").len == str.len); #if C4CORE_HAVE_FAST_FLOAT // fastfloat cannot parse hexadecimal floats bool isneg = (str.str[0] == '-'); csubstr rem = str.sub(isneg || str.str[0] == '+'); if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) { fast_float::from_chars_result result; result = fast_float::from_chars(str.str, str.str + str.len, *v); return result.ec == std::errc(); } else if(detail::scan_rhex(rem.sub(2), v)) { *v *= isneg ? -1. : 1.; return true; } return false; #elif C4CORE_HAVE_STD_FROMCHARS std::from_chars_result result; result = std::from_chars(str.str, str.str + str.len, *v); return result.ec == std::errc(); #else csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) return detail::scan_one(str, "lf", v) != csubstr::npos; else return detail::scan_one(str, "la", v) != csubstr::npos; #endif } /** Convert a string to a double precision real number. * Leading whitespace is skipped until valid characters are found. * @return the number of characters read from the string, or npos if * conversion was not successful or if the string was empty */ inline size_t atod_first(csubstr str, double * C4_RESTRICT v) noexcept { csubstr trimmed = str.first_real_span(); if(trimmed.len == 0) return csubstr::npos; if(atod(trimmed, v)) return static_cast(trimmed.end() - str.begin()); return csubstr::npos; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // generic versions /** @cond dev */ // on some platforms, (unsigned) int and (unsigned) long // are not any of the fixed length types above #define _C4_IF_NOT_FIXED_LENGTH_I(T, ty) typename std::enable_if::value && !is_fixed_length::value_i, ty> #define _C4_IF_NOT_FIXED_LENGTH_U(T, ty) typename std::enable_if::value && !is_fixed_length::value_u, ty> /** @endcond*/ /** @defgroup doc_xtoa xtoa: generic value to chars * * Dispatches to the most appropriate and efficient conversion * function * * @{ */ C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v) noexcept { return write_dec(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v) noexcept { return write_dec(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v) noexcept { return write_dec(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v) noexcept { return write_dec(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v) noexcept { return itoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v) noexcept { return itoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v) noexcept { return itoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v) noexcept { return itoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, float v) noexcept { return ftoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, double v) noexcept { return dtoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix) noexcept { return utoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix) noexcept { return utoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix) noexcept { return utoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix) noexcept { return utoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix) noexcept { return itoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix) noexcept { return itoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix) noexcept { return itoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix) noexcept { return itoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); } C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); } C4_ALWAYS_INLINE size_t xtoa(substr s, float v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return ftoa(s, v, precision, formatting); } C4_ALWAYS_INLINE size_t xtoa(substr s, double v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return dtoa(s, v, precision, formatting); } template C4_ALWAYS_INLINE auto xtoa(substr buf, T v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type { return itoa(buf, v); } template C4_ALWAYS_INLINE auto xtoa(substr buf, T v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type { return write_dec(buf, v); } template C4_ALWAYS_INLINE size_t xtoa(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); } /** @} */ /** @defgroup doc_atox atox: generic chars to value * * Dispatches to the most appropriate and efficient conversion * function * * @{ */ C4_ALWAYS_INLINE bool atox(csubstr s, uint8_t *C4_RESTRICT v) noexcept { return atou(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, uint16_t *C4_RESTRICT v) noexcept { return atou(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, uint32_t *C4_RESTRICT v) noexcept { return atou(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, uint64_t *C4_RESTRICT v) noexcept { return atou(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, int8_t *C4_RESTRICT v) noexcept { return atoi(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, int16_t *C4_RESTRICT v) noexcept { return atoi(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, int32_t *C4_RESTRICT v) noexcept { return atoi(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, int64_t *C4_RESTRICT v) noexcept { return atoi(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, float *C4_RESTRICT v) noexcept { return atof(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, double *C4_RESTRICT v) noexcept { return atod(s, v); } template C4_ALWAYS_INLINE auto atox(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, bool)::type { return atoi(buf, v); } template C4_ALWAYS_INLINE auto atox(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, bool)::type { return atou(buf, v); } template C4_ALWAYS_INLINE bool atox(csubstr s, T **v) noexcept { intptr_t tmp; bool ret = atox(s, &tmp); if(ret) { *v = (T*)tmp; } return ret; } /** @} */ /** @defgroup doc_to_chars to_chars: generalized chars to value * * Convert the given value, writing into the string. The resulting * string will NOT be null-terminated. Return the number of * characters needed. This function is safe to call when the string * is too small - no writes will occur beyond the string's last * character. * * Dispatches to the most appropriate and efficient conversion * function. * * @see write_dec, doc_utoa, doc_itoa, doc_ftoa, doc_dtoa * * @warning When serializing floating point values (float or double), * be aware that because it uses defaults, to_chars() may cause a * truncation of the precision. To enforce a particular precision, use * for example @ref c4::fmt::real, or call directly @ref c4::ftoa or * @ref c4::dtoa. * * @{ */ C4_ALWAYS_INLINE size_t to_chars(substr buf, uint8_t v) noexcept { return write_dec(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, uint16_t v) noexcept { return write_dec(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, uint32_t v) noexcept { return write_dec(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, uint64_t v) noexcept { return write_dec(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, int8_t v) noexcept { return itoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, int16_t v) noexcept { return itoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, int32_t v) noexcept { return itoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, int64_t v) noexcept { return itoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, float v) noexcept { return ftoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, double v) noexcept { return dtoa(buf, v); } template C4_ALWAYS_INLINE auto to_chars(substr buf, T v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type { return itoa(buf, v); } template C4_ALWAYS_INLINE auto to_chars(substr buf, T v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type { return write_dec(buf, v); } template C4_ALWAYS_INLINE size_t to_chars(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); } /** @} */ /** @defgroup doc_from_chars from_chars: generalized chars to value * * Read a value from the string, which must be trimmed to the value * (ie, no leading/trailing whitespace). return true if the * conversion succeeded. There is no check for overflow; the value * wraps around in a way similar to the standard C/C++ overflow * behavior. For example, from_chars("128", &val) returns true * and val will be set tot 0. See @ref doc_overflows and @ref * doc_overflow_checked for facilities enforcing no-overflow. * * Dispatches to the most appropriate and efficient conversion * function * * @see doc_from_chars_first, atou, atoi, atof, atod * @{ */ C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, float *C4_RESTRICT v) noexcept { return atof(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, double *C4_RESTRICT v) noexcept { return atod(buf, v); } template C4_ALWAYS_INLINE auto from_chars(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, bool)::type { return atoi(buf, v); } template C4_ALWAYS_INLINE auto from_chars(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, bool)::type { return atou(buf, v); } template C4_ALWAYS_INLINE bool from_chars(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; } /** @defgroup doc_from_chars_first from_chars_first: generalized chars to value * * Read the first valid sequence of characters from the string, * skipping leading whitespace, and convert it using @ref doc_from_chars . * Return the number of characters read for converting. * * Dispatches to the most appropriate and efficient conversion * function. * * @see atou_first, atoi_first, atof_first, atod_first * @{ */ C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, float *C4_RESTRICT v) noexcept { return atof_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, double *C4_RESTRICT v) noexcept { return atod_first(buf, v); } template C4_ALWAYS_INLINE auto from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type { return atoi_first(buf, v); } template C4_ALWAYS_INLINE auto from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept -> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type { return atou_first(buf, v); } template C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars_first(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; } /** @} */ /** @} */ #undef _C4_IF_NOT_FIXED_LENGTH_I #undef _C4_IF_NOT_FIXED_LENGTH_U //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** call to_chars() and return a substr consisting of the * written portion of the input buffer. Ie, same as to_chars(), * but return a substr instead of a size_t. * Convert the given value to a string using to_chars(), and * return the resulting string, up to and including the last * written character. * @ingroup doc_to_chars * @see to_chars() */ template C4_ALWAYS_INLINE substr to_chars_sub(substr buf, T const& C4_RESTRICT v) noexcept { size_t sz = to_chars(buf, v); return buf.left_of(sz <= buf.len ? sz : buf.len); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // bool implementation /** @ingroup doc_to_chars */ C4_ALWAYS_INLINE size_t to_chars(substr buf, bool v) noexcept { int val = v; return to_chars(buf, val); } /** @ingroup doc_from_chars */ inline bool from_chars(csubstr buf, bool * C4_RESTRICT v) noexcept { if(buf == '0') { *v = false; return true; } else if(buf == '1') { *v = true; return true; } else if(buf == "false") { *v = false; return true; } else if(buf == "true") { *v = true; return true; } else if(buf == "False") { *v = false; return true; } else if(buf == "True") { *v = true; return true; } else if(buf == "FALSE") { *v = false; return true; } else if(buf == "TRUE") { *v = true; return true; } // fallback to c-style int bools int val = 0; bool ret = from_chars(buf, &val); if(C4_LIKELY(ret)) { *v = (val != 0); } return ret; } /** @ingroup doc_from_chars_first */ inline size_t from_chars_first(csubstr buf, bool * C4_RESTRICT v) noexcept { csubstr trimmed = buf.first_non_empty_span(); if(trimmed.len == 0 || !from_chars(buf, v)) return csubstr::npos; return trimmed.len; } //----------------------------------------------------------------------------- // single-char implementation /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, char v) noexcept { if(buf.len > 0) { C4_XASSERT(buf.str); buf.str[0] = v; } return 1; } /** extract a single character from a substring * @note to extract a string instead and not just a single character, use the csubstr overload * @ingroup doc_from_chars * */ inline bool from_chars(csubstr buf, char * C4_RESTRICT v) noexcept { if(buf.len != 1) return false; C4_XASSERT(buf.str); *v = buf.str[0]; return true; } /** @ingroup doc_from_chars_first */ inline size_t from_chars_first(csubstr buf, char * C4_RESTRICT v) noexcept { if(buf.len < 1) return csubstr::npos; *v = buf.str[0]; return 1; } //----------------------------------------------------------------------------- // csubstr implementation /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, csubstr v) noexcept { C4_ASSERT(!buf.overlaps(v)); size_t len = buf.len < v.len ? buf.len : v.len; // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(len) { C4_ASSERT(buf.str != nullptr); C4_ASSERT(v.str != nullptr); memcpy(buf.str, v.str, len); } return v.len; } /** @ingroup doc_from_chars */ inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v) noexcept { *v = buf; return true; } /** @ingroup doc_from_chars_first */ inline size_t from_chars_first(substr buf, csubstr * C4_RESTRICT v) noexcept { csubstr trimmed = buf.first_non_empty_span(); if(trimmed.len == 0) return csubstr::npos; *v = trimmed; return static_cast(trimmed.end() - buf.begin()); } //----------------------------------------------------------------------------- // substr /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, substr v) noexcept { C4_ASSERT(!buf.overlaps(v)); size_t len = buf.len < v.len ? buf.len : v.len; // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(len) { C4_ASSERT(buf.str != nullptr); C4_ASSERT(v.str != nullptr); memcpy(buf.str, v.str, len); } return v.len; } /** @ingroup doc_from_chars */ inline bool from_chars(csubstr buf, substr * C4_RESTRICT v) noexcept { C4_ASSERT(!buf.overlaps(*v)); // is the destination buffer wide enough? if(v->len >= buf.len) { // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(buf.len) { C4_ASSERT(buf.str != nullptr); C4_ASSERT(v->str != nullptr); memcpy(v->str, buf.str, buf.len); } v->len = buf.len; return true; } return false; } /** @ingroup doc_from_chars_first */ inline size_t from_chars_first(csubstr buf, substr * C4_RESTRICT v) noexcept { csubstr trimmed = buf.first_non_empty_span(); C4_ASSERT(!trimmed.overlaps(*v)); if(C4_UNLIKELY(trimmed.len == 0)) return csubstr::npos; size_t len = trimmed.len > v->len ? v->len : trimmed.len; // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(len) { C4_ASSERT(buf.str != nullptr); C4_ASSERT(v->str != nullptr); memcpy(v->str, trimmed.str, len); } if(C4_UNLIKELY(trimmed.len > v->len)) return csubstr::npos; return static_cast(trimmed.end() - buf.begin()); } //----------------------------------------------------------------------------- /** @ingroup doc_to_chars */ template inline size_t to_chars(substr buf, const char (& C4_RESTRICT v)[N]) noexcept { csubstr sp(v); return to_chars(buf, sp); } /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, const char * C4_RESTRICT v) noexcept { return to_chars(buf, to_csubstr(v)); } /** @} */ } // namespace c4 // NOLINTEND(hicpp-signed-bitwise) #if defined(_MSC_VER) && !defined(__clang__) # pragma warning(pop) #elif defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif /* _C4_CHARCONV_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/charconv.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/utf.hpp // https://github.com/biojppm/c4core/src/c4/utf.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef C4_UTF_HPP_ #define C4_UTF_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/language.hpp //#include "c4/language.hpp" #if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) #error "amalgamate: file c4/language.hpp must have been included at this point" #endif /* C4_LANGUAGE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/substr_fwd.hpp //#include "c4/substr_fwd.hpp" #if !defined(C4_SUBSTR_FWD_HPP_) && !defined(_C4_SUBSTR_FWD_HPP_) #error "amalgamate: file c4/substr_fwd.hpp must have been included at this point" #endif /* C4_SUBSTR_FWD_HPP_ */ //included above: //#include //included above: //#include /** @file utf.hpp utilities for UTF and Byte Order Mark */ namespace c4 { /** @defgroup doc_utf UTF utilities * @{ */ /** skip the Byte Order Mark, or get the full string if there is Byte Order Mark. * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ C4CORE_EXPORT substr skip_bom(substr s); /** skip the Byte Order Mark, or get the full string if there is Byte Order Mark * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ C4CORE_EXPORT csubstr skip_bom(csubstr s); /** get the Byte Order Mark, or an empty string if there is no Byte Order Mark * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ C4CORE_EXPORT substr get_bom(substr s); /** get the Byte Order Mark, or an empty string if there is no Byte Order Mark * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ C4CORE_EXPORT csubstr get_bom(csubstr s); /** return the position of the first character not belonging to the * Byte Order Mark, or 0 if there is no Byte Order Mark. * @see Implements the Byte Order Marks as described in https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding */ C4CORE_EXPORT size_t first_non_bom(csubstr s); /** decode the given @p code_point, writing into the output string in * @p out. * * @param out the output string. must have at least 4 bytes (this is * asserted), and must not have a null string. * * @param code_point: must have length in ]0,8], and must not begin * with any of `U+`,`\\x`,`\\u,`\\U`,`0` (asserted) * * @return the part of @p out that was written, which will always be * at most 4 bytes. */ C4CORE_EXPORT substr decode_code_point(substr out, csubstr code_point); /** decode the given @p code point, writing into the output string @p * buf, of size @p buflen * * @param buf the output string. must have at least 4 bytes (this is * asserted), and must not be null * * @param buflen the length of the output string. must be at least 4 * * @param code: the code point must have length in ]0,8], and must not begin * with any of `U+`,`\\x`,`\\u,`\\U`,`0` (asserted) * * @return the part of @p out that was written, which will always be * at most 4 bytes. */ size_t decode_code_point(uint8_t *C4_RESTRICT buf, size_t buflen, uint32_t code); /** @} */ } // namespace c4 #endif // C4_UTF_HPP_ // (end https://github.com/biojppm/c4core/src/c4/utf.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/format.hpp // https://github.com/biojppm/c4core/src/c4/format.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_FORMAT_HPP_ #define _C4_FORMAT_HPP_ /** @file format.hpp provides type-safe facilities for formatting arguments * to string buffers */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/charconv.hpp //#include "c4/charconv.hpp" #if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) #error "amalgamate: file c4/charconv.hpp must have been included at this point" #endif /* C4_CHARCONV_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/blob.hpp //#include "c4/blob.hpp" #if !defined(C4_BLOB_HPP_) && !defined(_C4_BLOB_HPP_) #error "amalgamate: file c4/blob.hpp must have been included at this point" #endif /* C4_BLOB_HPP_ */ #if defined(_MSC_VER) && !defined(__clang__) # pragma warning(push) # if C4_MSVC_VERSION != C4_MSVC_VERSION_2017 # pragma warning(disable: 4800) // forcing value to bool 'true' or 'false' (performance warning) # endif # pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe #elif defined(__clang__) # pragma clang diagnostic push #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wuseless-cast" #endif // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast,*avoid-goto*) /** @defgroup doc_format_utils Format utilities * * @brief Provides generic and type-safe formatting/scanning utilities * built on top of @ref doc_to_chars() and @ref doc_from_chars, * forwarding the arguments to these functions, which in turn use the * @ref doc_charconv utilities. Like @ref doc_charconv, the formatting * facilities are very efficient and many times faster than printf(). * * @see [a formatting sample in rapidyaml's docs](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#gac2425b515eb552589708cfff70c52b14) * */ /** @defgroup doc_format_specifiers Format specifiers * * @brief Format specifiers are tag types and functions that are used * together with @ref doc_to_chars and @ref doc_from_chars * * @see [a formatting sample in rapidyaml's docs](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#gac2425b515eb552589708cfff70c52b14) * @ingroup doc_format_utils */ namespace c4 { /** @addtogroup doc_format_utils * @{ */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // formatting truthy types as booleans namespace fmt { /** @addtogroup doc_format_specifiers * @{ */ /** @defgroup doc_boolean_specifiers boolean specifiers * @{ */ /** write a variable as an alphabetic boolean, ie as either true or false * @param strict_read */ template struct boolalpha_ { boolalpha_(T val_, bool strict_read_=false) : val(val_ ? true : false), strict_read(strict_read_) {} bool val; bool strict_read; }; template boolalpha_ boolalpha(T const& val, bool strict_read=false) { return boolalpha_(val, strict_read); } /** @} */ /** @} */ } // namespace fmt /** write a variable as an alphabetic boolean, ie as either true or * false * @ingroup doc_to_chars */ template inline size_t to_chars(substr buf, fmt::boolalpha_ fmt) { return to_chars(buf, fmt.val ? "true" : "false"); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // formatting integral types namespace fmt { /** @addtogroup doc_format_specifiers * @{ */ /** @defgroup doc_integer_specifiers Integer specifiers * @{ */ /** format an integral type with a custom radix */ template struct integral_ { C4_STATIC_ASSERT(std::is_integral::value); T val; T radix; C4_ALWAYS_INLINE integral_(T val_, T radix_) : val(val_), radix(radix_) {} }; /** format an integral type with a custom radix, and pad with zeroes on the left */ template struct integral_padded_ { C4_STATIC_ASSERT(std::is_integral::value); T val; T radix; size_t num_digits; C4_ALWAYS_INLINE integral_padded_(T val_, T radix_, size_t nd) : val(val_), radix(radix_), num_digits(nd) {} }; /** format an integral type with a custom radix */ template C4_ALWAYS_INLINE integral_ integral(T val, T radix=10) { return integral_(val, radix); } /** format an integral type with a custom radix */ template C4_ALWAYS_INLINE integral_ integral(T const* val, T radix=10) { return integral_(reinterpret_cast(val), static_cast(radix)); } /** format an integral type with a custom radix */ template C4_ALWAYS_INLINE integral_ integral(std::nullptr_t, T radix=10) { return integral_(intptr_t(0), static_cast(radix)); } /** format the pointer as an hexadecimal value */ template inline integral_ hex(T * v) { return integral_(reinterpret_cast(v), intptr_t(16)); } /** format the pointer as an hexadecimal value */ template inline integral_ hex(T const* v) { return integral_(reinterpret_cast(v), intptr_t(16)); } /** format null as an hexadecimal value * @overload hex */ inline integral_ hex(std::nullptr_t) { return integral_(0, intptr_t(16)); } /** format the integral_ argument as an hexadecimal value * @overload hex */ template inline integral_ hex(T v) { return integral_(v, T(16)); } /** format the pointer as an octal value */ template inline integral_ oct(T const* v) { return integral_(reinterpret_cast(v), intptr_t(8)); } /** format the pointer as an octal value */ template inline integral_ oct(T * v) { return integral_(reinterpret_cast(v), intptr_t(8)); } /** format null as an octal value */ inline integral_ oct(std::nullptr_t) { return integral_(intptr_t(0), intptr_t(8)); } /** format the integral_ argument as an octal value */ template inline integral_ oct(T v) { return integral_(v, T(8)); } /** format the pointer as a binary 0-1 value * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ template inline integral_ bin(T const* v) { return integral_(reinterpret_cast(v), intptr_t(2)); } /** format the pointer as a binary 0-1 value * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ template inline integral_ bin(T * v) { return integral_(reinterpret_cast(v), intptr_t(2)); } /** format null as a binary 0-1 value * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ inline integral_ bin(std::nullptr_t) { return integral_(intptr_t(0), intptr_t(2)); } /** format the integral_ argument as a binary 0-1 value * @see c4::raw() if you want to use a raw memcpy-based binary dump instead of 0-1 formatting */ template inline integral_ bin(T v) { return integral_(v, T(2)); } /** @} */ // integer_specifiers /** @defgroup doc_zpad Pad the number with zeroes on the left * @{ */ /** pad the argument with zeroes on the left, with decimal radix */ template C4_ALWAYS_INLINE integral_padded_ zpad(T val, size_t num_digits) { return integral_padded_(val, T(10), num_digits); } /** pad the argument with zeroes on the left */ template C4_ALWAYS_INLINE integral_padded_ zpad(integral_ val, size_t num_digits) { return integral_padded_(val.val, val.radix, num_digits); } /** pad the argument with zeroes on the left */ C4_ALWAYS_INLINE integral_padded_ zpad(std::nullptr_t, size_t num_digits) { return integral_padded_(0, 16, num_digits); } /** pad the argument with zeroes on the left */ template C4_ALWAYS_INLINE integral_padded_ zpad(T const* val, size_t num_digits) { return integral_padded_(reinterpret_cast(val), 16, num_digits); } template C4_ALWAYS_INLINE integral_padded_ zpad(T * val, size_t num_digits) { return integral_padded_(reinterpret_cast(val), 16, num_digits); } /** @} */ // zpad /** @defgroup doc_overflow_checked Check read for overflow * @{ */ template struct overflow_checked_ { static_assert(std::is_integral::value, "range checking only for integral types"); C4_ALWAYS_INLINE overflow_checked_(T &val_) : val(&val_) {} T *val; }; template C4_ALWAYS_INLINE overflow_checked_ overflow_checked(T &val) { return overflow_checked_(val); } /** @} */ // overflow_checked /** @} */ // format_specifiers } // namespace fmt /** format an integer signed type * @ingroup doc_to_chars */ template C4_ALWAYS_INLINE typename std::enable_if::value, size_t>::type to_chars(substr buf, fmt::integral_ fmt) { return itoa(buf, fmt.val, fmt.radix); } /** format an integer signed type, pad with zeroes * @ingroup doc_to_chars */ template C4_ALWAYS_INLINE typename std::enable_if::value, size_t>::type to_chars(substr buf, fmt::integral_padded_ fmt) { return itoa(buf, fmt.val, fmt.radix, fmt.num_digits); } /** format an integer unsigned type * @ingroup doc_to_chars */ template C4_ALWAYS_INLINE typename std::enable_if::value, size_t>::type to_chars(substr buf, fmt::integral_ fmt) { return utoa(buf, fmt.val, fmt.radix); } /** format an integer unsigned type, pad with zeroes * @ingroup doc_to_chars */ template C4_ALWAYS_INLINE typename std::enable_if::value, size_t>::type to_chars(substr buf, fmt::integral_padded_ fmt) { return utoa(buf, fmt.val, fmt.radix, fmt.num_digits); } /** read an integer type, detecting overflow (returns false on overflow) * @ingroup doc_from_chars */ template C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_ wrapper) { if(C4_LIKELY(!overflows(s))) return atox(s, wrapper.val); return false; } /** read an integer type, detecting overflow (returns false on overflow) * @ingroup doc_from_chars */ template C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_ *wrapper) { if(C4_LIKELY(!overflows(s))) return atox(s, wrapper->val); return false; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // formatting real types namespace fmt { /** @addtogroup doc_format_specifiers * @{ */ /** @defgroup doc_real_specifiers Real specifiers * @{ */ template struct real_ { T val; int precision; RealFormat_e fmt; real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f) {} }; template real_ real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT) { return real_(val, precision, fmt); } /** @} */ // real_specifiers /** @} */ // format_specifiers } // namespace fmt /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); } /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, fmt::real_ fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // writing raw binary data namespace fmt { /** @addtogroup doc_format_specifiers * @{ */ /** @defgroup doc_raw_binary_specifiers Raw binary data * @{ */ /** @see blob_ */ template struct raw_wrapper_ : public blob_ { size_t alignment; C4_ALWAYS_INLINE raw_wrapper_(blob_ data, size_t alignment_) noexcept : blob_(data), alignment(alignment_) { C4_ASSERT_MSG(alignment > 0 && (alignment & (alignment - 1)) == 0, "alignment must be a power of two"); } }; using const_raw_wrapper = raw_wrapper_; using raw_wrapper = raw_wrapper_; /** mark a variable to be written in raw binary format, using memcpy * @see blob_ */ inline const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t)) { return const_raw_wrapper(data, alignment); } /** mark a variable to be written in raw binary format, using memcpy * @see blob_ */ inline const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t)) { return const_raw_wrapper(data, alignment); } /** mark a variable to be written in raw binary format, using memcpy * @see blob_ */ template inline const_raw_wrapper craw(T const& C4_RESTRICT data, size_t alignment=alignof(T)) { return const_raw_wrapper(cblob(data), alignment); } /** mark a variable to be written in raw binary format, using memcpy * @see blob_ */ template inline const_raw_wrapper raw(T const& C4_RESTRICT data, size_t alignment=alignof(T)) { return const_raw_wrapper(cblob(data), alignment); } /** mark a variable to be read in raw binary format, using memcpy */ inline raw_wrapper raw(blob data, size_t alignment=alignof(max_align_t)) { return raw_wrapper(data, alignment); } /** mark a variable to be read in raw binary format, using memcpy */ template inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T)) { return raw_wrapper(blob(data), alignment); } /** @} */ // raw_binary_specifiers /** @} */ // format_specifiers } // namespace fmt /** write a variable in raw binary format, using memcpy * @ingroup doc_to_chars */ C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r); /** read a variable in raw binary format, using memcpy * @ingroup doc_from_chars */ C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r); /** read a variable in raw binary format, using memcpy * @ingroup doc_from_chars */ inline bool from_chars(csubstr buf, fmt::raw_wrapper r) { return from_chars(buf, &r); } /** read a variable in raw binary format, using memcpy * @ingroup doc_from_chars_first */ inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper *r) { return from_chars(buf, r); } /** read a variable in raw binary format, using memcpy * @ingroup doc_from_chars_first */ inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper r) { return from_chars(buf, &r); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // formatting aligned to left/right namespace fmt { /** @addtogroup doc_format_specifiers * @{ */ /** @defgroup doc_alignment_specifiers Alignment specifiers * @{ */ template struct left_ { T val; size_t width; char pad; left_(T v, size_t w, char p) : val(v), width(w), pad(p) {} }; template struct right_ { T val; size_t width; char pad; right_(T v, size_t w, char p) : val(v), width(w), pad(p) {} }; /** mark an argument to be aligned left */ template left_ left(T val, size_t width, char padchar=' ') { return left_(val, width, padchar); } /** mark an argument to be aligned right */ template right_ right(T val, size_t width, char padchar=' ') { return right_(val, width, padchar); } /** @} */ // alignment_specifiers /** @} */ // format_specifiers } // namespace fmt /** @ingroup doc_to_chars */ template size_t to_chars(substr buf, fmt::left_ const& C4_RESTRICT align) { size_t ret = to_chars(buf, align.val); if(ret >= buf.len || ret >= align.width) return ret > align.width ? ret : align.width; buf.first(align.width).sub(ret).fill(align.pad); to_chars(buf, align.val); return align.width; } /** @ingroup doc_to_chars */ template size_t to_chars(substr buf, fmt::right_ const& C4_RESTRICT align) { size_t ret = to_chars(buf, align.val); if(ret >= buf.len || ret >= align.width) return ret > align.width ? ret : align.width; size_t rem = static_cast(align.width - ret); buf.first(rem).fill(align.pad); to_chars(buf.sub(rem), align.val); return align.width; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_cat cat: concatenate arguments to string * @{ */ /** @cond dev */ // terminates the variadic recursion inline size_t cat(substr /*buf*/) { return 0; } /** @endcond */ /** serialize the arguments, concatenating them to the given fixed-size buffer. * The buffer size is strictly respected: no writes will occur beyond its end. * @return the number of characters needed to write all the arguments into the buffer. * @see c4::catrs() if instead of a fixed-size buffer, a resizeable container is desired * @see c4::uncat() for the inverse function * @see c4::catsep() if a separator between each argument is to be used * @see c4::format() if a format string is desired */ template size_t cat(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) { size_t num = to_chars(buf, a); buf = buf.len >= num ? buf.sub(num) : substr{}; num += cat(buf, more...); return num; } /** like c4::cat() but return a substr instead of a size */ template substr cat_sub(substr buf, Args && ...args) { size_t sz = cat(buf, std::forward(args)...); C4_CHECK(sz <= buf.len); return {buf.str, sz <= buf.len ? sz : buf.len}; } /** @} */ //----------------------------------------------------------------------------- /** @defgroup doc_uncat uncat: read concatenated arguments from string * @{ */ /** @cond dev */ // terminates the variadic recursion inline size_t uncat(csubstr /*buf*/) { return 0; } /** @endcond */ /** deserialize the arguments from the given buffer. * * @return the number of characters read from the buffer, or csubstr::npos * if a conversion was not successful. * @see c4::cat(). c4::uncat() is the inverse of c4::cat(). */ template size_t uncat(csubstr buf, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) { size_t out = from_chars_first(buf, &a); if(C4_UNLIKELY(out == csubstr::npos)) return csubstr::npos; buf = buf.len >= out ? buf.sub(out) : substr{}; size_t num = uncat(buf, more...); if(C4_UNLIKELY(num == csubstr::npos)) return csubstr::npos; return out + num; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_catsep catsep: cat arguments to string with separator * @{ */ /** @cond dev */ namespace detail { template C4_ALWAYS_INLINE size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/) { return 0; } template size_t catsep_more(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) { size_t ret = to_chars(buf, sep); size_t num = ret; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = to_chars(buf, a); num += ret; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = catsep_more(buf, sep, more...); num += ret; return num; } template inline size_t uncatsep_more(csubstr /*buf*/, Sep & /*sep*/) { return 0; } template size_t uncatsep_more(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) { size_t ret = from_chars_first(buf, &sep); size_t num = ret; if(C4_UNLIKELY(ret == csubstr::npos)) return csubstr::npos; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = from_chars_first(buf, &a); if(C4_UNLIKELY(ret == csubstr::npos)) return csubstr::npos; num += ret; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = uncatsep_more(buf, sep, more...); if(C4_UNLIKELY(ret == csubstr::npos)) return csubstr::npos; num += ret; return num; } } // namespace detail template size_t catsep(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/) { return 0; } /** @endcond */ /** serialize the arguments, concatenating them to the given fixed-size * buffer, using a separator between each argument. * The buffer size is strictly respected: no writes will occur beyond its end. * @return the number of characters needed to write all the arguments into the buffer. * @see c4::catseprs() if instead of a fixed-size buffer, a resizeable container is desired * @see c4::uncatsep() for the inverse function (ie, reading instead of writing) * @see c4::cat() if no separator is needed * @see c4::format() if a format string is desired */ template size_t catsep(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) { size_t num = to_chars(buf, a); buf = buf.len >= num ? buf.sub(num) : substr{}; num += detail::catsep_more(buf, sep, more...); return num; } /** like c4::catsep() but return a substr instead of a size * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */ template substr catsep_sub(substr buf, Args && ...args) { size_t sz = catsep(buf, std::forward(args)...); C4_CHECK(sz <= buf.len); return {buf.str, sz <= buf.len ? sz : buf.len}; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_uncatsep uncatsep: deserialize the separated arguments from a string * @{ */ /** deserialize the arguments from the given buffer. * * @return the number of characters read from the buffer, or csubstr::npos * if a conversion was not successful. * @see c4::cat(). c4::uncat() is the inverse of c4::cat(). */ /** deserialize the arguments from the given buffer, using a separator. * * @return the number of characters read from the buffer, or csubstr::npos * if a conversion was not successful * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */ template size_t uncatsep(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) { size_t ret = from_chars_first(buf, &a), num = ret; if(C4_UNLIKELY(ret == csubstr::npos)) return csubstr::npos; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = detail::uncatsep_more(buf, sep, more...); if(C4_UNLIKELY(ret == csubstr::npos)) return csubstr::npos; num += ret; return num; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_format format: formatted string interpolation * @{ */ /// @cond dev // terminates the variadic recursion inline size_t format(substr buf, csubstr fmt) { return to_chars(buf, fmt); } /// @endcond /** using a format string, serialize the arguments into the given * fixed-size buffer. * The buffer size is strictly respected: no writes will occur beyond its end. * In the format string, each argument is marked with a compact * curly-bracket pair: {}. Arguments beyond the last curly bracket pair * are silently ignored. For example: * @code{.cpp} * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers"); // the partier drank 5 beers * c4::format(buf, "the {} drank {} {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees * @endcode * @return the number of characters needed to write into the buffer. * @see c4::formatrs() if instead of a fixed-size buffer, a resizeable container is desired * @see c4::unformat() for the inverse function * @see c4::cat() if no format or separator is needed * @see c4::catsep() if no format is needed, but a separator must be used */ template size_t format(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) { size_t pos = fmt.find("{}"); // @todo use _find_fmt() if(C4_UNLIKELY(pos == csubstr::npos)) return to_chars(buf, fmt); size_t num = to_chars(buf, fmt.sub(0, pos)); size_t out = num; buf = buf.len >= num ? buf.sub(num) : substr{}; num = to_chars(buf, a); out += num; buf = buf.len >= num ? buf.sub(num) : substr{}; num = format(buf, fmt.sub(pos + 2), more...); out += num; return out; } /** like c4::format() but return a substr instead of a size * @see c4::format() * @see c4::catsep(). uncatsep() is the inverse of catsep(). */ template substr format_sub(substr buf, csubstr fmt, Args const& C4_RESTRICT ...args) { size_t sz = c4::format(buf, fmt, args...); C4_CHECK(sz <= buf.len); return {buf.str, sz <= buf.len ? sz : buf.len}; } /** @} */ //----------------------------------------------------------------------------- /** @defgroup doc_unformat unformat: formatted read from string * @{ */ /// @cond dev // terminates the variadic recursion inline size_t unformat(csubstr /*buf*/, csubstr fmt) { return fmt.len; } /// @endcond /** using a format string, deserialize the arguments from the given * buffer. * @return the number of characters read from the buffer, or npos if a conversion failed. * @see c4::format(). c4::unformat() is the inverse function to format(). */ template size_t unformat(csubstr buf, csubstr fmt, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) { const size_t pos = fmt.find("{}"); if(C4_UNLIKELY(pos == csubstr::npos)) return unformat(buf, fmt); size_t num = pos; size_t out = num; buf = buf.len >= num ? buf.sub(num) : substr{}; num = from_chars_first(buf, &a); if(C4_UNLIKELY(num == csubstr::npos)) return csubstr::npos; out += num; buf = buf.len >= num ? buf.sub(num) : substr{}; num = unformat(buf, fmt.sub(pos + 2), more...); if(C4_UNLIKELY(num == csubstr::npos)) return csubstr::npos; out += num; return out; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** cat+resize: like c4::cat(), but receives a container, and resizes * it as needed to contain the result. The container is * overwritten. To append to it, use the append overload. * @see c4::cat() * @ingroup doc_cat */ template inline void catrs(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args) { retry: substr buf = to_substr(*cont); size_t ret = cat(buf, args...); cont->resize(ret); if(ret > buf.len) goto retry; } /** cat+resize: like c4::cat(), but creates and returns a new * container sized as needed to contain the result. * @see c4::cat() * @ingroup doc_cat */ template inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args) { CharOwningContainer cont; catrs(&cont, args...); return cont; } /** cat+resize+append: like c4::cat(), but receives a container, and * appends to it instead of overwriting it. The container is resized * as needed to contain the result. * * @return the region newly appended to the original container * @see c4::cat() * @see c4::catrs() * @ingroup doc_cat */ template inline csubstr catrs_append(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args) { const size_t pos = cont->size(); retry: substr buf = to_substr(*cont).sub(pos); size_t ret = cat(buf, args...); cont->resize(pos + ret); if(ret > buf.len) goto retry; return to_csubstr(*cont).range(pos, cont->size()); } //----------------------------------------------------------------------------- /** catsep+resize: like c4::catsep(), but receives a container, and * resizes it as needed to contain the result. The container is * overwritten. To append to the container use the append overload. * * @see c4::catsep() * @ingroup doc_catsep */ template inline void catseprs(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) { retry: substr buf = to_substr(*cont); size_t ret = catsep(buf, sep, args...); cont->resize(ret); if(ret > buf.len) goto retry; } /** catsep+resize: like c4::catsep(), but create a new container with * the result. * * @return the requested container * @ingroup doc_catsep */ template inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) { CharOwningContainer cont; catseprs(&cont, sep, args...); return cont; } /** catsep+resize+append: like catsep(), but receives a container, and * appends the arguments, resizing the container as needed to contain * the result. The buffer is appended to. * * @return a csubstr of the appended part * @ingroup doc_catsep */ template inline csubstr catseprs_append(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) { const size_t pos = cont->size(); retry: substr buf = to_substr(*cont).sub(pos); size_t ret = catsep(buf, sep, args...); cont->resize(pos + ret); if(ret > buf.len) goto retry; return to_csubstr(*cont).range(pos, cont->size()); } //----------------------------------------------------------------------------- /** format+resize: like c4::format(), but receives a container, and * resizes it as needed to contain the result. The container is * overwritten. To append to the container use the append overload. * * @see c4::format() * @ingroup doc_format */ template inline void formatrs(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args) { retry: substr buf = to_substr(*cont); size_t ret = format(buf, fmt, args...); cont->resize(ret); if(ret > buf.len) goto retry; } /** format+resize: like c4::format(), but create a new container with * the result. * * @return the requested container * @ingroup doc_format */ template inline CharOwningContainer formatrs(csubstr fmt, Args const& C4_RESTRICT ...args) { CharOwningContainer cont; formatrs(&cont, fmt, args...); return cont; } /** format+resize+append: like format(), but receives a container, and appends the * arguments, resizing the container as needed to contain the * result. The buffer is appended to. * @return the region newly appended to the original container * @ingroup doc_format */ template inline csubstr formatrs_append(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args) { const size_t pos = cont->size(); retry: substr buf = to_substr(*cont).sub(pos); size_t ret = format(buf, fmt, args...); cont->resize(pos + ret); if(ret > buf.len) goto retry; return to_csubstr(*cont).range(pos, cont->size()); } /** @} */ } // namespace c4 // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast,*avoid-goto*) #ifdef _MSC_VER # pragma warning(pop) #elif defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif /* _C4_FORMAT_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/format.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/dump.hpp // https://github.com/biojppm/c4core/src/c4/dump.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef C4_DUMP_HPP_ #define C4_DUMP_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/substr.hpp //#include #if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) #error "amalgamate: file c4/substr.hpp must have been included at this point" #endif /* C4_SUBSTR_HPP_ */ /** @file dump.hpp This file provides functions to dump several * arguments as strings to a user-provided function sink, for example * to implement a type-safe printf()-like function (where the sink * would just be a plain call to putchars()). The function sink can be * passed either by dynamic dispatching or by static dispatching (as a * template argument). There are analogs to @ref c4::cat() (@ref * c4::cat_dump() and @ref c4::cat_dump_resume()), @ref c4::catsep() * (@ref catsetp_dump() and @ref catsep_dump_resume()) and @ref * c4::format() (@ref c4::format_dump() and @ref * c4::format_dump_resume()). The analogs have two types: immediate * and resuming. An analog of immediate type cannot be retried when * the work buffer is too small; this means that successful dumps in * the first (successful) arguments will be dumped again in the * subsequent attempt to call. An analog of resuming type will only * ever dump as-yet-undumped arguments, through the use of @ref * DumpResults return type. */ namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup dump_building_blocks Basic building blocks for dumping. * * The basic building block: given an argument and a * buffer, serialize the argument to the buffer using @ref * c4::to_chars(), and dump the buffer to the provided sink * function. When the argument is a string, no serialization is * performed, and the argument is dumped directly to the sink. * * @{ */ /** Type of the function to be used as the sink. This function * receives as its argument the string with characters to send to the * sink. * * @warning the string passed to the sink may have zero length. If the * user sink uses memcpy(), the call to memcpy() should be defended * with a check for zero length (calling memcpy with zero length is * undefined behavior). * */ using SinkPfn = void (*)(csubstr str); /** a traits class to use in SFINAE with @ref c4::dump() to select if * a type is treated as string type (which is dumped directly to the * sink, using to_csubstr()), or if the type is treated as a value, * which is first serialized to a buffer using to_chars(), and then * the serialization serialized as */ template struct dump_directly : public std::false_type {}; template<> struct dump_directly : public std::true_type {}; template<> struct dump_directly< substr> : public std::true_type {}; template<> struct dump_directly : public std::true_type {}; template<> struct dump_directly< char*> : public std::true_type {}; template struct dump_directly : public std::true_type {}; template struct dump_directly< char (&)[N]> : public std::true_type {}; template struct dump_directly : public std::true_type {}; template struct dump_directly< char[N]> : public std::true_type {}; /** Dump a string-type object to the (statically dispatched) sink. The * string is dumped directly, without any intermediate serialization. * * @return the number of bytes needed to serialize the string-type * object, which is always 0 because there is no serialization * * @note the argument is considered a value when @ref * dump_directly is a false type, which is the default. To enable * the argument to be treated as a string type, which is dumped * directly to the sink without intermediate serialization, define * dump_directly to a true type. * * @warning the string passed to the sink may have zero length. If the * user sink uses memcpy(), the call to memcpy() should be defended * with a check for zero length (calling memcpy with zero length is * undefined behavior). * * @see dump_directly */ template inline auto dump(substr buf, Arg const& a) -> typename std::enable_if::value, size_t>::type { C4_ASSERT(!buf.overlaps(a)); C4_UNUSED(buf); // dump directly, no need to serialize to the buffer sinkfn(to_csubstr(a)); return 0; // no space was used in the buffer } /** Dump a string-type object to the (dynamically dispatched) * sink. The string is dumped directly, without any intermediate * serialization to the buffer. * * @return the number of bytes needed to serialize the string-type * object, which is always 0 because there is no serialization * * @note the argument is considered a value when @ref * dump_directly is a false type, which is the default. To enable * the argument to be treated as a string type, which is dumped * directly to the sink without intermediate serialization, define * dump_directly to a true type. * * @warning the string passed to the sink may have zero length. If the * user sink uses memcpy(), the call to memcpy() should be defended * with a check for zero length (calling memcpy with zero length is * undefined behavior). * * @see dump_directly * */ template inline auto dump(SinkFn &&sinkfn, substr buf, Arg const& a) -> typename std::enable_if::value, size_t>::type { C4_UNUSED(buf); C4_ASSERT(!buf.overlaps(a)); // dump directly, no need to serialize to the buffer std::forward(sinkfn)(to_csubstr(a)); return 0; // no space was used in the buffer } /** Dump a value to the sink. Given an argument @p a and a buffer @p * buf, serialize the argument to the buffer using @ref to_chars(), * and then dump the buffer to the (statically dispatched) sink * function passed as the template argument. If the buffer is too * small to serialize the argument, the sink function is not called. * * @note the argument is considered a value when @ref * dump_directly is a false type, which is the default. To enable * the argument to be treated as a string type, which is dumped * directly to the sink without intermediate serialization, define * dump_directly to a true type. * * @see dump_directly * * @return the number of characters required to serialize the * argument. */ template inline auto dump(substr buf, Arg const& a) -> typename std::enable_if::value, size_t>::type { // serialize to the buffer const size_t sz = to_chars(buf, a); // dump the buffer to the sink if(C4_LIKELY(sz <= buf.len)) { // NOTE: don't do this: //sinkfn(buf.first(sz)); // ... but do this instead: sinkfn({buf.str, sz}); // ... this is needed because Release builds for armv5 and // armv6 were failing for the first call, with the wrong // buffer being passed into the function (!) } return sz; } /** Dump a value to the sink. Given an argument @p a and a buffer @p * buf, serialize the argument to the buffer using @ref * c4::to_chars(), and then dump the buffer to the (dynamically * dispatched) sink function, passed as @p sinkfn. If the buffer is too * small to serialize the argument, the sink function is not called. * * @note the argument is considered a value when @ref * dump_directly is a false type, which is the default. To enable * the argument to be treated as a string type, which is dumped * directly to the sink without intermediate serialization, define * dump_directly to a true type. * * @see @ref dump_directly * * @return the number of characters required to serialize the * argument. */ template inline auto dump(SinkFn &&sinkfn, substr buf, Arg const& a) -> typename std::enable_if::value, size_t>::type { // serialize to the buffer const size_t sz = to_chars(buf, a); // dump the buffer to the sink if(C4_LIKELY(sz <= buf.len)) { // NOTE: don't do this: //std::forward(sinkfn)(buf.first(sz)); // ... but do this instead: std::forward(sinkfn)({buf.str, sz}); // ... this is needed because Release builds for armv5 and // armv6 were failing for the first call, with the wrong // buffer being passed into the function (!) } return sz; } /** An opaque type used by resumeable dump functions like @ref * cat_dump_resume(), @ref catsep_dump_resume() or @ref * format_dump_resume(). */ struct DumpResults { enum : size_t { noarg = (size_t)-1 }; size_t bufsize = 0; size_t lastok = noarg; bool success_until(size_t expected) const { return lastok == noarg ? false : lastok >= expected; } bool write_arg(size_t arg) const { return lastok == noarg || arg > lastok; } size_t argfail() const { return lastok + 1; } }; /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup cat_dump Dump several arguments to a sink, * concatenated. This is the analog to @ref c4::cat(), with the * significant difference that each argument is immediately sent to * the sink (resulting in multiple calls to the sink function, once * per argument), whereas equivalent usage of c4::cat() would first * serialize all the arguments to the buffer, and then call the sink * once at the end. As a consequence, the size needed for the buffer * is only the maximum of the size needed for the arguments, whereas * with c4::cat(), the size needed for the buffer would be the sum of * the size needed for the arguments. When the size of dump * * @{ */ /// @cond dev // terminates the variadic recursion template size_t cat_dump(SinkFn &&, substr) // NOLINT { return 0; } // terminates the variadic recursion template size_t cat_dump(substr) // NOLINT { return 0; } /// @endcond /** Dump several arguments to the (dynamically dispatched) sink * function, as if through c4::cat(). For each argument, @ref dump() * is called with the buffer and sink. If any of the arguments is too * large for the buffer, no subsequent argument is sent to the sink, * (but all the arguments are still processed to compute the size * required for the buffer). This function can be safely called with an * empty buffer. * * @return the size required for the buffer, which is the maximum size * across all arguments * * @note subsequent calls with the same set of arguments will dump * again the first successful arguments. If each argument must only be * sent once to the sink (for example with printf-like behavior), use * instead @ref cat_dump_resume(). */ template size_t cat_dump(SinkFn &&sinkfn, substr buf, Arg const& a, Args const& ...more) { const size_t size_for_a = dump(std::forward(sinkfn), buf, a); if(C4_UNLIKELY(size_for_a > buf.len)) buf.len = 0; // ensure no more calls to the sink const size_t size_for_more = cat_dump(std::forward(sinkfn), buf, more...); return size_for_more > size_for_a ? size_for_more : size_for_a; } /** Dump several arguments to the (statically dispatched) sink * function, as if through c4::cat(). For each argument, @ref dump() * is called with the buffer and sink. If any of the arguments is too * large for the buffer, no subsequent argument is sent to the sink, * (but all the arguments are still processed to compute the size * required for the buffer). This function can be safely called with an * empty buffer. * * @return the size required for the buffer, which is the maximum size * across all arguments * * @note subsequent calls with the same set of arguments will dump * again the first successful arguments. If each argument must only be * sent once to the sink (for example with printf-like behavior), use * instead @ref cat_dump_resume(). */ template size_t cat_dump(substr buf, Arg const& a, Args const& ...more) { const size_t size_for_a = dump(buf, a); if(C4_UNLIKELY(size_for_a > buf.len)) buf.len = 0; // ensure no more calls to the sink const size_t size_for_more = cat_dump(buf, more...); return size_for_more > size_for_a ? size_for_more : size_for_a; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /// @cond dev namespace detail { // terminates the variadic recursion template C4_ALWAYS_INLINE DumpResults cat_dump_resume(size_t, DumpResults results, substr) { return results; } // terminates the variadic recursion template C4_ALWAYS_INLINE DumpResults cat_dump_resume(size_t, SinkFn &&, DumpResults results, substr) // NOLINT { return results; } template DumpResults cat_dump_resume(size_t currarg, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) { if(C4_LIKELY(results.write_arg(currarg))) { size_t sz = dump(buf, a); // yield to the specialized function if(currarg == results.lastok + 1 && sz <= buf.len) results.lastok = currarg; results.bufsize = sz > results.bufsize ? sz : results.bufsize; } return detail::cat_dump_resume(currarg + 1u, results, buf, more...); } template DumpResults cat_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) { if(C4_LIKELY(results.write_arg(currarg))) { size_t sz = dump(std::forward(sinkfn), buf, a); // yield to the specialized function if(currarg == results.lastok + 1 && sz <= buf.len) results.lastok = currarg; results.bufsize = sz > results.bufsize ? sz : results.bufsize; } return detail::cat_dump_resume(currarg + 1u, std::forward(sinkfn), results, buf, more...); } } // namespace detail /// @endcond template C4_ALWAYS_INLINE DumpResults cat_dump_resume(substr buf, Arg const& C4_RESTRICT a, Args const& ...more) { return detail::cat_dump_resume(0u, DumpResults{}, buf, a, more...); } template C4_ALWAYS_INLINE DumpResults cat_dump_resume(SinkFn &&sinkfn, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) { return detail::cat_dump_resume(0u, std::forward(sinkfn), DumpResults{}, buf, a, more...); } template C4_ALWAYS_INLINE DumpResults cat_dump_resume(DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) { if(results.bufsize > buf.len) return results; return detail::cat_dump_resume(0u, results, buf, a, more...); } template C4_ALWAYS_INLINE DumpResults cat_dump_resume(SinkFn &&sinkfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& ...more) { if(results.bufsize > buf.len) return results; return detail::cat_dump_resume(0u, std::forward(sinkfn), results, buf, a, more...); } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /// @cond dev // terminate the recursion template size_t catsep_dump(SinkFn &&, substr, Sep const& C4_RESTRICT) // NOLINT { return 0; } // terminate the recursion template size_t catsep_dump(substr, Sep const& C4_RESTRICT) // NOLINT { return 0; } /// @endcond /** take the function pointer as a function argument */ template size_t catsep_dump(SinkFn &&sinkfn, substr buf, Sep const& sep, Arg const& a, Args const& ...more) { size_t sz = dump(std::forward(sinkfn), buf, a); if(C4_UNLIKELY(sz > buf.len)) buf.len = 0; // ensure no more calls if C4_IF_CONSTEXPR (sizeof...(more) > 0) { size_t szsep = dump(std::forward(sinkfn), buf, sep); if(C4_UNLIKELY(szsep > buf.len)) buf.len = 0; // ensure no more calls sz = sz > szsep ? sz : szsep; } size_t size_for_more = catsep_dump(std::forward(sinkfn), buf, sep, more...); return size_for_more > sz ? size_for_more : sz; } /** take the function pointer as a template argument */ template size_t catsep_dump(substr buf, Sep const& sep, Arg const& a, Args const& ...more) { size_t sz = dump(buf, a); if(C4_UNLIKELY(sz > buf.len)) buf.len = 0; // ensure no more calls if C4_IF_CONSTEXPR (sizeof...(more) > 0) { size_t szsep = dump(buf, sep); if(C4_UNLIKELY(szsep > buf.len)) buf.len = 0; // ensure no more calls sz = sz > szsep ? sz : szsep; } size_t size_for_more = catsep_dump(buf, sep, more...); return size_for_more > sz ? size_for_more : sz; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /// @cond dev namespace detail { template void catsep_dump_resume_(size_t currarg, DumpResults *C4_RESTRICT results, substr *buf, Arg const& a) { if(C4_LIKELY(results->write_arg(currarg))) { size_t sz = dump(*buf, a); results->bufsize = sz > results->bufsize ? sz : results->bufsize; if(C4_LIKELY(sz <= buf->len)) results->lastok = currarg; else buf->len = 0; } } template void catsep_dump_resume_(size_t currarg, SinkFn &&sinkfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Arg const& C4_RESTRICT a) { if(C4_LIKELY(results->write_arg(currarg))) { size_t sz = dump(std::forward(sinkfn), *buf, a); results->bufsize = sz > results->bufsize ? sz : results->bufsize; if(C4_LIKELY(sz <= buf->len)) results->lastok = currarg; else buf->len = 0; } } template C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const&, Arg const& a) { detail::catsep_dump_resume_(currarg, results, buf, a); } template C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const&, Arg const& a) { detail::catsep_dump_resume_(currarg, std::forward(sinkfn), results, buf, a); } template C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& sep, Arg const& a, Args const& ...more) { detail::catsep_dump_resume_(currarg , results, buf, a); detail::catsep_dump_resume_(currarg + 1u, results, buf, sep); detail::catsep_dump_resume (currarg + 2u, results, buf, sep, more...); } template C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& sep, Arg const& a, Args const& ...more) { detail::catsep_dump_resume_(currarg , std::forward(sinkfn), results, buf, a); detail::catsep_dump_resume_(currarg + 1u, std::forward(sinkfn), results, buf, sep); detail::catsep_dump_resume (currarg + 2u, std::forward(sinkfn), results, buf, sep, more...); } } // namespace detail /// @endcond template C4_ALWAYS_INLINE DumpResults catsep_dump_resume(substr buf, Sep const& sep, Args const& ...args) { DumpResults results; detail::catsep_dump_resume(0u, &results, &buf, sep, args...); return results; } template C4_ALWAYS_INLINE DumpResults catsep_dump_resume(SinkFn &&sinkfn, substr buf, Sep const& sep, Args const& ...args) { DumpResults results; detail::catsep_dump_resume(0u, std::forward(sinkfn), &results, &buf, sep, args...); return results; } template C4_ALWAYS_INLINE DumpResults catsep_dump_resume(DumpResults results, substr buf, Sep const& sep, Args const& ...args) { detail::catsep_dump_resume(0u, &results, &buf, sep, args...); return results; } template C4_ALWAYS_INLINE DumpResults catsep_dump_resume(SinkFn &&sinkfn, DumpResults results, substr buf, Sep const& sep, Args const& ...args) { detail::catsep_dump_resume(0u, std::forward(sinkfn), &results, &buf, sep, args...); return results; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /// @cond dev namespace detail { // terminate the recursion C4_ALWAYS_INLINE size_t _format_dump_compute_size() { return 0u; } template C4_ALWAYS_INLINE auto _format_dump_compute_size(T const&) -> typename std::enable_if::value, size_t>::type { return 0u; // no buffer needed } template C4_ALWAYS_INLINE auto _format_dump_compute_size(T const& v) -> typename std::enable_if::value, size_t>::type { return to_chars({}, v); } template size_t _format_dump_compute_size(Arg const& a, Args const& ...more) { const size_t sz = _format_dump_compute_size(a); // don't call to_chars() directly const size_t rest = _format_dump_compute_size(more...); return sz > rest ? sz : rest; } } // namespace detail // terminate the recursion template C4_ALWAYS_INLINE size_t format_dump(SinkFn &&sinkfn, substr, csubstr fmt) { // we can dump without using buf, so no need to check it std::forward(sinkfn)(fmt); return 0u; } // terminate the recursion /** take the function pointer as a template argument */ template C4_ALWAYS_INLINE size_t format_dump(substr, csubstr fmt) { // we can dump without using buf, so no need to check it sinkfn(fmt); return 0u; } /// @endcond /** take the function pointer as a function argument */ template C4_NO_INLINE size_t format_dump(SinkFn &&sinkfn, substr buf, csubstr fmt, Arg const& a, Args const& ...more) { // we can dump without using buf // but we'll only dump if the buffer is ok size_t pos = fmt.find("{}"); // @todo use _find_fmt() if(C4_UNLIKELY(pos == csubstr::npos)) { std::forward(sinkfn)(fmt); return 0u; } std::forward(sinkfn)(fmt.first(pos)); // we can dump without using buf fmt = fmt.sub(pos + 2); // skip {} do this before assigning to pos again pos = dump(std::forward(sinkfn), buf, a); // reuse pos to get needed_size // dump no more if the buffer was exhausted size_t size_for_more; if(C4_LIKELY(pos <= buf.len)) size_for_more = format_dump(std::forward(sinkfn), buf, fmt, more...); else size_for_more = detail::_format_dump_compute_size(more...); return size_for_more > pos ? size_for_more : pos; } /** take the function pointer as a template argument */ template C4_NO_INLINE size_t format_dump(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& ...more) { // we can dump without using buf // but we'll only dump if the buffer is ok size_t pos = fmt.find("{}"); // @todo use _find_fmt() if(C4_UNLIKELY(pos == csubstr::npos)) { sinkfn(fmt); return 0u; } sinkfn(fmt.first(pos)); // we can dump without using buf fmt = fmt.sub(pos + 2); // skip {} do this before assigning to pos again pos = dump(buf, a); // reuse pos to get needed_size // dump no more if the buffer was exhausted size_t size_for_more; if(C4_LIKELY(pos <= buf.len)) size_for_more = format_dump(buf, fmt, more...); else size_for_more = detail::_format_dump_compute_size(more...); return size_for_more > pos ? size_for_more : pos; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /// @cond dev namespace detail { // terminate the recursion template DumpResults format_dump_resume(size_t currarg, DumpResults results, substr, csubstr fmt) { if(C4_LIKELY(results.write_arg(currarg))) { // we can dump without using buf sinkfn(fmt); results.lastok = currarg; } return results; } // terminate the recursion template DumpResults format_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults results, substr, csubstr fmt) { if(C4_LIKELY(results.write_arg(currarg))) { // we can dump without using buf std::forward(sinkfn)(fmt); results.lastok = currarg; } return results; } template DumpResults format_dump_resume(size_t currarg, DumpResults results, substr buf, csubstr fmt, Arg const& a, Args const& ...more) { // we need to process the format even if we're not // going to print the first arguments because we're resuming const size_t pos = fmt.find("{}"); // @todo use _find_fmt() if(C4_LIKELY(pos != csubstr::npos)) { if(C4_LIKELY(results.write_arg(currarg))) { sinkfn(fmt.first(pos)); results.lastok = currarg; } if(C4_LIKELY(results.write_arg(currarg + 1u))) { const size_t len = dump(buf, a); results.bufsize = len > results.bufsize ? len : results.bufsize; if(C4_LIKELY(len <= buf.len)) { results.lastok = currarg + 1u; } else { const size_t rest = _format_dump_compute_size(more...); results.bufsize = rest > results.bufsize ? rest : results.bufsize; return results; } } } else { if(C4_LIKELY(results.write_arg(currarg))) { sinkfn(fmt); results.lastok = currarg; } return results; } // NOTE: sparc64 had trouble with reassignment to fmt, and // was passing the original fmt to the recursion: //fmt = fmt.sub(pos + 2); // DONT! return detail::format_dump_resume(currarg + 2u, results, buf, fmt.sub(pos + 2), more...); } template DumpResults format_dump_resume(size_t currarg, SinkFn &&sinkfn, DumpResults results, substr buf, csubstr fmt, Arg const& a, Args const& ...more) { // we need to process the format even if we're not // going to print the first arguments because we're resuming const size_t pos = fmt.find("{}"); // @todo use _find_fmt() if(C4_LIKELY(pos != csubstr::npos)) { if(C4_LIKELY(results.write_arg(currarg))) { std::forward(sinkfn)(fmt.first(pos)); results.lastok = currarg; } if(C4_LIKELY(results.write_arg(currarg + 1u))) { const size_t len = dump(std::forward(sinkfn), buf, a); results.bufsize = len > results.bufsize ? len : results.bufsize; if(C4_LIKELY(len <= buf.len)) { results.lastok = currarg + 1u; } else { const size_t rest = _format_dump_compute_size(more...); results.bufsize = rest > results.bufsize ? rest : results.bufsize; return results; } } } else { if(C4_LIKELY(results.write_arg(currarg))) { std::forward(sinkfn)(fmt); results.lastok = currarg; } return results; } // NOTE: sparc64 had trouble with reassignment to fmt, and // was passing the original fmt to the recursion: //fmt = fmt.sub(pos + 2); // DONT! return detail::format_dump_resume(currarg + 2u, std::forward(sinkfn), results, buf, fmt.sub(pos + 2), more...); } } // namespace detail /// @endcond template C4_ALWAYS_INLINE DumpResults format_dump_resume(substr buf, csubstr fmt, Args const& ...args) { return detail::format_dump_resume(0u, DumpResults{}, buf, fmt, args...); } template C4_ALWAYS_INLINE DumpResults format_dump_resume(SinkFn &&sinkfn, substr buf, csubstr fmt, Args const& ...args) { return detail::format_dump_resume(0u, std::forward(sinkfn), DumpResults{}, buf, fmt, args...); } template C4_ALWAYS_INLINE DumpResults format_dump_resume(DumpResults results, substr buf, csubstr fmt, Args const& ...args) { return detail::format_dump_resume(0u, results, buf, fmt, args...); } template C4_ALWAYS_INLINE DumpResults format_dump_resume(SinkFn &&sinkfn, DumpResults results, substr buf, csubstr fmt, Args const& ...args) { return detail::format_dump_resume(0u, std::forward(sinkfn), results, buf, fmt, args...); } C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 #endif /* C4_DUMP_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/dump.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/enum.hpp // https://github.com/biojppm/c4core/src/c4/enum.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_ENUM_HPP_ #define _C4_ENUM_HPP_ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ //included above: //#include //included above: //#include /** @file enum.hpp utilities for enums: convert to/from string */ namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") //! taken from http://stackoverflow.com/questions/15586163/c11-type-trait-to-differentiate-between-enum-class-and-regular-enum template using is_scoped_enum = std::integral_constant::value && !std::is_convertible::value>; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- typedef enum { EOFFS_NONE = 0, ///< no offset EOFFS_CLS = 1, ///< get the enum offset for the class name. @see eoffs_cls() EOFFS_PFX = 2, ///< get the enum offset for the enum prefix. @see eoffs_pfx() _EOFFS_LAST ///< reserved } EnumOffsetType; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A simple (proxy) container for the value-name pairs of an enum type. * Uses linear search for finds; this could be improved for time-critical * code. */ template class EnumSymbols { public: struct Sym { Enum value; const char *name; bool cmp(const char *s) const; bool cmp(const char *s, size_t len) const; const char *name_offs(EnumOffsetType t) const; }; using const_iterator = Sym const*; public: template EnumSymbols(Sym const (&p)[N]) : m_symbols(p), m_num(N) {} size_t size() const { return m_num; } bool empty() const { return m_num == 0; } Sym const* get(Enum v) const { auto p = find(v); C4_CHECK_MSG(p != nullptr, "could not find symbol=%zd", (std::ptrdiff_t)v); return p; } Sym const* get(const char *s) const { auto p = find(s); C4_CHECK_MSG(p != nullptr, "could not find symbol \"%s\"", s); return p; } Sym const* get(const char *s, size_t len) const { auto p = find(s, len); C4_CHECK_MSG(p != nullptr, "could not find symbol \"%.*s\"", len, s); return p; } Sym const* find(Enum v) const; Sym const* find(const char *s) const; Sym const* find(const char *s, size_t len) const; Sym const& operator[] (size_t i) const { C4_CHECK(i < m_num); return m_symbols[i]; } Sym const* begin() const { return m_symbols; } Sym const* end () const { return m_symbols + m_num; } private: Sym const* m_symbols; size_t const m_num; // NOLINT(*avoid-const*) }; //----------------------------------------------------------------------------- /** return an EnumSymbols object for the enum type T * * @warning SPECIALIZE! This needs to be specialized for each enum * type. Failure to provide a specialization will cause a linker * error. */ template EnumSymbols const esyms(); /** return the offset for an enum symbol class. For example, * eoffs_cls() would be 13=strlen("MyEnumClass::"). * * With this function you can announce that the full prefix (including * an eventual enclosing class or C++11 enum class) is of a certain * length. * * @warning Needs to be specialized for each enum class type that * wants to use this. When no specialization is given, will return * 0. */ template size_t eoffs_cls() { return 0; } /** return the offset for an enum symbol prefix. This includes * eoffs_cls(). With this function you can announce that the full * prefix (including an eventual enclosing class or C++11 enum class * plus the string prefix) is of a certain length. * * @warning Needs to be specialized for each enum class type that * wants to use this. When no specialization is given, will return * 0. */ template size_t eoffs_pfx() { return 0; } template size_t eoffs(EnumOffsetType which) { switch(which) { case EOFFS_NONE: return 0; case EOFFS_CLS: return eoffs_cls(); case EOFFS_PFX: { size_t pfx = eoffs_pfx(); return pfx > 0 ? pfx : eoffs_cls(); } default: C4_ERROR("unknown offset type %d", (int)which); } } //----------------------------------------------------------------------------- /** get the enum value corresponding to a c-string */ #ifdef __clang__ # pragma clang diagnostic push #elif defined(__GNUC__) # pragma GCC diagnostic push # if __GNUC__ >= 6 # pragma GCC diagnostic ignored "-Wnull-dereference" # endif #endif template Enum str2e(const char* str) { auto pairs = esyms(); auto *p = pairs.get(str); C4_CHECK_MSG(p != nullptr, "no valid enum pair name for '%s'", str); return p->value; } /** get the c-string corresponding to an enum value */ template const char* e2str(Enum e) { auto es = esyms(); auto *p = es.get(e); C4_CHECK_MSG(p != nullptr, "no valid enum pair name"); return p->name; } /** like e2str(), but add an offset. */ template const char* e2stroffs(Enum e, EnumOffsetType ot=EOFFS_PFX) { const char *s = e2str(e) + eoffs(ot); return s; } #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif //----------------------------------------------------------------------------- /** Find a symbol by value. Returns nullptr when none is found */ template typename EnumSymbols::Sym const* EnumSymbols::find(Enum v) const { for(Sym const* p = this->m_symbols, *e = p+this->m_num; p < e; ++p) if(p->value == v) return p; return nullptr; } /** Find a symbol by name. Returns nullptr when none is found */ template typename EnumSymbols::Sym const* EnumSymbols::find(const char *s) const { for(Sym const* p = this->m_symbols, *e = p+this->m_num; p < e; ++p) if(p->cmp(s)) return p; return nullptr; } /** Find a symbol by name. Returns nullptr when none is found */ template typename EnumSymbols::Sym const* EnumSymbols::find(const char *s, size_t len) const { for(Sym const* p = this->m_symbols, *e = p+this->m_num; p < e; ++p) if(p->cmp(s, len)) return p; return nullptr; } //----------------------------------------------------------------------------- template bool EnumSymbols::Sym::cmp(const char *s) const { if(strcmp(name, s) == 0) return true; for(int i = 1; i < _EOFFS_LAST; ++i) { auto o = eoffs((EnumOffsetType)i); if(o > 0) if(strcmp(name + o, s) == 0) return true; } return false; } template bool EnumSymbols::Sym::cmp(const char *s, size_t len) const { if(strncmp(name, s, len) == 0) return true; size_t nlen = 0; for(int i = 1; i <_EOFFS_LAST; ++i) { auto o = eoffs((EnumOffsetType)i); if(o > 0) { if(!nlen) { nlen = strlen(name); } C4_ASSERT(o < nlen); size_t rem = nlen - o; auto m = len > rem ? len : rem; if(len >= m && strncmp(name + o, s, m) == 0) return true; } } return false; } //----------------------------------------------------------------------------- template const char* EnumSymbols::Sym::name_offs(EnumOffsetType t) const { C4_ASSERT(eoffs(t) < strlen(name)); return name + eoffs(t); } C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 #endif // _C4_ENUM_HPP_ // (end https://github.com/biojppm/c4core/src/c4/enum.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/bitmask.hpp // https://github.com/biojppm/c4core/src/c4/bitmask.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_BITMASK_HPP_ #define _C4_BITMASK_HPP_ /** @file bitmask.hpp bitmask utilities */ //included above: //#include //included above: //#include // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/enum.hpp //#include "c4/enum.hpp" #if !defined(C4_ENUM_HPP_) && !defined(_C4_ENUM_HPP_) #error "amalgamate: file c4/enum.hpp must have been included at this point" #endif /* C4_ENUM_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/format.hpp //#include "c4/format.hpp" #if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) #error "amalgamate: file c4/format.hpp must have been included at this point" #endif /* C4_FORMAT_HPP_ */ #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable : 4996) // 'strncpy', fopen, etc: This function or variable may be unsafe #endif #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wold-style-cast" # if __GNUC__ >= 8 # pragma GCC diagnostic ignored "-Wstringop-truncation" # pragma GCC diagnostic ignored "-Wstringop-overflow" # endif #endif namespace c4 { //----------------------------------------------------------------------------- /** write a bitmask to a stream, formatted as a string */ template Stream& bm2stream(Stream &s, typename std::underlying_type::type bits, EnumOffsetType offst=EOFFS_PFX) { using I = typename std::underlying_type::type; bool written = false; auto const& pairs = esyms(); // write non null value if(bits) { // do reverse iteration to give preference to composite enum symbols, // which are likely to appear at the end of the enum sequence for(size_t i = pairs.size() - 1; i != size_t(-1); --i) { auto p = pairs[i]; I b(static_cast(p.value)); if(b && (bits & b) == b) { if(written) s << '|'; // append bit-or character written = true; s << p.name_offs(offst); // append bit string bits &= ~b; } } return s; } else { // write a null value for(size_t i = pairs.size() - 1; i != size_t(-1); --i) { auto p = pairs[i]; I b(static_cast(p.value)); if(b == 0) { s << p.name_offs(offst); written = true; break; } } } if(!written) { s << '0'; } return s; } template typename std::enable_if::value, Stream&>::type bm2stream(Stream &s, Enum value, EnumOffsetType offst=EOFFS_PFX) { using I = typename std::underlying_type::type; return bm2stream(s, static_cast(value), offst); } //----------------------------------------------------------------------------- // some utility macros, undefed below /// @cond dev /* Execute `code` if the `num` of characters is available in the str * buffer. This macro simplifies the code for bm2str(). * @todo improve performance by writing from the end and moving only once. */ #define _c4prependchars(code, num) \ if(str && (pos + (num) <= sz)) \ { \ /* move the current string to the right */ \ memmove(str + (num), str, pos); \ /* now write in the beginning of the string */ \ code; \ } \ else if(str && sz) \ { \ C4_ERROR("cannot write to string pos=%d num=%d sz=%d", \ (int)pos, (int)(num), (int)sz); \ } \ pos += num /* Execute `code` if the `num` of characters is available in the str * buffer. This macro simplifies the code for bm2str(). */ #define _c4appendchars(code, num) \ if(str && (pos + (num) <= sz)) \ { \ code; \ } \ else if(str && sz) \ { \ C4_ERROR("cannot write to string pos=%d num=%d sz=%d", \ (int)pos, (int)(num), (int)sz); \ } \ pos += num /// @endcond /** convert a bitmask to string. * return the number of characters written. To find the needed size, * call first with str=nullptr and sz=0 */ template size_t bm2str ( typename std::underlying_type::type bits, char *str=nullptr, size_t sz=0, EnumOffsetType offst=EOFFS_PFX ) { using I = typename std::underlying_type::type; C4_ASSERT((str == nullptr) == (sz == 0)); auto syms = esyms(); size_t pos = 0; typename EnumSymbols::Sym const* C4_RESTRICT zero = nullptr; // do reverse iteration to give preference to composite enum symbols, // which are likely to appear later in the enum sequence for(size_t i = syms.size()-1; i != size_t(-1); --i) { auto const &C4_RESTRICT p = syms[i]; // do not copy, we are assigning to `zero` I b = static_cast(p.value); if(b == 0) { zero = &p; // save this symbol for later } else if((bits & b) == b) { bits &= ~b; // append bit-or character if(pos > 0) { _c4prependchars(*str = '|', 1); } // append bit string const char *pname = p.name_offs(offst); size_t len = strlen(pname); _c4prependchars(strncpy(str, pname, len), len); } } C4_CHECK_MSG(bits == 0, "could not find all bits"); if(pos == 0) // make sure at least something is written { if(zero) // if we have a zero symbol, use that { const char *pname = zero->name_offs(offst); size_t len = strlen(pname); _c4prependchars(strncpy(str, pname, len), len); } else // otherwise just write an integer zero { _c4prependchars(*str = '0', 1); } } _c4appendchars(str[pos] = '\0', 1); return pos; } // cleanup! #undef _c4appendchars #undef _c4prependchars /** scoped enums do not convert automatically to their underlying type, * so this SFINAE overload will accept scoped enum symbols and cast them * to the underlying type */ template typename std::enable_if::value, size_t>::type bm2str ( Enum bits, char *str=nullptr, size_t sz=0, EnumOffsetType offst=EOFFS_PFX ) { using I = typename std::underlying_type::type; return bm2str(static_cast(bits), str, sz, offst); } //----------------------------------------------------------------------------- namespace detail { #ifdef __clang__ # pragma clang diagnostic push #elif defined(__GNUC__) # pragma GCC diagnostic push # if __GNUC__ >= 6 # pragma GCC diagnostic ignored "-Wnull-dereference" # endif #endif template typename std::underlying_type::type str2bm_read_one(const char *str, size_t sz, bool alnum) { using I = typename std::underlying_type::type; auto pairs = esyms(); if(alnum) { auto *p = pairs.find(str, sz); C4_CHECK_MSG(p != nullptr, "no valid enum pair name for '%.*s'", (int)sz, str); return static_cast(p->value); } I tmp{0}; size_t len = uncat(csubstr(str, sz), tmp); C4_CHECK_MSG(len != csubstr::npos, "could not read string as an integral type: '%.*s'", (int)sz, str); return tmp; } #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif } // namespace detail /** convert a string to a bitmask */ template typename std::underlying_type::type str2bm(const char *str, size_t sz) { using I = typename std::underlying_type::type; I val = 0; bool started = false; bool alnum = false, num = false; const char *f = nullptr, *pc = str; for( ; pc < str+sz; ++pc) { const char c = *pc; if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') { C4_CHECK(( ! num) || ((pc - f) == 1 && (c == 'x' || c == 'X'))); // accept hexadecimal numbers if( ! started) { f = pc; alnum = started = true; } } else if(c >= '0' && c <= '9') { C4_CHECK( ! alnum); if(!started) { f = pc; num = started = true; } } else if(c == ':' || c == ' ') { // skip this char } else if(c == '|' || c == '\0') { C4_ASSERT(num != alnum); C4_ASSERT(pc >= f); val |= detail::str2bm_read_one(f, static_cast(pc-f), alnum); started = num = alnum = false; if(c == '\0') { return val; } } else { C4_ERROR("bad character '%c' in bitmask string", c); } } if(f) { C4_ASSERT(num != alnum); C4_ASSERT(pc >= f); val |= detail::str2bm_read_one(f, static_cast(pc-f), alnum); } return val; } /** convert a string to a bitmask */ template typename std::underlying_type::type str2bm(const char *str) { return str2bm(str, strlen(str)); } } // namespace c4 #ifdef _MSC_VER # pragma warning(pop) #endif #if defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif // _C4_BITMASK_HPP_ // (end https://github.com/biojppm/c4core/src/c4/bitmask.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/span.hpp // https://github.com/biojppm/c4core/src/c4/span.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_SPAN_HPP_ #define _C4_SPAN_HPP_ /** @file span.hpp Provides span classes. */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/config.hpp //#include "c4/config.hpp" #if !defined(C4_CONFIG_HPP_) && !defined(_C4_CONFIG_HPP_) #error "amalgamate: file c4/config.hpp must have been included at this point" #endif /* C4_CONFIG_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/szconv.hpp //#include "c4/szconv.hpp" #if !defined(C4_SZCONV_HPP_) && !defined(_C4_SZCONV_HPP_) #error "amalgamate: file c4/szconv.hpp must have been included at this point" #endif /* C4_SZCONV_HPP_ */ //included above: //#include namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") // NOLINTBEGIN(misc-confusable-identifiers) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** a crtp base for implementing span classes * * A span is a non-owning range of elements contiguously stored in memory. * Unlike STL's array_view, the span allows write-access to its members. * * To obtain subspans from a span, the following const member functions * are available: * - subspan(first, num) * - range(first, last) * - first(num) * - last(num) * * A span can also be resized via the following non-const member functions: * - resize(sz) * - ltrim(num) * - rtrim(num) * * @see span * @see cspan * @see spanrs * @see cspanrs * @see spanrsl * @see cspanrsl */ template class span_crtp { // some utility defines, undefined at the end of this class #define _c4this ((SpanImpl *)this) #define _c4cthis ((SpanImpl const*)this) #define _c4ptr ((SpanImpl *)this)->m_ptr #define _c4cptr ((SpanImpl const*)this)->m_ptr #define _c4sz ((SpanImpl *)this)->m_size #define _c4csz ((SpanImpl const*)this)->m_size public: _c4_DEFINE_ARRAY_TYPES(T, I); public: C4_ALWAYS_INLINE constexpr I value_size() const noexcept { return sizeof(T); } C4_ALWAYS_INLINE constexpr I elm_size () const noexcept { return sizeof(T); } C4_ALWAYS_INLINE constexpr I type_size () const noexcept { return sizeof(T); } C4_ALWAYS_INLINE I byte_size () const noexcept { return _c4csz*sizeof(T); } C4_ALWAYS_INLINE bool empty() const noexcept { return _c4csz == 0; } C4_ALWAYS_INLINE I size() const noexcept { return _c4csz; } //C4_ALWAYS_INLINE I capacity() const noexcept { return _c4sz; } // this must be defined by impl classes C4_ALWAYS_INLINE void clear() noexcept { _c4sz = 0; } C4_ALWAYS_INLINE T * data() noexcept { return _c4ptr; } C4_ALWAYS_INLINE T const* data() const noexcept { return _c4cptr; } C4_ALWAYS_INLINE iterator begin() noexcept { return _c4ptr; } C4_ALWAYS_INLINE const_iterator begin() const noexcept { return _c4cptr; } C4_ALWAYS_INLINE const_iterator cbegin() const noexcept { return _c4cptr; } C4_ALWAYS_INLINE iterator end() noexcept { return _c4ptr + _c4sz; } C4_ALWAYS_INLINE const_iterator end() const noexcept { return _c4cptr + _c4csz; } C4_ALWAYS_INLINE const_iterator cend() const noexcept { return _c4cptr + _c4csz; } C4_ALWAYS_INLINE reverse_iterator rbegin() noexcept { return reverse_iterator(_c4ptr + _c4sz); } C4_ALWAYS_INLINE const_reverse_iterator rbegin() const noexcept { return reverse_iterator(_c4cptr + _c4csz); } C4_ALWAYS_INLINE const_reverse_iterator crbegin() const noexcept { return reverse_iterator(_c4cptr + _c4csz); } C4_ALWAYS_INLINE reverse_iterator rend() noexcept { return const_reverse_iterator(_c4ptr); } C4_ALWAYS_INLINE const_reverse_iterator rend() const noexcept { return const_reverse_iterator(_c4cptr); } C4_ALWAYS_INLINE const_reverse_iterator crend() const noexcept { return const_reverse_iterator(_c4cptr); } C4_ALWAYS_INLINE T & front() C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4ptr [0]; } C4_ALWAYS_INLINE T const& front() const C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4cptr[0]; } C4_ALWAYS_INLINE T & back() C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4ptr [_c4sz - 1]; } C4_ALWAYS_INLINE T const& back() const C4_NOEXCEPT_X { C4_XASSERT(!empty()); return _c4cptr[_c4csz - 1]; } C4_ALWAYS_INLINE T & operator[] (I i) C4_NOEXCEPT_X { C4_XASSERT(i >= 0 && i < _c4sz ); return _c4ptr [i]; } C4_ALWAYS_INLINE T const& operator[] (I i) const C4_NOEXCEPT_X { C4_XASSERT(i >= 0 && i < _c4csz); return _c4cptr[i]; } C4_ALWAYS_INLINE SpanImpl subspan(I first, I num) const C4_NOEXCEPT_X { C4_XASSERT((first >= 0 && first < _c4csz) || (first == _c4csz && num == 0)); C4_XASSERT((first + num >= 0) && (first + num <= _c4csz)); return _c4cthis->_select(_c4cptr + first, num); } C4_ALWAYS_INLINE SpanImpl subspan(I first) const C4_NOEXCEPT_X ///< goes up until the end of the span { C4_XASSERT(first >= 0 && first <= _c4csz); return _c4cthis->_select(_c4cptr + first, _c4csz - first); } C4_ALWAYS_INLINE SpanImpl range(I first, I last) const C4_NOEXCEPT_X ///< last element is NOT included { C4_XASSERT(((first >= 0) && (first < _c4csz)) || (first == _c4csz && first == last)); C4_XASSERT((last >= 0) && (last <= _c4csz)); C4_XASSERT(last >= first); return _c4cthis->_select(_c4cptr + first, last - first); } C4_ALWAYS_INLINE SpanImpl range(I first) const C4_NOEXCEPT_X ///< goes up until the end of the span { C4_XASSERT(((first >= 0) && (first <= _c4csz))); return _c4cthis->_select(_c4cptr + first, _c4csz - first); } C4_ALWAYS_INLINE SpanImpl first(I num) const C4_NOEXCEPT_X ///< get the first num elements, starting at 0 { C4_XASSERT((num >= 0) && (num <= _c4csz)); return _c4cthis->_select(_c4cptr, num); } C4_ALWAYS_INLINE SpanImpl last(I num) const C4_NOEXCEPT_X ///< get the last num elements, starting at size()-num { C4_XASSERT((num >= 0) && (num <= _c4csz)); return _c4cthis->_select(_c4cptr + _c4csz - num, num); } bool is_subspan(span_crtp const& ss) const noexcept { if(_c4cptr == nullptr) return false; auto *b = begin(), *e = end(); auto *ssb = ss.begin(), *sse = ss.end(); if(ssb >= b && sse <= e) { return true; } else { return false; } } /** COMPLement Left: return the complement to the left of the beginning of the given subspan. * If ss does not begin inside this, returns an empty substring. */ SpanImpl compll(span_crtp const& ss) const C4_NOEXCEPT_X { auto ssb = ss.begin(); auto b = begin(); auto e = end(); if(ssb >= b && ssb <= e) { return subspan(0, static_cast(ssb - b)); } else { return subspan(0, 0); } } /** COMPLement Right: return the complement to the right of the end of the given subspan. * If ss does not end inside this, returns an empty substring. */ SpanImpl complr(span_crtp const& ss) const C4_NOEXCEPT_X { auto sse = ss.end(); auto b = begin(); auto e = end(); if(sse >= b && sse <= e) { return subspan(static_cast(sse - b), static_cast(e - sse)); } else { return subspan(0, 0); } } C4_ALWAYS_INLINE bool same_span(span_crtp const& that) const noexcept { return size() == that.size() && data() == that.data(); } template C4_ALWAYS_INLINE bool same_span(span_crtp const& that) const C4_NOEXCEPT_X { I tsz = szconv(that.size()); // x-asserts that the size does not overflow return size() == tsz && data() == that.data(); } #undef _c4this #undef _c4cthis #undef _c4ptr #undef _c4cptr #undef _c4sz #undef _c4csz }; //----------------------------------------------------------------------------- // NOLINTBEGIN(*-redundant-inline*) template inline constexpr bool operator== ( span_crtp const& l, span_crtp const& r ) { #if C4_CPP >= 14 return std::equal(l.begin(), l.end(), r.begin(), r.end()); #else return l.same_span(r) || std::equal(l.begin(), l.end(), r.begin()); #endif } template inline constexpr bool operator!= ( span_crtp const& l, span_crtp const& r ) { return ! (l == r); } //----------------------------------------------------------------------------- template inline constexpr bool operator< ( span_crtp const& l, span_crtp const& r ) { return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); } template inline constexpr bool operator<= ( span_crtp const& l, span_crtp const& r ) { return ! (l > r); } //----------------------------------------------------------------------------- template inline constexpr bool operator> ( span_crtp const& l, span_crtp const& r ) { return r < l; } //----------------------------------------------------------------------------- template inline constexpr bool operator>= ( span_crtp const& l, span_crtp const& r ) { return ! (l < r); } // NOLINTEND(*-redundant-inline*) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A non-owning span of elements contiguously stored in memory. */ template class span : public span_crtp> // NOLINT(*-special-member-functions) { friend class span_crtp>; T * C4_RESTRICT m_ptr; I m_size; C4_ALWAYS_INLINE span _select(T *p, I sz) const { return span(p, sz); } public: _c4_DEFINE_ARRAY_TYPES(T, I); using NCT = typename std::remove_const::type; //!< NCT=non const type using CT = typename std::add_const::type; //!< CT=const type using const_type = span; /// convert automatically to span of const T operator span () const { span s(m_ptr, m_size); return s; } public: C4_ALWAYS_INLINE C4_CONSTEXPR14 span() noexcept : m_ptr{nullptr}, m_size{0} {} span(span const&) = default; span(span &&) = default; span& operator= (span const&) = default; span& operator= (span &&) = default; public: /** @name Construction and assignment from same type */ /** @{ */ template C4_ALWAYS_INLINE C4_CONSTEXPR14 span (T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N} {} template C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; } C4_ALWAYS_INLINE C4_CONSTEXPR14 span(T *p, I sz) noexcept : m_ptr{p}, m_size{sz} {} C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; } C4_ALWAYS_INLINE C4_CONSTEXPR14 span (c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{&*il.begin()}, m_size{il.size()} {} C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = &*il.begin(); m_size = il.size(); } /** @} */ public: C4_ALWAYS_INLINE I capacity() const noexcept { return m_size; } C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_size); m_size = sz; } C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; } }; template using cspan = span; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A non-owning span resizeable up to a capacity. Subselection or resizing * will keep the original provided it starts at begin(). If subselection or * resizing change the pointer, then the original capacity information will * be lost. * * Thus, resizing via resize() and ltrim() and subselecting via first() * or any of subspan() or range() when starting from the beginning will keep * the original capacity. OTOH, using last(), or any of subspan() or range() * with an offset from the start will remove from capacity (shifting the * pointer) by the corresponding offset. If this is undesired, then consider * using spanrsl. * * @see spanrs for a span resizeable on the right * @see spanrsl for a span resizeable on the right and left */ template class spanrs : public span_crtp> // NOLINT(*-special-member-functions) { friend class span_crtp>; T * C4_RESTRICT m_ptr; I m_size; I m_capacity; C4_ALWAYS_INLINE spanrs _select(T *p, I sz) const noexcept { C4_ASSERT(p >= m_ptr); size_t delta = static_cast(p - m_ptr); C4_ASSERT(m_capacity >= delta); return spanrs(p, sz, static_cast(m_capacity - delta)); } public: _c4_DEFINE_ARRAY_TYPES(T, I); using NCT = typename std::remove_const::type; //!< NCT=non const type using CT = typename std::add_const::type; //!< CT=const type using const_type = spanrs; /// convert automatically to span of T C4_ALWAYS_INLINE operator span () const noexcept { return span(m_ptr, m_size); } /// convert automatically to span of const T //C4_ALWAYS_INLINE operator span () const noexcept { span s(m_ptr, m_size); return s; } /// convert automatically to spanrs of const T C4_ALWAYS_INLINE operator spanrs () const noexcept { spanrs s(m_ptr, m_size, m_capacity); return s; } public: C4_ALWAYS_INLINE spanrs() noexcept : m_ptr{nullptr}, m_size{0}, m_capacity{0} {} spanrs(spanrs const&) = default; spanrs(spanrs &&) = default; spanrs& operator= (spanrs const&) = default; spanrs& operator= (spanrs &&) = default; public: /** @name Construction and assignment from same type */ /** @{ */ C4_ALWAYS_INLINE spanrs(T *p, I sz) noexcept : m_ptr{p}, m_size{sz}, m_capacity{sz} {} /** @warning will reset the capacity to sz */ C4_ALWAYS_INLINE void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; m_capacity = sz; } C4_ALWAYS_INLINE spanrs(T *p, I sz, I cap) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap} {} C4_ALWAYS_INLINE void assign(T *p, I sz, I cap) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; } template C4_ALWAYS_INLINE spanrs(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N} {} template C4_ALWAYS_INLINE void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; m_capacity = N; } C4_ALWAYS_INLINE spanrs(c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{il.begin()}, m_size{il.size()}, m_capacity{il.size()} {} C4_ALWAYS_INLINE void assign(c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = il.begin(); m_size = il.size(); m_capacity = il.size(); } /** @} */ public: C4_ALWAYS_INLINE I capacity() const noexcept { return m_capacity; } C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_capacity); m_size = sz; } C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; m_capacity -= n; } }; template using cspanrs = spanrs; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A non-owning span which always retains the capacity of the original * range it was taken from (though it may loose its original size). * The resizing methods resize(), ltrim(), rtrim() as well * as the subselection methods subspan(), range(), first() and last() can be * used at will without loosing the original capacity; the full capacity span * can always be recovered by calling original(). */ template class spanrsl : public span_crtp> // NOLINT(*-special-member-functions) { friend class span_crtp>; T *C4_RESTRICT m_ptr; ///< the current ptr. the original ptr is (m_ptr - m_offset). I m_size; ///< the current size. the original size is unrecoverable. I m_capacity; ///< the current capacity. the original capacity is (m_capacity + m_offset). I m_offset; ///< the offset of the current m_ptr to the start of the original memory block. C4_ALWAYS_INLINE spanrsl _select(T *p, I sz) const noexcept { C4_ASSERT(p >= m_ptr); I delta = static_cast(p - m_ptr); C4_ASSERT(m_capacity >= delta); return spanrsl(p, sz, static_cast(m_capacity - delta), m_offset + delta); } public: _c4_DEFINE_ARRAY_TYPES(T, I); using NCT = typename std::remove_const::type; //!< NCT=non const type using CT = typename std::add_const::type; //!< CT=const type using const_type = spanrsl; C4_ALWAYS_INLINE operator span () const noexcept { return span(m_ptr, m_size); } C4_ALWAYS_INLINE operator spanrs () const noexcept { return spanrs(m_ptr, m_size, m_capacity); } C4_ALWAYS_INLINE operator spanrsl () const noexcept { return spanrsl(m_ptr, m_size, m_capacity, m_offset); } public: C4_ALWAYS_INLINE spanrsl() noexcept : m_ptr{nullptr}, m_size{0}, m_capacity{0}, m_offset{0} {} spanrsl(spanrsl const&) = default; spanrsl(spanrsl &&) = default; spanrsl& operator= (spanrsl const&) = default; spanrsl& operator= (spanrsl &&) = default; public: C4_ALWAYS_INLINE spanrsl(T *p, I sz) noexcept : m_ptr{p}, m_size{sz}, m_capacity{sz}, m_offset{0} {} C4_ALWAYS_INLINE void assign(T *p, I sz) noexcept { m_ptr = p; m_size = sz; m_capacity = sz; m_offset = 0; } C4_ALWAYS_INLINE spanrsl(T *p, I sz, I cap) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap}, m_offset{0} {} C4_ALWAYS_INLINE void assign(T *p, I sz, I cap) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; m_offset = 0; } C4_ALWAYS_INLINE spanrsl(T *p, I sz, I cap, I offs) noexcept : m_ptr{p}, m_size{sz}, m_capacity{cap}, m_offset{offs} {} C4_ALWAYS_INLINE void assign(T *p, I sz, I cap, I offs) noexcept { m_ptr = p; m_size = sz; m_capacity = cap; m_offset = offs; } template C4_ALWAYS_INLINE spanrsl(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N}, m_offset{0} {} template C4_ALWAYS_INLINE void assign(T (&arr)[N]) noexcept { m_ptr = arr; m_size = N; m_capacity = N; m_offset = 0; } C4_ALWAYS_INLINE spanrsl(c4::aggregate_t, std::initializer_list il) noexcept : m_ptr{il.begin()}, m_size{il.size()}, m_capacity{il.size()}, m_offset{0} {} C4_ALWAYS_INLINE void assign (c4::aggregate_t, std::initializer_list il) noexcept { m_ptr = il.begin(); m_size = il.size(); m_capacity = il.size(); m_offset = 0; } public: C4_ALWAYS_INLINE I offset() const noexcept { return m_offset; } C4_ALWAYS_INLINE I capacity() const noexcept { return m_capacity; } C4_ALWAYS_INLINE void resize(I sz) C4_NOEXCEPT_A { C4_ASSERT(sz <= m_capacity); m_size = sz; } C4_ALWAYS_INLINE void rtrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; } C4_ALWAYS_INLINE void ltrim (I n ) C4_NOEXCEPT_A { C4_ASSERT(n >= 0 && n < m_size); m_size -= n; m_ptr += n; m_offset += n; m_capacity -= n; } /** recover the original span as an spanrsl */ C4_ALWAYS_INLINE spanrsl original() const { return spanrsl(m_ptr - m_offset, m_capacity + m_offset, m_capacity + m_offset, 0); } /** recover the original span as a different span type. Example: spanrs<...> orig = s.original(); */ template class OtherSpanType> C4_ALWAYS_INLINE OtherSpanType original() { return OtherSpanType(m_ptr - m_offset, m_capacity + m_offset); } }; template using cspanrsl = spanrsl; // NOLINTEND(misc-confusable-identifiers) C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 #endif /* _C4_SPAN_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/span.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/type_name.hpp // https://github.com/biojppm/c4core/src/c4/type_name.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_TYPENAME_HPP_ #define _C4_TYPENAME_HPP_ /** @file type_name.hpp compile-time type name */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/span.hpp //#include "c4/span.hpp" #if !defined(C4_SPAN_HPP_) && !defined(_C4_SPAN_HPP_) #error "amalgamate: file c4/span.hpp must have been included at this point" #endif /* C4_SPAN_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/compiler.hpp //#include "c4/compiler.hpp" #if !defined(C4_COMPILER_HPP_) && !defined(_C4_COMPILER_HPP_) #error "amalgamate: file c4/compiler.hpp must have been included at this point" #endif /* C4_COMPILER_HPP_ */ /// @cond dev struct _c4t { const char *str; size_t sz; template constexpr _c4t(const char (&s)[N]) : str(s), sz(N-1) {} // take off the \0 }; // this is a more abbreviated way of getting the type name // (if we used span in the return type, the name would involve // templates and would create longer type name strings, // as well as larger differences between compilers) template C4_CONSTEXPR14 C4_ALWAYS_INLINE _c4t _c4tn() { auto p = _c4t(C4_PRETTY_FUNC); return p; } /// @endcond namespace c4 { /** compile-time type name * @see http://stackoverflow.com/a/20170989/5875572 */ template C4_CONSTEXPR14 cspan type_name() { const _c4t p = _c4tn(); #if (0) // enable this to debug and find the offsets for(size_t index = 0; index < p.sz; ++index) printf(" %2c", p.str[index]); printf("\n"); for(size_t index = 0; index < p.sz; ++index) printf(" %2zu", index); printf("\n"); #endif #if defined(_MSC_VER) # if defined(__clang__) // Visual Studio has the clang toolset # if (_MSC_VER >= 1930) // do not use this: defined(C4_MSVC_2022) // ..............................xxx. // _c4t __cdecl _c4tn(void) [T = int] enum : size_t { tstart = 30, tend = 1}; # else // example: // ..........................xxx. // _c4t __cdecl _c4tn() [T = int] enum : size_t { tstart = 26, tend = 1}; # endif # elif defined(C4_MSVC_2015) || defined(C4_MSVC_2017) || defined(C4_MSVC_2019) || defined(C4_MSVC_2022) // Note: subtract 7 at the end because the function terminates with ">(void)" in VS2015+ cspan::size_type tstart = 26, tend = 7; const char *C4_RESTRICT s = p.str + tstart; // look at the start // we're not using strcmp() or memcmp() to spare the #include // does it start with 'class '? if(p.sz > 6 && s[0] == 'c' && s[1] == 'l' && s[2] == 'a' && s[3] == 's' && s[4] == 's' && s[5] == ' ') { tstart += 6; } // does it start with 'struct '? else if(p.sz > 7 && s[0] == 's' && s[1] == 't' && s[2] == 'r' && s[3] == 'u' && s[4] == 'c' && s[5] == 't' && s[6] == ' ') { tstart += 7; } # else C4_NOT_IMPLEMENTED(); # endif #elif defined(__ICC) // example: // ........................xxx. // "_c4t _c4tn() [with T = int]" enum : size_t { tstart = 23, tend = 1}; #elif defined(__clang__) // example: // ...................xxx. // "_c4t _c4tn() [T = int]" enum : size_t { tstart = 18, tend = 1}; #elif defined(__GNUC__) #if __GNUC__ >= 7 && C4_CPP >= 14 // example: // ..................................xxx. // "constexpr _c4t _c4tn() [with T = int]" enum : size_t { tstart = 33, tend = 1 }; #else // example: // ........................xxx. // "_c4t _c4tn() [with T = int]" enum : size_t { tstart = 23, tend = 1 }; #endif #else C4_NOT_IMPLEMENTED(); #endif cspan o(p.str + tstart, p.sz - tstart - tend); return o; } /** compile-time type name * @overload */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE cspan type_name(T const&) { return type_name(); } } // namespace c4 #endif //_C4_TYPENAME_HPP_ // (end https://github.com/biojppm/c4core/src/c4/type_name.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/base64.hpp // https://github.com/biojppm/c4core/src/c4/base64.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_BASE64_HPP_ #define _C4_BASE64_HPP_ /** @file base64.hpp encoding/decoding for base64. * @see https://en.wikipedia.org/wiki/Base64 * @see https://www.base64encode.org/ * */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/charconv.hpp //#include "c4/charconv.hpp" #if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) #error "amalgamate: file c4/charconv.hpp must have been included at this point" #endif /* C4_CHARCONV_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/blob.hpp //#include "c4/blob.hpp" #if !defined(C4_BLOB_HPP_) && !defined(_C4_BLOB_HPP_) #error "amalgamate: file c4/blob.hpp must have been included at this point" #endif /* C4_BLOB_HPP_ */ namespace c4 { /** @defgroup doc_base64 Base64 encoding/decoding * @see https://en.wikipedia.org/wiki/Base64 * @see https://www.base64encode.org/ * @{ */ /** check that the given buffer is a valid base64 encoding * @see https://en.wikipedia.org/wiki/Base64 */ C4CORE_EXPORT bool base64_valid(csubstr encoded); /** base64-encode binary data. * @param encoded [out] output buffer for encoded data * @param data [in] the input buffer with the binary data * * @return the number of bytes needed to return the output (ie the * required size for @p encoded). No writes occur beyond the end of * the output buffer, so it is safe to do a speculative call where the * encoded buffer is empty, or maybe too small. The caller should * ensure that the returned size is smaller than the size of the * encoded buffer. * * @note the result depends on endianness. If transfer between * little/big endian systems is desired, the caller should normalize * @p data before encoding. * * @see https://en.wikipedia.org/wiki/Base64 */ C4CORE_EXPORT size_t base64_encode(substr encoded, cblob data); /** decode the base64 encoding in the given buffer * @param encoded [in] the encoded base64 * @param data [out] the output buffer * * @return the number of bytes needed to return the output (ie the * required size for @p data). No writes occur beyond the end of the * output buffer, so it is safe to do a speculative call where the * data buffer is empty, or maybe too small. The caller should ensure * that the returned size is smaller than the size of the data buffer. * * @note the result depends on endianness. If transfer between * little/big endian systems is desired, the caller should normalize * @p data after decoding. * * @see https://en.wikipedia.org/wiki/Base64 */ C4CORE_EXPORT size_t base64_decode(csubstr encoded, blob data); /** @} */ // base64 namespace fmt { /** @addtogroup doc_format_specifiers * @{ */ /** @defgroup doc_base64_fmt Base64 * @{ */ template struct base64_wrapper_ { blob_ data; base64_wrapper_() : data() {} base64_wrapper_(blob_ blob) : data(blob) {} }; /** a tag type to mark a payload as base64-encoded */ using const_base64_wrapper = base64_wrapper_; /** a tag type to mark a payload to be encoded as base64 */ using base64_wrapper = base64_wrapper_; /** mark a variable to be written in base64 format */ template C4_ALWAYS_INLINE const_base64_wrapper cbase64(Args const& C4_RESTRICT ...args) { return const_base64_wrapper(cblob(args...)); } /** mark a csubstr to be written in base64 format */ C4_ALWAYS_INLINE const_base64_wrapper cbase64(csubstr s) { return const_base64_wrapper(cblob(s.str, s.len)); } /** mark a variable to be written in base64 format */ template C4_ALWAYS_INLINE const_base64_wrapper base64(Args const& C4_RESTRICT ...args) { return const_base64_wrapper(cblob(args...)); } /** mark a csubstr to be written in base64 format */ C4_ALWAYS_INLINE const_base64_wrapper base64(csubstr s) { return const_base64_wrapper(cblob(s.str, s.len)); } /** mark a variable to be read in base64 format */ template C4_ALWAYS_INLINE base64_wrapper base64(Args &... args) { return base64_wrapper(blob(args...)); } /** mark a variable to be read in base64 format */ C4_ALWAYS_INLINE base64_wrapper base64(substr s) { return base64_wrapper(blob(s.str, s.len)); } /** @} */ // base64_fmt /** @} */ // format_specifiers } // namespace fmt /** write a variable in base64 format * @ingroup doc_to_chars */ inline size_t to_chars(substr buf, fmt::const_base64_wrapper b) { return base64_encode(buf, b.data); } /** read a variable in base64 format * @ingroup doc_from_chars */ inline size_t from_chars(csubstr buf, fmt::base64_wrapper *b) { return base64_decode(buf, b->data); } } // namespace c4 #endif /* _C4_BASE64_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/base64.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/std/string.hpp // https://github.com/biojppm/c4core/src/c4/std/string.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_STD_STRING_HPP_ #define _C4_STD_STRING_HPP_ /** @file string.hpp */ #ifndef C4CORE_SINGLE_HEADER // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/substr.hpp //#include "c4/substr.hpp" #if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) #error "amalgamate: file c4/substr.hpp must have been included at this point" #endif /* C4_SUBSTR_HPP_ */ #endif //included above: //#include namespace c4 { //----------------------------------------------------------------------------- /** get a writeable view to an existing std::string. * When the string is empty, the returned view will be pointing * at the character with value '\0', but the size will be zero. * @see https://en.cppreference.com/w/cpp/string/basic_string/operator_at */ C4_ALWAYS_INLINE c4::substr to_substr(std::string &s) noexcept { #if C4_CPP < 11 #error this function will have undefined behavior #endif // since c++11 it is legal to call s[s.size()]. return c4::substr(&s[0], s.size()); // NOLINT(readability-container-data-pointer) } /** get a readonly view to an existing std::string. * When the string is empty, the returned view will be pointing * at the character with value '\0', but the size will be zero. * @see https://en.cppreference.com/w/cpp/string/basic_string/operator_at */ C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::string const& s) noexcept { #if C4_CPP < 11 #error this function will have undefined behavior #endif // since c++11 it is legal to call s[s.size()]. return c4::csubstr(&s[0], s.size()); // NOLINT(readability-container-data-pointer) } //----------------------------------------------------------------------------- C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) == 0; } C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) != 0; } C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) >= 0; } C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) > 0; } C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) <= 0; } C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::string const& s) { return ss.compare(to_csubstr(s)) < 0; } C4_ALWAYS_INLINE bool operator== (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) == 0; } C4_ALWAYS_INLINE bool operator!= (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) != 0; } C4_ALWAYS_INLINE bool operator>= (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) <= 0; } C4_ALWAYS_INLINE bool operator> (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) < 0; } C4_ALWAYS_INLINE bool operator<= (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) >= 0; } C4_ALWAYS_INLINE bool operator< (std::string const& s, c4::csubstr ss) { return ss.compare(to_csubstr(s)) > 0; } //----------------------------------------------------------------------------- /** copy an std::string to a writeable string view */ inline size_t to_chars(c4::substr buf, std::string const& s) { C4_ASSERT(!buf.overlaps(to_csubstr(s))); size_t len = buf.len < s.size() ? buf.len : s.size(); // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(len) { C4_ASSERT(s.data() != nullptr); C4_ASSERT(buf.str != nullptr); memcpy(buf.str, s.data(), len); } return s.size(); // return the number of needed chars } /** copy a string view to an existing std::string */ inline bool from_chars(c4::csubstr buf, std::string * s) { s->resize(buf.len); C4_ASSERT(!buf.overlaps(to_csubstr(*s))); // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(buf.len) { C4_ASSERT(buf.str != nullptr); memcpy(&(*s)[0], buf.str, buf.len); // NOLINT(readability-container-data-pointer) } return true; } } // namespace c4 #endif // _C4_STD_STRING_HPP_ // (end https://github.com/biojppm/c4core/src/c4/std/string.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/std/string_view.hpp // https://github.com/biojppm/c4core/src/c4/std/string_view.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_STD_STRING_VIEW_HPP_ #define _C4_STD_STRING_VIEW_HPP_ /** @file string_view.hpp */ #ifndef C4CORE_SINGLE_HEADER // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/language.hpp //#include "c4/language.hpp" #if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) #error "amalgamate: file c4/language.hpp must have been included at this point" #endif /* C4_LANGUAGE_HPP_ */ #endif #if (C4_CPP >= 17 && defined(__cpp_lib_string_view)) || defined(__DOXYGEN__) #ifndef C4CORE_SINGLE_HEADER // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/substr.hpp //#include "c4/substr.hpp" #if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) #error "amalgamate: file c4/substr.hpp must have been included at this point" #endif /* C4_SUBSTR_HPP_ */ #endif #include namespace c4 { //----------------------------------------------------------------------------- /** create a csubstr from an existing std::string_view. */ C4_ALWAYS_INLINE c4::csubstr to_csubstr(std::string_view s) noexcept { return c4::csubstr(s.data(), s.size()); } //----------------------------------------------------------------------------- C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) == 0; } C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) != 0; } C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) >= 0; } C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) > 0; } C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) <= 0; } C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::string_view s) { return ss.compare(s.data(), s.size()) < 0; } C4_ALWAYS_INLINE bool operator== (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) == 0; } C4_ALWAYS_INLINE bool operator!= (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) != 0; } C4_ALWAYS_INLINE bool operator<= (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) >= 0; } C4_ALWAYS_INLINE bool operator< (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) > 0; } C4_ALWAYS_INLINE bool operator>= (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) <= 0; } C4_ALWAYS_INLINE bool operator> (std::string_view s, c4::csubstr ss) { return ss.compare(s.data(), s.size()) < 0; } //----------------------------------------------------------------------------- /** copy an std::string_view to a writeable substr */ inline size_t to_chars(c4::substr buf, std::string_view s) { C4_ASSERT(!buf.overlaps(to_csubstr(s))); size_t sz = s.size(); size_t len = buf.len < sz ? buf.len : sz; // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(len) { C4_ASSERT(s.data() != nullptr); C4_ASSERT(buf.str != nullptr); memcpy(buf.str, s.data(), len); } return sz; // return the number of needed chars } } // namespace c4 #endif // C4_STRING_VIEW_AVAILABLE #endif // _C4_STD_STRING_VIEW_HPP_ // (end https://github.com/biojppm/c4core/src/c4/std/string_view.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/std/vector.hpp // https://github.com/biojppm/c4core/src/c4/std/vector.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_STD_VECTOR_HPP_ #define _C4_STD_VECTOR_HPP_ /** @file vector.hpp provides conversion and comparison facilities * from/between std::vector to c4::substr and c4::csubstr. * @todo add to_span() and friends */ #ifndef C4CORE_SINGLE_HEADER // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/substr.hpp //#include "c4/substr.hpp" #if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) #error "amalgamate: file c4/substr.hpp must have been included at this point" #endif /* C4_SUBSTR_HPP_ */ #endif #include namespace c4 { //----------------------------------------------------------------------------- /** get a substr (writeable string view) of an existing std::vector */ template c4::substr to_substr(std::vector &vec) { char *data = vec.empty() ? nullptr : vec.data(); // data() may or may not return a null pointer. return c4::substr(data, vec.size()); } /** get a csubstr (read-only string) view of an existing std::vector */ template c4::csubstr to_csubstr(std::vector const& vec) { const char *data = vec.empty() ? nullptr : vec.data(); // data() may or may not return a null pointer. return c4::csubstr(data, vec.size()); } //----------------------------------------------------------------------------- // comparisons between substrings and std::vector template C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::vector const& s) { return ss != to_csubstr(s); } template C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::vector const& s) { return ss == to_csubstr(s); } template C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::vector const& s) { return ss >= to_csubstr(s); } template C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::vector const& s) { return ss > to_csubstr(s); } template C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::vector const& s) { return ss <= to_csubstr(s); } template C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::vector const& s) { return ss < to_csubstr(s); } template C4_ALWAYS_INLINE bool operator!= (std::vector const& s, c4::csubstr ss) { return ss != to_csubstr(s); } template C4_ALWAYS_INLINE bool operator== (std::vector const& s, c4::csubstr ss) { return ss == to_csubstr(s); } template C4_ALWAYS_INLINE bool operator>= (std::vector const& s, c4::csubstr ss) { return ss <= to_csubstr(s); } template C4_ALWAYS_INLINE bool operator> (std::vector const& s, c4::csubstr ss) { return ss < to_csubstr(s); } template C4_ALWAYS_INLINE bool operator<= (std::vector const& s, c4::csubstr ss) { return ss >= to_csubstr(s); } template C4_ALWAYS_INLINE bool operator< (std::vector const& s, c4::csubstr ss) { return ss > to_csubstr(s); } //----------------------------------------------------------------------------- /** copy a std::vector to a writeable string view */ template inline size_t to_chars(c4::substr buf, std::vector const& s) { C4_ASSERT(!buf.overlaps(to_csubstr(s))); size_t len = buf.len < s.size() ? buf.len : s.size(); // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(len > 0) { memcpy(buf.str, s.data(), len); } return s.size(); // return the number of needed chars } /** copy a string view to an existing std::vector */ template inline bool from_chars(c4::csubstr buf, std::vector * s) { s->resize(buf.len); C4_ASSERT(!buf.overlaps(to_csubstr(*s))); // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(buf.len > 0) { memcpy(&(*s)[0], buf.str, buf.len); // NOLINT(readability-container-data-pointer) } return true; } } // namespace c4 #endif // _C4_STD_VECTOR_HPP_ // (end https://github.com/biojppm/c4core/src/c4/std/vector.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/std/tuple.hpp // https://github.com/biojppm/c4core/src/c4/std/tuple.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_STD_TUPLE_HPP_ #define _C4_STD_TUPLE_HPP_ /** @file tuple.hpp */ #ifndef C4CORE_SINGLE_HEADER // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/format.hpp //#include "c4/format.hpp" #if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) #error "amalgamate: file c4/format.hpp must have been included at this point" #endif /* C4_FORMAT_HPP_ */ #endif #include /** this is a work in progress */ #undef C4_TUPLE_TO_CHARS namespace c4 { #ifdef C4_TUPLE_TO_CHARS namespace detail { template< size_t Curr, class... Types > struct tuple_helper { static size_t do_cat(substr buf, std::tuple< Types... > const& tp) { size_t num = to_chars(buf, std::get(tp)); buf = buf.len >= num ? buf.sub(num) : substr{}; num += tuple_helper< Curr+1, Types... >::do_cat(buf, tp); return num; } static size_t do_uncat(csubstr buf, std::tuple< Types... > & tp) { size_t num = from_str_trim(buf, &std::get(tp)); if(num == csubstr::npos) return csubstr::npos; buf = buf.len >= num ? buf.sub(num) : substr{}; num += tuple_helper< Curr+1, Types... >::do_uncat(buf, tp); return num; } template< class Sep > static size_t do_catsep_more(substr buf, Sep const& sep, std::tuple< Types... > const& tp) { size_t ret = to_chars(buf, sep), num = ret; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = to_chars(buf, std::get(tp)); num += ret; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = tuple_helper< Curr+1, Types... >::do_catsep_more(buf, sep, tp); num += ret; return num; } template< class Sep > static size_t do_uncatsep_more(csubstr buf, Sep & sep, std::tuple< Types... > & tp) { size_t ret = from_str_trim(buf, &sep), num = ret; if(ret == csubstr::npos) return csubstr::npos; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = from_str_trim(buf, &std::get(tp)); if(ret == csubstr::npos) return csubstr::npos; num += ret; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = tuple_helper< Curr+1, Types... >::do_uncatsep_more(buf, sep, tp); if(ret == csubstr::npos) return csubstr::npos; num += ret; return num; } static size_t do_format(substr buf, csubstr fmt, std::tuple< Types... > const& tp) { auto pos = fmt.find("{}"); if(pos != csubstr::npos) { size_t num = to_chars(buf, fmt.sub(0, pos)); size_t out = num; buf = buf.len >= num ? buf.sub(num) : substr{}; num = to_chars(buf, std::get(tp)); out += num; buf = buf.len >= num ? buf.sub(num) : substr{}; num = tuple_helper< Curr+1, Types... >::do_format(buf, fmt.sub(pos + 2), tp); out += num; return out; } else { return format(buf, fmt); } } static size_t do_unformat(csubstr buf, csubstr fmt, std::tuple< Types... > & tp) { auto pos = fmt.find("{}"); if(pos != csubstr::npos) { size_t num = pos; size_t out = num; buf = buf.len >= num ? buf.sub(num) : substr{}; num = from_str_trim(buf, &std::get(tp)); out += num; buf = buf.len >= num ? buf.sub(num) : substr{}; num = tuple_helper< Curr+1, Types... >::do_unformat(buf, fmt.sub(pos + 2), tp); out += num; return out; } else { return tuple_helper< sizeof...(Types), Types... >::do_unformat(buf, fmt, tp); } } }; /** @todo VS compilation fails for this class */ template< class... Types > struct tuple_helper< sizeof...(Types), Types... > { static size_t do_cat(substr /*buf*/, std::tuple const& /*tp*/) { return 0; } static size_t do_uncat(csubstr /*buf*/, std::tuple & /*tp*/) { return 0; } template< class Sep > static size_t do_catsep_more(substr /*buf*/, Sep const& /*sep*/, std::tuple const& /*tp*/) { return 0; } template< class Sep > static size_t do_uncatsep_more(csubstr /*buf*/, Sep & /*sep*/, std::tuple & /*tp*/) { return 0; } static size_t do_format(substr buf, csubstr fmt, std::tuple const& /*tp*/) { return to_chars(buf, fmt); } static size_t do_unformat(csubstr buf, csubstr fmt, std::tuple const& /*tp*/) { return 0; } }; } // namespace detail template< class... Types > inline size_t cat(substr buf, std::tuple< Types... > const& tp) { return detail::tuple_helper< 0, Types... >::do_cat(buf, tp); } template< class... Types > inline size_t uncat(csubstr buf, std::tuple< Types... > & tp) { return detail::tuple_helper< 0, Types... >::do_uncat(buf, tp); } template< class Sep, class... Types > inline size_t catsep(substr buf, Sep const& sep, std::tuple< Types... > const& tp) { size_t num = to_chars(buf, std::cref(std::get<0>(tp))); buf = buf.len >= num ? buf.sub(num) : substr{}; num += detail::tuple_helper< 1, Types... >::do_catsep_more(buf, sep, tp); return num; } template< class Sep, class... Types > inline size_t uncatsep(csubstr buf, Sep & sep, std::tuple< Types... > & tp) { size_t ret = from_str_trim(buf, &std::get<0>(tp)), num = ret; if(ret == csubstr::npos) return csubstr::npos; buf = buf.len >= ret ? buf.sub(ret) : substr{}; ret = detail::tuple_helper< 1, Types... >::do_uncatsep_more(buf, sep, tp); if(ret == csubstr::npos) return csubstr::npos; num += ret; return num; } template< class... Types > inline size_t format(substr buf, csubstr fmt, std::tuple< Types... > const& tp) { return detail::tuple_helper< 0, Types... >::do_format(buf, fmt, tp); } template< class... Types > inline size_t unformat(csubstr buf, csubstr fmt, std::tuple< Types... > & tp) { return detail::tuple_helper< 0, Types... >::do_unformat(buf, fmt, tp); } #endif // C4_TUPLE_TO_CHARS } // namespace c4 #endif /* _C4_STD_TUPLE_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/std/tuple.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/ext/rng/rng.hpp // https://github.com/biojppm/c4core/src/c4/ext/rng/rng.hpp //-------------------------------------------------------------------------------- //******************************************************************************** /* Copyright (c) 2018 Arvid Gerstmann. * * https://arvid.io/2018/07/02/better-cxx-prng/ * * This code is licensed under MIT license. */ #ifndef AG_RANDOM_H #define AG_RANDOM_H //included above: //#include #include #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wold-style-cast" #endif namespace c4 { namespace rng { class splitmix { public: using result_type = uint32_t; static constexpr result_type (min)() { return 0; } static constexpr result_type (max)() { return UINT32_MAX; } friend bool operator==(splitmix const &, splitmix const &); friend bool operator!=(splitmix const &, splitmix const &); splitmix() : m_seed(1) {} explicit splitmix(uint64_t s) : m_seed(s) {} explicit splitmix(std::random_device &rd) { seed(rd); } void seed(uint64_t s) { m_seed = s; } void seed(std::random_device &rd) { m_seed = uint64_t(rd()) << 31 | uint64_t(rd()); } result_type operator()() { uint64_t z = (m_seed += UINT64_C(0x9E3779B97F4A7C15)); z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9); z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB); return result_type((z ^ (z >> 31)) >> 31); } void discard(unsigned long long n) { for (unsigned long long i = 0; i < n; ++i) operator()(); } private: uint64_t m_seed; }; inline bool operator==(splitmix const &lhs, splitmix const &rhs) { return lhs.m_seed == rhs.m_seed; } inline bool operator!=(splitmix const &lhs, splitmix const &rhs) { return lhs.m_seed != rhs.m_seed; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class xorshift { public: using result_type = uint32_t; static constexpr result_type (min)() { return 0; } static constexpr result_type (max)() { return UINT32_MAX; } friend bool operator==(xorshift const &, xorshift const &); friend bool operator!=(xorshift const &, xorshift const &); xorshift() : m_seed(0xc1f651c67c62c6e0ull) {} explicit xorshift(std::random_device &rd) { seed(rd); } void seed(uint64_t s) { m_seed = s; } void seed(std::random_device &rd) { m_seed = uint64_t(rd()) << 31 | uint64_t(rd()); } result_type operator()() { uint64_t result = m_seed * 0xd989bcacc137dcd5ull; m_seed ^= m_seed >> 11; m_seed ^= m_seed << 31; m_seed ^= m_seed >> 18; return uint32_t(result >> 32ull); } void discard(unsigned long long n) { for (unsigned long long i = 0; i < n; ++i) operator()(); } private: uint64_t m_seed; }; inline bool operator==(xorshift const &lhs, xorshift const &rhs) { return lhs.m_seed == rhs.m_seed; } inline bool operator!=(xorshift const &lhs, xorshift const &rhs) { return lhs.m_seed != rhs.m_seed; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class pcg { public: using result_type = uint32_t; static constexpr result_type (min)() { return 0; } static constexpr result_type (max)() { return UINT32_MAX; } friend bool operator==(pcg const &, pcg const &); friend bool operator!=(pcg const &, pcg const &); pcg() : m_state(0x853c49e6748fea9bULL) , m_inc(0xda3e39cb94b95bdbULL) {} explicit pcg(uint64_t s) { m_state = s; m_inc = m_state << 1; } explicit pcg(std::random_device &rd) { seed(rd); } void seed(uint64_t s) { m_state = s; } void seed(std::random_device &rd) { uint64_t s0 = uint64_t(rd()) << 31 | uint64_t(rd()); uint64_t s1 = uint64_t(rd()) << 31 | uint64_t(rd()); m_state = 0; m_inc = (s1 << 1) | 1; (void)operator()(); m_state += s0; (void)operator()(); } result_type operator()() { uint64_t oldstate = m_state; m_state = oldstate * 6364136223846793005ULL + m_inc; uint32_t xorshifted = uint32_t(((oldstate >> 18u) ^ oldstate) >> 27u); //int rot = oldstate >> 59u; // the original. error? int64_t rot = (int64_t)oldstate >> 59u; // error? return (xorshifted >> rot) | (xorshifted << ((uint64_t)(-rot) & 31)); } void discard(unsigned long long n) { for (unsigned long long i = 0; i < n; ++i) operator()(); } private: uint64_t m_state; uint64_t m_inc; }; inline bool operator==(pcg const &lhs, pcg const &rhs) { return lhs.m_state == rhs.m_state && lhs.m_inc == rhs.m_inc; } inline bool operator!=(pcg const &lhs, pcg const &rhs) { return lhs.m_state != rhs.m_state || lhs.m_inc != rhs.m_inc; } } // namespace rng } // namespace c4 #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif /* AG_RANDOM_H */ // (end https://github.com/biojppm/c4core/src/c4/ext/rng/rng.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/ext/sg14/inplace_function.h // https://github.com/biojppm/c4core/src/c4/ext/sg14/inplace_function.h //-------------------------------------------------------------------------------- //******************************************************************************** /* * Boost Software License - Version 1.0 - August 17th, 2003 * * Permission is hereby granted, free of charge, to any person or organization * obtaining a copy of the software and accompanying documentation covered by * this license (the "Software") to use, reproduce, display, distribute, * execute, and transmit the Software, and to prepare derivative works of the * Software, and to permit third-parties to whom the Software is furnished to * do so, all subject to the following: * * The copyright notices in the Software and this entire statement, including * the above license grant, this restriction and the following disclaimer, * must be included in all copies of the Software, in whole or in part, and * all derivative works of the Software, unless such copies or derivative * works are solely in the form of machine-executable object code generated by * a source language processor. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef _C4_EXT_SG14_INPLACE_FUNCTION_H_ #define _C4_EXT_SG14_INPLACE_FUNCTION_H_ //included above: //#include //included above: //#include #include //included above: //#include namespace stdext { namespace inplace_function_detail { static constexpr size_t InplaceFunctionDefaultCapacity = 32; #if defined(__GLIBCXX__) // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61458 template union aligned_storage_helper { struct double1 { double a; }; struct double4 { double a[4]; }; template using maybe = typename std::conditional<(Cap >= sizeof(T)), T, char>::type; char real_data[Cap]; maybe a; maybe b; maybe c; maybe d; maybe e; maybe f; maybe g; maybe h; }; template>::value> struct aligned_storage { using type = typename std::aligned_storage::type; }; #else using std::aligned_storage; #endif template struct wrapper { using type = T; }; template struct vtable { using storage_ptr_t = void*; using invoke_ptr_t = R(*)(storage_ptr_t, Args&&...); using process_ptr_t = void(*)(storage_ptr_t, storage_ptr_t); using destructor_ptr_t = void(*)(storage_ptr_t); const invoke_ptr_t invoke_ptr; const process_ptr_t copy_ptr; const process_ptr_t move_ptr; const destructor_ptr_t destructor_ptr; explicit constexpr vtable() noexcept : invoke_ptr{ [](storage_ptr_t, Args&&...) -> R { #if (defined(_MSC_VER) && (defined(_CPPUNWIND) && (__CPPUNWIND == 1))) \ || \ (defined(__EXCEPTIONS) || defined(__cpp_exceptions)) throw std::bad_function_call(); #else std::abort(); #endif } }, copy_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} }, move_ptr{ [](storage_ptr_t, storage_ptr_t) noexcept -> void {} }, destructor_ptr{ [](storage_ptr_t) noexcept -> void {} } {} template explicit constexpr vtable(wrapper) noexcept : invoke_ptr{ [](storage_ptr_t storage_ptr, Args&&... args) noexcept(noexcept(std::declval()(args...))) -> R { return (*static_cast(storage_ptr))( std::forward(args)... ); } }, copy_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) noexcept(std::is_nothrow_copy_constructible::value) -> void { new (dst_ptr) C{ (*static_cast(src_ptr)) }; } }, move_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) noexcept(std::is_nothrow_move_constructible::value) -> void { new (dst_ptr) C{ std::move(*static_cast(src_ptr)) }; } }, destructor_ptr{ [](storage_ptr_t storage_ptr) noexcept -> void { static_cast(storage_ptr)->~C(); } } {} vtable(const vtable&) = delete; vtable(vtable&&) = delete; vtable& operator= (const vtable&) = delete; vtable& operator= (vtable&&) = delete; ~vtable() = default; }; template struct is_valid_inplace_dst : std::true_type { static_assert(DstCap >= SrcCap, "Can't squeeze larger inplace_function into a smaller one" ); static_assert(DstAlign % SrcAlign == 0, "Incompatible inplace_function alignments" ); }; } // namespace inplace_function_detail template< typename Signature, size_t Capacity = inplace_function_detail::InplaceFunctionDefaultCapacity, size_t Alignment = std::alignment_of::type>::value > class inplace_function; // unspecified template< typename R, typename... Args, size_t Capacity, size_t Alignment > class inplace_function { static const constexpr inplace_function_detail::vtable empty_vtable{}; public: using capacity = std::integral_constant; using alignment = std::integral_constant; using storage_t = typename inplace_function_detail::aligned_storage::type; using vtable_t = inplace_function_detail::vtable; using vtable_ptr_t = const vtable_t*; template friend class inplace_function; inplace_function() noexcept : vtable_ptr_{std::addressof(empty_vtable)} {} template< typename T, typename C = typename std::decay::type, typename = typename std::enable_if< !(std::is_same::value || std::is_convertible::value) >::type > inplace_function(T&& closure) { #if __cplusplus >= 201703L static_assert(std::is_invocable_r::value, "inplace_function cannot be constructed from non-callable type" ); #endif static_assert(std::is_copy_constructible::value, "inplace_function cannot be constructed from non-copyable type" ); static_assert(sizeof(C) <= Capacity, "inplace_function cannot be constructed from object with this (large) size" ); static_assert(Alignment % std::alignment_of::value == 0, "inplace_function cannot be constructed from object with this (large) alignment" ); static const vtable_t vt{inplace_function_detail::wrapper{}}; vtable_ptr_ = std::addressof(vt); new (std::addressof(storage_)) C{std::forward(closure)}; } inplace_function(std::nullptr_t) noexcept : vtable_ptr_{std::addressof(empty_vtable)} {} inplace_function(const inplace_function& other) : vtable_ptr_{other.vtable_ptr_} { vtable_ptr_->copy_ptr( std::addressof(storage_), std::addressof(other.storage_) ); } inplace_function(inplace_function&& other) : vtable_ptr_{other.vtable_ptr_} { vtable_ptr_->move_ptr( std::addressof(storage_), std::addressof(other.storage_) ); } inplace_function& operator= (std::nullptr_t) noexcept { vtable_ptr_->destructor_ptr(std::addressof(storage_)); vtable_ptr_ = std::addressof(empty_vtable); return *this; } inplace_function& operator= (const inplace_function& other) { if(this != std::addressof(other)) { vtable_ptr_->destructor_ptr(std::addressof(storage_)); vtable_ptr_ = other.vtable_ptr_; vtable_ptr_->copy_ptr( std::addressof(storage_), std::addressof(other.storage_) ); } return *this; } inplace_function& operator= (inplace_function&& other) { if(this != std::addressof(other)) { vtable_ptr_->destructor_ptr(std::addressof(storage_)); vtable_ptr_ = other.vtable_ptr_; vtable_ptr_->move_ptr( std::addressof(storage_), std::addressof(other.storage_) ); } return *this; } ~inplace_function() { vtable_ptr_->destructor_ptr(std::addressof(storage_)); } R operator() (Args... args) const { return vtable_ptr_->invoke_ptr( std::addressof(storage_), std::forward(args)... ); } constexpr bool operator== (std::nullptr_t) const noexcept { return !operator bool(); } constexpr bool operator!= (std::nullptr_t) const noexcept { return operator bool(); } explicit constexpr operator bool() const noexcept { return vtable_ptr_ != std::addressof(empty_vtable); } template operator inplace_function() const& { static_assert(inplace_function_detail::is_valid_inplace_dst< Cap, Align, Capacity, Alignment >::value, "conversion not allowed"); return {vtable_ptr_, vtable_ptr_->copy_ptr, std::addressof(storage_)}; } template operator inplace_function() && { static_assert(inplace_function_detail::is_valid_inplace_dst< Cap, Align, Capacity, Alignment >::value, "conversion not allowed"); return {vtable_ptr_, vtable_ptr_->move_ptr, std::addressof(storage_)}; } void swap(inplace_function& other) { if (this == std::addressof(other)) return; storage_t tmp; vtable_ptr_->move_ptr( std::addressof(tmp), std::addressof(storage_) ); vtable_ptr_->destructor_ptr(std::addressof(storage_)); other.vtable_ptr_->move_ptr( std::addressof(storage_), std::addressof(other.storage_) ); other.vtable_ptr_->destructor_ptr(std::addressof(other.storage_)); vtable_ptr_->move_ptr( std::addressof(other.storage_), std::addressof(tmp) ); vtable_ptr_->destructor_ptr(std::addressof(tmp)); std::swap(vtable_ptr_, other.vtable_ptr_); } private: vtable_ptr_t vtable_ptr_; mutable storage_t storage_; inplace_function( vtable_ptr_t vtable_ptr, typename vtable_t::process_ptr_t process_ptr, typename vtable_t::storage_ptr_t storage_ptr ) : vtable_ptr_{vtable_ptr} { process_ptr(std::addressof(storage_), storage_ptr); } }; } // namespace stdext #endif /* _C4_EXT_SG14_INPLACE_FUNCTION_H_ */ // (end https://github.com/biojppm/c4core/src/c4/ext/sg14/inplace_function.h) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/version.cpp // https://github.com/biojppm/c4core/src/c4/version.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef C4CORE_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/version.hpp //#include "c4/version.hpp" #if !defined(C4_VERSION_HPP_) && !defined(_C4_VERSION_HPP_) #error "amalgamate: file c4/version.hpp must have been included at this point" #endif /* C4_VERSION_HPP_ */ namespace c4 { const char* version() { return C4CORE_VERSION; } int version_major() { return C4CORE_VERSION_MAJOR; } int version_minor() { return C4CORE_VERSION_MINOR; } int version_patch() { return C4CORE_VERSION_PATCH; } } // namespace c4 #endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/c4core/src/c4/version.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/language.cpp // https://github.com/biojppm/c4core/src/c4/language.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef C4CORE_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/language.hpp //#include "c4/language.hpp" #if !defined(C4_LANGUAGE_HPP_) && !defined(_C4_LANGUAGE_HPP_) #error "amalgamate: file c4/language.hpp must have been included at this point" #endif /* C4_LANGUAGE_HPP_ */ namespace c4 { namespace detail { #ifndef __GNUC__ void use_char_pointer(char const volatile* v) { C4_UNUSED(v); } #else void foo() {} // to avoid empty file warning from the linker #endif } // namespace detail } // namespace c4 #endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/c4core/src/c4/language.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/format.cpp // https://github.com/biojppm/c4core/src/c4/format.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef C4CORE_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/format.hpp //#include "c4/format.hpp" #if !defined(C4_FORMAT_HPP_) && !defined(_C4_FORMAT_HPP_) #error "amalgamate: file c4/format.hpp must have been included at this point" #endif /* C4_FORMAT_HPP_ */ //included above: //#include // for std::align #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wformat-nonliteral" # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wformat-nonliteral" # pragma GCC diagnostic ignored "-Wold-style-cast" #endif namespace c4 { size_t to_chars(substr buf, fmt::const_raw_wrapper r) { void * vptr = buf.str; size_t space = buf.len; char * ptr = (char*) std::align(r.alignment, r.len, vptr, space); if(ptr == nullptr) { // if it was not possible to align, return a conservative estimate // of the required space return r.alignment + r.len; } C4_CHECK(ptr >= buf.begin() && ptr <= buf.end()); size_t sz = static_cast(ptr - buf.str) + r.len; if(sz <= buf.len) { memcpy(ptr, r.buf, r.len); } return sz; } bool from_chars(csubstr buf, fmt::raw_wrapper *r) { C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wcast-qual") void * vptr = (void*)buf.str; C4_SUPPRESS_WARNING_GCC_POP size_t space = buf.len; char * ptr = (char*) std::align(r->alignment, r->len, vptr, space); C4_CHECK(ptr != nullptr); C4_CHECK(ptr >= buf.begin() && ptr <= buf.end()); C4_SUPPRESS_WARNING_GCC_PUSH #if defined(__GNUC__) && __GNUC__ > 9 C4_SUPPRESS_WARNING_GCC("-Wanalyzer-null-argument") #endif memcpy(r->buf, ptr, r->len); C4_SUPPRESS_WARNING_GCC_POP return true; } } // namespace c4 #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/c4core/src/c4/format.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/memory_util.cpp // https://github.com/biojppm/c4core/src/c4/memory_util.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef C4CORE_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/memory_util.hpp //#include "c4/memory_util.hpp" #if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) #error "amalgamate: file c4/memory_util.hpp must have been included at this point" #endif /* C4_MEMORY_UTIL_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ namespace c4 { /** Fills 'dest' with the first 'pattern_size' bytes at 'pattern', 'num_times'. */ void mem_repeat(void* dest, void const* pattern, size_t pattern_size, size_t num_times) { if(C4_UNLIKELY(num_times == 0)) return; C4_ASSERT( ! mem_overlaps(dest, pattern, num_times*pattern_size, pattern_size)); char *begin = static_cast(dest); char *end = begin + num_times * pattern_size; // copy the pattern once ::memcpy(begin, pattern, pattern_size); // now copy from dest to itself, doubling up every time size_t n = pattern_size; while(begin + 2*n < end) { ::memcpy(begin + n, begin, n); n <<= 1u; // double n } // copy the missing part if(begin + n < end) { ::memcpy(begin + n, begin, static_cast(end - (begin + n))); } } } // namespace c4 #endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/c4core/src/c4/memory_util.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/char_traits.cpp // https://github.com/biojppm/c4core/src/c4/char_traits.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef C4CORE_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/char_traits.hpp //#include "c4/char_traits.hpp" #if !defined(C4_CHAR_TRAITS_HPP_) && !defined(_C4_CHAR_TRAITS_HPP_) #error "amalgamate: file c4/char_traits.hpp must have been included at this point" #endif /* C4_CHAR_TRAITS_HPP_ */ namespace c4 { constexpr const char char_traits< char >::whitespace_chars[]; constexpr const size_t char_traits< char >::num_whitespace_chars; constexpr const wchar_t char_traits< wchar_t >::whitespace_chars[]; constexpr const size_t char_traits< wchar_t >::num_whitespace_chars; } // namespace c4 #endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/c4core/src/c4/char_traits.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/memory_resource.cpp // https://github.com/biojppm/c4core/src/c4/memory_resource.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef C4CORE_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/memory_resource.hpp //#include "c4/memory_resource.hpp" #if !defined(C4_MEMORY_RESOURCE_HPP_) && !defined(_C4_MEMORY_RESOURCE_HPP_) #error "amalgamate: file c4/memory_resource.hpp must have been included at this point" #endif /* C4_MEMORY_RESOURCE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/memory_util.hpp //#include "c4/memory_util.hpp" #if !defined(C4_MEMORY_UTIL_HPP_) && !defined(_C4_MEMORY_UTIL_HPP_) #error "amalgamate: file c4/memory_util.hpp must have been included at this point" #endif /* C4_MEMORY_UTIL_HPP_ */ //included above: //#include //included above: //#include #if defined(C4_POSIX) || defined(C4_IOS) || defined(C4_MACOS) || defined(C4_ARM) # include #endif #if defined(C4_ARM) # include #endif //included above: //#include namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") namespace detail { #ifdef C4_NO_ALLOC_DEFAULTS aalloc_pfn s_aalloc = nullptr; free_pfn s_afree = nullptr; arealloc_pfn s_arealloc = nullptr; #else void afree_impl(void *ptr) { #if defined(C4_WIN) || defined(C4_XBOX) ::_aligned_free(ptr); #else ::free(ptr); #endif } void* aalloc_impl(size_t size, size_t alignment) { // alignment must be nonzero and a power of 2 C4_CHECK(alignment > 0 && (alignment & (alignment - 1u)) == 0); // NOTE: alignment needs to be sized in multiples of sizeof(void*) if(C4_UNLIKELY(alignment < sizeof(void*))) alignment = sizeof(void*); static_assert((sizeof(void*) & (sizeof(void*)-1u)) == 0, "sizeof(void*) must be a power of 2"); C4_CHECK(((alignment & (sizeof(void*) - 1u))) == 0u); void *mem; #if defined(C4_WIN) || defined(C4_XBOX) mem = ::_aligned_malloc(size, alignment); C4_CHECK(mem != nullptr || size == 0); #elif defined(C4_POSIX) || defined(C4_IOS) || defined(C4_MACOS) int ret = ::posix_memalign(&mem, alignment, size); if(C4_UNLIKELY(ret)) { C4_ASSERT(ret != EINVAL); // this was already handled above if(ret == ENOMEM) { C4_ERROR("There was insufficient memory to fulfill the " "allocation request of %zu bytes (alignment=%lu)", size, size); } return nullptr; } #elif defined(C4_ARM) || defined(C4_ANDROID) // https://stackoverflow.com/questions/53614538/undefined-reference-to-posix-memalign-in-arm-gcc // https://electronics.stackexchange.com/questions/467382/e2-studio-undefined-reference-to-posix-memalign/467753 mem = memalign(alignment, size); C4_CHECK(mem != nullptr || size == 0); #else (void)size; (void)alignment; mem = nullptr; C4_NOT_IMPLEMENTED_MSG("need to implement an aligned allocation for this platform"); #endif C4_ASSERT_MSG((uintptr_t(mem) & (alignment-1)) == 0, "address %p is not aligned to %zu boundary", mem, alignment); return mem; } void* arealloc_impl(void* ptr, size_t oldsz, size_t newsz, size_t alignment) { /** @todo make this more efficient * @see https://stackoverflow.com/questions/9078259/does-realloc-keep-the-memory-alignment-of-posix-memalign * @see look for qReallocAligned() in http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qmalloc.cpp */ void *tmp = aalloc(newsz, alignment); size_t min = newsz < oldsz ? newsz : oldsz; if(mem_overlaps(ptr, tmp, oldsz, newsz)) { ::memmove(tmp, ptr, min); } else { ::memcpy(tmp, ptr, min); } afree(ptr); return tmp; } aalloc_pfn s_aalloc = aalloc_impl; afree_pfn s_afree = afree_impl; arealloc_pfn s_arealloc = arealloc_impl; #endif // C4_NO_ALLOC_DEFAULTS } // namespace detail aalloc_pfn get_aalloc() { return detail::s_aalloc; } void set_aalloc(aalloc_pfn fn) { detail::s_aalloc = fn; } afree_pfn get_afree() { return detail::s_afree; } void set_afree(afree_pfn fn) { detail::s_afree = fn; } arealloc_pfn get_arealloc() { return detail::s_arealloc; } void set_arealloc(arealloc_pfn fn) { detail::s_arealloc = fn; } void* aalloc(size_t sz, size_t alignment) { C4_ASSERT_MSG(c4::get_aalloc() != nullptr, "did you forget to call set_aalloc()?"); auto fn = c4::get_aalloc(); void* ptr = fn(sz, alignment); return ptr; } void afree(void* ptr) { C4_ASSERT_MSG(c4::get_afree() != nullptr, "did you forget to call set_afree()?"); auto fn = c4::get_afree(); fn(ptr); } void* arealloc(void *ptr, size_t oldsz, size_t newsz, size_t alignment) { C4_ASSERT_MSG(c4::get_arealloc() != nullptr, "did you forget to call set_arealloc()?"); auto fn = c4::get_arealloc(); void* nptr = fn(ptr, oldsz, newsz, alignment); return nptr; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void detail::_MemoryResourceSingleChunk::release() { if(m_mem && m_owner) { impl_type::deallocate(m_mem, m_size); } m_mem = nullptr; m_size = 0; m_owner = false; m_pos = 0; } void detail::_MemoryResourceSingleChunk::acquire(size_t sz) { clear(); m_owner = true; m_mem = (char*) impl_type::allocate(sz, alignof(max_align_t)); m_size = sz; m_pos = 0; } void detail::_MemoryResourceSingleChunk::acquire(void *mem, size_t sz) { clear(); m_owner = false; m_mem = (char*) mem; m_size = sz; m_pos = 0; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void* MemoryResourceLinear::do_allocate(size_t sz, size_t alignment, void *hint) { C4_UNUSED(hint); if(sz == 0) return nullptr; // make sure there's enough room to allocate if(m_pos + sz > m_size) { C4_ERROR("out of memory"); } void *mem = m_mem + m_pos; size_t space = m_size - m_pos; if(std::align(alignment, sz, mem, space)) { C4_ASSERT(m_pos <= m_size); C4_ASSERT(m_size - m_pos >= space); m_pos += (m_size - m_pos) - space; m_pos += sz; C4_ASSERT(m_pos <= m_size); } else { C4_ERROR("could not align memory"); } return mem; } void MemoryResourceLinear::do_deallocate(void* ptr, size_t sz, size_t alignment) { C4_UNUSED(ptr); C4_UNUSED(sz); C4_UNUSED(alignment); // nothing to do!! } void* MemoryResourceLinear::do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) { if(newsz == oldsz) return ptr; // is ptr the most recently allocated (MRA) block? char *cptr = (char*)ptr; bool same_pos = (m_mem + m_pos == cptr + oldsz); // no need to get more memory when shrinking if(newsz < oldsz) { // if this is the MRA, we can safely shrink the position if(same_pos) { m_pos -= oldsz - newsz; } return ptr; } // we're growing the block, and it fits in size else if(same_pos && cptr + newsz <= m_mem + m_size) { // if this is the MRA, we can safely shrink the position m_pos += newsz - oldsz; return ptr; } // we're growing the block or it doesn't fit - // delegate any of these situations to do_deallocate() return do_allocate(newsz, alignment, ptr); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @todo add a free list allocator. A good candidate because of its * small size is TLSF. * * @see https://github.com/mattconte/tlsf * * Comparisons: * * @see https://www.researchgate.net/publication/262375150_A_Comparative_Study_on_Memory_Allocators_in_Multicore_and_Multithreaded_Applications_-_SBESC_2011_-_Presentation_Slides * @see http://webkit.sed.hu/blog/20100324/war-allocators-tlsf-action * @see https://github.com/emeryberger/Malloc-Implementations/tree/master/allocators * * */ C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- #ifdef C4_REDEFINE_CPPNEW #include void* operator new(size_t size) { auto *mr = ::c4::get_memory_resource(); return mr->allocate(size); } void operator delete(void *p) noexcept { C4_NEVER_REACH(); } void operator delete(void *p, size_t size) { auto *mr = ::c4::get_memory_resource(); mr->deallocate(p, size); } void* operator new[](size_t size) { return operator new(size); } void operator delete[](void *p) noexcept { operator delete(p); } void operator delete[](void *p, size_t size) { operator delete(p, size); } void* operator new(size_t size, std::nothrow_t) { return operator new(size); } void operator delete(void *p, std::nothrow_t) { operator delete(p); } void operator delete(void *p, size_t size, std::nothrow_t) { operator delete(p, size); } void* operator new[](size_t size, std::nothrow_t) { return operator new(size); } void operator delete[](void *p, std::nothrow_t) { operator delete(p); } void operator delete[](void *p, size_t, std::nothrow_t) { operator delete(p, size); } #endif // C4_REDEFINE_CPPNEW #endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/c4core/src/c4/memory_resource.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/utf.cpp // https://github.com/biojppm/c4core/src/c4/utf.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef C4CORE_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/utf.hpp //#include "c4/utf.hpp" #if !defined(C4_UTF_HPP_) && !defined(_C4_UTF_HPP_) #error "amalgamate: file c4/utf.hpp must have been included at this point" #endif /* C4_UTF_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/charconv.hpp //#include "c4/charconv.hpp" #if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) #error "amalgamate: file c4/charconv.hpp must have been included at this point" #endif /* C4_CHARCONV_HPP_ */ namespace c4 { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") size_t decode_code_point(uint8_t *C4_RESTRICT buf, size_t buflen, const uint32_t code) { C4_ASSERT(buf); C4_ASSERT(buflen >= 4); C4_UNUSED(buflen); if (code <= UINT32_C(0x7f)) { buf[0] = (uint8_t)code; return 1u; } else if(code <= UINT32_C(0x7ff)) { buf[0] = (uint8_t)(UINT32_C(0xc0) | (code >> 6u)); /* 110xxxxx */ buf[1] = (uint8_t)(UINT32_C(0x80) | (code & UINT32_C(0x3f))); /* 10xxxxxx */ return 2u; } else if(code <= UINT32_C(0xffff)) { buf[0] = (uint8_t)(UINT32_C(0xe0) | ((code >> 12u))); /* 1110xxxx */ buf[1] = (uint8_t)(UINT32_C(0x80) | ((code >> 6u) & UINT32_C(0x3f))); /* 10xxxxxx */ buf[2] = (uint8_t)(UINT32_C(0x80) | ((code ) & UINT32_C(0x3f))); /* 10xxxxxx */ return 3u; } else if(code <= UINT32_C(0x10ffff)) { buf[0] = (uint8_t)(UINT32_C(0xf0) | ((code >> 18u))); /* 11110xxx */ buf[1] = (uint8_t)(UINT32_C(0x80) | ((code >> 12u) & UINT32_C(0x3f))); /* 10xxxxxx */ buf[2] = (uint8_t)(UINT32_C(0x80) | ((code >> 6u) & UINT32_C(0x3f))); /* 10xxxxxx */ buf[3] = (uint8_t)(UINT32_C(0x80) | ((code ) & UINT32_C(0x3f))); /* 10xxxxxx */ return 4u; } return 0; } substr decode_code_point(substr out, csubstr code_point) { C4_ASSERT(out.len >= 4); C4_ASSERT(!code_point.begins_with("U+")); C4_ASSERT(!code_point.begins_with("\\x")); C4_ASSERT(!code_point.begins_with("\\u")); C4_ASSERT(!code_point.begins_with("\\U")); C4_ASSERT(!code_point.begins_with('0')); C4_ASSERT(code_point.len <= 8); C4_ASSERT(code_point.len > 0); uint32_t code_point_val; C4_CHECK(read_hex(code_point, &code_point_val)); size_t ret = decode_code_point((uint8_t*)out.str, out.len, code_point_val); C4_ASSERT(ret <= 4); return out.first(ret); } size_t first_non_bom(csubstr s) { #define c4check2_(s, c0, c1) ((s).len >= 2) && (((s).str[0] == (c0)) && ((s).str[1] == (c1))) #define c4check3_(s, c0, c1, c2) ((s).len >= 3) && (((s).str[0] == (c0)) && ((s).str[1] == (c1)) && ((s).str[2] == (c2))) #define c4check4_(s, c0, c1, c2, c3) ((s).len >= 4) && (((s).str[0] == (c0)) && ((s).str[1] == (c1)) && ((s).str[2] == (c2)) && ((s).str[3] == (c3))) // see https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding if(s.len < 2u) return false; else if(c4check3_(s, '\xef', '\xbb', '\xbf')) // UTF-8 return 3u; else if(c4check4_(s, '\x00', '\x00', '\xfe', '\xff')) // UTF-32BE return 4u; else if(c4check4_(s, '\xff', '\xfe', '\x00', '\x00')) // UTF-32LE return 4u; else if(c4check2_(s, '\xfe', '\xff')) // UTF-16BE return 2u; else if(c4check2_(s, '\xff', '\xfe')) // UTF-16BE return 2u; else if(c4check3_(s, '\x2b', '\x2f', '\x76')) // UTF-7 return 3u; else if(c4check3_(s, '\xf7', '\x64', '\x4c')) // UTF-1 return 3u; else if(c4check4_(s, '\xdd', '\x73', '\x66', '\x73')) // UTF-EBCDIC return 4u; else if(c4check3_(s, '\x0e', '\xfe', '\xff')) // SCSU return 3u; else if(c4check3_(s, '\xfb', '\xee', '\x28')) // BOCU-1 return 3u; else if(c4check4_(s, '\x84', '\x31', '\x95', '\x33')) // GB18030 return 4u; return 0u; #undef c4check2_ #undef c4check3_ #undef c4check4_ } substr get_bom(substr s) { return s.first(first_non_bom(s)); } csubstr get_bom(csubstr s) { return s.first(first_non_bom(s)); } substr skip_bom(substr s) { return s.sub(first_non_bom(s)); } csubstr skip_bom(csubstr s) { return s.sub(first_non_bom(s)); } C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace c4 #endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/c4core/src/c4/utf.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/base64.cpp // https://github.com/biojppm/c4core/src/c4/base64.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef C4CORE_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/base64.hpp //#include "c4/base64.hpp" #if !defined(C4_BASE64_HPP_) && !defined(_C4_BASE64_HPP_) #error "amalgamate: file c4/base64.hpp must have been included at this point" #endif /* C4_BASE64_HPP_ */ #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wchar-subscripts" // array subscript is of type 'char' # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wuseless-cast" # pragma GCC diagnostic ignored "-Wchar-subscripts" # pragma GCC diagnostic ignored "-Wtype-limits" # pragma GCC diagnostic ignored "-Wold-style-cast" #endif // NOLINTBEGIN(bugprone-signed-char-misuse,cert-str34-c,hicpp-signed-bitwise) namespace c4 { namespace detail { constexpr static const char base64_sextet_to_char_[64] = { /* 0/ 65*/ 'A', /* 1/ 66*/ 'B', /* 2/ 67*/ 'C', /* 3/ 68*/ 'D', /* 4/ 69*/ 'E', /* 5/ 70*/ 'F', /* 6/ 71*/ 'G', /* 7/ 72*/ 'H', /* 8/ 73*/ 'I', /* 9/ 74*/ 'J', /*10/ 75*/ 'K', /*11/ 74*/ 'L', /*12/ 77*/ 'M', /*13/ 78*/ 'N', /*14/ 79*/ 'O', /*15/ 78*/ 'P', /*16/ 81*/ 'Q', /*17/ 82*/ 'R', /*18/ 83*/ 'S', /*19/ 82*/ 'T', /*20/ 85*/ 'U', /*21/ 86*/ 'V', /*22/ 87*/ 'W', /*23/ 88*/ 'X', /*24/ 89*/ 'Y', /*25/ 90*/ 'Z', /*26/ 97*/ 'a', /*27/ 98*/ 'b', /*28/ 99*/ 'c', /*29/100*/ 'd', /*30/101*/ 'e', /*31/102*/ 'f', /*32/103*/ 'g', /*33/104*/ 'h', /*34/105*/ 'i', /*35/106*/ 'j', /*36/107*/ 'k', /*37/108*/ 'l', /*38/109*/ 'm', /*39/110*/ 'n', /*40/111*/ 'o', /*41/112*/ 'p', /*42/113*/ 'q', /*43/114*/ 'r', /*44/115*/ 's', /*45/116*/ 't', /*46/117*/ 'u', /*47/118*/ 'v', /*48/119*/ 'w', /*49/120*/ 'x', /*50/121*/ 'y', /*51/122*/ 'z', /*52/ 48*/ '0', /*53/ 49*/ '1', /*54/ 50*/ '2', /*55/ 51*/ '3', /*56/ 52*/ '4', /*57/ 53*/ '5', /*58/ 54*/ '6', /*59/ 55*/ '7', /*60/ 56*/ '8', /*61/ 57*/ '9', /*62/ 43*/ '+', /*63/ 47*/ '/', }; // https://www.cs.cmu.edu/~pattis/15-1XX/common/handouts/ascii.html constexpr static const char base64_char_to_sextet_[128] = { #define __ char(-1) // undefined below /* 0 NUL*/ __, /* 1 SOH*/ __, /* 2 STX*/ __, /* 3 ETX*/ __, /* 4 EOT*/ __, /* 5 ENQ*/ __, /* 6 ACK*/ __, /* 7 BEL*/ __, /* 8 BS */ __, /* 9 TAB*/ __, /* 10 LF */ __, /* 11 VT */ __, /* 12 FF */ __, /* 13 CR */ __, /* 14 SO */ __, /* 15 SI */ __, /* 16 DLE*/ __, /* 17 DC1*/ __, /* 18 DC2*/ __, /* 19 DC3*/ __, /* 20 DC4*/ __, /* 21 NAK*/ __, /* 22 SYN*/ __, /* 23 ETB*/ __, /* 24 CAN*/ __, /* 25 EM */ __, /* 26 SUB*/ __, /* 27 ESC*/ __, /* 28 FS */ __, /* 29 GS */ __, /* 30 RS */ __, /* 31 US */ __, /* 32 SPC*/ __, /* 33 ! */ __, /* 34 " */ __, /* 35 # */ __, /* 36 $ */ __, /* 37 % */ __, /* 38 & */ __, /* 39 ' */ __, /* 40 ( */ __, /* 41 ) */ __, /* 42 * */ __, /* 43 + */ 62, /* 44 , */ __, /* 45 - */ __, /* 46 . */ __, /* 47 / */ 63, /* 48 0 */ 52, /* 49 1 */ 53, /* 50 2 */ 54, /* 51 3 */ 55, /* 52 4 */ 56, /* 53 5 */ 57, /* 54 6 */ 58, /* 55 7 */ 59, /* 56 8 */ 60, /* 57 9 */ 61, /* 58 : */ __, /* 59 ; */ __, /* 60 < */ __, /* 61 = */ __, /* 62 > */ __, /* 63 ? */ __, /* 64 @ */ __, /* 65 A */ 0, /* 66 B */ 1, /* 67 C */ 2, /* 68 D */ 3, /* 69 E */ 4, /* 70 F */ 5, /* 71 G */ 6, /* 72 H */ 7, /* 73 I */ 8, /* 74 J */ 9, /* 75 K */ 10, /* 76 L */ 11, /* 77 M */ 12, /* 78 N */ 13, /* 79 O */ 14, /* 80 P */ 15, /* 81 Q */ 16, /* 82 R */ 17, /* 83 S */ 18, /* 84 T */ 19, /* 85 U */ 20, /* 86 V */ 21, /* 87 W */ 22, /* 88 X */ 23, /* 89 Y */ 24, /* 90 Z */ 25, /* 91 [ */ __, /* 92 \ */ __, /* 93 ] */ __, /* 94 ^ */ __, /* 95 _ */ __, /* 96 ` */ __, /* 97 a */ 26, /* 98 b */ 27, /* 99 c */ 28, /*100 d */ 29, /*101 e */ 30, /*102 f */ 31, /*103 g */ 32, /*104 h */ 33, /*105 i */ 34, /*106 j */ 35, /*107 k */ 36, /*108 l */ 37, /*109 m */ 38, /*110 n */ 39, /*111 o */ 40, /*112 p */ 41, /*113 q */ 42, /*114 r */ 43, /*115 s */ 44, /*116 t */ 45, /*117 u */ 46, /*118 v */ 47, /*119 w */ 48, /*120 x */ 49, /*121 y */ 50, /*122 z */ 51, /*123 { */ __, /*124 | */ __, /*125 } */ __, /*126 ~ */ __, /*127 DEL*/ __, #undef __ }; #ifndef NDEBUG void base64_test_tables() { for(size_t i = 0; i < C4_COUNTOF(detail::base64_sextet_to_char_); ++i) { char s2c = base64_sextet_to_char_[i]; char c2s = base64_char_to_sextet_[(unsigned)s2c]; C4_CHECK((size_t)c2s == i); } for(size_t i = 0; i < C4_COUNTOF(detail::base64_char_to_sextet_); ++i) { char c2s = base64_char_to_sextet_[i]; if(c2s == char(-1)) continue; char s2c = base64_sextet_to_char_[(unsigned)c2s]; C4_CHECK((size_t)s2c == i); } } #endif } // namespace detail bool base64_valid(csubstr encoded) { if((encoded.len & size_t(3u)) != size_t(0)) // (encoded.len % 4u) return false; for(const char c : encoded) { if(c < 0/* || c >= 128*/) return false; if(c == '=') continue; if(detail::base64_char_to_sextet_[c] == char(-1)) return false; } return true; } size_t base64_encode(substr buf, cblob data) { #define c4append_(c) { if(pos < buf.len) { buf.str[pos] = (c); } ++pos; } #define c4append_idx_(char_idx) \ {\ C4_XASSERT((char_idx) < sizeof(detail::base64_sextet_to_char_));\ c4append_(detail::base64_sextet_to_char_[(char_idx)]);\ } size_t rem, pos = 0; constexpr const uint32_t sextet_mask = uint32_t(1 << 6) - 1; const unsigned char *C4_RESTRICT d = (const unsigned char *) data.buf; // cast to unsigned to avoid wrapping high-bits for(rem = data.len; rem >= 3; rem -= 3, d += 3) { const uint32_t val = ((uint32_t(d[0]) << 16) | (uint32_t(d[1]) << 8) | (uint32_t(d[2]))); c4append_idx_((val >> 18) & sextet_mask); c4append_idx_((val >> 12) & sextet_mask); c4append_idx_((val >> 6) & sextet_mask); c4append_idx_((val ) & sextet_mask); } C4_ASSERT(rem < 3); if(rem == 2) { const uint32_t val = ((uint32_t(d[0]) << 16) | (uint32_t(d[1]) << 8)); c4append_idx_((val >> 18) & sextet_mask); c4append_idx_((val >> 12) & sextet_mask); c4append_idx_((val >> 6) & sextet_mask); c4append_('='); } else if(rem == 1) { const uint32_t val = ((uint32_t(d[0]) << 16)); c4append_idx_((val >> 18) & sextet_mask); c4append_idx_((val >> 12) & sextet_mask); c4append_('='); c4append_('='); } return pos; #undef c4append_ #undef c4append_idx_ } size_t base64_decode(csubstr encoded, blob data) { #define c4append_(c) { if(wpos < data.len) { data.buf[wpos] = static_cast(c); } ++wpos; } #define c4appendval_(c, shift)\ {\ C4_XASSERT((c) >= 0);\ C4_XASSERT(size_t(c) < sizeof(detail::base64_char_to_sextet_));\ val |= static_cast(detail::base64_char_to_sextet_[(c)]) << ((shift) * 6);\ } C4_ASSERT(base64_valid(encoded)); C4_CHECK((encoded.len & 3u) == 0); size_t wpos = 0; // the write position const char *C4_RESTRICT d = encoded.str; constexpr const uint32_t full_byte = 0xff; // process every quartet of input 6 bits --> triplet of output bytes for(size_t rpos = 0; rpos < encoded.len; rpos += 4, d += 4) { if(d[2] == '=' || d[3] == '=') // skip the last quartet if it is padded { C4_ASSERT(d + 4 == encoded.str + encoded.len); break; } uint32_t val = 0; c4appendval_(d[3], 0); c4appendval_(d[2], 1); c4appendval_(d[1], 2); c4appendval_(d[0], 3); c4append_((val >> (2 * 8)) & full_byte); c4append_((val >> (1 * 8)) & full_byte); c4append_((val ) & full_byte); } // deal with the last quartet when it is padded if(d == encoded.str + encoded.len) return wpos; if(d[2] == '=') // 2 padding chars { C4_ASSERT(d + 4 == encoded.str + encoded.len); C4_ASSERT(d[3] == '='); uint32_t val = 0; c4appendval_(d[1], 2); c4appendval_(d[0], 3); c4append_((val >> (2 * 8)) & full_byte); } else if(d[3] == '=') // 1 padding char { C4_ASSERT(d + 4 == encoded.str + encoded.len); uint32_t val = 0; c4appendval_(d[2], 1); c4appendval_(d[1], 2); c4appendval_(d[0], 3); c4append_((val >> (2 * 8)) & full_byte); c4append_((val >> (1 * 8)) & full_byte); } return wpos; #undef c4append_ #undef c4appendval_ } } // namespace c4 // NOLINTEND(bugprone-signed-char-misuse,cert-str34-c,hicpp-signed-bitwise) #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/c4core/src/c4/base64.cpp) #define C4_WINDOWS_POP_HPP_ //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/windows_push.hpp // https://github.com/biojppm/c4core/src/c4/windows_push.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_WINDOWS_PUSH_HPP_ #define _C4_WINDOWS_PUSH_HPP_ /** @file windows_push.hpp sets up macros to include windows header files * without pulling in all of * * @see #include windows_pop.hpp to undefine these macros * * @see https://aras-p.info/blog/2018/01/12/Minimizing-windows.h/ */ #if defined(_WIN64) || defined(_WIN32) #if defined(_M_AMD64) # ifndef _AMD64_ # define _c4_AMD64_ # define _AMD64_ # endif #elif defined(_M_IX86) # ifndef _X86_ # define _c4_X86_ # define _X86_ # endif #elif defined(_M_ARM64) # ifndef _ARM64_ # define _c4_ARM64_ # define _ARM64_ # endif #elif defined(_M_ARM) # ifndef _ARM_ # define _c4_ARM_ # define _ARM_ # endif #endif #ifndef NOMINMAX # define _c4_NOMINMAX # define NOMINMAX #endif #ifndef NOGDI # define _c4_NOGDI # define NOGDI #endif #ifndef VC_EXTRALEAN # define _c4_VC_EXTRALEAN # define VC_EXTRALEAN #endif #ifndef WIN32_LEAN_AND_MEAN # define _c4_WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN #endif /* If defined, the following flags inhibit definition * of the indicated items. * * NOGDICAPMASKS - CC_*, LC_*, PC_*, CP_*, TC_*, RC_ * NOVIRTUALKEYCODES - VK_* * NOWINMESSAGES - WM_*, EM_*, LB_*, CB_* * NOWINSTYLES - WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* * NOSYSMETRICS - SM_* * NOMENUS - MF_* * NOICONS - IDI_* * NOKEYSTATES - MK_* * NOSYSCOMMANDS - SC_* * NORASTEROPS - Binary and Tertiary raster ops * NOSHOWWINDOW - SW_* * OEMRESOURCE - OEM Resource values * NOATOM - Atom Manager routines * NOCLIPBOARD - Clipboard routines * NOCOLOR - Screen colors * NOCTLMGR - Control and Dialog routines * NODRAWTEXT - DrawText() and DT_* * NOGDI - All GDI defines and routines * NOKERNEL - All KERNEL defines and routines * NOUSER - All USER defines and routines * NONLS - All NLS defines and routines * NOMB - MB_* and MessageBox() * NOMEMMGR - GMEM_*, LMEM_*, GHND, LHND, associated routines * NOMETAFILE - typedef METAFILEPICT * NOMINMAX - Macros min(a,b) and max(a,b) * NOMSG - typedef MSG and associated routines * NOOPENFILE - OpenFile(), OemToAnsi, AnsiToOem, and OF_* * NOSCROLL - SB_* and scrolling routines * NOSERVICE - All Service Controller routines, SERVICE_ equates, etc. * NOSOUND - Sound driver routines * NOTEXTMETRIC - typedef TEXTMETRIC and associated routines * NOWH - SetWindowsHook and WH_* * NOWINOFFSETS - GWL_*, GCL_*, associated routines * NOCOMM - COMM driver routines * NOKANJI - Kanji support stuff. * NOHELP - Help engine interface. * NOPROFILER - Profiler interface. * NODEFERWINDOWPOS - DeferWindowPos routines * NOMCX - Modem Configuration Extensions */ #endif /* defined(_WIN64) || defined(_WIN32) */ #endif /* _C4_WINDOWS_PUSH_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/windows_push.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/windows.hpp // https://github.com/biojppm/c4core/src/c4/windows.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_WINDOWS_HPP_ #define _C4_WINDOWS_HPP_ #if defined(_WIN64) || defined(_WIN32) // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/windows_push.hpp //#include "c4/windows_push.hpp" #if !defined(C4_WINDOWS_PUSH_HPP_) && !defined(_C4_WINDOWS_PUSH_HPP_) #error "amalgamate: file c4/windows_push.hpp must have been included at this point" #endif /* C4_WINDOWS_PUSH_HPP_ */ #include // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/windows_pop.hpp //#include "c4/windows_pop.hpp" #if !defined(C4_WINDOWS_POP_HPP_) && !defined(_C4_WINDOWS_POP_HPP_) #error "amalgamate: file c4/windows_pop.hpp must have been included at this point" #endif /* C4_WINDOWS_POP_HPP_ */ #endif #endif /* _C4_WINDOWS_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/windows.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/windows_pop.hpp // https://github.com/biojppm/c4core/src/c4/windows_pop.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_WINDOWS_POP_HPP_ #define _C4_WINDOWS_POP_HPP_ #if defined(_WIN64) || defined(_WIN32) #ifdef _c4_AMD64_ # undef _c4_AMD64_ # undef _AMD64_ #endif #ifdef _c4_X86_ # undef _c4_X86_ # undef _X86_ #endif #ifdef _c4_ARM_ # undef _c4_ARM_ # undef _ARM_ #endif #ifdef _c4_NOMINMAX # undef _c4_NOMINMAX # undef NOMINMAX #endif #ifdef NOGDI # undef _c4_NOGDI # undef NOGDI #endif #ifdef VC_EXTRALEAN # undef _c4_VC_EXTRALEAN # undef VC_EXTRALEAN #endif #ifdef WIN32_LEAN_AND_MEAN # undef _c4_WIN32_LEAN_AND_MEAN # undef WIN32_LEAN_AND_MEAN #endif #endif /* defined(_WIN64) || defined(_WIN32) */ #endif /* _C4_WINDOWS_POP_HPP_ */ // (end https://github.com/biojppm/c4core/src/c4/windows_pop.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/error.cpp // https://github.com/biojppm/c4core/src/c4/error.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef C4CORE_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ //included above: //#include //included above: //#include //included above: //#include #define C4_LOGF_ERR(...) (void)fprintf(stderr, __VA_ARGS__); (void)fflush(stderr) #define C4_LOGF_WARN(...) (void)fprintf(stderr, __VA_ARGS__); (void)fflush(stderr) #define C4_LOGP(msg, ...) (void)printf(msg) #if defined(C4_XBOX) || (defined(C4_WIN) && defined(C4_MSVC)) // amalgamate: removed include of // https://github.com/biojppm/c4core/src/c4/windows.hpp //# include "c4/windows.hpp" #if !defined(C4_WINDOWS_HPP_) && !defined(_C4_WINDOWS_HPP_) #error "amalgamate: file c4/windows.hpp must have been included at this point" #endif /* C4_WINDOWS_HPP_ */ #elif defined(C4_PS4) # include #elif defined(C4_UNIX) || defined(C4_LINUX) # include //included above: //# include # include #elif defined(C4_MACOS) || defined(C4_IOS) //included above: //# include # include # include # include #endif // the amalgamation tool is dumb and was omitting this include under MACOS. // So do it only once: #if defined(C4_UNIX) || defined(C4_LINUX) || defined(C4_MACOS) || defined(C4_IOS) # include #endif #if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION) # include #endif #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wformat-nonliteral" # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wformat-nonliteral" # pragma GCC diagnostic ignored "-Wold-style-cast" #endif // NOLINTBEGIN(*use-anonymous-namespace*,cert-dcl50-cpp) //----------------------------------------------------------------------------- namespace c4 { static error_flags s_error_flags = ON_ERROR_DEFAULTS; static error_callback_type s_error_callback = nullptr; //----------------------------------------------------------------------------- error_flags get_error_flags() { return s_error_flags; } void set_error_flags(error_flags flags) { s_error_flags = flags; } error_callback_type get_error_callback() { return s_error_callback; } /** Set the function which is called when an error occurs. */ void set_error_callback(error_callback_type cb) { s_error_callback = cb; } //----------------------------------------------------------------------------- void handle_error(srcloc where, const char *fmt, ...) { char buf[1024]; size_t msglen = 0; if(s_error_flags & (ON_ERROR_LOG|ON_ERROR_CALLBACK)) { va_list args; va_start(args, fmt); int ilen = vsnprintf(buf, sizeof(buf), fmt, args); // NOLINT(clang-analyzer-valist.Uninitialized) va_end(args); msglen = ilen >= 0 && ilen < (int)sizeof(buf) ? static_cast(ilen) : sizeof(buf)-1; } if(s_error_flags & ON_ERROR_LOG) { C4_LOGF_ERR("\n"); #if defined(C4_ERROR_SHOWS_FILELINE) && defined(C4_ERROR_SHOWS_FUNC) C4_LOGF_ERR("%s:%d: ERROR: %s\n", where.file, where.line, buf); C4_LOGF_ERR("%s:%d: ERROR here: %s\n", where.file, where.line, where.func); #elif defined(C4_ERROR_SHOWS_FILELINE) C4_LOGF_ERR("%s:%d: ERROR: %s\n", where.file, where.line, buf); #elif ! defined(C4_ERROR_SHOWS_FUNC) C4_LOGF_ERR("ERROR: %s\n", buf); #endif } if(s_error_flags & ON_ERROR_CALLBACK) { if(s_error_callback) { s_error_callback(buf, msglen); } } if(s_error_flags & ON_ERROR_THROW) { #if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION) throw std::runtime_error(buf); #endif } if(s_error_flags & ON_ERROR_ABORT) { abort(); } abort(); // abort anyway, in case nothing was set C4_UNREACHABLE_AFTER_ERR(); } //----------------------------------------------------------------------------- void handle_warning(srcloc where, const char *fmt, ...) { va_list args; char buf[1024]; va_start(args, fmt); int ret = vsnprintf(buf, sizeof(buf), fmt, args); // NOLINT(clang-analyzer-valist.Uninitialized) if(ret+1 > (int)sizeof(buf)) buf[sizeof(buf) - 1] = '\0'; // truncate else if(ret < 0) buf[0] = '\0'; // output/format error va_end(args); C4_LOGF_WARN("\n"); #if defined(C4_ERROR_SHOWS_FILELINE) && defined(C4_ERROR_SHOWS_FUNC) C4_LOGF_WARN("%s:%d: WARNING: %s\n", where.file, where.line, buf); C4_LOGF_WARN("%s:%d: WARNING: here: %s\n", where.file, where.line, where.func); #elif defined(C4_ERROR_SHOWS_FILELINE) C4_LOGF_WARN("%s:%d: WARNING: %s\n", where.file, where.line, buf); #elif ! defined(C4_ERROR_SHOWS_FUNC) C4_LOGF_WARN("WARNING: %s\n", buf); #endif } //----------------------------------------------------------------------------- bool is_debugger_attached() { #if defined(C4_UNIX) || defined(C4_LINUX) static bool first_call = true; static bool first_call_result = false; if(first_call) { first_call = false; C4_SUPPRESS_WARNING_GCC_PUSH #if defined(__GNUC__) && __GNUC__ > 9 C4_SUPPRESS_WARNING_GCC("-Wanalyzer-fd-leak") #endif //! @see http://stackoverflow.com/questions/3596781/how-to-detect-if-the-current-process-is-being-run-by-gdb //! (this answer: http://stackoverflow.com/a/24969863/3968589 ) char buf[1024] = ""; int status_fd = open("/proc/self/status", O_RDONLY); // NOLINT if (status_fd == -1) return false; ssize_t num_read = ::read(status_fd, buf, sizeof(buf)); if (num_read > 0) { static const char TracerPid[] = "TracerPid:"; char *tracer_pid; if(num_read < 1024) buf[num_read] = 0; tracer_pid = strstr(buf, TracerPid); if(tracer_pid) first_call_result = !!::atoi(tracer_pid + sizeof(TracerPid) - 1); // NOLINT } close(status_fd); C4_SUPPRESS_WARNING_GCC_POP } return first_call_result; #elif defined(C4_PS4) return (sceDbgIsDebuggerAttached() != 0); #elif defined(C4_XBOX) || (defined(C4_WIN) && defined(C4_MSVC)) return IsDebuggerPresent() != 0; #elif defined(C4_MACOS) || defined(C4_IOS) // https://stackoverflow.com/questions/2200277/detecting-debugger-on-mac-os-x // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). int junk; int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); assert(junk == 0); (void)junk; // We're being debugged if the P_TRACED flag is set. return ((info.kp_proc.p_flag & P_TRACED) != 0); #else return false; #endif } // is_debugger_attached() } // namespace c4 // NOLINTEND(*use-anonymous-namespace*,cert-dcl50-cpp) #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif /* C4CORE_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/c4core/src/c4/error.cpp) #endif /* _C4CORE_SINGLE_HEADER_AMALGAMATED_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/c4core_all.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/export.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/export.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef C4_YML_EXPORT_HPP_ #define C4_YML_EXPORT_HPP_ #ifdef _WIN32 #ifdef RYML_SHARED #ifdef RYML_EXPORTS #define RYML_EXPORT __declspec(dllexport) #else #define RYML_EXPORT __declspec(dllimport) #endif #else #define RYML_EXPORT #endif #else #define RYML_EXPORT #endif #endif /* C4_YML_EXPORT_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/export.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/fwd.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/fwd.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_FWD_HPP_ #define _C4_YML_FWD_HPP_ /** @file fwd.hpp forward declarations */ namespace c4 { namespace yml { struct NodeScalar; struct NodeInit; struct NodeData; struct NodeType; class NodeRef; class ConstNodeRef; class Tree; struct ReferenceResolver; template class ParseEngine; struct EventHandlerTree; using Parser = ParseEngine; } // namespace c4 } // namespace yml #endif /* _C4_YML_FWD_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/fwd.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/version.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/version.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_VERSION_HPP_ #define _C4_YML_VERSION_HPP_ /** @file version.hpp */ #define RYML_VERSION "0.9.0" #define RYML_VERSION_MAJOR 0 #define RYML_VERSION_MINOR 9 #define RYML_VERSION_PATCH 0 // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/substr.hpp //#include #if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) #error "amalgamate: file c4/substr.hpp must have been included at this point" #endif /* C4_SUBSTR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/export.hpp //#include #if !defined(C4_YML_EXPORT_HPP_) && !defined(_C4_YML_EXPORT_HPP_) #error "amalgamate: file c4/yml/export.hpp must have been included at this point" #endif /* C4_YML_EXPORT_HPP_ */ namespace c4 { namespace yml { RYML_EXPORT csubstr version(); RYML_EXPORT int version_major(); RYML_EXPORT int version_minor(); RYML_EXPORT int version_patch(); } // namespace yml } // namespace c4 #endif /* _C4_YML_VERSION_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/version.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/common.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_COMMON_HPP_ #define _C4_YML_COMMON_HPP_ /** @file common.hpp Common utilities and infrastructure used by ryml. */ //included above: //#include // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/substr.hpp //#include #if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) #error "amalgamate: file c4/substr.hpp must have been included at this point" #endif /* C4_SUBSTR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/charconv.hpp //#include #if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) #error "amalgamate: file c4/charconv.hpp must have been included at this point" #endif /* C4_CHARCONV_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/dump.hpp //#include #if !defined(C4_DUMP_HPP_) && !defined(_C4_DUMP_HPP_) #error "amalgamate: file c4/dump.hpp must have been included at this point" #endif /* C4_DUMP_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/export.hpp //#include #if !defined(C4_YML_EXPORT_HPP_) && !defined(_C4_YML_EXPORT_HPP_) #error "amalgamate: file c4/yml/export.hpp must have been included at this point" #endif /* C4_YML_EXPORT_HPP_ */ #if defined(C4_MSVC) || defined(C4_MINGW) //included above: //#include #else #include #endif //----------------------------------------------------------------------------- #ifndef RYML_ERRMSG_SIZE /// size for the error message buffer #define RYML_ERRMSG_SIZE (1024) #endif #ifndef RYML_LOGBUF_SIZE /// size for the buffer used to format individual values to string /// while preparing an error message. This is only used for formatting /// individual values in the message; final messages will be larger /// than this value (see @ref RYML_ERRMSG_SIZE). This is also used for /// the detailed debug log messages when RYML_DBG is defined. #define RYML_LOGBUF_SIZE (256) #endif #ifndef RYML_LOGBUF_SIZE_MAX /// size for the fallback larger log buffer. When @ref /// RYML_LOGBUF_SIZE is not large enough to convert a value to string, /// then temporary stack memory is allocated up to /// RYML_LOGBUF_SIZE_MAX. This limit is in place to prevent a stack /// overflow. If the printed value requires more than /// RYML_LOGBUF_SIZE_MAX, the value is silently skipped. #define RYML_LOGBUF_SIZE_MAX (1024) #endif #ifndef RYML_LOCATIONS_SMALL_THRESHOLD /// threshold at which a location search will revert from linear to /// binary search. #define RYML_LOCATIONS_SMALL_THRESHOLD (30) #endif //----------------------------------------------------------------------------- // Specify groups to have a predefined topic order in doxygen: /** @defgroup doc_quickstart Quickstart * * Example code for every feature. */ /** @defgroup doc_parse Parse utilities * @see sample::sample_parse_in_place * @see sample::sample_parse_in_arena * @see sample::sample_parse_file * @see sample::sample_parse_reuse_tree * @see sample::sample_parse_reuse_parser * @see sample::sample_parse_reuse_tree_and_parser * @see sample::sample_location_tracking */ /** @defgroup doc_emit Emit utilities * * Utilities to emit YAML and JSON, either to a memory buffer or to a * file or ostream-like class. * * @see sample::sample_emit_to_container * @see sample::sample_emit_to_stream * @see sample::sample_emit_to_file * @see sample::sample_emit_nested_node * @see sample::sample_emit_style */ /** @defgroup doc_node_type Node types */ /** @defgroup doc_tree Tree utilities * @see sample::sample_quick_overview * @see sample::sample_iterate_trees * @see sample::sample_create_trees * @see sample::sample_tree_arena * * @see sample::sample_static_trees * @see sample::sample_location_tracking * * @see sample::sample_docs * @see sample::sample_anchors_and_aliases * @see sample::sample_tags */ /** @defgroup doc_node_classes Node classes * * High-level node classes. * * @see sample::sample_quick_overview * @see sample::sample_iterate_trees * @see sample::sample_create_trees * @see sample::sample_tree_arena */ /** @defgroup doc_callbacks Callbacks for errors and allocation * * Functions called by ryml to allocate/free memory and to report * errors. * * @see sample::sample_error_handler * @see sample::sample_global_allocator * @see sample::sample_per_tree_allocator */ /** @defgroup doc_serialization Serialization/deserialization * * Contains information on how to serialize and deserialize * fundamental types, user scalar types, user container types and * interop with std scalar/container types. * */ /** @defgroup doc_ref_utils Anchor/Reference utilities * * @see sample::sample_anchors_and_aliases * */ /** @defgroup doc_tag_utils Tag utilities * @see sample::sample_tags */ /** @defgroup doc_preprocessors Preprocessors * * Functions for preprocessing YAML prior to parsing. */ //----------------------------------------------------------------------------- // document macros for doxygen #ifdef __DOXYGEN__ // defined in Doxyfile::PREDEFINED /** define this macro with a boolean value to enable/disable * assertions to check preconditions and assumptions throughout the * codebase; this causes a slowdown of the code, and larger code * size. By default, this macro is defined unless NDEBUG is defined * (see C4_USE_ASSERT); as a result, by default this macro is truthy * only in debug builds. */ # define RYML_USE_ASSERT /** (Undefined by default) Define this macro to disable ryml's default * implementation of the callback functions; see @ref c4::yml::Callbacks */ # define RYML_NO_DEFAULT_CALLBACKS /** (Undefined by default) When this macro is defined (and * @ref RYML_NO_DEFAULT_CALLBACKS is not defined), the default error * handler will throw C++ exceptions of type `std::runtime_error`. */ # define RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS /** Conditionally expands to `noexcept` when @ref RYML_USE_ASSERT is 0 and * is empty otherwise. The user is unable to override this macro. */ # define RYML_NOEXCEPT #endif //----------------------------------------------------------------------------- /** @cond dev*/ #ifndef RYML_USE_ASSERT # define RYML_USE_ASSERT C4_USE_ASSERT #endif #if RYML_USE_ASSERT # define RYML_ASSERT(cond) RYML_CHECK(cond) # define RYML_ASSERT_MSG(cond, msg) RYML_CHECK_MSG(cond, msg) # define _RYML_CB_ASSERT(cb, cond) _RYML_CB_CHECK((cb), (cond)) # define _RYML_CB_ASSERT_(cb, cond, loc) _RYML_CB_CHECK((cb), (cond), (loc)) # define RYML_NOEXCEPT #else # define RYML_ASSERT(cond) # define RYML_ASSERT_MSG(cond, msg) # define _RYML_CB_ASSERT(cb, cond) # define _RYML_CB_ASSERT_(cb, cond, loc) # define RYML_NOEXCEPT noexcept #endif #define RYML_DEPRECATED(msg) C4_DEPRECATED(msg) #define RYML_CHECK(cond) \ do { \ if(C4_UNLIKELY(!(cond))) \ { \ RYML_DEBUG_BREAK(); \ c4::yml::error("check failed: " #cond, c4::yml::Location(__FILE__, __LINE__, 0)); \ C4_UNREACHABLE_AFTER_ERR(); \ } \ } while(0) #define RYML_CHECK_MSG(cond, msg) \ do \ { \ if(C4_UNLIKELY(!(cond))) \ { \ RYML_DEBUG_BREAK(); \ c4::yml::error(msg ": check failed: " #cond, c4::yml::Location(__FILE__, __LINE__, 0)); \ C4_UNREACHABLE_AFTER_ERR(); \ } \ } while(0) #if defined(RYML_DBG) && !defined(NDEBUG) && !defined(C4_NO_DEBUG_BREAK) # define RYML_DEBUG_BREAK() \ do { \ if(c4::get_error_flags() & c4::ON_ERROR_DEBUGBREAK) \ { \ C4_DEBUG_BREAK(); \ } \ } while(0) #else # define RYML_DEBUG_BREAK() #endif /** @endcond */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- namespace c4 { namespace yml { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") #ifndef RYML_ID_TYPE /** The type of a node id in the YAML tree. In the future, the default * will likely change to int32_t, which was observed to be faster. * @see id_type */ #define RYML_ID_TYPE size_t #endif /** The type of a node id in the YAML tree; to override the default * type, define the macro @ref RYML_ID_TYPE to a suitable integer * type. */ using id_type = RYML_ID_TYPE; static_assert(std::is_integral::value, "id_type must be an integer type"); C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wuseless-cast") enum : id_type { /** an index to none */ NONE = id_type(-1), }; C4_SUPPRESS_WARNING_GCC_CLANG_POP enum : size_t { /** a null string position */ npos = size_t(-1) }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //! holds a position into a source buffer struct RYML_EXPORT LineCol { //! number of bytes from the beginning of the source buffer size_t offset; //! line size_t line; //! column size_t col; LineCol() = default; //! construct from line and column LineCol(size_t l, size_t c) : offset(0), line(l), col(c) {} //! construct from offset, line and column LineCol(size_t o, size_t l, size_t c) : offset(o), line(l), col(c) {} }; static_assert(std::is_trivial::value, "LineCol not trivial"); static_assert(std::is_standard_layout::value, "Location not trivial"); //! a source file position struct RYML_EXPORT Location { //! number of bytes from the beginning of the source buffer size_t offset; //! line size_t line; //! column size_t col; //! file name csubstr name; operator bool () const { return !name.empty() || line != 0 || offset != 0 || col != 0; } operator LineCol const& () const { return reinterpret_cast(*this); } // NOLINT Location() = default; Location( size_t l, size_t c) : offset( ), line(l), col(c), name( ) {} Location( size_t b, size_t l, size_t c) : offset(b), line(l), col(c), name( ) {} Location( csubstr n, size_t l, size_t c) : offset( ), line(l), col(c), name(n) {} Location( csubstr n, size_t b, size_t l, size_t c) : offset(b), line(l), col(c), name(n) {} Location(const char *n, size_t l, size_t c) : offset( ), line(l), col(c), name(to_csubstr(n)) {} Location(const char *n, size_t b, size_t l, size_t c) : offset(b), line(l), col(c), name(to_csubstr(n)) {} }; static_assert(std::is_standard_layout::value, "Location not trivial"); //----------------------------------------------------------------------------- /** @addtogroup doc_callbacks * * @{ */ struct Callbacks; /** set the global callbacks for the library; after a call to this * function, these callbacks will be used by newly created objects * (unless they are copying older objects with different * callbacks). If @ref RYML_NO_DEFAULT_CALLBACKS is defined, it is * mandatory to call this function prior to using any other library * facility. * * @warning This function is NOT thread-safe. * * @warning the error callback must never return: see @ref pfn_error * for more details */ RYML_EXPORT void set_callbacks(Callbacks const& c); /** get the global callbacks * @warning This function is not thread-safe. */ RYML_EXPORT Callbacks const& get_callbacks(); /** set the global callbacks back to their defaults () * @warning This function is not thread-safe. */ RYML_EXPORT void reset_callbacks(); /** the type of the function used to report errors * * @warning When given by the user, this function MUST interrupt * execution, typically by either throwing an exception, or using * `std::longjmp()` ([see * documentation](https://en.cppreference.com/w/cpp/utility/program/setjmp)) * or by calling `std::abort()`. If the function returned, the parser * would enter into an infinite loop, or the program may crash. */ using pfn_error = void (*) (const char* msg, size_t msg_len, Location location, void *user_data); /** the type of the function used to allocate memory; ryml will only * allocate memory through this callback. */ using pfn_allocate = void* (*)(size_t len, void* hint, void *user_data); /** the type of the function used to free memory; ryml will only free * memory through this callback. */ using pfn_free = void (*)(void* mem, size_t size, void *user_data); /** a c-style callbacks class. Can be used globally by the library * and/or locally by @ref Tree and @ref Parser objects. */ struct RYML_EXPORT Callbacks { void * m_user_data; pfn_allocate m_allocate; pfn_free m_free; pfn_error m_error; /** Construct an object with the default callbacks. If * @ref RYML_NO_DEFAULT_CALLBACKS is defined, the object will have null * members.*/ Callbacks() noexcept; /** Construct an object with the given callbacks. * * @param user_data Data to be forwarded in every call to a callback. * * @param alloc A pointer to an allocate function. Unless * @ref RYML_NO_DEFAULT_CALLBACKS is defined, when this * parameter is null, will fall back to ryml's default * alloc implementation. * * @param free A pointer to a free function. Unless * @ref RYML_NO_DEFAULT_CALLBACKS is defined, when this * parameter is null, will fall back to ryml's default free * implementation. * * @param error A pointer to an error function, which must never * return (see @ref pfn_error). Unless * @ref RYML_NO_DEFAULT_CALLBACKS is defined, when this * parameter is null, will fall back to ryml's default * error implementation. */ Callbacks(void *user_data, pfn_allocate alloc, pfn_free free, pfn_error error); bool operator!= (Callbacks const& that) const { return !operator==(that); } bool operator== (Callbacks const& that) const { return (m_user_data == that.m_user_data && m_allocate == that.m_allocate && m_free == that.m_free && m_error == that.m_error); } }; /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- typedef enum { NOBOM, UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE, } Encoding_e; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /// @cond dev // BEWARE! MSVC requires that [[noreturn]] appears before RYML_EXPORT [[noreturn]] RYML_EXPORT void error(Callbacks const& cb, const char *msg, size_t msg_len, Location loc); [[noreturn]] RYML_EXPORT void error(const char *msg, size_t msg_len, Location loc); [[noreturn]] inline void error(const char *msg, size_t msg_len) { error(msg, msg_len, Location{}); } template [[noreturn]] inline void error(const char (&msg)[N], Location loc) { error(msg, N-1, loc); } template [[noreturn]] inline void error(const char (&msg)[N]) { error(msg, N-1, Location{}); } #define _RYML_CB_ERR(cb, msg_literal) \ _RYML_CB_ERR_(cb, msg_literal, c4::yml::Location(__FILE__, 0, __LINE__, 0)) #define _RYML_CB_CHECK(cb, cond) \ _RYML_CB_CHECK_(cb, cond, c4::yml::Location(__FILE__, 0, __LINE__, 0)) #define _RYML_CB_ERR_(cb, msg_literal, loc) \ do \ { \ const char msg[] = msg_literal; \ RYML_DEBUG_BREAK(); \ c4::yml::error((cb), msg, sizeof(msg)-1, loc); \ C4_UNREACHABLE_AFTER_ERR(); \ } while(0) #define _RYML_CB_CHECK_(cb, cond, loc) \ do \ { \ if(C4_UNLIKELY(!(cond))) \ { \ const char msg[] = "check failed: " #cond; \ RYML_DEBUG_BREAK(); \ c4::yml::error((cb), msg, sizeof(msg)-1, loc); \ C4_UNREACHABLE_AFTER_ERR(); \ } \ } while(0) #define _RYML_CB_ALLOC_HINT(cb, T, num, hint) (T*) (cb).m_allocate((num) * sizeof(T), (hint), (cb).m_user_data) #define _RYML_CB_ALLOC(cb, T, num) _RYML_CB_ALLOC_HINT((cb), T, (num), nullptr) #define _RYML_CB_FREE(cb, buf, T, num) \ do { \ (cb).m_free((buf), (num) * sizeof(T), (cb).m_user_data); \ (buf) = nullptr; \ } while(0) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- typedef enum { BLOCK_LITERAL, //!< keep newlines (|) BLOCK_FOLD //!< replace newline with single space (>) } BlockStyle_e; typedef enum { CHOMP_CLIP, //!< single newline at end (default) CHOMP_STRIP, //!< no newline at end (-) CHOMP_KEEP //!< all newlines from end (+) } BlockChomp_e; /** Abstracts the fact that a scalar filter result may not fit in the * intended memory. */ struct FilterResult { C4_ALWAYS_INLINE bool valid() const noexcept { return str.str != nullptr; } C4_ALWAYS_INLINE size_t required_len() const noexcept { return str.len; } C4_ALWAYS_INLINE csubstr get() const { RYML_ASSERT(valid()); return str; } csubstr str; }; /** Abstracts the fact that a scalar filter result may not fit in the * intended memory. */ struct FilterResultExtending { C4_ALWAYS_INLINE bool valid() const noexcept { return str.str != nullptr; } C4_ALWAYS_INLINE size_t required_len() const noexcept { return reqlen; } C4_ALWAYS_INLINE csubstr get() const { RYML_ASSERT(valid()); return str; } csubstr str; size_t reqlen; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- namespace detail { // is there a better way to do this? template struct _charconstant_t : public std::conditional::value, std::integral_constant(unsignedval)>, std::integral_constant>::type {}; #define _RYML_CHCONST(signedval, unsignedval) ::c4::yml::detail::_charconstant_t::value } // namespace detail namespace detail { struct _SubstrWriter { substr buf; size_t pos; _SubstrWriter(substr buf_, size_t pos_=0) : buf(buf_), pos(pos_) { C4_ASSERT(buf.str); } void append(csubstr s) { C4_ASSERT(!s.overlaps(buf)); C4_ASSERT(s.str || !s.len); if(s.len && pos + s.len <= buf.len) { C4_ASSERT(s.str); memcpy(buf.str + pos, s.str, s.len); } pos += s.len; } void append(char c) { C4_ASSERT(buf.str); if(pos < buf.len) buf.str[pos] = c; ++pos; } void append_n(char c, size_t numtimes) { C4_ASSERT(buf.str); if(numtimes && pos + numtimes < buf.len) memset(buf.str + pos, c, numtimes); pos += numtimes; } size_t slack() const { return pos <= buf.len ? buf.len - pos : 0; } size_t excess() const { return pos > buf.len ? pos - buf.len : 0; } //! get the part written so far csubstr curr() const { return pos <= buf.len ? buf.first(pos) : buf; } //! get the part that is still free to write to (the remainder) substr rem() const { return pos < buf.len ? buf.sub(pos) : buf.last(0); } size_t advance(size_t more) { pos += more; return pos; } }; } // namespace detail namespace detail { // dumpfn is a function abstracting prints to terminal (or to string). template C4_NO_INLINE void _dump(DumpFn &&dumpfn, csubstr fmt, Args&& ...args) { DumpResults results; // try writing everything: { // buffer for converting individual arguments. it is defined // in a child scope to free it in case the buffer is too small // for any of the arguments. char writebuf[RYML_LOGBUF_SIZE]; results = format_dump_resume(std::forward(dumpfn), writebuf, fmt, std::forward(args)...); } // if any of the arguments failed to fit the buffer, allocate a // larger buffer (up to a limit) and resume writing. // // results.bufsize is set to the size of the largest element // serialized. Eg int(1) will require 1 byte. if(C4_UNLIKELY(results.bufsize > RYML_LOGBUF_SIZE)) { const size_t bufsize = results.bufsize <= RYML_LOGBUF_SIZE_MAX ? results.bufsize : RYML_LOGBUF_SIZE_MAX; #ifdef C4_MSVC substr largerbuf = {static_cast(_alloca(bufsize)), bufsize}; #else substr largerbuf = {static_cast(alloca(bufsize)), bufsize}; #endif results = format_dump_resume(std::forward(dumpfn), results, largerbuf, fmt, std::forward(args)...); } } template C4_NORETURN C4_NO_INLINE void _report_err(Callbacks const& C4_RESTRICT callbacks, csubstr fmt, Args const& C4_RESTRICT ...args) { char errmsg[RYML_ERRMSG_SIZE] = {0}; detail::_SubstrWriter writer(errmsg); auto dumpfn = [&writer](csubstr s){ writer.append(s); }; _dump(dumpfn, fmt, args...); writer.append('\n'); const size_t len = writer.pos < RYML_ERRMSG_SIZE ? writer.pos : RYML_ERRMSG_SIZE; callbacks.m_error(errmsg, len, {}, callbacks.m_user_data); C4_UNREACHABLE_AFTER_ERR(); } } // namespace detail inline csubstr _c4prc(const char &C4_RESTRICT c) // pass by reference! { switch(c) { case '\n': return csubstr("\\n"); case '\t': return csubstr("\\t"); case '\0': return csubstr("\\0"); case '\r': return csubstr("\\r"); case '\f': return csubstr("\\f"); case '\b': return csubstr("\\b"); case '\v': return csubstr("\\v"); case '\a': return csubstr("\\a"); default: return csubstr(&c, 1); } } /// @endcond C4_SUPPRESS_WARNING_GCC_POP } // namespace yml } // namespace c4 #endif /* _C4_YML_COMMON_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/node_type.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/node_type.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef C4_YML_NODE_TYPE_HPP_ #define C4_YML_NODE_TYPE_HPP_ #ifndef _C4_YML_COMMON_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp //#include "c4/yml/common.hpp" #if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) #error "amalgamate: file c4/yml/common.hpp must have been included at this point" #endif /* C4_YML_COMMON_HPP_ */ #endif C4_SUPPRESS_WARNING_MSVC_PUSH C4_SUPPRESS_WARNING_GCC_CLANG_PUSH C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast") namespace c4 { namespace yml { /** @addtogroup doc_node_type * * @{ */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** the integral type necessary to cover all the bits for NodeType_e */ using type_bits = uint32_t; /** a bit mask for marking node types and styles */ typedef enum : type_bits { #define __(v) (type_bits(1) << v) // a convenience define, undefined below // NOLINT NOTYPE = 0, ///< no node type or style is set KEY = __(0), ///< is member of a map VAL = __(1), ///< a scalar: has a scalar (ie string) value, possibly empty. must be a leaf node, and cannot be MAP or SEQ MAP = __(2), ///< a map: a parent of KEYVAL/KEYSEQ/KEYMAP nodes SEQ = __(3), ///< a seq: a parent of VAL/SEQ/MAP nodes DOC = __(4), ///< a document STREAM = __(5)|SEQ, ///< a stream: a seq of docs KEYREF = __(6), ///< a *reference: the key references an &anchor VALREF = __(7), ///< a *reference: the val references an &anchor KEYANCH = __(8), ///< the key has an &anchor VALANCH = __(9), ///< the val has an &anchor KEYTAG = __(10), ///< the key has a tag VALTAG = __(11), ///< the val has a tag KEYNIL = __(12), ///< the key is null (eg `{ : b}` results in a null key) VALNIL = __(13), ///< the val is null (eg `{a : }` results in a null val) _TYMASK = __(14)-1, ///< all the bits up to here // // unfiltered flags: // KEY_UNFILT = __(14), ///< the key scalar was left unfiltered; the parser was set not to filter. @see ParserOptions VAL_UNFILT = __(15), ///< the val scalar was left unfiltered; the parser was set not to filter. @see ParserOptions // // style flags: // FLOW_SL = __(16), ///< mark container with single-line flow style (seqs as '[val1,val2], maps as '{key: val,key2: val2}') FLOW_ML = __(17), ///< (NOT IMPLEMENTED, work in progress) mark container with multi-line flow style (seqs as '[\n val1,\n val2\n], maps as '{\n key: val,\n key2: val2\n}') BLOCK = __(18), ///< mark container with block style (seqs as '- val\n', maps as 'key: val') KEY_LITERAL = __(19), ///< mark key scalar as multiline, block literal | VAL_LITERAL = __(20), ///< mark val scalar as multiline, block literal | KEY_FOLDED = __(21), ///< mark key scalar as multiline, block folded > VAL_FOLDED = __(22), ///< mark val scalar as multiline, block folded > KEY_SQUO = __(23), ///< mark key scalar as single quoted ' VAL_SQUO = __(24), ///< mark val scalar as single quoted ' KEY_DQUO = __(25), ///< mark key scalar as double quoted " VAL_DQUO = __(26), ///< mark val scalar as double quoted " KEY_PLAIN = __(27), ///< mark key scalar as plain scalar (unquoted, even when multiline) VAL_PLAIN = __(28), ///< mark val scalar as plain scalar (unquoted, even when multiline) // // type combination masks: // KEYVAL = KEY|VAL, KEYSEQ = KEY|SEQ, KEYMAP = KEY|MAP, DOCMAP = DOC|MAP, DOCSEQ = DOC|SEQ, DOCVAL = DOC|VAL, // // style combination masks: // SCALAR_LITERAL = KEY_LITERAL|VAL_LITERAL, SCALAR_FOLDED = KEY_FOLDED|VAL_FOLDED, SCALAR_SQUO = KEY_SQUO|VAL_SQUO, SCALAR_DQUO = KEY_DQUO|VAL_DQUO, SCALAR_PLAIN = KEY_PLAIN|VAL_PLAIN, KEYQUO = KEY_SQUO|KEY_DQUO|KEY_FOLDED|KEY_LITERAL, ///< key style is one of ', ", > or | VALQUO = VAL_SQUO|VAL_DQUO|VAL_FOLDED|VAL_LITERAL, ///< val style is one of ', ", > or | KEY_STYLE = KEY_LITERAL|KEY_FOLDED|KEY_SQUO|KEY_DQUO|KEY_PLAIN, ///< mask of all the scalar styles for key (not container styles!) VAL_STYLE = VAL_LITERAL|VAL_FOLDED|VAL_SQUO|VAL_DQUO|VAL_PLAIN, ///< mask of all the scalar styles for val (not container styles!) SCALAR_STYLE = KEY_STYLE|VAL_STYLE, CONTAINER_STYLE_FLOW = FLOW_SL|FLOW_ML, CONTAINER_STYLE_BLOCK = BLOCK, CONTAINER_STYLE = FLOW_SL|FLOW_ML|BLOCK, STYLE = SCALAR_STYLE | CONTAINER_STYLE, // // mixed masks _KEYMASK = KEY | KEYQUO | KEYANCH | KEYREF | KEYTAG, _VALMASK = VAL | VALQUO | VALANCH | VALREF | VALTAG, #undef __ } NodeType_e; constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator| (NodeType_e lhs, NodeType_e rhs) noexcept { return (NodeType_e)(((type_bits)lhs) | ((type_bits)rhs)); } constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator& (NodeType_e lhs, NodeType_e rhs) noexcept { return (NodeType_e)(((type_bits)lhs) & ((type_bits)rhs)); } constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator>> (NodeType_e bits, uint32_t n) noexcept { return (NodeType_e)(((type_bits)bits) >> n); } constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator<< (NodeType_e bits, uint32_t n) noexcept { return (NodeType_e)(((type_bits)bits) << n); } constexpr C4_ALWAYS_INLINE C4_CONST NodeType_e operator~ (NodeType_e bits) noexcept { return (NodeType_e)(~(type_bits)bits); } C4_ALWAYS_INLINE NodeType_e& operator&= (NodeType_e &subject, NodeType_e bits) noexcept { subject = (NodeType_e)((type_bits)subject & (type_bits)bits); return subject; } C4_ALWAYS_INLINE NodeType_e& operator|= (NodeType_e &subject, NodeType_e bits) noexcept { subject = (NodeType_e)((type_bits)subject | (type_bits)bits); return subject; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** wraps a NodeType_e element with some syntactic sugar and predicates */ struct RYML_EXPORT NodeType { public: NodeType_e type; public: C4_ALWAYS_INLINE NodeType() noexcept : type(NOTYPE) {} C4_ALWAYS_INLINE NodeType(NodeType_e t) noexcept : type(t) {} C4_ALWAYS_INLINE NodeType(type_bits t) noexcept : type((NodeType_e)t) {} C4_ALWAYS_INLINE bool has_any(NodeType_e t) const noexcept { return (type & t) != 0u; } C4_ALWAYS_INLINE bool has_all(NodeType_e t) const noexcept { return (type & t) == t; } C4_ALWAYS_INLINE bool has_none(NodeType_e t) const noexcept { return (type & t) == 0; } C4_ALWAYS_INLINE void set(NodeType_e t) noexcept { type = t; } C4_ALWAYS_INLINE void add(NodeType_e t) noexcept { type = (type|t); } C4_ALWAYS_INLINE void rem(NodeType_e t) noexcept { type = (type & ~t); } C4_ALWAYS_INLINE void addrem(NodeType_e bits_to_add, NodeType_e bits_to_remove) noexcept { type |= bits_to_add; type &= ~bits_to_remove; } C4_ALWAYS_INLINE void clear() noexcept { type = NOTYPE; } public: C4_ALWAYS_INLINE operator NodeType_e & C4_RESTRICT () noexcept { return type; } C4_ALWAYS_INLINE operator NodeType_e const& C4_RESTRICT () const noexcept { return type; } public: /** @name node type queries * @{ */ /** return a preset string based on the node type */ C4_ALWAYS_INLINE const char *type_str() const noexcept { return type_str(type); } /** return a preset string based on the node type */ static const char* type_str(NodeType_e t) noexcept; /** fill a string with the node type flags. If the string is small, returns {null, len} */ C4_ALWAYS_INLINE csubstr type_str(substr buf) const noexcept { return type_str(buf, type); } /** fill a string with the node type flags. If the string is small, returns {null, len} */ static csubstr type_str(substr buf, NodeType_e t) noexcept; public: /** @name node type queries * @{ */ C4_ALWAYS_INLINE bool is_notype() const noexcept { return type == NOTYPE; } C4_ALWAYS_INLINE bool is_stream() const noexcept { return ((type & STREAM) == STREAM) != 0; } C4_ALWAYS_INLINE bool is_doc() const noexcept { return (type & DOC) != 0; } C4_ALWAYS_INLINE bool is_container() const noexcept { return (type & (MAP|SEQ|STREAM)) != 0; } C4_ALWAYS_INLINE bool is_map() const noexcept { return (type & MAP) != 0; } C4_ALWAYS_INLINE bool is_seq() const noexcept { return (type & SEQ) != 0; } C4_ALWAYS_INLINE bool has_key() const noexcept { return (type & KEY) != 0; } C4_ALWAYS_INLINE bool has_val() const noexcept { return (type & VAL) != 0; } C4_ALWAYS_INLINE bool is_val() const noexcept { return (type & KEYVAL) == VAL; } C4_ALWAYS_INLINE bool is_keyval() const noexcept { return (type & KEYVAL) == KEYVAL; } C4_ALWAYS_INLINE bool key_is_null() const noexcept { return (type & KEYNIL) != 0; } C4_ALWAYS_INLINE bool val_is_null() const noexcept { return (type & VALNIL) != 0; } C4_ALWAYS_INLINE bool has_key_tag() const noexcept { return (type & KEYTAG) != 0; } C4_ALWAYS_INLINE bool has_val_tag() const noexcept { return (type & VALTAG) != 0; } C4_ALWAYS_INLINE bool has_key_anchor() const noexcept { return (type & KEYANCH) != 0; } C4_ALWAYS_INLINE bool has_val_anchor() const noexcept { return (type & VALANCH) != 0; } C4_ALWAYS_INLINE bool has_anchor() const noexcept { return (type & (KEYANCH|VALANCH)) != 0; } C4_ALWAYS_INLINE bool is_key_ref() const noexcept { return (type & KEYREF) != 0; } C4_ALWAYS_INLINE bool is_val_ref() const noexcept { return (type & VALREF) != 0; } C4_ALWAYS_INLINE bool is_ref() const noexcept { return (type & (KEYREF|VALREF)) != 0; } C4_ALWAYS_INLINE bool is_key_unfiltered() const noexcept { return (type & (KEY_UNFILT)) != 0; } C4_ALWAYS_INLINE bool is_val_unfiltered() const noexcept { return (type & (VAL_UNFILT)) != 0; } RYML_DEPRECATED("use has_key_anchor()") bool is_key_anchor() const noexcept { return has_key_anchor(); } RYML_DEPRECATED("use has_val_anchor()") bool is_val_anchor() const noexcept { return has_val_anchor(); } RYML_DEPRECATED("use has_anchor()") bool is_anchor() const noexcept { return has_anchor(); } RYML_DEPRECATED("use has_anchor() || is_ref()") bool is_anchor_or_ref() const noexcept { return has_anchor() || is_ref(); } /** @} */ public: /** @name container+scalar style queries * @{ */ C4_ALWAYS_INLINE bool is_container_styled() const noexcept { return (type & (CONTAINER_STYLE)) != 0; } C4_ALWAYS_INLINE bool is_block() const noexcept { return (type & (BLOCK)) != 0; } C4_ALWAYS_INLINE bool is_flow_sl() const noexcept { return (type & (FLOW_SL)) != 0; } C4_ALWAYS_INLINE bool is_flow_ml() const noexcept { return (type & (FLOW_ML)) != 0; } C4_ALWAYS_INLINE bool is_flow() const noexcept { return (type & (FLOW_ML|FLOW_SL)) != 0; } C4_ALWAYS_INLINE bool is_key_styled() const noexcept { return (type & (KEY_STYLE)) != 0; } C4_ALWAYS_INLINE bool is_val_styled() const noexcept { return (type & (VAL_STYLE)) != 0; } C4_ALWAYS_INLINE bool is_key_literal() const noexcept { return (type & (KEY_LITERAL)) != 0; } C4_ALWAYS_INLINE bool is_val_literal() const noexcept { return (type & (VAL_LITERAL)) != 0; } C4_ALWAYS_INLINE bool is_key_folded() const noexcept { return (type & (KEY_FOLDED)) != 0; } C4_ALWAYS_INLINE bool is_val_folded() const noexcept { return (type & (VAL_FOLDED)) != 0; } C4_ALWAYS_INLINE bool is_key_squo() const noexcept { return (type & (KEY_SQUO)) != 0; } C4_ALWAYS_INLINE bool is_val_squo() const noexcept { return (type & (VAL_SQUO)) != 0; } C4_ALWAYS_INLINE bool is_key_dquo() const noexcept { return (type & (KEY_DQUO)) != 0; } C4_ALWAYS_INLINE bool is_val_dquo() const noexcept { return (type & (VAL_DQUO)) != 0; } C4_ALWAYS_INLINE bool is_key_plain() const noexcept { return (type & (KEY_PLAIN)) != 0; } C4_ALWAYS_INLINE bool is_val_plain() const noexcept { return (type & (VAL_PLAIN)) != 0; } C4_ALWAYS_INLINE bool is_key_quoted() const noexcept { return (type & KEYQUO) != 0; } C4_ALWAYS_INLINE bool is_val_quoted() const noexcept { return (type & VALQUO) != 0; } C4_ALWAYS_INLINE bool is_quoted() const noexcept { return (type & (KEYQUO|VALQUO)) != 0; } C4_ALWAYS_INLINE void set_container_style(NodeType_e style) noexcept { type = ((style & CONTAINER_STYLE) | (type & ~CONTAINER_STYLE)); } C4_ALWAYS_INLINE void set_key_style(NodeType_e style) noexcept { type = ((style & KEY_STYLE) | (type & ~KEY_STYLE)); } C4_ALWAYS_INLINE void set_val_style(NodeType_e style) noexcept { type = ((style & VAL_STYLE) | (type & ~VAL_STYLE)); } /** @} */ }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @name scalar style helpers * @{ */ /** choose a YAML emitting style based on the scalar's contents */ RYML_EXPORT NodeType_e scalar_style_choose(csubstr scalar) noexcept; /** choose a json style based on the scalar's contents */ RYML_EXPORT NodeType_e scalar_style_json_choose(csubstr scalar) noexcept; /** query whether a scalar can be encoded using single quotes. * It may not be possible, notably when there is leading * whitespace after a newline. */ RYML_EXPORT bool scalar_style_query_squo(csubstr s) noexcept; /** query whether a scalar can be encoded using plain style (no * quotes, not a literal/folded block scalar). */ RYML_EXPORT bool scalar_style_query_plain(csubstr s) noexcept; /** YAML-sense query of nullity. returns true if the scalar points * to `nullptr` or is otherwise equal to one of the strings * `"~"`,`"null"`,`"Null"`,`"NULL"` */ RYML_EXPORT inline C4_NO_INLINE bool scalar_is_null(csubstr s) noexcept { return s.str == nullptr || s == "~" || s == "null" || s == "Null" || s == "NULL"; } /** @} */ /** @} */ } // namespace yml } // namespace c4 C4_SUPPRESS_WARNING_MSVC_POP C4_SUPPRESS_WARNING_GCC_CLANG_POP #endif /* C4_YML_NODE_TYPE_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/node_type.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/tag.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/tag.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_TAG_HPP_ #define _C4_YML_TAG_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp //#include #if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) #error "amalgamate: file c4/yml/common.hpp must have been included at this point" #endif /* C4_YML_COMMON_HPP_ */ namespace c4 { namespace yml { class Tree; /** @addtogroup doc_tag_utils * * @{ */ #ifndef RYML_MAX_TAG_DIRECTIVES /** the maximum number of tag directives in a Tree */ #define RYML_MAX_TAG_DIRECTIVES 4 #endif /** the integral type necessary to cover all the bits marking node tags */ using tag_bits = uint16_t; /** a bit mask for marking tags for types */ typedef enum : tag_bits { TAG_NONE = 0, // container types TAG_MAP = 1, /**< !!map Unordered set of key: value pairs without duplicates. @see https://yaml.org/type/map.html */ TAG_OMAP = 2, /**< !!omap Ordered sequence of key: value pairs without duplicates. @see https://yaml.org/type/omap.html */ TAG_PAIRS = 3, /**< !!pairs Ordered sequence of key: value pairs allowing duplicates. @see https://yaml.org/type/pairs.html */ TAG_SET = 4, /**< !!set Unordered set of non-equal values. @see https://yaml.org/type/set.html */ TAG_SEQ = 5, /**< !!seq Sequence of arbitrary values. @see https://yaml.org/type/seq.html */ // scalar types TAG_BINARY = 6, /**< !!binary A sequence of zero or more octets (8 bit values). @see https://yaml.org/type/binary.html */ TAG_BOOL = 7, /**< !!bool Mathematical Booleans. @see https://yaml.org/type/bool.html */ TAG_FLOAT = 8, /**< !!float Floating-point approximation to real numbers. https://yaml.org/type/float.html */ TAG_INT = 9, /**< !!float Mathematical integers. https://yaml.org/type/int.html */ TAG_MERGE = 10, /**< !!merge Specify one or more mapping to be merged with the current one. https://yaml.org/type/merge.html */ TAG_NULL = 11, /**< !!null Devoid of value. https://yaml.org/type/null.html */ TAG_STR = 12, /**< !!str A sequence of zero or more Unicode characters. https://yaml.org/type/str.html */ TAG_TIMESTAMP = 13, /**< !!timestamp A point in time https://yaml.org/type/timestamp.html */ TAG_VALUE = 14, /**< !!value Specify the default value of a mapping https://yaml.org/type/value.html */ TAG_YAML = 15, /**< !!yaml Specify the default value of a mapping https://yaml.org/type/yaml.html */ } YamlTag_e; RYML_EXPORT YamlTag_e to_tag(csubstr tag); RYML_EXPORT csubstr from_tag(YamlTag_e tag); RYML_EXPORT csubstr from_tag_long(YamlTag_e tag); RYML_EXPORT csubstr normalize_tag(csubstr tag); RYML_EXPORT csubstr normalize_tag_long(csubstr tag); RYML_EXPORT csubstr normalize_tag_long(csubstr tag, substr output); RYML_EXPORT bool is_custom_tag(csubstr tag); struct RYML_EXPORT TagDirective { /** Eg
!e!
in
%TAG !e! tag:example.com,2000:app/
*/ csubstr handle; /** Eg
tag:example.com,2000:app/
in
%TAG !e! tag:example.com,2000:app/
*/ csubstr prefix; /** The next node to which this tag directive applies */ id_type next_node_id; bool create_from_str(csubstr directive_); ///< leaves next_node_id unfilled bool create_from_str(csubstr directive_, Tree *tree); size_t transform(csubstr tag, substr output, Callbacks const& callbacks) const; }; struct RYML_EXPORT TagDirectiveRange { TagDirective const* C4_RESTRICT b; TagDirective const* C4_RESTRICT e; C4_ALWAYS_INLINE TagDirective const* begin() const noexcept { return b; } C4_ALWAYS_INLINE TagDirective const* end() const noexcept { return e; } }; /** @} */ } // namespace yml } // namespace c4 #endif /* _C4_YML_TAG_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/tag.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/tree.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_TREE_HPP_ #define _C4_YML_TREE_HPP_ /** @file tree.hpp */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/types.hpp //#include "c4/types.hpp" #if !defined(C4_TYPES_HPP_) && !defined(_C4_TYPES_HPP_) #error "amalgamate: file c4/types.hpp must have been included at this point" #endif /* C4_TYPES_HPP_ */ #ifndef _C4_YML_FWD_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/fwd.hpp //#include "c4/yml/fwd.hpp" #if !defined(C4_YML_FWD_HPP_) && !defined(_C4_YML_FWD_HPP_) #error "amalgamate: file c4/yml/fwd.hpp must have been included at this point" #endif /* C4_YML_FWD_HPP_ */ #endif #ifndef _C4_YML_COMMON_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp //#include "c4/yml/common.hpp" #if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) #error "amalgamate: file c4/yml/common.hpp must have been included at this point" #endif /* C4_YML_COMMON_HPP_ */ #endif #ifndef C4_YML_NODE_TYPE_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node_type.hpp //#include "c4/yml/node_type.hpp" #if !defined(C4_YML_NODE_TYPE_HPP_) && !defined(_C4_YML_NODE_TYPE_HPP_) #error "amalgamate: file c4/yml/node_type.hpp must have been included at this point" #endif /* C4_YML_NODE_TYPE_HPP_ */ #endif #ifndef _C4_YML_TAG_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tag.hpp //#include "c4/yml/tag.hpp" #if !defined(C4_YML_TAG_HPP_) && !defined(_C4_YML_TAG_HPP_) #error "amalgamate: file c4/yml/tag.hpp must have been included at this point" #endif /* C4_YML_TAG_HPP_ */ #endif #ifndef _C4_CHARCONV_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/charconv.hpp //#include #if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) #error "amalgamate: file c4/charconv.hpp must have been included at this point" #endif /* C4_CHARCONV_HPP_ */ #endif //included above: //#include //included above: //#include C4_SUPPRESS_WARNING_MSVC_PUSH C4_SUPPRESS_WARNING_MSVC(4251) // needs to have dll-interface to be used by clients of struct C4_SUPPRESS_WARNING_MSVC(4296) // expression is always 'boolean_value' C4_SUPPRESS_WARNING_GCC_CLANG_PUSH C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast") C4_SUPPRESS_WARNING_GCC("-Wuseless-cast") C4_SUPPRESS_WARNING_GCC("-Wtype-limits") namespace c4 { namespace yml { template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type; template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type; template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; template size_t to_chars_float(substr buf, T val); template bool from_chars_float(csubstr buf, T *C4_RESTRICT val); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @addtogroup doc_tree * * @{ */ /** a node scalar is a csubstr, which may be tagged and anchored. */ struct NodeScalar { csubstr tag; csubstr scalar; csubstr anchor; public: /// initialize as an empty scalar NodeScalar() noexcept : tag(), scalar(), anchor() {} // NOLINT /// initialize as an untagged scalar template NodeScalar(const char (&s)[N]) noexcept : tag(), scalar(s), anchor() {} NodeScalar(csubstr s ) noexcept : tag(), scalar(s), anchor() {} /// initialize as a tagged scalar template NodeScalar(const char (&t)[N], const char (&s)[N]) noexcept : tag(t), scalar(s), anchor() {} NodeScalar(csubstr t , csubstr s ) noexcept : tag(t), scalar(s), anchor() {} public: ~NodeScalar() noexcept = default; NodeScalar(NodeScalar &&) noexcept = default; NodeScalar(NodeScalar const&) noexcept = default; NodeScalar& operator= (NodeScalar &&) noexcept = default; NodeScalar& operator= (NodeScalar const&) noexcept = default; public: bool empty() const noexcept { return tag.empty() && scalar.empty() && anchor.empty(); } void clear() noexcept { tag.clear(); scalar.clear(); anchor.clear(); } void set_ref_maybe_replacing_scalar(csubstr ref, bool has_scalar) RYML_NOEXCEPT { csubstr trimmed = ref.begins_with('*') ? ref.sub(1) : ref; anchor = trimmed; if((!has_scalar) || !scalar.ends_with(trimmed)) scalar = ref; } }; C4_MUST_BE_TRIVIAL_COPY(NodeScalar); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** convenience class to initialize nodes */ struct NodeInit { NodeType type; NodeScalar key; NodeScalar val; public: /// initialize as an empty node NodeInit() : type(NOTYPE), key(), val() {} /// initialize as a typed node NodeInit(NodeType_e t) : type(t), key(), val() {} /// initialize as a sequence member NodeInit(NodeScalar const& v) : type(VAL), key(), val(v) { _add_flags(); } /// initialize as a sequence member with explicit type NodeInit(NodeScalar const& v, NodeType_e t) : type(t|VAL), key(), val(v) { _add_flags(); } /// initialize as a mapping member NodeInit( NodeScalar const& k, NodeScalar const& v) : type(KEYVAL), key(k), val(v) { _add_flags(); } /// initialize as a mapping member with explicit type NodeInit(NodeType_e t, NodeScalar const& k, NodeScalar const& v) : type(t), key(k), val(v) { _add_flags(); } /// initialize as a mapping member with explicit type (eg for SEQ or MAP) NodeInit(NodeType_e t, NodeScalar const& k ) : type(t), key(k), val( ) { _add_flags(KEY); } public: void clear() { type.clear(); key.clear(); val.clear(); } void _add_flags(type_bits more_flags=0) { type = (type|more_flags); if( ! key.tag.empty()) type = (type|KEYTAG); if( ! val.tag.empty()) type = (type|VALTAG); if( ! key.anchor.empty()) type = (type|KEYANCH); if( ! val.anchor.empty()) type = (type|VALANCH); } bool _check() const { // key cannot be empty RYML_ASSERT(key.scalar.empty() == ((type & KEY) == 0)); // key tag cannot be empty RYML_ASSERT(key.tag.empty() == ((type & KEYTAG) == 0)); // val may be empty even though VAL is set. But when VAL is not set, val must be empty RYML_ASSERT(((type & VAL) != 0) || val.scalar.empty()); // val tag cannot be empty RYML_ASSERT(val.tag.empty() == ((type & VALTAG) == 0)); return true; } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** contains the data for each YAML node. */ struct NodeData { NodeType m_type; NodeScalar m_key; NodeScalar m_val; id_type m_parent; id_type m_first_child; id_type m_last_child; id_type m_next_sibling; id_type m_prev_sibling; }; C4_MUST_BE_TRIVIAL_COPY(NodeData); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class RYML_EXPORT Tree { public: /** @name construction and assignment */ /** @{ */ Tree() : Tree(get_callbacks()) {} Tree(Callbacks const& cb); Tree(id_type node_capacity, size_t arena_capacity=0) : Tree(node_capacity, arena_capacity, get_callbacks()) {} Tree(id_type node_capacity, size_t arena_capacity, Callbacks const& cb); ~Tree(); Tree(Tree const& that); Tree(Tree && that) noexcept; Tree& operator= (Tree const& that); Tree& operator= (Tree && that) noexcept; /** @} */ public: /** @name memory and sizing */ /** @{ */ void reserve(id_type node_capacity); /** clear the tree and zero every node * @note does NOT clear the arena * @see clear_arena() */ void clear(); void clear_arena() { m_arena_pos = 0; } bool empty() const { return m_size == 0; } id_type size() const { return m_size; } id_type capacity() const { return m_cap; } id_type slack() const { RYML_ASSERT(m_cap >= m_size); return m_cap - m_size; } Callbacks const& callbacks() const { return m_callbacks; } void callbacks(Callbacks const& cb) { m_callbacks = cb; } /** @} */ public: /** @name node getters */ /** @{ */ //! get the index of a node belonging to this tree. //! @p n can be nullptr, in which case NONE is returned id_type id(NodeData const* n) const { if( ! n) return NONE; _RYML_CB_ASSERT(m_callbacks, n >= m_buf && n < m_buf + m_cap); return static_cast(n - m_buf); } //! get a pointer to a node's NodeData. //! i can be NONE, in which case a nullptr is returned NodeData *get(id_type node) // NOLINT(readability-make-member-function-const) { if(node == NONE) return nullptr; _RYML_CB_ASSERT(m_callbacks, node >= 0 && node < m_cap); return m_buf + node; } //! get a pointer to a node's NodeData. //! i can be NONE, in which case a nullptr is returned. NodeData const *get(id_type node) const { if(node == NONE) return nullptr; _RYML_CB_ASSERT(m_callbacks, node >= 0 && node < m_cap); return m_buf + node; } //! An if-less form of get() that demands a valid node index. //! This function is implementation only; use at your own risk. NodeData * _p(id_type node) { _RYML_CB_ASSERT(m_callbacks, node != NONE && node >= 0 && node < m_cap); return m_buf + node; } // NOLINT(readability-make-member-function-const) //! An if-less form of get() that demands a valid node index. //! This function is implementation only; use at your own risk. NodeData const * _p(id_type node) const { _RYML_CB_ASSERT(m_callbacks, node != NONE && node >= 0 && node < m_cap); return m_buf + node; } //! Get the id of the root node id_type root_id() { if(m_cap == 0) { reserve(16); } _RYML_CB_ASSERT(m_callbacks, m_cap > 0 && m_size > 0); return 0; } //! Get the id of the root node id_type root_id() const { _RYML_CB_ASSERT(m_callbacks, m_cap > 0 && m_size > 0); return 0; } //! Get a NodeRef of a node by id NodeRef ref(id_type node); //! Get a NodeRef of a node by id ConstNodeRef ref(id_type node) const; //! Get a NodeRef of a node by id ConstNodeRef cref(id_type node) const; //! Get the root as a NodeRef NodeRef rootref(); //! Get the root as a ConstNodeRef ConstNodeRef rootref() const; //! Get the root as a ConstNodeRef ConstNodeRef crootref() const; //! get the i-th document of the stream //! @note @p i is NOT the node id, but the doc position within the stream NodeRef docref(id_type i); //! get the i-th document of the stream //! @note @p i is NOT the node id, but the doc position within the stream ConstNodeRef docref(id_type i) const; //! get the i-th document of the stream //! @note @p i is NOT the node id, but the doc position within the stream ConstNodeRef cdocref(id_type i) const; //! find a root child by name, return it as a NodeRef //! @note requires the root to be a map. NodeRef operator[] (csubstr key); //! find a root child by name, return it as a NodeRef //! @note requires the root to be a map. ConstNodeRef operator[] (csubstr key) const; //! find a root child by index: return the root node's @p i-th child as a NodeRef //! @note @p i is NOT the node id, but the child's position NodeRef operator[] (id_type i); //! find a root child by index: return the root node's @p i-th child as a NodeRef //! @note @p i is NOT the node id, but the child's position ConstNodeRef operator[] (id_type i) const; /** @} */ public: /** @name node property getters */ /** @{ */ NodeType type(id_type node) const { return _p(node)->m_type; } const char* type_str(id_type node) const { return NodeType::type_str(_p(node)->m_type); } csubstr const& key (id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_key(node)); return _p(node)->m_key.scalar; } csubstr const& key_tag (id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_key_tag(node)); return _p(node)->m_key.tag; } csubstr const& key_ref (id_type node) const { _RYML_CB_ASSERT(m_callbacks, is_key_ref(node)); return _p(node)->m_key.anchor; } csubstr const& key_anchor(id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_key_anchor(node)); return _p(node)->m_key.anchor; } NodeScalar const& keysc (id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_key(node)); return _p(node)->m_key; } csubstr const& val (id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_val(node)); return _p(node)->m_val.scalar; } csubstr const& val_tag (id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_val_tag(node)); return _p(node)->m_val.tag; } csubstr const& val_ref (id_type node) const { _RYML_CB_ASSERT(m_callbacks, is_val_ref(node)); return _p(node)->m_val.anchor; } csubstr const& val_anchor(id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_val_anchor(node)); return _p(node)->m_val.anchor; } NodeScalar const& valsc (id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_val(node)); return _p(node)->m_val; } /** @} */ public: /** @name node type predicates */ /** @{ */ C4_ALWAYS_INLINE bool type_has_any(id_type node, NodeType_e bits) const { return _p(node)->m_type.has_any(bits); } C4_ALWAYS_INLINE bool type_has_all(id_type node, NodeType_e bits) const { return _p(node)->m_type.has_all(bits); } C4_ALWAYS_INLINE bool type_has_none(id_type node, NodeType_e bits) const { return _p(node)->m_type.has_none(bits); } C4_ALWAYS_INLINE bool is_stream(id_type node) const { return _p(node)->m_type.is_stream(); } C4_ALWAYS_INLINE bool is_doc(id_type node) const { return _p(node)->m_type.is_doc(); } C4_ALWAYS_INLINE bool is_container(id_type node) const { return _p(node)->m_type.is_container(); } C4_ALWAYS_INLINE bool is_map(id_type node) const { return _p(node)->m_type.is_map(); } C4_ALWAYS_INLINE bool is_seq(id_type node) const { return _p(node)->m_type.is_seq(); } C4_ALWAYS_INLINE bool has_key(id_type node) const { return _p(node)->m_type.has_key(); } C4_ALWAYS_INLINE bool has_val(id_type node) const { return _p(node)->m_type.has_val(); } C4_ALWAYS_INLINE bool is_val(id_type node) const { return _p(node)->m_type.is_val(); } C4_ALWAYS_INLINE bool is_keyval(id_type node) const { return _p(node)->m_type.is_keyval(); } C4_ALWAYS_INLINE bool has_key_tag(id_type node) const { return _p(node)->m_type.has_key_tag(); } C4_ALWAYS_INLINE bool has_val_tag(id_type node) const { return _p(node)->m_type.has_val_tag(); } C4_ALWAYS_INLINE bool has_key_anchor(id_type node) const { return _p(node)->m_type.has_key_anchor(); } C4_ALWAYS_INLINE bool has_val_anchor(id_type node) const { return _p(node)->m_type.has_val_anchor(); } C4_ALWAYS_INLINE bool has_anchor(id_type node) const { return _p(node)->m_type.has_anchor(); } C4_ALWAYS_INLINE bool is_key_ref(id_type node) const { return _p(node)->m_type.is_key_ref(); } C4_ALWAYS_INLINE bool is_val_ref(id_type node) const { return _p(node)->m_type.is_val_ref(); } C4_ALWAYS_INLINE bool is_ref(id_type node) const { return _p(node)->m_type.is_ref(); } C4_ALWAYS_INLINE bool parent_is_seq(id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_parent(node)); return is_seq(_p(node)->m_parent); } C4_ALWAYS_INLINE bool parent_is_map(id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_parent(node)); return is_map(_p(node)->m_parent); } /** true when the node has an anchor named a */ C4_ALWAYS_INLINE bool has_anchor(id_type node, csubstr a) const { return _p(node)->m_key.anchor == a || _p(node)->m_val.anchor == a; } /** true if the node key is empty, or its scalar verifies @ref scalar_is_null(). * @warning the node must verify @ref Tree::has_key() (asserted) (ie must be a member of a map) * @see https://github.com/biojppm/rapidyaml/issues/413 */ C4_ALWAYS_INLINE bool key_is_null(id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_key(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_key_quoted() && (n->m_type.key_is_null() || scalar_is_null(n->m_key.scalar)); } /** true if the node val is empty, or its scalar verifies @ref scalar_is_null(). * @warning the node must verify @ref Tree::has_val() (asserted) (ie must be a scalar / must not be a container) * @see https://github.com/biojppm/rapidyaml/issues/413 */ C4_ALWAYS_INLINE bool val_is_null(id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_val(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_val_quoted() && (n->m_type.val_is_null() || scalar_is_null(n->m_val.scalar)); } /// true if the key was a scalar requiring filtering and was left /// unfiltered during the parsing (see ParserOptions) C4_ALWAYS_INLINE bool is_key_unfiltered(id_type node) const { return _p(node)->m_type.is_key_unfiltered(); } /// true if the val was a scalar requiring filtering and was left /// unfiltered during the parsing (see ParserOptions) C4_ALWAYS_INLINE bool is_val_unfiltered(id_type node) const { return _p(node)->m_type.is_val_unfiltered(); } RYML_DEPRECATED("use has_key_anchor()") bool is_key_anchor(id_type node) const { return _p(node)->m_type.has_key_anchor(); } RYML_DEPRECATED("use has_val_anchor()") bool is_val_anchor(id_type node) const { return _p(node)->m_type.has_val_anchor(); } RYML_DEPRECATED("use has_anchor()") bool is_anchor(id_type node) const { return _p(node)->m_type.has_anchor(); } RYML_DEPRECATED("use has_anchor_or_ref()") bool is_anchor_or_ref(id_type node) const { return _p(node)->m_type.has_anchor() || _p(node)->m_type.is_ref(); } /** @} */ public: /** @name hierarchy predicates */ /** @{ */ bool is_root(id_type node) const { _RYML_CB_ASSERT(m_callbacks, _p(node)->m_parent != NONE || node == 0); return _p(node)->m_parent == NONE; } bool has_parent(id_type node) const { return _p(node)->m_parent != NONE; } /** true when ancestor is parent or parent of a parent of node */ bool is_ancestor(id_type node, id_type ancestor) const; /** true when key and val are empty, and has no children */ bool empty(id_type node) const { return ! has_children(node) && _p(node)->m_key.empty() && (( ! (_p(node)->m_type & VAL)) || _p(node)->m_val.empty()); } /** true if @p node has a child with id @p ch */ bool has_child(id_type node, id_type ch) const { return _p(ch)->m_parent == node; } /** true if @p node has a child with key @p key */ bool has_child(id_type node, csubstr key) const { return find_child(node, key) != NONE; } /** true if @p node has any children key */ bool has_children(id_type node) const { return _p(node)->m_first_child != NONE; } /** true if @p node has a sibling with id @p sib */ bool has_sibling(id_type node, id_type sib) const { return _p(node)->m_parent == _p(sib)->m_parent; } /** true if one of the node's siblings has the given key */ bool has_sibling(id_type node, csubstr key) const { return find_sibling(node, key) != NONE; } /** true if node is not a single child */ bool has_other_siblings(id_type node) const { NodeData const *n = _p(node); if(C4_LIKELY(n->m_parent != NONE)) { n = _p(n->m_parent); return n->m_first_child != n->m_last_child; } return false; } RYML_DEPRECATED("use has_other_siblings()") static bool has_siblings(id_type /*node*/) { return true; } /** @} */ public: /** @name hierarchy getters */ /** @{ */ id_type parent(id_type node) const { return _p(node)->m_parent; } id_type prev_sibling(id_type node) const { return _p(node)->m_prev_sibling; } id_type next_sibling(id_type node) const { return _p(node)->m_next_sibling; } /** O(#num_children) */ id_type num_children(id_type node) const; id_type child_pos(id_type node, id_type ch) const; id_type first_child(id_type node) const { return _p(node)->m_first_child; } id_type last_child(id_type node) const { return _p(node)->m_last_child; } id_type child(id_type node, id_type pos) const; id_type find_child(id_type node, csubstr const& key) const; /** O(#num_siblings) */ /** counts with this */ id_type num_siblings(id_type node) const { return is_root(node) ? 1 : num_children(_p(node)->m_parent); } /** does not count with this */ id_type num_other_siblings(id_type node) const { id_type ns = num_siblings(node); _RYML_CB_ASSERT(m_callbacks, ns > 0); return ns-1; } id_type sibling_pos(id_type node, id_type sib) const { _RYML_CB_ASSERT(m_callbacks, ! is_root(node) || node == root_id()); return child_pos(_p(node)->m_parent, sib); } id_type first_sibling(id_type node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_first_child; } id_type last_sibling(id_type node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_last_child; } id_type sibling(id_type node, id_type pos) const { return child(_p(node)->m_parent, pos); } id_type find_sibling(id_type node, csubstr const& key) const { return find_child(_p(node)->m_parent, key); } id_type doc(id_type i) const { id_type rid = root_id(); _RYML_CB_ASSERT(m_callbacks, is_stream(rid)); return child(rid, i); } //!< gets the @p i document node index. requires that the root node is a stream. id_type depth_asc(id_type node) const; /**< O(log(num_tree_nodes)) get the ascending depth of the node: number of levels between root and node */ id_type depth_desc(id_type node) const; /**< O(num_tree_nodes) get the descending depth of the node: number of levels between node and deepest child */ /** @} */ public: /** @name node style predicates and modifiers. see the corresponding predicate in NodeType */ /** @{ */ C4_ALWAYS_INLINE bool is_container_styled(id_type node) const { return _p(node)->m_type.is_container_styled(); } C4_ALWAYS_INLINE bool is_block(id_type node) const { return _p(node)->m_type.is_block(); } C4_ALWAYS_INLINE bool is_flow_sl(id_type node) const { return _p(node)->m_type.is_flow_sl(); } C4_ALWAYS_INLINE bool is_flow_ml(id_type node) const { return _p(node)->m_type.is_flow_ml(); } C4_ALWAYS_INLINE bool is_flow(id_type node) const { return _p(node)->m_type.is_flow(); } C4_ALWAYS_INLINE bool is_key_styled(id_type node) const { return _p(node)->m_type.is_key_styled(); } C4_ALWAYS_INLINE bool is_val_styled(id_type node) const { return _p(node)->m_type.is_val_styled(); } C4_ALWAYS_INLINE bool is_key_literal(id_type node) const { return _p(node)->m_type.is_key_literal(); } C4_ALWAYS_INLINE bool is_val_literal(id_type node) const { return _p(node)->m_type.is_val_literal(); } C4_ALWAYS_INLINE bool is_key_folded(id_type node) const { return _p(node)->m_type.is_key_folded(); } C4_ALWAYS_INLINE bool is_val_folded(id_type node) const { return _p(node)->m_type.is_val_folded(); } C4_ALWAYS_INLINE bool is_key_squo(id_type node) const { return _p(node)->m_type.is_key_squo(); } C4_ALWAYS_INLINE bool is_val_squo(id_type node) const { return _p(node)->m_type.is_val_squo(); } C4_ALWAYS_INLINE bool is_key_dquo(id_type node) const { return _p(node)->m_type.is_key_dquo(); } C4_ALWAYS_INLINE bool is_val_dquo(id_type node) const { return _p(node)->m_type.is_val_dquo(); } C4_ALWAYS_INLINE bool is_key_plain(id_type node) const { return _p(node)->m_type.is_key_plain(); } C4_ALWAYS_INLINE bool is_val_plain(id_type node) const { return _p(node)->m_type.is_val_plain(); } C4_ALWAYS_INLINE bool is_key_quoted(id_type node) const { return _p(node)->m_type.is_key_quoted(); } C4_ALWAYS_INLINE bool is_val_quoted(id_type node) const { return _p(node)->m_type.is_val_quoted(); } C4_ALWAYS_INLINE bool is_quoted(id_type node) const { return _p(node)->m_type.is_quoted(); } C4_ALWAYS_INLINE void set_container_style(id_type node, NodeType_e style) { _RYML_CB_ASSERT(m_callbacks, is_container(node)); _p(node)->m_type.set_container_style(style); } C4_ALWAYS_INLINE void set_key_style(id_type node, NodeType_e style) { _RYML_CB_ASSERT(m_callbacks, has_key(node)); _p(node)->m_type.set_key_style(style); } C4_ALWAYS_INLINE void set_val_style(id_type node, NodeType_e style) { _RYML_CB_ASSERT(m_callbacks, has_val(node)); _p(node)->m_type.set_val_style(style); } /** @} */ public: /** @name node type modifiers */ /** @{ */ void to_keyval(id_type node, csubstr key, csubstr val, type_bits more_flags=0); void to_map(id_type node, csubstr key, type_bits more_flags=0); void to_seq(id_type node, csubstr key, type_bits more_flags=0); void to_val(id_type node, csubstr val, type_bits more_flags=0); void to_map(id_type node, type_bits more_flags=0); void to_seq(id_type node, type_bits more_flags=0); void to_doc(id_type node, type_bits more_flags=0); void to_stream(id_type node, type_bits more_flags=0); void set_key(id_type node, csubstr key) { _RYML_CB_ASSERT(m_callbacks, has_key(node)); _p(node)->m_key.scalar = key; } void set_val(id_type node, csubstr val) { _RYML_CB_ASSERT(m_callbacks, has_val(node)); _p(node)->m_val.scalar = val; } void set_key_tag(id_type node, csubstr tag) { _RYML_CB_ASSERT(m_callbacks, has_key(node)); _p(node)->m_key.tag = tag; _add_flags(node, KEYTAG); } void set_val_tag(id_type node, csubstr tag) { _RYML_CB_ASSERT(m_callbacks, has_val(node) || is_container(node)); _p(node)->m_val.tag = tag; _add_flags(node, VALTAG); } void set_key_anchor(id_type node, csubstr anchor) { _RYML_CB_ASSERT(m_callbacks, ! is_key_ref(node)); _p(node)->m_key.anchor = anchor.triml('&'); _add_flags(node, KEYANCH); } void set_val_anchor(id_type node, csubstr anchor) { _RYML_CB_ASSERT(m_callbacks, ! is_val_ref(node)); _p(node)->m_val.anchor = anchor.triml('&'); _add_flags(node, VALANCH); } void set_key_ref (id_type node, csubstr ref ) { _RYML_CB_ASSERT(m_callbacks, ! has_key_anchor(node)); NodeData* C4_RESTRICT n = _p(node); n->m_key.set_ref_maybe_replacing_scalar(ref, n->m_type.has_key()); _add_flags(node, KEY|KEYREF); } void set_val_ref (id_type node, csubstr ref ) { _RYML_CB_ASSERT(m_callbacks, ! has_val_anchor(node)); NodeData* C4_RESTRICT n = _p(node); n->m_val.set_ref_maybe_replacing_scalar(ref, n->m_type.has_val()); _add_flags(node, VAL|VALREF); } void rem_key_anchor(id_type node) { _p(node)->m_key.anchor.clear(); _rem_flags(node, KEYANCH); } void rem_val_anchor(id_type node) { _p(node)->m_val.anchor.clear(); _rem_flags(node, VALANCH); } void rem_key_ref (id_type node) { _p(node)->m_key.anchor.clear(); _rem_flags(node, KEYREF); } void rem_val_ref (id_type node) { _p(node)->m_val.anchor.clear(); _rem_flags(node, VALREF); } void rem_anchor_ref(id_type node) { _p(node)->m_key.anchor.clear(); _p(node)->m_val.anchor.clear(); _rem_flags(node, KEYANCH|VALANCH|KEYREF|VALREF); } /** @} */ public: /** @name tree modifiers */ /** @{ */ /** reorder the tree in memory so that all the nodes are stored * in a linear sequence when visited in depth-first order. * This will invalidate existing ids, since the node id is its * position in the tree's node array. */ void reorder(); /** @} */ public: /** @name anchors and references/aliases */ /** @{ */ /** Resolve references (aliases <- anchors), by forwarding to @ref * ReferenceResolver::resolve(); refer to @ref * ReferenceResolver::resolve() for further details. */ void resolve(ReferenceResolver *C4_RESTRICT rr, bool clear_anchors=true); /** Resolve references (aliases <- anchors), by forwarding to @ref * ReferenceResolver::resolve(); refer to @ref * ReferenceResolver::resolve() for further details. This overload * uses a throwaway resolver object. */ void resolve(bool clear_anchors=true); public: /** @name tag directives */ /** @{ */ void resolve_tags(); void normalize_tags(); void normalize_tags_long(); id_type num_tag_directives() const; bool add_tag_directive(csubstr directive); id_type add_tag_directive(TagDirective const& td); void clear_tag_directives(); /** resolve the given tag, appearing at node_id. Write the result into output. * @return the number of characters required for the resolved tag */ size_t resolve_tag(substr output, csubstr tag, id_type node_id) const; csubstr resolve_tag_sub(substr output, csubstr tag, id_type node_id) const { size_t needed = resolve_tag(output, tag, node_id); return needed <= output.len ? output.first(needed) : output; } TagDirective const* begin_tag_directives() const { return m_tag_directives; } TagDirective const* end_tag_directives() const { return m_tag_directives + num_tag_directives(); } c4::yml::TagDirectiveRange tag_directives() const { return c4::yml::TagDirectiveRange{begin_tag_directives(), end_tag_directives()}; } RYML_DEPRECATED("use c4::yml::tag_directive_const_iterator") typedef TagDirective const* tag_directive_const_iterator; RYML_DEPRECATED("use c4::yml::TagDirectiveRange") typedef c4::yml::TagDirectiveRange TagDirectiveProxy; /** @} */ public: /** @name modifying hierarchy */ /** @{ */ /** create and insert a new child of @p parent. insert after the (to-be) * sibling @p after, which must be a child of @p parent. To insert as the * first child, set after to NONE */ C4_ALWAYS_INLINE id_type insert_child(id_type parent, id_type after) { _RYML_CB_ASSERT(m_callbacks, parent != NONE); _RYML_CB_ASSERT(m_callbacks, is_container(parent) || is_root(parent)); _RYML_CB_ASSERT(m_callbacks, after == NONE || (_p(after)->m_parent == parent)); id_type child = _claim(); _set_hierarchy(child, parent, after); return child; } /** create and insert a node as the first child of @p parent */ C4_ALWAYS_INLINE id_type prepend_child(id_type parent) { return insert_child(parent, NONE); } /** create and insert a node as the last child of @p parent */ C4_ALWAYS_INLINE id_type append_child(id_type parent) { return insert_child(parent, _p(parent)->m_last_child); } C4_ALWAYS_INLINE id_type _append_child__unprotected(id_type parent) { id_type child = _claim(); _set_hierarchy(child, parent, _p(parent)->m_last_child); return child; } public: #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wnull-dereference" #elif defined(__GNUC__) # pragma GCC diagnostic push # if __GNUC__ >= 6 # pragma GCC diagnostic ignored "-Wnull-dereference" # endif #endif //! create and insert a new sibling of n. insert after "after" C4_ALWAYS_INLINE id_type insert_sibling(id_type node, id_type after) { return insert_child(_p(node)->m_parent, after); } /** create and insert a node as the first node of @p parent */ C4_ALWAYS_INLINE id_type prepend_sibling(id_type node) { return prepend_child(_p(node)->m_parent); } C4_ALWAYS_INLINE id_type append_sibling(id_type node) { return append_child(_p(node)->m_parent); } public: /** remove an entire branch at once: ie remove the children and the node itself */ void remove(id_type node) { remove_children(node); _release(node); } /** remove all the node's children, but keep the node itself */ void remove_children(id_type node); /** change the @p type of the node to one of MAP, SEQ or VAL. @p * type must have one and only one of MAP,SEQ,VAL; @p type may * possibly have KEY, but if it does, then the @p node must also * have KEY. Changing to the same type is a no-op. Otherwise, * changing to a different type will initialize the node with an * empty value of the desired type: changing to VAL will * initialize with a null scalar (~), changing to MAP will * initialize with an empty map ({}), and changing to SEQ will * initialize with an empty seq ([]). */ bool change_type(id_type node, NodeType type); bool change_type(id_type node, type_bits type) { return change_type(node, (NodeType)type); } #if defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif public: /** change the node's position in the parent */ void move(id_type node, id_type after); /** change the node's parent and position */ void move(id_type node, id_type new_parent, id_type after); /** change the node's parent and position to a different tree * @return the index of the new node in the destination tree */ id_type move(Tree * src, id_type node, id_type new_parent, id_type after); /** ensure the first node is a stream. Eg, change this tree * * DOCMAP * MAP * KEYVAL * KEYVAL * SEQ * VAL * * to * * STREAM * DOCMAP * MAP * KEYVAL * KEYVAL * SEQ * VAL * * If the root is already a stream, this is a no-op. */ void set_root_as_stream(); public: /** recursively duplicate a node from this tree into a new parent, * placing it after one of its children * @return the index of the copy */ id_type duplicate(id_type node, id_type new_parent, id_type after); /** recursively duplicate a node from a different tree into a new parent, * placing it after one of its children * @return the index of the copy */ id_type duplicate(Tree const* src, id_type node, id_type new_parent, id_type after); /** recursively duplicate the node's children (but not the node) * @return the index of the last duplicated child */ id_type duplicate_children(id_type node, id_type parent, id_type after); /** recursively duplicate the node's children (but not the node), where * the node is from a different tree * @return the index of the last duplicated child */ id_type duplicate_children(Tree const* src, id_type node, id_type parent, id_type after); /** duplicate the node's children (but not the node) in a new parent, but * omit repetitions where a duplicated node has the same key (in maps) or * value (in seqs). If one of the duplicated children has the same key * (in maps) or value (in seqs) as one of the parent's children, the one * that is placed closest to the end will prevail. */ id_type duplicate_children_no_rep(id_type node, id_type parent, id_type after); id_type duplicate_children_no_rep(Tree const* src, id_type node, id_type parent, id_type after); void duplicate_contents(id_type node, id_type where); void duplicate_contents(Tree const* src, id_type node, id_type where); public: void merge_with(Tree const* src, id_type src_node=NONE, id_type dst_root=NONE); /** @} */ public: /** @name internal string arena */ /** @{ */ /** get the current size of the tree's internal arena */ RYML_DEPRECATED("use arena_size() instead") size_t arena_pos() const { return m_arena_pos; } /** get the current size of the tree's internal arena */ size_t arena_size() const { return m_arena_pos; } /** get the current capacity of the tree's internal arena */ size_t arena_capacity() const { return m_arena.len; } /** get the current slack of the tree's internal arena */ size_t arena_slack() const { _RYML_CB_ASSERT(m_callbacks, m_arena.len >= m_arena_pos); return m_arena.len - m_arena_pos; } /** get the current arena */ csubstr arena() const { return m_arena.first(m_arena_pos); } /** get the current arena */ substr arena() { return m_arena.first(m_arena_pos); } // NOLINT(readability-make-member-function-const) /** return true if the given substring is part of the tree's string arena */ bool in_arena(csubstr s) const { return m_arena.is_super(s); } /** serialize the given floating-point variable to the tree's * arena, growing it as needed to accomodate the serialization. * * @note Growing the arena may cause relocation of the entire * existing arena, and thus change the contents of individual * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this * cost, ensure that the arena is reserved to an appropriate size * using @ref Tree::reserve_arena(). * * @see alloc_arena() */ template auto to_arena(T const& C4_RESTRICT a) -> typename std::enable_if::value, csubstr>::type { substr rem(m_arena.sub(m_arena_pos)); size_t num = to_chars_float(rem, a); if(num > rem.len) { rem = _grow_arena(num); num = to_chars_float(rem, a); _RYML_CB_ASSERT(m_callbacks, num <= rem.len); } rem = _request_span(num); return rem; } /** serialize the given non-floating-point variable to the tree's * arena, growing it as needed to accomodate the serialization. * * @note Growing the arena may cause relocation of the entire * existing arena, and thus change the contents of individual * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this * cost, ensure that the arena is reserved to an appropriate size * using @ref Tree::reserve_arena(). * * @see alloc_arena() */ template auto to_arena(T const& C4_RESTRICT a) -> typename std::enable_if::value, csubstr>::type { substr rem(m_arena.sub(m_arena_pos)); size_t num = to_chars(rem, a); if(num > rem.len) { rem = _grow_arena(num); num = to_chars(rem, a); _RYML_CB_ASSERT(m_callbacks, num <= rem.len); } rem = _request_span(num); return rem; } /** serialize the given csubstr to the tree's arena, growing the * arena as needed to accomodate the serialization. * * @note Growing the arena may cause relocation of the entire * existing arena, and thus change the contents of individual * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this * cost, ensure that the arena is reserved to an appropriate size * using @ref Tree::reserve_arena(). * * @see alloc_arena() */ csubstr to_arena(csubstr a) { if(a.len > 0) { substr rem(m_arena.sub(m_arena_pos)); size_t num = to_chars(rem, a); if(num > rem.len) { rem = _grow_arena(num); num = to_chars(rem, a); _RYML_CB_ASSERT(m_callbacks, num <= rem.len); } return _request_span(num); } else { if(a.str == nullptr) { return csubstr{}; } else if(m_arena.str == nullptr) { // Arena is empty and we want to store a non-null // zero-length string. // Even though the string has zero length, we need // some "memory" to store a non-nullptr string _grow_arena(1); } return _request_span(0); } } C4_ALWAYS_INLINE csubstr to_arena(const char *s) { return to_arena(to_csubstr(s)); } C4_ALWAYS_INLINE static csubstr to_arena(std::nullptr_t) { return csubstr{}; } /** copy the given substr to the tree's arena, growing it by the * required size * * @note Growing the arena may cause relocation of the entire * existing arena, and thus change the contents of individual * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this * cost, ensure that the arena is reserved to an appropriate size * before using @ref Tree::reserve_arena() * * @see reserve_arena() * @see alloc_arena() */ substr copy_to_arena(csubstr s) { substr cp = alloc_arena(s.len); _RYML_CB_ASSERT(m_callbacks, cp.len == s.len); _RYML_CB_ASSERT(m_callbacks, !s.overlaps(cp)); #if (!defined(__clang__)) && (defined(__GNUC__) && __GNUC__ >= 10) C4_SUPPRESS_WARNING_GCC_PUSH C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow=") // no need for terminating \0 C4_SUPPRESS_WARNING_GCC("-Wrestrict") // there's an assert to ensure no violation of restrict behavior #endif if(s.len) memcpy(cp.str, s.str, s.len); #if (!defined(__clang__)) && (defined(__GNUC__) && __GNUC__ >= 10) C4_SUPPRESS_WARNING_GCC_POP #endif return cp; } /** grow the tree's string arena by the given size and return a substr * of the added portion * * @note Growing the arena may cause relocation of the entire * existing arena, and thus change the contents of individual * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this * cost, ensure that the arena is reserved to an appropriate size * using .reserve_arena(). * * @see reserve_arena() */ substr alloc_arena(size_t sz) { if(sz > arena_slack()) _grow_arena(sz - arena_slack()); substr s = _request_span(sz); return s; } /** ensure the tree's internal string arena is at least the given capacity * @warning This operation may be expensive, with a potential complexity of O(numNodes)+O(arenasize). * @warning Growing the arena may cause relocation of the entire * existing arena, and thus change the contents of individual nodes. */ void reserve_arena(size_t arena_cap) { if(arena_cap > m_arena.len) { substr buf; buf.str = (char*) m_callbacks.m_allocate(arena_cap, m_arena.str, m_callbacks.m_user_data); buf.len = arena_cap; if(m_arena.str) { _RYML_CB_ASSERT(m_callbacks, m_arena.len >= 0); _relocate(buf); // does a memcpy and changes nodes using the arena m_callbacks.m_free(m_arena.str, m_arena.len, m_callbacks.m_user_data); } m_arena = buf; } } /** @} */ private: substr _grow_arena(size_t more) { size_t cap = m_arena.len + more; cap = cap < 2 * m_arena.len ? 2 * m_arena.len : cap; cap = cap < 64 ? 64 : cap; reserve_arena(cap); return m_arena.sub(m_arena_pos); } substr _request_span(size_t sz) { _RYML_CB_ASSERT(m_callbacks, m_arena_pos + sz <= m_arena.len); substr s; s = m_arena.sub(m_arena_pos, sz); m_arena_pos += sz; return s; } substr _relocated(csubstr s, substr next_arena) const { _RYML_CB_ASSERT(m_callbacks, m_arena.is_super(s)); _RYML_CB_ASSERT(m_callbacks, m_arena.sub(0, m_arena_pos).is_super(s)); auto pos = (s.str - m_arena.str); // this is larger than 0 based on the assertions above substr r(next_arena.str + pos, s.len); _RYML_CB_ASSERT(m_callbacks, r.str - next_arena.str == pos); _RYML_CB_ASSERT(m_callbacks, next_arena.sub(0, m_arena_pos).is_super(r)); return r; } public: /** @name lookup */ /** @{ */ struct lookup_result { id_type target; id_type closest; size_t path_pos; csubstr path; operator bool() const { return target != NONE; } lookup_result() : target(NONE), closest(NONE), path_pos(0), path() {} lookup_result(csubstr path_, id_type start) : target(NONE), closest(start), path_pos(0), path(path_) {} /** get the part ot the input path that was resolved */ csubstr resolved() const; /** get the part ot the input path that was unresolved */ csubstr unresolved() const; }; /** for example foo.bar[0].baz */ lookup_result lookup_path(csubstr path, id_type start=NONE) const; /** defaulted lookup: lookup @p path; if the lookup fails, recursively modify * the tree so that the corresponding lookup_path() would return the * default value. * @see lookup_path() */ id_type lookup_path_or_modify(csubstr default_value, csubstr path, id_type start=NONE); /** defaulted lookup: lookup @p path; if the lookup fails, recursively modify * the tree so that the corresponding lookup_path() would return the * branch @p src_node (from the tree @p src). * @see lookup_path() */ id_type lookup_path_or_modify(Tree const *src, id_type src_node, csubstr path, id_type start=NONE); /** @} */ private: struct _lookup_path_token { csubstr value; NodeType type; _lookup_path_token() : value(), type() {} _lookup_path_token(csubstr v, NodeType t) : value(v), type(t) {} operator bool() const { return type != NOTYPE; } bool is_index() const { return value.begins_with('[') && value.ends_with(']'); } }; id_type _lookup_path_or_create(csubstr path, id_type start); void _lookup_path (lookup_result *r) const; void _lookup_path_modify(lookup_result *r); id_type _next_node (lookup_result *r, _lookup_path_token *parent) const; id_type _next_node_modify(lookup_result *r, _lookup_path_token *parent); static void _advance(lookup_result *r, size_t more); _lookup_path_token _next_token(lookup_result *r, _lookup_path_token const& parent) const; private: void _clear(); void _free(); void _copy(Tree const& that); void _move(Tree & that) noexcept; void _relocate(substr next_arena); public: /** @cond dev*/ #if ! RYML_USE_ASSERT C4_ALWAYS_INLINE void _check_next_flags(id_type, type_bits) {} #else void _check_next_flags(id_type node, type_bits f) { NodeData *n = _p(node); type_bits o = n->m_type; // old C4_UNUSED(o); if(f & MAP) { RYML_ASSERT_MSG((f & SEQ) == 0, "cannot mark simultaneously as map and seq"); RYML_ASSERT_MSG((f & VAL) == 0, "cannot mark simultaneously as map and val"); RYML_ASSERT_MSG((o & SEQ) == 0, "cannot turn a seq into a map; clear first"); RYML_ASSERT_MSG((o & VAL) == 0, "cannot turn a val into a map; clear first"); } else if(f & SEQ) { RYML_ASSERT_MSG((f & MAP) == 0, "cannot mark simultaneously as seq and map"); RYML_ASSERT_MSG((f & VAL) == 0, "cannot mark simultaneously as seq and val"); RYML_ASSERT_MSG((o & MAP) == 0, "cannot turn a map into a seq; clear first"); RYML_ASSERT_MSG((o & VAL) == 0, "cannot turn a val into a seq; clear first"); } if(f & KEY) { _RYML_CB_ASSERT(m_callbacks, !is_root(node)); auto pid = parent(node); C4_UNUSED(pid); _RYML_CB_ASSERT(m_callbacks, is_map(pid)); } if((f & VAL) && !is_root(node)) { auto pid = parent(node); C4_UNUSED(pid); _RYML_CB_ASSERT(m_callbacks, is_map(pid) || is_seq(pid)); } } #endif void _set_flags(id_type node, NodeType_e f) { _check_next_flags(node, f); _p(node)->m_type = f; } void _set_flags(id_type node, type_bits f) { _check_next_flags(node, f); _p(node)->m_type = f; } void _add_flags(id_type node, NodeType_e f) { NodeData *d = _p(node); type_bits fb = f | d->m_type; _check_next_flags(node, fb); d->m_type = (NodeType_e) fb; } void _add_flags(id_type node, type_bits f) { NodeData *d = _p(node); f |= d->m_type; _check_next_flags(node, f); d->m_type = f; } void _rem_flags(id_type node, NodeType_e f) { NodeData *d = _p(node); type_bits fb = d->m_type & ~f; _check_next_flags(node, fb); d->m_type = (NodeType_e) fb; } void _rem_flags(id_type node, type_bits f) { NodeData *d = _p(node); f = d->m_type & ~f; _check_next_flags(node, f); d->m_type = f; } void _set_key(id_type node, csubstr key, type_bits more_flags=0) { _p(node)->m_key.scalar = key; _add_flags(node, KEY|more_flags); } void _set_key(id_type node, NodeScalar const& key, type_bits more_flags=0) { _p(node)->m_key = key; _add_flags(node, KEY|more_flags); } void _set_val(id_type node, csubstr val, type_bits more_flags=0) { _RYML_CB_ASSERT(m_callbacks, num_children(node) == 0); _RYML_CB_ASSERT(m_callbacks, !is_seq(node) && !is_map(node)); _p(node)->m_val.scalar = val; _add_flags(node, VAL|more_flags); } void _set_val(id_type node, NodeScalar const& val, type_bits more_flags=0) { _RYML_CB_ASSERT(m_callbacks, num_children(node) == 0); _RYML_CB_ASSERT(m_callbacks, ! is_container(node)); _p(node)->m_val = val; _add_flags(node, VAL|more_flags); } void _set(id_type node, NodeInit const& i) { _RYML_CB_ASSERT(m_callbacks, i._check()); NodeData *n = _p(node); _RYML_CB_ASSERT(m_callbacks, n->m_key.scalar.empty() || i.key.scalar.empty() || i.key.scalar == n->m_key.scalar); _add_flags(node, i.type); if(n->m_key.scalar.empty()) { if( ! i.key.scalar.empty()) { _set_key(node, i.key.scalar); } } n->m_key.tag = i.key.tag; n->m_val = i.val; } void _set_parent_as_container_if_needed(id_type in) { NodeData const* n = _p(in); id_type ip = parent(in); if(ip != NONE) { if( ! (is_seq(ip) || is_map(ip))) { if((in == first_child(ip)) && (in == last_child(ip))) { if( ! n->m_key.empty() || has_key(in)) { _add_flags(ip, MAP); } else { _add_flags(ip, SEQ); } } } } } void _seq2map(id_type node) { _RYML_CB_ASSERT(m_callbacks, is_seq(node)); for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) { NodeData *C4_RESTRICT ch = _p(i); if(ch->m_type.is_keyval()) continue; ch->m_type.add(KEY); ch->m_key = ch->m_val; } auto *C4_RESTRICT n = _p(node); n->m_type.rem(SEQ); n->m_type.add(MAP); } id_type _do_reorder(id_type *node, id_type count); void _swap(id_type n_, id_type m_); void _swap_props(id_type n_, id_type m_); void _swap_hierarchy(id_type n_, id_type m_); void _copy_hierarchy(id_type dst_, id_type src_); void _copy_props(id_type dst_, id_type src_) { _copy_props(dst_, this, src_); } void _copy_props_wo_key(id_type dst_, id_type src_) { _copy_props_wo_key(dst_, this, src_); } void _copy_props(id_type dst_, Tree const* that_tree, id_type src_) { auto & C4_RESTRICT dst = *_p(dst_); auto const& C4_RESTRICT src = *that_tree->_p(src_); dst.m_type = src.m_type; dst.m_key = src.m_key; dst.m_val = src.m_val; } void _copy_props(id_type dst_, Tree const* that_tree, id_type src_, type_bits src_mask) { auto & C4_RESTRICT dst = *_p(dst_); auto const& C4_RESTRICT src = *that_tree->_p(src_); dst.m_type = (src.m_type & src_mask) | (dst.m_type & ~src_mask); dst.m_key = src.m_key; dst.m_val = src.m_val; } void _copy_props_wo_key(id_type dst_, Tree const* that_tree, id_type src_) { auto & C4_RESTRICT dst = *_p(dst_); auto const& C4_RESTRICT src = *that_tree->_p(src_); dst.m_type = (src.m_type & ~_KEYMASK) | (dst.m_type & _KEYMASK); dst.m_val = src.m_val; } void _copy_props_wo_key(id_type dst_, Tree const* that_tree, id_type src_, type_bits src_mask) { auto & C4_RESTRICT dst = *_p(dst_); auto const& C4_RESTRICT src = *that_tree->_p(src_); dst.m_type = (src.m_type & ((~_KEYMASK)|src_mask)) | (dst.m_type & (_KEYMASK|~src_mask)); dst.m_val = src.m_val; } void _clear_type(id_type node) { _p(node)->m_type = NOTYPE; } void _clear(id_type node) { auto *C4_RESTRICT n = _p(node); n->m_type = NOTYPE; n->m_key.clear(); n->m_val.clear(); n->m_parent = NONE; n->m_first_child = NONE; n->m_last_child = NONE; } void _clear_key(id_type node) { _p(node)->m_key.clear(); _rem_flags(node, KEY); } void _clear_val(id_type node) { _p(node)->m_val.clear(); _rem_flags(node, VAL); } /** @endcond */ private: void _clear_range(id_type first, id_type num); public: id_type _claim(); private: void _claim_root(); void _release(id_type node); void _free_list_add(id_type node); void _free_list_rem(id_type node); void _set_hierarchy(id_type node, id_type parent, id_type after_sibling); void _rem_hierarchy(id_type node); public: // members are exposed, but you should NOT access them directly NodeData *m_buf; id_type m_cap; id_type m_size; id_type m_free_head; id_type m_free_tail; substr m_arena; size_t m_arena_pos; Callbacks m_callbacks; TagDirective m_tag_directives[RYML_MAX_TAG_DIRECTIVES]; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_serialization_helpers Serialization helpers * * @{ */ // NON-ARITHMETIC ------------------------------------------------------------- /** convert the val of a scalar node to a particular non-arithmetic * non-float type, by forwarding its val to @ref from_chars(). The * full string is used. * @return false if the conversion failed, or if the key was empty and unquoted */ template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type { return C4_LIKELY(!(tree->type(id) & VALNIL)) ? from_chars(tree->val(id), v) : false; } /** convert the key of a node to a particular non-arithmetic * non-float type, by forwarding its key to @ref from_chars(). The * full string is used. * @return false if the conversion failed, or if the key was empty and unquoted */ template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type { return C4_LIKELY(!(tree->type(id) & KEYNIL)) ? from_chars(tree->key(id), v) : false; } // INTEGRAL, NOT FLOATING ------------------------------------------------------------- /** convert the val of a scalar node to a particular arithmetic * integral non-float type, by forwarding its val to @ref * from_chars(). The full string is used. * * @return false if the conversion failed */ template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type { using U = typename std::remove_cv::type; enum { ischar = std::is_same::value || std::is_same::value || std::is_same::value }; csubstr val = tree->val(id); NodeType ty = tree->type(id); if(C4_UNLIKELY((ty & VALNIL) || val.empty())) return false; // quote integral numbers if they have a leading 0 // https://github.com/biojppm/rapidyaml/issues/291 char first = val[0]; if(ty.is_val_quoted() && (first != '0' && !ischar)) return false; else if(first == '+') val = val.sub(1); return from_chars(val, v); } /** convert the key of a node to a particular arithmetic * integral non-float type, by forwarding its val to @ref * from_chars(). The full string is used. * * @return false if the conversion failed */ template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type { using U = typename std::remove_cv::type; enum { ischar = std::is_same::value || std::is_same::value || std::is_same::value }; csubstr key = tree->key(id); NodeType ty = tree->type(id); if((ty & KEYNIL) || key.empty()) return false; // quote integral numbers if they have a leading 0 // https://github.com/biojppm/rapidyaml/issues/291 char first = key[0]; if(ty.is_key_quoted() && (first != '0' && !ischar)) return false; else if(first == '+') key = key.sub(1); return from_chars(key, v); } // FLOATING ------------------------------------------------------------- /** encode a floating point value to a string. */ template size_t to_chars_float(substr buf, T val) { static_assert(std::is_floating_point::value, "must be floating point"); C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal"); if(C4_UNLIKELY(std::isnan(val))) return to_chars(buf, csubstr(".nan")); else if(C4_UNLIKELY(val == std::numeric_limits::infinity())) return to_chars(buf, csubstr(".inf")); else if(C4_UNLIKELY(val == -std::numeric_limits::infinity())) return to_chars(buf, csubstr("-.inf")); return to_chars(buf, val); C4_SUPPRESS_WARNING_GCC_CLANG_POP } /** decode a floating point from string. Accepts special values: .nan, * .inf, -.inf */ template bool from_chars_float(csubstr buf, T *C4_RESTRICT val) { static_assert(std::is_floating_point::value, "must be floating point"); if(buf.begins_with('+')) { buf = buf.sub(1); } if(C4_LIKELY(from_chars(buf, val))) { return true; } else if(C4_UNLIKELY(buf == ".nan" || buf == ".NaN" || buf == ".NAN")) { *val = std::numeric_limits::quiet_NaN(); return true; } else if(C4_UNLIKELY(buf == ".inf" || buf == ".Inf" || buf == ".INF")) { *val = std::numeric_limits::infinity(); return true; } else if(C4_UNLIKELY(buf == "-.inf" || buf == "-.Inf" || buf == "-.INF")) { *val = -std::numeric_limits::infinity(); return true; } else { return false; } } /** convert the val of a scalar node to a floating point type, by * forwarding its val to @ref from_chars_float(). * * @return false if the conversion failed * * @warning Unlike non-floating types, only the leading part of the * string that may constitute a number is processed. This happens * because the float parsing is delegated to fast_float, which is * implemented that way. Consequently, for example, all of `"34"`, * `"34 "` `"34hg"` `"34 gh"` will be read as 34. If you are not sure * about the contents of the data, you can use * csubstr::first_real_span() to check before calling `>>`, for * example like this: * * ```cpp * csubstr val = node.val(); * if(val.first_real_span() == val) * node >> v; * else * ERROR("not a real") * ``` */ template typename std::enable_if::value, bool>::type inline read(Tree const* C4_RESTRICT tree, id_type id, T *v) { csubstr val = tree->val(id); return C4_LIKELY(!val.empty()) ? from_chars_float(val, v) : false; } /** convert the key of a scalar node to a floating point type, by * forwarding its key to @ref from_chars_float(). * * @return false if the conversion failed * * @warning Unlike non-floating types, only the leading part of the * string that may constitute a number is processed. This happens * because the float parsing is delegated to fast_float, which is * implemented that way. Consequently, for example, all of `"34"`, * `"34 "` `"34hg"` `"34 gh"` will be read as 34. If you are not sure * about the contents of the data, you can use * csubstr::first_real_span() to check before calling `>>`, for * example like this: * * ```cpp * csubstr key = node.key(); * if(key.first_real_span() == key) * node >> v; * else * ERROR("not a real") * ``` */ template typename std::enable_if::value, bool>::type inline readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) { csubstr key = tree->key(id); return C4_LIKELY(!key.empty()) ? from_chars_float(key, v) : false; } /** @} */ /** @} */ } // namespace yml } // namespace c4 C4_SUPPRESS_WARNING_MSVC_POP C4_SUPPRESS_WARNING_GCC_CLANG_POP #endif /* _C4_YML_TREE_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/node.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_NODE_HPP_ #define _C4_YML_NODE_HPP_ /** @file node.hpp Node classes */ //included above: //#include // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp //#include "c4/yml/tree.hpp" #if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) #error "amalgamate: file c4/yml/tree.hpp must have been included at this point" #endif /* C4_YML_TREE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/base64.hpp //#include "c4/base64.hpp" #if !defined(C4_BASE64_HPP_) && !defined(_C4_BASE64_HPP_) #error "amalgamate: file c4/base64.hpp must have been included at this point" #endif /* C4_BASE64_HPP_ */ #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wtype-limits" # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wtype-limits" # pragma GCC diagnostic ignored "-Wold-style-cast" # pragma GCC diagnostic ignored "-Wuseless-cast" #elif defined(_MSC_VER) # pragma warning(push) # pragma warning(disable: 4251/*needs to have dll-interface to be used by clients of struct*/) # pragma warning(disable: 4296/*expression is always 'boolean_value'*/) #endif namespace c4 { namespace yml { /** @addtogroup doc_node_classes * * @{ */ /** @defgroup doc_serialization_helpers Serialization helpers * * @{ */ template struct Key { K & k; }; // NOLINT template<> struct Key { fmt::const_base64_wrapper wrapper; }; template<> struct Key { fmt::base64_wrapper wrapper; }; template C4_ALWAYS_INLINE Key key(K & k) { return Key{k}; } C4_ALWAYS_INLINE Key key(fmt::const_base64_wrapper w) { return {w}; } C4_ALWAYS_INLINE Key key(fmt::base64_wrapper w) { return {w}; } template void write(NodeRef *n, T const& v); template inline bool read(ConstNodeRef const& C4_RESTRICT n, T *v); template inline bool read(NodeRef const& C4_RESTRICT n, T *v); template inline bool readkey(ConstNodeRef const& C4_RESTRICT n, T *v); template inline bool readkey(NodeRef const& C4_RESTRICT n, T *v); /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // forward decls class NodeRef; class ConstNodeRef; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ namespace detail { template struct child_iterator { using value_type = NodeRefType; using tree_type = typename NodeRefType::tree_type; tree_type * C4_RESTRICT m_tree; id_type m_child_id; child_iterator(tree_type * t, id_type id) : m_tree(t), m_child_id(id) {} child_iterator& operator++ () { RYML_ASSERT(m_child_id != NONE); m_child_id = m_tree->next_sibling(m_child_id); return *this; } child_iterator& operator-- () { RYML_ASSERT(m_child_id != NONE); m_child_id = m_tree->prev_sibling(m_child_id); return *this; } NodeRefType operator* () const { return NodeRefType(m_tree, m_child_id); } NodeRefType operator-> () const { return NodeRefType(m_tree, m_child_id); } bool operator!= (child_iterator that) const { RYML_ASSERT(m_tree == that.m_tree); return m_child_id != that.m_child_id; } bool operator== (child_iterator that) const { RYML_ASSERT(m_tree == that.m_tree); return m_child_id == that.m_child_id; } }; template struct children_view_ { using n_iterator = child_iterator; n_iterator b, e; children_view_(n_iterator const& C4_RESTRICT b_, n_iterator const& C4_RESTRICT e_) : b(b_), e(e_) {} n_iterator begin() const { return b; } n_iterator end () const { return e; } }; template bool _visit(NodeRefType &node, Visitor fn, id_type indentation_level, bool skip_root=false) { id_type increment = 0; if( ! (node.is_root() && skip_root)) { if(fn(node, indentation_level)) return true; ++increment; } if(node.has_children()) { for(auto ch : node.children()) { if(_visit(ch, fn, indentation_level + increment, false)) // no need to forward skip_root as it won't be root { return true; } } } return false; } template bool _visit_stacked(NodeRefType &node, Visitor fn, id_type indentation_level, bool skip_root=false) { id_type increment = 0; if( ! (node.is_root() && skip_root)) { if(fn(node, indentation_level)) { return true; } ++increment; } if(node.has_children()) { fn.push(node, indentation_level); for(auto ch : node.children()) { if(_visit_stacked(ch, fn, indentation_level + increment, false)) // no need to forward skip_root as it won't be root { fn.pop(node, indentation_level); return true; } } fn.pop(node, indentation_level); } return false; } template struct RoNodeMethods; } // detail /** @endcond */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** a CRTP base providing read-only methods for @ref ConstNodeRef and @ref NodeRef */ namespace detail { template struct RoNodeMethods { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align") /** @cond dev */ // helper CRTP macros, undefined at the end #define tree_ ((ConstImpl const* C4_RESTRICT)this)->m_tree #define id_ ((ConstImpl const* C4_RESTRICT)this)->m_id #define tree__ ((Impl const* C4_RESTRICT)this)->m_tree #define id__ ((Impl const* C4_RESTRICT)this)->m_id // require readable: this is a precondition for reading from the // tree using this object. #define _C4RR() \ RYML_ASSERT(tree_ != nullptr); \ _RYML_CB_ASSERT(tree_->m_callbacks, id_ != NONE); \ _RYML_CB_ASSERT(tree_->m_callbacks, (((Impl const* C4_RESTRICT)this)->readable())) // a SFINAE beautifier to enable a function only if the // implementation is mutable #define _C4_IF_MUTABLE(ty) typename std::enable_if::value, ty>::type /** @endcond */ public: /** @name node property getters */ /** @{ */ /** returns the data or null when the id is NONE */ C4_ALWAYS_INLINE NodeData const* get() const RYML_NOEXCEPT { return ((Impl const*)this)->readable() ? tree_->get(id_) : nullptr; } /** returns the data or null when the id is NONE */ template C4_ALWAYS_INLINE auto get() RYML_NOEXCEPT -> _C4_IF_MUTABLE(NodeData*) { return ((Impl const*)this)->readable() ? tree__->get(id__) : nullptr; } C4_ALWAYS_INLINE NodeType type() const RYML_NOEXCEPT { _C4RR(); return tree_->type(id_); } /**< Forward to @ref Tree::type_str(). Node must be readable. */ C4_ALWAYS_INLINE const char* type_str() const RYML_NOEXCEPT { _C4RR(); return tree_->type_str(id_); } /**< Forward to @ref Tree::type_str(). Node must be readable. */ C4_ALWAYS_INLINE csubstr key() const RYML_NOEXCEPT { _C4RR(); return tree_->key(id_); } /**< Forward to @ref Tree::key(). Node must be readable. */ C4_ALWAYS_INLINE csubstr key_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->key_tag(id_); } /**< Forward to @ref Tree::key_tag(). Node must be readable. */ C4_ALWAYS_INLINE csubstr key_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->key_ref(id_); } /**< Forward to @ref Tree::key_ref(). Node must be readable. */ C4_ALWAYS_INLINE csubstr key_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->key_anchor(id_); } /**< Forward to @ref Tree::key_anchor(). Node must be readable. */ C4_ALWAYS_INLINE csubstr val() const RYML_NOEXCEPT { _C4RR(); return tree_->val(id_); } /**< Forward to @ref Tree::val(). Node must be readable. */ C4_ALWAYS_INLINE csubstr val_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->val_tag(id_); } /**< Forward to @ref Tree::val_tag(). Node must be readable. */ C4_ALWAYS_INLINE csubstr val_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->val_ref(id_); } /**< Forward to @ref Tree::val_ref(). Node must be readable. */ C4_ALWAYS_INLINE csubstr val_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->val_anchor(id_); } /**< Forward to @ref Tree::val_anchor(). Node must be readable. */ C4_ALWAYS_INLINE NodeScalar const& keysc() const RYML_NOEXCEPT { _C4RR(); return tree_->keysc(id_); } /**< Forward to @ref Tree::keysc(). Node must be readable. */ C4_ALWAYS_INLINE NodeScalar const& valsc() const RYML_NOEXCEPT { _C4RR(); return tree_->valsc(id_); } /**< Forward to @ref Tree::valsc(). Node must be readable. */ C4_ALWAYS_INLINE bool key_is_null() const RYML_NOEXCEPT { _C4RR(); return tree_->key_is_null(id_); } /**< Forward to @ref Tree::key_is_null(). Node must be readable. */ C4_ALWAYS_INLINE bool val_is_null() const RYML_NOEXCEPT { _C4RR(); return tree_->val_is_null(id_); } /**< Forward to @ref Tree::val_is_null(). Node must be readable. */ C4_ALWAYS_INLINE bool is_key_unfiltered() const noexcept { _C4RR(); return tree_->is_key_unfiltered(id_); } /**< Forward to @ref Tree::is_key_unfiltered(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val_unfiltered() const noexcept { _C4RR(); return tree_->is_val_unfiltered(id_); } /**< Forward to @ref Tree::is_val_unfiltered(). Node must be readable. */ /** @} */ public: /** @name node type predicates */ /** @{ */ C4_ALWAYS_INLINE bool empty() const RYML_NOEXCEPT { _C4RR(); return tree_->empty(id_); } /**< Forward to @ref Tree::empty(). Node must be readable. */ C4_ALWAYS_INLINE bool is_stream() const RYML_NOEXCEPT { _C4RR(); return tree_->is_stream(id_); } /**< Forward to @ref Tree::is_stream(). Node must be readable. */ C4_ALWAYS_INLINE bool is_doc() const RYML_NOEXCEPT { _C4RR(); return tree_->is_doc(id_); } /**< Forward to @ref Tree::is_doc(). Node must be readable. */ C4_ALWAYS_INLINE bool is_container() const RYML_NOEXCEPT { _C4RR(); return tree_->is_container(id_); } /**< Forward to @ref Tree::is_container(). Node must be readable. */ C4_ALWAYS_INLINE bool is_map() const RYML_NOEXCEPT { _C4RR(); return tree_->is_map(id_); } /**< Forward to @ref Tree::is_map(). Node must be readable. */ C4_ALWAYS_INLINE bool is_seq() const RYML_NOEXCEPT { _C4RR(); return tree_->is_seq(id_); } /**< Forward to @ref Tree::is_seq(). Node must be readable. */ C4_ALWAYS_INLINE bool has_val() const RYML_NOEXCEPT { _C4RR(); return tree_->has_val(id_); } /**< Forward to @ref Tree::has_val(). Node must be readable. */ C4_ALWAYS_INLINE bool has_key() const RYML_NOEXCEPT { _C4RR(); return tree_->has_key(id_); } /**< Forward to @ref Tree::has_key(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val(id_); } /**< Forward to @ref Tree::is_val(). Node must be readable. */ C4_ALWAYS_INLINE bool is_keyval() const RYML_NOEXCEPT { _C4RR(); return tree_->is_keyval(id_); } /**< Forward to @ref Tree::is_keyval(). Node must be readable. */ C4_ALWAYS_INLINE bool has_key_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->has_key_tag(id_); } /**< Forward to @ref Tree::has_key_tag(). Node must be readable. */ C4_ALWAYS_INLINE bool has_val_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->has_val_tag(id_); } /**< Forward to @ref Tree::has_val_tag(). Node must be readable. */ C4_ALWAYS_INLINE bool has_key_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->has_key_anchor(id_); } /**< Forward to @ref Tree::has_key_anchor(). Node must be readable. */ C4_ALWAYS_INLINE bool has_val_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->has_val_anchor(id_); } /**< Forward to @ref Tree::has_val_anchor(). Node must be readable. */ C4_ALWAYS_INLINE bool has_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->has_anchor(id_); } /**< Forward to @ref Tree::has_anchor(). Node must be readable. */ C4_ALWAYS_INLINE bool is_key_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_ref(id_); } /**< Forward to @ref Tree::is_key_ref(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_ref(id_); } /**< Forward to @ref Tree::is_val_ref(). Node must be readable. */ C4_ALWAYS_INLINE bool is_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_ref(id_); } /**< Forward to @ref Tree::is_ref(). Node must be readable. */ C4_ALWAYS_INLINE bool parent_is_seq() const RYML_NOEXCEPT { _C4RR(); return tree_->parent_is_seq(id_); } /**< Forward to @ref Tree::parent_is_seq(). Node must be readable. */ C4_ALWAYS_INLINE bool parent_is_map() const RYML_NOEXCEPT { _C4RR(); return tree_->parent_is_map(id_); } /**< Forward to @ref Tree::parent_is_map(). Node must be readable. */ RYML_DEPRECATED("use has_key_anchor()") bool is_key_anchor() const noexcept { _C4RR(); return tree_->has_key_anchor(id_); } RYML_DEPRECATED("use has_val_anchor()") bool is_val_hanchor() const noexcept { _C4RR(); return tree_->has_val_anchor(id_); } RYML_DEPRECATED("use has_anchor()") bool is_anchor() const noexcept { _C4RR(); return tree_->has_anchor(id_); } RYML_DEPRECATED("use has_anchor() || is_ref()") bool is_anchor_or_ref() const noexcept { _C4RR(); return tree_->is_anchor_or_ref(id_); } /** @} */ public: /** @name node container+scalar style predicates */ /** @{ */ // documentation to the right --> C4_ALWAYS_INLINE bool type_has_any(NodeType_e bits) const RYML_NOEXCEPT { _C4RR(); return tree_->type_has_any(id_, bits); } /**< Forward to @ref Tree::type_has_any(). Node must be readable. */ C4_ALWAYS_INLINE bool type_has_all(NodeType_e bits) const RYML_NOEXCEPT { _C4RR(); return tree_->type_has_all(id_, bits); } /**< Forward to @ref Tree::type_has_all(). Node must be readable. */ C4_ALWAYS_INLINE bool type_has_none(NodeType_e bits) const RYML_NOEXCEPT { _C4RR(); return tree_->type_has_none(id_, bits); } /**< Forward to @ref Tree::type_has_none(). Node must be readable. */ C4_ALWAYS_INLINE bool is_container_styled() const RYML_NOEXCEPT { _C4RR(); return tree_->is_container_styled(id_); } /**< Forward to @ref Tree::is_container_styled(). Node must be readable. */ C4_ALWAYS_INLINE bool is_block() const RYML_NOEXCEPT { _C4RR(); return tree_->is_block(id_); } /**< Forward to @ref Tree::is_block(). Node must be readable. */ C4_ALWAYS_INLINE bool is_flow_sl() const RYML_NOEXCEPT { _C4RR(); return tree_->is_flow_sl(id_); } /**< Forward to @ref Tree::is_flow_sl(). Node must be readable. */ C4_ALWAYS_INLINE bool is_flow_ml() const RYML_NOEXCEPT { _C4RR(); return tree_->is_flow_ml(id_); } /**< Forward to @ref Tree::is_flow_ml(). Node must be readable. */ C4_ALWAYS_INLINE bool is_flow() const RYML_NOEXCEPT { _C4RR(); return tree_->is_flow(id_); } /**< Forward to @ref Tree::is_flow(). Node must be readable. */ C4_ALWAYS_INLINE bool is_key_styled() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_styled(id_); } /**< Forward to @ref Tree::is_key_styled(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val_styled() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_styled(id_); } /**< Forward to @ref Tree::is_val_styled(). Node must be readable. */ C4_ALWAYS_INLINE bool is_key_literal() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_literal(id_); } /**< Forward to @ref Tree::is_key_literal(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val_literal() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_literal(id_); } /**< Forward to @ref Tree::is_val_literal(). Node must be readable. */ C4_ALWAYS_INLINE bool is_key_folded() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_folded(id_); } /**< Forward to @ref Tree::is_key_folded(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val_folded() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_folded(id_); } /**< Forward to @ref Tree::is_val_folded(). Node must be readable. */ C4_ALWAYS_INLINE bool is_key_squo() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_squo(id_); } /**< Forward to @ref Tree::is_key_squo(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val_squo() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_squo(id_); } /**< Forward to @ref Tree::is_val_squo(). Node must be readable. */ C4_ALWAYS_INLINE bool is_key_dquo() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_dquo(id_); } /**< Forward to @ref Tree::is_key_dquo(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val_dquo() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_dquo(id_); } /**< Forward to @ref Tree::is_val_dquo(). Node must be readable. */ C4_ALWAYS_INLINE bool is_key_plain() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_plain(id_); } /**< Forward to @ref Tree::is_key_plain(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val_plain() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_plain(id_); } /**< Forward to @ref Tree::is_val_plain(). Node must be readable. */ C4_ALWAYS_INLINE bool is_key_quoted() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_quoted(id_); } /**< Forward to @ref Tree::is_key_quoted(). Node must be readable. */ C4_ALWAYS_INLINE bool is_val_quoted() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_quoted(id_); } /**< Forward to @ref Tree::is_val_quoted(). Node must be readable. */ C4_ALWAYS_INLINE bool is_quoted() const RYML_NOEXCEPT { _C4RR(); return tree_->is_quoted(id_); } /**< Forward to @ref Tree::is_quoted(). Node must be readable. */ /** @} */ public: /** @name hierarchy predicates */ /** @{ */ // documentation to the right --> C4_ALWAYS_INLINE bool is_root() const RYML_NOEXCEPT { _C4RR(); return tree_->is_root(id_); } /**< Forward to @ref Tree::is_root(). Node must be readable. */ C4_ALWAYS_INLINE bool has_parent() const RYML_NOEXCEPT { _C4RR(); return tree_->has_parent(id_); } /**< Forward to @ref Tree::has_parent() Node must be readable. */ C4_ALWAYS_INLINE bool is_ancestor(ConstImpl const& ancestor) const RYML_NOEXCEPT { _C4RR(); return tree_->is_ancestor(id_, ancestor.m_id); } /**< Forward to @ref Tree::is_ancestor() Node must be readable. */ C4_ALWAYS_INLINE bool has_child(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); return n.readable() ? tree_->has_child(id_, n.m_id) : false; } /**< Forward to @ref Tree::has_child(). Node must be readable. */ C4_ALWAYS_INLINE bool has_child(id_type node) const RYML_NOEXCEPT { _C4RR(); return tree_->has_child(id_, node); } /**< Forward to @ref Tree::has_child(). Node must be readable. */ C4_ALWAYS_INLINE bool has_child(csubstr name) const RYML_NOEXCEPT { _C4RR(); return tree_->has_child(id_, name); } /**< Forward to @ref Tree::has_child(). Node must be readable. */ C4_ALWAYS_INLINE bool has_children() const RYML_NOEXCEPT { _C4RR(); return tree_->has_children(id_); } /**< Forward to @ref Tree::has_child(). Node must be readable. */ C4_ALWAYS_INLINE bool has_sibling(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); return n.readable() ? tree_->has_sibling(id_, n.m_id) : false; } /**< Forward to @ref Tree::has_sibling(). Node must be readable. */ C4_ALWAYS_INLINE bool has_sibling(id_type node) const RYML_NOEXCEPT { _C4RR(); return tree_->has_sibling(id_, node); } /**< Forward to @ref Tree::has_sibling(). Node must be readable. */ C4_ALWAYS_INLINE bool has_sibling(csubstr name) const RYML_NOEXCEPT { _C4RR(); return tree_->has_sibling(id_, name); } /**< Forward to @ref Tree::has_sibling(). Node must be readable. */ C4_ALWAYS_INLINE bool has_other_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->has_other_siblings(id_); } /**< Forward to @ref Tree::has_sibling(). Node must be readable. */ RYML_DEPRECATED("use has_other_siblings()") bool has_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->has_siblings(id_); } /** @} */ public: /** @name hierarchy getters */ /** @{ */ // documentation to the right --> template C4_ALWAYS_INLINE auto doc(id_type i) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { RYML_ASSERT(tree_); return {tree__, tree__->doc(i)}; } /**< Forward to @ref Tree::doc(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl doc(id_type i) const RYML_NOEXCEPT { RYML_ASSERT(tree_); return {tree_, tree_->doc(i)}; } /**< Forward to @ref Tree::doc(). Node must be readable. succeeds even when the node may have invalid or seed id */ template C4_ALWAYS_INLINE auto parent() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->parent(id__)}; } /**< Forward to @ref Tree::parent(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl parent() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->parent(id_)}; } /**< Forward to @ref Tree::parent(). Node must be readable. */ template C4_ALWAYS_INLINE auto first_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->first_child(id__)}; } /**< Forward to @ref Tree::first_child(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl first_child() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->first_child(id_)}; } /**< Forward to @ref Tree::first_child(). Node must be readable. */ template C4_ALWAYS_INLINE auto last_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->last_child(id__)}; } /**< Forward to @ref Tree::last_child(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl last_child () const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->last_child (id_)}; } /**< Forward to @ref Tree::last_child(). Node must be readable. */ template C4_ALWAYS_INLINE auto child(id_type pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->child(id__, pos)}; } /**< Forward to @ref Tree::child(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl child(id_type pos) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->child(id_, pos)}; } /**< Forward to @ref Tree::child(). Node must be readable. */ template C4_ALWAYS_INLINE auto find_child(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->find_child(id__, name)}; } /**< Forward to @ref Tree::first_child(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl find_child(csubstr name) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->find_child(id_, name)}; } /**< Forward to @ref Tree::first_child(). Node must be readable. */ template C4_ALWAYS_INLINE auto prev_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->prev_sibling(id__)}; } /**< Forward to @ref Tree::prev_sibling(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl prev_sibling() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->prev_sibling(id_)}; } /**< Forward to @ref Tree::prev_sibling(). Node must be readable. */ template C4_ALWAYS_INLINE auto next_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->next_sibling(id__)}; } /**< Forward to @ref Tree::next_sibling(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl next_sibling() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->next_sibling(id_)}; } /**< Forward to @ref Tree::next_sibling(). Node must be readable. */ template C4_ALWAYS_INLINE auto first_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->first_sibling(id__)}; } /**< Forward to @ref Tree::first_sibling(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl first_sibling() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->first_sibling(id_)}; } /**< Forward to @ref Tree::first_sibling(). Node must be readable. */ template C4_ALWAYS_INLINE auto last_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->last_sibling(id__)}; } /**< Forward to @ref Tree::last_sibling(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl last_sibling () const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->last_sibling(id_)}; } /**< Forward to @ref Tree::last_sibling(). Node must be readable. */ template C4_ALWAYS_INLINE auto sibling(id_type pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->sibling(id__, pos)}; } /**< Forward to @ref Tree::sibling(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl sibling(id_type pos) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->sibling(id_, pos)}; } /**< Forward to @ref Tree::sibling(). Node must be readable. */ template C4_ALWAYS_INLINE auto find_sibling(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->find_sibling(id__, name)}; } /**< Forward to @ref Tree::find_sibling(). Node must be readable. */ C4_ALWAYS_INLINE ConstImpl find_sibling(csubstr name) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->find_sibling(id_, name)}; } /**< Forward to @ref Tree::find_sibling(). Node must be readable. */ C4_ALWAYS_INLINE id_type num_children() const RYML_NOEXCEPT { _C4RR(); return tree_->num_children(id_); } /**< O(num_children). Forward to @ref Tree::num_children(). */ C4_ALWAYS_INLINE id_type num_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->num_siblings(id_); } /**< O(num_children). Forward to @ref Tree::num_siblings(). */ C4_ALWAYS_INLINE id_type num_other_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->num_other_siblings(id_); } /**< O(num_siblings). Forward to @ref Tree::num_other_siblings(). */ C4_ALWAYS_INLINE id_type child_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); _RYML_CB_ASSERT(tree_->m_callbacks, n.readable()); return tree_->child_pos(id_, n.m_id); } /**< O(num_children). Forward to @ref Tree::child_pos(). */ C4_ALWAYS_INLINE id_type sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); _RYML_CB_ASSERT(tree_->callbacks(), n.readable()); return tree_->child_pos(tree_->parent(id_), n.m_id); } /**< O(num_siblings). Forward to @ref Tree::sibling_pos(). */ C4_ALWAYS_INLINE id_type depth_asc() const RYML_NOEXCEPT { _C4RR(); return tree_->depth_asc(id_); } /** O(log(num_nodes)). Forward to Tree::depth_asc(). Node must be readable. */ C4_ALWAYS_INLINE id_type depth_desc() const RYML_NOEXCEPT { _C4RR(); return tree_->depth_desc(id_); } /** O(num_nodes). Forward to Tree::depth_desc(). Node must be readable. */ /** @} */ public: /** @name square_brackets * operator[] */ /** @{ */ /** Find child by key; complexity is O(num_children). * * Returns the requested node, or an object in seed state if no * such child is found (see @ref NodeRef for an explanation of * what is seed state). When the object is in seed state, using it * to read from the tree is UB. The seed node can be used to write * to the tree provided that its create() method is called prior * to writing, which happens in most modifying methods in * NodeRef. It is the caller's responsibility to verify that the * returned node is readable before subsequently using it to read * from the tree. * * @warning the calling object must be readable. This precondition * is asserted. The assertion is performed only if @ref * RYML_USE_ASSERT is set to true. As with the non-const overload, * it is UB to call this method if the node is not readable. * * @see https://github.com/biojppm/rapidyaml/issues/389 */ template C4_ALWAYS_INLINE auto operator[] (csubstr key) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); id_type ch = tree__->find_child(id__, key); return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, key); } /** Find child by position; complexity is O(pos). * * Returns the requested node, or an object in seed state if no * such child is found (see @ref NodeRef for an explanation of * what is seed state). When the object is in seed state, using it * to read from the tree is UB. The seed node can be used to write * to the tree provided that its create() method is called prior * to writing, which happens in most modifying methods in * NodeRef. It is the caller's responsibility to verify that the * returned node is readable before subsequently using it to read * from the tree. * * @warning the calling object must be readable. This precondition * is asserted. The assertion is performed only if @ref * RYML_USE_ASSERT is set to true. As with the non-const overload, * it is UB to call this method if the node is not readable. * * @see https://github.com/biojppm/rapidyaml/issues/389 */ template C4_ALWAYS_INLINE auto operator[] (id_type pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); id_type ch = tree__->child(id__, pos); return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, pos); } /** Find a child by key; complexity is O(num_children). * * Behaves similar to the non-const overload, but further asserts * that the returned node is readable (because it can never be in * a seed state). The assertion is performed only if @ref * RYML_USE_ASSERT is set to true. As with the non-const overload, * it is UB to use the return value if it is not valid. * * @see https://github.com/biojppm/rapidyaml/issues/389 */ C4_ALWAYS_INLINE ConstImpl operator[] (csubstr key) const RYML_NOEXCEPT { _C4RR(); id_type ch = tree_->find_child(id_, key); _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); return {tree_, ch}; } /** Find a child by position; complexity is O(pos). * * Behaves similar to the non-const overload, but further asserts * that the returned node is readable (because it can never be in * a seed state). This assertion is performed only if @ref * RYML_USE_ASSERT is set to true. As with the non-const overload, * it is UB to use the return value if it is not valid. * * @see https://github.com/biojppm/rapidyaml/issues/389 */ C4_ALWAYS_INLINE ConstImpl operator[] (id_type pos) const RYML_NOEXCEPT { _C4RR(); id_type ch = tree_->child(id_, pos); _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); return {tree_, ch}; } /** @} */ public: /** @name at * * These functions are the analogue to operator[], with the * difference that they emit an error instead of an * assertion. That is, if any of the pre or post conditions is * violated, an error is always emitted (resulting in a call to * the error callback). * * @{ */ /** Find child by key; complexity is O(num_children). * * Returns the requested node, or an object in seed state if no * such child is found (see @ref NodeRef for an explanation of * what is seed state). When the object is in seed state, using it * to read from the tree is UB. The seed node can be subsequently * used to write to the tree provided that its create() method is * called prior to writing, which happens inside most mutating * methods in NodeRef. It is the caller's responsibility to verify * that the returned node is readable before subsequently using it * to read from the tree. * * @warning This method will call the error callback (regardless * of build type or of the value of RYML_USE_ASSERT) whenever any * of the following preconditions is violated: a) the object is * valid (points at a tree and a node), b) the calling object must * be readable (must not be in seed state), c) the calling object * must be pointing at a MAP node. The preconditions are similar * to the non-const operator[](csubstr), but instead of using * assertions, this function directly checks those conditions and * calls the error callback if any of the checks fail. * * @note since it is valid behavior for the returned node to be in * seed state, the error callback is not invoked when this * happens. */ template C4_ALWAYS_INLINE auto at(csubstr key) -> _C4_IF_MUTABLE(Impl) { RYML_CHECK(tree_ != nullptr); _RYML_CB_CHECK(tree_->m_callbacks, (id_ >= 0 && id_ < tree_->capacity())); _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); _RYML_CB_CHECK(tree_->m_callbacks, tree_->is_map(id_)); id_type ch = tree__->find_child(id__, key); return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, key); } /** Find child by position; complexity is O(pos). * * Returns the requested node, or an object in seed state if no * such child is found (see @ref NodeRef for an explanation of * what is seed state). When the object is in seed state, using it * to read from the tree is UB. The seed node can be used to write * to the tree provided that its create() method is called prior * to writing, which happens in most modifying methods in * NodeRef. It is the caller's responsibility to verify that the * returned node is readable before subsequently using it to read * from the tree. * * @warning This method will call the error callback (regardless * of build type or of the value of RYML_USE_ASSERT) whenever any * of the following preconditions is violated: a) the object is * valid (points at a tree and a node), b) the calling object must * be readable (must not be in seed state), c) the calling object * must be pointing at a MAP node. The preconditions are similar * to the non-const operator[](id_type), but instead of using * assertions, this function directly checks those conditions and * calls the error callback if any of the checks fail. * * @note since it is valid behavior for the returned node to be in * seed state, the error callback is not invoked when this * happens. */ template C4_ALWAYS_INLINE auto at(id_type pos) -> _C4_IF_MUTABLE(Impl) { RYML_CHECK(tree_ != nullptr); const id_type cap = tree_->capacity(); _RYML_CB_CHECK(tree_->m_callbacks, (id_ >= 0 && id_ < cap)); _RYML_CB_CHECK(tree_->m_callbacks, (pos >= 0 && pos < cap)); _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); _RYML_CB_CHECK(tree_->m_callbacks, tree_->is_container(id_)); id_type ch = tree__->child(id__, pos); return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, pos); } /** Get a child by name, with error checking; complexity is * O(num_children). * * Behaves as operator[](csubstr) const, but always raises an * error (even when RYML_USE_ASSERT is set to false) when the * returned node does not exist, or when this node is not * readable, or when it is not a map. This behaviour is similar to * std::vector::at(), but the error consists in calling the error * callback instead of directly raising an exception. */ ConstImpl at(csubstr key) const { RYML_CHECK(tree_ != nullptr); _RYML_CB_CHECK(tree_->m_callbacks, (id_ >= 0 && id_ < tree_->capacity())); _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); _RYML_CB_CHECK(tree_->m_callbacks, tree_->is_map(id_)); id_type ch = tree_->find_child(id_, key); _RYML_CB_CHECK(tree_->m_callbacks, ch != NONE); return {tree_, ch}; } /** Get a child by position, with error checking; complexity is * O(pos). * * Behaves as operator[](id_type) const, but always raises an error * (even when RYML_USE_ASSERT is set to false) when the returned * node does not exist, or when this node is not readable, or when * it is not a container. This behaviour is similar to * std::vector::at(), but the error consists in calling the error * callback instead of directly raising an exception. */ ConstImpl at(id_type pos) const { RYML_CHECK(tree_ != nullptr); const id_type cap = tree_->capacity(); _RYML_CB_CHECK(tree_->m_callbacks, (id_ >= 0 && id_ < cap)); _RYML_CB_CHECK(tree_->m_callbacks, (pos >= 0 && pos < cap)); _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); _RYML_CB_CHECK(tree_->m_callbacks, tree_->is_container(id_)); const id_type ch = tree_->child(id_, pos); _RYML_CB_CHECK(tree_->m_callbacks, ch != NONE); return {tree_, ch}; } /** @} */ public: /** @name deserialization */ /** @{ */ /** deserialize the node's val to the given variable, forwarding * to the user-overrideable @ref read() function. */ template ConstImpl const& operator>> (T &v) const { _C4RR(); if( ! read((ConstImpl const&)*this, &v)) _RYML_CB_ERR(tree_->m_callbacks, "could not deserialize value"); return *((ConstImpl const*)this); } /** deserialize the node's key to the given variable, forwarding * to the user-overrideable @ref read() function; use @ref key() * to disambiguate; for example: `node >> ryml::key(var)` */ template ConstImpl const& operator>> (Key v) const { _C4RR(); if( ! readkey((ConstImpl const&)*this, &v.k)) _RYML_CB_ERR(tree_->m_callbacks, "could not deserialize key"); return *((ConstImpl const*)this); } /** look for a child by name, if it exists assign to var. return * true if the child existed. */ template bool get_if(csubstr name, T *var) const { _C4RR(); ConstImpl ch = find_child(name); if(!ch.readable()) return false; ch >> *var; return true; } /** look for a child by name, if it exists assign to var, * otherwise default to fallback. return true if the child * existed. */ template bool get_if(csubstr name, T *var, T const& fallback) const { _C4RR(); ConstImpl ch = find_child(name); if(ch.readable()) { ch >> *var; return true; } else { *var = fallback; return false; } } /** @name deserialization_base64 */ /** @{ */ /** deserialize the node's key as base64. lightweight wrapper over @ref deserialize_key() */ ConstImpl const& operator>> (Key w) const { deserialize_key(w.wrapper); return *((ConstImpl const*)this); } /** deserialize the node's val as base64. lightweight wrapper over @ref deserialize_val() */ ConstImpl const& operator>> (fmt::base64_wrapper w) const { deserialize_val(w); return *((ConstImpl const*)this); } /** decode the base64-encoded key and assign the * decoded blob to the given buffer/ * @return the size of base64-decoded blob */ size_t deserialize_key(fmt::base64_wrapper v) const { _C4RR(); return from_chars(key(), &v); } /** decode the base64-encoded key and assign the * decoded blob to the given buffer/ * @return the size of base64-decoded blob */ size_t deserialize_val(fmt::base64_wrapper v) const { _C4RR(); return from_chars(val(), &v); }; /** @} */ /** @} */ public: #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wnull-dereference" #elif defined(__GNUC__) # pragma GCC diagnostic push # if __GNUC__ >= 6 # pragma GCC diagnostic ignored "-Wnull-dereference" # endif #endif /** @name iteration */ /** @{ */ using iterator = detail::child_iterator; using const_iterator = detail::child_iterator; using children_view = detail::children_view_; using const_children_view = detail::children_view_; /** get an iterator to the first child */ template C4_ALWAYS_INLINE auto begin() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RR(); return iterator(tree__, tree__->first_child(id__)); } /** get an iterator to the first child */ C4_ALWAYS_INLINE const_iterator begin() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, tree_->first_child(id_)); } /** get an iterator to the first child */ C4_ALWAYS_INLINE const_iterator cbegin() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, tree_->first_child(id_)); } /** get an iterator to after the last child */ template C4_ALWAYS_INLINE auto end() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RR(); return iterator(tree__, NONE); } /** get an iterator to after the last child */ C4_ALWAYS_INLINE const_iterator end() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, NONE); } /** get an iterator to after the last child */ C4_ALWAYS_INLINE const_iterator cend() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, tree_->first_child(id_)); } /** get an iterable view over children */ template C4_ALWAYS_INLINE auto children() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RR(); return children_view(begin(), end()); } /** get an iterable view over children */ C4_ALWAYS_INLINE const_children_view children() const RYML_NOEXCEPT { _C4RR(); return const_children_view(begin(), end()); } /** get an iterable view over children */ C4_ALWAYS_INLINE const_children_view cchildren() const RYML_NOEXCEPT { _C4RR(); return const_children_view(begin(), end()); } /** get an iterable view over all siblings (including the calling node) */ template C4_ALWAYS_INLINE auto siblings() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RR(); NodeData const *nd = tree__->get(id__); return (nd->m_parent != NONE) ? // does it have a parent? children_view(iterator(tree__, tree_->get(nd->m_parent)->m_first_child), iterator(tree__, NONE)) : children_view(end(), end()); } /** get an iterable view over all siblings (including the calling node) */ C4_ALWAYS_INLINE const_children_view siblings() const RYML_NOEXCEPT { _C4RR(); NodeData const *nd = tree_->get(id_); return (nd->m_parent != NONE) ? // does it have a parent? const_children_view(const_iterator(tree_, tree_->get(nd->m_parent)->m_first_child), const_iterator(tree_, NONE)) : const_children_view(end(), end()); } /** get an iterable view over all siblings (including the calling node) */ C4_ALWAYS_INLINE const_children_view csiblings() const RYML_NOEXCEPT { return siblings(); } /** visit every child node calling fn(node) */ template bool visit(Visitor fn, id_type indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT { _C4RR(); return detail::_visit(*(ConstImpl const*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node) */ template auto visit(Visitor fn, id_type indentation_level=0, bool skip_root=true) RYML_NOEXCEPT -> _C4_IF_MUTABLE(bool) { _C4RR(); return detail::_visit(*(Impl*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node, level) */ template bool visit_stacked(Visitor fn, id_type indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT { _C4RR(); return detail::_visit_stacked(*(ConstImpl const*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node, level) */ template auto visit_stacked(Visitor fn, id_type indentation_level=0, bool skip_root=true) RYML_NOEXCEPT -> _C4_IF_MUTABLE(bool) { _C4RR(); return detail::_visit_stacked(*(Impl*)this, fn, indentation_level, skip_root); } /** @} */ #if defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #undef _C4_IF_MUTABLE #undef _C4RR #undef tree_ #undef tree__ #undef id_ #undef id__ C4_SUPPRESS_WARNING_GCC_CLANG_POP }; } // detail //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** Holds a pointer to an existing tree, and a node id. It can be used * only to read from the tree. * * @warning The lifetime of the tree must be larger than that of this * object. It is up to the user to ensure that this happens. */ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods // NOLINT { public: using tree_type = Tree const; public: Tree const* C4_RESTRICT m_tree; id_type m_id; friend NodeRef; friend struct detail::RoNodeMethods; public: /** @name construction */ /** @{ */ ConstNodeRef() noexcept : m_tree(nullptr), m_id(NONE) {} ConstNodeRef(Tree const &t) noexcept : m_tree(&t), m_id(t .root_id()) {} ConstNodeRef(Tree const *t) noexcept : m_tree(t ), m_id(t->root_id()) {} ConstNodeRef(Tree const *t, id_type id) noexcept : m_tree(t), m_id(id) {} ConstNodeRef(std::nullptr_t) noexcept : m_tree(nullptr), m_id(NONE) {} ConstNodeRef(ConstNodeRef const&) noexcept = default; ConstNodeRef(ConstNodeRef &&) noexcept = default; inline ConstNodeRef(NodeRef const&) noexcept; inline ConstNodeRef(NodeRef &&) noexcept; /** @} */ public: /** @name assignment */ /** @{ */ ConstNodeRef& operator= (std::nullptr_t) noexcept { m_tree = nullptr; m_id = NONE; return *this; } ConstNodeRef& operator= (ConstNodeRef const&) noexcept = default; ConstNodeRef& operator= (ConstNodeRef &&) noexcept = default; ConstNodeRef& operator= (NodeRef const&) noexcept; ConstNodeRef& operator= (NodeRef &&) noexcept; /** @} */ public: /** @name state queries * * see @ref NodeRef for an explanation on what these states mean */ /** @{ */ C4_ALWAYS_INLINE bool invalid() const noexcept { return (!m_tree) || (m_id == NONE); } /** because a ConstNodeRef cannot be used to write to the tree, * readable() has the same meaning as !invalid() */ C4_ALWAYS_INLINE bool readable() const noexcept { return m_tree != nullptr && m_id != NONE; } /** because a ConstNodeRef cannot be used to write to the tree, it can never be a seed. * This method is provided for API equivalence between ConstNodeRef and NodeRef. */ constexpr static C4_ALWAYS_INLINE bool is_seed() noexcept { return false; } RYML_DEPRECATED("use one of readable(), is_seed() or !invalid()") bool valid() const noexcept { return m_tree != nullptr && m_id != NONE; } /** @} */ public: /** @name member getters */ /** @{ */ C4_ALWAYS_INLINE Tree const* tree() const noexcept { return m_tree; } C4_ALWAYS_INLINE id_type id() const noexcept { return m_id; } /** @} */ public: /** @name comparisons */ /** @{ */ C4_ALWAYS_INLINE bool operator== (ConstNodeRef const& that) const RYML_NOEXCEPT { return that.m_tree == m_tree && m_id == that.m_id; } C4_ALWAYS_INLINE bool operator!= (ConstNodeRef const& that) const RYML_NOEXCEPT { return ! this->operator== (that); } /** @cond dev */ RYML_DEPRECATED("use invalid()") bool operator== (std::nullptr_t) const noexcept { return m_tree == nullptr || m_id == NONE; } RYML_DEPRECATED("use !invalid()") bool operator!= (std::nullptr_t) const noexcept { return !(m_tree == nullptr || m_id == NONE); } RYML_DEPRECATED("use (this->val() == s)") bool operator== (csubstr s) const RYML_NOEXCEPT { RYML_ASSERT(m_tree); _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE); return m_tree->val(m_id) == s; } RYML_DEPRECATED("use (this->val() != s)") bool operator!= (csubstr s) const RYML_NOEXCEPT { RYML_ASSERT(m_tree); _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE); return m_tree->val(m_id) != s; } /** @endcond */ /** @} */ }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // NOLINTBEGIN(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) /** A reference to a node in an existing yaml tree, offering a more * convenient API than the index-based API used in the tree. * * Unlike its imutable ConstNodeRef peer, a NodeRef can be used to * mutate the tree, both by writing to existing nodes and by creating * new nodes to subsequently write to. Semantically, a NodeRef * object can be in one of three states: * * ```text * invalid := not pointing at anything * readable := points at an existing tree/node * seed := points at an existing tree, and the node * may come to exist, if we write to it. * ``` * * So both `readable` and `seed` are states where the node is also `valid`. * * ```cpp * Tree t = parse_in_arena("{a: b}"); * NodeRef invalid; // not pointing at anything. * NodeRef readable = t["a"]; // also valid, because "a" exists * NodeRef seed = t["none"]; // also valid, but is seed because "none" is not in the map * ``` * * When the object is in seed state, using it to read from the tree is * UB. The seed node can be used to write to the tree, provided that * its create() method is called prior to writing, which happens in * most modifying methods in NodeRef. * * It is the owners's responsibility to verify that an existing * node is readable before subsequently using it to read from the * tree. * * @warning The lifetime of the tree must be larger than that of this * object. It is up to the user to ensure that this happens. */ class RYML_EXPORT NodeRef : public detail::RoNodeMethods // NOLINT { public: using tree_type = Tree; using base_type = detail::RoNodeMethods; private: Tree *C4_RESTRICT m_tree; id_type m_id; /** This member is used to enable lazy operator[] writing. When a child * with a key or index is not found, m_id is set to the id of the parent * and the asked-for key or index are stored in this member until a write * does happen. Then it is given as key or index for creating the child. * When a key is used, the csubstr stores it (so the csubstr's string is * non-null and the csubstr's size is different from NONE). When an index is * used instead, the csubstr's string is set to null, and only the csubstr's * size is set to a value different from NONE. Otherwise, when operator[] * does find the child then this member is empty: the string is null and * the size is NONE. */ csubstr m_seed; friend ConstNodeRef; friend struct detail::RoNodeMethods; // require valid: a helper macro, undefined at the end #define _C4RR() \ RYML_ASSERT(m_tree != nullptr); \ _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE && !is_seed()) // require id: a helper macro, undefined at the end #define _C4RID() \ RYML_ASSERT(m_tree != nullptr); \ _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE) public: /** @name construction */ /** @{ */ NodeRef() noexcept : m_tree(nullptr), m_id(NONE), m_seed() { _clear_seed(); } NodeRef(Tree &t) noexcept : m_tree(&t), m_id(t .root_id()), m_seed() { _clear_seed(); } NodeRef(Tree *t) noexcept : m_tree(t ), m_id(t->root_id()), m_seed() { _clear_seed(); } NodeRef(Tree *t, id_type id) noexcept : m_tree(t), m_id(id), m_seed() { _clear_seed(); } NodeRef(Tree *t, id_type id, id_type seed_pos) noexcept : m_tree(t), m_id(id), m_seed() { m_seed.str = nullptr; m_seed.len = (size_t)seed_pos; } NodeRef(Tree *t, id_type id, csubstr seed_key) noexcept : m_tree(t), m_id(id), m_seed(seed_key) {} NodeRef(std::nullptr_t) noexcept : m_tree(nullptr), m_id(NONE), m_seed() {} void _clear_seed() noexcept { /*do the following manually or an assert is triggered: */ m_seed.str = nullptr; m_seed.len = npos; } /** @} */ public: /** @name assignment */ /** @{ */ NodeRef(NodeRef const&) noexcept = default; NodeRef(NodeRef &&) noexcept = default; NodeRef& operator= (NodeRef const&) noexcept = default; NodeRef& operator= (NodeRef &&) noexcept = default; /** @} */ public: /** @name state_queries * @{ */ /** true if the object is not referring to any existing or seed node. @see the doc for @ref NodeRef */ bool invalid() const noexcept { return m_tree == nullptr || m_id == NONE; } /** true if the object is not invalid and in seed state. @see the doc for @ref NodeRef */ bool is_seed() const noexcept { return (m_tree != nullptr && m_id != NONE) && (m_seed.str != nullptr || m_seed.len != (size_t)NONE); } /** true if the object is not invalid and not in seed state. @see the doc for @ref NodeRef */ bool readable() const noexcept { return (m_tree != nullptr && m_id != NONE) && (m_seed.str == nullptr && m_seed.len == (size_t)NONE); } RYML_DEPRECATED("use one of readable(), is_seed() or !invalid()") inline bool valid() const { return m_tree != nullptr && m_id != NONE; } /** @} */ public: /** @name comparisons */ /** @{ */ bool operator== (NodeRef const& that) const { if(m_tree == that.m_tree && m_id == that.m_id) { bool seed = is_seed(); if(seed == that.is_seed()) { if(seed) { return (m_seed.len == that.m_seed.len) && (m_seed.str == that.m_seed.str || m_seed == that.m_seed); // do strcmp only in the last resort } return true; } } return false; } bool operator!= (NodeRef const& that) const { return ! this->operator==(that); } bool operator== (ConstNodeRef const& that) const { return m_tree == that.m_tree && m_id == that.m_id && !is_seed(); } bool operator!= (ConstNodeRef const& that) const { return ! this->operator==(that); } /** @cond dev */ RYML_DEPRECATED("use !readable()") bool operator== (std::nullptr_t) const { return m_tree == nullptr || m_id == NONE || is_seed(); } RYML_DEPRECATED("use readable()") bool operator!= (std::nullptr_t) const { return !(m_tree == nullptr || m_id == NONE || is_seed()); } RYML_DEPRECATED("use `this->val() == s`") bool operator== (csubstr s) const { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, has_val()); return m_tree->val(m_id) == s; } RYML_DEPRECATED("use `this->val() != s`") bool operator!= (csubstr s) const { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, has_val()); return m_tree->val(m_id) != s; } /** @endcond */ public: /** @name node_property_getters * @{ */ C4_ALWAYS_INLINE Tree * tree() noexcept { return m_tree; } C4_ALWAYS_INLINE Tree const* tree() const noexcept { return m_tree; } C4_ALWAYS_INLINE id_type id() const noexcept { return m_id; } /** @} */ public: /** @name node_modifiers */ /** @{ */ void create() { _apply_seed(); } void change_type(NodeType t) { _C4RR(); m_tree->change_type(m_id, t); } void set_type(NodeType t) { _apply_seed(); m_tree->_set_flags(m_id, t); } void set_key(csubstr key) { _apply_seed(); m_tree->_set_key(m_id, key); } void set_val(csubstr val) { _apply_seed(); m_tree->_set_val(m_id, val); } void set_key_tag(csubstr key_tag) { _apply_seed(); m_tree->set_key_tag(m_id, key_tag); } void set_val_tag(csubstr val_tag) { _apply_seed(); m_tree->set_val_tag(m_id, val_tag); } void set_key_anchor(csubstr key_anchor) { _apply_seed(); m_tree->set_key_anchor(m_id, key_anchor); } void set_val_anchor(csubstr val_anchor) { _apply_seed(); m_tree->set_val_anchor(m_id, val_anchor); } void set_key_ref(csubstr key_ref) { _apply_seed(); m_tree->set_key_ref(m_id, key_ref); } void set_val_ref(csubstr val_ref) { _apply_seed(); m_tree->set_val_ref(m_id, val_ref); } void set_container_style(NodeType_e style) { _C4RR(); m_tree->set_container_style(m_id, style); } void set_key_style(NodeType_e style) { _C4RR(); m_tree->set_key_style(m_id, style); } void set_val_style(NodeType_e style) { _C4RR(); m_tree->set_val_style(m_id, style); } public: void clear() { if(is_seed()) return; m_tree->remove_children(m_id); m_tree->_clear(m_id); } void clear_key() { if(is_seed()) return; m_tree->_clear_key(m_id); } void clear_val() { if(is_seed()) return; m_tree->_clear_val(m_id); } void clear_children() { if(is_seed()) return; m_tree->remove_children(m_id); } void operator= (NodeType_e t) { _apply_seed(); m_tree->_add_flags(m_id, t); } void operator|= (NodeType_e t) { _apply_seed(); m_tree->_add_flags(m_id, t); } void operator= (NodeInit const& v) { _apply_seed(); _apply(v); } void operator= (NodeScalar const& v) { _apply_seed(); _apply(v); } void operator= (std::nullptr_t) { _apply_seed(); _apply(csubstr{}); } void operator= (csubstr v) { _apply_seed(); _apply(v); } template void operator= (const char (&v)[N]) { _apply_seed(); csubstr sv; sv.assign(v); _apply(sv); } /** @} */ public: /** @name serialization */ /** @{ */ /** serialize a variable to the arena */ template csubstr to_arena(T const& C4_RESTRICT s) { RYML_ASSERT(m_tree); // no need for valid or readable return m_tree->to_arena(s); } template size_t set_key_serialized(T const& C4_RESTRICT k) { _apply_seed(); csubstr s = m_tree->to_arena(k); m_tree->_set_key(m_id, s); return s.len; } size_t set_key_serialized(std::nullptr_t) { _apply_seed(); m_tree->_set_key(m_id, csubstr{}); return 0; } template size_t set_val_serialized(T const& C4_RESTRICT v) { _apply_seed(); csubstr s = m_tree->to_arena(v); m_tree->_set_val(m_id, s); return s.len; } size_t set_val_serialized(std::nullptr_t) { _apply_seed(); m_tree->_set_val(m_id, csubstr{}); return 0; } /** encode a blob as base64 into the tree's arena, then assign the * result to the node's key * @return the size of base64-encoded blob */ size_t set_key_serialized(fmt::const_base64_wrapper w); /** encode a blob as base64 into the tree's arena, then assign the * result to the node's val * @return the size of base64-encoded blob */ size_t set_val_serialized(fmt::const_base64_wrapper w); /** serialize a variable, then assign the result to the node's val */ NodeRef& operator<< (csubstr s) { // this overload is needed to prevent ambiguity (there's also // operator<< for writing a substr to a stream) _apply_seed(); write(this, s); _RYML_CB_ASSERT(m_tree->m_callbacks, val() == s); return *this; } template NodeRef& operator<< (T const& C4_RESTRICT v) { _apply_seed(); write(this, v); return *this; } /** serialize a variable, then assign the result to the node's key */ template NodeRef& operator<< (Key const& C4_RESTRICT v) { _apply_seed(); set_key_serialized(v.k); return *this; } /** serialize a variable, then assign the result to the node's key */ template NodeRef& operator<< (Key const& C4_RESTRICT v) { _apply_seed(); set_key_serialized(v.k); return *this; } NodeRef& operator<< (Key w) { set_key_serialized(w.wrapper); return *this; } NodeRef& operator<< (fmt::const_base64_wrapper w) { set_val_serialized(w); return *this; } /** @} */ private: void _apply_seed() { _C4RID(); if(m_seed.str) // we have a seed key: use it to create the new child { m_id = m_tree->append_child(m_id); m_tree->_set_key(m_id, m_seed); m_seed.str = nullptr; m_seed.len = (size_t)NONE; } else if(m_seed.len != (size_t)NONE) // we have a seed index: create a child at that position { _RYML_CB_ASSERT(m_tree->m_callbacks, (size_t)m_tree->num_children(m_id) == m_seed.len); m_id = m_tree->append_child(m_id); m_seed.str = nullptr; m_seed.len = (size_t)NONE; } else { _RYML_CB_ASSERT(m_tree->m_callbacks, readable()); } } void _apply(csubstr v) { m_tree->_set_val(m_id, v); } void _apply(NodeScalar const& v) { m_tree->_set_val(m_id, v); } void _apply(NodeInit const& i) { m_tree->_set(m_id, i); } public: /** @name modification of hierarchy */ /** @{ */ NodeRef insert_child(NodeRef after) { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, after.m_tree == m_tree); NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id)); return r; } NodeRef insert_child(NodeInit const& i, NodeRef after) { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, after.m_tree == m_tree); NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id)); r._apply(i); return r; } NodeRef prepend_child() { _C4RR(); NodeRef r(m_tree, m_tree->insert_child(m_id, NONE)); return r; } NodeRef prepend_child(NodeInit const& i) { _C4RR(); NodeRef r(m_tree, m_tree->insert_child(m_id, NONE)); r._apply(i); return r; } NodeRef append_child() { _C4RR(); NodeRef r(m_tree, m_tree->append_child(m_id)); return r; } NodeRef append_child(NodeInit const& i) { _C4RR(); NodeRef r(m_tree, m_tree->append_child(m_id)); r._apply(i); return r; } NodeRef insert_sibling(ConstNodeRef const& after) { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, after.m_tree == m_tree); NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id)); return r; } NodeRef insert_sibling(NodeInit const& i, ConstNodeRef const& after) { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, after.m_tree == m_tree); NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id)); r._apply(i); return r; } NodeRef prepend_sibling() { _C4RR(); NodeRef r(m_tree, m_tree->prepend_sibling(m_id)); return r; } NodeRef prepend_sibling(NodeInit const& i) { _C4RR(); NodeRef r(m_tree, m_tree->prepend_sibling(m_id)); r._apply(i); return r; } NodeRef append_sibling() { _C4RR(); NodeRef r(m_tree, m_tree->append_sibling(m_id)); return r; } NodeRef append_sibling(NodeInit const& i) { _C4RR(); NodeRef r(m_tree, m_tree->append_sibling(m_id)); r._apply(i); return r; } public: void remove_child(NodeRef & child) { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, has_child(child)); _RYML_CB_ASSERT(m_tree->m_callbacks, child.parent().id() == id()); m_tree->remove(child.id()); child.clear(); } //! remove the nth child of this node void remove_child(id_type pos) { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, pos >= 0 && pos < num_children()); id_type child = m_tree->child(m_id, pos); _RYML_CB_ASSERT(m_tree->m_callbacks, child != NONE); m_tree->remove(child); } //! remove a child by name void remove_child(csubstr key) { _C4RR(); id_type child = m_tree->find_child(m_id, key); _RYML_CB_ASSERT(m_tree->m_callbacks, child != NONE); m_tree->remove(child); } public: /** change the node's position within its parent, placing it after * @p after. To move to the first position in the parent, simply * pass an empty or default-constructed reference like this: * `n.move({})`. */ void move(ConstNodeRef const& after) { _C4RR(); m_tree->move(m_id, after.m_id); } /** move the node to a different @p parent (which may belong to a * different tree), placing it after @p after. When the * destination parent is in a new tree, then this node's tree * pointer is reset to the tree of the parent node. */ void move(NodeRef const& parent, ConstNodeRef const& after) { _C4RR(); if(parent.m_tree == m_tree) { m_tree->move(m_id, parent.m_id, after.m_id); } else { parent.m_tree->move(m_tree, m_id, parent.m_id, after.m_id); m_tree = parent.m_tree; } } /** duplicate the current node somewhere within its parent, and * place it after the node @p after. To place into the first * position of the parent, simply pass an empty or * default-constructed reference like this: `n.move({})`. */ NodeRef duplicate(ConstNodeRef const& after) const { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, m_tree == after.m_tree || after.m_id == NONE); id_type dup = m_tree->duplicate(m_id, m_tree->parent(m_id), after.m_id); NodeRef r(m_tree, dup); return r; } /** duplicate the current node somewhere into a different @p parent * (possibly from a different tree), and place it after the node * @p after. To place into the first position of the parent, * simply pass an empty or default-constructed reference like * this: `n.move({})`. */ NodeRef duplicate(NodeRef const& parent, ConstNodeRef const& after) const { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, parent.m_tree == after.m_tree || after.m_id == NONE); if(parent.m_tree == m_tree) { id_type dup = m_tree->duplicate(m_id, parent.m_id, after.m_id); NodeRef r(m_tree, dup); return r; } else { id_type dup = parent.m_tree->duplicate(m_tree, m_id, parent.m_id, after.m_id); NodeRef r(parent.m_tree, dup); return r; } } void duplicate_children(NodeRef const& parent, ConstNodeRef const& after) const { _C4RR(); _RYML_CB_ASSERT(m_tree->m_callbacks, parent.m_tree == after.m_tree); if(parent.m_tree == m_tree) { m_tree->duplicate_children(m_id, parent.m_id, after.m_id); } else { parent.m_tree->duplicate_children(m_tree, m_id, parent.m_id, after.m_id); } } /** @} */ #undef _C4RR #undef _C4RID }; // NOLINTEND(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) //----------------------------------------------------------------------------- inline ConstNodeRef::ConstNodeRef(NodeRef const& that) noexcept : m_tree(that.m_tree) , m_id(!that.is_seed() ? that.id() : (id_type)NONE) { } inline ConstNodeRef::ConstNodeRef(NodeRef && that) noexcept // NOLINT : m_tree(that.m_tree) , m_id(!that.is_seed() ? that.id() : (id_type)NONE) { } inline ConstNodeRef& ConstNodeRef::operator= (NodeRef const& that) noexcept { m_tree = (that.m_tree); m_id = (!that.is_seed() ? that.id() : (id_type)NONE); return *this; } inline ConstNodeRef& ConstNodeRef::operator= (NodeRef && that) noexcept // NOLINT { m_tree = (that.m_tree); m_id = (!that.is_seed() ? that.id() : (id_type)NONE); return *this; } //----------------------------------------------------------------------------- /** @addtogroup doc_serialization_helpers * * @{ */ template C4_ALWAYS_INLINE void write(NodeRef *n, T const& v) { n->set_val_serialized(v); } template C4_ALWAYS_INLINE bool read(ConstNodeRef const& C4_RESTRICT n, T *v) { return read(n.m_tree, n.m_id, v); } template C4_ALWAYS_INLINE bool read(NodeRef const& C4_RESTRICT n, T *v) { return read(n.tree(), n.id(), v); } template C4_ALWAYS_INLINE bool readkey(ConstNodeRef const& C4_RESTRICT n, T *v) { return readkey(n.m_tree, n.m_id, v); } template C4_ALWAYS_INLINE bool readkey(NodeRef const& C4_RESTRICT n, T *v) { return readkey(n.tree(), n.id(), v); } /** @} */ /** @} */ } // namespace yml } // namespace c4 #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #elif defined(_MSC_VER) # pragma warning(pop) #endif #endif /* _C4_YML_NODE_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/writer.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/writer.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_WRITER_HPP_ #define _C4_YML_WRITER_HPP_ #ifndef _C4_YML_COMMON_HPP_ #include "./common.hpp" #endif // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/substr.hpp //#include #if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) #error "amalgamate: file c4/substr.hpp must have been included at this point" #endif /* C4_SUBSTR_HPP_ */ //included above: //#include // fwrite(), fputc() //included above: //#include // memcpy() namespace c4 { namespace yml { /** @addtogroup doc_emit * @{ */ /** @defgroup doc_writers Writer objects to use with an Emitter * @see Emitter * @{ */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A writer that outputs to a file. Defaults to stdout. */ struct WriterFile { FILE * m_file; size_t m_pos; WriterFile(FILE *f = nullptr) : m_file(f ? f : stdout), m_pos(0) {} substr _get(bool /*error_on_excess*/) const { substr sp; sp.str = nullptr; sp.len = m_pos; return sp; } template void _do_write(const char (&a)[N]) { (void)fwrite(a, sizeof(char), N - 1, m_file); m_pos += N - 1; } void _do_write(csubstr sp) { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wsign-conversion") if(sp.empty()) return; (void)fwrite(sp.str, sizeof(csubstr::char_type), sp.len, m_file); m_pos += sp.len; C4_SUPPRESS_WARNING_GCC_CLANG_POP } void _do_write(const char c) { (void)fputc(c, m_file); ++m_pos; } void _do_write(const char c, size_t num_times) { for(size_t i = 0; i < num_times; ++i) (void)fputc(c, m_file); m_pos += num_times; } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A writer that outputs to an STL-like ostream. */ template struct WriterOStream { OStream* m_stream; size_t m_pos; WriterOStream(OStream &s) : m_stream(&s), m_pos(0) {} substr _get(bool /*error_on_excess*/) const { substr sp; sp.str = nullptr; sp.len = m_pos; return sp; } template void _do_write(const char (&a)[N]) { m_stream->write(a, N - 1); m_pos += N - 1; } void _do_write(csubstr sp) { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wsign-conversion") if(sp.empty()) return; m_stream->write(sp.str, sp.len); m_pos += sp.len; C4_SUPPRESS_WARNING_GCC_CLANG_POP } void _do_write(const char c) { m_stream->put(c); ++m_pos; } void _do_write(const char c, size_t num_times) { for(size_t i = 0; i < num_times; ++i) m_stream->put(c); m_pos += num_times; } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** a writer to a substr */ struct WriterBuf { substr m_buf; size_t m_pos; WriterBuf(substr sp) : m_buf(sp), m_pos(0) {} substr _get(bool error_on_excess) const { if(m_pos <= m_buf.len) return m_buf.first(m_pos); else if(error_on_excess) c4::yml::error("not enough space in the given buffer"); substr sp; sp.str = nullptr; sp.len = m_pos; return sp; } template void _do_write(const char (&a)[N]) { RYML_ASSERT( ! m_buf.overlaps(a)); if(m_pos + N-1 <= m_buf.len) memcpy(&(m_buf[m_pos]), a, N-1); m_pos += N-1; } void _do_write(csubstr sp) { if(sp.empty()) return; RYML_ASSERT( ! sp.overlaps(m_buf)); if(m_pos + sp.len <= m_buf.len) memcpy(&(m_buf[m_pos]), sp.str, sp.len); m_pos += sp.len; } void _do_write(const char c) { if(m_pos + 1 <= m_buf.len) m_buf[m_pos] = c; ++m_pos; } void _do_write(const char c, size_t num_times) { if(m_pos + num_times <= m_buf.len) for(size_t i = 0; i < num_times; ++i) m_buf[m_pos + i] = c; m_pos += num_times; } }; /** @ } */ /** @ } */ } // namespace yml } // namespace c4 #endif /* _C4_YML_WRITER_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/writer.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/detail/parser_dbg.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_DETAIL_PARSER_DBG_HPP_ #define _C4_YML_DETAIL_PARSER_DBG_HPP_ #ifndef _C4_YML_COMMON_HPP_ #include "../common.hpp" #endif #ifdef RYML_DBG //included above: //#include #endif //----------------------------------------------------------------------------- // some debugging scaffolds // NOLINTBEGIN(*) C4_SUPPRESS_WARNING_GCC_CLANG_PUSH C4_SUPPRESS_WARNING_MSVC_PUSH C4_SUPPRESS_WARNING_MSVC(4068/*unknown pragma*/) #pragma GCC system_header C4_SUPPRESS_WARNING_GCC("-Wunknown-pragmas") C4_SUPPRESS_WARNING_CLANG("-Wgnu-zero-variadic-macro-arguments") // NOLINTEND(*) #ifndef RYML_DBG # define _c4err(fmt, ...) \ this->_err("ERROR: " fmt, ## __VA_ARGS__) # define _c4dbgt(fmt, ...) # define _c4dbgpf(fmt, ...) # define _c4dbgpf_(fmt, ...) # define _c4dbgp(msg) # define _c4dbgp_(msg) # define _c4dbgq(msg) # define _c4presc(...) # define _c4prscalar(msg, scalar, keep_newlines) #else # define _c4err(fmt, ...) \ do { RYML_DEBUG_BREAK(); this->_err("ERROR:\n" "{}:{}: " fmt, __FILE__, __LINE__, ## __VA_ARGS__); } while(0) # define _c4dbgt(fmt, ...) do { if(_dbg_enabled()) { \ this->_dbg ("{}:{}: " fmt , __FILE__, __LINE__, ## __VA_ARGS__); } } while(0) # define _c4dbgpf(fmt, ...) _dbg_printf("{}:{}: " fmt "\n", __FILE__, __LINE__, ## __VA_ARGS__) # define _c4dbgpf_(fmt, ...) _dbg_printf("{}:{}: " fmt , __FILE__, __LINE__, ## __VA_ARGS__) # define _c4dbgp(msg) _dbg_printf("{}:{}: " msg "\n", __FILE__, __LINE__ ) # define _c4dbgp_(msg) _dbg_printf("{}:{}: " msg , __FILE__, __LINE__ ) # define _c4dbgq(msg) _dbg_printf(msg "\n") # define _c4presc(...) do { if(_dbg_enabled()) __c4presc(__VA_ARGS__); } while(0) # define _c4prscalar(msg, scalar, keep_newlines) \ do { \ _c4dbgpf_("{}: [{}]~~~", msg, scalar.len); \ if(_dbg_enabled()) { \ __c4presc((scalar).str, (scalar).len, (keep_newlines)); \ } \ _c4dbgq("~~~"); \ } while(0) #endif // RYML_DBG //----------------------------------------------------------------------------- #ifdef RYML_DBG // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/dump.hpp //#include #if !defined(C4_DUMP_HPP_) && !defined(_C4_DUMP_HPP_) #error "amalgamate: file c4/dump.hpp must have been included at this point" #endif /* C4_DUMP_HPP_ */ namespace c4 { inline bool& _dbg_enabled() { static bool enabled = true; return enabled; } inline void _dbg_set_enabled(bool yes) { _dbg_enabled() = yes; } inline void _dbg_dumper(csubstr s) { if(s.str) fwrite(s.str, 1, s.len, stdout); } inline substr _dbg_buf() noexcept { static char writebuf[2048]; return substr{writebuf, sizeof(writebuf)}; // g++-5 has trouble with return writebuf; } template C4_NO_INLINE void _dbg_printf(c4::csubstr fmt, Args const& ...args) { if(_dbg_enabled()) { substr buf = _dbg_buf(); const size_t needed_size = c4::format_dump(&_dbg_dumper, buf, fmt, args...); C4_CHECK(needed_size <= buf.len); } } inline C4_NO_INLINE void __c4presc(const char *s, size_t len, bool keep_newlines=false) { RYML_ASSERT(s || !len); size_t prev = 0; for(size_t i = 0; i < len; ++i) { switch(s[i]) { case '\n' : _dbg_printf("{}{}{}", csubstr(s+prev, i-prev), csubstr("\\n"), csubstr(keep_newlines ? "\n":"")); prev = i+1; break; case '\t' : _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\t")); prev = i+1; break; case '\0' : _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\0")); prev = i+1; break; case '\r' : _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\r")); prev = i+1; break; case '\f' : _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\f")); prev = i+1; break; case '\b' : _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\b")); prev = i+1; break; case '\v' : _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\v")); prev = i+1; break; case '\a' : _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\a")); prev = i+1; break; case '\x1b': _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\x1b")); prev = i+1; break; case -0x3e/*0xc2u*/: if(i+1 < len) { if(s[i+1] == -0x60/*0xa0u*/) { _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\_")); prev = i+1; } else if(s[i+1] == -0x7b/*0x85u*/) { _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\N")); prev = i+1; } } break; case -0x1e/*0xe2u*/: if(i+2 < len && s[i+1] == -0x80/*0x80u*/) { if(s[i+2] == -0x58/*0xa8u*/) { _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\L")); prev = i+1; } else if(s[i+2] == -0x57/*0xa9u*/) { _dbg_printf("{}{}", csubstr(s+prev, i-prev), csubstr("\\P")); prev = i+1; } } break; } } if(len > prev) _dbg_printf("{}", csubstr(s+prev, len-prev)); } inline void __c4presc(csubstr s, bool keep_newlines=false) { __c4presc(s.str, s.len, keep_newlines); } } // namespace c4 #endif // RYML_DBG C4_SUPPRESS_WARNING_GCC_CLANG_POP C4_SUPPRESS_WARNING_MSVC_POP #endif /* _C4_YML_DETAIL_PARSER_DBG_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp) #define C4_YML_EMIT_DEF_HPP_ //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/emit.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/emit.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_EMIT_HPP_ #define _C4_YML_EMIT_HPP_ /** @file emit.hpp Utilities to emit YAML and JSON. */ #ifndef _C4_YML_WRITER_HPP_ #include "./writer.hpp" #endif #ifndef _C4_YML_TREE_HPP_ #include "./tree.hpp" #endif #ifndef _C4_YML_NODE_HPP_ #include "./node.hpp" #endif C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- namespace c4 { namespace yml { /** @addtogroup doc_emit * * @{ */ // fwd declarations template class Emitter; template using EmitterOStream = Emitter>; using EmitterFile = Emitter; using EmitterBuf = Emitter; namespace detail { inline bool is_set_(ConstNodeRef n) { return n.tree() && (n.id() != NONE); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** Specifies the type of content to emit */ typedef enum { EMIT_YAML = 0, ///< emit YAML EMIT_JSON = 1 ///< emit JSON } EmitType_e; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A lightweight object containing options to be used when emitting. */ struct EmitOptions { typedef enum : uint32_t { DEFAULT_FLAGS = 0u, JSON_ERR_ON_TAG = 1u << 0u, JSON_ERR_ON_ANCHOR = 1u << 1u, _JSON_ERR_MASK = JSON_ERR_ON_TAG|JSON_ERR_ON_ANCHOR, } EmitOptionFlags_e; public: /** @name option flags * * @{ */ C4_ALWAYS_INLINE EmitOptionFlags_e json_error_flags() const noexcept { return m_option_flags; } EmitOptions& json_error_flags(EmitOptionFlags_e d) noexcept { m_option_flags = (EmitOptionFlags_e)(d & _JSON_ERR_MASK); return *this; } /** @} */ public: /** @name max depth for the emitted tree * * This makes the emitter fail when emitting trees exceeding the * max_depth. * * @{ */ C4_ALWAYS_INLINE id_type max_depth() const noexcept { return m_max_depth; } EmitOptions& max_depth(id_type d) noexcept { m_max_depth = d; return *this; } static constexpr const id_type max_depth_default = 64; /** @} */ public: bool operator== (const EmitOptions& that) const noexcept { return m_max_depth == that.m_max_depth && m_option_flags == that.m_option_flags; } private: /** @cond dev */ id_type m_max_depth{max_depth_default}; EmitOptionFlags_e m_option_flags{DEFAULT_FLAGS}; /** @endcond */ }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** A stateful emitter, for use with a writer such as @ref WriterBuf, * @ref WriterFile, or @ref WriterOStream */ template class Emitter : public Writer { public: /** Construct the emitter and its internal Writer state, using default emit options. * @param args arguments to be forwarded to the constructor of the writer. * */ template Emitter(Args &&...args) : Writer(std::forward(args)...), m_tree(), m_opts(), m_flow(false) {} /** Construct the emitter and its internal Writer state. * * @param opts EmitOptions * @param args arguments to be forwarded to the constructor of the writer. * */ template Emitter(EmitOptions const& opts, Args &&...args) : Writer(std::forward(args)...), m_tree(), m_opts(opts), m_flow(false) {} /** emit! * * When writing to a buffer, returns a substr of the emitted YAML. * If the given buffer has insufficient space, the returned substr * will be null and its size will be the needed space. Whatever * the size of the buffer, it is guaranteed that no writes are * done past its end. * * When writing to a file, the returned substr will be null, but its * length will be set to the number of bytes written. * * @param type specify what to emit * @param t the tree to emit * @param id the id of the node to emit * @param error_on_excess when true, an error is raised when the * output buffer is too small for the emitted YAML/JSON * */ substr emit_as(EmitType_e type, Tree const& t, id_type id, bool error_on_excess); /** emit starting at the root node */ substr emit_as(EmitType_e type, Tree const& t, bool error_on_excess=true) { if(t.empty()) return {}; return this->emit_as(type, t, t.root_id(), error_on_excess); } /** emit starting at the given node */ substr emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess=true) { if(!detail::is_set_(n)) return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return this->emit_as(type, *n.tree(), n.id(), error_on_excess); } public: /** get the emit options for this object */ EmitOptions const& options() const noexcept { return m_opts; } /** set the max depth for emitted trees (to prevent a stack overflow) */ void max_depth(id_type max_depth) noexcept { m_opts.max_depth(max_depth); } /** get the max depth for emitted trees (to prevent a stack overflow) */ id_type max_depth() const noexcept { return m_opts.max_depth(); } private: Tree const* C4_RESTRICT m_tree; EmitOptions m_opts; bool m_flow; private: void _emit_yaml(id_type id); void _do_visit_flow_sl(id_type id, id_type depth, id_type ilevel=0); void _do_visit_flow_ml(id_type id, id_type depth, id_type ilevel=0, id_type do_indent=1); void _do_visit_block(id_type id, id_type depth, id_type ilevel=0, id_type do_indent=1); void _do_visit_block_container(id_type id, id_type depth, id_type next_level, bool do_indent); void _do_visit_json(id_type id, id_type depth); private: void _write(NodeScalar const& C4_RESTRICT sc, NodeType flags, id_type level); void _write_json(NodeScalar const& C4_RESTRICT sc, NodeType flags); void _write_doc(id_type id); void _write_scalar_json_dquo(csubstr s); void _write_scalar_literal(csubstr s, id_type level, bool as_key); void _write_scalar_folded(csubstr s, id_type level, bool as_key); void _write_scalar_squo(csubstr s, id_type level); void _write_scalar_dquo(csubstr s, id_type level); void _write_scalar_plain(csubstr s, id_type level); size_t _write_escaped_newlines(csubstr s, size_t i); size_t _write_indented_block(csubstr s, size_t i, id_type level); void _write_tag(csubstr tag) { if(!tag.begins_with('!')) this->Writer::_do_write('!'); this->Writer::_do_write(tag); } enum : type_bits { _keysc = (KEY|KEYREF|KEYANCH|KEYQUO|KEY_STYLE) | ~(VAL|VALREF|VALANCH|VALQUO|VAL_STYLE) | CONTAINER_STYLE, _valsc = ~(KEY|KEYREF|KEYANCH|KEYQUO|KEY_STYLE) | (VAL|VALREF|VALANCH|VALQUO|VAL_STYLE) | CONTAINER_STYLE, _keysc_json = (KEY) | ~(VAL), _valsc_json = ~(KEY) | (VAL), }; C4_ALWAYS_INLINE void _writek(id_type id, id_type level) { _write(m_tree->keysc(id), (m_tree->_p(id)->m_type.type & ~_valsc), level); } C4_ALWAYS_INLINE void _writev(id_type id, id_type level) { _write(m_tree->valsc(id), (m_tree->_p(id)->m_type.type & ~_keysc), level); } C4_ALWAYS_INLINE void _writek_json(id_type id) { _write_json(m_tree->keysc(id), m_tree->_p(id)->m_type.type & ~(VAL)); } C4_ALWAYS_INLINE void _writev_json(id_type id) { _write_json(m_tree->valsc(id), m_tree->_p(id)->m_type.type & ~(KEY)); } void _indent(id_type level, bool enabled) { if(enabled) this->Writer::_do_write(' ', 2u * (size_t)level); } void _indent(id_type level) { if(!m_flow) this->Writer::_do_write(' ', 2u * (size_t)level); } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_emit_to_file Emit to file * * @{ */ // emit from tree and node id ----------------------- /** (1) emit YAML to the given file, starting at the given node. A null * file defaults to stdout. Return the number of bytes written. */ inline size_t emit_yaml(Tree const& t, id_type id, EmitOptions const& opts, FILE *f) { EmitterFile em(opts, f); return em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true).len; } /** (2) like (1), but use default emit options */ inline size_t emit_yaml(Tree const& t, id_type id, FILE *f) { EmitterFile em(f); return em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true).len; } /** (1) emit JSON to the given file, starting at the given node. A null * file defaults to stdout. Return the number of bytes written. */ inline size_t emit_json(Tree const& t, id_type id, EmitOptions const& opts, FILE *f) { EmitterFile em(opts, f); return em.emit_as(EMIT_JSON, t, id, /*error_on_excess*/true).len; } /** (2) like (1), but use default emit options */ inline size_t emit_json(Tree const& t, id_type id, FILE *f) { EmitterFile em(f); return em.emit_as(EMIT_JSON, t, id, /*error_on_excess*/true).len; } // emit from root ------------------------- /** (1) emit YAML to the given file, starting at the root node. A null file defaults to stdout. * Return the number of bytes written. */ inline size_t emit_yaml(Tree const& t, EmitOptions const& opts, FILE *f=nullptr) { EmitterFile em(opts, f); return em.emit_as(EMIT_YAML, t, /*error_on_excess*/true).len; } /** (2) like (1), but use default emit options */ inline size_t emit_yaml(Tree const& t, FILE *f=nullptr) { EmitterFile em(f); return em.emit_as(EMIT_YAML, t, /*error_on_excess*/true).len; } /** (1) emit JSON to the given file. A null file defaults to stdout. * Return the number of bytes written. */ inline size_t emit_json(Tree const& t, EmitOptions const& opts, FILE *f=nullptr) { EmitterFile em(opts, f); return em.emit_as(EMIT_JSON, t, /*error_on_excess*/true).len; } /** (2) like (1), but use default emit options */ inline size_t emit_json(Tree const& t, FILE *f=nullptr) { EmitterFile em(f); return em.emit_as(EMIT_JSON, t, /*error_on_excess*/true).len; } // emit from ConstNodeRef ------------------------ /** (1) emit YAML to the given file. A null file defaults to stdout. * Return the number of bytes written. */ inline size_t emit_yaml(ConstNodeRef const& r, EmitOptions const& opts, FILE *f=nullptr) { if(!detail::is_set_(r)) return {}; EmitterFile em(opts, f); return em.emit_as(EMIT_YAML, r, /*error_on_excess*/true).len; } /** (2) like (1), but use default emit options */ inline size_t emit_yaml(ConstNodeRef const& r, FILE *f=nullptr) { if(!detail::is_set_(r)) return {}; EmitterFile em(f); return em.emit_as(EMIT_YAML, r, /*error_on_excess*/true).len; } /** (1) emit JSON to the given file. A null file defaults to stdout. * Return the number of bytes written. */ inline size_t emit_json(ConstNodeRef const& r, EmitOptions const& opts, FILE *f=nullptr) { if(!detail::is_set_(r)) return {}; EmitterFile em(opts, f); return em.emit_as(EMIT_JSON, r, /*error_on_excess*/true).len; } /** (2) like (1), but use default emit options */ inline size_t emit_json(ConstNodeRef const& r, FILE *f=nullptr) { if(!detail::is_set_(r)) return {}; EmitterFile em(f); return em.emit_as(EMIT_JSON, r, /*error_on_excess*/true).len; } /** @} */ //----------------------------------------------------------------------------- /** @defgroup doc_emit_to_ostream Emit to an STL-like ostream * * @{ */ /** emit YAML to an STL-like ostream */ template inline OStream& operator<< (OStream& s, Tree const& t) { EmitterOStream em(s); em.emit_as(EMIT_YAML, t); return s; } /** emit YAML to an STL-like ostream * @overload */ template inline OStream& operator<< (OStream& s, ConstNodeRef const& n) { if(!detail::is_set_(n)) return s; EmitterOStream em(s); em.emit_as(EMIT_YAML, n); return s; } /** mark a tree or node to be emitted as yaml when using @ref * operator<<, with options. For example: * * ```cpp * Tree t = parse_in_arena("{foo: bar}"); * std::cout << t; // emits YAML * std::cout << as_yaml(t); // emits YAML, same as above * std::cout << as_yaml(t, EmitOptions().max_depth(10)); // emits JSON with a max tree depth * ``` * * @see @ref operator<< */ struct as_json { Tree const* tree; size_t node; EmitOptions options; as_json(Tree const& t, EmitOptions const& opts={}) : tree(&t), node(t.empty() ? NONE : t.root_id()), options(opts) {} as_json(Tree const& t, size_t id, EmitOptions const& opts={}) : tree(&t), node(id), options(opts) {} as_json(ConstNodeRef const& n, EmitOptions const& opts={}) : tree(n.tree()), node(n.id()), options(opts) {} }; /** mark a tree or node to be emitted as yaml when using @ref * operator<< . For example: * * ```cpp * Tree t = parse_in_arena("{foo: bar}"); * std::cout << t; // emits YAML * std::cout << as_json(t); // emits JSON * std::cout << as_json(t, EmitOptions().max_depth(10)); // emits JSON with a max tree depth * ``` * * @see @ref operator<< */ struct as_yaml { Tree const* tree; size_t node; EmitOptions options; as_yaml(Tree const& t, EmitOptions const& opts={}) : tree(&t), node(t.empty() ? NONE : t.root_id()), options(opts) {} as_yaml(Tree const& t, size_t id, EmitOptions const& opts={}) : tree(&t), node(id), options(opts) {} as_yaml(ConstNodeRef const& n, EmitOptions const& opts={}) : tree(n.tree()), node(n.id()), options(opts) {} }; /** emit json to an STL-like stream */ template inline OStream& operator<< (OStream& s, as_json const& j) { if(!j.tree || j.node == NONE) return s; EmitterOStream em(j.options, s); em.emit_as(EMIT_JSON, *j.tree, j.node, true); return s; } /** emit yaml to an STL-like stream */ template inline OStream& operator<< (OStream& s, as_yaml const& y) { if(!y.tree || y.node == NONE) return s; EmitterOStream em(y.options, s); em.emit_as(EMIT_YAML, *y.tree, y.node, true); return s; } /** @} */ //----------------------------------------------------------------------------- /** @defgroup doc_emit_to_buffer Emit to memory buffer * * @{ */ // emit from tree and node id ----------------------- /** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. * @param t the tree to emit. * @param id the node where to start emitting. * @param opts emit options. * @param buf the output buffer. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is * insufficient (when error_on_excess is false), the string pointer of the * result will be set to null, and the length will report the required buffer size. */ inline substr emit_yaml(Tree const& t, id_type id, EmitOptions const& opts, substr buf, bool error_on_excess=true) { EmitterBuf em(opts, buf); return em.emit_as(EMIT_YAML, t, id, error_on_excess); } /** (2) like (1), but use default emit options */ inline substr emit_yaml(Tree const& t, id_type id, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); return em.emit_as(EMIT_YAML, t, id, error_on_excess); } /** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. * @param t the tree to emit. * @param id the node where to start emitting. * @param opts emit options. * @param buf the output buffer. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is * insufficient (when error_on_excess is false), the string pointer of the * result will be set to null, and the length will report the required buffer size. */ inline substr emit_json(Tree const& t, id_type id, EmitOptions const& opts, substr buf, bool error_on_excess=true) { EmitterBuf em(opts, buf); return em.emit_as(EMIT_JSON, t, id, error_on_excess); } /** (2) like (1), but use default emit options */ inline substr emit_json(Tree const& t, id_type id, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); return em.emit_as(EMIT_JSON, t, id, error_on_excess); } // emit from root ------------------------- /** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. * @param t the tree; will be emitted from the root node. * @param opts emit options. * @param buf the output buffer. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is * insufficient (when error_on_excess is false), the string pointer of the * result will be set to null, and the length will report the required buffer size. */ inline substr emit_yaml(Tree const& t, EmitOptions const& opts, substr buf, bool error_on_excess=true) { EmitterBuf em(opts, buf); return em.emit_as(EMIT_YAML, t, error_on_excess); } /** (2) like (1), but use default emit options */ inline substr emit_yaml(Tree const& t, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); return em.emit_as(EMIT_YAML, t, error_on_excess); } /** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. * @param t the tree; will be emitted from the root node. * @param opts emit options. * @param buf the output buffer. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is * insufficient (when error_on_excess is false), the string pointer of the * result will be set to null, and the length will report the required buffer size. */ inline substr emit_json(Tree const& t, EmitOptions const& opts, substr buf, bool error_on_excess=true) { EmitterBuf em(opts, buf); return em.emit_as(EMIT_JSON, t, error_on_excess); } /** (2) like (1), but use default emit options */ inline substr emit_json(Tree const& t, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); return em.emit_as(EMIT_JSON, t, error_on_excess); } // emit from ConstNodeRef ------------------------ /** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. * @param r the starting node. * @param buf the output buffer. * @param opts emit options. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is * insufficient (when error_on_excess is false), the string pointer of the * result will be set to null, and the length will report the required buffer size. */ inline substr emit_yaml(ConstNodeRef const& r, EmitOptions const& opts, substr buf, bool error_on_excess=true) { if(!detail::is_set_(r)) return {}; EmitterBuf em(opts, buf); return em.emit_as(EMIT_YAML, r, error_on_excess); } /** (2) like (1), but use default emit options */ inline substr emit_yaml(ConstNodeRef const& r, substr buf, bool error_on_excess=true) { if(!detail::is_set_(r)) return {}; EmitterBuf em(buf); return em.emit_as(EMIT_YAML, r, error_on_excess); } /** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. * @param r the starting node. * @param buf the output buffer. * @param opts emit options. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is * insufficient (when error_on_excess is false), the string pointer of the * result will be set to null, and the length will report the required buffer size. */ inline substr emit_json(ConstNodeRef const& r, EmitOptions const& opts, substr buf, bool error_on_excess=true) { if(!detail::is_set_(r)) return {}; EmitterBuf em(opts, buf); return em.emit_as(EMIT_JSON, r, error_on_excess); } /** (2) like (1), but use default emit options */ inline substr emit_json(ConstNodeRef const& r, substr buf, bool error_on_excess=true) { if(!detail::is_set_(r)) return {}; EmitterBuf em(buf); return em.emit_as(EMIT_JSON, r, error_on_excess); } //----------------------------------------------------------------------------- /** @defgroup doc_emit_to_container Emit to resizeable container * * @{ */ // emit from tree and node id --------------------------- /** (1) emit+resize: emit YAML to the given `std::string`/`std::vector`-like * container, resizing it as needed to fit the emitted YAML. If @p append is * set to true, the emitted YAML is appended at the end of the container. * * @return a substr trimmed to the emitted YAML (excluding the initial contents, when appending) */ template substr emitrs_yaml(Tree const& t, id_type id, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { size_t startpos = append ? cont->size() : 0u; cont->resize(cont->capacity()); // otherwise the first emit would be certain to fail substr buf = to_substr(*cont).sub(startpos); substr ret = emit_yaml(t, id, opts, buf, /*error_on_excess*/false); if(ret.str == nullptr && ret.len > 0) { cont->resize(startpos + ret.len); buf = to_substr(*cont).sub(startpos); ret = emit_yaml(t, id, opts, buf, /*error_on_excess*/true); } else { cont->resize(startpos + ret.len); } return ret; } /** (2) like (1), but use default emit options */ template substr emitrs_yaml(Tree const& t, id_type id, CharOwningContainer * cont, bool append=false) { return emitrs_yaml(t, id, EmitOptions{}, cont, append); } /** (1) emit+resize: emit JSON to the given `std::string`/`std::vector`-like * container, resizing it as needed to fit the emitted JSON. If @p append is * set to true, the emitted YAML is appended at the end of the container. * * @return a substr trimmed to the emitted JSON (excluding the initial contents, when appending) */ template substr emitrs_json(Tree const& t, id_type id, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { const size_t startpos = append ? cont->size() : 0u; cont->resize(cont->capacity()); // otherwise the first emit would be certain to fail substr buf = to_substr(*cont).sub(startpos); EmitterBuf em(opts, buf); substr ret = emit_json(t, id, opts, buf, /*error_on_excess*/false); if(ret.str == nullptr && ret.len > 0) { cont->resize(startpos + ret.len); buf = to_substr(*cont).sub(startpos); ret = emit_json(t, id, opts, buf, /*error_on_excess*/true); } else { cont->resize(startpos + ret.len); } return ret; } /** (2) like (1), but use default emit options */ template substr emitrs_json(Tree const& t, id_type id, CharOwningContainer * cont, bool append=false) { return emitrs_json(t, id, EmitOptions{}, cont, append); } /** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ template CharOwningContainer emitrs_yaml(Tree const& t, id_type id, EmitOptions const& opts={}) { CharOwningContainer c; emitrs_yaml(t, id, opts, &c); return c; } /** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ template CharOwningContainer emitrs_json(Tree const& t, id_type id, EmitOptions const& opts={}) { CharOwningContainer c; emitrs_json(t, id, opts, &c); return c; } // emit from root ------------------------- /** (1) emit+resize: YAML to the given `std::string`/`std::vector`-like * container, resizing it as needed to fit the emitted YAML. * @return a substr trimmed to the new emitted contents. */ template substr emitrs_yaml(Tree const& t, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { if(t.empty()) return {}; return emitrs_yaml(t, t.root_id(), opts, cont, append); } /** (2) like (1), but use default emit options */ template substr emitrs_yaml(Tree const& t, CharOwningContainer * cont, bool append=false) { if(t.empty()) return {}; return emitrs_yaml(t, t.root_id(), EmitOptions{}, cont, append); } /** (1) emit+resize: JSON to the given `std::string`/`std::vector`-like * container, resizing it as needed to fit the emitted JSON. * @return a substr trimmed to the new emitted contents. */ template substr emitrs_json(Tree const& t, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { if(t.empty()) return {}; return emitrs_json(t, t.root_id(), opts, cont, append); } /** (2) like (1), but use default emit options */ template substr emitrs_json(Tree const& t, CharOwningContainer * cont, bool append=false) { if(t.empty()) return {}; return emitrs_json(t, t.root_id(), EmitOptions{}, cont, append); } /** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ template CharOwningContainer emitrs_yaml(Tree const& t, EmitOptions const& opts={}) { CharOwningContainer c; if(t.empty()) return c; emitrs_yaml(t, t.root_id(), opts, &c); return c; } /** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ template CharOwningContainer emitrs_json(Tree const& t, EmitOptions const& opts={}) { CharOwningContainer c; if(t.empty()) return c; emitrs_json(t, t.root_id(), opts, &c); return c; } // emit from ConstNodeRef ------------------------ /** (1) emit+resize: YAML to the given `std::string`/`std::vector`-like container, * resizing it as needed to fit the emitted YAML. * @return a substr trimmed to the new emitted contents */ template substr emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { if(!detail::is_set_(n)) return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_yaml(*n.tree(), n.id(), opts, cont, append); } /** (2) like (1), but use default emit options */ template substr emitrs_yaml(ConstNodeRef const& n, CharOwningContainer * cont, bool append=false) { if(!detail::is_set_(n)) return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_yaml(*n.tree(), n.id(), EmitOptions{}, cont, append); } /** (1) emit+resize: JSON to the given `std::string`/`std::vector`-like container, * resizing it as needed to fit the emitted JSON. * @return a substr trimmed to the new emitted contents */ template substr emitrs_json(ConstNodeRef const& n, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { if(!detail::is_set_(n)) return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_json(*n.tree(), n.id(), opts, cont, append); } /** (2) like (1), but use default emit options */ template substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont, bool append=false) { if(!detail::is_set_(n)) return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_json(*n.tree(), n.id(), EmitOptions{}, cont, append); } /** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ template CharOwningContainer emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts={}) { if(!detail::is_set_(n)) return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); CharOwningContainer c; emitrs_yaml(*n.tree(), n.id(), opts, &c); return c; } /** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ template CharOwningContainer emitrs_json(ConstNodeRef const& n, EmitOptions const& opts={}) { if(!detail::is_set_(n)) return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); CharOwningContainer c; emitrs_json(*n.tree(), n.id(), opts, &c); return c; } /** @} */ //----------------------------------------------------------------------------- /** @cond dev */ #define RYML_DEPRECATE_EMIT \ RYML_DEPRECATED("use emit_yaml() instead. " \ "See https://github.com/biojppm/rapidyaml/issues/120") #define RYML_DEPRECATE_EMITRS \ RYML_DEPRECATED("use emitrs_yaml() instead. " \ "See https://github.com/biojppm/rapidyaml/issues/120") // workaround for Qt emit which is a macro; // see https://github.com/biojppm/rapidyaml/issues/120. // emit is defined in qobjectdefs.h (as an empty define). #ifdef emit #define RYML_TMP_EMIT_ #undef emit #endif RYML_DEPRECATE_EMIT inline size_t emit(Tree const& t, id_type id, FILE *f) { return emit_yaml(t, id, f); } RYML_DEPRECATE_EMIT inline size_t emit(Tree const& t, FILE *f=nullptr) { return emit_yaml(t, f); } RYML_DEPRECATE_EMIT inline size_t emit(ConstNodeRef const& r, FILE *f=nullptr) { return emit_yaml(r, f); } RYML_DEPRECATE_EMIT inline substr emit(Tree const& t, id_type id, substr buf, bool error_on_excess=true) { return emit_yaml(t, id, buf, error_on_excess); } RYML_DEPRECATE_EMIT inline substr emit(Tree const& t, substr buf, bool error_on_excess=true) { return emit_yaml(t, buf, error_on_excess); } RYML_DEPRECATE_EMIT inline substr emit(ConstNodeRef const& r, substr buf, bool error_on_excess=true) { return emit_yaml(r, buf, error_on_excess); } #ifdef RYML_TMP_EMIT_ #define emit #undef RYML_TMP_EMIT_ #endif template RYML_DEPRECATE_EMITRS substr emitrs(Tree const& t, id_type id, CharOwningContainer * cont) { return emitrs_yaml(t, id, cont); } template RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(Tree const& t, id_type id) { return emitrs_yaml(t, id); } template RYML_DEPRECATE_EMITRS substr emitrs(Tree const& t, CharOwningContainer * cont) { return emitrs_yaml(t, cont); } template RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(Tree const& t) { return emitrs_yaml(t); } template RYML_DEPRECATE_EMITRS substr emitrs(ConstNodeRef const& n, CharOwningContainer * cont) { return emitrs_yaml(n, cont); } template RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(ConstNodeRef const& n) { return emitrs_yaml(n); } /** @endcond */ } // namespace yml } // namespace c4 C4_SUPPRESS_WARNING_GCC_CLANG_POP #undef RYML_DEPRECATE_EMIT #undef RYML_DEPRECATE_EMITRS // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/emit.def.hpp //#include "c4/yml/emit.def.hpp" // NOLINT #if !defined(C4_YML_EMIT_DEF_HPP_) && !defined(_C4_YML_EMIT_DEF_HPP_) #error "amalgamate: file c4/yml/emit.def.hpp must have been included at this point" #endif /* C4_YML_EMIT_DEF_HPP_ */ #endif /* _C4_YML_EMIT_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/emit.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/emit.def.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/emit.def.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_EMIT_DEF_HPP_ #define _C4_YML_EMIT_DEF_HPP_ #ifndef _C4_YML_EMIT_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/emit.hpp //#include "c4/yml/emit.hpp" #if !defined(C4_YML_EMIT_HPP_) && !defined(_C4_YML_EMIT_HPP_) #error "amalgamate: file c4/yml/emit.hpp must have been included at this point" #endif /* C4_YML_EMIT_HPP_ */ #endif /** @file emit.def.hpp Definitions for emit functions. */ #ifndef _C4_YML_DETAIL_PARSER_DBG_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //#include "c4/yml/detail/parser_dbg.hpp" #if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) #error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ #endif namespace c4 { namespace yml { template substr Emitter::emit_as(EmitType_e type, Tree const& t, id_type id, bool error_on_excess) { if(t.empty()) { _RYML_CB_ASSERT(t.callbacks(), id == NONE); return {}; } if(id == NONE) id = t.root_id(); _RYML_CB_CHECK(t.callbacks(), id < t.capacity()); m_tree = &t; m_flow = false; if(type == EMIT_YAML) _emit_yaml(id); else if(type == EMIT_JSON) _do_visit_json(id, 0); else _RYML_CB_ERR(m_tree->callbacks(), "unknown emit type"); m_tree = nullptr; return this->Writer::_get(error_on_excess); } //----------------------------------------------------------------------------- template void Emitter::_emit_yaml(id_type id) { // save branches in the visitor by doing the initial stream/doc // logic here, sparing the need to check stream/val/keyval inside // the visitor functions auto dispatch = [this](id_type node){ NodeType ty = m_tree->type(node); if(ty.is_flow_sl()) _do_visit_flow_sl(node, 0); else if(ty.is_flow_ml()) _do_visit_flow_ml(node, 0); else { _do_visit_block(node, 0); } }; if(!m_tree->is_root(id)) { if(m_tree->is_container(id) && !m_tree->type(id).is_flow()) { id_type ilevel = 0; if(m_tree->has_key(id)) { this->Writer::_do_write(m_tree->key(id)); this->Writer::_do_write(":\n"); ++ilevel; } _do_visit_block_container(id, 0, ilevel, ilevel); return; } } TagDirectiveRange tagds = m_tree->tag_directives(); auto write_tag_directives = [&tagds, this](const id_type next_node){ TagDirective const* C4_RESTRICT end = tagds.b; while(end < tagds.e) { if(end->next_node_id > next_node) break; ++end; } const id_type parent = m_tree->parent(next_node); for( ; tagds.b != end; ++tagds.b) { if(next_node != m_tree->first_child(parent)) this->Writer::_do_write("...\n"); this->Writer::_do_write("%TAG "); this->Writer::_do_write(tagds.b->handle); this->Writer::_do_write(' '); this->Writer::_do_write(tagds.b->prefix); this->Writer::_do_write('\n'); } }; if(m_tree->is_stream(id)) { const id_type first_child = m_tree->first_child(id); if(first_child != NONE) write_tag_directives(first_child); for(id_type child = first_child; child != NONE; child = m_tree->next_sibling(child)) { dispatch(child); if(m_tree->is_doc(child) && m_tree->type(child).is_flow_sl()) this->Writer::_do_write('\n'); if(m_tree->next_sibling(child) != NONE) write_tag_directives(m_tree->next_sibling(child)); } } else if(m_tree->is_container(id)) { dispatch(id); } else if(m_tree->is_doc(id)) { _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->is_container(id)); // checked above _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_val(id)); // so it must be a val _write_doc(id); } else if(m_tree->is_keyval(id)) { _writek(id, 0); this->Writer::_do_write(": "); _writev(id, 0); if(!m_tree->type(id).is_flow()) this->Writer::_do_write('\n'); } else if(m_tree->is_val(id)) { //this->Writer::_do_write("- "); _writev(id, 0); if(!m_tree->type(id).is_flow()) this->Writer::_do_write('\n'); } else if(m_tree->type(id) == NOTYPE) { ; } else { _RYML_CB_ERR(m_tree->callbacks(), "unknown type"); } } #define _rymlindent_nextline() this->_indent(ilevel + 1); template void Emitter::_write_doc(id_type id) { const NodeType ty = m_tree->type(id); RYML_ASSERT(ty.is_doc()); RYML_ASSERT(!ty.has_key()); if(!m_tree->is_root(id)) { RYML_ASSERT(m_tree->is_stream(m_tree->parent(id))); this->Writer::_do_write("---"); } // if(!ty.has_val()) // this is more frequent { const bool tag = ty.has_val_tag(); const bool anchor = ty.has_val_anchor(); if(!tag && !anchor) { ; } else if(!tag && anchor) { if(!m_tree->is_root(id)) this->Writer::_do_write(' '); this->Writer::_do_write('&'); this->Writer::_do_write(m_tree->val_anchor(id)); #ifdef RYML_NO_COVERAGE__TO_BE_DELETED if(m_tree->has_children(id) && m_tree->is_root(id)) this->Writer::_do_write('\n'); #endif } else if(tag && !anchor) { if(!m_tree->is_root(id)) this->Writer::_do_write(' '); _write_tag(m_tree->val_tag(id)); #ifdef RYML_NO_COVERAGE__TO_BE_DELETED if(m_tree->has_children(id) && m_tree->is_root(id)) this->Writer::_do_write('\n'); #endif } else // tag && anchor { if(!m_tree->is_root(id)) this->Writer::_do_write(' '); _write_tag(m_tree->val_tag(id)); this->Writer::_do_write(" &"); this->Writer::_do_write(m_tree->val_anchor(id)); #ifdef RYML_NO_COVERAGE__TO_BE_DELETED if(m_tree->has_children(id) && m_tree->is_root(id)) this->Writer::_do_write('\n'); #endif } } else // docval { _RYML_CB_ASSERT(m_tree->callbacks(), ty.has_val()); // some plain scalars such as '...' and '---' must not // appear at 0-indentation const csubstr val = m_tree->val(id); const bool preceded_by_3_dashes = !m_tree->is_root(id); const type_bits style_marks = ty & VAL_STYLE; const bool is_plain = ty.is_val_plain(); const bool is_ambiguous = (is_plain || !style_marks) && ((val.begins_with("...") || val.begins_with("---")) || (val.find('\n') != npos)); if(preceded_by_3_dashes) { if(is_plain && val.len == 0 && !ty.has_val_anchor() && !ty.has_val_tag()) { this->Writer::_do_write('\n'); return; } else if(val.len && is_ambiguous) { this->Writer::_do_write('\n'); } else { this->Writer::_do_write(' '); } } id_type ilevel = 0u; if(is_ambiguous) { _rymlindent_nextline(); ++ilevel; } _writev(id, ilevel); if(val.len && m_tree->is_root(id)) this->Writer::_do_write('\n'); } if(!m_tree->is_root(id)) this->Writer::_do_write('\n'); } template void Emitter::_do_visit_flow_sl(id_type node, id_type depth, id_type ilevel) { const bool prev_flow = m_flow; m_flow = true; _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->is_stream(node)); _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(node) || m_tree->is_doc(node)); _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node))); if(C4_UNLIKELY(depth > m_opts.max_depth())) _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); if(m_tree->is_doc(node)) { _write_doc(node); #ifdef RYML_NO_COVERAGE__TO_BE_DELETED if(!m_tree->has_children(node)) return; else #endif { if(m_tree->is_map(node)) { this->Writer::_do_write('{'); } else { _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_seq(node)); this->Writer::_do_write('['); } } } else if(m_tree->is_container(node)) { _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_map(node) || m_tree->is_seq(node)); bool spc = false; // write a space if(m_tree->has_key(node)) { _writek(node, ilevel); this->Writer::_do_write(':'); spc = true; } if(m_tree->has_val_tag(node)) { if(spc) this->Writer::_do_write(' '); _write_tag(m_tree->val_tag(node)); spc = true; } if(m_tree->has_val_anchor(node)) { if(spc) this->Writer::_do_write(' '); this->Writer::_do_write('&'); this->Writer::_do_write(m_tree->val_anchor(node)); spc = true; } if(spc) this->Writer::_do_write(' '); if(m_tree->is_map(node)) { this->Writer::_do_write('{'); } else { _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_seq(node)); this->Writer::_do_write('['); } } // container for(id_type child = m_tree->first_child(node), count = 0; child != NONE; child = m_tree->next_sibling(child)) { if(count++) this->Writer::_do_write(','); if(m_tree->is_keyval(child)) { _writek(child, ilevel); this->Writer::_do_write(": "); _writev(child, ilevel); } else if(m_tree->is_val(child)) { _writev(child, ilevel); } else { // with single-line flow, we can never go back to block _do_visit_flow_sl(child, depth + 1, ilevel + 1); } } if(m_tree->is_map(node)) { this->Writer::_do_write('}'); } else if(m_tree->is_seq(node)) { this->Writer::_do_write(']'); } m_flow = prev_flow; } C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4702) // unreachable error, triggered by flow_ml not implemented template void Emitter::_do_visit_flow_ml(id_type id, id_type depth, id_type ilevel, id_type do_indent) { C4_UNUSED(id); C4_UNUSED(depth); C4_UNUSED(ilevel); C4_UNUSED(do_indent); c4::yml::error("not implemented"); #ifdef THIS_IS_A_WORK_IN_PROGRESS if(C4_UNLIKELY(depth > m_opts.max_depth())) _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); const bool prev_flow = m_flow; m_flow = true; // do it... m_flow = prev_flow; #endif } template void Emitter::_do_visit_block_container(id_type node, id_type depth, id_type level, bool do_indent) { if(m_tree->is_seq(node)) { for(id_type child = m_tree->first_child(node); child != NONE; child = m_tree->next_sibling(child)) { _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->has_key(child)); if(m_tree->is_val(child)) { _indent(level, do_indent); this->Writer::_do_write("- "); _writev(child, level); this->Writer::_do_write('\n'); } else { _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(child)); NodeType ty = m_tree->type(child); if(ty.is_flow_sl()) { _indent(level, do_indent); this->Writer::_do_write("- "); _do_visit_flow_sl(child, depth+1, 0u); this->Writer::_do_write('\n'); } else if(ty.is_flow_ml()) { _indent(level, do_indent); this->Writer::_do_write("- "); _do_visit_flow_ml(child, depth+1, 0u, do_indent); this->Writer::_do_write('\n'); } else { _do_visit_block(child, depth+1, level, do_indent); // same indentation level } } do_indent = true; } } else // map { _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_map(node)); for(id_type ich = m_tree->first_child(node); ich != NONE; ich = m_tree->next_sibling(ich)) { _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->has_key(ich)); if(m_tree->is_keyval(ich)) { _indent(level, do_indent); _writek(ich, level); this->Writer::_do_write(": "); _writev(ich, level); this->Writer::_do_write('\n'); } else { _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(ich)); NodeType ty = m_tree->type(ich); if(ty.is_flow_sl()) { _indent(level, do_indent); _do_visit_flow_sl(ich, depth+1, 0u); this->Writer::_do_write('\n'); } else if(ty.is_flow_ml()) { _indent(level, do_indent); _do_visit_flow_ml(ich, depth+1, 0u); this->Writer::_do_write('\n'); } else { _do_visit_block(ich, depth+1, level, do_indent); // same level! } } // keyval vs container do_indent = true; } // for children } // seq vs map } template void Emitter::_do_visit_block(id_type node, id_type depth, id_type ilevel, id_type do_indent) { _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->is_stream(node)); _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(node) || m_tree->is_doc(node)); _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node))); if(C4_UNLIKELY(depth > m_opts.max_depth())) _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); if(m_tree->is_doc(node)) { _write_doc(node); if(!m_tree->has_children(node)) return; } else if(m_tree->is_container(node)) { _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_map(node) || m_tree->is_seq(node)); bool spc = false; // write a space bool nl = false; // write a newline if(m_tree->has_key(node)) { _indent(ilevel, do_indent); _writek(node, ilevel); this->Writer::_do_write(':'); spc = true; } else if(!m_tree->is_root(node)) { _indent(ilevel, do_indent); this->Writer::_do_write('-'); spc = true; } if(m_tree->has_val_tag(node)) { if(spc) this->Writer::_do_write(' '); _write_tag(m_tree->val_tag(node)); spc = true; nl = true; } if(m_tree->has_val_anchor(node)) { if(spc) this->Writer::_do_write(' '); this->Writer::_do_write('&'); this->Writer::_do_write(m_tree->val_anchor(node)); spc = true; nl = true; } if(m_tree->has_children(node)) { if(m_tree->has_key(node)) nl = true; else if(!m_tree->is_root(node) && !nl) spc = true; } else { if(m_tree->is_seq(node)) this->Writer::_do_write(" []\n"); else if(m_tree->is_map(node)) this->Writer::_do_write(" {}\n"); return; } if(spc && !nl) this->Writer::_do_write(' '); do_indent = 0; if(nl) { this->Writer::_do_write('\n'); do_indent = 1; } } // container id_type next_level = ilevel + 1; if(m_tree->is_root(node) || m_tree->is_doc(node)) next_level = ilevel; // do not indent at top level _do_visit_block_container(node, depth, next_level, do_indent); } C4_SUPPRESS_WARNING_MSVC_POP template void Emitter::_do_visit_json(id_type id, id_type depth) { _RYML_CB_CHECK(m_tree->callbacks(), !m_tree->is_stream(id)); // JSON does not have streams if(C4_UNLIKELY(depth > m_opts.max_depth())) _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); if(m_tree->is_keyval(id)) { _writek_json(id); this->Writer::_do_write(": "); _writev_json(id); } else if(m_tree->is_val(id)) { _writev_json(id); } else if(m_tree->is_container(id)) { if(m_tree->has_key(id)) { _writek_json(id); this->Writer::_do_write(": "); } if(m_tree->is_seq(id)) this->Writer::_do_write('['); else if(m_tree->is_map(id)) this->Writer::_do_write('{'); } // container for(id_type ich = m_tree->first_child(id); ich != NONE; ich = m_tree->next_sibling(ich)) { if(ich != m_tree->first_child(id)) this->Writer::_do_write(','); _do_visit_json(ich, depth+1); } if(m_tree->is_seq(id)) this->Writer::_do_write(']'); else if(m_tree->is_map(id)) this->Writer::_do_write('}'); } template void Emitter::_write(NodeScalar const& C4_RESTRICT sc, NodeType flags, id_type ilevel) { if( ! sc.tag.empty()) { _write_tag(sc.tag); this->Writer::_do_write(' '); } if(flags.has_anchor()) { RYML_ASSERT(flags.is_ref() != flags.has_anchor()); RYML_ASSERT( ! sc.anchor.empty()); this->Writer::_do_write('&'); this->Writer::_do_write(sc.anchor); this->Writer::_do_write(' '); } else if(flags.is_ref()) { if(sc.anchor != "<<") this->Writer::_do_write('*'); this->Writer::_do_write(sc.anchor); if(flags.is_key_ref()) this->Writer::_do_write(' '); return; } // ensure the style flags only have one of KEY or VAL _RYML_CB_ASSERT(m_tree->callbacks(), ((flags & SCALAR_STYLE) == 0) || (((flags & KEY_STYLE) == 0) != ((flags & VAL_STYLE) == 0))); type_bits style_marks = flags & SCALAR_STYLE; if(!style_marks) style_marks = scalar_style_choose(sc.scalar); if(style_marks & (KEY_LITERAL|VAL_LITERAL)) { _write_scalar_literal(sc.scalar, ilevel, flags.has_key()); } else if(style_marks & (KEY_FOLDED|VAL_FOLDED)) { _write_scalar_folded(sc.scalar, ilevel, flags.has_key()); } else if(style_marks & (KEY_SQUO|VAL_SQUO)) { _write_scalar_squo(sc.scalar, ilevel); } else if(style_marks & (KEY_DQUO|VAL_DQUO)) { _write_scalar_dquo(sc.scalar, ilevel); } else if(style_marks & (KEY_PLAIN|VAL_PLAIN)) { if(C4_LIKELY(!(sc.scalar.begins_with(": ") || sc.scalar.begins_with(":\t")))) _write_scalar_plain(sc.scalar, ilevel); else _write_scalar_squo(sc.scalar, ilevel); } else { _RYML_CB_ERR(m_tree->callbacks(), "not implemented"); } } template void Emitter::_write_json(NodeScalar const& C4_RESTRICT sc, NodeType flags) { if(flags & (KEYTAG|VALTAG)) if(m_opts.json_error_flags() & EmitOptions::JSON_ERR_ON_TAG) _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have tags"); if(C4_UNLIKELY(flags.has_anchor())) if(m_opts.json_error_flags() & EmitOptions::JSON_ERR_ON_ANCHOR) _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have anchors"); if(sc.scalar.len) { // use double quoted style... // if it is a key (mandatory in JSON) // if the style is marked quoted bool dquoted = ((flags & (KEY|VALQUO)) || (scalar_style_json_choose(sc.scalar) & SCALAR_DQUO)); // choose the style if(dquoted) _write_scalar_json_dquo(sc.scalar); else this->Writer::_do_write(sc.scalar); } else { if(sc.scalar.str || (flags & (KEY|VALQUO|KEYTAG|VALTAG))) this->Writer::_do_write("\"\""); else this->Writer::_do_write("null"); } } template size_t Emitter::_write_escaped_newlines(csubstr s, size_t i) { RYML_ASSERT(s.len > i); RYML_ASSERT(s.str[i] == '\n'); //_c4dbgpf("nl@i={} rem=[{}]~~~{}~~~", i, s.sub(i).len, s.sub(i)); // add an extra newline for each sequence of consecutive // newline/whitespace this->Writer::_do_write('\n'); do { this->Writer::_do_write('\n'); // write the newline again ++i; // increase the outer loop counter! } while(i < s.len && s.str[i] == '\n'); _RYML_CB_ASSERT(m_tree->callbacks(), i > 0); --i; _RYML_CB_ASSERT(m_tree->callbacks(), s.str[i] == '\n'); return i; } inline bool _is_indented_block(csubstr s, size_t prev, size_t i) noexcept { if(prev == 0 && s.begins_with_any(" \t")) return true; const size_t pos = s.first_not_of('\n', i); return (pos != npos) && (s.str[pos] == ' ' || s.str[pos] == '\t'); } template size_t Emitter::_write_indented_block(csubstr s, size_t i, id_type ilevel) { //_c4dbgpf("indblock@i={} rem=[{}]~~~\n{}~~~", i, s.sub(i).len, s.sub(i)); _RYML_CB_ASSERT(m_tree->callbacks(), i > 0); _RYML_CB_ASSERT(m_tree->callbacks(), s.str[i-1] == '\n'); _RYML_CB_ASSERT(m_tree->callbacks(), i < s.len); _RYML_CB_ASSERT(m_tree->callbacks(), s.str[i] == ' ' || s.str[i] == '\t' || s.str[i] == '\n'); again: size_t pos = s.find("\n ", i); if(pos == npos) pos = s.find("\n\t", i); if(pos != npos) { ++pos; //_c4dbgpf("indblock line@i={} rem=[{}]~~~\n{}~~~", i, s.range(i, pos).len, s.range(i, pos)); _rymlindent_nextline(); this->Writer::_do_write(s.range(i, pos)); i = pos; goto again; // NOLINT } // consume the newlines after the indented block // to prevent them from being escaped pos = s.find('\n', i); if(pos != npos) { const size_t pos2 = s.first_not_of('\n', pos); pos = (pos2 != npos) ? pos2 : pos; //_c4dbgpf("indblock line@i={} rem=[{}]~~~\n{}~~~", i, s.range(i, pos).len, s.range(i, pos)); _rymlindent_nextline(); this->Writer::_do_write(s.range(i, pos)); i = pos; } return i; } template void Emitter::_write_scalar_literal(csubstr s, id_type ilevel, bool explicit_key) { _RYML_CB_ASSERT(m_tree->callbacks(), s.find("\r") == csubstr::npos); if(explicit_key) this->Writer::_do_write("? "); csubstr trimmed = s.trimr('\n'); const size_t numnewlines_at_end = s.len - trimmed.len; const bool is_newline_only = (trimmed.len == 0 && (s.len > 0)); const bool explicit_indentation = s.triml("\n\r").begins_with_any(" \t"); // this->Writer::_do_write('|'); if(explicit_indentation) this->Writer::_do_write('2'); // if(numnewlines_at_end > 1 || is_newline_only) this->Writer::_do_write('+'); else if(numnewlines_at_end == 0) this->Writer::_do_write('-'); // if(trimmed.len) { this->Writer::_do_write('\n'); size_t pos = 0; // tracks the last character that was already written for(size_t i = 0; i < trimmed.len; ++i) { if(trimmed[i] != '\n') continue; // write everything up to this point csubstr since_pos = trimmed.range(pos, i+1); // include the newline _rymlindent_nextline() this->Writer::_do_write(since_pos); pos = i+1; // already written } if(pos < trimmed.len) { _rymlindent_nextline() this->Writer::_do_write(trimmed.sub(pos)); } } for(size_t i = !is_newline_only; i < numnewlines_at_end; ++i) this->Writer::_do_write('\n'); if(explicit_key) { this->Writer::_do_write('\n'); this->_indent(ilevel); } } template void Emitter::_write_scalar_folded(csubstr s, id_type ilevel, bool explicit_key) { if(explicit_key) this->Writer::_do_write("? "); _RYML_CB_ASSERT(m_tree->callbacks(), s.find("\r") == csubstr::npos); csubstr trimmed = s.trimr('\n'); const size_t numnewlines_at_end = s.len - trimmed.len; const bool is_newline_only = (trimmed.len == 0 && (s.len > 0)); const bool explicit_indentation = s.triml("\n\r").begins_with_any(" \t"); // this->Writer::_do_write('>'); if(explicit_indentation) this->Writer::_do_write('2'); // if(numnewlines_at_end == 0) this->Writer::_do_write('-'); else if(numnewlines_at_end > 1 || is_newline_only) this->Writer::_do_write('+'); // if(trimmed.len) { this->Writer::_do_write('\n'); size_t pos = 0; // tracks the last character that was already written for(size_t i = 0; i < trimmed.len; ++i) { if(trimmed[i] != '\n') continue; // escape newline sequences if( ! _is_indented_block(s, pos, i)) { if(pos < i) { _rymlindent_nextline() this->Writer::_do_write(s.range(pos, i)); i = _write_escaped_newlines(s, i); pos = i+1; } else { if(i+1 < s.len) { if(s.str[i+1] == '\n') { ++i; i = _write_escaped_newlines(s, i); pos = i+1; } else { this->Writer::_do_write('\n'); pos = i+1; } } } } else // do not escape newlines in indented blocks { ++i; _rymlindent_nextline() this->Writer::_do_write(s.range(pos, i)); if(pos > 0 || !s.begins_with_any(" \t")) i = _write_indented_block(s, i, ilevel); pos = i; } } if(pos < trimmed.len) { _rymlindent_nextline() this->Writer::_do_write(trimmed.sub(pos)); } } for(size_t i = !is_newline_only; i < numnewlines_at_end; ++i) this->Writer::_do_write('\n'); if(explicit_key) { this->Writer::_do_write('\n'); this->_indent(ilevel); } } template void Emitter::_write_scalar_squo(csubstr s, id_type ilevel) { size_t pos = 0; // tracks the last character that was already written this->Writer::_do_write('\''); for(size_t i = 0; i < s.len; ++i) { if(s[i] == '\n') { this->Writer::_do_write(s.range(pos, i)); // write everything up to (excluding) this char //_c4dbgpf("newline at {}. writing ~~~{}~~~", i, s.range(pos, i)); i = _write_escaped_newlines(s, i); //_c4dbgpf("newline --> {}", i); if(i < s.len) _rymlindent_nextline() pos = i+1; } else if(s[i] == '\'') { csubstr sub = s.range(pos, i+1); //_c4dbgpf("squote at {}. writing ~~~{}~~~", i, sub); this->Writer::_do_write(sub); // write everything up to (including) this squote this->Writer::_do_write('\''); // write the squote again pos = i+1; } } // write missing characters at the end of the string if(pos < s.len) this->Writer::_do_write(s.sub(pos)); this->Writer::_do_write('\''); } template void Emitter::_write_scalar_dquo(csubstr s, id_type ilevel) { size_t pos = 0; // tracks the last character that was already written this->Writer::_do_write('"'); for(size_t i = 0; i < s.len; ++i) { const char curr = s.str[i]; switch(curr) // NOLINT { case '"': case '\\': { csubstr sub = s.range(pos, i); this->Writer::_do_write(sub); // write everything up to (excluding) this char this->Writer::_do_write('\\'); // write the escape this->Writer::_do_write(curr); // write the char pos = i+1; break; } #ifndef prefer_writing_newlines_as_double_newlines case '\n': { csubstr sub = s.range(pos, i); this->Writer::_do_write(sub); // write everything up to (excluding) this char this->Writer::_do_write("\\n"); // write the escape pos = i+1; (void)ilevel; break; } #else case '\n': { // write everything up to (excluding) this newline //_c4dbgpf("nl@i={} rem=[{}]~~~{}~~~", i, s.sub(i).len, s.sub(i)); this->Writer::_do_write(s.range(pos, i)); i = _write_escaped_newlines(s, i); ++i; pos = i; // as for the next line... if(i < s.len) { _rymlindent_nextline() // indent the next line // escape leading whitespace, and flush it size_t first = s.first_not_of(" \t", i); _c4dbgpf("@i={} first={} rem=[{}]~~~{}~~~", i, first, s.sub(i).len, s.sub(i)); if(first > i) { if(first == npos) first = s.len; this->Writer::_do_write('\\'); this->Writer::_do_write(s.range(i, first)); this->Writer::_do_write('\\'); i = first-1; pos = first; } } break; } // escape trailing whitespace before a newline case ' ': case '\t': { const size_t next = s.first_not_of(" \t\r", i); if(next != npos && s.str[next] == '\n') { csubstr sub = s.range(pos, i); this->Writer::_do_write(sub); // write everything up to (excluding) this char this->Writer::_do_write('\\'); // escape the whitespace pos = i; } break; } #endif case '\r': { csubstr sub = s.range(pos, i); this->Writer::_do_write(sub); // write everything up to (excluding) this char this->Writer::_do_write("\\r"); // write the escaped char pos = i+1; break; } case '\b': { csubstr sub = s.range(pos, i); this->Writer::_do_write(sub); // write everything up to (excluding) this char this->Writer::_do_write("\\b"); // write the escaped char pos = i+1; break; } } } // write missing characters at the end of the string if(pos < s.len) this->Writer::_do_write(s.sub(pos)); this->Writer::_do_write('"'); } template void Emitter::_write_scalar_plain(csubstr s, id_type ilevel) { if(C4_UNLIKELY(ilevel == 0 && (s.begins_with("...") || s.begins_with("---")))) { _rymlindent_nextline() // indent the next line ++ilevel; } size_t pos = 0; // tracks the last character that was already written for(size_t i = 0; i < s.len; ++i) { const char curr = s.str[i]; if(curr == '\n') { csubstr sub = s.range(pos, i); this->Writer::_do_write(sub); // write everything up to (including) this newline i = _write_escaped_newlines(s, i); pos = i+1; if(pos < s.len) _rymlindent_nextline() // indent the next line } } // write missing characters at the end of the string if(pos < s.len) this->Writer::_do_write(s.sub(pos)); } #undef _rymlindent_nextline template void Emitter::_write_scalar_json_dquo(csubstr s) { size_t pos = 0; this->Writer::_do_write('"'); for(size_t i = 0; i < s.len; ++i) { switch(s.str[i]) { case '"': this->Writer ::_do_write(s.range(pos, i)); this->Writer ::_do_write("\\\""); pos = i + 1; break; case '\n': this->Writer ::_do_write(s.range(pos, i)); this->Writer ::_do_write("\\n"); pos = i + 1; break; case '\t': this->Writer ::_do_write(s.range(pos, i)); this->Writer ::_do_write("\\t"); pos = i + 1; break; case '\\': this->Writer ::_do_write(s.range(pos, i)); this->Writer ::_do_write("\\\\"); pos = i + 1; break; case '\r': this->Writer ::_do_write(s.range(pos, i)); this->Writer ::_do_write("\\r"); pos = i + 1; break; case '\b': this->Writer ::_do_write(s.range(pos, i)); this->Writer ::_do_write("\\b"); pos = i + 1; break; case '\f': this->Writer ::_do_write(s.range(pos, i)); this->Writer ::_do_write("\\f"); pos = i + 1; break; } } if(pos < s.len) { csubstr sub = s.sub(pos); this->Writer::_do_write(sub); } this->Writer::_do_write('"'); } } // namespace yml } // namespace c4 #endif /* _C4_YML_EMIT_DEF_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/emit.def.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/detail/stack.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/stack.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_DETAIL_STACK_HPP_ #define _C4_YML_DETAIL_STACK_HPP_ #ifndef _C4_YML_COMMON_HPP_ //included above: //#include "../common.hpp" #endif #ifdef RYML_DBG //included above: //# include #endif //included above: //#include namespace c4 { namespace yml { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") namespace detail { /** A lightweight contiguous stack with Small Storage * Optimization. This is required because std::vector can throw * exceptions, and we don't want to enforce any particular error * mechanism. */ template class stack { static_assert(std::is_trivially_copyable::value, "T must be trivially copyable"); static_assert(std::is_trivially_destructible::value, "T must be trivially destructible"); public: enum : id_type { sso_size = N }; public: T m_buf[size_t(N)]; T *C4_RESTRICT m_stack; id_type m_size; id_type m_capacity; Callbacks m_callbacks; public: constexpr static bool is_contiguous() { return true; } stack(Callbacks const& cb) : m_buf() , m_stack(m_buf) , m_size(0) , m_capacity(N) , m_callbacks(cb) {} stack() : stack(get_callbacks()) {} ~stack() { _free(); } stack(stack const& that) RYML_NOEXCEPT : stack(that.m_callbacks) { resize(that.m_size); _cp(&that); } stack(stack &&that) noexcept : stack(that.m_callbacks) { _mv(&that); } stack& operator= (stack const& that) RYML_NOEXCEPT { if(&that != this) { _cb(that.m_callbacks); resize(that.m_size); _cp(&that); } return *this; } stack& operator= (stack &&that) noexcept { _cb(that.m_callbacks); _mv(&that); return *this; } public: id_type size() const { return m_size; } id_type empty() const { return m_size == 0; } id_type capacity() const { return m_capacity; } void clear() { m_size = 0; } void resize(id_type sz) { reserve(sz); m_size = sz; } void reserve(id_type sz); void push(T const& C4_RESTRICT n) { _RYML_CB_ASSERT(m_callbacks, (const char*)&n + sizeof(T) < (const char*)m_stack || &n > m_stack + m_capacity); if(m_size == m_capacity) { id_type cap = m_capacity == 0 ? N : 2 * m_capacity; reserve(cap); } m_stack[m_size] = n; ++m_size; } void push_top() { _RYML_CB_ASSERT(m_callbacks, m_size > 0); if(m_size == m_capacity) { id_type cap = m_capacity == 0 ? N : 2 * m_capacity; reserve(cap); } m_stack[m_size] = m_stack[m_size - 1]; ++m_size; } T const& C4_RESTRICT pop() { _RYML_CB_ASSERT(m_callbacks, m_size > 0); --m_size; return m_stack[m_size]; } C4_ALWAYS_INLINE T const& C4_RESTRICT top() const { _RYML_CB_ASSERT(m_callbacks, m_size > 0); return m_stack[m_size - 1]; } C4_ALWAYS_INLINE T & C4_RESTRICT top() { _RYML_CB_ASSERT(m_callbacks, m_size > 0); return m_stack[m_size - 1]; } C4_ALWAYS_INLINE T const& C4_RESTRICT bottom() const { _RYML_CB_ASSERT(m_callbacks, m_size > 0); return m_stack[0]; } C4_ALWAYS_INLINE T & C4_RESTRICT bottom() { _RYML_CB_ASSERT(m_callbacks, m_size > 0); return m_stack[0]; } C4_ALWAYS_INLINE T const& C4_RESTRICT top(id_type i) const { _RYML_CB_ASSERT(m_callbacks, i < m_size); return m_stack[m_size - 1 - i]; } C4_ALWAYS_INLINE T & C4_RESTRICT top(id_type i) { _RYML_CB_ASSERT(m_callbacks, i < m_size); return m_stack[m_size - 1 - i]; } C4_ALWAYS_INLINE T const& C4_RESTRICT bottom(id_type i) const { _RYML_CB_ASSERT(m_callbacks, i < m_size); return m_stack[i]; } C4_ALWAYS_INLINE T & C4_RESTRICT bottom(id_type i) { _RYML_CB_ASSERT(m_callbacks, i < m_size); return m_stack[i]; } C4_ALWAYS_INLINE T const& C4_RESTRICT operator[](id_type i) const { _RYML_CB_ASSERT(m_callbacks, i < m_size); return m_stack[i]; } C4_ALWAYS_INLINE T & C4_RESTRICT operator[](id_type i) { _RYML_CB_ASSERT(m_callbacks, i < m_size); return m_stack[i]; } public: using iterator = T *; using const_iterator = T const *; iterator begin() { return m_stack; } iterator end () { return m_stack + m_size; } const_iterator begin() const { return (const_iterator)m_stack; } const_iterator end () const { return (const_iterator)m_stack + m_size; } public: void _free(); void _cp(stack const* C4_RESTRICT that); void _mv(stack * that); void _cb(Callbacks const& cb); }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template void stack::reserve(id_type sz) { if(sz <= m_size) return; if(sz <= N) { m_stack = m_buf; m_capacity = N; return; } T *buf = (T*) m_callbacks.m_allocate((size_t)sz * sizeof(T), m_stack, m_callbacks.m_user_data); _RYML_CB_ASSERT(m_callbacks, ((uintptr_t)buf % alignof(T)) == 0u); memcpy(buf, m_stack, (size_t)m_size * sizeof(T)); if(m_stack != m_buf) { m_callbacks.m_free(m_stack, (size_t)m_capacity * sizeof(T), m_callbacks.m_user_data); } m_stack = buf; m_capacity = sz; } //----------------------------------------------------------------------------- template void stack::_free() { _RYML_CB_ASSERT(m_callbacks, m_stack != nullptr); // this structure cannot be memset() to zero if(m_stack != m_buf) { m_callbacks.m_free(m_stack, (size_t)m_capacity * sizeof(T), m_callbacks.m_user_data); m_stack = m_buf; m_size = N; m_capacity = N; } else { _RYML_CB_ASSERT(m_callbacks, m_capacity == N); } } //----------------------------------------------------------------------------- template void stack::_cp(stack const* C4_RESTRICT that) { if(that->m_stack != that->m_buf) { _RYML_CB_ASSERT(m_callbacks, that->m_capacity > N); _RYML_CB_ASSERT(m_callbacks, that->m_size <= that->m_capacity); } else { _RYML_CB_ASSERT(m_callbacks, that->m_capacity <= N); _RYML_CB_ASSERT(m_callbacks, that->m_size <= that->m_capacity); } memcpy(m_stack, that->m_stack, that->m_size * sizeof(T)); m_size = that->m_size; m_capacity = that->m_size < N ? N : that->m_size; m_callbacks = that->m_callbacks; } //----------------------------------------------------------------------------- template void stack::_mv(stack * that) { if(that->m_stack != that->m_buf) { _RYML_CB_ASSERT(m_callbacks, that->m_capacity > N); _RYML_CB_ASSERT(m_callbacks, that->m_size <= that->m_capacity); m_stack = that->m_stack; } else { _RYML_CB_ASSERT(m_callbacks, that->m_capacity <= N); _RYML_CB_ASSERT(m_callbacks, that->m_size <= that->m_capacity); memcpy(m_buf, that->m_buf, that->m_size * sizeof(T)); m_stack = m_buf; } m_size = that->m_size; m_capacity = that->m_capacity; m_callbacks = that->m_callbacks; // make sure no deallocation happens on destruction _RYML_CB_ASSERT(m_callbacks, that->m_stack != m_buf); that->m_stack = that->m_buf; that->m_capacity = N; that->m_size = 0; } //----------------------------------------------------------------------------- template void stack::_cb(Callbacks const& cb) { if(cb != m_callbacks) { _free(); m_callbacks = cb; } } } // namespace detail C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace yml } // namespace c4 #endif /* _C4_YML_DETAIL_STACK_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/detail/stack.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/filter_processor.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/filter_processor.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_FILTER_PROCESSOR_HPP_ #define _C4_YML_FILTER_PROCESSOR_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp //#include "c4/yml/common.hpp" #if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) #error "amalgamate: file c4/yml/common.hpp must have been included at this point" #endif /* C4_YML_COMMON_HPP_ */ #ifdef RYML_DBG // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/charconv.hpp //#include "c4/charconv.hpp" #if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) #error "amalgamate: file c4/charconv.hpp must have been included at this point" #endif /* C4_CHARCONV_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //#include "c4/yml/detail/parser_dbg.hpp" #if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) #error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ #endif namespace c4 { namespace yml { /** @defgroup doc_filter_processors Scalar filter processors * * These are internal classes used by @ref ParseEngine to parse the * scalars; normally there is no reason for a user to be manually * using these classes. * * @ingroup doc_parse */ /** @{ */ //----------------------------------------------------------------------------- /** Filters an input string into a different output string */ struct FilterProcessorSrcDst { csubstr src; substr dst; size_t rpos; ///< read position size_t wpos; ///< write position C4_ALWAYS_INLINE FilterProcessorSrcDst(csubstr src_, substr dst_) noexcept : src(src_) , dst(dst_) , rpos(0) , wpos(0) { RYML_ASSERT(!dst.overlaps(src)); } C4_ALWAYS_INLINE void setwpos(size_t wpos_) noexcept { wpos = wpos_; } C4_ALWAYS_INLINE void setpos(size_t rpos_, size_t wpos_) noexcept { rpos = rpos_; wpos = wpos_; } C4_ALWAYS_INLINE void set_at_end() noexcept { skip(src.len - rpos); } C4_ALWAYS_INLINE bool has_more_chars() const noexcept { return rpos < src.len; } C4_ALWAYS_INLINE bool has_more_chars(size_t maxpos) const noexcept { RYML_ASSERT(maxpos <= src.len); return rpos < maxpos; } C4_ALWAYS_INLINE csubstr rem() const noexcept { return src.sub(rpos); } C4_ALWAYS_INLINE csubstr sofar() const noexcept { return csubstr(dst.str, wpos <= dst.len ? wpos : dst.len); } C4_ALWAYS_INLINE FilterResult result() const noexcept { FilterResult ret; ret.str.str = wpos <= dst.len ? dst.str : nullptr; ret.str.len = wpos; return ret; } C4_ALWAYS_INLINE char curr() const noexcept { RYML_ASSERT(rpos < src.len); return src[rpos]; } C4_ALWAYS_INLINE char next() const noexcept { return rpos+1 < src.len ? src[rpos+1] : '\0'; } C4_ALWAYS_INLINE bool skipped_chars() const noexcept { return wpos != rpos; } C4_ALWAYS_INLINE void skip() noexcept { ++rpos; } C4_ALWAYS_INLINE void skip(size_t num) noexcept { rpos += num; } C4_ALWAYS_INLINE void set_at(size_t pos, char c) noexcept // NOLINT(readability-make-member-function-const) { RYML_ASSERT(pos < wpos); dst.str[pos] = c; } C4_ALWAYS_INLINE void set(char c) noexcept { if(wpos < dst.len) dst.str[wpos] = c; ++wpos; } C4_ALWAYS_INLINE void set(char c, size_t num) noexcept { RYML_ASSERT(num > 0); if(wpos + num <= dst.len) memset(dst.str + wpos, c, num); wpos += num; } C4_ALWAYS_INLINE void copy() noexcept { RYML_ASSERT(rpos < src.len); if(wpos < dst.len) dst.str[wpos] = src.str[rpos]; ++wpos; ++rpos; } C4_ALWAYS_INLINE void copy(size_t num) noexcept { RYML_ASSERT(num); RYML_ASSERT(rpos+num <= src.len); if(wpos + num <= dst.len) memcpy(dst.str + wpos, src.str + rpos, num); wpos += num; rpos += num; } C4_ALWAYS_INLINE void translate_esc(char c) noexcept { if(wpos < dst.len) dst.str[wpos] = c; ++wpos; rpos += 2; } C4_ALWAYS_INLINE void translate_esc_bulk(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept { RYML_ASSERT(nw > 0); RYML_ASSERT(nr > 0); RYML_ASSERT(rpos+nr <= src.len); if(wpos+nw <= dst.len) memcpy(dst.str + wpos, s, nw); wpos += nw; rpos += 1 + nr; } C4_ALWAYS_INLINE void translate_esc_extending(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept { translate_esc_bulk(s, nw, nr); } }; //----------------------------------------------------------------------------- // filter in place // debugging scaffold /** @cond dev */ #if defined(RYML_DBG) && 0 #define _c4dbgip(...) _c4dbgpf(__VA_ARGS__) #else #define _c4dbgip(...) #endif /** @endcond */ /** Filters in place. While the result may be larger than the source, * any extending happens only at the end of the string. Consequently, * it's impossible for characters to be left unfiltered. * * @see FilterProcessorInplaceMidExtending */ struct FilterProcessorInplaceEndExtending { substr src; ///< the subject string size_t wcap; ///< write capacity - the capacity of the subject string's buffer size_t rpos; ///< read position size_t wpos; ///< write position C4_ALWAYS_INLINE FilterProcessorInplaceEndExtending(substr src_, size_t wcap_) noexcept : src(src_) , wcap(wcap_) , rpos(0) , wpos(0) { RYML_ASSERT(wcap >= src.len); } C4_ALWAYS_INLINE void setwpos(size_t wpos_) noexcept { wpos = wpos_; } C4_ALWAYS_INLINE void setpos(size_t rpos_, size_t wpos_) noexcept { rpos = rpos_; wpos = wpos_; } C4_ALWAYS_INLINE void set_at_end() noexcept { skip(src.len - rpos); } C4_ALWAYS_INLINE bool has_more_chars() const noexcept { return rpos < src.len; } C4_ALWAYS_INLINE bool has_more_chars(size_t maxpos) const noexcept { RYML_ASSERT(maxpos <= src.len); return rpos < maxpos; } C4_ALWAYS_INLINE FilterResult result() const noexcept { _c4dbgip("inplace: wpos={} wcap={} small={}", wpos, wcap, wpos > rpos); FilterResult ret; ret.str.str = (wpos <= wcap) ? src.str : nullptr; ret.str.len = wpos; return ret; } C4_ALWAYS_INLINE csubstr sofar() const noexcept { return csubstr(src.str, wpos <= wcap ? wpos : wcap); } C4_ALWAYS_INLINE csubstr rem() const noexcept { return src.sub(rpos); } C4_ALWAYS_INLINE char curr() const noexcept { RYML_ASSERT(rpos < src.len); return src[rpos]; } C4_ALWAYS_INLINE char next() const noexcept { return rpos+1 < src.len ? src[rpos+1] : '\0'; } C4_ALWAYS_INLINE void skip() noexcept { ++rpos; } C4_ALWAYS_INLINE void skip(size_t num) noexcept { rpos += num; } void set_at(size_t pos, char c) noexcept { RYML_ASSERT(pos < wpos); const size_t save = wpos; wpos = pos; set(c); wpos = save; } void set(char c) noexcept { if(wpos < wcap) // respect write-capacity src.str[wpos] = c; ++wpos; } void set(char c, size_t num) noexcept { RYML_ASSERT(num); if(wpos + num <= wcap) // respect write-capacity memset(src.str + wpos, c, num); wpos += num; } void copy() noexcept { RYML_ASSERT(wpos <= rpos); RYML_ASSERT(rpos < src.len); if(wpos < wcap) // respect write-capacity src.str[wpos] = src.str[rpos]; ++rpos; ++wpos; } void copy(size_t num) noexcept { RYML_ASSERT(num); RYML_ASSERT(rpos+num <= src.len); RYML_ASSERT(wpos <= rpos); if(wpos + num <= wcap) // respect write-capacity { if(wpos + num <= rpos) // there is no overlap memcpy(src.str + wpos, src.str + rpos, num); else // there is overlap memmove(src.str + wpos, src.str + rpos, num); } rpos += num; wpos += num; } void translate_esc(char c) noexcept { RYML_ASSERT(rpos + 2 <= src.len); RYML_ASSERT(wpos <= rpos); if(wpos < wcap) // respect write-capacity src.str[wpos] = c; rpos += 2; // add 1u to account for the escape character ++wpos; } void translate_esc_bulk(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept { RYML_ASSERT(nw > 0); RYML_ASSERT(nr > 0); RYML_ASSERT(nw <= nr + 1u); RYML_ASSERT(rpos+nr <= src.len); RYML_ASSERT(wpos <= rpos); const size_t wpos_next = wpos + nw; const size_t rpos_next = rpos + nr + 1u; // add 1u to account for the escape character RYML_ASSERT(wpos_next <= rpos_next); if(wpos_next <= wcap) memcpy(src.str + wpos, s, nw); rpos = rpos_next; wpos = wpos_next; } C4_ALWAYS_INLINE void translate_esc_extending(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept { translate_esc_bulk(s, nw, nr); } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** Filters in place. The result may be larger than the source, and * extending may happen anywhere. As a result some characters may be * left unfiltered when there is no slack in the buffer and the * write-position would overlap the read-position. Consequently, it's * possible for characters to be left unfiltered. In YAML, this * happens only with double-quoted strings, and only with a small * number of escape sequences such as `\L` which is substituted by three * bytes. These escape sequences cause a call to translate_esc_extending() * which is the only entry point to this unfiltered situation. * * @see FilterProcessorInplaceMidExtending */ struct FilterProcessorInplaceMidExtending { substr src; ///< the subject string size_t wcap; ///< write capacity - the capacity of the subject string's buffer size_t rpos; ///< read position size_t wpos; ///< write position size_t maxcap; ///< the max capacity needed for filtering the string. This may be larger than the final string size. bool unfiltered_chars; ///< number of characters that were not added to wpos from lack of capacity C4_ALWAYS_INLINE FilterProcessorInplaceMidExtending(substr src_, size_t wcap_) noexcept : src(src_) , wcap(wcap_) , rpos(0) , wpos(0) , maxcap(src.len) , unfiltered_chars(false) { RYML_ASSERT(wcap >= src.len); } C4_ALWAYS_INLINE void setwpos(size_t wpos_) noexcept { wpos = wpos_; } C4_ALWAYS_INLINE void setpos(size_t rpos_, size_t wpos_) noexcept { rpos = rpos_; wpos = wpos_; } C4_ALWAYS_INLINE void set_at_end() noexcept { skip(src.len - rpos); } C4_ALWAYS_INLINE bool has_more_chars() const noexcept { return rpos < src.len; } C4_ALWAYS_INLINE bool has_more_chars(size_t maxpos) const noexcept { RYML_ASSERT(maxpos <= src.len); return rpos < maxpos; } C4_ALWAYS_INLINE FilterResultExtending result() const noexcept { _c4dbgip("inplace: wpos={} wcap={} unfiltered={} maxcap={}", this->wpos, this->wcap, this->unfiltered_chars, this->maxcap); FilterResultExtending ret; ret.str.str = (wpos <= wcap && !unfiltered_chars) ? src.str : nullptr; ret.str.len = wpos; ret.reqlen = maxcap; return ret; } C4_ALWAYS_INLINE csubstr sofar() const noexcept { return csubstr(src.str, wpos <= wcap ? wpos : wcap); } C4_ALWAYS_INLINE csubstr rem() const noexcept { return src.sub(rpos); } C4_ALWAYS_INLINE char curr() const noexcept { RYML_ASSERT(rpos < src.len); return src[rpos]; } C4_ALWAYS_INLINE char next() const noexcept { return rpos+1 < src.len ? src[rpos+1] : '\0'; } C4_ALWAYS_INLINE void skip() noexcept { ++rpos; } C4_ALWAYS_INLINE void skip(size_t num) noexcept { rpos += num; } void set_at(size_t pos, char c) noexcept { RYML_ASSERT(pos < wpos); const size_t save = wpos; wpos = pos; set(c); wpos = save; } void set(char c) noexcept { if(wpos < wcap) // respect write-capacity { if((wpos <= rpos) && !unfiltered_chars) src.str[wpos] = c; } else { _c4dbgip("inplace: add unwritten {}->{} maxcap={}->{}!", unfiltered_chars, true, maxcap, (wpos+1u > maxcap ? wpos+1u : maxcap)); unfiltered_chars = true; } ++wpos; maxcap = wpos > maxcap ? wpos : maxcap; } void set(char c, size_t num) noexcept { RYML_ASSERT(num); if(wpos + num <= wcap) // respect write-capacity { if((wpos <= rpos) && !unfiltered_chars) memset(src.str + wpos, c, num); } else { _c4dbgip("inplace: add unwritten {}->{} maxcap={}->{}!", unfiltered_chars, true, maxcap, (wpos+num > maxcap ? wpos+num : maxcap)); unfiltered_chars = true; } wpos += num; maxcap = wpos > maxcap ? wpos : maxcap; } void copy() noexcept { RYML_ASSERT(rpos < src.len); if(wpos < wcap) // respect write-capacity { if((wpos < rpos) && !unfiltered_chars) // write only if wpos is behind rpos src.str[wpos] = src.str[rpos]; } else { _c4dbgip("inplace: add unwritten {}->{} (wpos={}!=rpos={})={} (wpos={}{}!", unfiltered_chars, true, wpos, rpos, wpos!=rpos, wpos, wcap, wpos maxcap ? wpos+1u : maxcap)); unfiltered_chars = true; } ++rpos; ++wpos; maxcap = wpos > maxcap ? wpos : maxcap; } void copy(size_t num) noexcept { RYML_ASSERT(num); RYML_ASSERT(rpos+num <= src.len); if(wpos + num <= wcap) // respect write-capacity { if((wpos < rpos) && !unfiltered_chars) // write only if wpos is behind rpos { if(wpos + num <= rpos) // there is no overlap memcpy(src.str + wpos, src.str + rpos, num); else // there is overlap memmove(src.str + wpos, src.str + rpos, num); } } else { _c4dbgip("inplace: add unwritten {}->{} (wpos={}!=rpos={})={} (wpos={}{}!", unfiltered_chars, true, wpos, rpos, wpos!=rpos, wpos, wcap, wpos maxcap ? wpos : maxcap; } void translate_esc(char c) noexcept { RYML_ASSERT(rpos + 2 <= src.len); if(wpos < wcap) // respect write-capacity { if((wpos <= rpos) && !unfiltered_chars) src.str[wpos] = c; } else { _c4dbgip("inplace: add unfiltered {}->{} maxcap={}->{}!", unfiltered_chars, true, maxcap, (wpos+1u > maxcap ? wpos+1u : maxcap)); unfiltered_chars = true; } rpos += 2; ++wpos; maxcap = wpos > maxcap ? wpos : maxcap; } C4_NO_INLINE void translate_esc_bulk(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept { RYML_ASSERT(nw > 0); RYML_ASSERT(nr > 0); RYML_ASSERT(nr+1u >= nw); const size_t wpos_next = wpos + nw; const size_t rpos_next = rpos + nr + 1u; // add 1u to account for the escape character if(wpos_next <= wcap) // respect write-capacity { if((wpos <= rpos) && !unfiltered_chars) // write only if wpos is behind rpos memcpy(src.str + wpos, s, nw); } else { _c4dbgip("inplace: add unwritten {}->{} (wpos={}!=rpos={})={} (wpos={}{}!", unfiltered_chars, true, wpos, rpos, wpos!=rpos, wpos, wcap, wpos maxcap ? wpos : maxcap; } C4_NO_INLINE void translate_esc_extending(const char *C4_RESTRICT s, size_t nw, size_t nr) noexcept { RYML_ASSERT(nw > 0); RYML_ASSERT(nr > 0); RYML_ASSERT(rpos+nr <= src.len); const size_t wpos_next = wpos + nw; const size_t rpos_next = rpos + nr + 1u; // add 1u to account for the escape character if(wpos_next <= rpos_next) // read and write do not overlap. just do a vanilla copy. { if((wpos_next <= wcap) && !unfiltered_chars) memcpy(src.str + wpos, s, nw); rpos = rpos_next; wpos = wpos_next; maxcap = wpos > maxcap ? wpos : maxcap; } else // there is overlap. move the (to-be-read) string to the right. { const size_t excess = wpos_next - rpos_next; RYML_ASSERT(wpos_next > rpos_next); if(src.len + excess <= wcap) // ensure we do not go past the end { RYML_ASSERT(rpos+nr+excess <= src.len); if(wpos_next <= wcap) { if(!unfiltered_chars) { memmove(src.str + wpos_next, src.str + rpos_next, src.len - rpos_next); memcpy(src.str + wpos, s, nw); } rpos = wpos_next; // wpos, not rpos } else { rpos = rpos_next; //const size_t unw = nw > (nr + 1u) ? nw - (nr + 1u) : 0; _c4dbgip("inplace: add unfiltered {}->{} maxcap={}->{}!", unfiltered_chars, true); unfiltered_chars = true; } wpos = wpos_next; // extend the string up to capacity src.len += excess; maxcap = wpos > maxcap ? wpos : maxcap; } else { //const size_t unw = nw > (nr + 1u) ? nw - (nr + 1u) : 0; RYML_ASSERT(rpos_next <= src.len); const size_t required_size = wpos_next + (src.len - rpos_next); _c4dbgip("inplace: add unfiltered {}->{} maxcap={}->{}!", unfiltered_chars, true, maxcap, required_size > maxcap ? required_size : maxcap); RYML_ASSERT(required_size > wcap); unfiltered_chars = true; maxcap = required_size > maxcap ? required_size : maxcap; wpos = wpos_next; rpos = rpos_next; } } } }; #undef _c4dbgip /** @} */ } // namespace yml } // namespace c4 #endif /* _C4_YML_FILTER_PROCESSOR_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/filter_processor.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/parser_state.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/parser_state.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_PARSER_STATE_HPP_ #define _C4_YML_PARSER_STATE_HPP_ #ifndef _C4_YML_COMMON_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp //#include "c4/yml/common.hpp" #if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) #error "amalgamate: file c4/yml/common.hpp must have been included at this point" #endif /* C4_YML_COMMON_HPP_ */ #endif // NOLINTBEGIN(hicpp-signed-bitwise) namespace c4 { namespace yml { /** data type for @ref ParserState_e */ using ParserFlag_t = int; /** Enumeration of the state flags for the parser */ typedef enum : ParserFlag_t { RTOP = 0x01 << 0, ///< reading at top level RUNK = 0x01 << 1, ///< reading unknown state (when starting): must determine whether scalar, map or seq RMAP = 0x01 << 2, ///< reading a map RSEQ = 0x01 << 3, ///< reading a seq FLOW = 0x01 << 4, ///< reading is inside explicit flow chars: [] or {} BLCK = 0x01 << 5, ///< reading in block mode QMRK = 0x01 << 6, ///< reading an explicit key (`? key`) RKEY = 0x01 << 7, ///< reading a scalar as key RVAL = 0x01 << 9, ///< reading a scalar as val RKCL = 0x01 << 8, ///< reading the key colon (ie the : after the key in the map) RNXT = 0x01 << 10, ///< read next val or keyval SSCL = 0x01 << 11, ///< there's a stored scalar QSCL = 0x01 << 12, ///< stored scalar was quoted RSET = 0x01 << 13, ///< the (implicit) map being read is a !!set. @see https://yaml.org/type/set.html RDOC = 0x01 << 14, ///< reading a document NDOC = 0x01 << 15, ///< no document mode. a document has ended and another has not started yet. USTY = 0x01 << 16, ///< reading in unknown style mode - must determine FLOW or BLCK //! reading an implicit map nested in an explicit seq. //! eg, {key: [key2: value2, key3: value3]} //! is parsed as {key: [{key2: value2}, {key3: value3}]} RSEQIMAP = 0x01 << 17, } ParserState_e; #ifdef RYML_DBG /** @cond dev */ namespace detail { csubstr _parser_flags_to_str(substr buf, ParserFlag_t flags); } // namespace /** @endcond */ #endif /** Helper to control the line contents while parsing a buffer */ struct LineContents { substr rem; ///< the stripped line remainder; initially starts at the first non-space character size_t indentation; ///< the number of spaces on the beginning of the line substr full; ///< the full line, including newlines on the right substr stripped; ///< the stripped line, excluding newlines on the right LineContents() = default; void reset_with_next_line(substr buf, size_t offset) { RYML_ASSERT(offset <= buf.len); size_t e = offset; // get the current line stripped of newline chars while(e < buf.len && (buf.str[e] != '\n' && buf.str[e] != '\r')) ++e; RYML_ASSERT(e >= offset); const substr stripped_ = buf.range(offset, e); #if defined(__GNUC__) && __GNUC__ == 11 C4_DONT_OPTIMIZE(stripped_); #endif // advance pos to include the first line ending if(e < buf.len && buf.str[e] == '\r') ++e; if(e < buf.len && buf.str[e] == '\n') ++e; const substr full_ = buf.range(offset, e); reset(full_, stripped_); } void reset(substr full_, substr stripped_) { rem = stripped_; indentation = stripped_.first_not_of(' '); // find the first column where the character is not a space full = full_; stripped = stripped_; } C4_ALWAYS_INLINE size_t current_col() const RYML_NOEXCEPT { // WARNING: gcc x86 release builds were wrong (eg returning 0 // when the result should be 4 ) when this function was like // this: // //return current_col(rem); // // (see below for the full definition of the called overload // of current_col()) // // ... so we explicitly inline the code in here: RYML_ASSERT(rem.str >= full.str); size_t col = static_cast(rem.str - full.str); return col; // // this was happening only on builds specifically with (gcc // AND x86 AND release); no other builds were having the // problem: not in debug, not in x64, not in other // architectures, not in clang, not in visual studio. WTF!? // // Enabling debug prints with RYML_DBG made the problem go // away, so these could not be used to debug the // problem. Adding prints inside the called current_col() also // made the problem go away! WTF!??? // // a prize will be offered to anybody able to explain why this // was happening. } C4_ALWAYS_INLINE size_t current_col(csubstr s) const RYML_NOEXCEPT { RYML_ASSERT(s.str >= full.str); RYML_ASSERT(full.is_super(s)); size_t col = static_cast(s.str - full.str); return col; } }; static_assert(std::is_standard_layout::value, "LineContents not standard"); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- struct ParserState { LineContents line_contents; Location pos; ParserFlag_t flags; size_t indref; ///< the reference indentation in the current block scope id_type level; id_type node_id; ///< don't hold a pointer to the node as it will be relocated during tree resizes size_t scalar_col; // the column where the scalar (or its quotes) begin bool more_indented; bool has_children; ParserState() = default; void start_parse(const char *file, id_type node_id_) { level = 0; pos.name = to_csubstr(file); pos.offset = 0; pos.line = 1; pos.col = 1; node_id = node_id_; more_indented = false; scalar_col = 0; indref = 0; has_children = false; } void reset_after_push() { node_id = NONE; indref = npos; more_indented = false; ++level; has_children = false; } C4_ALWAYS_INLINE void reset_before_pop(ParserState const& to_pop) { pos = to_pop.pos; line_contents = to_pop.line_contents; } public: C4_ALWAYS_INLINE bool at_line_beginning() const noexcept { return line_contents.rem.str == line_contents.full.str; } C4_ALWAYS_INLINE bool indentation_eq() const noexcept { RYML_ASSERT(indref != npos); return line_contents.indentation != npos && line_contents.indentation == indref; } C4_ALWAYS_INLINE bool indentation_ge() const noexcept { RYML_ASSERT(indref != npos); return line_contents.indentation != npos && line_contents.indentation >= indref; } C4_ALWAYS_INLINE bool indentation_gt() const noexcept { RYML_ASSERT(indref != npos); return line_contents.indentation != npos && line_contents.indentation > indref; } C4_ALWAYS_INLINE bool indentation_lt() const noexcept { RYML_ASSERT(indref != npos); return line_contents.indentation != npos && line_contents.indentation < indref; } }; static_assert(std::is_standard_layout::value, "ParserState not standard"); } // namespace yml } // namespace c4 // NOLINTEND(hicpp-signed-bitwise) #endif /* _C4_YML_PARSER_STATE_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/parser_state.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/event_handler_stack.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/event_handler_stack.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_EVENT_HANDLER_STACK_HPP_ #define _C4_YML_EVENT_HANDLER_STACK_HPP_ #ifndef _C4_YML_DETAIL_STACK_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/stack.hpp //#include "c4/yml/detail/stack.hpp" #if !defined(C4_YML_DETAIL_STACK_HPP_) && !defined(_C4_YML_DETAIL_STACK_HPP_) #error "amalgamate: file c4/yml/detail/stack.hpp must have been included at this point" #endif /* C4_YML_DETAIL_STACK_HPP_ */ #endif #ifndef _C4_YML_NODE_TYPE_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node_type.hpp //#include "c4/yml/node_type.hpp" #if !defined(C4_YML_NODE_TYPE_HPP_) && !defined(_C4_YML_NODE_TYPE_HPP_) #error "amalgamate: file c4/yml/node_type.hpp must have been included at this point" #endif /* C4_YML_NODE_TYPE_HPP_ */ #endif #ifndef _C4_YML_DETAIL_PARSER_DBG_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //#include "c4/yml/detail/parser_dbg.hpp" #if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) #error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ #endif #ifndef _C4_YML_PARSER_STATE_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/parser_state.hpp //#include "c4/yml/parser_state.hpp" #if !defined(C4_YML_PARSER_STATE_HPP_) && !defined(_C4_YML_PARSER_STATE_HPP_) #error "amalgamate: file c4/yml/parser_state.hpp must have been included at this point" #endif /* C4_YML_PARSER_STATE_HPP_ */ #endif #ifdef RYML_DBG #ifndef _C4_YML_DETAIL_PRINT_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/print.hpp //#include "c4/yml/detail/print.hpp" #if !defined(C4_YML_DETAIL_PRINT_HPP_) && !defined(_C4_YML_DETAIL_PRINT_HPP_) #error "amalgamate: file c4/yml/detail/print.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PRINT_HPP_ */ #endif #endif // NOLINTBEGIN(hicpp-signed-bitwise) namespace c4 { namespace yml { /** @addtogroup doc_event_handlers * @{ */ namespace detail { using pfn_relocate_arena = void (*)(void*, csubstr prev_arena, substr next_arena); } // detail /** Use this class a base of implementations of event handler to * simplify the stack logic. */ template struct EventHandlerStack { static_assert(std::is_base_of::value, "ParserState must be a base of HandlerState"); using state = HandlerState; using pfn_relocate_arena = detail::pfn_relocate_arena; public: detail::stack m_stack; state *C4_RESTRICT m_curr; ///< current stack level: top of the stack. cached here for easier access. state *C4_RESTRICT m_parent; ///< parent of the current stack level. pfn_relocate_arena m_relocate_arena; ///< callback when the arena gets relocated void * m_relocate_arena_data; protected: EventHandlerStack() : m_stack(), m_curr(), m_parent(), m_relocate_arena(), m_relocate_arena_data() {} EventHandlerStack(Callbacks const& cb) : m_stack(cb), m_curr(), m_parent(), m_relocate_arena(), m_relocate_arena_data() {} protected: void _stack_start_parse(const char *filename, pfn_relocate_arena relocate_arena, void *relocate_arena_data) { _RYML_CB_ASSERT(m_stack.m_callbacks, m_curr != nullptr); _RYML_CB_ASSERT(m_stack.m_callbacks, relocate_arena != nullptr); _RYML_CB_ASSERT(m_stack.m_callbacks, relocate_arena_data != nullptr); m_curr->start_parse(filename, m_curr->node_id); m_relocate_arena = relocate_arena; m_relocate_arena_data = relocate_arena_data; } void _stack_finish_parse() { } protected: void _stack_reset_root() { m_stack.clear(); m_stack.push({}); m_parent = nullptr; m_curr = &m_stack.top(); } void _stack_reset_non_root() { m_stack.clear(); m_stack.push({}); // parent m_stack.push({}); // node m_parent = &m_stack.top(1); m_curr = &m_stack.top(); } void _stack_push() { m_stack.push_top(); m_parent = &m_stack.top(1); // don't use m_curr. watch out for relocations inside the prev push m_curr = &m_stack.top(); m_curr->reset_after_push(); } void _stack_pop() { _RYML_CB_ASSERT(m_stack.m_callbacks, m_parent); _RYML_CB_ASSERT(m_stack.m_callbacks, m_stack.size() > 1); m_parent->reset_before_pop(*m_curr); m_stack.pop(); m_parent = m_stack.size() > 1 ? &m_stack.top(1) : nullptr; m_curr = &m_stack.top(); #ifdef RYML_DBG if(m_parent) _c4dbgpf("popped! top is now node={} (parent={})", m_curr->node_id, m_parent->node_id); else _c4dbgpf("popped! top is now node={} @ ROOT", m_curr->node_id); #endif } protected: // undefined at the end #define _has_any_(bits) (static_cast(this)->template _has_any__()) bool _stack_should_push_on_begin_doc() const { const bool is_root = (m_stack.size() == 1u); return is_root && (_has_any_(DOC|VAL|MAP|SEQ) || m_curr->has_children); } bool _stack_should_pop_on_end_doc() const { const bool is_root = (m_stack.size() == 1u); return !is_root && _has_any_(DOC); } protected: void _stack_relocate_to_new_arena(csubstr prev, substr curr) { for(state &st : m_stack) { if(st.line_contents.rem.is_sub(prev)) st.line_contents.rem = _stack_relocate_to_new_arena(st.line_contents.rem, prev, curr); if(st.line_contents.full.is_sub(prev)) st.line_contents.full = _stack_relocate_to_new_arena(st.line_contents.full, prev, curr); if(st.line_contents.stripped.is_sub(prev)) st.line_contents.stripped = _stack_relocate_to_new_arena(st.line_contents.stripped, prev, curr); } _RYML_CB_ASSERT(m_stack.m_callbacks, m_relocate_arena != nullptr); _RYML_CB_ASSERT(m_stack.m_callbacks, m_relocate_arena_data != nullptr); m_relocate_arena(m_relocate_arena_data, prev, curr); } substr _stack_relocate_to_new_arena(csubstr s, csubstr prev, substr curr) { _RYML_CB_ASSERT(m_stack.m_callbacks, prev.is_super(s)); auto pos = s.str - prev.str; substr out = {curr.str + pos, s.len}; _RYML_CB_ASSERT(m_stack.m_callbacks, curr.is_super(out)); return out; } public: /** Check whether the current parse tokens are trailing on the * previous doc, and raise an error if they are. This function is * called by the parse engine (not the event handler) before a doc * is started. */ void check_trailing_doc_token() const { const bool is_root = (m_stack.size() == 1u); const bool isndoc = (m_curr->flags & NDOC) != 0; const bool suspicious = _has_any_(MAP|SEQ|VAL); _c4dbgpf("target={} isroot={} suspicious={} ndoc={}", m_curr->node_id, is_root, suspicious, isndoc); if((is_root || _has_any_(DOC)) && suspicious && !isndoc) _RYML_CB_ERR_(m_stack.m_callbacks, "parse error", m_curr->pos); } protected: #undef _has_any_ }; /** @} */ } // namespace yml } // namespace c4 // NOLINTEND(hicpp-signed-bitwise) #endif /* _C4_YML_EVENT_HANDLER_STACK_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/event_handler_stack.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/event_handler_tree.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/event_handler_tree.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_EVENT_HANDLER_TREE_HPP_ #define _C4_YML_EVENT_HANDLER_TREE_HPP_ #ifndef _C4_YML_TREE_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp //#include "c4/yml/tree.hpp" #if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) #error "amalgamate: file c4/yml/tree.hpp must have been included at this point" #endif /* C4_YML_TREE_HPP_ */ #endif #ifndef _C4_YML_EVENT_HANDLER_STACK_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/event_handler_stack.hpp //#include "c4/yml/event_handler_stack.hpp" #if !defined(C4_YML_EVENT_HANDLER_STACK_HPP_) && !defined(_C4_YML_EVENT_HANDLER_STACK_HPP_) #error "amalgamate: file c4/yml/event_handler_stack.hpp must have been included at this point" #endif /* C4_YML_EVENT_HANDLER_STACK_HPP_ */ #endif C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4702) // unreachable code // NOLINTBEGIN(hicpp-signed-bitwise) namespace c4 { namespace yml { /** @addtogroup doc_event_handlers * @{ */ /** The stack state needed specifically by @ref EventHandlerTree */ struct EventHandlerTreeState : public ParserState { NodeData *tr_data; }; /** The event handler to create a ryml @ref Tree. See the * documentation for @ref doc_event_handlers, which has important * notes about the event model used by rapidyaml. */ struct EventHandlerTree : public EventHandlerStack { /** @name types * @{ */ using state = EventHandlerTreeState; /** @} */ public: /** @cond dev */ Tree *C4_RESTRICT m_tree; id_type m_id; size_t m_num_directives; bool m_yaml_directive; #ifdef RYML_DBG #define _enable_(bits) _enable__(); _c4dbgpf("node[{}]: enable {}", m_curr->node_id, #bits) #define _disable_(bits) _disable__(); _c4dbgpf("node[{}]: disable {}", m_curr->node_id, #bits) #else #define _enable_(bits) _enable__() #define _disable_(bits) _disable__() #endif #define _has_any_(bits) _has_any__() /** @endcond */ public: /** @name construction and resetting * @{ */ EventHandlerTree() : EventHandlerStack(), m_tree(), m_id(NONE), m_num_directives(), m_yaml_directive() {} EventHandlerTree(Callbacks const& cb) : EventHandlerStack(cb), m_tree(), m_id(NONE), m_num_directives(), m_yaml_directive() {} EventHandlerTree(Tree *tree, id_type id) : EventHandlerStack(tree->callbacks()), m_tree(tree), m_id(id), m_num_directives(), m_yaml_directive() { reset(tree, id); } void reset(Tree *tree, id_type id) { if(C4_UNLIKELY(!tree)) _RYML_CB_ERR(m_stack.m_callbacks, "null tree"); if(C4_UNLIKELY(id >= tree->capacity())) _RYML_CB_ERR(tree->callbacks(), "invalid node"); if(C4_UNLIKELY(!tree->is_root(id))) if(C4_UNLIKELY(tree->is_map(tree->parent(id)))) if(C4_UNLIKELY(!tree->has_key(id))) _RYML_CB_ERR(tree->callbacks(), "destination node belongs to a map and has no key"); m_tree = tree; m_id = id; if(m_tree->is_root(id)) { _stack_reset_root(); _reset_parser_state(m_curr, id, m_tree->root_id()); } else { _stack_reset_non_root(); _reset_parser_state(m_parent, id, m_tree->parent(id)); _reset_parser_state(m_curr, id, id); } m_num_directives = 0; m_yaml_directive = false; } /** @} */ public: /** @name parse events * @{ */ void start_parse(const char* filename, detail::pfn_relocate_arena relocate_arena, void *relocate_arena_data) { _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree != nullptr); this->_stack_start_parse(filename, relocate_arena, relocate_arena_data); } void finish_parse() { _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree != nullptr); if(m_num_directives && !m_tree->is_stream(m_tree->root_id())) _RYML_CB_ERR_(m_stack.m_callbacks, "directives cannot be used without a document", {}); this->_stack_finish_parse(); /* This pointer is temporary. Remember that: * * - this handler object may be held by the user * - it may be used with a temporary tree inside the parse function * - when the parse function returns the temporary tree, its address * will change * * As a result, the user could try to read the tree from m_tree, and * end up reading the stale temporary object. * * So it is better to clear it here; then the user will get an obvious * segfault if reading from m_tree. */ m_tree = nullptr; } void cancel_parse() { m_tree = nullptr; } /** @} */ public: /** @name YAML stream events */ /** @{ */ C4_ALWAYS_INLINE void begin_stream() const noexcept { /*nothing to do*/ } C4_ALWAYS_INLINE void end_stream() const noexcept { /*nothing to do*/ } /** @} */ public: /** @name YAML document events */ /** @{ */ /** implicit doc start (without ---) */ void begin_doc() { _c4dbgp("begin_doc"); if(_stack_should_push_on_begin_doc()) { _c4dbgp("push!"); _set_root_as_stream(); _push(); _enable_(DOC); } } /** implicit doc end (without ...) */ void end_doc() { _c4dbgp("end_doc"); if(_stack_should_pop_on_end_doc()) { _remove_speculative(); _c4dbgp("pop!"); _pop(); } } /** explicit doc start, with --- */ void begin_doc_expl() { _c4dbgp("begin_doc_expl"); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->root_id() == m_curr->node_id); if(!m_tree->is_stream(m_tree->root_id())) //if(_should_push_on_begin_doc()) { _c4dbgp("ensure stream"); _set_root_as_stream(); id_type first = m_tree->first_child(m_tree->root_id()); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_stream(m_tree->root_id())); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->num_children(m_tree->root_id()) == 1u); if(m_tree->has_children(first) || m_tree->is_val(first)) { _c4dbgp("push!"); _push(); } else { _c4dbgp("tweak"); _push(); _remove_speculative(); m_curr->node_id = m_tree->last_child(m_tree->root_id()); m_curr->tr_data = m_tree->_p(m_curr->node_id); } } else { _c4dbgp("push!"); _push(); } _enable_(DOC); } /** explicit doc end, with ... */ void end_doc_expl() { _c4dbgp("end_doc_expl"); _remove_speculative(); if(_stack_should_pop_on_end_doc()) { _c4dbgp("pop!"); _pop(); } m_yaml_directive = false; } /** @} */ public: /** @name YAML map events */ /** @{ */ void begin_map_key_flow() { _RYML_CB_ERR_(m_stack.m_callbacks, "ryml trees cannot handle containers as keys", m_curr->pos); } void begin_map_key_block() { _RYML_CB_ERR_(m_stack.m_callbacks, "ryml trees cannot handle containers as keys", m_curr->pos); } void begin_map_val_flow() { _c4dbgpf("node[{}]: begin_map_val_flow", m_curr->node_id); _RYML_CB_CHECK(m_stack.m_callbacks, !_has_any_(VAL)); _enable_(MAP|FLOW_SL); _save_loc(); _push(); } void begin_map_val_block() { _c4dbgpf("node[{}]: begin_map_val_block", m_curr->node_id); _RYML_CB_CHECK(m_stack.m_callbacks, !_has_any_(VAL)); _enable_(MAP|BLOCK); _save_loc(); _push(); } void end_map() { _pop(); _c4dbgpf("node[{}]: end_map_val", m_curr->node_id); } /** @} */ public: /** @name YAML seq events */ /** @{ */ void begin_seq_key_flow() { _RYML_CB_ERR_(m_stack.m_callbacks, "ryml trees cannot handle containers as keys", m_curr->pos); } void begin_seq_key_block() { _RYML_CB_ERR_(m_stack.m_callbacks, "ryml trees cannot handle containers as keys", m_curr->pos); } void begin_seq_val_flow() { _c4dbgpf("node[{}]: begin_seq_val_flow", m_curr->node_id); _RYML_CB_CHECK(m_stack.m_callbacks, !_has_any_(VAL)); _enable_(SEQ|FLOW_SL); _save_loc(); _push(); } void begin_seq_val_block() { _c4dbgpf("node[{}]: begin_seq_val_block", m_curr->node_id); _RYML_CB_CHECK(m_stack.m_callbacks, !_has_any_(VAL)); _enable_(SEQ|BLOCK); _save_loc(); _push(); } void end_seq() { _pop(); _c4dbgpf("node[{}]: end_seq_val", m_curr->node_id); } /** @} */ public: /** @name YAML structure events */ /** @{ */ void add_sibling() { _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); _RYML_CB_ASSERT(m_stack.m_callbacks, m_parent); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->has_children(m_parent->node_id)); NodeData const* prev = m_tree->m_buf; // watchout against relocation of the tree nodes _set_state_(m_curr, m_tree->_append_child__unprotected(m_parent->node_id)); if(prev != m_tree->m_buf) _refresh_after_relocation(); _c4dbgpf("node[{}]: added sibling={} prev={}", m_parent->node_id, m_curr->node_id, m_tree->prev_sibling(m_curr->node_id)); } /** set the previous val as the first key of a new map, with flow style. * * See the documentation for @ref doc_event_handlers, which has * important notes about this event. */ void actually_val_is_first_key_of_new_map_flow() { if(C4_UNLIKELY(m_tree->is_container(m_curr->node_id))) _RYML_CB_ERR_(m_stack.m_callbacks, "ryml trees cannot handle containers as keys", m_curr->pos); _RYML_CB_ASSERT(m_stack.m_callbacks, m_parent); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_seq(m_parent->node_id)); _RYML_CB_ASSERT(m_stack.m_callbacks, !m_tree->is_container(m_curr->node_id)); _RYML_CB_ASSERT(m_stack.m_callbacks, !m_tree->has_key(m_curr->node_id)); const NodeData tmp = _val2key_(*m_curr->tr_data); _disable_(_VALMASK|VAL_STYLE); m_curr->tr_data->m_val = {}; begin_map_val_flow(); m_curr->tr_data->m_type = tmp.m_type; m_curr->tr_data->m_key = tmp.m_key; } /** like its flow counterpart, but this function can only be * called after the end of a flow-val at root or doc level. * * See the documentation for @ref doc_event_handlers, which has * important notes about this event. */ void actually_val_is_first_key_of_new_map_block() { _RYML_CB_ERR_(m_stack.m_callbacks, "ryml trees cannot handle containers as keys", m_curr->pos); } /** @} */ public: /** @name YAML scalar events */ /** @{ */ C4_ALWAYS_INLINE void set_key_scalar_plain_empty() noexcept { _c4dbgpf("node[{}]: set key scalar plain as empty", m_curr->node_id); m_curr->tr_data->m_key.scalar = {}; _enable_(KEY|KEY_PLAIN|KEYNIL); } C4_ALWAYS_INLINE void set_val_scalar_plain_empty() noexcept { _c4dbgpf("node[{}]: set val scalar plain as empty", m_curr->node_id); m_curr->tr_data->m_val.scalar = {}; _enable_(VAL|VAL_PLAIN|VALNIL); } C4_ALWAYS_INLINE void set_key_scalar_plain(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set key scalar plain: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_key.scalar = scalar; _enable_(KEY|KEY_PLAIN); } C4_ALWAYS_INLINE void set_val_scalar_plain(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set val scalar plain: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_val.scalar = scalar; _enable_(VAL|VAL_PLAIN); } C4_ALWAYS_INLINE void set_key_scalar_dquoted(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set key scalar dquot: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_key.scalar = scalar; _enable_(KEY|KEY_DQUO); } C4_ALWAYS_INLINE void set_val_scalar_dquoted(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set val scalar dquot: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_val.scalar = scalar; _enable_(VAL|VAL_DQUO); } C4_ALWAYS_INLINE void set_key_scalar_squoted(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set key scalar squot: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_key.scalar = scalar; _enable_(KEY|KEY_SQUO); } C4_ALWAYS_INLINE void set_val_scalar_squoted(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set val scalar squot: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_val.scalar = scalar; _enable_(VAL|VAL_SQUO); } C4_ALWAYS_INLINE void set_key_scalar_literal(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set key scalar literal: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_key.scalar = scalar; _enable_(KEY|KEY_LITERAL); } C4_ALWAYS_INLINE void set_val_scalar_literal(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set val scalar literal: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_val.scalar = scalar; _enable_(VAL|VAL_LITERAL); } C4_ALWAYS_INLINE void set_key_scalar_folded(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set key scalar folded: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_key.scalar = scalar; _enable_(KEY|KEY_FOLDED); } C4_ALWAYS_INLINE void set_val_scalar_folded(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set val scalar folded: [{}]~~~{}~~~", m_curr->node_id, scalar.len, scalar); m_curr->tr_data->m_val.scalar = scalar; _enable_(VAL|VAL_FOLDED); } C4_ALWAYS_INLINE void mark_key_scalar_unfiltered() noexcept { _enable_(KEY_UNFILT); } C4_ALWAYS_INLINE void mark_val_scalar_unfiltered() noexcept { _enable_(VAL_UNFILT); } /** @} */ public: /** @name YAML anchor/reference events */ /** @{ */ void set_key_anchor(csubstr anchor) { _c4dbgpf("node[{}]: set key anchor: [{}]~~~{}~~~", m_curr->node_id, anchor.len, anchor); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); if(C4_UNLIKELY(_has_any_(KEYREF))) _RYML_CB_ERR_(m_tree->callbacks(), "key cannot have both anchor and ref", m_curr->pos); _RYML_CB_ASSERT(m_tree->callbacks(), !anchor.begins_with('&')); _enable_(KEYANCH); m_curr->tr_data->m_key.anchor = anchor; } void set_val_anchor(csubstr anchor) { _c4dbgpf("node[{}]: set val anchor: [{}]~~~{}~~~", m_curr->node_id, anchor.len, anchor); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); if(C4_UNLIKELY(_has_any_(VALREF))) _RYML_CB_ERR_(m_tree->callbacks(), "val cannot have both anchor and ref", m_curr->pos); _RYML_CB_ASSERT(m_tree->callbacks(), !anchor.begins_with('&')); _enable_(VALANCH); m_curr->tr_data->m_val.anchor = anchor; } void set_key_ref(csubstr ref) { _c4dbgpf("node[{}]: set key ref: [{}]~~~{}~~~", m_curr->node_id, ref.len, ref); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); if(C4_UNLIKELY(_has_any_(KEYANCH))) _RYML_CB_ERR_(m_tree->callbacks(), "key cannot have both anchor and ref", m_curr->pos); _RYML_CB_ASSERT(m_tree->callbacks(), ref.begins_with('*')); _enable_(KEY|KEYREF); m_curr->tr_data->m_key.anchor = ref.sub(1); m_curr->tr_data->m_key.scalar = ref; } void set_val_ref(csubstr ref) { _c4dbgpf("node[{}]: set val ref: [{}]~~~{}~~~", m_curr->node_id, ref.len, ref); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); if(C4_UNLIKELY(_has_any_(VALANCH))) _RYML_CB_ERR_(m_tree->callbacks(), "val cannot have both anchor and ref", m_curr->pos); _RYML_CB_ASSERT(m_tree->callbacks(), ref.begins_with('*')); _enable_(VAL|VALREF); m_curr->tr_data->m_val.anchor = ref.sub(1); m_curr->tr_data->m_val.scalar = ref; } /** @} */ public: /** @name YAML tag events */ /** @{ */ void set_key_tag(csubstr tag) noexcept { _c4dbgpf("node[{}]: set key tag: [{}]~~~{}~~~", m_curr->node_id, tag.len, tag); _enable_(KEYTAG); m_curr->tr_data->m_key.tag = tag; } void set_val_tag(csubstr tag) noexcept { _c4dbgpf("node[{}]: set val tag: [{}]~~~{}~~~", m_curr->node_id, tag.len, tag); _enable_(VALTAG); m_curr->tr_data->m_val.tag = tag; } /** @} */ public: /** @name YAML directive events */ /** @{ */ C4_NO_INLINE void add_directive(csubstr directive) { _c4dbgpf("% directive! {}", directive); _RYML_CB_ASSERT(m_tree->callbacks(), directive.begins_with('%')); if(directive.begins_with("%TAG")) { if(C4_UNLIKELY(!m_tree->add_tag_directive(directive))) _RYML_CB_ERR_(m_stack.m_callbacks, "failed to add directive", m_curr->pos); } else if(directive.begins_with("%YAML")) { _c4dbgpf("%YAML directive! ignoring...: {}", directive); if(C4_UNLIKELY(m_yaml_directive)) _RYML_CB_ERR_(m_stack.m_callbacks, "multiple yaml directives", m_curr->pos); m_yaml_directive = true; } else { _c4dbgpf("unknown directive! ignoring... {}", directive); } ++m_num_directives; } /** @} */ public: /** @name arena functions */ /** @{ */ substr alloc_arena(size_t len) { _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); csubstr prev = m_tree->arena(); substr out = m_tree->alloc_arena(len); substr curr = m_tree->arena(); if(curr.str != prev.str) _stack_relocate_to_new_arena(prev, curr); return out; } substr alloc_arena(size_t len, substr *relocated) { _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); csubstr prev = m_tree->arena(); if(!prev.is_super(*relocated)) return alloc_arena(len); substr out = alloc_arena(len); substr curr = m_tree->arena(); if(curr.str != prev.str) *relocated = _stack_relocate_to_new_arena(*relocated, prev, curr); return out; } /** @} */ public: /** @cond dev */ void _reset_parser_state(state* st, id_type parse_root, id_type node) { _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); _set_state_(st, node); const NodeType type = m_tree->type(node); #ifdef RYML_DBG char flagbuf[80]; _c4dbgpf("resetting state: initial flags={}", detail::_parser_flags_to_str(flagbuf, st->flags)); #endif if(type == NOTYPE) { _c4dbgpf("node[{}] is notype", node); if(m_tree->is_root(parse_root)) { _c4dbgpf("node[{}] is root", node); st->flags |= RUNK|RTOP; } else { _c4dbgpf("node[{}] is not root. setting USTY", node); st->flags |= USTY; } } else if(type.is_map()) { _c4dbgpf("node[{}] is map", node); st->flags |= RMAP|USTY; } else if(type.is_seq()) { _c4dbgpf("node[{}] is map", node); st->flags |= RSEQ|USTY; } else if(type.has_key()) { _c4dbgpf("node[{}] has key. setting USTY", node); st->flags |= USTY; } else { _RYML_CB_ERR(m_tree->callbacks(), "cannot append to node"); } if(type.is_doc()) { _c4dbgpf("node[{}] is doc", node); st->flags |= RDOC; } #ifdef RYML_DBG _c4dbgpf("resetting state: final flags={}", detail::_parser_flags_to_str(flagbuf, st->flags)); #endif } /** push a new parent, add a child to the new parent, and set the * child as the current node */ void _push() { _stack_push(); NodeData const* prev = m_tree->m_buf; // watch out against relocation of the tree nodes m_curr->node_id = m_tree->_append_child__unprotected(m_parent->node_id); m_curr->tr_data = m_tree->_p(m_curr->node_id); if(prev != m_tree->m_buf) _refresh_after_relocation(); _c4dbgpf("pushed! level={}. top is now node={} (parent={})", m_curr->level, m_curr->node_id, m_parent ? m_parent->node_id : NONE); } /** end the current scope */ void _pop() { _remove_speculative_with_parent(); _stack_pop(); } public: template C4_HOT C4_ALWAYS_INLINE void _enable__() noexcept { m_curr->tr_data->m_type.type = static_cast(m_curr->tr_data->m_type.type | bits); } template C4_HOT C4_ALWAYS_INLINE void _disable__() noexcept { m_curr->tr_data->m_type.type = static_cast(m_curr->tr_data->m_type.type & (~bits)); } template C4_HOT C4_ALWAYS_INLINE bool _has_any__() const noexcept { return (m_curr->tr_data->m_type.type & bits) != 0; } public: C4_ALWAYS_INLINE void _set_state_(state *C4_RESTRICT s, id_type id) const noexcept { s->node_id = id; s->tr_data = m_tree->_p(id); } void _refresh_after_relocation() { _c4dbgp("tree: refreshing stack data after tree data relocation"); for(auto &st : m_stack) st.tr_data = m_tree->_p(st.node_id); } void _set_root_as_stream() { _c4dbgp("set root as stream"); _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->root_id() == 0u); _RYML_CB_ASSERT(m_tree->callbacks(), m_curr->node_id == 0u); const bool hack = !m_tree->has_children(m_curr->node_id) && !m_tree->is_val(m_curr->node_id); if(hack) m_tree->_p(m_tree->root_id())->m_type.add(VAL); m_tree->set_root_as_stream(); _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_stream(m_tree->root_id())); _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->has_children(m_tree->root_id())); _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_doc(m_tree->first_child(m_tree->root_id()))); if(hack) m_tree->_p(m_tree->first_child(m_tree->root_id()))->m_type.rem(VAL); _set_state_(m_curr, m_tree->root_id()); } static NodeData _val2key_(NodeData const& C4_RESTRICT d) noexcept { NodeData r = d; r.m_key = d.m_val; r.m_val = {}; r.m_type = d.m_type; static_assert((_VALMASK >> 1u) == _KEYMASK, "required for this function to work"); static_assert((VAL_STYLE >> 1u) == KEY_STYLE, "required for this function to work"); r.m_type.type = ((d.m_type.type & (_VALMASK|VAL_STYLE)) >> 1u); r.m_type.type = (r.m_type.type & ~(_VALMASK|VAL_STYLE)); r.m_type.type = (r.m_type.type | KEY); return r; } void _remove_speculative() { _c4dbgp("remove speculative node"); _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->empty()); const id_type last_added = m_tree->size() - 1; if(m_tree->has_parent(last_added)) if(m_tree->_p(last_added)->m_type == NOTYPE) m_tree->remove(last_added); } void _remove_speculative_with_parent() { _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->empty()); const id_type last_added = m_tree->size() - 1; _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->has_parent(last_added)); if(m_tree->_p(last_added)->m_type == NOTYPE) { _c4dbgpf("remove speculative node with parent. parent={} node={} parent(node)={}", m_parent->node_id, last_added, m_tree->parent(last_added)); m_tree->remove(last_added); } } C4_ALWAYS_INLINE void _save_loc() { _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree); _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->_p(m_curr->node_id)->m_val.scalar.len == 0); m_tree->_p(m_curr->node_id)->m_val.scalar.str = m_curr->line_contents.rem.str; } #undef _enable_ #undef _disable_ #undef _has_any_ /** @endcond */ }; /** @} */ } // namespace yml } // namespace c4 // NOLINTEND(hicpp-signed-bitwise) C4_SUPPRESS_WARNING_MSVC_POP #endif /* _C4_YML_EVENT_HANDLER_TREE_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/event_handler_tree.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/parse_engine.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/parse_engine.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_PARSE_ENGINE_HPP_ #define _C4_YML_PARSE_ENGINE_HPP_ #ifndef _C4_YML_DETAIL_PARSER_DBG_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //#include "c4/yml/detail/parser_dbg.hpp" #if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) #error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ #endif #ifndef _C4_YML_PARSER_STATE_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/parser_state.hpp //#include "c4/yml/parser_state.hpp" #if !defined(C4_YML_PARSER_STATE_HPP_) && !defined(_C4_YML_PARSER_STATE_HPP_) #error "amalgamate: file c4/yml/parser_state.hpp must have been included at this point" #endif /* C4_YML_PARSER_STATE_HPP_ */ #endif #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable: 4251/*needs to have dll-interface to be used by clients of struct*/) #endif // NOLINTBEGIN(hicpp-signed-bitwise) namespace c4 { namespace yml { /** @addtogroup doc_parse * @{ */ /** @defgroup doc_event_handlers Event Handlers * * @brief rapidyaml implements its parsing logic with a two-level * model, where a @ref ParseEngine object reads through the YAML * source, and dispatches events to an EventHandler bound to the @ref * ParseEngine. Because @ref ParseEngine is templated on the event * handler, the binding uses static polymorphism, without any virtual * functions. The actual handler object can be changed at run time, * (but of course needs to be the type of the template parameter). * This is thus a very efficient architecture, and further enables the * user to provide his own custom handler if he wishes to bypass the * rapidyaml @ref Tree. * * There are two handlers implemented in this project: * * - @ref EventHandlerTree is the handler responsible for creating the * ryml @ref Tree * * - @ref EventHandlerYamlStd is the handler responsible for emitting * standardized [YAML test suite * events](https://github.com/yaml/yaml-test-suite), used (only) in * the CI of this project. * * * ### Event model * * The event model used by the parse engine and event handlers follows * very closely the event model in the [YAML test * suite](https://github.com/yaml/yaml-test-suite). * * Consider for example this YAML, * ```yaml * {foo: bar,foo2: bar2} * ``` * which would produce these events in the test-suite parlance: * ``` * +STR * +DOC * +MAP {} * =VAL :foo * =VAL :bar * =VAL :foo2 * =VAL :bar2 * -MAP * -DOC * -STR * ``` * * For reference, the @ref ParseEngine object will produce this * sequence of calls to its bound EventHandler: * ```cpp * handler.begin_stream(); * handler.begin_doc(); * handler.begin_map_val_flow(); * handler.set_key_scalar_plain("foo"); * handler.set_val_scalar_plain("bar"); * handler.add_sibling(); * handler.set_key_scalar_plain("foo2"); * handler.set_val_scalar_plain("bar2"); * handler.end_map(); * handler.end_doc(); * handler.end_stream(); * ``` * * For many other examples of all areas of YAML and how ryml's parse * model corresponds to the YAML standard model, refer to the [unit * tests for the parse * engine](https://github.com/biojppm/rapidyaml/tree/master/test/test_parse_engine.cpp). * * * ### Special events * * Most of the parsing events adopted by rapidyaml in its event model * are fairly obvious, but there are two less-obvious events requiring * some explanation. * * These events exist to make it easier to parse some special YAML * cases. They are called by the parser when a just-handled * value/container is actually the first key of a new map: * * - `actually_val_is_first_key_of_new_map_flow()` (@ref EventHandlerTree::actually_val_is_first_key_of_new_map_flow() "see implementation in EventHandlerTree" / @ref EventHandlerYamlStd::actually_val_is_first_key_of_new_map_flow() "see implementation in EventHandlerYamlStd") * - `actually_val_is_first_key_of_new_map_block()` (@ref EventHandlerTree::actually_val_is_first_key_of_new_map_block() "see implementation in EventHandlerTree" / @ref EventHandlerYamlStd::actually_val_is_first_key_of_new_map_block() "see implementation in EventHandlerYamlStd") * * For example, consider an implicit map inside a seq: `[a: b, c: * d]` which is parsed as `[{a: b}, {c: d}]`. The standard event * sequence for this YAML would be the following: * ```cpp * handler.begin_seq_val_flow(); * handler.begin_map_val_flow(); * handler.set_key_scalar_plain("a"); * handler.set_val_scalar_plain("b"); * handler.end_map(); * handler.add_sibling(); * handler.begin_map_val_flow(); * handler.set_key_scalar_plain("c"); * handler.set_val_scalar_plain("d"); * handler.end_map(); * handler.end_seq(); * ``` * The problem with this event sequence is that it forces the * parser to delay setting the val scalar (in this case "a" and * "c") until it knows whether the scalar is a key or a val. This * would require the parser to store the scalar until this * time. For instance, in the example above, the parser should * delay setting "a" and "c", because they are in fact keys and * not vals. Until then, the parser would have to store "a" and * "c" in its internal state. The downside is that this complexity * cost would apply even if there is no implicit map -- every val * in a seq would have to be delayed until one of the * disambiguating subsequent tokens `,-]:` is found. * By calling this function, the parser can avoid this complexity, * by preemptively setting the scalar as a val. Then a call to * this function will create the map and rearrange the scalar as * key. Now the cost applies only once: when a seqimap starts. So * the following (easier and cheaper) event sequence below has the * same effect as the event sequence above: * ```cpp * handler.begin_seq_val_flow(); * handler.set_val_scalar_plain("notmap"); * handler.set_val_scalar_plain("a"); // preemptively set "a" as val! * handler.actually_as_new_map_key(); // create a map, move the "a" val as the key of the first child of the new map * handler.set_val_scalar_plain("b"); // now "a" is a key and "b" the val * handler.end_map(); * handler.set_val_scalar_plain("c"); // "c" also as val! * handler.actually_as_block_flow(); // likewise * handler.set_val_scalar_plain("d"); // now "c" is a key and "b" the val * handler.end_map(); * handler.end_seq(); * ``` * This also applies to container keys (although ryml's tree * cannot accomodate these): the parser can preemptively set a * container as a val, and call this event to turn that container * into a key. For example, consider this yaml: * ```yaml * [aa, bb]: [cc, dd] * # ^ ^ ^ * # | | | * # (2) (1) (3) <- event sequence * ``` * The standard event sequence for this YAML would be the * following: * ```cpp * handler.begin_map_val_block(); // (1) * handler.begin_seq_key_flow(); // (2) * handler.set_val_scalar_plain("aa"); * handler.add_sibling(); * handler.set_val_scalar_plain("bb"); * handler.end_seq(); * handler.begin_seq_val_flow(); // (3) * handler.set_val_scalar_plain("cc"); * handler.add_sibling(); * handler.set_val_scalar_plain("dd"); * handler.end_seq(); * handler.end_map(); * ``` * The problem with the sequence above is that, reading from * left-to-right, the parser can only detect the proper calls at * (1) and (2) once it reaches (1) in the YAML source. So, the * parser would have to buffer the entire event sequence starting * from the beginning until it reaches (1). Using this function, * the parser can do instead: * ```cpp * handler.begin_seq_val_flow(); // (2) -- preemptively as val! * handler.set_val_scalar_plain("aa"); * handler.add_sibling(); * handler.set_val_scalar_plain("bb"); * handler.end_seq(); * handler.actually_as_new_map_key(); // (1) -- adjust when finding that the prev val was actually a key. * handler.begin_seq_val_flow(); // (3) -- go on as before * handler.set_val_scalar_plain("cc"); * handler.add_sibling(); * handler.set_val_scalar_plain("dd"); * handler.end_seq(); * handler.end_map(); * ``` */ class Tree; class NodeRef; class ConstNodeRef; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** Options to give to the parser to control its behavior. */ struct RYML_EXPORT ParserOptions { private: typedef enum : uint32_t { SCALAR_FILTERING = (1u << 0u), LOCATIONS = (1u << 1u), DEFAULTS = SCALAR_FILTERING, } Flags_e; uint32_t flags = DEFAULTS; public: ParserOptions() = default; public: /** @name source location tracking */ /** @{ */ /** enable/disable source location tracking */ ParserOptions& locations(bool enabled) noexcept { if(enabled) flags |= LOCATIONS; else flags &= ~LOCATIONS; return *this; } /** query source location tracking status */ C4_ALWAYS_INLINE bool locations() const noexcept { return (flags & LOCATIONS); } /** @} */ public: /** @name scalar filtering status (experimental; disable at your discretion) */ /** @{ */ /** enable/disable scalar filtering while parsing */ ParserOptions& scalar_filtering(bool enabled) noexcept { if(enabled) flags |= SCALAR_FILTERING; else flags &= ~SCALAR_FILTERING; return *this; } /** query scalar filtering status */ C4_ALWAYS_INLINE bool scalar_filtering() const noexcept { return (flags & SCALAR_FILTERING); } /** @} */ }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** This is the main driver of parsing logic: it scans the YAML or * JSON source for tokens, and emits the appropriate sequence of * parsing events to its event handler. The parse engine itself has no * special limitations, and *can* accomodate containers as keys; it is the * event handler may introduce additional constraints. * * There are two implemented handlers (see @ref doc_event_handlers, * which has important notes about the event model): * * - @ref EventHandlerTree is the handler responsible for creating the * ryml @ref Tree * * - @ref EventHandlerYamlStd is the handler responsible for emitting * standardized [YAML test suite * events](https://github.com/yaml/yaml-test-suite), used (only) in * the CI of this project. This is not part of the library and is * not installed. */ template class ParseEngine { public: using handler_type = EventHandler; public: /** @name construction and assignment */ /** @{ */ ParseEngine(EventHandler *evt_handler, ParserOptions opts={}); ~ParseEngine(); ParseEngine(ParseEngine &&) noexcept; ParseEngine(ParseEngine const&); ParseEngine& operator=(ParseEngine &&) noexcept; ParseEngine& operator=(ParseEngine const&); /** @} */ public: /** @name modifiers */ /** @{ */ /** Reserve a certain capacity for the parsing stack. * This should be larger than the expected depth of the parsed * YAML tree. * * The parsing stack is the only (potential) heap memory used * directly by the parser. * * If the requested capacity is below the default * stack size of 16, the memory is used directly in the parser * object; otherwise it will be allocated from the heap. * * @note this reserves memory only for the parser itself; all the * allocations for the parsed tree will go through the tree's * allocator (when different). * * @note for maximum efficiency, the tree and the arena can (and * should) also be reserved. */ void reserve_stack(id_type capacity) { m_evt_handler->m_stack.reserve(capacity); } /** Reserve a certain capacity for the array used to track node * locations in the source buffer. */ void reserve_locations(size_t num_source_lines) { _resize_locations(num_source_lines); } RYML_DEPRECATED("filter arena no longer needed") void reserve_filter_arena(size_t) {} /** @} */ public: /** @name getters */ /** @{ */ /** Get the options used to build this parser object. */ ParserOptions const& options() const { return m_options; } /** Get the current callbacks in the parser. */ Callbacks const& callbacks() const { RYML_ASSERT(m_evt_handler); return m_evt_handler->m_stack.m_callbacks; } /** Get the name of the latest file parsed by this object. */ csubstr filename() const { return m_file; } /** Get the latest YAML buffer parsed by this object. */ csubstr source() const { return m_buf; } /** Get the encoding of the latest YAML buffer parsed by this object. * If no encoding was specified, UTF8 is assumed as per the YAML standard. */ Encoding_e encoding() const { return m_encoding != NOBOM ? m_encoding : UTF8; } id_type stack_capacity() const { RYML_ASSERT(m_evt_handler); return m_evt_handler->m_stack.capacity(); } size_t locations_capacity() const { return m_newline_offsets_capacity; } RYML_DEPRECATED("filter arena no longer needed") size_t filter_arena_capacity() const { return 0u; } /** @} */ public: /** @name parse methods */ /** @{ */ /** parse YAML in place, emitting events to the current handler */ void parse_in_place_ev(csubstr filename, substr src); /** parse JSON in place, emitting events to the current handler */ void parse_json_in_place_ev(csubstr filename, substr src); /** @} */ public: /** @name deprecated parse methods * @{ */ /** @cond dev */ template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_place(csubstr filename, substr yaml, Tree *t, size_t node_id); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_place( substr yaml, Tree *t, size_t node_id); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_place(csubstr filename, substr yaml, Tree *t ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_place( substr yaml, Tree *t ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_place(csubstr filename, substr yaml, NodeRef node ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_place( substr yaml, NodeRef node ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_place(csubstr filename, substr yaml ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_place( substr yaml ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, csubstr yaml, Tree *t, size_t node_id); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_arena( csubstr yaml, Tree *t, size_t node_id); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, csubstr yaml, Tree *t ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_arena( csubstr yaml, Tree *t ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, csubstr yaml, NodeRef node ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_arena( csubstr yaml, NodeRef node ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, csubstr yaml ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding function in parse.hpp.") typename std::enable_if::type parse_in_arena( csubstr yaml ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, substr yaml, Tree *t, size_t node_id); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena( substr yaml, Tree *t, size_t node_id); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, substr yaml, Tree *t ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena( substr yaml, Tree *t ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, substr yaml, NodeRef node ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena( substr yaml, NodeRef node ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena(csubstr filename, substr yaml ); template RYML_DEPRECATED("removed, deliberately undefined. use the freestanding csubstr version in parse.hpp.") typename std::enable_if::type parse_in_arena( substr yaml ); /** @endcond */ /** @} */ public: /** @name locations */ /** @{ */ /** Get the location of a node of the last tree to be parsed by this parser. */ Location location(Tree const& tree, id_type node_id) const; /** Get the location of a node of the last tree to be parsed by this parser. */ Location location(ConstNodeRef node) const; /** Get the string starting at a particular location, to the end * of the parsed source buffer. */ csubstr location_contents(Location const& loc) const; /** Given a pointer to a buffer position, get the location. * @param[in] val must be pointing to somewhere in the source * buffer that was last parsed by this object. */ Location val_location(const char *val) const; /** @} */ public: /** @name scalar filtering */ /** @{*/ /** filter a plain scalar */ FilterResult filter_scalar_plain(csubstr scalar, substr dst, size_t indentation); /** filter a plain scalar in place */ FilterResult filter_scalar_plain_in_place(substr scalar, size_t cap, size_t indentation); /** filter a single-quoted scalar */ FilterResult filter_scalar_squoted(csubstr scalar, substr dst); /** filter a single-quoted scalar in place */ FilterResult filter_scalar_squoted_in_place(substr scalar, size_t cap); /** filter a double-quoted scalar */ FilterResult filter_scalar_dquoted(csubstr scalar, substr dst); /** filter a double-quoted scalar in place */ FilterResultExtending filter_scalar_dquoted_in_place(substr scalar, size_t cap); /** filter a block-literal scalar */ FilterResult filter_scalar_block_literal(csubstr scalar, substr dst, size_t indentation, BlockChomp_e chomp); /** filter a block-literal scalar in place */ FilterResult filter_scalar_block_literal_in_place(substr scalar, size_t cap, size_t indentation, BlockChomp_e chomp); /** filter a block-folded scalar */ FilterResult filter_scalar_block_folded(csubstr scalar, substr dst, size_t indentation, BlockChomp_e chomp); /** filter a block-folded scalar in place */ FilterResult filter_scalar_block_folded_in_place(substr scalar, size_t cap, size_t indentation, BlockChomp_e chomp); /** @} */ private: struct ScannedScalar { substr scalar; bool needs_filter; }; struct ScannedBlock { substr scalar; size_t indentation; BlockChomp_e chomp; }; bool _is_doc_begin(csubstr s); bool _is_doc_end(csubstr s); bool _scan_scalar_plain_blck(ScannedScalar *C4_RESTRICT sc, size_t indentation); bool _scan_scalar_plain_seq_flow(ScannedScalar *C4_RESTRICT sc); bool _scan_scalar_plain_seq_blck(ScannedScalar *C4_RESTRICT sc); bool _scan_scalar_plain_map_flow(ScannedScalar *C4_RESTRICT sc); bool _scan_scalar_plain_map_blck(ScannedScalar *C4_RESTRICT sc); bool _scan_scalar_map_json(ScannedScalar *C4_RESTRICT sc); bool _scan_scalar_seq_json(ScannedScalar *C4_RESTRICT sc); bool _scan_scalar_plain_unk(ScannedScalar *C4_RESTRICT sc); bool _is_valid_start_scalar_plain_flow(csubstr s); ScannedScalar _scan_scalar_squot(); ScannedScalar _scan_scalar_dquot(); void _scan_block(ScannedBlock *C4_RESTRICT sb, size_t indref); csubstr _scan_anchor(); csubstr _scan_ref_seq(); csubstr _scan_ref_map(); csubstr _scan_tag(); public: // exposed for testing /** @cond dev */ csubstr _filter_scalar_plain(substr s, size_t indentation); csubstr _filter_scalar_squot(substr s); csubstr _filter_scalar_dquot(substr s); csubstr _filter_scalar_literal(substr s, size_t indentation, BlockChomp_e chomp); csubstr _filter_scalar_folded(substr s, size_t indentation, BlockChomp_e chomp); csubstr _maybe_filter_key_scalar_plain(ScannedScalar const& sc, size_t indendation); csubstr _maybe_filter_val_scalar_plain(ScannedScalar const& sc, size_t indendation); csubstr _maybe_filter_key_scalar_squot(ScannedScalar const& sc); csubstr _maybe_filter_val_scalar_squot(ScannedScalar const& sc); csubstr _maybe_filter_key_scalar_dquot(ScannedScalar const& sc); csubstr _maybe_filter_val_scalar_dquot(ScannedScalar const& sc); csubstr _maybe_filter_key_scalar_literal(ScannedBlock const& sb); csubstr _maybe_filter_val_scalar_literal(ScannedBlock const& sb); csubstr _maybe_filter_key_scalar_folded(ScannedBlock const& sb); csubstr _maybe_filter_val_scalar_folded(ScannedBlock const& sb); /** @endcond */ private: void _handle_map_block(); void _handle_seq_block(); void _handle_map_flow(); void _handle_seq_flow(); void _handle_seq_imap(); void _handle_map_json(); void _handle_seq_json(); void _handle_unk(); void _handle_unk_json(); void _handle_usty(); void _handle_flow_skip_whitespace(); void _end_map_blck(); void _end_seq_blck(); void _end2_map(); void _end2_seq(); void _begin2_doc(); void _begin2_doc_expl(); void _end2_doc(); void _end2_doc_expl(); void _maybe_begin_doc(); void _maybe_end_doc(); void _start_doc_suddenly(); void _end_doc_suddenly(); void _end_doc_suddenly__pop(); void _end_stream(); void _set_indentation(size_t indentation); void _save_indentation(); void _handle_indentation_pop_from_block_seq(); void _handle_indentation_pop_from_block_map(); void _handle_indentation_pop(ParserState const* dst); void _maybe_skip_comment(); void _skip_comment(); void _maybe_skip_whitespace_tokens(); void _maybe_skipchars(char c); #ifdef RYML_NO_COVERAGE__TO_BE_DELETED void _maybe_skipchars_up_to(char c, size_t max_to_skip); #endif template void _skipchars(const char (&chars)[N]); bool _maybe_scan_following_colon() noexcept; bool _maybe_scan_following_comma() noexcept; public: /** @cond dev */ template auto _filter_plain(FilterProcessor &C4_RESTRICT proc, size_t indentation) -> decltype(proc.result()); template auto _filter_squoted(FilterProcessor &C4_RESTRICT proc) -> decltype(proc.result()); template auto _filter_dquoted(FilterProcessor &C4_RESTRICT proc) -> decltype(proc.result()); template auto _filter_block_literal(FilterProcessor &C4_RESTRICT proc, size_t indentation, BlockChomp_e chomp) -> decltype(proc.result()); template auto _filter_block_folded(FilterProcessor &C4_RESTRICT proc, size_t indentation, BlockChomp_e chomp) -> decltype(proc.result()); /** @endcond */ public: /** @cond dev */ template void _filter_nl_plain(FilterProcessor &C4_RESTRICT proc, size_t indentation); template void _filter_nl_squoted(FilterProcessor &C4_RESTRICT proc); template void _filter_nl_dquoted(FilterProcessor &C4_RESTRICT proc); template bool _filter_ws_handle_to_first_non_space(FilterProcessor &C4_RESTRICT proc); template void _filter_ws_copy_trailing(FilterProcessor &C4_RESTRICT proc); template void _filter_ws_skip_trailing(FilterProcessor &C4_RESTRICT proc); template void _filter_dquoted_backslash(FilterProcessor &C4_RESTRICT proc); template void _filter_chomp(FilterProcessor &C4_RESTRICT proc, BlockChomp_e chomp, size_t indentation); template size_t _handle_all_whitespace(FilterProcessor &C4_RESTRICT proc, BlockChomp_e chomp); template size_t _extend_to_chomp(FilterProcessor &C4_RESTRICT proc, size_t contents_len); template void _filter_block_indentation(FilterProcessor &C4_RESTRICT proc, size_t indentation); template void _filter_block_folded_newlines(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len); template size_t _filter_block_folded_newlines_compress(FilterProcessor &C4_RESTRICT proc, size_t num_newl, size_t wpos_at_first_newl); template void _filter_block_folded_newlines_leading(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len); template void _filter_block_folded_indented_block(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len, size_t curr_indentation) noexcept; /** @endcond */ private: void _line_progressed(size_t ahead); void _line_ended(); void _line_ended_undo(); bool _finished_file() const; bool _finished_line() const; void _scan_line(); substr _peek_next_line(size_t pos=npos) const; bool _at_line_begin() const { return m_evt_handler->m_curr->line_contents.rem.begin() == m_evt_handler->m_curr->line_contents.full.begin(); } void _relocate_arena(csubstr prev_arena, substr next_arena); static void _s_relocate_arena(void*, csubstr prev_arena, substr next_arena); private: C4_ALWAYS_INLINE bool has_all(ParserFlag_t f) const noexcept { return (m_evt_handler->m_curr->flags & f) == f; } C4_ALWAYS_INLINE bool has_any(ParserFlag_t f) const noexcept { return (m_evt_handler->m_curr->flags & f) != 0; } C4_ALWAYS_INLINE bool has_none(ParserFlag_t f) const noexcept { return (m_evt_handler->m_curr->flags & f) == 0; } static C4_ALWAYS_INLINE bool has_all(ParserFlag_t f, ParserState const* C4_RESTRICT s) noexcept { return (s->flags & f) == f; } static C4_ALWAYS_INLINE bool has_any(ParserFlag_t f, ParserState const* C4_RESTRICT s) noexcept { return (s->flags & f) != 0; } static C4_ALWAYS_INLINE bool has_none(ParserFlag_t f, ParserState const* C4_RESTRICT s) noexcept { return (s->flags & f) == 0; } #ifndef RYML_DBG C4_ALWAYS_INLINE static void add_flags(ParserFlag_t on, ParserState *C4_RESTRICT s) noexcept { s->flags |= on; } C4_ALWAYS_INLINE static void addrem_flags(ParserFlag_t on, ParserFlag_t off, ParserState *C4_RESTRICT s) noexcept { s->flags &= ~off; s->flags |= on; } C4_ALWAYS_INLINE static void rem_flags(ParserFlag_t off, ParserState *C4_RESTRICT s) noexcept { s->flags &= ~off; } C4_ALWAYS_INLINE void add_flags(ParserFlag_t on) noexcept { m_evt_handler->m_curr->flags |= on; } C4_ALWAYS_INLINE void addrem_flags(ParserFlag_t on, ParserFlag_t off) noexcept { m_evt_handler->m_curr->flags &= ~off; m_evt_handler->m_curr->flags |= on; } C4_ALWAYS_INLINE void rem_flags(ParserFlag_t off) noexcept { m_evt_handler->m_curr->flags &= ~off; } #else static void add_flags(ParserFlag_t on, ParserState *C4_RESTRICT s); static void addrem_flags(ParserFlag_t on, ParserFlag_t off, ParserState *C4_RESTRICT s); static void rem_flags(ParserFlag_t off, ParserState *C4_RESTRICT s); C4_ALWAYS_INLINE void add_flags(ParserFlag_t on) noexcept { add_flags(on, m_evt_handler->m_curr); } C4_ALWAYS_INLINE void addrem_flags(ParserFlag_t on, ParserFlag_t off) noexcept { addrem_flags(on, off, m_evt_handler->m_curr); } C4_ALWAYS_INLINE void rem_flags(ParserFlag_t off) noexcept { rem_flags(off, m_evt_handler->m_curr); } #endif private: void _prepare_locations(); void _resize_locations(size_t sz); bool _locations_dirty() const; bool _location_from_cont(Tree const& tree, id_type node, Location *C4_RESTRICT loc) const; bool _location_from_node(Tree const& tree, id_type node, Location *C4_RESTRICT loc, id_type level) const; private: void _reset(); void _free(); void _clr(); #ifdef RYML_DBG template void _dbg(csubstr fmt, Args const& C4_RESTRICT ...args) const; #endif template void _err(csubstr fmt, Args const& C4_RESTRICT ...args) const; template void _errloc(csubstr fmt, Location const& loc, Args const& C4_RESTRICT ...args) const; template void _fmt_msg(DumpFn &&dumpfn) const; private: /** store pending tag or anchor/ref annotations */ struct Annotation { struct Entry { csubstr str; size_t indentation; size_t line; }; Entry annotations[2]; size_t num_entries; }; void _handle_colon(); void _add_annotation(Annotation *C4_RESTRICT dst, csubstr str, size_t indentation, size_t line); void _clear_annotations(Annotation *C4_RESTRICT dst); bool _has_pending_annotations() const { return m_pending_tags.num_entries || m_pending_anchors.num_entries; } #ifdef RYML_NO_COVERAGE__TO_BE_DELETED bool _handle_indentation_from_annotations(); #endif bool _annotations_require_key_container() const; void _handle_annotations_before_blck_key_scalar(); void _handle_annotations_before_blck_val_scalar(); void _handle_annotations_before_start_mapblck(size_t current_line); void _handle_annotations_before_start_mapblck_as_key(); void _handle_annotations_and_indentation_after_start_mapblck(size_t key_indentation, size_t key_line); size_t _select_indentation_from_annotations(size_t val_indentation, size_t val_line); void _handle_directive(csubstr rem); bool _handle_bom(); void _handle_bom(Encoding_e enc); void _check_tag(csubstr tag); private: ParserOptions m_options; csubstr m_file; substr m_buf; public: /** @cond dev */ EventHandler *C4_RESTRICT m_evt_handler; // NOLINT /** @endcond */ private: Annotation m_pending_anchors; Annotation m_pending_tags; bool m_was_inside_qmrk; bool m_doc_empty = true; size_t m_prev_colon = npos; Encoding_e m_encoding = UTF8; private: size_t *m_newline_offsets; size_t m_newline_offsets_size; size_t m_newline_offsets_capacity; csubstr m_newline_offsets_buf; }; /** @cond dev */ RYML_EXPORT C4_NO_INLINE size_t _find_last_newline_and_larger_indentation(csubstr s, size_t indentation) noexcept; /** @endcond */ /** Quickly inspect the source to estimate the number of nodes the * resulting tree is likely have. If a tree is empty before * parsing, considerable time will be spent growing it, so calling * this to reserve the tree size prior to parsing is likely to * result in a time gain. We encourage using this method before * parsing, but as always measure its impact in performance to * obtain a good trade-off. * * @note since this method is meant for optimizing performance, it * is approximate. The result may be actually smaller than the * resulting number of nodes, notably if the YAML uses implicit * maps as flow seq members as in `[these: are, individual: * maps]`. */ RYML_EXPORT id_type estimate_tree_capacity(csubstr src); // NOLINT(readability-redundant-declaration) /** @} */ } // namespace yml } // namespace c4 // NOLINTEND(hicpp-signed-bitwise) #if defined(_MSC_VER) # pragma warning(pop) #endif #endif /* _C4_YML_PARSE_ENGINE_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/parse_engine.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/preprocess.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_PREPROCESS_HPP_ #define _C4_YML_PREPROCESS_HPP_ /** @file preprocess.hpp Functions for preprocessing YAML prior to parsing. */ #ifndef _C4_YML_COMMON_HPP_ //included above: //#include "./common.hpp" #endif // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/substr.hpp //#include #if !defined(C4_SUBSTR_HPP_) && !defined(_C4_SUBSTR_HPP_) #error "amalgamate: file c4/substr.hpp must have been included at this point" #endif /* C4_SUBSTR_HPP_ */ namespace c4 { namespace yml { /** @addtogroup doc_preprocessors * @{ */ /** @cond dev */ namespace detail { using Preprocessor = size_t(csubstr, substr); template substr preprocess_into_container(csubstr input, CharContainer *out) { // try to write once. the preprocessor will stop writing at the end of // the container, but will process all the input to determine the // required container size. size_t sz = PP(input, to_substr(*out)); // if the container size is not enough, resize, and run again in the // resized container if(sz > out->size()) { out->resize(sz); sz = PP(input, to_substr(*out)); } return to_substr(*out).first(sz); } } // namespace detail /** @endcond */ //----------------------------------------------------------------------------- /** @defgroup doc_preprocess_rxmap preprocess_rxmap * * @brief Convert flow-type relaxed maps (with implicit bools) into strict YAML * flow map: * * @code{.yaml} * {a, b, c, d: [e, f], g: {a, b}} * # is converted into this: * {a: 1, b: 1, c: 1, d: [e, f], g: {a, b}} * @endcode * @note this is NOT recursive - conversion happens only in the top-level map * @param rxmap A relaxed map * @param buf output buffer * @param out output container * * @{ */ /** Write into a given output buffer. This function is safe to call with * empty or small buffers; it won't write beyond the end of the buffer. * * @return the number of characters required for output */ RYML_EXPORT size_t preprocess_rxmap(csubstr rxmap, substr buf); /** Write into an existing container. It is resized to contained the output. * @return a substr of the container * @overload preprocess_rxmap */ template substr preprocess_rxmap(csubstr rxmap, CharContainer *out) { return detail::preprocess_into_container(rxmap, out); } /** Create a container with the result. * @overload preprocess_rxmap */ template CharContainer preprocess_rxmap(csubstr rxmap) { CharContainer out; preprocess_rxmap(rxmap, &out); return out; } /** @} */ // preprocess_rxmap /** @} */ // group } // namespace yml } // namespace c4 #endif /* _C4_YML_PREPROCESS_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/reference_resolver.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/reference_resolver.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_REFERENCE_RESOLVER_HPP_ #define _C4_YML_REFERENCE_RESOLVER_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp //#include "c4/yml/tree.hpp" #if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) #error "amalgamate: file c4/yml/tree.hpp must have been included at this point" #endif /* C4_YML_TREE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/stack.hpp //#include "c4/yml/detail/stack.hpp" #if !defined(C4_YML_DETAIL_STACK_HPP_) && !defined(_C4_YML_DETAIL_STACK_HPP_) #error "amalgamate: file c4/yml/detail/stack.hpp must have been included at this point" #endif /* C4_YML_DETAIL_STACK_HPP_ */ namespace c4 { namespace yml { /** @addtogroup doc_ref_utils * @{ */ /** Reusable object to resolve references/aliases in a @ref Tree. */ struct RYML_EXPORT ReferenceResolver { ReferenceResolver() = default; /** Resolve references: for each reference, look for a matching * anchor, and copy its contents to the ref node. * * @p tree the subject tree * * @p clear_anchors whether to clear existing anchors after * resolving * * This method first does a full traversal of the tree to gather * all anchors and references in a separate collection, then it * goes through that collection to locate the names, which it does * by obeying the YAML standard diktat that "an alias node refers * to the most recent node in the serialization having the * specified anchor" * * So, depending on the number of anchor/alias nodes, this is a * potentially expensive operation, with a best-case linear * complexity (from the initial traversal). This potential cost is * one of the reasons for requiring an explicit call. * * The @ref Tree has an `Tree::resolve()` overload set forwarding * here. Previously this operation was done there, using a * discarded object; using this separate class offers opportunity * for reuse of the object. * * @warning resolving references opens an attack vector when the * data is malicious or severely malformed, as the tree can expand * exponentially. See for example the [Billion Laughs * Attack](https://en.wikipedia.org/wiki/Billion_laughs_attack). * */ void resolve(Tree *tree, bool clear_anchors=true); public: /** @cond dev */ struct RefData { NodeType type; id_type node; id_type prev_anchor; id_type target; id_type parent_ref; id_type parent_ref_sibling; }; void reset_(Tree *t_); void resolve_(); void gather_anchors_and_refs_(); void gather_anchors_and_refs__(id_type n); id_type count_anchors_and_refs_(id_type n); id_type lookup_(RefData const* C4_RESTRICT ra); Tree *C4_RESTRICT m_tree; /** We're using this stack purely as an array. */ detail::stack m_refs; /** @endcond */ }; /** @} */ } // namespace ryml } // namespace c4 #endif // _C4_YML_REFERENCE_RESOLVER_HPP_ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/reference_resolver.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/parse.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/parse.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_PARSE_HPP_ #define _C4_YML_PARSE_HPP_ #ifndef _C4_YML_COMMON_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp //#include "c4/yml/common.hpp" #if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) #error "amalgamate: file c4/yml/common.hpp must have been included at this point" #endif /* C4_YML_COMMON_HPP_ */ #endif namespace c4 { namespace yml { class Tree; class NodeRef; template class ParseEngine; struct EventHandlerTree; RYML_EXPORT id_type estimate_tree_capacity(csubstr src); // NOLINT /** @addtogroup doc_parse * @{ */ /** This is the main ryml parser, where the parser events are handled * to create a ryml tree. * * @warning This class cannot parse YAML where there are container * keys. This is not a limitation of the @ref ParseEngine, but of the * @ref EventHandlerTree, which is present because the @ref Tree does * not accept containers as keys. However, the @ref ParseEngine *can* * parse container keys; consult its documentation for more details. * * @see ParserOptions * @see ParseEngine * @see EventHandlerTree * */ using Parser = RYML_EXPORT ParseEngine; //----------------------------------------------------------------------------- /** @defgroup doc_parse_in_place__with_existing_parser Parse in place with existing parser * * @brief parse a mutable YAML source buffer (re)using an existing * parser. Scalars requiring filtering are mutated in place (except in * the rare cases where the filtered scalar is longer than the * original scalar, or where filtering was disabled before the * call). These overloads accept an existing parser object, and * provide the opportunity to use special parser options. * * @see ParserOptions * * @{ */ // this is vertically aligned to highlight the parameter differences. RYML_EXPORT void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *t, id_type node_id); /**< (1) parse YAML into an existing tree node. * * The filename will be used in any error messages * arising during the parse. The callbacks in the * tree are kept, and used to allocate * the tree members, if any allocation is required. */ RYML_EXPORT void parse_in_place(Parser *parser, substr yaml, Tree *t, id_type node_id); /**< (2) like (1) but no filename will be reported */ RYML_EXPORT void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *t ); /**< (3) parse YAML into the root node of an existing tree. * * The filename will be used in any error messages * arising during the parse. The callbacks in the * tree are kept, and used to allocate * the tree members, if any allocation is required. */ RYML_EXPORT void parse_in_place(Parser *parser, substr yaml, Tree *t ); /**< (4) like (3) but no filename will be reported */ RYML_EXPORT void parse_in_place(Parser *parser, csubstr filename, substr yaml, NodeRef node ); /**< (5) like (1) but the node is given as a NodeRef */ RYML_EXPORT void parse_in_place(Parser *parser, substr yaml, NodeRef node ); /**< (6) like (5) but no filename will be reported */ RYML_EXPORT Tree parse_in_place(Parser *parser, csubstr filename, substr yaml ); /**< (7) create a new tree, and parse YAML into its root node. * * The filename will be used in any error messages * arising during the parse. The tree is created with * the callbacks currently in the parser. */ RYML_EXPORT Tree parse_in_place(Parser *parser, substr yaml ); /**< (8) like (7) but no filename will be reported */ // this is vertically aligned to highlight the parameter differences. RYML_EXPORT void parse_json_in_place(Parser *parser, csubstr filename, substr json, Tree *t, id_type node_id); ///< (1) parse JSON into an existing tree node. The filename will be used in any error messages arising during the parse. RYML_EXPORT void parse_json_in_place(Parser *parser, substr json, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported RYML_EXPORT void parse_json_in_place(Parser *parser, csubstr filename, substr json, Tree *t ); ///< (3) parse JSON into an existing tree, into its root node. RYML_EXPORT void parse_json_in_place(Parser *parser, substr json, Tree *t ); ///< (4) like (3) but no filename will be reported RYML_EXPORT void parse_json_in_place(Parser *parser, csubstr filename, substr json, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef RYML_EXPORT void parse_json_in_place(Parser *parser, substr json, NodeRef node ); ///< (6) like (5) but no filename will be reported RYML_EXPORT Tree parse_json_in_place(Parser *parser, csubstr filename, substr json ); ///< (7) create a new tree, and parse JSON into its root node. RYML_EXPORT Tree parse_json_in_place(Parser *parser, substr json ); ///< (8) like (7) but no filename will be reported /** @} */ //----------------------------------------------------------------------------- /** @defgroup doc_parse_in_place___with_temporary_parser Parse in place with temporary parser * * @brief parse a mutable YAML source buffer. Scalars requiring * filtering are mutated in place (except in the rare cases where the * filtered scalar is longer than the original scalar). * * @note These freestanding functions use a temporary parser object, * and are convenience functions to enable the user to easily parse * YAML without the need to explicitly instantiate a parser and event * handler. Note that some properties (notably node locations in the * original source code) are only available through the parser * class. If you need access to any of these properties, use * the appropriate overload from @ref doc_parse_in_place__with_existing_parser * * @{ */ // this is vertically aligned to highlight the parameter differences. RYML_EXPORT void parse_in_place(csubstr filename, substr yaml, Tree *t, id_type node_id); ///< (1) parse YAML into an existing tree node. The filename will be used in any error messages arising during the parse. RYML_EXPORT void parse_in_place( substr yaml, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported RYML_EXPORT void parse_in_place(csubstr filename, substr yaml, Tree *t ); ///< (3) parse YAML into an existing tree, into its root node. RYML_EXPORT void parse_in_place( substr yaml, Tree *t ); ///< (4) like (3) but no filename will be reported RYML_EXPORT void parse_in_place(csubstr filename, substr yaml, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef RYML_EXPORT void parse_in_place( substr yaml, NodeRef node ); ///< (6) like (5) but no filename will be reported RYML_EXPORT Tree parse_in_place(csubstr filename, substr yaml ); ///< (7) create a new tree, and parse YAML into its root node. RYML_EXPORT Tree parse_in_place( substr yaml ); ///< (8) like (7) but no filename will be reported // this is vertically aligned to highlight the parameter differences. RYML_EXPORT void parse_json_in_place(csubstr filename, substr json, Tree *t, id_type node_id); ///< (1) parse JSON into an existing tree node. The filename will be used in any error messages arising during the parse. RYML_EXPORT void parse_json_in_place( substr json, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported RYML_EXPORT void parse_json_in_place(csubstr filename, substr json, Tree *t ); ///< (3) parse JSON into an existing tree, into its root node. RYML_EXPORT void parse_json_in_place( substr json, Tree *t ); ///< (4) like (3) but no filename will be reported RYML_EXPORT void parse_json_in_place(csubstr filename, substr json, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef RYML_EXPORT void parse_json_in_place( substr json, NodeRef node ); ///< (6) like (5) but no filename will be reported RYML_EXPORT Tree parse_json_in_place(csubstr filename, substr json ); ///< (7) create a new tree, and parse JSON into its root node. RYML_EXPORT Tree parse_json_in_place( substr json ); ///< (8) like (7) but no filename will be reported /** @} */ //----------------------------------------------------------------------------- /** @defgroup doc_parse_in_arena__with_existing_parser Parse in arena with existing parser * * @brief parse a read-only (immutable) YAML source buffer. This is * achieved by first copying the contents of the buffer to the tree's * arena, and then calling @ref parse_in_arena() . All the resulting * scalars will be filtered in the arena. These overloads accept an * existing parser object, and provide the opportunity to use special * parser options. * * @see ParserOptions * * * @note These freestanding functions use a temporary parser object, * and are convenience functions to easily parse YAML without the need * to instantiate a separate parser. Note that some properties * (notably node locations in the original source code) are only * available through the parser class. If you need access to any of * these properties, use the appropriate overload from @ref * doc_parse_in_arena__with_existing_parser * * @warning overloads receiving a substr YAML buffer are intentionally * left undefined, such that calling parse_in_arena() with a substr * will cause a linker error. This is to prevent an accidental copy of * the source buffer to the tree's arena, because substr (which is * mutable) is implicitly convertible to csubstr (which is * immutable). If you really intend to parse a mutable buffer in the * tree's arena, convert it first to immutable by assigning the substr * to a csubstr prior to calling parse_in_arena(). This is not needed * for parse_in_place() because csubstr is not implicitly convertible * to substr. To be clear: * ```c++ * substr mutable_buffer = ...; * parser.parse_in_arena(mutable_buffer); // linker error * * csubstr immutable_buffer = ...; * parser.parse_in_arena(immutable_buffer); // ok * ``` * * @{ */ #define RYML_DONT_PARSE_SUBSTR_IN_ARENA "" \ "Do not pass a (mutable) substr to parse_in_arena(); " \ "if you have a substr, it should be parsed in place. " \ "Consider using parse_in_place() instead, or convert " \ "the buffer to csubstr prior to calling. This function " \ " is deliberately left undefined, so that calling it " \ "will cause a linker error." // this is vertically aligned to highlight the parameter differences. RYML_EXPORT void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *t, id_type node_id); ///< (1) parse YAML into an existing tree node. The filename will be used in any error messages arising during the parse. RYML_EXPORT void parse_in_arena(Parser *parser, csubstr yaml, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported RYML_EXPORT void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *t ); ///< (3) parse YAML into an existing tree, into its root node. RYML_EXPORT void parse_in_arena(Parser *parser, csubstr yaml, Tree *t ); ///< (4) like (3) but no filename will be reported RYML_EXPORT void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef RYML_EXPORT void parse_in_arena(Parser *parser, csubstr yaml, NodeRef node ); ///< (6) like (5) but no filename will be reported RYML_EXPORT Tree parse_in_arena(Parser *parser, csubstr filename, csubstr yaml ); ///< (7) create a new tree, and parse YAML into its root node. RYML_EXPORT Tree parse_in_arena(Parser *parser, csubstr yaml ); ///< (8) like (7) but no filename will be reported // this is vertically aligned to highlight the parameter differences. RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t, id_type node_id); ///< (1) parse JSON into an existing tree node. The filename will be used in any error messages arising during the parse. RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr json, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t ); ///< (3) parse JSON into an existing tree, into its root node. RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr json, Tree *t ); ///< (4) like (3) but no filename will be reported RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef RYML_EXPORT void parse_json_in_arena(Parser *parser, csubstr json, NodeRef node ); ///< (6) like (5) but no filename will be reported RYML_EXPORT Tree parse_json_in_arena(Parser *parser, csubstr filename, csubstr json ); ///< (7) create a new tree, and parse JSON into its root node. RYML_EXPORT Tree parse_json_in_arena(Parser *parser, csubstr json ); ///< (8) like (7) but no filename will be reported /* READ THE DEPRECATION NOTE! * * All of the functions below are intentionally left undefined, to * prevent them being used. * */ /** @cond dev */ RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, substr yaml, Tree *t, id_type node_id); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, csubstr filename, substr yaml, Tree *t, id_type node_id); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, substr yaml, Tree *t ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, csubstr filename, substr yaml, Tree *t ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, substr yaml, NodeRef node ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(Parser *parser, csubstr filename, substr yaml, NodeRef node ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena(Parser *parser, substr yaml ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena(Parser *parser, csubstr filename, substr yaml ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, substr json, Tree *t, id_type node_id); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, csubstr filename, substr json, Tree *t, id_type node_id); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, substr json, Tree *t ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, csubstr filename, substr json, Tree *t ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, substr json, NodeRef node ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(Parser *parser, csubstr filename, substr json, NodeRef node ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_json_in_arena(Parser *parser, substr json ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_json_in_arena(Parser *parser, csubstr filename, substr json ); /** @endcond */ /** @} */ //----------------------------------------------------------------------------- /** @defgroup doc_parse_in_arena__with_temporary_parser Parse in arena with temporary parser * * @brief parse a read-only (immutable) YAML source buffer. This is * achieved by first copying the contents of the buffer to the tree's * arena, and then calling @ref parse_in_arena() . * * @note These freestanding functions use a temporary parser object, * and are convenience functions to easily one-off parse YAML without * the need to instantiate a separate parser. Note that some * properties (notably node locations in the original source code) are * only available through the parser class. If you need access to any * of these properties, use the appropriate overload from @ref * doc_parse_in_arena__with_existing_parser * * @warning overloads receiving a substr YAML buffer are intentionally * left undefined, such that calling parse_in_arena() with a substr * will cause a linker error. This is to prevent an accidental copy of * the source buffer to the tree's arena, because substr (which is * mutable) is implicitly convertible to csubstr (which is * immutable). If you really intend to parse a mutable buffer in the * tree's arena, convert it first to immutable by assigning the substr * to a csubstr prior to calling parse_in_arena(). This is not needed * for parse_in_place() because csubstr is not implicitly convertible * to substr. To be clear: * ```c++ * substr mutable_buffer = ...; * parser.parse_in_arena(mutable_buffer); // linker error * * csubstr immutable_buffer = ...; * parser.parse_in_arena(immutable_buffer); // ok * ``` * * @{ */ // this is vertically aligned to highlight the parameter differences. RYML_EXPORT void parse_in_arena(csubstr filename, csubstr yaml, Tree *t, id_type node_id); ///< (1) parse YAML into an existing tree node. The filename will be used in any error messages arising during the parse. RYML_EXPORT void parse_in_arena( csubstr yaml, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported RYML_EXPORT void parse_in_arena(csubstr filename, csubstr yaml, Tree *t ); ///< (3) parse YAML into an existing tree, into its root node. RYML_EXPORT void parse_in_arena( csubstr yaml, Tree *t ); ///< (4) like (3) but no filename will be reported RYML_EXPORT void parse_in_arena(csubstr filename, csubstr yaml, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef RYML_EXPORT void parse_in_arena( csubstr yaml, NodeRef node ); ///< (6) like (5) but no filename will be reported RYML_EXPORT Tree parse_in_arena(csubstr filename, csubstr yaml ); ///< (7) create a new tree, and parse YAML into its root node. RYML_EXPORT Tree parse_in_arena( csubstr yaml ); ///< (8) like (7) but no filename will be reported // this is vertically aligned to highlight the parameter differences. RYML_EXPORT void parse_json_in_arena(csubstr filename, csubstr json, Tree *t, id_type node_id); ///< (1) parse JSON into an existing tree node. The filename will be used in any error messages arising during the parse. RYML_EXPORT void parse_json_in_arena( csubstr json, Tree *t, id_type node_id); ///< (2) like (1) but no filename will be reported RYML_EXPORT void parse_json_in_arena(csubstr filename, csubstr json, Tree *t ); ///< (3) parse JSON into an existing tree, into its root node. RYML_EXPORT void parse_json_in_arena( csubstr json, Tree *t ); ///< (4) like (3) but no filename will be reported RYML_EXPORT void parse_json_in_arena(csubstr filename, csubstr json, NodeRef node ); ///< (5) like (1) but the node is given as a NodeRef RYML_EXPORT void parse_json_in_arena( csubstr json, NodeRef node ); ///< (6) like (5) but no filename will be reported RYML_EXPORT Tree parse_json_in_arena(csubstr filename, csubstr json ); ///< (7) create a new tree, and parse JSON into its root node. RYML_EXPORT Tree parse_json_in_arena( csubstr json ); ///< (8) like (7) but no filename will be reported /* READ THE DEPRECATION NOTE! * * All of the functions below are intentionally left undefined, to * prevent them being used. */ /** @cond dev */ RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena( substr yaml, Tree *t, id_type node_id); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, Tree *t, id_type node_id); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena( substr yaml, Tree *t ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, Tree *t ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena( substr yaml, NodeRef node ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, NodeRef node ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena( substr yaml ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena(csubstr filename, substr yaml ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena( substr json, Tree *t, id_type node_id); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(csubstr filename, substr json, Tree *t, id_type node_id); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena( substr json, Tree *t ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(csubstr filename, substr json, Tree *t ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena( substr json, NodeRef node ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_json_in_arena(csubstr filename, substr json, NodeRef node ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_json_in_arena( substr json ); RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_json_in_arena(csubstr filename, substr json ); /** @endcond */ /** @} */ /** @} */ } // namespace yml } // namespace c4 #endif /* _C4_YML_PARSE_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/parse.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/std/map.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/std/map.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_STD_MAP_HPP_ #define _C4_YML_STD_MAP_HPP_ /** @file map.hpp write/read std::map to/from a YAML tree. */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp //#include "c4/yml/node.hpp" #if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) #error "amalgamate: file c4/yml/node.hpp must have been included at this point" #endif /* C4_YML_NODE_HPP_ */ #include namespace c4 { namespace yml { // std::map requires child nodes in the data // tree hierarchy (a MAP node in ryml parlance). // So it should be serialized via write()/read(). template void write(c4::yml::NodeRef *n, std::map const& m) { *n |= c4::yml::MAP; for(auto const& C4_RESTRICT p : m) { auto ch = n->append_child(); ch << c4::yml::key(p.first); ch << p.second; } } /** read the node members, assigning into the existing map. If a key * is already present in the map, then its value will be * move-assigned. */ template bool read(c4::yml::ConstNodeRef const& n, std::map * m) { for(auto const& C4_RESTRICT ch : n) { K k{}; ch >> c4::yml::key(k); ch >> (*m)[k]; } return true; } } // namespace yml } // namespace c4 #endif // _C4_YML_STD_MAP_HPP_ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/std/map.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/std/string.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/std/string.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef C4_YML_STD_STRING_HPP_ #define C4_YML_STD_STRING_HPP_ /** @file string.hpp substring conversions for/from std::string */ // everything we need is implemented here: // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/std/string.hpp //#include #if !defined(C4_STD_STRING_HPP_) && !defined(_C4_STD_STRING_HPP_) #error "amalgamate: file c4/std/string.hpp must have been included at this point" #endif /* C4_STD_STRING_HPP_ */ #endif // C4_YML_STD_STRING_HPP_ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/std/string.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/std/vector.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/std/vector.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_STD_VECTOR_HPP_ #define _C4_YML_STD_VECTOR_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp //#include "c4/yml/node.hpp" #if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) #error "amalgamate: file c4/yml/node.hpp must have been included at this point" #endif /* C4_YML_NODE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/std/vector.hpp //#include #if !defined(C4_STD_VECTOR_HPP_) && !defined(_C4_STD_VECTOR_HPP_) #error "amalgamate: file c4/std/vector.hpp must have been included at this point" #endif /* C4_STD_VECTOR_HPP_ */ //included above: //#include namespace c4 { namespace yml { // vector is a sequence-like type, and it requires child nodes // in the data tree hierarchy (a SEQ node in ryml parlance). // So it should be serialized via write()/read(). template void write(c4::yml::NodeRef *n, std::vector const& vec) { *n |= c4::yml::SEQ; for(V const& v : vec) n->append_child() << v; } /** read the node members, overwriting existing vector entries. */ template bool read(c4::yml::ConstNodeRef const& n, std::vector *vec) { C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wuseless-cast") vec->resize(static_cast(n.num_children())); C4_SUPPRESS_WARNING_GCC_POP size_t pos = 0; for(ConstNodeRef const child : n) child >> (*vec)[pos++]; return true; } /** read the node members, overwriting existing vector entries. * specialization: std::vector uses std::vector::reference as * the return value of its operator[]. */ template bool read(c4::yml::ConstNodeRef const& n, std::vector *vec) { C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wuseless-cast") vec->resize(static_cast(n.num_children())); C4_SUPPRESS_WARNING_GCC_POP size_t pos = 0; bool tmp = {}; for(ConstNodeRef const child : n) { child >> tmp; (*vec)[pos++] = tmp; } return true; } } // namespace yml } // namespace c4 #endif // _C4_YML_STD_VECTOR_HPP_ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/std/vector.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/std/std.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/std/std.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_STD_STD_HPP_ #define _C4_YML_STD_STD_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/std/string.hpp //#include "c4/yml/std/string.hpp" #if !defined(C4_YML_STD_STRING_HPP_) && !defined(_C4_YML_STD_STRING_HPP_) #error "amalgamate: file c4/yml/std/string.hpp must have been included at this point" #endif /* C4_YML_STD_STRING_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/std/vector.hpp //#include "c4/yml/std/vector.hpp" #if !defined(C4_YML_STD_VECTOR_HPP_) && !defined(_C4_YML_STD_VECTOR_HPP_) #error "amalgamate: file c4/yml/std/vector.hpp must have been included at this point" #endif /* C4_YML_STD_VECTOR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/std/map.hpp //#include "c4/yml/std/map.hpp" #if !defined(C4_YML_STD_MAP_HPP_) && !defined(_C4_YML_STD_MAP_HPP_) #error "amalgamate: file c4/yml/std/map.hpp must have been included at this point" #endif /* C4_YML_STD_MAP_HPP_ */ #endif // _C4_YML_STD_STD_HPP_ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/std/std.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/version.cpp // https://github.com/biojppm/rapidyaml/src/c4/yml/version.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef RYML_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/version.hpp //#include "c4/yml/version.hpp" #if !defined(C4_YML_VERSION_HPP_) && !defined(_C4_YML_VERSION_HPP_) #error "amalgamate: file c4/yml/version.hpp must have been included at this point" #endif /* C4_YML_VERSION_HPP_ */ namespace c4 { namespace yml { csubstr version() { return RYML_VERSION; } int version_major() { return RYML_VERSION_MAJOR; } int version_minor() { return RYML_VERSION_MINOR; } int version_patch() { return RYML_VERSION_PATCH; } } // namespace yml } // namespace c4 #endif /* RYML_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/version.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/common.cpp // https://github.com/biojppm/rapidyaml/src/c4/yml/common.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef RYML_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp //#include "c4/yml/common.hpp" #if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) #error "amalgamate: file c4/yml/common.hpp must have been included at this point" #endif /* C4_YML_COMMON_HPP_ */ #ifndef RYML_NO_DEFAULT_CALLBACKS //included above: //# include //included above: //# include # ifdef RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS # include # endif #endif // RYML_NO_DEFAULT_CALLBACKS namespace c4 { namespace yml { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4702/*unreachable code*/) // on the call to the unreachable macro namespace { Callbacks s_default_callbacks; } // anon namespace #ifndef RYML_NO_DEFAULT_CALLBACKS void report_error_impl(const char* msg, size_t length, Location loc, FILE *f) { if(!f) f = stderr; if(loc) { if(!loc.name.empty()) { // this is more portable than using fprintf("%.*s:") which // is not available in some embedded platforms fwrite(loc.name.str, 1, loc.name.len, f); // NOLINT fputc(':', f); // NOLINT } fprintf(f, "%zu:", loc.line); // NOLINT if(loc.col) fprintf(f, "%zu:", loc.col); // NOLINT if(loc.offset) fprintf(f, " (%zuB):", loc.offset); // NOLINT fputc(' ', f); // NOLINT } RYML_ASSERT(!csubstr(msg, length).ends_with('\0')); fwrite(msg, 1, length, f); // NOLINT fputc('\n', f); // NOLINT fflush(f); // NOLINT } [[noreturn]] void error_impl(const char* msg, size_t length, Location loc, void * /*user_data*/) { RYML_ASSERT(!csubstr(msg, length).ends_with('\0')); report_error_impl(msg, length, loc, nullptr); #ifdef RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS throw std::runtime_error(std::string(msg, length)); #else ::abort(); #endif } void* allocate_impl(size_t length, void * /*hint*/, void * /*user_data*/) { void *mem = ::malloc(length); if(mem == nullptr) { const char msg[] = "could not allocate memory"; error_impl(msg, sizeof(msg)-1, {}, nullptr); } return mem; } void free_impl(void *mem, size_t /*length*/, void * /*user_data*/) { ::free(mem); } #endif // RYML_NO_DEFAULT_CALLBACKS Callbacks::Callbacks() noexcept : m_user_data(nullptr), #ifndef RYML_NO_DEFAULT_CALLBACKS m_allocate(allocate_impl), m_free(free_impl), m_error(error_impl) #else m_allocate(nullptr), m_free(nullptr), m_error(nullptr) #endif { } Callbacks::Callbacks(void *user_data, pfn_allocate alloc_, pfn_free free_, pfn_error error_) : m_user_data(user_data), #ifndef RYML_NO_DEFAULT_CALLBACKS m_allocate(alloc_ ? alloc_ : allocate_impl), m_free(free_ ? free_ : free_impl), m_error((error_ ? error_ : error_impl)) #else m_allocate(alloc_), m_free(free_), m_error(error_) #endif { RYML_CHECK(m_allocate); RYML_CHECK(m_free); RYML_CHECK(m_error); } void set_callbacks(Callbacks const& c) { s_default_callbacks = c; } Callbacks const& get_callbacks() { return s_default_callbacks; } void reset_callbacks() { set_callbacks(Callbacks()); } // the [[noreturn]] attribute needs to be here as well (UB otherwise) // https://en.cppreference.com/w/cpp/language/attributes/noreturn [[noreturn]] void error(Callbacks const& cb, const char *msg, size_t msg_len, Location loc) { cb.m_error(msg, msg_len, loc, cb.m_user_data); abort(); // call abort in case the error callback didn't interrupt execution C4_UNREACHABLE(); } // the [[noreturn]] attribute needs to be here as well (UB otherwise) // see https://en.cppreference.com/w/cpp/language/attributes/noreturn [[noreturn]] void error(const char *msg, size_t msg_len, Location loc) { error(s_default_callbacks, msg, msg_len, loc); C4_UNREACHABLE(); } C4_SUPPRESS_WARNING_MSVC_POP C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace yml } // namespace c4 #endif /* RYML_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/common.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/node_type.cpp // https://github.com/biojppm/rapidyaml/src/c4/yml/node_type.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef RYML_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node_type.hpp //#include "c4/yml/node_type.hpp" #if !defined(C4_YML_NODE_TYPE_HPP_) && !defined(_C4_YML_NODE_TYPE_HPP_) #error "amalgamate: file c4/yml/node_type.hpp must have been included at this point" #endif /* C4_YML_NODE_TYPE_HPP_ */ namespace c4 { namespace yml { const char* NodeType::type_str(NodeType_e ty) noexcept { switch(ty & _TYMASK) { case KEYVAL: return "KEYVAL"; case KEY: return "KEY"; case VAL: return "VAL"; case MAP: return "MAP"; case SEQ: return "SEQ"; case KEYMAP: return "KEYMAP"; case KEYSEQ: return "KEYSEQ"; case DOCSEQ: return "DOCSEQ"; case DOCMAP: return "DOCMAP"; case DOCVAL: return "DOCVAL"; case DOC: return "DOC"; case STREAM: return "STREAM"; case NOTYPE: return "NOTYPE"; default: if((ty & KEYVAL) == KEYVAL) return "KEYVAL***"; if((ty & KEYMAP) == KEYMAP) return "KEYMAP***"; if((ty & KEYSEQ) == KEYSEQ) return "KEYSEQ***"; if((ty & DOCSEQ) == DOCSEQ) return "DOCSEQ***"; if((ty & DOCMAP) == DOCMAP) return "DOCMAP***"; if((ty & DOCVAL) == DOCVAL) return "DOCVAL***"; if(ty & KEY) return "KEY***"; if(ty & VAL) return "VAL***"; if(ty & MAP) return "MAP***"; if(ty & SEQ) return "SEQ***"; if(ty & DOC) return "DOC***"; return "(unk)"; } } csubstr NodeType::type_str(substr buf, NodeType_e flags) noexcept { size_t pos = 0; bool gotone = false; #define _prflag(fl, txt) \ do { \ if((flags & (fl)) == (fl)) \ { \ if(gotone) \ { \ if(pos + 1 < buf.len) \ buf[pos] = '|'; \ ++pos; \ } \ csubstr fltxt = txt; \ if(pos + fltxt.len <= buf.len) \ memcpy(buf.str + pos, fltxt.str, fltxt.len); \ pos += fltxt.len; \ gotone = true; \ flags = (flags & ~(fl)); /*remove the flag*/ \ } \ } while(0) _prflag(STREAM, "STREAM"); _prflag(DOC, "DOC"); // key properties _prflag(KEY, "KEY"); _prflag(KEYNIL, "KNIL"); _prflag(KEYTAG, "KTAG"); _prflag(KEYANCH, "KANCH"); _prflag(KEYREF, "KREF"); _prflag(KEY_LITERAL, "KLITERAL"); _prflag(KEY_FOLDED, "KFOLDED"); _prflag(KEY_SQUO, "KSQUO"); _prflag(KEY_DQUO, "KDQUO"); _prflag(KEY_PLAIN, "KPLAIN"); _prflag(KEY_UNFILT, "KUNFILT"); // val properties _prflag(VAL, "VAL"); _prflag(VALNIL, "VNIL"); _prflag(VALTAG, "VTAG"); _prflag(VALANCH, "VANCH"); _prflag(VALREF, "VREF"); _prflag(VAL_UNFILT, "VUNFILT"); _prflag(VAL_LITERAL, "VLITERAL"); _prflag(VAL_FOLDED, "VFOLDED"); _prflag(VAL_SQUO, "VSQUO"); _prflag(VAL_DQUO, "VDQUO"); _prflag(VAL_PLAIN, "VPLAIN"); _prflag(VAL_UNFILT, "VUNFILT"); // container properties _prflag(MAP, "MAP"); _prflag(SEQ, "SEQ"); _prflag(FLOW_SL, "FLOWSL"); _prflag(FLOW_ML, "FLOWML"); _prflag(BLOCK, "BLCK"); if(pos == 0) _prflag(NOTYPE, "NOTYPE"); #undef _prflag if(pos < buf.len) { buf[pos] = '\0'; return buf.first(pos); } else { csubstr failed; failed.len = pos + 1; failed.str = nullptr; return failed; } } //----------------------------------------------------------------------------- // see https://www.yaml.info/learn/quote.html#noplain bool scalar_style_query_squo(csubstr s) noexcept { return ! s.first_of_any("\n ", "\n\t"); } // see https://www.yaml.info/learn/quote.html#noplain bool scalar_style_query_plain(csubstr s) noexcept { if(s.begins_with("-.")) { if(s == "-.inf" || s == "-.INF") return true; else if(s.sub(2).is_number()) return true; } else if(s.begins_with_any("0123456789.-+") && s.is_number()) { return true; } return s != ':' && ( ! s.begins_with_any("-:?*&,'\"{}[]|>%#@`\r")) // @ and ` are reserved characters && ( ! s.ends_with_any(":#")) // make this check in the last place, as it has linear // complexity, while the previous ones are // constant-time && (s.first_of("\n#:[]{},") == npos); } NodeType_e scalar_style_choose(csubstr s) noexcept { if(s.len) { if(s.begins_with_any(" \n\t") || s.ends_with_any(" \n\t")) { return SCALAR_DQUO; } else if( ! scalar_style_query_plain(s)) { return scalar_style_query_squo(s) ? SCALAR_SQUO : SCALAR_DQUO; } // nothing remarkable - use plain return SCALAR_PLAIN; } return s.str ? SCALAR_SQUO : SCALAR_PLAIN; } NodeType_e scalar_style_json_choose(csubstr s) noexcept { // do not quote special cases bool plain = ( (s == "true" || s == "false" || s == "null") || ( // do not quote numbers s.is_number() && ( // quote integral numbers if they have a leading 0 // https://github.com/biojppm/rapidyaml/issues/291 (!(s.len > 1 && s.begins_with('0'))) // do not quote reals with leading 0 // https://github.com/biojppm/rapidyaml/issues/313 || (s.find('.') != csubstr::npos) ) ) ); return plain ? SCALAR_PLAIN : SCALAR_DQUO; } } // namespace yml } // namespace c4 #endif /* RYML_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/node_type.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/tag.cpp // https://github.com/biojppm/rapidyaml/src/c4/yml/tag.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef RYML_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tag.hpp //#include "c4/yml/tag.hpp" #if !defined(C4_YML_TAG_HPP_) && !defined(_C4_YML_TAG_HPP_) #error "amalgamate: file c4/yml/tag.hpp must have been included at this point" #endif /* C4_YML_TAG_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp //#include "c4/yml/tree.hpp" #if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) #error "amalgamate: file c4/yml/tree.hpp must have been included at this point" #endif /* C4_YML_TREE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //#include "c4/yml/detail/parser_dbg.hpp" #if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) #error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ namespace c4 { namespace yml { bool is_custom_tag(csubstr tag) { if((tag.len > 2) && (tag.str[0] == '!')) { size_t pos = tag.find('!', 1); return pos != npos && pos > 1 && tag.str[1] != '<'; } return false; } csubstr normalize_tag(csubstr tag) { YamlTag_e t = to_tag(tag); if(t != TAG_NONE) return from_tag(t); if(tag.begins_with("!<")) tag = tag.sub(1); if(tag.begins_with("'; result = output.first(len); } else { result.str = nullptr; result.len = len; } } return result; } YamlTag_e to_tag(csubstr tag) { if(tag.begins_with("!<")) tag = tag.sub(1); if(tag.begins_with("!!")) tag = tag.sub(2); else if(tag.begins_with('!')) return TAG_NONE; else if(tag.begins_with("tag:yaml.org,2002:")) { RYML_ASSERT(csubstr("tag:yaml.org,2002:").len == 18); tag = tag.sub(18); } else if(tag.begins_with(""}; case TAG_OMAP: return {""}; case TAG_PAIRS: return {""}; case TAG_SET: return {""}; case TAG_SEQ: return {""}; case TAG_BINARY: return {""}; case TAG_BOOL: return {""}; case TAG_FLOAT: return {""}; case TAG_INT: return {""}; case TAG_MERGE: return {""}; case TAG_NULL: return {""}; case TAG_STR: return {""}; case TAG_TIMESTAMP: return {""}; case TAG_VALUE: return {""}; case TAG_YAML: return {""}; case TAG_NONE: default: return {""}; } } csubstr from_tag(YamlTag_e tag) { switch(tag) { case TAG_MAP: return {"!!map"}; case TAG_OMAP: return {"!!omap"}; case TAG_PAIRS: return {"!!pairs"}; case TAG_SET: return {"!!set"}; case TAG_SEQ: return {"!!seq"}; case TAG_BINARY: return {"!!binary"}; case TAG_BOOL: return {"!!bool"}; case TAG_FLOAT: return {"!!float"}; case TAG_INT: return {"!!int"}; case TAG_MERGE: return {"!!merge"}; case TAG_NULL: return {"!!null"}; case TAG_STR: return {"!!str"}; case TAG_TIMESTAMP: return {"!!timestamp"}; case TAG_VALUE: return {"!!value"}; case TAG_YAML: return {"!!yaml"}; case TAG_NONE: default: return {""}; } } bool TagDirective::create_from_str(csubstr directive_) { csubstr directive = directive_; directive = directive.sub(4); if(!directive.begins_with(' ')) return false; directive = directive.triml(' '); size_t pos = directive.find(' '); if(pos == npos) return false; handle = directive.first(pos); directive = directive.sub(handle.len).triml(' '); pos = directive.find(' '); if(pos != npos) directive = directive.first(pos); prefix = directive; next_node_id = NONE; _c4dbgpf("%TAG: handle={} prefix={}", handle, prefix); return true; } bool TagDirective::create_from_str(csubstr directive_, Tree *tree) { _RYML_CB_CHECK(tree->callbacks(), directive_.begins_with("%TAG ")); if(!create_from_str(directive_)) { _RYML_CB_ERR(tree->callbacks(), "invalid tag directive"); } next_node_id = tree->size(); if(!tree->empty()) { const id_type prev = tree->size() - 1; if(tree->is_root(prev) && tree->type(prev) != NOTYPE && !tree->is_stream(prev)) ++next_node_id; } _c4dbgpf("%TAG: handle={} prefix={} next_node={}", handle, prefix, next_node_id); return true; } size_t TagDirective::transform(csubstr tag, substr output, Callbacks const& callbacks) const { _c4dbgpf("%TAG: handle={} prefix={} next_node={}. tag={}", handle, prefix, next_node_id, tag); _RYML_CB_ASSERT(callbacks, tag.len >= handle.len); csubstr rest = tag.sub(handle.len); _c4dbgpf("%TAG: rest={}", rest); if(rest.begins_with('<')) { _c4dbgpf("%TAG: begins with <. rest={}", rest); if(C4_UNLIKELY(!rest.ends_with('>'))) _RYML_CB_ERR(callbacks, "malformed tag"); rest = rest.offs(1, 1); if(rest.begins_with(prefix)) { _c4dbgpf("%TAG: already transformed! actual={}", rest.sub(prefix.len)); return 0; // return 0 to signal that the tag is local and cannot be resolved } } size_t len = 1u + prefix.len + rest.len + 1u; size_t numpc = rest.count('%'); if(numpc == 0) { if(len <= output.len) { output.str[0] = '<'; memcpy(1u + output.str, prefix.str, prefix.len); memcpy(1u + output.str + prefix.len, rest.str, rest.len); output.str[1u + prefix.len + rest.len] = '>'; } } else { // need to decode URI % sequences size_t pos = rest.find('%'); _RYML_CB_ASSERT(callbacks, pos != npos); do { size_t next = rest.first_not_of("0123456789abcdefABCDEF", pos+1); if(next == npos) next = rest.len; _RYML_CB_CHECK(callbacks, pos+1 < next); _RYML_CB_CHECK(callbacks, pos+1 + 2 <= next); size_t delta = next - (pos+1); len -= delta; pos = rest.find('%', pos+1); } while(pos != npos); if(len <= output.len) { size_t prev = 0, wpos = 0; auto appendstr = [&](csubstr s) { memcpy(output.str + wpos, s.str, s.len); wpos += s.len; }; auto appendchar = [&](char c) { output.str[wpos++] = c; }; appendchar('<'); appendstr(prefix); pos = rest.find('%'); _RYML_CB_ASSERT(callbacks, pos != npos); do { size_t next = rest.first_not_of("0123456789abcdefABCDEF", pos+1); if(next == npos) next = rest.len; _RYML_CB_CHECK(callbacks, pos+1 < next); _RYML_CB_CHECK(callbacks, pos+1 + 2 <= next); uint8_t val; if(C4_UNLIKELY(!read_hex(rest.range(pos+1, next), &val) || val > 127)) _RYML_CB_ERR(callbacks, "invalid URI character"); appendstr(rest.range(prev, pos)); appendchar(static_cast(val)); prev = next; pos = rest.find('%', pos+1); } while(pos != npos); _RYML_CB_ASSERT(callbacks, pos == npos); _RYML_CB_ASSERT(callbacks, prev > 0); _RYML_CB_ASSERT(callbacks, rest.len >= prev); appendstr(rest.sub(prev)); appendchar('>'); _RYML_CB_ASSERT(callbacks, wpos == len); } } return len; } } // namespace yml } // namespace c4 #endif /* RYML_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/tag.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/tree.cpp // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef RYML_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp //#include "c4/yml/tree.hpp" #if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) #error "amalgamate: file c4/yml/tree.hpp must have been included at this point" #endif /* C4_YML_TREE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //#include "c4/yml/detail/parser_dbg.hpp" #if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) #error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp //#include "c4/yml/node.hpp" #if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) #error "amalgamate: file c4/yml/node.hpp must have been included at this point" #endif /* C4_YML_NODE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/reference_resolver.hpp //#include "c4/yml/reference_resolver.hpp" #if !defined(C4_YML_REFERENCE_RESOLVER_HPP_) && !defined(_C4_YML_REFERENCE_RESOLVER_HPP_) #error "amalgamate: file c4/yml/reference_resolver.hpp must have been included at this point" #endif /* C4_YML_REFERENCE_RESOLVER_HPP_ */ C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4296/*expression is always 'boolean_value'*/) C4_SUPPRESS_WARNING_MSVC(4702/*unreachable code*/) C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") C4_SUPPRESS_WARNING_GCC("-Wtype-limits") C4_SUPPRESS_WARNING_GCC("-Wuseless-cast") namespace c4 { namespace yml { //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- NodeRef Tree::rootref() { return NodeRef(this, root_id()); } ConstNodeRef Tree::rootref() const { return ConstNodeRef(this, root_id()); } ConstNodeRef Tree::crootref() const { return ConstNodeRef(this, root_id()); } NodeRef Tree::ref(id_type id) { _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_cap); return NodeRef(this, id); } ConstNodeRef Tree::ref(id_type id) const { _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_cap); return ConstNodeRef(this, id); } ConstNodeRef Tree::cref(id_type id) const { _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_cap); return ConstNodeRef(this, id); } NodeRef Tree::operator[] (csubstr key) { return rootref()[key]; } ConstNodeRef Tree::operator[] (csubstr key) const { return rootref()[key]; } NodeRef Tree::operator[] (id_type i) { return rootref()[i]; } ConstNodeRef Tree::operator[] (id_type i) const { return rootref()[i]; } NodeRef Tree::docref(id_type i) { return ref(doc(i)); } ConstNodeRef Tree::docref(id_type i) const { return cref(doc(i)); } ConstNodeRef Tree::cdocref(id_type i) const { return cref(doc(i)); } //----------------------------------------------------------------------------- Tree::Tree(Callbacks const& cb) : m_buf(nullptr) , m_cap(0) , m_size(0) , m_free_head(NONE) , m_free_tail(NONE) , m_arena() , m_arena_pos(0) , m_callbacks(cb) , m_tag_directives() { } Tree::Tree(id_type node_capacity, size_t arena_capacity, Callbacks const& cb) : Tree(cb) { reserve(node_capacity); reserve_arena(arena_capacity); } Tree::~Tree() { _free(); } Tree::Tree(Tree const& that) : Tree(that.m_callbacks) { _copy(that); } Tree& Tree::operator= (Tree const& that) { if(&that != this) { _free(); m_callbacks = that.m_callbacks; _copy(that); } return *this; } Tree::Tree(Tree && that) noexcept : Tree(that.m_callbacks) { _move(that); } Tree& Tree::operator= (Tree && that) noexcept { if(&that != this) { _free(); m_callbacks = that.m_callbacks; _move(that); } return *this; } void Tree::_free() { if(m_buf) { _RYML_CB_ASSERT(m_callbacks, m_cap > 0); _RYML_CB_FREE(m_callbacks, m_buf, NodeData, (size_t)m_cap); } if(m_arena.str) { _RYML_CB_ASSERT(m_callbacks, m_arena.len > 0); _RYML_CB_FREE(m_callbacks, m_arena.str, char, m_arena.len); } _clear(); } C4_SUPPRESS_WARNING_GCC_PUSH #if defined(__GNUC__) && __GNUC__>= 8 C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wclass-memaccess") // error: ‘void* memset(void*, int, size_t)’ clearing an object of type ‘class c4::yml::Tree’ with no trivial copy-assignment; use assignment or value-initialization instead #endif void Tree::_clear() { m_buf = nullptr; m_cap = 0; m_size = 0; m_free_head = 0; m_free_tail = 0; m_arena = {}; m_arena_pos = 0; for(id_type i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) m_tag_directives[i] = {}; } void Tree::_copy(Tree const& that) { _RYML_CB_ASSERT(m_callbacks, m_buf == nullptr); _RYML_CB_ASSERT(m_callbacks, m_arena.str == nullptr); _RYML_CB_ASSERT(m_callbacks, m_arena.len == 0); if(that.m_cap) { m_buf = _RYML_CB_ALLOC_HINT(m_callbacks, NodeData, (size_t)that.m_cap, that.m_buf); memcpy(m_buf, that.m_buf, (size_t)that.m_cap * sizeof(NodeData)); } m_cap = that.m_cap; m_size = that.m_size; m_free_head = that.m_free_head; m_free_tail = that.m_free_tail; m_arena_pos = that.m_arena_pos; m_arena = that.m_arena; if(that.m_arena.str) { _RYML_CB_ASSERT(m_callbacks, that.m_arena.len > 0); substr arena; arena.str = _RYML_CB_ALLOC_HINT(m_callbacks, char, that.m_arena.len, that.m_arena.str); arena.len = that.m_arena.len; _relocate(arena); // does a memcpy of the arena and updates nodes using the old arena m_arena = arena; } for(id_type i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) m_tag_directives[i] = that.m_tag_directives[i]; } void Tree::_move(Tree & that) noexcept { _RYML_CB_ASSERT(m_callbacks, m_buf == nullptr); _RYML_CB_ASSERT(m_callbacks, m_arena.str == nullptr); _RYML_CB_ASSERT(m_callbacks, m_arena.len == 0); m_buf = that.m_buf; m_cap = that.m_cap; m_size = that.m_size; m_free_head = that.m_free_head; m_free_tail = that.m_free_tail; m_arena = that.m_arena; m_arena_pos = that.m_arena_pos; for(id_type i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) m_tag_directives[i] = that.m_tag_directives[i]; that._clear(); } void Tree::_relocate(substr next_arena) { _RYML_CB_ASSERT(m_callbacks, next_arena.not_empty()); _RYML_CB_ASSERT(m_callbacks, next_arena.len >= m_arena.len); if(m_arena_pos) memcpy(next_arena.str, m_arena.str, m_arena_pos); for(NodeData *C4_RESTRICT n = m_buf, *e = m_buf + m_cap; n != e; ++n) { if(in_arena(n->m_key.scalar)) n->m_key.scalar = _relocated(n->m_key.scalar, next_arena); if(in_arena(n->m_key.tag)) n->m_key.tag = _relocated(n->m_key.tag, next_arena); if(in_arena(n->m_key.anchor)) n->m_key.anchor = _relocated(n->m_key.anchor, next_arena); if(in_arena(n->m_val.scalar)) n->m_val.scalar = _relocated(n->m_val.scalar, next_arena); if(in_arena(n->m_val.tag)) n->m_val.tag = _relocated(n->m_val.tag, next_arena); if(in_arena(n->m_val.anchor)) n->m_val.anchor = _relocated(n->m_val.anchor, next_arena); } for(TagDirective &C4_RESTRICT td : m_tag_directives) { if(in_arena(td.prefix)) td.prefix = _relocated(td.prefix, next_arena); if(in_arena(td.handle)) td.handle = _relocated(td.handle, next_arena); } } //----------------------------------------------------------------------------- void Tree::reserve(id_type cap) { if(cap > m_cap) { NodeData *buf = _RYML_CB_ALLOC_HINT(m_callbacks, NodeData, (size_t)cap, m_buf); if(m_buf) { memcpy(buf, m_buf, (size_t)m_cap * sizeof(NodeData)); _RYML_CB_FREE(m_callbacks, m_buf, NodeData, (size_t)m_cap); } id_type first = m_cap, del = cap - m_cap; m_cap = cap; m_buf = buf; _clear_range(first, del); if(m_free_head != NONE) { _RYML_CB_ASSERT(m_callbacks, m_buf != nullptr); _RYML_CB_ASSERT(m_callbacks, m_free_tail != NONE); m_buf[m_free_tail].m_next_sibling = first; m_buf[first].m_prev_sibling = m_free_tail; m_free_tail = cap-1; } else { _RYML_CB_ASSERT(m_callbacks, m_free_tail == NONE); m_free_head = first; m_free_tail = cap-1; } _RYML_CB_ASSERT(m_callbacks, m_free_head == NONE || (m_free_head >= 0 && m_free_head < cap)); _RYML_CB_ASSERT(m_callbacks, m_free_tail == NONE || (m_free_tail >= 0 && m_free_tail < cap)); if( ! m_size) _claim_root(); } } //----------------------------------------------------------------------------- void Tree::clear() { _clear_range(0, m_cap); m_size = 0; if(m_buf) { _RYML_CB_ASSERT(m_callbacks, m_cap >= 0); m_free_head = 0; m_free_tail = m_cap-1; _claim_root(); } else { m_free_head = NONE; m_free_tail = NONE; } for(id_type i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) m_tag_directives[i] = {}; } void Tree::_claim_root() { id_type r = _claim(); _RYML_CB_ASSERT(m_callbacks, r == 0); _set_hierarchy(r, NONE, NONE); } //----------------------------------------------------------------------------- void Tree::_clear_range(id_type first, id_type num) { if(num == 0) return; // prevent overflow when subtracting _RYML_CB_ASSERT(m_callbacks, first >= 0 && first + num <= m_cap); memset(m_buf + first, 0, (size_t)num * sizeof(NodeData)); // TODO we should not need this for(id_type i = first, e = first + num; i < e; ++i) { _clear(i); NodeData *n = m_buf + i; n->m_prev_sibling = i - 1; n->m_next_sibling = i + 1; } m_buf[first + num - 1].m_next_sibling = NONE; } C4_SUPPRESS_WARNING_GCC_POP //----------------------------------------------------------------------------- void Tree::_release(id_type i) { _RYML_CB_ASSERT(m_callbacks, i >= 0 && i < m_cap); _rem_hierarchy(i); _free_list_add(i); _clear(i); --m_size; } //----------------------------------------------------------------------------- // add to the front of the free list void Tree::_free_list_add(id_type i) { _RYML_CB_ASSERT(m_callbacks, i >= 0 && i < m_cap); NodeData &C4_RESTRICT w = m_buf[i]; w.m_parent = NONE; w.m_next_sibling = m_free_head; w.m_prev_sibling = NONE; if(m_free_head != NONE) m_buf[m_free_head].m_prev_sibling = i; m_free_head = i; if(m_free_tail == NONE) m_free_tail = m_free_head; } void Tree::_free_list_rem(id_type i) { if(m_free_head == i) m_free_head = _p(i)->m_next_sibling; _rem_hierarchy(i); } //----------------------------------------------------------------------------- id_type Tree::_claim() { if(m_free_head == NONE || m_buf == nullptr) { id_type sz = 2 * m_cap; sz = sz ? sz : 16; reserve(sz); _RYML_CB_ASSERT(m_callbacks, m_free_head != NONE); } _RYML_CB_ASSERT(m_callbacks, m_size < m_cap); _RYML_CB_ASSERT(m_callbacks, m_free_head >= 0 && m_free_head < m_cap); id_type ichild = m_free_head; NodeData *child = m_buf + ichild; ++m_size; m_free_head = child->m_next_sibling; if(m_free_head == NONE) { m_free_tail = NONE; _RYML_CB_ASSERT(m_callbacks, m_size == m_cap); } _clear(ichild); return ichild; } //----------------------------------------------------------------------------- C4_SUPPRESS_WARNING_GCC_PUSH C4_SUPPRESS_WARNING_CLANG_PUSH C4_SUPPRESS_WARNING_CLANG("-Wnull-dereference") #if defined(__GNUC__) #if (__GNUC__ >= 6) C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") #endif #if (__GNUC__ > 9) C4_SUPPRESS_WARNING_GCC("-Wanalyzer-fd-leak") #endif #endif void Tree::_set_hierarchy(id_type ichild, id_type iparent, id_type iprev_sibling) { _RYML_CB_ASSERT(m_callbacks, ichild >= 0 && ichild < m_cap); _RYML_CB_ASSERT(m_callbacks, iparent == NONE || (iparent >= 0 && iparent < m_cap)); _RYML_CB_ASSERT(m_callbacks, iprev_sibling == NONE || (iprev_sibling >= 0 && iprev_sibling < m_cap)); NodeData *C4_RESTRICT child = _p(ichild); child->m_parent = iparent; child->m_prev_sibling = NONE; child->m_next_sibling = NONE; if(iparent == NONE) { _RYML_CB_ASSERT(m_callbacks, ichild == 0); _RYML_CB_ASSERT(m_callbacks, iprev_sibling == NONE); } if(iparent == NONE) return; id_type inext_sibling = iprev_sibling != NONE ? next_sibling(iprev_sibling) : first_child(iparent); NodeData *C4_RESTRICT parent = get(iparent); NodeData *C4_RESTRICT psib = get(iprev_sibling); NodeData *C4_RESTRICT nsib = get(inext_sibling); if(psib) { _RYML_CB_ASSERT(m_callbacks, next_sibling(iprev_sibling) == id(nsib)); child->m_prev_sibling = id(psib); psib->m_next_sibling = id(child); _RYML_CB_ASSERT(m_callbacks, psib->m_prev_sibling != psib->m_next_sibling || psib->m_prev_sibling == NONE); } if(nsib) { _RYML_CB_ASSERT(m_callbacks, prev_sibling(inext_sibling) == id(psib)); child->m_next_sibling = id(nsib); nsib->m_prev_sibling = id(child); _RYML_CB_ASSERT(m_callbacks, nsib->m_prev_sibling != nsib->m_next_sibling || nsib->m_prev_sibling == NONE); } if(parent->m_first_child == NONE) { _RYML_CB_ASSERT(m_callbacks, parent->m_last_child == NONE); parent->m_first_child = id(child); parent->m_last_child = id(child); } else { if(child->m_next_sibling == parent->m_first_child) parent->m_first_child = id(child); if(child->m_prev_sibling == parent->m_last_child) parent->m_last_child = id(child); } } C4_SUPPRESS_WARNING_GCC_POP C4_SUPPRESS_WARNING_CLANG_POP //----------------------------------------------------------------------------- void Tree::_rem_hierarchy(id_type i) { _RYML_CB_ASSERT(m_callbacks, i >= 0 && i < m_cap); NodeData &C4_RESTRICT w = m_buf[i]; // remove from the parent if(w.m_parent != NONE) { NodeData &C4_RESTRICT p = m_buf[w.m_parent]; if(p.m_first_child == i) { p.m_first_child = w.m_next_sibling; } if(p.m_last_child == i) { p.m_last_child = w.m_prev_sibling; } } // remove from the used list if(w.m_prev_sibling != NONE) { NodeData *C4_RESTRICT prev = get(w.m_prev_sibling); prev->m_next_sibling = w.m_next_sibling; } if(w.m_next_sibling != NONE) { NodeData *C4_RESTRICT next = get(w.m_next_sibling); next->m_prev_sibling = w.m_prev_sibling; } } //----------------------------------------------------------------------------- /** @cond dev */ id_type Tree::_do_reorder(id_type *node, id_type count) { // swap this node if it's not in place if(*node != count) { _swap(*node, count); *node = count; } ++count; // bump the count from this node // now descend in the hierarchy for(id_type i = first_child(*node); i != NONE; i = next_sibling(i)) { // this child may have been relocated to a different index, // so get an updated version count = _do_reorder(&i, count); } return count; } /** @endcond */ void Tree::reorder() { id_type r = root_id(); _do_reorder(&r, 0); } //----------------------------------------------------------------------------- /** @cond dev */ void Tree::_swap(id_type n_, id_type m_) { _RYML_CB_ASSERT(m_callbacks, (parent(n_) != NONE) || type(n_) == NOTYPE); _RYML_CB_ASSERT(m_callbacks, (parent(m_) != NONE) || type(m_) == NOTYPE); NodeType tn = type(n_); NodeType tm = type(m_); if(tn != NOTYPE && tm != NOTYPE) { _swap_props(n_, m_); _swap_hierarchy(n_, m_); } else if(tn == NOTYPE && tm != NOTYPE) { _copy_props(n_, m_); _free_list_rem(n_); _copy_hierarchy(n_, m_); _clear(m_); _free_list_add(m_); } else if(tn != NOTYPE && tm == NOTYPE) { _copy_props(m_, n_); _free_list_rem(m_); _copy_hierarchy(m_, n_); _clear(n_); _free_list_add(n_); } else { C4_NEVER_REACH(); } } //----------------------------------------------------------------------------- void Tree::_swap_hierarchy(id_type ia, id_type ib) { if(ia == ib) return; for(id_type i = first_child(ia); i != NONE; i = next_sibling(i)) { if(i == ib || i == ia) continue; _p(i)->m_parent = ib; } for(id_type i = first_child(ib); i != NONE; i = next_sibling(i)) { if(i == ib || i == ia) continue; _p(i)->m_parent = ia; } auto & C4_RESTRICT a = *_p(ia); auto & C4_RESTRICT b = *_p(ib); auto & C4_RESTRICT pa = *_p(a.m_parent); auto & C4_RESTRICT pb = *_p(b.m_parent); if(&pa == &pb) { if((pa.m_first_child == ib && pa.m_last_child == ia) || (pa.m_first_child == ia && pa.m_last_child == ib)) { std::swap(pa.m_first_child, pa.m_last_child); } else { bool changed = false; if(pa.m_first_child == ia) { pa.m_first_child = ib; changed = true; } if(pa.m_last_child == ia) { pa.m_last_child = ib; changed = true; } if(pb.m_first_child == ib && !changed) { pb.m_first_child = ia; } if(pb.m_last_child == ib && !changed) { pb.m_last_child = ia; } } } else { if(pa.m_first_child == ia) pa.m_first_child = ib; if(pa.m_last_child == ia) pa.m_last_child = ib; if(pb.m_first_child == ib) pb.m_first_child = ia; if(pb.m_last_child == ib) pb.m_last_child = ia; } std::swap(a.m_first_child , b.m_first_child); std::swap(a.m_last_child , b.m_last_child); if(a.m_prev_sibling != ib && b.m_prev_sibling != ia && a.m_next_sibling != ib && b.m_next_sibling != ia) { if(a.m_prev_sibling != NONE && a.m_prev_sibling != ib) _p(a.m_prev_sibling)->m_next_sibling = ib; if(a.m_next_sibling != NONE && a.m_next_sibling != ib) _p(a.m_next_sibling)->m_prev_sibling = ib; if(b.m_prev_sibling != NONE && b.m_prev_sibling != ia) _p(b.m_prev_sibling)->m_next_sibling = ia; if(b.m_next_sibling != NONE && b.m_next_sibling != ia) _p(b.m_next_sibling)->m_prev_sibling = ia; std::swap(a.m_prev_sibling, b.m_prev_sibling); std::swap(a.m_next_sibling, b.m_next_sibling); } else { if(a.m_next_sibling == ib) // n will go after m { _RYML_CB_ASSERT(m_callbacks, b.m_prev_sibling == ia); if(a.m_prev_sibling != NONE) { _RYML_CB_ASSERT(m_callbacks, a.m_prev_sibling != ib); _p(a.m_prev_sibling)->m_next_sibling = ib; } if(b.m_next_sibling != NONE) { _RYML_CB_ASSERT(m_callbacks, b.m_next_sibling != ia); _p(b.m_next_sibling)->m_prev_sibling = ia; } id_type ns = b.m_next_sibling; b.m_prev_sibling = a.m_prev_sibling; b.m_next_sibling = ia; a.m_prev_sibling = ib; a.m_next_sibling = ns; } else if(a.m_prev_sibling == ib) // m will go after n { _RYML_CB_ASSERT(m_callbacks, b.m_next_sibling == ia); if(b.m_prev_sibling != NONE) { _RYML_CB_ASSERT(m_callbacks, b.m_prev_sibling != ia); _p(b.m_prev_sibling)->m_next_sibling = ia; } if(a.m_next_sibling != NONE) { _RYML_CB_ASSERT(m_callbacks, a.m_next_sibling != ib); _p(a.m_next_sibling)->m_prev_sibling = ib; } id_type ns = b.m_prev_sibling; a.m_prev_sibling = b.m_prev_sibling; a.m_next_sibling = ib; b.m_prev_sibling = ia; b.m_next_sibling = ns; } else { C4_NEVER_REACH(); } } _RYML_CB_ASSERT(m_callbacks, a.m_next_sibling != ia); _RYML_CB_ASSERT(m_callbacks, a.m_prev_sibling != ia); _RYML_CB_ASSERT(m_callbacks, b.m_next_sibling != ib); _RYML_CB_ASSERT(m_callbacks, b.m_prev_sibling != ib); if(a.m_parent != ib && b.m_parent != ia) { std::swap(a.m_parent, b.m_parent); } else { if(a.m_parent == ib && b.m_parent != ia) { a.m_parent = b.m_parent; b.m_parent = ia; } else if(a.m_parent != ib && b.m_parent == ia) { b.m_parent = a.m_parent; a.m_parent = ib; } else { C4_NEVER_REACH(); } } } //----------------------------------------------------------------------------- void Tree::_copy_hierarchy(id_type dst_, id_type src_) { auto const& C4_RESTRICT src = *_p(src_); auto & C4_RESTRICT dst = *_p(dst_); auto & C4_RESTRICT prt = *_p(src.m_parent); for(id_type i = src.m_first_child; i != NONE; i = next_sibling(i)) { _p(i)->m_parent = dst_; } if(src.m_prev_sibling != NONE) { _p(src.m_prev_sibling)->m_next_sibling = dst_; } if(src.m_next_sibling != NONE) { _p(src.m_next_sibling)->m_prev_sibling = dst_; } if(prt.m_first_child == src_) { prt.m_first_child = dst_; } if(prt.m_last_child == src_) { prt.m_last_child = dst_; } dst.m_parent = src.m_parent; dst.m_first_child = src.m_first_child; dst.m_last_child = src.m_last_child; dst.m_prev_sibling = src.m_prev_sibling; dst.m_next_sibling = src.m_next_sibling; } //----------------------------------------------------------------------------- void Tree::_swap_props(id_type n_, id_type m_) { NodeData &C4_RESTRICT n = *_p(n_); NodeData &C4_RESTRICT m = *_p(m_); std::swap(n.m_type, m.m_type); std::swap(n.m_key, m.m_key); std::swap(n.m_val, m.m_val); } /** @endcond */ //----------------------------------------------------------------------------- void Tree::move(id_type node, id_type after) { _RYML_CB_ASSERT(m_callbacks, node != NONE); _RYML_CB_ASSERT(m_callbacks, node != after); _RYML_CB_ASSERT(m_callbacks, ! is_root(node)); _RYML_CB_ASSERT(m_callbacks, (after == NONE) || (has_sibling(node, after) && has_sibling(after, node))); _rem_hierarchy(node); _set_hierarchy(node, parent(node), after); } //----------------------------------------------------------------------------- void Tree::move(id_type node, id_type new_parent, id_type after) { _RYML_CB_ASSERT(m_callbacks, node != NONE); _RYML_CB_ASSERT(m_callbacks, node != after); _RYML_CB_ASSERT(m_callbacks, new_parent != NONE); _RYML_CB_ASSERT(m_callbacks, new_parent != node); _RYML_CB_ASSERT(m_callbacks, new_parent != after); _RYML_CB_ASSERT(m_callbacks, ! is_root(node)); _rem_hierarchy(node); _set_hierarchy(node, new_parent, after); } id_type Tree::move(Tree *src, id_type node, id_type new_parent, id_type after) { _RYML_CB_ASSERT(m_callbacks, src != nullptr); _RYML_CB_ASSERT(m_callbacks, node != NONE); _RYML_CB_ASSERT(m_callbacks, new_parent != NONE); _RYML_CB_ASSERT(m_callbacks, new_parent != after); id_type dup = duplicate(src, node, new_parent, after); src->remove(node); return dup; } void Tree::set_root_as_stream() { id_type root = root_id(); if(is_stream(root)) return; // don't use _add_flags() because it's checked and will fail if(!has_children(root)) { if(is_val(root)) { _p(root)->m_type.add(SEQ); id_type next_doc = append_child(root); _copy_props_wo_key(next_doc, root); _p(next_doc)->m_type.add(DOC); _p(next_doc)->m_type.rem(SEQ); } _p(root)->m_type = STREAM; return; } _RYML_CB_ASSERT(m_callbacks, !has_key(root)); id_type next_doc = append_child(root); _copy_props_wo_key(next_doc, root); _add_flags(next_doc, DOC); for(id_type prev = NONE, ch = first_child(root), next = next_sibling(ch); ch != NONE; ) { if(ch == next_doc) break; move(ch, next_doc, prev); prev = ch; ch = next; next = next_sibling(next); } _p(root)->m_type = STREAM; } //----------------------------------------------------------------------------- void Tree::remove_children(id_type node) { _RYML_CB_ASSERT(m_callbacks, get(node) != nullptr); id_type ich = get(node)->m_first_child; while(ich != NONE) { remove_children(ich); _RYML_CB_ASSERT(m_callbacks, get(ich) != nullptr); id_type next = get(ich)->m_next_sibling; _release(ich); if(ich == get(node)->m_last_child) break; ich = next; } } bool Tree::change_type(id_type node, NodeType type) { _RYML_CB_ASSERT(m_callbacks, type.is_val() || type.is_map() || type.is_seq()); _RYML_CB_ASSERT(m_callbacks, type.is_val() + type.is_map() + type.is_seq() == 1); _RYML_CB_ASSERT(m_callbacks, type.has_key() == has_key(node) || (has_key(node) && !type.has_key())); NodeData *d = _p(node); if(type.is_map() && is_map(node)) return false; else if(type.is_seq() && is_seq(node)) return false; else if(type.is_val() && is_val(node)) return false; d->m_type = (d->m_type & (~(MAP|SEQ|VAL))) | type; remove_children(node); return true; } //----------------------------------------------------------------------------- id_type Tree::duplicate(id_type node, id_type parent, id_type after) { return duplicate(this, node, parent, after); } id_type Tree::duplicate(Tree const* src, id_type node, id_type parent, id_type after) { _RYML_CB_ASSERT(m_callbacks, src != nullptr); _RYML_CB_ASSERT(m_callbacks, node != NONE); _RYML_CB_ASSERT(m_callbacks, parent != NONE); _RYML_CB_ASSERT(m_callbacks, ! src->is_root(node)); id_type copy = _claim(); _copy_props(copy, src, node); _set_hierarchy(copy, parent, after); duplicate_children(src, node, copy, NONE); return copy; } //----------------------------------------------------------------------------- id_type Tree::duplicate_children(id_type node, id_type parent, id_type after) { return duplicate_children(this, node, parent, after); } id_type Tree::duplicate_children(Tree const* src, id_type node, id_type parent, id_type after) { _RYML_CB_ASSERT(m_callbacks, src != nullptr); _RYML_CB_ASSERT(m_callbacks, node != NONE); _RYML_CB_ASSERT(m_callbacks, parent != NONE); _RYML_CB_ASSERT(m_callbacks, after == NONE || has_child(parent, after)); id_type prev = after; for(id_type i = src->first_child(node); i != NONE; i = src->next_sibling(i)) { prev = duplicate(src, i, parent, prev); } return prev; } //----------------------------------------------------------------------------- void Tree::duplicate_contents(id_type node, id_type where) { duplicate_contents(this, node, where); } void Tree::duplicate_contents(Tree const *src, id_type node, id_type where) { _RYML_CB_ASSERT(m_callbacks, src != nullptr); _RYML_CB_ASSERT(m_callbacks, node != NONE); _RYML_CB_ASSERT(m_callbacks, where != NONE); _copy_props_wo_key(where, src, node); duplicate_children(src, node, where, last_child(where)); } //----------------------------------------------------------------------------- id_type Tree::duplicate_children_no_rep(id_type node, id_type parent, id_type after) { return duplicate_children_no_rep(this, node, parent, after); } id_type Tree::duplicate_children_no_rep(Tree const *src, id_type node, id_type parent, id_type after) { _RYML_CB_ASSERT(m_callbacks, node != NONE); _RYML_CB_ASSERT(m_callbacks, parent != NONE); _RYML_CB_ASSERT(m_callbacks, after == NONE || has_child(parent, after)); // don't loop using pointers as there may be a relocation // find the position where "after" is id_type after_pos = NONE; if(after != NONE) { for(id_type i = first_child(parent), icount = 0; i != NONE; ++icount, i = next_sibling(i)) { if(i == after) { after_pos = icount; break; } } _RYML_CB_ASSERT(m_callbacks, after_pos != NONE); } // for each child to be duplicated... id_type prev = after; for(id_type i = src->first_child(node); i != NONE; i = src->next_sibling(i)) { _c4dbgpf("duplicate_no_rep: {} -> {}/{}", i, parent, prev); _RYML_CB_CHECK(m_callbacks, this != src || (parent != i && !is_ancestor(parent, i))); if(is_seq(parent)) { _c4dbgpf("duplicate_no_rep: {} is seq", parent); prev = duplicate(src, i, parent, prev); } else { _c4dbgpf("duplicate_no_rep: {} is map", parent); _RYML_CB_ASSERT(m_callbacks, is_map(parent)); // does the parent already have a node with key equal to that of the current duplicate? id_type dstnode_dup = NONE, dstnode_dup_pos = NONE; { csubstr srckey = src->key(i); for(id_type j = first_child(parent), jcount = 0; j != NONE; ++jcount, j = next_sibling(j)) { if(key(j) == srckey) { _c4dbgpf("duplicate_no_rep: found matching key '{}' src={}/{} dst={}/{}", srckey, node, i, parent, j); dstnode_dup = j; dstnode_dup_pos = jcount; break; } } } _c4dbgpf("duplicate_no_rep: dstnode_dup={} dstnode_dup_pos={} after_pos={}", dstnode_dup, dstnode_dup_pos, after_pos); if(dstnode_dup == NONE) // there is no repetition; just duplicate { _c4dbgpf("duplicate_no_rep: no repetition, just duplicate i={} parent={} prev={}", i, parent, prev); prev = duplicate(src, i, parent, prev); } else // yes, there is a repetition { if(after_pos != NONE && dstnode_dup_pos <= after_pos) { // the dst duplicate is located before the node which will be inserted, // and will be overridden by the duplicate. So replace it. _c4dbgpf("duplicate_no_dstnode_dup: replace {}/{} with {}/{}", parent, dstnode_dup, node, i); if(prev == dstnode_dup) prev = prev_sibling(dstnode_dup); remove(dstnode_dup); prev = duplicate(src, i, parent, prev); } else if(prev == NONE) { _c4dbgpf("duplicate_no_dstnode_dup: {}=prev <- {}", prev, dstnode_dup); // first iteration with prev = after = NONE and dstnode_dupetition prev = dstnode_dup; } else if(dstnode_dup != prev) { // dstnode_dup is located after the node which will be inserted // and overrides it. So move the dstnode_dup into this node's place. _c4dbgpf("duplicate_no_dstnode_dup: move({}, {})", dstnode_dup, prev); move(dstnode_dup, prev); prev = dstnode_dup; } } // there's a dstnode_dupetition } } return prev; } //----------------------------------------------------------------------------- void Tree::merge_with(Tree const *src, id_type src_node, id_type dst_node) { _RYML_CB_ASSERT(m_callbacks, src != nullptr); if(src_node == NONE) src_node = src->root_id(); if(dst_node == NONE) dst_node = root_id(); _RYML_CB_ASSERT(m_callbacks, src->has_val(src_node) || src->is_seq(src_node) || src->is_map(src_node)); if(src->has_val(src_node)) { type_bits mask_src = ~STYLE; // keep the existing style if it is already a val if( ! has_val(dst_node)) { if(has_children(dst_node)) remove_children(dst_node); mask_src |= VAL_STYLE; // copy the src style } if(src->is_keyval(src_node)) { _copy_props(dst_node, src, src_node, mask_src); } else { _RYML_CB_ASSERT(m_callbacks, src->is_val(src_node)); _copy_props_wo_key(dst_node, src, src_node, mask_src); } } else if(src->is_seq(src_node)) { if( ! is_seq(dst_node)) { if(has_children(dst_node)) remove_children(dst_node); _clear_type(dst_node); if(src->has_key(src_node)) to_seq(dst_node, src->key(src_node)); else to_seq(dst_node); _p(dst_node)->m_type = src->_p(src_node)->m_type; } for(id_type sch = src->first_child(src_node); sch != NONE; sch = src->next_sibling(sch)) { id_type dch = append_child(dst_node); _copy_props_wo_key(dch, src, sch); merge_with(src, sch, dch); } } else { _RYML_CB_ASSERT(m_callbacks, src->is_map(src_node)); if( ! is_map(dst_node)) { if(has_children(dst_node)) remove_children(dst_node); _clear_type(dst_node); if(src->has_key(src_node)) to_map(dst_node, src->key(src_node)); else to_map(dst_node); _p(dst_node)->m_type = src->_p(src_node)->m_type; } for(id_type sch = src->first_child(src_node); sch != NONE; sch = src->next_sibling(sch)) { id_type dch = find_child(dst_node, src->key(sch)); if(dch == NONE) { dch = append_child(dst_node); _copy_props(dch, src, sch); } merge_with(src, sch, dch); } } } //----------------------------------------------------------------------------- void Tree::resolve(bool clear_anchors) { if(m_size == 0) return; ReferenceResolver rr; resolve(&rr, clear_anchors); } void Tree::resolve(ReferenceResolver *C4_RESTRICT rr, bool clear_anchors) { if(m_size == 0) return; rr->resolve(this, clear_anchors); } //----------------------------------------------------------------------------- id_type Tree::num_children(id_type node) const { id_type count = 0; for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) ++count; return count; } id_type Tree::child(id_type node, id_type pos) const { _RYML_CB_ASSERT(m_callbacks, node != NONE); id_type count = 0; for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) { if(count++ == pos) return i; } return NONE; } id_type Tree::child_pos(id_type node, id_type ch) const { _RYML_CB_ASSERT(m_callbacks, node != NONE); id_type count = 0; for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) { if(i == ch) return count; ++count; } return NONE; } #if defined(__clang__) # pragma clang diagnostic push # pragma GCC diagnostic ignored "-Wnull-dereference" #elif defined(__GNUC__) # pragma GCC diagnostic push # if __GNUC__ >= 6 # pragma GCC diagnostic ignored "-Wnull-dereference" # endif # if __GNUC__ > 9 # pragma GCC diagnostic ignored "-Wanalyzer-null-dereference" # endif #endif id_type Tree::find_child(id_type node, csubstr const& name) const { _RYML_CB_ASSERT(m_callbacks, node != NONE); _RYML_CB_ASSERT(m_callbacks, is_map(node)); if(get(node)->m_first_child == NONE) { _RYML_CB_ASSERT(m_callbacks, _p(node)->m_last_child == NONE); return NONE; } else { _RYML_CB_ASSERT(m_callbacks, _p(node)->m_last_child != NONE); } for(id_type i = first_child(node); i != NONE; i = next_sibling(i)) { if(_p(i)->m_key.scalar == name) { return i; } } return NONE; } #if defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif namespace { id_type depth_desc_(Tree const& C4_RESTRICT t, id_type id, id_type currdepth=0, id_type maxdepth=0) { maxdepth = currdepth > maxdepth ? currdepth : maxdepth; for(id_type child = t.first_child(id); child != NONE; child = t.next_sibling(child)) { const id_type d = depth_desc_(t, child, currdepth+1, maxdepth); maxdepth = d > maxdepth ? d : maxdepth; } return maxdepth; } } id_type Tree::depth_desc(id_type node) const { _RYML_CB_ASSERT(m_callbacks, node != NONE); return depth_desc_(*this, node); } id_type Tree::depth_asc(id_type node) const { _RYML_CB_ASSERT(m_callbacks, node != NONE); id_type depth = 0; while(!is_root(node)) { ++depth; node = parent(node); } return depth; } bool Tree::is_ancestor(id_type node, id_type ancestor) const { _RYML_CB_ASSERT(m_callbacks, node != NONE); id_type p = parent(node); while(p != NONE) { if(p == ancestor) return true; p = parent(p); } return false; } //----------------------------------------------------------------------------- void Tree::to_val(id_type node, csubstr val, type_bits more_flags) { _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || ! parent_is_map(node)); _set_flags(node, VAL|more_flags); _p(node)->m_key.clear(); _p(node)->m_val = val; } void Tree::to_keyval(id_type node, csubstr key, csubstr val, type_bits more_flags) { _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || parent_is_map(node)); _set_flags(node, KEYVAL|more_flags); _p(node)->m_key = key; _p(node)->m_val = val; } void Tree::to_map(id_type node, type_bits more_flags) { _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || ! parent_is_map(node)); // parent must not have children with keys _set_flags(node, MAP|more_flags); _p(node)->m_key.clear(); _p(node)->m_val.clear(); } void Tree::to_map(id_type node, csubstr key, type_bits more_flags) { _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || parent_is_map(node)); _set_flags(node, KEY|MAP|more_flags); _p(node)->m_key = key; _p(node)->m_val.clear(); } void Tree::to_seq(id_type node, type_bits more_flags) { _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || parent_is_seq(node)); _set_flags(node, SEQ|more_flags); _p(node)->m_key.clear(); _p(node)->m_val.clear(); } void Tree::to_seq(id_type node, csubstr key, type_bits more_flags) { _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); _RYML_CB_ASSERT(m_callbacks, parent(node) == NONE || parent_is_map(node)); _set_flags(node, KEY|SEQ|more_flags); _p(node)->m_key = key; _p(node)->m_val.clear(); } void Tree::to_doc(id_type node, type_bits more_flags) { _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); _set_flags(node, DOC|more_flags); _p(node)->m_key.clear(); _p(node)->m_val.clear(); } void Tree::to_stream(id_type node, type_bits more_flags) { _RYML_CB_ASSERT(m_callbacks, ! has_children(node)); _set_flags(node, STREAM|more_flags); _p(node)->m_key.clear(); _p(node)->m_val.clear(); } //----------------------------------------------------------------------------- id_type Tree::num_tag_directives() const { // this assumes we have a very small number of tag directives for(id_type i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i) if(m_tag_directives[i].handle.empty()) return i; return RYML_MAX_TAG_DIRECTIVES; } void Tree::clear_tag_directives() { for(TagDirective &td : m_tag_directives) td = {}; } id_type Tree::add_tag_directive(TagDirective const& td) { _RYML_CB_CHECK(m_callbacks, !td.handle.empty()); _RYML_CB_CHECK(m_callbacks, !td.prefix.empty()); _RYML_CB_CHECK(m_callbacks, td.handle.begins_with('!')); _RYML_CB_CHECK(m_callbacks, td.handle.ends_with('!')); // https://yaml.org/spec/1.2.2/#rule-ns-word-char _RYML_CB_CHECK(m_callbacks, td.handle == '!' || td.handle == "!!" || td.handle.trim('!').first_not_of("01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-") == npos); id_type pos = num_tag_directives(); _RYML_CB_CHECK(m_callbacks, pos < RYML_MAX_TAG_DIRECTIVES); m_tag_directives[pos] = td; return pos; } bool Tree::add_tag_directive(csubstr directive_) { TagDirective td; if(td.create_from_str(directive_, this)) { add_tag_directive(td); return true; } return false; } size_t Tree::resolve_tag(substr output, csubstr tag, id_type node_id) const { // lookup from the end. We want to find the first directive that // matches the tag and has a target node id leq than the given // node_id. for(id_type i = RYML_MAX_TAG_DIRECTIVES-1; i != (id_type)-1; --i) { auto const& td = m_tag_directives[i]; if(td.handle.empty()) continue; if(tag.begins_with(td.handle) && td.next_node_id <= node_id) return td.transform(tag, output, m_callbacks); } if(tag.begins_with('!')) { if(is_custom_tag(tag)) { _RYML_CB_ERR(m_callbacks, "tag directive not found"); } } return 0; // return 0 to signal that the tag is local and cannot be resolved } namespace { csubstr _transform_tag(Tree *t, csubstr tag, id_type node) { _c4dbgpf("[{}] resolving tag ~~~{}~~~", node, tag); size_t required_size = t->resolve_tag(substr{}, tag, node); if(!required_size) { if(tag.begins_with("!<")) tag = tag.sub(1); _c4dbgpf("[{}] resolved tag: ~~~{}~~~", node, tag); return tag; } const char *prev_arena = t->arena().str;(void)prev_arena; substr buf = t->alloc_arena(required_size); _RYML_CB_ASSERT(t->m_callbacks, t->arena().str == prev_arena); size_t actual_size = t->resolve_tag(buf, tag, node); _RYML_CB_ASSERT(t->m_callbacks, actual_size <= required_size); _c4dbgpf("[{}] resolved tag: ~~~{}~~~", node, buf.first(actual_size)); return buf.first(actual_size); } void _resolve_tags(Tree *t, id_type node) { NodeData *C4_RESTRICT d = t->_p(node); if(d->m_type & KEYTAG) d->m_key.tag = _transform_tag(t, d->m_key.tag, node); if(d->m_type & VALTAG) d->m_val.tag = _transform_tag(t, d->m_val.tag, node); for(id_type child = t->first_child(node); child != NONE; child = t->next_sibling(child)) _resolve_tags(t, child); } size_t _count_resolved_tags_size(Tree const* t, id_type node) { size_t sz = 0; NodeData const* C4_RESTRICT d = t->_p(node); if(d->m_type & KEYTAG) sz += t->resolve_tag(substr{}, d->m_key.tag, node); if(d->m_type & VALTAG) sz += t->resolve_tag(substr{}, d->m_val.tag, node); for(id_type child = t->first_child(node); child != NONE; child = t->next_sibling(child)) sz += _count_resolved_tags_size(t, child); return sz; } void _normalize_tags(Tree *t, id_type node) { NodeData *C4_RESTRICT d = t->_p(node); if(d->m_type & KEYTAG) d->m_key.tag = normalize_tag(d->m_key.tag); if(d->m_type & VALTAG) d->m_val.tag = normalize_tag(d->m_val.tag); for(id_type child = t->first_child(node); child != NONE; child = t->next_sibling(child)) _normalize_tags(t, child); } void _normalize_tags_long(Tree *t, id_type node) { NodeData *C4_RESTRICT d = t->_p(node); if(d->m_type & KEYTAG) d->m_key.tag = normalize_tag_long(d->m_key.tag); if(d->m_type & VALTAG) d->m_val.tag = normalize_tag_long(d->m_val.tag); for(id_type child = t->first_child(node); child != NONE; child = t->next_sibling(child)) _normalize_tags_long(t, child); } } // namespace void Tree::resolve_tags() { if(empty()) return; size_t needed_size = _count_resolved_tags_size(this, root_id()); if(needed_size) reserve_arena(arena_size() + needed_size); _resolve_tags(this, root_id()); } void Tree::normalize_tags() { if(empty()) return; _normalize_tags(this, root_id()); } void Tree::normalize_tags_long() { if(empty()) return; _normalize_tags_long(this, root_id()); } //----------------------------------------------------------------------------- csubstr Tree::lookup_result::resolved() const { csubstr p = path.first(path_pos); if(p.ends_with('.')) p = p.first(p.len-1); return p; } csubstr Tree::lookup_result::unresolved() const { return path.sub(path_pos); } void Tree::_advance(lookup_result *r, size_t more) { r->path_pos += more; if(r->path.sub(r->path_pos).begins_with('.')) ++r->path_pos; } Tree::lookup_result Tree::lookup_path(csubstr path, id_type start) const { if(start == NONE) start = root_id(); lookup_result r(path, start); if(path.empty()) return r; _lookup_path(&r); if(r.target == NONE && r.closest == start) r.closest = NONE; return r; } id_type Tree::lookup_path_or_modify(csubstr default_value, csubstr path, id_type start) { id_type target = _lookup_path_or_create(path, start); if(parent_is_map(target)) to_keyval(target, key(target), default_value); else to_val(target, default_value); return target; } id_type Tree::lookup_path_or_modify(Tree const *src, id_type src_node, csubstr path, id_type start) { id_type target = _lookup_path_or_create(path, start); merge_with(src, src_node, target); return target; } id_type Tree::_lookup_path_or_create(csubstr path, id_type start) { if(start == NONE) start = root_id(); lookup_result r(path, start); _lookup_path(&r); if(r.target != NONE) { C4_ASSERT(r.unresolved().empty()); return r.target; } _lookup_path_modify(&r); return r.target; } void Tree::_lookup_path(lookup_result *r) const { C4_ASSERT( ! r->unresolved().empty()); _lookup_path_token parent{"", type(r->closest)}; id_type node; do { node = _next_node(r, &parent); if(node != NONE) r->closest = node; if(r->unresolved().empty()) { r->target = node; return; } } while(node != NONE); } void Tree::_lookup_path_modify(lookup_result *r) { C4_ASSERT( ! r->unresolved().empty()); _lookup_path_token parent{"", type(r->closest)}; id_type node; do { node = _next_node_modify(r, &parent); if(node != NONE) r->closest = node; if(r->unresolved().empty()) { r->target = node; return; } } while(node != NONE); } id_type Tree::_next_node(lookup_result * r, _lookup_path_token *parent) const { _lookup_path_token token = _next_token(r, *parent); if( ! token) return NONE; id_type node = NONE; csubstr prev = token.value; if(token.type == MAP || token.type == SEQ) { _RYML_CB_ASSERT(m_callbacks, !token.value.begins_with('[')); //_RYML_CB_ASSERT(m_callbacks, is_container(r->closest) || r->closest == NONE); _RYML_CB_ASSERT(m_callbacks, is_map(r->closest)); node = find_child(r->closest, token.value); } else if(token.type == KEYVAL) { _RYML_CB_ASSERT(m_callbacks, r->unresolved().empty()); if(is_map(r->closest)) node = find_child(r->closest, token.value); } else if(token.type == KEY) { _RYML_CB_ASSERT(m_callbacks, token.value.begins_with('[') && token.value.ends_with(']')); token.value = token.value.offs(1, 1).trim(' '); id_type idx = 0; _RYML_CB_CHECK(m_callbacks, from_chars(token.value, &idx)); node = child(r->closest, idx); } else { C4_NEVER_REACH(); } if(node != NONE) { *parent = token; } else { csubstr p = r->path.sub(r->path_pos > 0 ? r->path_pos - 1 : r->path_pos); r->path_pos -= prev.len; if(p.begins_with('.')) r->path_pos -= 1u; } return node; } id_type Tree::_next_node_modify(lookup_result * r, _lookup_path_token *parent) { _lookup_path_token token = _next_token(r, *parent); if( ! token) return NONE; id_type node = NONE; if(token.type == MAP || token.type == SEQ) { _RYML_CB_ASSERT(m_callbacks, !token.value.begins_with('[')); //_RYML_CB_ASSERT(m_callbacks, is_container(r->closest) || r->closest == NONE); if( ! is_container(r->closest)) { if(has_key(r->closest)) to_map(r->closest, key(r->closest)); else to_map(r->closest); } else { if(is_map(r->closest)) node = find_child(r->closest, token.value); else { id_type pos = NONE; _RYML_CB_CHECK(m_callbacks, c4::atox(token.value, &pos)); _RYML_CB_ASSERT(m_callbacks, pos != NONE); node = child(r->closest, pos); } } if(node == NONE) { _RYML_CB_ASSERT(m_callbacks, is_map(r->closest)); node = append_child(r->closest); NodeData *n = _p(node); n->m_key.scalar = token.value; n->m_type.add(KEY); } } else if(token.type == KEYVAL) { _RYML_CB_ASSERT(m_callbacks, r->unresolved().empty()); if(is_map(r->closest)) { node = find_child(r->closest, token.value); if(node == NONE) node = append_child(r->closest); } else { _RYML_CB_ASSERT(m_callbacks, !is_seq(r->closest)); _add_flags(r->closest, MAP); node = append_child(r->closest); } NodeData *n = _p(node); n->m_key.scalar = token.value; n->m_val.scalar = ""; n->m_type.add(KEYVAL); } else if(token.type == KEY) { _RYML_CB_ASSERT(m_callbacks, token.value.begins_with('[') && token.value.ends_with(']')); token.value = token.value.offs(1, 1).trim(' '); id_type idx; if( ! from_chars(token.value, &idx)) return NONE; if( ! is_container(r->closest)) { if(has_key(r->closest)) { csubstr k = key(r->closest); _clear_type(r->closest); to_seq(r->closest, k); } else { _clear_type(r->closest); to_seq(r->closest); } } _RYML_CB_ASSERT(m_callbacks, is_container(r->closest)); node = child(r->closest, idx); if(node == NONE) { _RYML_CB_ASSERT(m_callbacks, num_children(r->closest) <= idx); for(id_type i = num_children(r->closest); i <= idx; ++i) { node = append_child(r->closest); if(i < idx) { if(is_map(r->closest)) to_keyval(node, /*"~"*/{}, /*"~"*/{}); else if(is_seq(r->closest)) to_val(node, /*"~"*/{}); } } } } else { C4_NEVER_REACH(); } _RYML_CB_ASSERT(m_callbacks, node != NONE); *parent = token; return node; } /* types of tokens: * - seeing "map." ---> "map"/MAP * - finishing "scalar" ---> "scalar"/KEYVAL * - seeing "seq[n]" ---> "seq"/SEQ (--> "[n]"/KEY) * - seeing "[n]" ---> "[n]"/KEY */ Tree::_lookup_path_token Tree::_next_token(lookup_result *r, _lookup_path_token const& parent) const { csubstr unres = r->unresolved(); if(unres.empty()) return {}; // is it an indexation like [0], [1], etc? if(unres.begins_with('[')) { size_t pos = unres.find(']'); if(pos == csubstr::npos) return {}; csubstr idx = unres.first(pos + 1); _advance(r, pos + 1); return {idx, KEY}; } // no. so it must be a name size_t pos = unres.first_of(".["); if(pos == csubstr::npos) { _advance(r, unres.len); NodeType t; if(( ! parent) || parent.type.is_seq()) return {unres, VAL}; return {unres, KEYVAL}; } // it's either a map or a seq _RYML_CB_ASSERT(m_callbacks, unres[pos] == '.' || unres[pos] == '['); if(unres[pos] == '.') { _RYML_CB_ASSERT(m_callbacks, pos != 0); _advance(r, pos + 1); return {unres.first(pos), MAP}; } _RYML_CB_ASSERT(m_callbacks, unres[pos] == '['); _advance(r, pos); return {unres.first(pos), SEQ}; } } // namespace yml } // namespace c4 C4_SUPPRESS_WARNING_GCC_CLANG_POP C4_SUPPRESS_WARNING_MSVC_POP #endif /* RYML_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/tree.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/parse_engine.def.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/parse_engine.def.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_PARSE_ENGINE_DEF_HPP_ #define _C4_YML_PARSE_ENGINE_DEF_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/parse_engine.hpp //#include "c4/yml/parse_engine.hpp" #if !defined(C4_YML_PARSE_ENGINE_HPP_) && !defined(_C4_YML_PARSE_ENGINE_HPP_) #error "amalgamate: file c4/yml/parse_engine.hpp must have been included at this point" #endif /* C4_YML_PARSE_ENGINE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/error.hpp //#include "c4/error.hpp" #if !defined(C4_ERROR_HPP_) && !defined(_C4_ERROR_HPP_) #error "amalgamate: file c4/error.hpp must have been included at this point" #endif /* C4_ERROR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/charconv.hpp //#include "c4/charconv.hpp" #if !defined(C4_CHARCONV_HPP_) && !defined(_C4_CHARCONV_HPP_) #error "amalgamate: file c4/charconv.hpp must have been included at this point" #endif /* C4_CHARCONV_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/utf.hpp //#include "c4/utf.hpp" #if !defined(C4_UTF_HPP_) && !defined(_C4_UTF_HPP_) #error "amalgamate: file c4/utf.hpp must have been included at this point" #endif /* C4_UTF_HPP_ */ //included above: //#include // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //#include "c4/yml/detail/parser_dbg.hpp" #if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) #error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/filter_processor.hpp //#include "c4/yml/filter_processor.hpp" #if !defined(C4_YML_FILTER_PROCESSOR_HPP_) && !defined(_C4_YML_FILTER_PROCESSOR_HPP_) #error "amalgamate: file c4/yml/filter_processor.hpp must have been included at this point" #endif /* C4_YML_FILTER_PROCESSOR_HPP_ */ #ifdef RYML_DBG // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/dump.hpp //#include #if !defined(C4_DUMP_HPP_) && !defined(_C4_DUMP_HPP_) #error "amalgamate: file c4/dump.hpp must have been included at this point" #endif /* C4_DUMP_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/print.hpp //#include "c4/yml/detail/print.hpp" #if !defined(C4_YML_DETAIL_PRINT_HPP_) && !defined(_C4_YML_DETAIL_PRINT_HPP_) #error "amalgamate: file c4/yml/detail/print.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PRINT_HPP_ */ #endif #if defined(RYML_WITH_TAB_TOKENS) #define _RYML_WITH_TAB_TOKENS(...) __VA_ARGS__ #define _RYML_WITHOUT_TAB_TOKENS(...) #define _RYML_WITH_OR_WITHOUT_TAB_TOKENS(with, without) with #else #define _RYML_WITH_TAB_TOKENS(...) #define _RYML_WITHOUT_TAB_TOKENS(...) __VA_ARGS__ #define _RYML_WITH_OR_WITHOUT_TAB_TOKENS(with, without) without #endif // scaffold: #define _c4dbgnextline() \ do { \ _c4dbgq("\n-----------"); \ _c4dbgt("handling line={}, offset={}B", \ m_evt_handler->m_curr->pos.line, \ m_evt_handler->m_curr->pos.offset); \ } while(0) #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable: 4296/*expression is always 'boolean_value'*/) # pragma warning(disable: 4702/*unreachable code*/) #elif defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wtype-limits" // to remove a warning on an assertion that a size_t >= 0. Later on, this size_t will turn into a template argument, and then it can become < 0. # pragma clang diagnostic ignored "-Wformat-nonliteral" # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wtype-limits" // to remove a warning on an assertion that a size_t >= 0. Later on, this size_t will turn into a template argument, and then it can become < 0. # pragma GCC diagnostic ignored "-Wformat-nonliteral" # pragma GCC diagnostic ignored "-Wold-style-cast" # if __GNUC__ >= 7 # pragma GCC diagnostic ignored "-Wduplicated-branches" # endif #endif // NOLINTBEGIN(hicpp-signed-bitwise,cppcoreguidelines-avoid-goto,hicpp-avoid-goto,hicpp-multiway-paths-covered) namespace c4 { namespace yml { namespace { // NOLINT C4_HOT C4_ALWAYS_INLINE bool _is_blck_token(csubstr s) noexcept { RYML_ASSERT(s.len > 0); RYML_ASSERT(s.str[0] == '-' || s.str[0] == ':' || s.str[0] == '?'); return ((s.len == 1) || ((s.str[1] == ' ') _RYML_WITH_TAB_TOKENS( || (s.str[1] == '\t')))); } inline bool _is_doc_begin_token(csubstr s) { RYML_ASSERT(s.begins_with('-')); RYML_ASSERT(!s.ends_with("\n")); RYML_ASSERT(!s.ends_with("\r")); return (s.len >= 3 && s.str[1] == '-' && s.str[2] == '-') && (s.len == 3 || (s.str[3] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[3] == '\t'))); } inline bool _is_doc_end_token(csubstr s) { RYML_ASSERT(s.begins_with('.')); RYML_ASSERT(!s.ends_with("\n")); RYML_ASSERT(!s.ends_with("\r")); return (s.len >= 3 && s.str[1] == '.' && s.str[2] == '.') && (s.len == 3 || (s.str[3] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[3] == '\t'))); } inline bool _is_doc_token(csubstr s) noexcept { // // NOTE: this function was failing under some scenarios when // compiled with gcc -O2 (but not -O3 or -O1 or -O0), likely // related to optimizer assumptions on the input string and // possibly caused from UB around assignment to that string (the // call site was in _scan_block()). For more details see: // // https://github.com/biojppm/rapidyaml/issues/440 // // The current version does not suffer this problem, but it may // appear again. // // // UPDATE. The problem appeared again in gcc12 and gcc13 with -Os // (but not any other optimization level, nor any other compiler // or version), because the assignment to s is being hoisted out // of the loop which calls this function. Then the length doesn't // enter the s.len >= 3 when it should. Adding a // C4_DONT_OPTIMIZE(var) makes the problem go away. // if(s.len >= 3) { switch(s.str[0]) { case '-': //return _is_doc_begin_token(s); // this was failing with gcc -O2 return (s.str[1] == '-' && s.str[2] == '-') && (s.len == 3 || (s.str[3] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[3] == '\t'))); case '.': //return _is_doc_end_token(s); // this was failing with gcc -O2 return (s.str[1] == '.' && s.str[2] == '.') && (s.len == 3 || (s.str[3] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[3] == '\t'))); } } return false; } inline size_t _is_special_json_scalar(csubstr s) { RYML_ASSERT(s.len); switch(s.str[0]) { case 'f': if(s.len >= 5 && s.begins_with("false")) return 5u; break; case 't': if(s.len >= 4 && s.begins_with("true")) return 4u; break; case 'n': if(s.len >= 4 && s.begins_with("null")) return 4u; break; } return 0u; } //----------------------------------------------------------------------------- C4_ALWAYS_INLINE size_t _extend_from_combined_newline(char nl, char following) { return (nl == '\n' && following == '\r') || (nl == '\r' && following == '\n'); } //! look for the next newline chars, and jump to the right of those inline substr from_next_line(substr rem) { size_t nlpos = rem.first_of("\r\n"); if(nlpos == csubstr::npos) return {}; const char nl = rem[nlpos]; rem = rem.right_of(nlpos); if(rem.empty()) return {}; if(_extend_from_combined_newline(nl, rem.front())) rem = rem.sub(1); return rem; } //----------------------------------------------------------------------------- inline size_t _count_following_newlines(csubstr r, size_t *C4_RESTRICT i) { RYML_ASSERT(r[*i] == '\n'); size_t numnl_following = 0; ++(*i); for( ; *i < r.len; ++(*i)) { if(r.str[*i] == '\n') ++numnl_following; // skip leading whitespace else if(r.str[*i] == ' ' || r.str[*i] == '\t' || r.str[*i] == '\r') ; else break; } return numnl_following; } /** @p i is set to the first non whitespace character after the line * @return the number of empty lines after the initial position */ inline size_t _count_following_newlines(csubstr r, size_t *C4_RESTRICT i, size_t indentation) { RYML_ASSERT(r[*i] == '\n'); size_t numnl_following = 0; ++(*i); if(indentation == 0) { for( ; *i < r.len; ++(*i)) { if(r.str[*i] == '\n') ++numnl_following; // skip leading whitespace else if(r.str[*i] == ' ' || r.str[*i] == '\t' || r.str[*i] == '\r') ; else break; } } else { for( ; *i < r.len; ++(*i)) { if(r.str[*i] == '\n') { ++numnl_following; // skip the indentation after the newline size_t stop = *i + indentation; for( ; *i < r.len; ++(*i)) { if(r.str[*i] != ' ' && r.str[*i] != '\r') break; RYML_ASSERT(*i < stop); } C4_UNUSED(stop); } // skip leading whitespace else if(r.str[*i] == ' ' || r.str[*i] == '\t' || r.str[*i] == '\r') ; else break; } } return numnl_following; } } // anon namespace //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template ParseEngine::~ParseEngine() { _free(); _clr(); } template ParseEngine::ParseEngine(EventHandler *evt_handler, ParserOptions opts) : m_options(opts) , m_file() , m_buf() , m_evt_handler(evt_handler) , m_pending_anchors() , m_pending_tags() , m_was_inside_qmrk(false) , m_doc_empty(false) , m_prev_colon(npos) , m_encoding(NOBOM) , m_newline_offsets() , m_newline_offsets_size(0) , m_newline_offsets_capacity(0) , m_newline_offsets_buf() { RYML_CHECK(evt_handler); } template ParseEngine::ParseEngine(ParseEngine &&that) noexcept : m_options(that.m_options) , m_file(that.m_file) , m_buf(that.m_buf) , m_evt_handler(that.m_evt_handler) , m_pending_anchors(that.m_pending_anchors) , m_pending_tags(that.m_pending_tags) , m_was_inside_qmrk(false) , m_doc_empty(false) , m_prev_colon(npos) , m_encoding(NOBOM) , m_newline_offsets(that.m_newline_offsets) , m_newline_offsets_size(that.m_newline_offsets_size) , m_newline_offsets_capacity(that.m_newline_offsets_capacity) , m_newline_offsets_buf(that.m_newline_offsets_buf) { that._clr(); } template ParseEngine::ParseEngine(ParseEngine const& that) : m_options(that.m_options) , m_file(that.m_file) , m_buf(that.m_buf) , m_evt_handler(that.m_evt_handler) , m_pending_anchors(that.m_pending_anchors) , m_pending_tags(that.m_pending_tags) , m_was_inside_qmrk(false) , m_doc_empty(false) , m_prev_colon(npos) , m_encoding(NOBOM) , m_newline_offsets() , m_newline_offsets_size() , m_newline_offsets_capacity() , m_newline_offsets_buf() { if(that.m_newline_offsets_capacity) { _resize_locations(that.m_newline_offsets_capacity); _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_capacity == that.m_newline_offsets_capacity); memcpy(m_newline_offsets, that.m_newline_offsets, that.m_newline_offsets_size * sizeof(size_t)); m_newline_offsets_size = that.m_newline_offsets_size; } } template ParseEngine& ParseEngine::operator=(ParseEngine &&that) noexcept { _free(); m_options = (that.m_options); m_file = (that.m_file); m_buf = (that.m_buf); m_evt_handler = that.m_evt_handler; m_pending_anchors = that.m_pending_anchors; m_pending_tags = that.m_pending_tags; m_was_inside_qmrk = that.m_was_inside_qmrk; m_doc_empty = that.m_doc_empty; m_prev_colon = that.m_prev_colon; m_encoding = that.m_encoding; m_newline_offsets = (that.m_newline_offsets); m_newline_offsets_size = (that.m_newline_offsets_size); m_newline_offsets_capacity = (that.m_newline_offsets_capacity); m_newline_offsets_buf = (that.m_newline_offsets_buf); that._clr(); return *this; } template ParseEngine& ParseEngine::operator=(ParseEngine const& that) { if(&that != this) { _free(); m_options = (that.m_options); m_file = (that.m_file); m_buf = (that.m_buf); m_evt_handler = that.m_evt_handler; m_pending_anchors = that.m_pending_anchors; m_pending_tags = that.m_pending_tags; m_was_inside_qmrk = that.m_was_inside_qmrk; m_doc_empty = that.m_doc_empty; m_prev_colon = that.m_prev_colon; m_encoding = that.m_encoding; if(that.m_newline_offsets_capacity > m_newline_offsets_capacity) _resize_locations(that.m_newline_offsets_capacity); _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_capacity >= that.m_newline_offsets_capacity); _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_capacity >= that.m_newline_offsets_size); memcpy(m_newline_offsets, that.m_newline_offsets, that.m_newline_offsets_size * sizeof(size_t)); m_newline_offsets_size = that.m_newline_offsets_size; m_newline_offsets_buf = that.m_newline_offsets_buf; } return *this; } template void ParseEngine::_clr() { m_options = {}; m_file = {}; m_buf = {}; m_evt_handler = {}; m_pending_anchors = {}; m_pending_tags = {}; m_was_inside_qmrk = false; m_doc_empty = true; m_prev_colon = npos; m_encoding = NOBOM; m_newline_offsets = {}; m_newline_offsets_size = {}; m_newline_offsets_capacity = {}; m_newline_offsets_buf = {}; } template void ParseEngine::_free() { if(m_newline_offsets) { _RYML_CB_FREE(m_evt_handler->m_stack.m_callbacks, m_newline_offsets, size_t, m_newline_offsets_capacity); m_newline_offsets = nullptr; m_newline_offsets_size = 0u; m_newline_offsets_capacity = 0u; m_newline_offsets_buf = nullptr; } } //----------------------------------------------------------------------------- template void ParseEngine::_reset() { m_pending_anchors = {}; m_pending_tags = {}; m_doc_empty = true; m_was_inside_qmrk = false; m_prev_colon = npos; m_encoding = NOBOM; if(m_options.locations()) { _prepare_locations(); } } //----------------------------------------------------------------------------- template void ParseEngine::_relocate_arena(csubstr prev_arena, substr next_arena) { #define _ryml_relocate(s) \ if((s).is_sub(prev_arena)) \ { \ (s).str = next_arena.str + ((s).str - prev_arena.str); \ } _ryml_relocate(m_buf); _ryml_relocate(m_newline_offsets_buf); for(size_t i = 0; i < m_pending_tags.num_entries; ++i) _ryml_relocate(m_pending_tags.annotations[i].str); for(size_t i = 0; i < m_pending_anchors.num_entries; ++i) _ryml_relocate(m_pending_anchors.annotations[i].str); #undef _ryml_relocate } template void ParseEngine::_s_relocate_arena(void* data, csubstr prev_arena, substr next_arena) { ((ParseEngine*)data)->_relocate_arena(prev_arena, next_arena); } //----------------------------------------------------------------------------- template template void ParseEngine::_fmt_msg(DumpFn &&dumpfn) const { auto const *const C4_RESTRICT st = m_evt_handler->m_curr; auto const& lc = st->line_contents; csubstr contents = lc.stripped; if(contents.len) { // print the yaml src line size_t offs = 3u + to_chars(substr{}, st->pos.line) + to_chars(substr{}, st->pos.col); if(m_file.len) { detail::_dump(std::forward(dumpfn), "{}:", m_file); offs += m_file.len + 1; } detail::_dump(std::forward(dumpfn), "{}:{}: ", st->pos.line, st->pos.col); csubstr maybe_full_content = (contents.len < 80u ? contents : contents.first(80u)); csubstr maybe_ellipsis = (contents.len < 80u ? csubstr{} : csubstr("...")); detail::_dump(std::forward(dumpfn), "{}{} (size={})\n", maybe_full_content, maybe_ellipsis, contents.len); // highlight the remaining portion of the previous line size_t firstcol = (size_t)(lc.rem.begin() - lc.full.begin()); size_t lastcol = firstcol + lc.rem.len; for(size_t i = 0; i < offs + firstcol; ++i) std::forward(dumpfn)(" "); std::forward(dumpfn)("^"); for(size_t i = 1, e = (lc.rem.len < 80u ? lc.rem.len : 80u); i < e; ++i) std::forward(dumpfn)("~"); detail::_dump(std::forward(dumpfn), "{} (cols {}-{})\n", maybe_ellipsis, firstcol+1, lastcol+1); } else { std::forward(dumpfn)("\n"); } #ifdef RYML_DBG // next line: print the state flags { char flagbuf_[128]; detail::_dump(std::forward(dumpfn), "top state: {}\n", detail::_parser_flags_to_str(flagbuf_, m_evt_handler->m_curr->flags)); } #endif } //----------------------------------------------------------------------------- template template void ParseEngine::_err(csubstr fmt, Args const& C4_RESTRICT ...args) const { char errmsg[RYML_ERRMSG_SIZE]; detail::_SubstrWriter writer(errmsg); auto dumpfn = [&writer](csubstr s){ writer.append(s); }; detail::_dump(dumpfn, fmt, args...); writer.append('\n'); _fmt_msg(dumpfn); size_t len = writer.pos < RYML_ERRMSG_SIZE ? writer.pos : RYML_ERRMSG_SIZE; m_evt_handler->cancel_parse(); m_evt_handler->m_stack.m_callbacks.m_error(errmsg, len, m_evt_handler->m_curr->pos, m_evt_handler->m_stack.m_callbacks.m_user_data); } //----------------------------------------------------------------------------- #ifdef RYML_DBG template template void ParseEngine::_dbg(csubstr fmt, Args const& C4_RESTRICT ...args) const { if(_dbg_enabled()) { auto dumpfn = [](csubstr s){ if(s.str) fwrite(s.str, 1, s.len, stdout); }; detail::_dump(dumpfn, fmt, args...); dumpfn("\n"); _fmt_msg(dumpfn); } } #endif //----------------------------------------------------------------------------- template bool ParseEngine::_finished_file() const { bool ret = m_evt_handler->m_curr->pos.offset >= m_buf.len; if(ret) { _c4dbgp("finished file!!!"); } return ret; } template C4_HOT C4_ALWAYS_INLINE bool ParseEngine::_finished_line() const { return m_evt_handler->m_curr->line_contents.rem.empty(); } //----------------------------------------------------------------------------- template void ParseEngine::_maybe_skip_whitespace_tokens() { csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(rem.len && (rem.str[0] == ' ' _RYML_WITH_TAB_TOKENS(|| rem.str[0] == '\t'))) { size_t pos = rem.first_not_of(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); if(pos == npos) pos = rem.len; // maybe the line is just all whitespace _c4dbgpf("skip {} whitespace characters", pos); _line_progressed(pos); } } template void ParseEngine::_maybe_skipchars(char c) { csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(rem.len && rem.str[0] == c) { size_t pos = rem.first_not_of(c); if(pos == npos) pos = rem.len; // maybe the line is just all c _c4dbgpf("skip {}x'{}'", pos, c); _line_progressed(pos); } } #ifdef RYML_NO_COVERAGE__TO_BE_DELETED template void ParseEngine::_maybe_skipchars_up_to(char c, size_t max_to_skip) { csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(rem.len && rem.str[0] == c) { size_t pos = rem.first_not_of(c); if(pos == npos) pos = rem.len; // maybe the line is just all c if(pos > max_to_skip) pos = max_to_skip; _c4dbgpf("skip {}x'{}'", pos, c); _line_progressed(pos); } } #endif template template void ParseEngine::_skipchars(const char (&chars)[N]) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.rem.begins_with_any(chars)); size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of(chars); if(pos == npos) pos = m_evt_handler->m_curr->line_contents.rem.len; // maybe the line is just whitespace _c4dbgpf("skip {} characters", pos); _line_progressed(pos); } template void ParseEngine::_skip_comment() { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.rem.begins_with('#')); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.rem.is_sub(m_evt_handler->m_curr->line_contents.full)); csubstr rem = m_evt_handler->m_curr->line_contents.rem; csubstr full = m_evt_handler->m_curr->line_contents.full; // raise an error if the comment is not preceded by whitespace if(!full.begins_with('#')) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, rem.str > full.str); const char c = full[(size_t)(rem.str - full.str - 1)]; if(C4_UNLIKELY(c != ' ' && c != '\t')) _RYML_CB_ERR(m_evt_handler->m_stack.m_callbacks, "comment not preceded by whitespace"); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, rem.str == full.str); } _c4dbgpf("comment was '{}'", rem); _line_progressed(rem.len); } template void ParseEngine::_maybe_skip_comment() { csubstr s = m_evt_handler->m_curr->line_contents.rem.triml(' '); if(s.begins_with('#')) { _line_progressed((size_t)(s.str - m_evt_handler->m_curr->line_contents.rem.str)); _skip_comment(); } } template bool ParseEngine::_maybe_scan_following_colon() noexcept { if(m_evt_handler->m_curr->line_contents.rem.len) { if(m_evt_handler->m_curr->line_contents.rem.str[0] == ' ' || m_evt_handler->m_curr->line_contents.rem.str[0] == '\t') { size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of(" \t"); if(pos == npos) pos = m_evt_handler->m_curr->line_contents.rem.len; // maybe the line has only spaces _c4dbgpf("skip {}x'{}'", pos, ' '); _line_progressed(pos); } if(m_evt_handler->m_curr->line_contents.rem.len && (m_evt_handler->m_curr->line_contents.rem.str[0] == ':')) { _c4dbgp("found ':' colon next"); _line_progressed(1); return true; } } return false; } template bool ParseEngine::_maybe_scan_following_comma() noexcept { if(m_evt_handler->m_curr->line_contents.rem.len) { if(m_evt_handler->m_curr->line_contents.rem.str[0] == ' ' || m_evt_handler->m_curr->line_contents.rem.str[0] == '\t') { size_t pos = m_evt_handler->m_curr->line_contents.rem.first_not_of(" \t"); if(pos == npos) pos = m_evt_handler->m_curr->line_contents.rem.len; // maybe the line has only spaces _c4dbgpf("skip {}x'{}'", pos, ' '); _line_progressed(pos); } if(m_evt_handler->m_curr->line_contents.rem.len && (m_evt_handler->m_curr->line_contents.rem.str[0] == ',')) { _c4dbgp("found ',' comma next"); _line_progressed(1); return true; } } return false; } //----------------------------------------------------------------------------- template csubstr ParseEngine::_scan_anchor() { csubstr s = m_evt_handler->m_curr->line_contents.rem; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.begins_with('&')); csubstr anchor = s.range(1, s.first_of(' ')); _line_progressed(1u + anchor.len); _maybe_skipchars(' '); return anchor; } template csubstr ParseEngine::_scan_ref_seq() { csubstr s = m_evt_handler->m_curr->line_contents.rem; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.begins_with('*')); csubstr ref = s.first(s.first_of(",] :")); _line_progressed(ref.len); return ref; } template csubstr ParseEngine::_scan_ref_map() { csubstr s = m_evt_handler->m_curr->line_contents.rem; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.begins_with('*')); csubstr ref = s.first(s.first_of(",} ")); _line_progressed(ref.len); return ref; } template csubstr ParseEngine::_scan_tag() { csubstr rem = m_evt_handler->m_curr->line_contents.rem.triml(' '); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, rem.begins_with('!')); csubstr t; if(rem.begins_with("!!")) { _c4dbgp("begins with '!!'"); if(has_any(FLOW)) t = rem.left_of(rem.first_of(" ,")); else t = rem.left_of(rem.first_of(' ')); } else if(rem.begins_with("!<")) { _c4dbgp("begins with '!<'"); t = rem.left_of(rem.first_of('>'), true); } #ifdef RYML_NO_COVERAGE__TO_BE_DELETED else if(rem.begins_with("!h!")) { _c4dbgp("begins with '!h!'"); t = rem.left_of(rem.first_of(' ')); } #endif else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, rem.begins_with('!')); _c4dbgp("begins with '!'"); if(has_any(FLOW)) t = rem.left_of(rem.first_of(" ,")); else t = rem.left_of(rem.first_of(' ')); } _line_progressed(t.len); _maybe_skip_whitespace_tokens(); return t; } //----------------------------------------------------------------------------- template bool ParseEngine::_is_valid_start_scalar_plain_flow(csubstr s) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, !s.empty()); // it's not a scalar if it starts with any of these characters: switch(s.str[0]) { // these are all legal tokens which mean no scalar is starting: case '[': case ']': case '{': case '}': case '!': case '&': case '*': case '|': case '>': case '#': _c4dbgpf("not a scalar: found non-scalar token '{}'", _c4prc(s.str[0])); return false; // '-' and ':' are illegal at the beginning if not followed by a scalar character case '-': case ':': if(s.len > 1) { switch(s.str[1]) { case '\n': case '\r': case '{': case '[': //_RYML_WITHOUT_TAB_TOKENS(case '\t'): _c4err("invalid token \":{}\"", _c4prc(s.str[1])); break; case ' ': case '}': case ']': if(s.str[0] == ':') { _c4dbgpf("not a scalar: found non-scalar token '{}{}'", s.str[0], s.str[1]); return false; } break; default: break; } } else { return false; } break; case '?': if(s.len > 1) { switch(s.str[1]) { case ' ': case '\n': case '\r': _RYML_WITHOUT_TAB_TOKENS(case '\t':) _c4dbgpf("not a scalar: found non-scalar token '?{}'", _c4prc(s.str[1])); return false; case '{': case '}': case '[': case ']': _c4err("invalid token \"?{}\"", _c4prc(s.str[1])); break; default: break; } } else { return false; } break; // everything else is a legal starting character default: break; } return true; } template bool ParseEngine::_scan_scalar_plain_seq_flow(ScannedScalar *C4_RESTRICT sc) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(BLCK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RSEQ|RSEQIMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RVAL)); substr s = m_evt_handler->m_curr->line_contents.rem; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, !s.begins_with(' ')); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, !s.begins_with('\n')); if(!s.len) return false; if(!_is_valid_start_scalar_plain_flow(s)) return false; _c4dbgp("scanning seqflow scalar..."); const size_t start_offset = m_evt_handler->m_curr->pos.offset; bool needs_filter = false; while(true) { _c4dbgpf("scanning scalar: curr line=[{}]~~~{}~~~", s.len, s); for(size_t i = 0; i < s.len; ++i) { const char c = s.str[i]; switch(c) { case ',': _c4dbgpf("found terminating character at {}: '{}'", i, c); _line_progressed(i); if(m_evt_handler->m_curr->pos.offset + i > start_offset) { goto ended_scalar; } else { _c4dbgp("at the beginning. no scalar here."); return false; } break; case ']': _c4dbgpf("found terminating character at {}: '{}'", i, c); _line_progressed(i); goto ended_scalar; break; case '#': _c4dbgp("found suspicious '#'"); if(!i || (s.str[i-1] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[i-1] == '\t'))) { _c4dbgpf("found terminating character at {}: '{}'", i, c); _line_progressed(i); goto ended_scalar; } break; case ':': _c4dbgp("found suspicious ':'"); if(s.len > i+1) { const char next = s.str[i+1]; _c4dbgpf("next char is '{}'", _c4prc(next)); if(next == ' ' || next == ',' _RYML_WITH_TAB_TOKENS(|| next == '\t')) { _c4dbgp("map starting!"); if(m_evt_handler->m_curr->pos.offset + i > start_offset) { _c4dbgp("scalar finished!"); _line_progressed(i); goto ended_scalar; } else { _c4dbgp("at the beginning. no scalar here."); return false; } } else { _c4dbgp("it's a scalar indeed."); ++i; // skip the next char } } else if(s.len == i+1) { _c4dbgp("':' at line end. map starting!"); return false; } break; case '[': case '{': case '}': _line_progressed(i); _c4err("invalid character: '{}'", c); // noreturn default: ; } } _line_progressed(s.len); if(!_finished_file()) { _c4dbgp("next line!"); _line_ended(); _scan_line(); } else { _c4dbgp("file finished!"); goto ended_scalar; } s = m_evt_handler->m_curr->line_contents.rem; needs_filter = true; } ended_scalar: sc->scalar = m_buf.range(start_offset, m_evt_handler->m_curr->pos.offset).trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' ')); sc->needs_filter = needs_filter; _c4prscalar("scanned plain scalar", sc->scalar, /*keep_newlines*/true); return true; } template bool ParseEngine::_scan_scalar_plain_map_flow(ScannedScalar *C4_RESTRICT sc) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RSEQ) || has_any(RSEQIMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(BLCK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RMAP|RSEQIMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RVAL|QMRK)); substr s = m_evt_handler->m_curr->line_contents.rem; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, !s.begins_with(' ')); if(!s.len) return false; if(!_is_valid_start_scalar_plain_flow(s)) return false; _c4dbgp("scanning scalar..."); const size_t start_offset = m_evt_handler->m_curr->pos.offset; bool needs_filter = false; while(true) { for(size_t i = 0; i < s.len; ++i) { const char c = s.str[i]; switch(c) { case ',': case '}': _line_progressed(i); _c4dbgpf("found terminating character: '{}'", c); goto ended_scalar; case ':': if(s.len == i+1 || s.str[i+1] == ' ' || s.str[i+1] == ',' || s.str[i+1] == '}' _RYML_WITH_TAB_TOKENS(|| s.str[i+1] == '\t')) { _line_progressed(i); _c4dbgpf("found terminating character: '{}'", c); goto ended_scalar; } break; case '{': case '[': _line_progressed(i); _c4err("invalid character: '{}'", c); // noreturn break; case ']': _line_progressed(i); if(has_any(RSEQIMAP)) goto ended_scalar; else _c4err("invalid character: '{}'", c); // noreturn break; case '#': if(!i || s.str[i-1] == ' ' _RYML_WITH_TAB_TOKENS(|| s.str[i-1] == '\t')) { _line_progressed(i); _c4dbgpf("found terminating character: '{}'", c); goto ended_scalar; } break; default: ; } } _c4dbgp("next line!"); _line_progressed(s.len); if(!_finished_file()) { _c4dbgp("next line!"); _line_ended(); _scan_line(); } else { _c4dbgp("file finished!"); goto ended_scalar; } s = m_evt_handler->m_curr->line_contents.rem; needs_filter = true; } ended_scalar: sc->scalar = m_buf.range(start_offset, m_evt_handler->m_curr->pos.offset).trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \n\t\r", " \n\r")); sc->needs_filter = needs_filter; _c4dbgpf("scalar was [{}]~~~{}~~~", sc->scalar.len, sc->scalar); return sc->scalar.len > 0u; } template bool ParseEngine::_scan_scalar_seq_json(ScannedScalar *C4_RESTRICT sc) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(BLCK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RSEQ)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(FLOW)); substr s = m_evt_handler->m_curr->line_contents.rem; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, !s.begins_with(' ')); if(!s.len) return false; _c4dbgp("scanning scalar..."); switch(s.str[0]) { case ']': case '{': case ',': _c4dbgp("not a scalar."); return false; } { const size_t len = _is_special_json_scalar(s); if(len) { sc->scalar = s.first(len); sc->needs_filter = false; _c4dbgpf("special json scalar: '{}'", sc->scalar); _line_progressed(len); return true; } } // must be a number size_t i = 0; for( ; i < s.len; ++i) { const char c = s.str[i]; switch(c) { case ',': case ']': case ' ': case '\t': _c4dbgpf("found terminating character: '{}'", c); goto ended_scalar; case '#': if(!i || s.str[i-1] == ' ') { _c4dbgpf("found terminating character: '{}'", c); goto ended_scalar; } break; default: ; } } ended_scalar: if(C4_LIKELY(i > 0)) { _line_progressed(i); sc->scalar = s.first(i); sc->needs_filter = false; _c4dbgpf("scalar was [{}]~~~{}~~~", sc->scalar.len, sc->scalar); return true; } return false; } template bool ParseEngine::_scan_scalar_map_json(ScannedScalar *C4_RESTRICT sc) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RSEQ)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(BLCK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RVAL)); substr s = m_evt_handler->m_curr->line_contents.rem; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, !s.begins_with(' ')); if(!s.len) return false; _c4dbgp("scanning scalar..."); { const size_t len = _is_special_json_scalar(s); if(len) { sc->scalar = s.first(len); sc->needs_filter = false; _c4dbgpf("special json scalar: '{}'", sc->scalar); _line_progressed(len); return true; } } // must be a number size_t i = 0; for( ; i < s.len; ++i) { const char c = s.str[i]; switch(c) { case ',': case '}': case ' ': case '\t': _c4dbgpf("found terminating character: '{}'", c); goto ended_scalar; case '#': if(!i || s.str[i-1] == ' ') { _c4dbgpf("found terminating character: '{}'", c); goto ended_scalar; } break; default: ; } } ended_scalar: if(C4_LIKELY(i > 0)) { _line_progressed(i); sc->scalar = s.first(i); sc->needs_filter = false; _c4dbgpf("scalar was [{}]~~~{}~~~", sc->scalar.len, sc->scalar); return true; } return false; } template bool ParseEngine::_is_doc_begin(csubstr s) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s[0] == '-'); return (m_evt_handler->m_curr->line_contents.indentation == 0u && _at_line_begin() && _is_doc_begin_token(s)); } template bool ParseEngine::_is_doc_end(csubstr s) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s[0] == '.'); return (m_evt_handler->m_curr->line_contents.indentation == 0u && _at_line_begin() && _is_doc_end_token(s)); } template bool ParseEngine::_scan_scalar_plain_blck(ScannedScalar *C4_RESTRICT sc, size_t indentation) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RSEQIMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(BLCK|RUNK|USTY)); substr s = m_evt_handler->m_curr->line_contents.rem; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, !s.begins_with(' ')); if(!s.len) return false; switch(s.str[0]) { case '-': if(_is_blck_token(s)) { return false; } else if(_is_doc_begin(s)) { _c4dbgp("token is doc start"); return false; } break; case ':': case '?': if(_is_blck_token(s)) return false; break; case '[': case '{': case '&': case '*': case '!': _RYML_WITH_TAB_TOKENS(case '\t':) return false; case '.': if(_is_doc_end(s)) { _c4dbgp("token is doc end"); return false; } break; } _c4dbgpf("plain scalar! indentation={}", indentation); const size_t start_offset = m_evt_handler->m_curr->pos.offset; const size_t start_line = m_evt_handler->m_curr->pos.line; bool needs_filter = false; while(true) { _c4dbgpf("plain scalar line: [{}]~~~{}~~~", s.len, s); for(size_t i = 0; i < s.len; ++i) { const char curr = s.str[i]; //_c4dbgpf("[{}]='{}'", i, _c4prc(curr)); switch(curr) { case ':': _c4dbgpf("[{}]: got suspicious ':'", i); // are there more characters? if((i + 1 == s.len) || ((s.str[i+1] == ' ') _RYML_WITH_TAB_TOKENS( || (s.str[i+1] == '\t')))) { _c4dbgpf("followed by '{}'", i+1 == s.len ? csubstr("\\n") : _c4prc(s.str[i+1])); _line_progressed(i); // ': ' is accepted only on the first line if(C4_LIKELY(m_evt_handler->m_curr->pos.line == start_line)) { _c4dbgp("start line. scalar ends here"); goto ended_scalar; } else { _c4err("parse error"); } } else { size_t j = i; while(j + 1 < s.len && s.str[j+1] == ':') { _c4dbgp("skip colon"); ++j; } i = j > i ? j-1 : i; _c4dbgp("nothing to see here"); } break; case '#': _c4dbgp("got suspicious '#'"); if(!i || (s.str[i-1] == ' ' || s.str[i-1] == '\t')) { _c4dbgp("comment! scalar ends here"); _line_progressed(i); goto ended_scalar; } else { _c4dbgp("nothing to see here"); } break; } } _line_progressed(s.len); csubstr next_peeked = _peek_next_line(m_evt_handler->m_curr->pos.offset); next_peeked = next_peeked.trimr("\n\r"); const size_t next_indentation = next_peeked.first_not_of(' '); _c4dbgpf("indentation curr={} next={}", indentation, next_indentation); if(next_indentation < indentation) { _c4dbgp("smaller indentation! scalar ended"); goto ended_scalar; } else if(next_indentation == 0 && next_peeked.len > 0) { const char first = next_peeked.str[0]; switch(first) { case '-': next_peeked = next_peeked.trimr("\n\r"); _c4dbgpf("doc begin? peeked=[{}]~~~{}{}~~~", next_peeked.len, next_peeked.len >= 3 ? next_peeked.first(3) : next_peeked, next_peeked.len > 3 ? "..." : ""); if(_is_doc_begin_token(next_peeked)) { _c4dbgp("doc begin! scalar ended"); goto ended_scalar; } break; case '.': next_peeked = next_peeked.trimr("\n\r"); _c4dbgpf("doc end? peeked=[{}]~~~{}{}~~~", next_peeked.len, next_peeked.len >= 3 ? next_peeked.first(3) : next_peeked, next_peeked.len > 3 ? "..." : ""); if(_is_doc_end_token(next_peeked)) { _c4dbgp("doc end! scalar ended"); goto ended_scalar; } break; } } // load with next line _c4dbgp("next line!"); if(!_finished_file()) { _c4dbgp("next line!"); _line_ended(); _scan_line(); } else { _c4dbgp("file finished!"); goto ended_scalar; } s = m_evt_handler->m_curr->line_contents.rem; needs_filter = true; } ended_scalar: sc->scalar = m_buf.range(start_offset, m_evt_handler->m_curr->pos.offset).trimr(" \n\r\t"); sc->needs_filter = needs_filter; _c4dbgpf("scalar was [{}]~~~{}~~~", sc->scalar.len, sc->scalar); return true; } template bool ParseEngine::_scan_scalar_plain_seq_blck(ScannedScalar *C4_RESTRICT sc) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RSEQIMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RSEQ)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(BLCK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RVAL)); return _scan_scalar_plain_blck(sc, m_evt_handler->m_curr->indref + 1u); } template bool ParseEngine::_scan_scalar_plain_map_blck(ScannedScalar *C4_RESTRICT sc) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RSEQ)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(BLCK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RVAL|QMRK)); return _scan_scalar_plain_blck(sc, m_evt_handler->m_curr->indref + 1u); } template bool ParseEngine::_scan_scalar_plain_unk(ScannedScalar *C4_RESTRICT sc) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RUNK|USTY)); return _scan_scalar_plain_blck(sc, m_evt_handler->m_curr->indref); } //----------------------------------------------------------------------------- template substr ParseEngine::_peek_next_line(size_t pos) const { substr rem{}; // declare here because of the goto size_t nlpos{}; // declare here because of the goto pos = pos == npos ? m_evt_handler->m_curr->pos.offset : pos; if(pos >= m_buf.len) goto next_is_empty; // look for the next newline chars, and jump to the right of those rem = from_next_line(m_buf.sub(pos)); if(rem.empty()) goto next_is_empty; // now get everything up to and including the following newline chars nlpos = rem.first_of("\r\n"); if((nlpos != csubstr::npos) && (nlpos + 1 < rem.len)) nlpos += _extend_from_combined_newline(rem[nlpos], rem[nlpos+1]); rem = rem.left_of(nlpos, /*include_pos*/true); _c4dbgpf("peek next line @ {}: (len={})'{}'", pos, rem.len, rem.trimr("\r\n")); return rem; next_is_empty: _c4dbgpf("peek next line @ {}: (len=0)''", pos); return {}; } //----------------------------------------------------------------------------- template void ParseEngine::_scan_line() { if(C4_LIKELY(m_evt_handler->m_curr->pos.offset < m_buf.len)) m_evt_handler->m_curr->line_contents.reset_with_next_line(m_buf, m_evt_handler->m_curr->pos.offset); else m_evt_handler->m_curr->line_contents.reset(m_buf.last(0), m_buf.last(0)); } template void ParseEngine::_line_progressed(size_t ahead) { _c4dbgpf("line[{}] ({} cols) progressed by {}: col {}-->{} offset {}-->{}", m_evt_handler->m_curr->pos.line, m_evt_handler->m_curr->line_contents.full.len, ahead, m_evt_handler->m_curr->pos.col, m_evt_handler->m_curr->pos.col+ahead, m_evt_handler->m_curr->pos.offset, m_evt_handler->m_curr->pos.offset+ahead); m_evt_handler->m_curr->pos.offset += ahead; m_evt_handler->m_curr->pos.col += ahead; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.col <= m_evt_handler->m_curr->line_contents.stripped.len+1); m_evt_handler->m_curr->line_contents.rem = m_evt_handler->m_curr->line_contents.rem.sub(ahead); } template void ParseEngine::_line_ended() { _c4dbgpf("line[{}] ({} cols) ended! offset {}-->{} / col {}-->{}", m_evt_handler->m_curr->pos.line, m_evt_handler->m_curr->line_contents.full.len, m_evt_handler->m_curr->pos.offset, m_evt_handler->m_curr->pos.offset + m_evt_handler->m_curr->line_contents.full.len - m_evt_handler->m_curr->line_contents.stripped.len, m_evt_handler->m_curr->pos.col, 1); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.col == m_evt_handler->m_curr->line_contents.stripped.len + 1); m_evt_handler->m_curr->pos.offset += m_evt_handler->m_curr->line_contents.full.len - m_evt_handler->m_curr->line_contents.stripped.len; ++m_evt_handler->m_curr->pos.line; m_evt_handler->m_curr->pos.col = 1; } template void ParseEngine::_line_ended_undo() { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.col == 1u); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.line > 0u); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.offset >= m_evt_handler->m_curr->line_contents.full.len - m_evt_handler->m_curr->line_contents.stripped.len); const size_t delta = m_evt_handler->m_curr->line_contents.full.len - m_evt_handler->m_curr->line_contents.stripped.len; _c4dbgpf("line[{}] undo ended! line {}-->{}, offset {}-->{}", m_evt_handler->m_curr->pos.line, m_evt_handler->m_curr->pos.line, m_evt_handler->m_curr->pos.line - 1, m_evt_handler->m_curr->pos.offset, m_evt_handler->m_curr->pos.offset - delta); m_evt_handler->m_curr->pos.offset -= delta; --m_evt_handler->m_curr->pos.line; m_evt_handler->m_curr->pos.col = m_evt_handler->m_curr->line_contents.stripped.len + 1u; // don't forget to undo also the changes to the remainder of the line //_RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.offset >= m_buf.len || m_buf[m_evt_handler->m_curr->pos.offset] == '\n' || m_buf[m_evt_handler->m_curr->pos.offset] == '\r'); m_evt_handler->m_curr->line_contents.rem = m_buf.sub(m_evt_handler->m_curr->pos.offset, 0); } //----------------------------------------------------------------------------- template void ParseEngine::_set_indentation(size_t indentation) { m_evt_handler->m_curr->indref = indentation; _c4dbgpf("state[{}]: saving indentation: {}", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); } template void ParseEngine::_save_indentation() { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.rem.begin() >= m_evt_handler->m_curr->line_contents.full.begin()); m_evt_handler->m_curr->indref = m_evt_handler->m_curr->line_contents.current_col(); _c4dbgpf("state[{}]: saving indentation: {}", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); } //----------------------------------------------------------------------------- template void ParseEngine::_end_map_blck() { _c4dbgp("mapblck: end"); if(has_any(RKCL|RVAL)) { _c4dbgp("mapblck: set missing val"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); } else if(has_any(QMRK)) { _c4dbgp("mapblck: set missing keyval"); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_plain_empty(); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); } m_evt_handler->end_map(); } template void ParseEngine::_end_seq_blck() { if(has_any(RVAL)) { _c4dbgp("seqblck: set missing val"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); } m_evt_handler->end_seq(); } template void ParseEngine::_end2_map() { _c4dbgp("map: end"); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RMAP)); if(has_any(BLCK)) { _end_map_blck(); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(USTY)); m_evt_handler->_pop(); } } template void ParseEngine::_end2_seq() { _c4dbgp("seq: end"); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RSEQ)); if(has_any(BLCK)) { _end_seq_blck(); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(USTY)); m_evt_handler->_pop(); } } template void ParseEngine::_begin2_doc() { m_doc_empty = true; add_flags(RDOC); m_evt_handler->begin_doc(); m_evt_handler->m_curr->indref = 0; // ? } template void ParseEngine::_begin2_doc_expl() { m_doc_empty = true; add_flags(RDOC); m_evt_handler->begin_doc_expl(); m_evt_handler->m_curr->indref = 0; // ? } template void ParseEngine::_end2_doc() { _c4dbgp("doc: end"); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RDOC)); if(m_doc_empty || (m_pending_tags.num_entries || m_pending_anchors.num_entries)) { _c4dbgp("doc was empty; add empty val"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); } m_evt_handler->end_doc(); } template void ParseEngine::_end2_doc_expl() { _c4dbgp("doc: end"); if(m_doc_empty || (m_pending_tags.num_entries || m_pending_anchors.num_entries)) { _c4dbgp("doc: no children; add empty val"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); } m_evt_handler->end_doc_expl(); } template void ParseEngine::_maybe_begin_doc() { if(has_none(RDOC)) { _c4dbgp("doc must be started"); _begin2_doc(); } } template void ParseEngine::_maybe_end_doc() { if(has_any(RDOC)) { _c4dbgp("doc must be finished"); _end2_doc(); } else if(m_doc_empty && (m_pending_tags.num_entries || m_pending_anchors.num_entries)) { _c4dbgp("no doc to finish, but pending annotations"); m_evt_handler->begin_doc(); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_doc(); } } template void ParseEngine::_end_doc_suddenly__pop() { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_stack.size() >= 1); if(m_evt_handler->m_stack[0].flags & RDOC) { _c4dbgp("root is RDOC"); if(m_evt_handler->m_curr->level != 0) _handle_indentation_pop(&m_evt_handler->m_stack[0]); } else if((m_evt_handler->m_stack.size() > 1) && (m_evt_handler->m_stack[1].flags & RDOC)) { _c4dbgp("root is STREAM"); if(m_evt_handler->m_curr->level != 1) _handle_indentation_pop(&m_evt_handler->m_stack[1]); } else { _c4err("internal error"); } _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RDOC)); } template void ParseEngine::_end_doc_suddenly() { _c4dbgp("end doc suddenly"); _end_doc_suddenly__pop(); _end2_doc_expl(); addrem_flags(RUNK|RTOP|NDOC, RMAP|RSEQ|RDOC); } template void ParseEngine::_start_doc_suddenly() { _c4dbgp("start doc suddenly"); _end_doc_suddenly__pop(); _end2_doc(); _begin2_doc_expl(); } template void ParseEngine::_end_stream() { _c4dbgpf("end_stream, level={} node_id={}", m_evt_handler->m_curr->level, m_evt_handler->m_curr->node_id); if(has_all(RSEQ|FLOW)) _c4err("missing terminating ]"); else if(has_all(RMAP|FLOW)) _c4err("missing terminating }"); if(m_evt_handler->m_stack.size() > 1) _handle_indentation_pop(m_evt_handler->m_stack.begin()); if(has_all(RDOC)) { _end2_doc(); } else if(has_all(RTOP|RUNK)) { if(m_pending_anchors.num_entries || m_pending_tags.num_entries) { if(m_doc_empty) { m_evt_handler->begin_doc(); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_doc(); } } } m_evt_handler->end_stream(); } template void ParseEngine::_handle_indentation_pop(ParserState const* popto) { _c4dbgpf("popping {} level{}: from level {}(@ind={}) to level {}(@ind={})", m_evt_handler->m_curr->level - popto->level, (((m_evt_handler->m_curr->level - popto->level) > 1) ? "s" : ""), m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref, popto->level, popto->indref); while(m_evt_handler->m_curr != popto) { if(has_any(RSEQ)) { _c4dbgpf("popping seq at level {} (indentation={},addr={})", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref, m_evt_handler->m_curr); _end2_seq(); } else if(has_any(RMAP)) { _c4dbgpf("popping map at level {} (indentation={},addr={})", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref, m_evt_handler->m_curr); _end2_map(); } else { break; } } _c4dbgpf("current level is {} (indentation={})", m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); } template void ParseEngine::_handle_indentation_pop_from_block_seq() { // search the stack frame to jump to based on its indentation using state_type = typename EventHandler::state; state_type const* popto = nullptr; auto &stack = m_evt_handler->m_stack; _RYML_CB_ASSERT(stack.m_callbacks, stack.is_contiguous()); // this search relies on the stack being contiguous _RYML_CB_ASSERT(stack.m_callbacks, m_evt_handler->m_curr >= stack.begin() && m_evt_handler->m_curr < stack.end()); const size_t ind = m_evt_handler->m_curr->line_contents.indentation; #ifdef RYML_DBG if(_dbg_enabled()) { char flagbuf_[128]; for(state_type const& s : stack) _dbg_printf("state[{}]: ind={} node={} flags={}\n", s.level, s.indref, s.node_id, detail::_parser_flags_to_str(flagbuf_, s.flags)); } #endif for(state_type const* s = m_evt_handler->m_curr-1; s >= stack.begin(); --s) { _c4dbgpf("searching for state with indentation {}. curr={} (level={},node={})", ind, s->indref, s->level, s->node_id); if(s->indref == ind) { _c4dbgpf("gotit!!! level={} node={}", s->level, s->node_id); popto = s; break; } } if(!popto || popto >= m_evt_handler->m_curr || popto->level >= m_evt_handler->m_curr->level) { _c4err("parse error: incorrect indentation?"); } _handle_indentation_pop(popto); } template void ParseEngine::_handle_indentation_pop_from_block_map() { // search the stack frame to jump to based on its indentation using state_type = typename EventHandler::state; auto &stack = m_evt_handler->m_stack; _RYML_CB_ASSERT(stack.m_callbacks, stack.is_contiguous()); // this search relies on the stack being contiguous _RYML_CB_ASSERT(stack.m_callbacks, m_evt_handler->m_curr >= stack.begin() && m_evt_handler->m_curr < stack.end()); const size_t ind = m_evt_handler->m_curr->line_contents.indentation; state_type const* popto = nullptr; #ifdef RYML_DBG char flagbuf_[128]; if(_dbg_enabled()) { for(state_type const& s : stack) _dbg_printf("state[{}]: ind={} node={} flags={}\n", s.level, s.indref, s.node_id, detail::_parser_flags_to_str(flagbuf_, s.flags)); } #endif for(state_type const* s = m_evt_handler->m_curr-1; s > stack.begin(); --s) // never go to the stack bottom. that's the root { _c4dbgpf("searching for state with indentation {}. current: ind={},level={},node={},flags={}", ind, s->indref, s->level, s->node_id, detail::_parser_flags_to_str(flagbuf_, s->flags)); if(s->indref < ind) { break; } else if(s->indref == ind) { _c4dbgpf("same indentation!!! level={} node={}", s->level, s->node_id); if(popto && has_any(RTOP, s) && has_none(RMAP|RSEQ, s)) { break; } popto = s; if(has_all(RSEQ|BLCK, s)) { csubstr rem = m_evt_handler->m_curr->line_contents.rem; const size_t first = rem.first_not_of(' '); _RYML_CB_ASSERT(stack.m_callbacks, first == ind || first == npos); rem = rem.right_of(first, true); _c4dbgpf("indentless? rem='{}' first={}", rem, first); if(rem.begins_with('-') && _is_blck_token(rem)) { _c4dbgp("parent was indentless seq"); break; } } } } if(!popto || popto >= m_evt_handler->m_curr || popto->level >= m_evt_handler->m_curr->level) { _c4err("parse error: incorrect indentation?"); } _handle_indentation_pop(popto); } //----------------------------------------------------------------------------- template typename ParseEngine::ScannedScalar ParseEngine::_scan_scalar_squot() { // quoted scalars can spread over multiple lines! // nice explanation here: http://yaml-multiline.info/ // a span to the end of the file size_t b = m_evt_handler->m_curr->pos.offset; substr s = m_buf.sub(b); if(s.begins_with(' ')) { s = s.triml(' '); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_buf.sub(b).is_super(s)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.begin() >= m_buf.sub(b).begin()); _line_progressed((size_t)(s.begin() - m_buf.sub(b).begin())); } b = m_evt_handler->m_curr->pos.offset; // take this into account _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.begins_with('\'')); // skip the opening quote _line_progressed(1); s = s.sub(1); bool needs_filter = false; size_t numlines = 1; // we already have one line size_t pos = npos; // find the pos of the matching quote while( ! _finished_file()) { const csubstr line = m_evt_handler->m_curr->line_contents.rem; bool line_is_blank = true; _c4dbgpf("scanning single quoted scalar @ line[{}]: ~~~{}~~~", m_evt_handler->m_curr->pos.line, line); for(size_t i = 0; i < line.len; ++i) { const char curr = line.str[i]; if(curr == '\'') // single quotes are escaped with two single quotes { const char next = i+1 < line.len ? line.str[i+1] : '~'; if(next != '\'') // so just look for the first quote { // without another after it pos = i; break; } else { needs_filter = true; // needs filter to remove escaped quotes ++i; // skip the escaped quote } } else if(curr != ' ') { line_is_blank = false; } } // leading whitespace also needs filtering needs_filter = needs_filter || (numlines > 1) || line_is_blank || (_at_line_begin() && line.begins_with(' ')); if(pos == npos) { _line_progressed(line.len); ++numlines; } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, pos >= 0 && pos < m_buf.len); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_buf[m_evt_handler->m_curr->pos.offset + pos] == '\''); _line_progressed(pos + 1); // progress beyond the quote pos = m_evt_handler->m_curr->pos.offset - b - 1; // but we stop before it break; } _line_ended(); _scan_line(); } if(pos == npos) { _c4err("reached end of file while looking for closing quote"); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, pos > 0); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.end() >= m_buf.begin() && s.end() <= m_buf.end()); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.end() == m_buf.end() || *s.end() == '\''); s = s.sub(0, pos-1); } _c4prscalar("scanned squoted scalar", s, /*keep_newlines*/true); return ScannedScalar { s, needs_filter }; } //----------------------------------------------------------------------------- template typename ParseEngine::ScannedScalar ParseEngine::_scan_scalar_dquot() { // quoted scalars can spread over multiple lines! // nice explanation here: http://yaml-multiline.info/ // a span to the end of the file size_t b = m_evt_handler->m_curr->pos.offset; substr s = m_buf.sub(b); if(s.begins_with(' ')) { s = s.triml(' '); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_buf.sub(b).is_super(s)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.begin() >= m_buf.sub(b).begin()); _line_progressed((size_t)(s.begin() - m_buf.sub(b).begin())); } b = m_evt_handler->m_curr->pos.offset; // take this into account _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.begins_with('"')); // skip the opening quote _line_progressed(1); s = s.sub(1); bool needs_filter = false; size_t numlines = 1; // we already have one line size_t pos = npos; // find the pos of the matching quote while( ! _finished_file()) { const csubstr line = m_evt_handler->m_curr->line_contents.rem; #if defined(__GNUC__) && __GNUC__ == 11 C4_DONT_OPTIMIZE(line); // prevent erroneous hoist of the assignment out of the loop #endif bool line_is_blank = true; _c4dbgpf("scanning double quoted scalar @ line[{}]: line='{}'", m_evt_handler->m_curr->pos.line, line); for(size_t i = 0; i < line.len; ++i) { const char curr = line.str[i]; if(curr != ' ') line_is_blank = false; // every \ is an escape if(curr == '\\') { const char next = i+1 < line.len ? line.str[i+1] : '~'; needs_filter = true; if(next == '"' || next == '\\') ++i; } else if(curr == '"') { pos = i; break; } } // leading whitespace also needs filtering needs_filter = needs_filter || (numlines > 1) || line_is_blank || (_at_line_begin() && line.begins_with(' ')); if(pos == npos) { _line_progressed(line.len); ++numlines; } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, pos >= 0 && pos < m_buf.len); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_buf[m_evt_handler->m_curr->pos.offset + pos] == '"'); _line_progressed(pos + 1); // progress beyond the quote pos = m_evt_handler->m_curr->pos.offset - b - 1; // but we stop before it break; } _line_ended(); _scan_line(); } if(pos == npos) { _c4err("reached end of file looking for closing quote"); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, pos > 0); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.end() == m_buf.end() || *s.end() == '"'); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.end() >= m_buf.begin() && s.end() <= m_buf.end()); s = s.sub(0, pos-1); } _c4prscalar("scanned dquoted scalar", s, /*keep_newlines*/true); return ScannedScalar { s, needs_filter }; } //----------------------------------------------------------------------------- template void ParseEngine::_scan_block(ScannedBlock *C4_RESTRICT sb, size_t indref) { _c4dbgpf("blck: indref={}", indref); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, indref != npos); // nice explanation here: http://yaml-multiline.info/ csubstr s = m_evt_handler->m_curr->line_contents.rem; csubstr trimmed = s.triml(' '); if(trimmed.str > s.str) { _c4dbgp("skipping whitespace"); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, trimmed.str >= s.str); _line_progressed(static_cast(trimmed.str - s.str)); s = trimmed; } _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.begins_with('|') || s.begins_with('>')); _c4dbgpf("blck: specs=[{}]~~~{}~~~", s.len, s); // parse the spec BlockChomp_e chomp = CHOMP_CLIP; // default to clip unless + or - are used size_t indentation = npos; // have to find out if no spec is given csubstr digits; if(s.len > 1) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, s.begins_with_any("|>")); csubstr t = s.sub(1); _c4dbgpf("blck: spec is multichar: '{}'", t); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, t.len >= 1); size_t pos = t.first_of("-+"); _c4dbgpf("blck: spec chomp char at {}", pos); if(pos != npos) { if(t[pos] == '-') chomp = CHOMP_STRIP; else if(t[pos] == '+') chomp = CHOMP_KEEP; if(pos == 0) t = t.sub(1); else t = t.first(pos); } // from here to the end, only digits are considered digits = t.left_of(t.first_not_of("0123456789")); if( ! digits.empty()) { if(C4_UNLIKELY(digits.len > 1)) _c4err("parse error: invalid indentation"); _c4dbgpf("blck: parse indentation digits: [{}]~~~{}~~~", digits.len, digits); if(C4_UNLIKELY( ! c4::atou(digits, &indentation))) _c4err("parse error: could not read indentation as decimal"); if(C4_UNLIKELY( ! indentation)) _c4err("parse error: null indentation"); _c4dbgpf("blck: indentation specified: {}. add {} from curr state -> {}", indentation, m_evt_handler->m_curr->indref, indentation+indref); indentation += m_evt_handler->m_curr->indref; } } _c4dbgpf("blck: style={} chomp={} indentation={}", s.begins_with('>') ? "fold" : "literal", chomp==CHOMP_CLIP ? "clip" : (chomp==CHOMP_STRIP ? "strip" : "keep"), indentation); // finish the current line _line_progressed(s.len); _line_ended(); _scan_line(); // start with a zero-length block, already pointing at the right place substr raw_block(m_buf.data() + m_evt_handler->m_curr->pos.offset, size_t(0));// m_evt_handler->m_curr->line_contents.full.sub(0, 0); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, raw_block.begin() == m_evt_handler->m_curr->line_contents.full.begin()); // read every full line into a raw block, // from which newlines are to be stripped as needed. // // If no explicit indentation was given, pick it from the first // non-empty line. See // https://yaml.org/spec/1.2.2/#8111-block-indentation-indicator size_t num_lines = 0; size_t first = m_evt_handler->m_curr->pos.line; size_t provisional_indentation = npos; LineContents lc; while(( ! _finished_file())) { // peek next line, but do not advance immediately lc.reset_with_next_line(m_buf, m_evt_handler->m_curr->pos.offset); #if defined(__GNUC__) && (__GNUC__ == 12 || __GNUC__ == 13) C4_DONT_OPTIMIZE(lc.rem); #endif _c4dbgpf("blck: peeking at [{}]~~~{}~~~", lc.stripped.len, lc.stripped); // evaluate termination conditions if(indentation != npos) { _c4dbgpf("blck: indentation={}", indentation); // stop when the line is deindented and not empty if(lc.indentation < indentation && ( ! lc.rem.trim(" \t").empty())) { if(raw_block.len) { _c4dbgpf("blck: indentation decreased ref={} thisline={}", indentation, lc.indentation); } else { _c4err("indentation decreased without any scalar"); } break; } else if(indentation == 0) { _c4dbgpf("blck: noindent. lc.rem=[{}]~~~{}~~~", lc.rem.len, lc.rem); if(_is_doc_token(lc.rem)) { _c4dbgp("blck: stop. indentation=0 and doc ended"); break; } } } else { const size_t fns = lc.stripped.first_not_of(' '); _c4dbgpf("blck: indentation ref not set. firstnonws={}", fns); if(fns != npos) // non-empty line { _RYML_WITH_TAB_TOKENS( if(C4_UNLIKELY(lc.stripped.begins_with('\t'))) _c4err("parse error"); ) _c4dbgpf("blck: line not empty. indref={} indprov={} indentation={}", indref, provisional_indentation, lc.indentation); if(provisional_indentation == npos) { if(lc.indentation < indref) { _c4dbgpf("blck: block terminated indentation={} < indref={}", lc.indentation, indref); if(raw_block.len == 0) { _c4dbgp("blck: was empty, undo next line"); _line_ended_undo(); } break; } else if(lc.indentation == m_evt_handler->m_curr->indref) { if(has_any(RSEQ|RMAP)) { _c4dbgpf("blck: block terminated. reading container and indentation={}==indref={}", lc.indentation, m_evt_handler->m_curr->indref); break; } } _c4dbgpf("blck: set indentation ref from this line: ref={}", lc.indentation); indentation = lc.indentation; } else { if(lc.indentation >= provisional_indentation) { _c4dbgpf("blck: set indentation ref from provisional indentation: provisional_ref={}, thisline={}", provisional_indentation, lc.indentation); //indentation = provisional_indentation ? provisional_indentation : lc.indentation; indentation = lc.indentation; } else { break; //_c4err("parse error: first non-empty block line should have at least the original indentation"); } } } else // empty line { _c4dbgpf("blck: line empty or {} spaces. line_indentation={} prov_indentation={}", lc.stripped.len, lc.indentation, provisional_indentation); if(provisional_indentation != npos) { if(lc.stripped.len >= provisional_indentation) { _c4dbgpf("blck: increase provisional_ref {} -> {}", provisional_indentation, lc.stripped.len); provisional_indentation = lc.stripped.len; } #ifdef RYML_NO_COVERAGE__TO_BE_DELETED else if(lc.indentation >= provisional_indentation && lc.indentation != npos) { _c4dbgpf("blck: increase provisional_ref {} -> {}", provisional_indentation, lc.indentation); provisional_indentation = lc.indentation; } #endif } else { provisional_indentation = lc.indentation ? lc.indentation : has_any(RSEQ|RVAL); _c4dbgpf("blck: initialize provisional_ref={}", provisional_indentation); if(provisional_indentation == npos) { provisional_indentation = lc.stripped.len ? lc.stripped.len : has_any(RSEQ|RVAL); _c4dbgpf("blck: initialize provisional_ref={}", provisional_indentation); } if(provisional_indentation < indref) { provisional_indentation = indref; _c4dbgpf("blck: initialize provisional_ref={}", provisional_indentation); } } } } // advance now that we know the folded scalar continues m_evt_handler->m_curr->line_contents = lc; _c4dbgpf("blck: append '{}'", m_evt_handler->m_curr->line_contents.rem); raw_block.len += m_evt_handler->m_curr->line_contents.full.len; _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); _line_ended(); ++num_lines; } _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->pos.line == (first + num_lines) || (raw_block.len == 0)); C4_UNUSED(num_lines); C4_UNUSED(first); if(indentation == npos) { _c4dbgpf("blck: set indentation from provisional: {}", provisional_indentation); indentation = provisional_indentation; } if(num_lines) _line_ended_undo(); _c4prscalar("scanned block", raw_block, /*keep_newlines*/true); sb->scalar = raw_block; sb->indentation = indentation; sb->chomp = chomp; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ // a debugging scaffold: #if 0 #define _c4dbgfws(fmt, ...) _c4dbgpf("filt_ws[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) #else #define _c4dbgfws(...) #endif template template bool ParseEngine::_filter_ws_handle_to_first_non_space(FilterProcessor &proc) { _c4dbgfws("found whitespace '{}'", _c4prc(proc.curr())); _RYML_CB_ASSERT(this->callbacks(), proc.curr() == ' ' || proc.curr() == '\t'); const size_t first_pos = proc.rpos > 0 ? proc.src.first_not_of(" \t", proc.rpos) : proc.src.first_not_of(' ', proc.rpos); if(first_pos != npos) { const char first_char = proc.src[first_pos]; _c4dbgfws("firstnonws='{}'@{}", _c4prc(first_char), first_pos); if(first_char == '\n' || first_char == '\r') // skip trailing whitespace { _c4dbgfws("whitespace is trailing on line", ""); proc.skip(first_pos - proc.rpos); } else // a legit whitespace { proc.copy(); _c4dbgfws("legit whitespace. sofar=[{}]~~~{}~~~", proc.wpos, proc.sofar()); } return true; } _c4dbgfws("whitespace is trailing on line", ""); return false; } template template void ParseEngine::_filter_ws_copy_trailing(FilterProcessor &proc) { if(!_filter_ws_handle_to_first_non_space(proc)) { _c4dbgfws("... everything else is trailing whitespace - copy {} chars", proc.src.len - proc.rpos); proc.copy(proc.src.len - proc.rpos); } } template template void ParseEngine::_filter_ws_skip_trailing(FilterProcessor &proc) { if(!_filter_ws_handle_to_first_non_space(proc)) { _c4dbgfws("... everything else is trailing whitespace - skip {} chars", proc.src.len - proc.rpos); proc.skip(proc.src.len - proc.rpos); } } #undef _c4dbgfws //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /* plain scalars */ // a debugging scaffold: #if 0 #define _c4dbgfps(fmt, ...) _c4dbgpf("filt_plain[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) #else #define _c4dbgfps(fmt, ...) #endif template template void ParseEngine::_filter_nl_plain(FilterProcessor &C4_RESTRICT proc, size_t indentation) { _RYML_CB_ASSERT(this->callbacks(), proc.curr() == '\n'); _c4dbgfps("found newline. sofar=[{}]~~~{}~~~", proc.wpos, proc.sofar()); size_t ii = proc.rpos; const size_t numnl_following = _count_following_newlines(proc.src, &ii, indentation); if(numnl_following) { proc.set('\n', numnl_following); _c4dbgfps("{} consecutive (empty) lines {}. totalws={}", 1+numnl_following, ii < proc.src.len ? "in the middle" : "at the end", proc.rpos-ii); } else { const size_t ret = proc.src.first_not_of(" \t", proc.rpos+1); if(ret != npos) { proc.set(' '); _c4dbgfps("single newline. convert to space. ret={}/{}. sofar=[{}]~~~{}~~~", ii, proc.src.len, proc.wpos, proc.sofar()); } else { _c4dbgfps("last newline, everything else is whitespace. ii={}/{}", ii, proc.src.len); ii = proc.src.len; } } proc.rpos = ii; } template template auto ParseEngine::_filter_plain(FilterProcessor &C4_RESTRICT proc, size_t indentation) -> decltype(proc.result()) { _RYML_CB_ASSERT(this->callbacks(), indentation != npos); _c4dbgfps("before=[{}]~~~{}~~~", proc.src.len, proc.src); while(proc.has_more_chars()) { const char curr = proc.curr(); _c4dbgfps("'{}', sofar=[{}]~~~{}~~~", _c4prc(curr), proc.wpos, proc.sofar()); switch(curr) { case ' ': _RYML_WITH_TAB_TOKENS(case '\t':) _c4dbgfps("whitespace", curr); _filter_ws_skip_trailing(proc); break; case '\n': _c4dbgfps("newline", curr); _filter_nl_plain(proc, /*indentation*/indentation); break; case '\r': // skip \r --- https://stackoverflow.com/questions/1885900 _c4dbgfps("carriage return, ignore", curr); proc.skip(); break; default: proc.copy(); break; } } _c4dbgfps("after[{}]=~~~{}~~~", proc.wpos, proc.sofar()); return proc.result(); } #undef _c4dbgfps template FilterResult ParseEngine::filter_scalar_plain(csubstr scalar, substr dst, size_t indentation) { FilterProcessorSrcDst proc(scalar, dst); return _filter_plain(proc, indentation); } template FilterResult ParseEngine::filter_scalar_plain_in_place(substr dst, size_t cap, size_t indentation) { FilterProcessorInplaceEndExtending proc(dst, cap); return _filter_plain(proc, indentation); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /* single quoted */ // a debugging scaffold: #if 0 #define _c4dbgfsq(fmt, ...) _c4dbgpf("filt_squo[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) #else #define _c4dbgfsq(fmt, ...) #endif template template void ParseEngine::_filter_nl_squoted(FilterProcessor &C4_RESTRICT proc) { _RYML_CB_ASSERT(this->callbacks(), proc.curr() == '\n'); _c4dbgfsq("found newline. sofar=[{}]~~~{}~~~", proc.wpos, proc.sofar()); size_t ii = proc.rpos; const size_t numnl_following = _count_following_newlines(proc.src, &ii); if(numnl_following) { proc.set('\n', numnl_following); _c4dbgfsq("{} consecutive (empty) lines {}. totalws={}", 1+numnl_following, ii < proc.src.len ? "in the middle" : "at the end", proc.rpos-ii); } else { const size_t ret = proc.src.first_not_of(" \t", proc.rpos+1); if(ret != npos) { proc.set(' '); _c4dbgfsq("single newline. convert to space. ret={}/{}. sofar=[{}]~~~{}~~~", ii, proc.src.len, proc.wpos, proc.sofar()); } else { proc.set(' '); _c4dbgfsq("single newline. convert to space. ii={}/{}. sofar=[{}]~~~{}~~~", ii, proc.src.len, proc.wpos, proc.sofar()); } } proc.rpos = ii; } template template auto ParseEngine::_filter_squoted(FilterProcessor &C4_RESTRICT proc) -> decltype(proc.result()) { _c4dbgfsq("before=[{}]~~~{}~~~", proc.src.len, proc.src); // from the YAML spec for double-quoted scalars: // https://yaml.org/spec/1.2-old/spec.html#style/flow/single-quoted while(proc.has_more_chars()) { const char curr = proc.curr(); _c4dbgfsq("'{}', sofar=[{}]~~~{}~~~", _c4prc(curr), proc.wpos, proc.sofar()); switch(curr) { case ' ': case '\t': _c4dbgfsq("whitespace", curr); _filter_ws_copy_trailing(proc); break; case '\n': _c4dbgfsq("newline", curr); _filter_nl_squoted(proc); break; case '\r': // skip \r --- https://stackoverflow.com/questions/1885900 _c4dbgfsq("skip cr", curr); proc.skip(); break; case '\'': _c4dbgfsq("squote", curr); if(proc.next() == '\'') { _c4dbgfsq("two consecutive squotes", curr); proc.skip(); proc.copy(); } else { _c4err("filter error"); } break; default: proc.copy(); break; } } _c4dbgfsq(": #filteredchars={} after=~~~[{}]{}~~~", proc.src.len-proc.sofar().len, proc.sofar().len, proc.sofar()); return proc.result(); } #undef _c4dbgfsq template FilterResult ParseEngine::filter_scalar_squoted(csubstr scalar, substr dst) { FilterProcessorSrcDst proc(scalar, dst); return _filter_squoted(proc); } template FilterResult ParseEngine::filter_scalar_squoted_in_place(substr dst, size_t cap) { FilterProcessorInplaceEndExtending proc(dst, cap); return _filter_squoted(proc); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /* double quoted */ // a debugging scaffold: #if 0 #define _c4dbgfdq(fmt, ...) _c4dbgpf("filt_dquo[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) #else #define _c4dbgfdq(...) #endif template template void ParseEngine::_filter_nl_dquoted(FilterProcessor &C4_RESTRICT proc) { _RYML_CB_ASSERT(this->callbacks(), proc.curr() == '\n'); _c4dbgfdq("found newline. sofar=[{}]~~~{}~~~", proc.wpos, proc.sofar()); size_t ii = proc.rpos; const size_t numnl_following = _count_following_newlines(proc.src, &ii); if(numnl_following) { proc.set('\n', numnl_following); _c4dbgfdq("{} consecutive (empty) lines {}. totalws={}", 1+numnl_following, ii < proc.src.len ? "in the middle" : "at the end", proc.rpos-ii); } else { const size_t ret = proc.src.first_not_of(" \t", proc.rpos+1); if(ret != npos) { proc.set(' '); _c4dbgfdq("single newline. convert to space. ret={}/{}. sofar=[{}]~~~{}~~~", ii, proc.src.len, proc.wpos, proc.sofar()); } else { proc.set(' '); _c4dbgfdq("single newline. convert to space. ii={}/{}. sofar=[{}]~~~{}~~~", ii, proc.src.len, proc.wpos, proc.sofar()); } if(ii < proc.src.len && proc.src.str[ii] == '\\') { _c4dbgfdq("backslash at [{}]", ii); const char next = ii+1 < proc.src.len ? proc.src.str[ii+1] : '\0'; if(next == ' ' || next == '\t') { _c4dbgfdq("extend skip to backslash", ""); ++ii; } } } proc.rpos = ii; } template template void ParseEngine::_filter_dquoted_backslash(FilterProcessor &C4_RESTRICT proc) { char next = proc.next(); _c4dbgfdq("backslash, next='{}'", _c4prc(next)); if(next == '\r') { if(proc.rpos+2 < proc.src.len && proc.src.str[proc.rpos+2] == '\n') { proc.skip(); // newline escaped with \ -- skip both (add only one as i is loop-incremented) next = '\n'; _c4dbgfdq("[{}]: was \\r\\n, now next='\\n'", proc.rpos); } } if(next == '\n') { size_t ii = proc.rpos + 2; for( ; ii < proc.src.len; ++ii) { // skip leading whitespace if(proc.src.str[ii] == ' ' || proc.src.str[ii] == '\t') ; else break; } proc.skip(ii - proc.rpos); } else if(next == '"' || next == '/' || next == ' ' || next == '\t') { // escapes for json compatibility proc.translate_esc(next); _c4dbgfdq("here, used '{}'", _c4prc(next)); } else if(next == '\r') { proc.skip(); } else if(next == 'n') { proc.translate_esc('\n'); } else if(next == 'r') { proc.translate_esc('\r'); } else if(next == 't') { proc.translate_esc('\t'); } else if(next == '\\') { proc.translate_esc('\\'); } else if(next == 'x') // UTF8 { if(C4_UNLIKELY(proc.rpos + 1u + 2u >= proc.src.len)) _c4err("\\x requires 2 hex digits. scalar pos={}", proc.rpos); csubstr codepoint = proc.src.sub(proc.rpos + 2u, 2u); _c4dbgfdq("utf8 ~~~{}~~~ rpos={} rem=~~~{}~~~", codepoint, proc.rpos, proc.src.sub(proc.rpos)); uint8_t byteval = {}; if(C4_UNLIKELY(!read_hex(codepoint, &byteval))) _c4err("failed to read \\x codepoint. scalar pos={}", proc.rpos); proc.translate_esc_bulk((const char*)&byteval, 1u, /*nread*/3u); _c4dbgfdq("utf8 after rpos={} rem=~~~{}~~~", proc.rpos, proc.src.sub(proc.rpos)); } else if(next == 'u') // UTF16 { if(C4_UNLIKELY(proc.rpos + 1u + 4u >= proc.src.len)) _c4err("\\u requires 4 hex digits. scalar pos={}", proc.rpos); char readbuf[8]; csubstr codepoint = proc.src.sub(proc.rpos + 2u, 4u); uint32_t codepoint_val = {}; if(C4_UNLIKELY(!read_hex(codepoint, &codepoint_val))) _c4err("failed to parse \\u codepoint. scalar pos={}", proc.rpos); const size_t numbytes = decode_code_point((uint8_t*)readbuf, sizeof(readbuf), codepoint_val); if(C4_UNLIKELY(numbytes == 0)) _c4err("failed to decode code point={}", proc.rpos); _RYML_CB_ASSERT(callbacks(), numbytes <= 4); proc.translate_esc_bulk(readbuf, numbytes, /*nread*/5u); } else if(next == 'U') // UTF32 { if(C4_UNLIKELY(proc.rpos + 1u + 8u >= proc.src.len)) _c4err("\\U requires 8 hex digits. scalar pos={}", proc.rpos); char readbuf[8]; csubstr codepoint = proc.src.sub(proc.rpos + 2u, 8u); uint32_t codepoint_val = {}; if(C4_UNLIKELY(!read_hex(codepoint, &codepoint_val))) _c4err("failed to parse \\U codepoint. scalar pos={}", proc.rpos); const size_t numbytes = decode_code_point((uint8_t*)readbuf, sizeof(readbuf), codepoint_val); if(C4_UNLIKELY(numbytes == 0)) _c4err("failed to decode code point={}", proc.rpos); _RYML_CB_ASSERT(callbacks(), numbytes <= 4); proc.translate_esc_bulk(readbuf, numbytes, /*nread*/9u); } // https://yaml.org/spec/1.2.2/#rule-c-ns-esc-char else if(next == '0') { proc.translate_esc('\0'); } else if(next == 'b') // backspace { proc.translate_esc('\b'); } else if(next == 'f') // form feed { proc.translate_esc('\f'); } else if(next == 'a') // bell character { proc.translate_esc('\a'); } else if(next == 'v') // vertical tab { proc.translate_esc('\v'); } else if(next == 'e') // escape character { proc.translate_esc('\x1b'); } else if(next == '_') // unicode non breaking space \u00a0 { // https://www.compart.com/en/unicode/U+00a0 const char payload[] = { _RYML_CHCONST(-0x3e, 0xc2), _RYML_CHCONST(-0x60, 0xa0), }; proc.translate_esc_bulk(payload, /*nwrite*/2, /*nread*/1); } else if(next == 'N') // unicode next line \u0085 { // https://www.compart.com/en/unicode/U+0085 const char payload[] = { _RYML_CHCONST(-0x3e, 0xc2), _RYML_CHCONST(-0x7b, 0x85), }; proc.translate_esc_bulk(payload, /*nwrite*/2, /*nread*/1); } else if(next == 'L') // unicode line separator \u2028 { // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192&number=1024&names=-&utf8=0x&unicodeinhtml=hex const char payload[] = { _RYML_CHCONST(-0x1e, 0xe2), _RYML_CHCONST(-0x80, 0x80), _RYML_CHCONST(-0x58, 0xa8), }; proc.translate_esc_extending(payload, /*nwrite*/3, /*nread*/1); } else if(next == 'P') // unicode paragraph separator \u2029 { // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192&number=1024&names=-&utf8=0x&unicodeinhtml=hex const char payload[] = { _RYML_CHCONST(-0x1e, 0xe2), _RYML_CHCONST(-0x80, 0x80), _RYML_CHCONST(-0x57, 0xa9), }; proc.translate_esc_extending(payload, /*nwrite*/3, /*nread*/1); } else if(next == '\0') { proc.skip(); } else { _c4err("unknown character '{}' after '\\' pos={}", _c4prc(next), proc.rpos); } _c4dbgfdq("backslash...sofar=[{}]~~~{}~~~", proc.wpos, proc.sofar()); } template template auto ParseEngine::_filter_dquoted(FilterProcessor &C4_RESTRICT proc) -> decltype(proc.result()) { _c4dbgfdq("before=[{}]~~~{}~~~", proc.src.len, proc.src); // from the YAML spec for double-quoted scalars: // https://yaml.org/spec/1.2-old/spec.html#style/flow/double-quoted while(proc.has_more_chars()) { const char curr = proc.curr(); _c4dbgfdq("'{}' sofar=[{}]~~~{}~~~", _c4prc(curr), proc.wpos, proc.sofar()); switch(curr) { case ' ': case '\t': { _c4dbgfdq("whitespace", curr); _filter_ws_copy_trailing(proc); break; } case '\n': { _c4dbgfdq("newline", curr); _filter_nl_dquoted(proc); break; } case '\r': // skip \r --- https://stackoverflow.com/questions/1885900 { _c4dbgfdq("carriage return, ignore", curr); proc.skip(); break; } case '\\': { _filter_dquoted_backslash(proc); break; } default: { proc.copy(); break; } } } _c4dbgfdq("after[{}]=~~~{}~~~", proc.wpos, proc.sofar()); return proc.result(); } #undef _c4dbgfdq template FilterResult ParseEngine::filter_scalar_dquoted(csubstr scalar, substr dst) { FilterProcessorSrcDst proc(scalar, dst); return _filter_dquoted(proc); } template FilterResultExtending ParseEngine::filter_scalar_dquoted_in_place(substr dst, size_t cap) { FilterProcessorInplaceMidExtending proc(dst, cap); return _filter_dquoted(proc); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // block filtering helpers template template void ParseEngine::_filter_chomp(FilterProcessor &C4_RESTRICT proc, BlockChomp_e chomp, size_t indentation) { _RYML_CB_ASSERT(this->callbacks(), chomp == CHOMP_CLIP || chomp == CHOMP_KEEP || chomp == CHOMP_STRIP); _RYML_CB_ASSERT(this->callbacks(), proc.rem().first_not_of(" \n\r") == npos); // a debugging scaffold: #if 0 #define _c4dbgchomp(fmt, ...) _c4dbgpf("chomp[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) #else #define _c4dbgchomp(...) #endif // advance to the last line having spaces beyond the indentation { size_t last = _find_last_newline_and_larger_indentation(proc.rem(), indentation); if(last != npos) { _c4dbgchomp("found newline and larger indentation. last={}", last); last = proc.rpos + last + size_t(1) + indentation; // last started at to-be-read. _RYML_CB_ASSERT(this->callbacks(), last <= proc.src.len); // remove indentation spaces, copy the rest while((proc.rpos < last) && proc.has_more_chars()) { const char curr = proc.curr(); _c4dbgchomp("curr='{}'", _c4prc(curr)); switch(curr) { case '\n': { _c4dbgchomp("newline! remlen={}", proc.rem().len); proc.copy(); // are there spaces after the newline? csubstr at_next_line = proc.rem(); if(at_next_line.begins_with(' ')) { _c4dbgchomp("next line begins with spaces. indentation={}", indentation); // there are spaces. size_t first_non_space = at_next_line.first_not_of(' '); _c4dbgchomp("first_non_space={}", first_non_space); if(first_non_space == npos) { _c4dbgchomp("{} spaces, to the end", at_next_line.len); first_non_space = at_next_line.len; } if(first_non_space <= indentation) { _c4dbgchomp("skip spaces={}<=indentation={}", first_non_space, indentation); proc.skip(first_non_space); } else { _c4dbgchomp("skip indentation={}{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) #else #define _c4dbgfb(...) #endif template template void ParseEngine::_filter_block_indentation(FilterProcessor &C4_RESTRICT proc, size_t indentation) { csubstr rem = proc.rem(); // remaining if(rem.len) { size_t first = rem.first_not_of(' '); if(first != npos) { _c4dbgfb("{} spaces follow before next nonws character", first); if(first < indentation) { _c4dbgfb("skip {}<{} spaces from indentation", first, indentation); proc.skip(first); } else { _c4dbgfb("skip {} spaces from indentation", indentation); proc.skip(indentation); } } #ifdef RYML_NO_COVERAGE__TO_BE_DELETED else { _c4dbgfb("all spaces to the end: {} spaces", first); first = rem.len; if(first) { if(first < indentation) { _c4dbgfb("skip everything", first); proc.skip(proc.src.len - proc.rpos); } else { _c4dbgfb("skip {} spaces from indentation", indentation); proc.skip(indentation); } } } #endif } } template template size_t ParseEngine::_handle_all_whitespace(FilterProcessor &C4_RESTRICT proc, BlockChomp_e chomp) { csubstr contents = proc.src.trimr(" \n\r"); _c4dbgfb("ws: contents_len={} wslen={}", contents.len, proc.src.len-contents.len); if(!contents.len) { _c4dbgfb("ws: all whitespace: len={}", proc.src.len); if(chomp == CHOMP_KEEP && proc.src.len) { _c4dbgfb("ws: chomp=KEEP all {} newlines", proc.src.count('\n')); while(proc.has_more_chars()) { const char curr = proc.curr(); if(curr == '\n') proc.copy(); else proc.skip(); } if(!proc.wpos) { proc.set('\n'); } } } return contents.len; } template template size_t ParseEngine::_extend_to_chomp(FilterProcessor &C4_RESTRICT proc, size_t contents_len) { _c4dbgfb("contents_len={}", contents_len); _RYML_CB_ASSERT(this->callbacks(), contents_len > 0u); // extend contents to just before the first newline at the end, // in case it is preceded by spaces size_t firstnewl = proc.src.first_of('\n', contents_len); if(firstnewl != npos) { contents_len = firstnewl; _c4dbgfb("contents_len={} <--- firstnewl={}", contents_len, firstnewl); } else { contents_len = proc.src.len; _c4dbgfb("contents_len={} <--- src.len={}", contents_len, proc.src.len); } return contents_len; } #undef _c4dbgfb //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // a debugging scaffold: #if 0 #define _c4dbgfbl(fmt, ...) _c4dbgpf("filt_block_lit[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) #else #define _c4dbgfbl(...) #endif template template auto ParseEngine::_filter_block_literal(FilterProcessor &C4_RESTRICT proc, size_t indentation, BlockChomp_e chomp) -> decltype(proc.result()) { _c4dbgfbl("indentation={} before=[{}]~~~{}~~~", indentation, proc.src.len, proc.src); size_t contents_len = _handle_all_whitespace(proc, chomp); if(!contents_len) return proc.result(); contents_len = _extend_to_chomp(proc, contents_len); _c4dbgfbl("to filter=[{}]~~~{}~~~", contents_len, proc.src.first(contents_len)); _filter_block_indentation(proc, indentation); // now filter the bulk while(proc.has_more_chars(/*maxpos*/contents_len)) { const char curr = proc.curr(); _c4dbgfbl("'{}' sofar=[{}]~~~{}~~~", _c4prc(curr), proc.wpos, proc.sofar()); switch(curr) { case '\n': { _c4dbgfbl("found newline. skip indentation on the next line", curr); proc.copy(); // copy the newline _filter_block_indentation(proc, indentation); break; } case '\r': proc.skip(); break; default: proc.copy(); break; } } _c4dbgfbl("before chomp: #tochomp={} sofar=[{}]~~~{}~~~", proc.rem().len, proc.sofar().len, proc.sofar()); _filter_chomp(proc, chomp, indentation); _c4dbgfbl("final=[{}]~~~{}~~~", proc.sofar().len, proc.sofar()); return proc.result(); } #undef _c4dbgfbl template FilterResult ParseEngine::filter_scalar_block_literal(csubstr scalar, substr dst, size_t indentation, BlockChomp_e chomp) { FilterProcessorSrcDst proc(scalar, dst); return _filter_block_literal(proc, indentation, chomp); } template FilterResult ParseEngine::filter_scalar_block_literal_in_place(substr scalar, size_t cap, size_t indentation, BlockChomp_e chomp) { FilterProcessorInplaceEndExtending proc(scalar, cap); return _filter_block_literal(proc, indentation, chomp); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // a debugging scaffold: #if 0 #define _c4dbgfbf(fmt, ...) _c4dbgpf("filt_block_folded[{}->{}]: " fmt, proc.rpos, proc.wpos, __VA_ARGS__) #else #define _c4dbgfbf(...) #endif template template void ParseEngine::_filter_block_folded_newlines_leading(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len) { _filter_block_indentation(proc, indentation); while(proc.has_more_chars(len)) { const char curr = proc.curr(); _c4dbgfbf("'{}' sofar=[{}]~~~{}~~~", _c4prc(curr), proc.wpos, proc.sofar()); switch(curr) { case '\n': _c4dbgfbf("newline.", curr); proc.copy(); _filter_block_indentation(proc, indentation); break; case '\r': proc.skip(); break; case ' ': case '\t': { size_t first = proc.rem().first_not_of(" \t"); _c4dbgfbf("space. first={}", first); if(first == npos) first = proc.rem().len; _c4dbgfbf("... indentation increased to {}", first); _filter_block_folded_indented_block(proc, indentation, len, first); break; } default: _c4dbgfbf("newl leading: not space, not newline. stop.", 0); return; } } } template template size_t ParseEngine::_filter_block_folded_newlines_compress(FilterProcessor &C4_RESTRICT proc, size_t num_newl, size_t wpos_at_first_newl) { switch(num_newl) { case 1u: _c4dbgfbf("... this is the first newline. turn into space. wpos={}", proc.wpos); wpos_at_first_newl = proc.wpos; proc.skip(); proc.set(' '); break; case 2u: _c4dbgfbf("... this is the second newline. prev space (at wpos={}) must be newline", wpos_at_first_newl); _RYML_CB_ASSERT(this->callbacks(), wpos_at_first_newl != npos); _RYML_CB_ASSERT(this->callbacks(), proc.sofar()[wpos_at_first_newl] == ' '); _RYML_CB_ASSERT(this->callbacks(), wpos_at_first_newl + 1u == proc.wpos); proc.skip(); proc.set_at(wpos_at_first_newl, '\n'); _RYML_CB_ASSERT(this->callbacks(), proc.sofar()[wpos_at_first_newl] == '\n'); break; default: _c4dbgfbf("... subsequent newline (num_newl={}). copy", num_newl); proc.copy(); break; } return wpos_at_first_newl; } template template void ParseEngine::_filter_block_folded_newlines(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len) { _RYML_CB_ASSERT(this->callbacks(), proc.curr() == '\n'); size_t num_newl = 0; size_t wpos_at_first_newl = npos; while(proc.has_more_chars(len)) { const char curr = proc.curr(); _c4dbgfbf("'{}' sofar=[{}]~~~{}~~~", _c4prc(curr), proc.wpos, proc.sofar()); switch(curr) { case '\n': { _c4dbgfbf("newline. sofar={}", num_newl); // NOTE: vs2022-32bit-release builds were giving wrong // results in this block, if it was written as either // as a switch(num_newl) or its equivalent if-form. // // For this reason, we're using a dedicated function // (**_compress), which seems to work around the issue. // // The manifested problem was that somewhere between the // assignment to curr and this point, proc.wpos (the // write-position of the processor) jumped to npos, which // made the write wrap-around! To make things worse, // enabling prints via _c4dbgpf() and _c4dbgfbf() made the // problem go away! // // The only way to make the problem appear with prints // enabled was by disabling all prints in this function // (including in the block which was moved to the compress // function) and then selectively enabling only some of // those prints. // // This may be due to some bug in the cl-x86 optimizer; or // it may be triggered by some UB which may be // inadvertedly present in this function or in the filter // processor. This is despite our best efforts to weed out // any such UB problem: neither clang-tidy nor none of the // sanitizers, or gcc's -fanalyzer pointed to any problems // in this code. // // In the end, moving this block to a separate function // was the only way to bury the problem. But it may // resurface again, as The Undead, rising to from the // grave to haunt us with his terrible presence. // // We may have to revisit this. With a stake, and lots of // garlic. wpos_at_first_newl = _filter_block_folded_newlines_compress(proc, ++num_newl, wpos_at_first_newl); _filter_block_indentation(proc, indentation); break; } case ' ': case '\t': { size_t first = proc.rem().first_not_of(" \t"); _c4dbgfbf("space. first={}", first); if(first == npos) first = proc.rem().len; _c4dbgfbf("... indentation increased to {}", first); if(num_newl) { _c4dbgfbf("... prev space (at wpos={}) must be newline", wpos_at_first_newl); proc.set_at(wpos_at_first_newl, '\n'); } if(num_newl > 1u) { _c4dbgfbf("... add missing newline", wpos_at_first_newl); proc.set('\n'); } _filter_block_folded_indented_block(proc, indentation, len, first); num_newl = 0; wpos_at_first_newl = npos; break; } case '\r': proc.skip(); break; default: _c4dbgfbf("not space, not newline. stop.", 0); return; } } } template template void ParseEngine::_filter_block_folded_indented_block(FilterProcessor &C4_RESTRICT proc, size_t indentation, size_t len, size_t curr_indentation) noexcept { _RYML_CB_ASSERT(this->callbacks(), (proc.rem().first_not_of(" \t") == curr_indentation) || (proc.rem().first_not_of(" \t") == npos)); if(curr_indentation) proc.copy(curr_indentation); while(proc.has_more_chars(len)) { const char curr = proc.curr(); _c4dbgfbf("'{}' sofar=[{}]~~~{}~~~", _c4prc(curr), proc.wpos, proc.sofar()); switch(curr) { case '\n': { proc.copy(); _filter_block_indentation(proc, indentation); csubstr rem = proc.rem(); const size_t first = rem.first_not_of(' '); _c4dbgfbf("newline. firstns={}", first); if(first == 0) { const char c = rem[first]; _c4dbgfbf("firstns={}='{}'", first, _c4prc(c)); if(c == '\n' || c == '\r') { ; } else { _c4dbgfbf("done with indented block", first); goto endloop; } } else if(first != npos) { proc.copy(first); _c4dbgfbf("copy all {} spaces", first); } break; } break; case '\r': proc.skip(); break; default: proc.copy(); break; } } endloop: return; } template template auto ParseEngine::_filter_block_folded(FilterProcessor &C4_RESTRICT proc, size_t indentation, BlockChomp_e chomp) -> decltype(proc.result()) { _c4dbgfbf("indentation={} before=[{}]~~~{}~~~", indentation, proc.src.len, proc.src); size_t contents_len = _handle_all_whitespace(proc, chomp); if(!contents_len) return proc.result(); contents_len = _extend_to_chomp(proc, contents_len); _c4dbgfbf("to filter=[{}]~~~{}~~~", contents_len, proc.src.first(contents_len)); _filter_block_folded_newlines_leading(proc, indentation, contents_len); // now filter the bulk while(proc.has_more_chars(/*maxpos*/contents_len)) { const char curr = proc.curr(); _c4dbgfbf("'{}' sofar=[{}]~~~{}~~~", _c4prc(curr), proc.wpos, proc.sofar()); switch(curr) { case '\n': { _c4dbgfbf("found newline", curr); _filter_block_folded_newlines(proc, indentation, contents_len); break; } case '\r': proc.skip(); break; default: proc.copy(); break; } } _c4dbgfbf("before chomp: #tochomp={} sofar=[{}]~~~{}~~~", proc.rem().len, proc.sofar().len, proc.sofar()); _filter_chomp(proc, chomp, indentation); _c4dbgfbf("final=[{}]~~~{}~~~", proc.sofar().len, proc.sofar()); return proc.result(); } #undef _c4dbgfbf template FilterResult ParseEngine::filter_scalar_block_folded(csubstr scalar, substr dst, size_t indentation, BlockChomp_e chomp) { FilterProcessorSrcDst proc(scalar, dst); return _filter_block_folded(proc, indentation, chomp); } template FilterResult ParseEngine::filter_scalar_block_folded_in_place(substr scalar, size_t cap, size_t indentation, BlockChomp_e chomp) { FilterProcessorInplaceEndExtending proc(scalar, cap); return _filter_block_folded(proc, indentation, chomp); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template csubstr ParseEngine::_filter_scalar_plain(substr s, size_t indentation) { _c4dbgpf("filtering plain scalar: s=[{}]~~~{}~~~", s.len, s); FilterResult r = this->filter_scalar_plain_in_place(s, s.len, indentation); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, r.valid()); _c4dbgpf("filtering plain scalar: success! s=[{}]~~~{}~~~", r.get().len, r.get()); return r.get(); } //----------------------------------------------------------------------------- template csubstr ParseEngine::_filter_scalar_squot(substr s) { _c4dbgpf("filtering squo scalar: s=[{}]~~~{}~~~", s.len, s); FilterResult r = this->filter_scalar_squoted_in_place(s, s.len); _RYML_CB_ASSERT(this->callbacks(), r.valid()); _c4dbgpf("filtering squo scalar: success! s=[{}]~~~{}~~~", r.get().len, r.get()); return r.get(); } //----------------------------------------------------------------------------- template csubstr ParseEngine::_filter_scalar_dquot(substr s) { _c4dbgpf("filtering dquo scalar: s=[{}]~~~{}~~~", s.len, s); FilterResultExtending r = this->filter_scalar_dquoted_in_place(s, s.len); if(C4_LIKELY(r.valid())) { _c4dbgpf("filtering dquo scalar: success! s=[{}]~~~{}~~~", r.get().len, r.get()); return r.get(); } else { const size_t len = r.required_len(); _c4dbgpf("filtering dquo scalar: not enough space: needs {}, have {}", len, s.len); substr dst = m_evt_handler->alloc_arena(len, &s); _c4dbgpf("filtering dquo scalar: dst.len={}", dst.len); _RYML_CB_ASSERT(this->callbacks(), dst.len == len); FilterResult rsd = this->filter_scalar_dquoted(s, dst); _c4dbgpf("filtering dquo scalar: ... result now needs {} was {}", rsd.required_len(), len); _RYML_CB_ASSERT(this->callbacks(), rsd.required_len() <= len); // may be smaller! _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, rsd.valid()); _c4dbgpf("filtering dquo scalar: success! s=[{}]~~~{}~~~", rsd.get().len, rsd.get()); return rsd.get(); } } //----------------------------------------------------------------------------- template csubstr ParseEngine::_filter_scalar_literal(substr s, size_t indentation, BlockChomp_e chomp) { _c4dbgpf("filtering block literal scalar: s=[{}]~~~{}~~~", s.len, s); FilterResult r = this->filter_scalar_block_literal_in_place(s, s.len, indentation, chomp); if(C4_LIKELY(r.valid())) { _c4dbgpf("filtering block literal scalar: success! s=[{}]~~~{}~~~", r.get().len, r.get()); return r.get(); } else { _c4dbgpf("filtering block literal scalar: not enough space: needs {}, have {}", r.required_len(), s.len); substr dst = m_evt_handler->alloc_arena(r.required_len(), &s); FilterResult rsd = this->filter_scalar_block_literal(s, dst, indentation, chomp); _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, rsd.valid()); _c4dbgpf("filtering block literal scalar: success! s=[{}]~~~{}~~~", rsd.get().len, rsd.get()); return rsd.get(); } } //----------------------------------------------------------------------------- template csubstr ParseEngine::_filter_scalar_folded(substr s, size_t indentation, BlockChomp_e chomp) { _c4dbgpf("filtering block folded scalar: s=[{}]~~~{}~~~", s.len, s); FilterResult r = this->filter_scalar_block_folded_in_place(s, s.len, indentation, chomp); if(C4_LIKELY(r.valid())) { _c4dbgpf("filtering block folded scalar: success! s=[{}]~~~{}~~~", r.get().len, r.get()); return r.get(); } else { _c4dbgpf("filtering block folded scalar: not enough space: needs {}, have {}", r.required_len(), s.len); substr dst = m_evt_handler->alloc_arena(r.required_len(), &s); FilterResult rsd = this->filter_scalar_block_folded(s, dst, indentation, chomp); _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, rsd.valid()); _c4dbgpf("filtering block folded scalar: success! s=[{}]~~~{}~~~", rsd.get().len, rsd.get()); return rsd.get(); } } //----------------------------------------------------------------------------- template csubstr ParseEngine::_maybe_filter_key_scalar_plain(ScannedScalar const& C4_RESTRICT sc, size_t indentation) { if(sc.needs_filter) { if(m_options.scalar_filtering()) { return _filter_scalar_plain(sc.scalar, indentation); } else { _c4dbgp("plain scalar left unfiltered"); m_evt_handler->mark_key_scalar_unfiltered(); } } else { _c4dbgp("plain scalar doesn't need filtering"); } return sc.scalar; } template csubstr ParseEngine::_maybe_filter_val_scalar_plain(ScannedScalar const& C4_RESTRICT sc, size_t indentation) { if(sc.needs_filter) { if(m_options.scalar_filtering()) { return _filter_scalar_plain(sc.scalar, indentation); } else { _c4dbgp("plain scalar left unfiltered"); m_evt_handler->mark_val_scalar_unfiltered(); } } else { _c4dbgp("plain scalar doesn't need filtering"); } return sc.scalar; } //----------------------------------------------------------------------------- template csubstr ParseEngine::_maybe_filter_key_scalar_squot(ScannedScalar const& C4_RESTRICT sc) { if(sc.needs_filter) { if(m_options.scalar_filtering()) { return _filter_scalar_squot(sc.scalar); } else { _c4dbgp("squo key scalar left unfiltered"); m_evt_handler->mark_key_scalar_unfiltered(); } } else { _c4dbgp("squo key scalar doesn't need filtering"); } return sc.scalar; } template csubstr ParseEngine::_maybe_filter_val_scalar_squot(ScannedScalar const& C4_RESTRICT sc) { if(sc.needs_filter) { if(m_options.scalar_filtering()) { return _filter_scalar_squot(sc.scalar); } else { _c4dbgp("squo val scalar left unfiltered"); m_evt_handler->mark_val_scalar_unfiltered(); } } else { _c4dbgp("squo val scalar doesn't need filtering"); } return sc.scalar; } //----------------------------------------------------------------------------- template csubstr ParseEngine::_maybe_filter_key_scalar_dquot(ScannedScalar const& C4_RESTRICT sc) { if(sc.needs_filter) { if(m_options.scalar_filtering()) { return _filter_scalar_dquot(sc.scalar); } else { _c4dbgp("dquo scalar left unfiltered"); m_evt_handler->mark_key_scalar_unfiltered(); } } else { _c4dbgp("dquo scalar doesn't need filtering"); } return sc.scalar; } template csubstr ParseEngine::_maybe_filter_val_scalar_dquot(ScannedScalar const& C4_RESTRICT sc) { if(sc.needs_filter) { if(m_options.scalar_filtering()) { return _filter_scalar_dquot(sc.scalar); } else { _c4dbgp("dquo scalar left unfiltered"); m_evt_handler->mark_val_scalar_unfiltered(); } } else { _c4dbgp("dquo scalar doesn't need filtering"); } return sc.scalar; } //----------------------------------------------------------------------------- template csubstr ParseEngine::_maybe_filter_key_scalar_literal(ScannedBlock const& C4_RESTRICT sb) { if(m_options.scalar_filtering()) { return _filter_scalar_literal(sb.scalar, sb.indentation, sb.chomp); } else { _c4dbgp("literal scalar left unfiltered"); m_evt_handler->mark_key_scalar_unfiltered(); } return sb.scalar; } template csubstr ParseEngine::_maybe_filter_val_scalar_literal(ScannedBlock const& C4_RESTRICT sb) { if(m_options.scalar_filtering()) { return _filter_scalar_literal(sb.scalar, sb.indentation, sb.chomp); } else { _c4dbgp("literal scalar left unfiltered"); m_evt_handler->mark_val_scalar_unfiltered(); } return sb.scalar; } //----------------------------------------------------------------------------- template csubstr ParseEngine::_maybe_filter_key_scalar_folded(ScannedBlock const& C4_RESTRICT sb) { if(m_options.scalar_filtering()) { return _filter_scalar_folded(sb.scalar, sb.indentation, sb.chomp); } else { _c4dbgp("folded scalar left unfiltered"); m_evt_handler->mark_key_scalar_unfiltered(); } return sb.scalar; } template csubstr ParseEngine::_maybe_filter_val_scalar_folded(ScannedBlock const& C4_RESTRICT sb) { if(m_options.scalar_filtering()) { return _filter_scalar_folded(sb.scalar, sb.indentation, sb.chomp); } else { _c4dbgp("folded scalar left unfiltered"); m_evt_handler->mark_val_scalar_unfiltered(); } return sb.scalar; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- #ifdef RYML_DBG // !!! <---------------------------------- template void ParseEngine::add_flags(ParserFlag_t on, ParserState * s) { char buf1_[64], buf2_[64], buf3_[64]; csubstr buf1 = detail::_parser_flags_to_str(buf1_, on); csubstr buf2 = detail::_parser_flags_to_str(buf2_, s->flags); csubstr buf3 = detail::_parser_flags_to_str(buf3_, s->flags|on); _c4dbgpf("state[{}]: add {}: before={} after={}", s->level, buf1, buf2, buf3); s->flags |= on; } template void ParseEngine::addrem_flags(ParserFlag_t on, ParserFlag_t off, ParserState * s) { char buf1_[64], buf2_[64], buf3_[64], buf4_[64]; csubstr buf1 = detail::_parser_flags_to_str(buf1_, on); csubstr buf2 = detail::_parser_flags_to_str(buf2_, off); csubstr buf3 = detail::_parser_flags_to_str(buf3_, s->flags); csubstr buf4 = detail::_parser_flags_to_str(buf4_, ((s->flags|on)&(~off))); _c4dbgpf("state[{}]: add {} / rem {}: before={} after={}", s->level, buf1, buf2, buf3, buf4); s->flags |= on; s->flags &= ~off; } template void ParseEngine::rem_flags(ParserFlag_t off, ParserState * s) { char buf1_[64], buf2_[64], buf3_[64]; csubstr buf1 = detail::_parser_flags_to_str(buf1_, off); csubstr buf2 = detail::_parser_flags_to_str(buf2_, s->flags); csubstr buf3 = detail::_parser_flags_to_str(buf3_, s->flags&(~off)); _c4dbgpf("state[{}]: rem {}: before={} after={}", s->level, buf1, buf2, buf3); s->flags &= ~off; } inline C4_NO_INLINE csubstr detail::_parser_flags_to_str(substr buf, ParserFlag_t flags) { size_t pos = 0; bool gotone = false; #define _prflag(fl) \ if((flags & fl) == (fl)) \ { \ if(gotone) \ { \ if(pos + 1 < buf.len) \ buf[pos] = '|'; \ ++pos; \ } \ csubstr fltxt = #fl; \ if(pos + fltxt.len <= buf.len) \ memcpy(buf.str + pos, fltxt.str, fltxt.len); \ pos += fltxt.len; \ gotone = true; \ } _prflag(RTOP); _prflag(RUNK); _prflag(RMAP); _prflag(RSEQ); _prflag(FLOW); _prflag(BLCK); _prflag(QMRK); _prflag(RKEY); _prflag(RVAL); _prflag(RKCL); _prflag(RNXT); _prflag(SSCL); _prflag(QSCL); _prflag(RSET); _prflag(RDOC); _prflag(NDOC); _prflag(USTY); _prflag(RSEQIMAP); #undef _prflag if(pos == 0) if(buf.len > 0) buf[pos++] = '0'; RYML_CHECK(pos <= buf.len); return buf.first(pos); } #endif // RYML_DBG !!! <---------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template csubstr ParseEngine::location_contents(Location const& loc) const { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, loc.offset < m_buf.len); return m_buf.sub(loc.offset); } template Location ParseEngine::location(ConstNodeRef node) const { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, node.readable()); return location(*node.tree(), node.id()); } template Location ParseEngine::location(Tree const& tree, id_type node) const { // try hard to avoid getting the location from a null string. Location loc; if(_location_from_node(tree, node, &loc, 0)) return loc; return val_location(m_buf.str); } template bool ParseEngine::_location_from_node(Tree const& tree, id_type node, Location *C4_RESTRICT loc, id_type level) const { if(tree.has_key(node)) { csubstr k = tree.key(node); if(C4_LIKELY(k.str != nullptr)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, k.is_sub(m_buf)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_buf.is_super(k)); *loc = val_location(k.str); return true; } } if(tree.has_val(node)) { csubstr v = tree.val(node); if(C4_LIKELY(v.str != nullptr)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, v.is_sub(m_buf)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_buf.is_super(v)); *loc = val_location(v.str); return true; } } if(tree.is_container(node)) { if(_location_from_cont(tree, node, loc)) return true; } if(tree.type(node) != NOTYPE && level == 0) { // try the prev sibling { const id_type prev = tree.prev_sibling(node); if(prev != NONE) { if(_location_from_node(tree, prev, loc, level+1)) return true; } } // try the next sibling { const id_type next = tree.next_sibling(node); if(next != NONE) { if(_location_from_node(tree, next, loc, level+1)) return true; } } // try the parent { const id_type parent = tree.parent(node); if(parent != NONE) { if(_location_from_node(tree, parent, loc, level+1)) return true; } } } return false; } template bool ParseEngine::_location_from_cont(Tree const& tree, id_type node, Location *C4_RESTRICT loc) const { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, tree.is_container(node)); if(!tree.is_stream(node)) { const char *node_start = tree._p(node)->m_val.scalar.str; // this was stored in the container if(tree.has_children(node)) { id_type child = tree.first_child(node); if(tree.has_key(child)) { // when a map starts, the container was set after the key csubstr k = tree.key(child); if(k.str && node_start > k.str) node_start = k.str; } } *loc = val_location(node_start); return true; } else // it's a stream { *loc = val_location(m_buf.str); // just return the front of the buffer } return true; } template Location ParseEngine::val_location(const char *val) const { if(C4_UNLIKELY(val == nullptr)) return {m_file, 0, 0, 0}; _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, m_options.locations()); // NOTE: if any of these checks fails, the parser needs to be // instantiated with locations enabled. _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_buf.str == m_newline_offsets_buf.str); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_buf.len == m_newline_offsets_buf.len); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_options.locations()); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, !_locations_dirty()); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_newline_offsets != nullptr); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_size > 0); // NOTE: the pointer needs to belong to the buffer that was used to parse. csubstr src = m_buf; _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, val != nullptr || src.str == nullptr); _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, (val >= src.begin() && val <= src.end()) || (src.str == nullptr && val == nullptr)); // ok. search the first stored newline after the given ptr using lineptr_type = size_t const* C4_RESTRICT; lineptr_type lineptr = nullptr; size_t offset = (size_t)(val - src.begin()); if(m_newline_offsets_size < RYML_LOCATIONS_SMALL_THRESHOLD) { // just do a linear search if the size is small. for(lineptr_type curr = m_newline_offsets, last = m_newline_offsets + m_newline_offsets_size; curr < last; ++curr) { if(*curr > offset) { lineptr = curr; break; } } } else { // do a bisection search if the size is not small. // // We could use std::lower_bound but this is simple enough and // spares the costly include of . size_t count = m_newline_offsets_size; size_t step; lineptr_type it; lineptr = m_newline_offsets; while(count) { step = count >> 1; it = lineptr + step; if(*it < offset) { lineptr = ++it; count -= step + 1; } else { count = step; } } } _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, lineptr >= m_newline_offsets); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, lineptr <= m_newline_offsets + m_newline_offsets_size); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, *lineptr > offset); Location loc; loc.name = m_file; loc.offset = offset; loc.line = (size_t)(lineptr - m_newline_offsets); if(lineptr > m_newline_offsets) loc.col = (offset - *(lineptr-1) - 1u); else loc.col = offset; return loc; } template void ParseEngine::_prepare_locations() { m_newline_offsets_buf = m_buf; size_t numnewlines = 1u + m_buf.count('\n'); _resize_locations(numnewlines); m_newline_offsets_size = 0; for(size_t i = 0; i < m_buf.len; i++) if(m_buf[i] == '\n') m_newline_offsets[m_newline_offsets_size++] = i; m_newline_offsets[m_newline_offsets_size++] = m_buf.len; _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_newline_offsets_size == numnewlines); } template void ParseEngine::_resize_locations(size_t numnewlines) { if(numnewlines > m_newline_offsets_capacity) { if(m_newline_offsets) _RYML_CB_FREE(m_evt_handler->m_stack.m_callbacks, m_newline_offsets, size_t, m_newline_offsets_capacity); m_newline_offsets = _RYML_CB_ALLOC_HINT(m_evt_handler->m_stack.m_callbacks, size_t, numnewlines, m_newline_offsets); m_newline_offsets_capacity = numnewlines; } } template bool ParseEngine::_locations_dirty() const { return !m_newline_offsets_size; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template void ParseEngine::_handle_flow_skip_whitespace() { // don't assign to csubstr rem: otherwise, gcc12,13,14 -O3 -m32 misbuilds if(m_evt_handler->m_curr->line_contents.rem.len > 0) { if(m_evt_handler->m_curr->line_contents.rem.str[0] == ' ' || m_evt_handler->m_curr->line_contents.rem.str[0] == '\t') { _c4dbgpf("starts with whitespace: '{}'", _c4prc(m_evt_handler->m_curr->line_contents.rem.str[0])); _skipchars(" \t"); } // comments if(m_evt_handler->m_curr->line_contents.rem.begins_with('#')) { _c4dbgpf("it's a comment: {}", m_evt_handler->m_curr->line_contents.rem); _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); } } } //----------------------------------------------------------------------------- template void ParseEngine::_handle_colon() { size_t curr = m_evt_handler->m_curr->pos.line; if(m_prev_colon != npos) { if(curr == m_prev_colon) _c4err("two colons on same line"); } m_prev_colon = curr; } template void ParseEngine::_add_annotation(Annotation *C4_RESTRICT dst, csubstr str, size_t indentation, size_t line) { _c4dbgpf("store annotation[{}]: '{}' indentation={} line={}", dst->num_entries, str, indentation, line); if(C4_UNLIKELY(dst->num_entries >= C4_COUNTOF(dst->annotations))) // NOLINT(bugprone-sizeof-expression) _c4err("too many annotations"); dst->annotations[dst->num_entries].str = str; dst->annotations[dst->num_entries].indentation = indentation; dst->annotations[dst->num_entries].line = line; ++dst->num_entries; } template void ParseEngine::_clear_annotations(Annotation *C4_RESTRICT dst) { dst->num_entries = 0; } #ifdef RYML_NO_COVERAGE__TO_BE_DELETED template bool ParseEngine::_handle_indentation_from_annotations() { if(m_pending_anchors.num_entries == 1u || m_pending_tags.num_entries == 1u) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_pending_anchors.num_entries < 2u && m_pending_tags.num_entries < 2u); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_pending_anchors.annotations[0].line < m_evt_handler->m_curr->pos.line); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_pending_tags.annotations[1].line < m_evt_handler->m_curr->pos.line); size_t to_skip = m_evt_handler->m_curr->indref; if(m_pending_anchors.num_entries) to_skip = m_pending_anchors.annotations[0].indentation > to_skip ? m_pending_anchors.annotations[0].indentation : to_skip; if(m_pending_tags.num_entries) to_skip = m_pending_tags.annotations[0].indentation > to_skip ? m_pending_tags.annotations[0].indentation : to_skip; _c4dbgpf("annotations pending, skip indentation up to {}!", to_skip); _maybe_skipchars_up_to(' ', to_skip); return true; } return false; } #endif template bool ParseEngine::_annotations_require_key_container() const { return m_pending_tags.num_entries > 1 || m_pending_anchors.num_entries > 1; } template void ParseEngine::_check_tag(csubstr tag) { if(!tag.begins_with("!<")) { if(C4_UNLIKELY(tag.first_of("[]{},") != npos)) _RYML_CB_ERR_(m_evt_handler->m_stack.m_callbacks, "tags must not contain any of '[]{},'", m_evt_handler->m_curr->pos); } else { if(C4_UNLIKELY(!tag.ends_with('>'))) _RYML_CB_ERR_(m_evt_handler->m_stack.m_callbacks, "malformed tag", m_evt_handler->m_curr->pos); } } template void ParseEngine::_handle_annotations_before_blck_key_scalar() { _c4dbgpf("annotations_before_blck_key_scalar, node={}", m_evt_handler->m_curr->node_id); if(m_pending_tags.num_entries) { _c4dbgpf("annotations_before_blck_key_scalar, #tags={}", m_pending_tags.num_entries); if(C4_LIKELY(m_pending_tags.num_entries == 1)) { _check_tag(m_pending_tags.annotations[0].str); m_evt_handler->set_key_tag(m_pending_tags.annotations[0].str); _clear_annotations(&m_pending_tags); } else { _c4err("too many tags"); } } if(m_pending_anchors.num_entries) { _c4dbgpf("annotations_before_blck_key_scalar, #anchors={}", m_pending_anchors.num_entries); if(C4_LIKELY(m_pending_anchors.num_entries == 1)) { m_evt_handler->set_key_anchor(m_pending_anchors.annotations[0].str); _clear_annotations(&m_pending_anchors); } else { _c4err("too many anchors"); } } } template void ParseEngine::_handle_annotations_before_blck_val_scalar() { _c4dbgpf("annotations_before_blck_val_scalar, node={}", m_evt_handler->m_curr->node_id); if(m_pending_tags.num_entries) { _c4dbgpf("annotations_before_blck_val_scalar, #tags={}", m_pending_tags.num_entries); if(C4_LIKELY(m_pending_tags.num_entries == 1)) { _check_tag(m_pending_tags.annotations[0].str); m_evt_handler->set_val_tag(m_pending_tags.annotations[0].str); _clear_annotations(&m_pending_tags); } else { _c4err("too many tags"); } } if(m_pending_anchors.num_entries) { _c4dbgpf("annotations_before_blck_val_scalar, #anchors={}", m_pending_anchors.num_entries); if(C4_LIKELY(m_pending_anchors.num_entries == 1)) { m_evt_handler->set_val_anchor(m_pending_anchors.annotations[0].str); _clear_annotations(&m_pending_anchors); } else { _c4err("too many anchors"); } } } template void ParseEngine::_handle_annotations_before_start_mapblck(size_t current_line) { _c4dbgpf("annotations_before_start_mapblck, current_line={}", current_line); if(m_pending_tags.num_entries == 2) { _c4dbgp("2 tags, setting entry 0"); _check_tag(m_pending_tags.annotations[0].str); m_evt_handler->set_val_tag(m_pending_tags.annotations[0].str); } else if(m_pending_tags.num_entries == 1) { _c4dbgpf("1 tag. line={}, curr={}", m_pending_tags.annotations[0].line); if(m_pending_tags.annotations[0].line < current_line) { _c4dbgp("...tag is for the map. setting it."); _check_tag(m_pending_tags.annotations[0].str); m_evt_handler->set_val_tag(m_pending_tags.annotations[0].str); _clear_annotations(&m_pending_tags); } } // if(m_pending_anchors.num_entries == 2) { _c4dbgp("2 anchors, setting entry 0"); m_evt_handler->set_val_anchor(m_pending_anchors.annotations[0].str); } else if(m_pending_anchors.num_entries == 1) { _c4dbgpf("1 anchor. line={}, curr={}", m_pending_anchors.annotations[0].line); if(m_pending_anchors.annotations[0].line < current_line) { _c4dbgp("...anchor is for the map. setting it."); m_evt_handler->set_val_anchor(m_pending_anchors.annotations[0].str); _clear_annotations(&m_pending_anchors); } } } template void ParseEngine::_handle_annotations_before_start_mapblck_as_key() { _c4dbgp("annotations_before_start_mapblck_as_key"); if(m_pending_tags.num_entries == 2) { _check_tag(m_pending_tags.annotations[0].str); m_evt_handler->set_key_tag(m_pending_tags.annotations[0].str); } if(m_pending_anchors.num_entries == 2) { m_evt_handler->set_key_anchor(m_pending_anchors.annotations[0].str); } } template void ParseEngine::_handle_annotations_and_indentation_after_start_mapblck(size_t key_indentation, size_t key_line) { _c4dbgp("annotations_after_start_mapblck"); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_pending_tags.num_entries <= 2); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_pending_anchors.num_entries <= 2); if(m_pending_anchors.num_entries || m_pending_tags.num_entries) { key_indentation = _select_indentation_from_annotations(key_indentation, key_line); switch(m_pending_tags.num_entries) { case 1u: _check_tag(m_pending_tags.annotations[0].str); m_evt_handler->set_key_tag(m_pending_tags.annotations[0].str); _clear_annotations(&m_pending_tags); break; case 2u: _check_tag(m_pending_tags.annotations[1].str); m_evt_handler->set_key_tag(m_pending_tags.annotations[1].str); _clear_annotations(&m_pending_tags); break; } switch(m_pending_anchors.num_entries) { case 1u: m_evt_handler->set_key_anchor(m_pending_anchors.annotations[0].str); _clear_annotations(&m_pending_anchors); break; case 2u: m_evt_handler->set_key_anchor(m_pending_anchors.annotations[1].str); _clear_annotations(&m_pending_anchors); break; } } _set_indentation(key_indentation); } template size_t ParseEngine::_select_indentation_from_annotations(size_t val_indentation, size_t val_line) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_pending_tags.num_entries || m_pending_anchors.num_entries); // select the left-most annotation on the max line auto const *C4_RESTRICT curr = m_pending_anchors.num_entries ? &m_pending_anchors.annotations[0] : &m_pending_tags.annotations[0]; for(size_t i = 0; i < m_pending_anchors.num_entries; ++i) { auto const& C4_RESTRICT ann = m_pending_anchors.annotations[i]; if(ann.line > curr->line) curr = &ann; else if(ann.indentation < curr->indentation) curr = &ann; } for(size_t j = 0; j < m_pending_tags.num_entries; ++j) { auto const& C4_RESTRICT ann = m_pending_tags.annotations[j]; if(ann.line > curr->line) curr = &ann; else if(ann.indentation < curr->indentation) curr = &ann; } return curr->line < val_line ? val_indentation : curr->indentation; } template void ParseEngine::_handle_directive(csubstr rem) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, rem.is_sub(m_evt_handler->m_curr->line_contents.rem)); const size_t pos = rem.find('#'); _c4dbgpf("handle_directive: pos={} rem={}", pos, rem); if(pos == npos) // no comments { m_evt_handler->add_directive(rem); _line_progressed(rem.len); } else { csubstr to_comment = rem.first(pos); csubstr trimmed = to_comment.trimr(" \t"); m_evt_handler->add_directive(trimmed); _line_progressed(pos); _skip_comment(); } } template bool ParseEngine::_handle_bom() { const csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(rem.len) { const csubstr rest = rem.sub(1); // https://yaml.org/spec/1.2.2/#52-character-encodings #define _rymlisascii(c) ((c) > '\0' && (c) <= '\x7f') // is the character ASCII? if(rem.begins_with({"\x00\x00\xfe\xff", 4}) || (rem.begins_with({"\x00\x00\x00", 3}) && rem.len >= 4u && _rymlisascii(rem.str[3]))) { _c4dbgp("byte order mark: UTF32BE"); _handle_bom(UTF32BE); _line_progressed(4); return true; } else if(rem.begins_with("\xff\xfe\x00\x00") || (rest.begins_with({"\x00\x00\x00", 3}) && rem.len >= 4u && _rymlisascii(rem.str[0]))) { _c4dbgp("byte order mark: UTF32LE"); _handle_bom(UTF32LE); _line_progressed(4); return true; } else if(rem.begins_with("\xfe\xff") || (rem.begins_with('\x00') && rem.len >= 2u && _rymlisascii(rem.str[1]))) { _c4dbgp("byte order mark: UTF16BE"); _handle_bom(UTF16BE); _line_progressed(2); return true; } else if(rem.begins_with("\xff\xfe") || (rest.begins_with('\x00') && rem.len >= 2u && _rymlisascii(rem.str[0]))) { _c4dbgp("byte order mark: UTF16LE"); _handle_bom(UTF16LE); _line_progressed(2); return true; } else if(rem.begins_with("\xef\xbb\xbf")) { _c4dbgp("byte order mark: UTF8"); _handle_bom(UTF8); _line_progressed(3); return true; } #undef _rymlisascii } return false; } template void ParseEngine::_handle_bom(Encoding_e enc) { if(m_encoding == NOBOM) { const bool is_beginning_of_file = m_evt_handler->m_curr->line_contents.rem.str == m_buf.str; if(enc == UTF8 || is_beginning_of_file) m_encoding = enc; else _c4err("non-UTF8 byte order mark can appear only at the beginning of the file"); } else if(enc != m_encoding) { _c4err("byte order mark can only be set once"); } } //----------------------------------------------------------------------------- template void ParseEngine::_handle_seq_json() { seqjson_start: _c4dbgpf("handle2_seq_json: node_id={} level={} indentation={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RSEQ)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RVAL|RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RVAL) != has_all(RNXT)); _handle_flow_skip_whitespace(); csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto seqjson_again; if(has_any(RVAL)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); const char first = rem.str[0]; _c4dbgpf("mapjson[RVAL]: '{}'", first); switch(first) { case '"': { _c4dbgp("seqjson[RVAL]: scanning double-quoted scalar"); ScannedScalar sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); m_evt_handler->set_val_scalar_dquoted(maybe_filtered); addrem_flags(RNXT, RVAL); break; } case '[': { _c4dbgp("seqjson[RVAL]: start child seqjson"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_seq_val_flow(); addrem_flags(RVAL, RNXT); _line_progressed(1); break; } case '{': { _c4dbgp("seqjson[RVAL]: start child mapjson"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_map_val_flow(); addrem_flags(RMAP|RKEY, RSEQ|RVAL|RNXT); _line_progressed(1); goto seqjson_finish; } case ']': // this happens on a trailing comma like ", ]" { _c4dbgp("seqjson[RVAL]: end!"); rem_flags(RSEQ); m_evt_handler->end_seq(); _line_progressed(1); if(!has_all(RSEQ|FLOW)) goto seqjson_finish; break; } default: { ScannedScalar sc; if(_scan_scalar_seq_json(&sc)) { _c4dbgp("seqjson[RVAL]: it's a plain scalar."); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); m_evt_handler->set_val_scalar_plain(maybe_filtered); addrem_flags(RNXT, RVAL); } else { _c4err("parse error"); } } } } else // RNXT { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); const char first = rem.str[0]; _c4dbgpf("mapjson[RNXT]: '{}'", first); switch(first) { case ',': { _c4dbgp("seqjson[RNXT]: expect next val"); addrem_flags(RVAL, RNXT); m_evt_handler->add_sibling(); _line_progressed(1); break; } case ']': { _c4dbgp("seqjson[RNXT]: end!"); m_evt_handler->end_seq(); _line_progressed(1); goto seqjson_finish; } default: _c4err("parse error"); } } seqjson_again: _c4dbgt("seqjson: go again", 0); if(_finished_line()) { if(C4_LIKELY(!_finished_file())) { _line_ended(); _scan_line(); _c4dbgnextline(); } else { _c4err("missing terminating ]"); } } goto seqjson_start; seqjson_finish: _c4dbgp("seqjson: finish"); } //----------------------------------------------------------------------------- template void ParseEngine::_handle_map_json() { mapjson_start: _c4dbgpf("handle2_map_json: node_id={} level={} indentation={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RKCL|RVAL|RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, 1 == (has_any(RKEY) + has_any(RKCL) + has_any(RVAL) + has_any(RNXT))); _handle_flow_skip_whitespace(); csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapjson_again; if(has_any(RKEY)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); const char first = rem.str[0]; _c4dbgpf("mapjson[RKEY]: '{}'", first); switch(first) { case '"': { _c4dbgp("mapjson[RKEY]: scanning double-quoted scalar"); ScannedScalar sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); addrem_flags(RKCL, RKEY); break; } case '}': // this happens on a trailing comma like ", }" { _c4dbgp("mapjson[RKEY]: end!"); m_evt_handler->end_map(); _line_progressed(1); goto mapjson_finish; } default: _c4err("parse error"); } } else if(has_any(RVAL)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); const char first = rem.str[0]; _c4dbgpf("mapjson[RVAL]: '{}'", first); switch(first) { case '"': { _c4dbgp("mapjson[RVAL]: scanning double-quoted scalar"); ScannedScalar sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); m_evt_handler->set_val_scalar_dquoted(maybe_filtered); addrem_flags(RNXT, RVAL); break; } case '[': { _c4dbgp("mapjson[RVAL]: start val seqjson"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_seq_val_flow(); _set_indentation(m_evt_handler->m_parent->indref); addrem_flags(RSEQ|RVAL, RMAP|RNXT); _line_progressed(1); goto mapjson_finish; } case '{': { _c4dbgp("mapjson[RVAL]: start val mapjson"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_map_val_flow(); _set_indentation(m_evt_handler->m_parent->indref); addrem_flags(RKEY, RNXT); _line_progressed(1); // keep going in this function break; } default: { ScannedScalar sc; if(_scan_scalar_map_json(&sc)) { _c4dbgp("mapjson[RVAL]: plain scalar."); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); m_evt_handler->set_val_scalar_plain(maybe_filtered); addrem_flags(RNXT, RVAL); } else { _c4err("parse error"); } break; } } } else if(has_any(RKCL)) // read the key colon { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); const char first = rem.str[0]; _c4dbgpf("mapjson[RKCL]: '{}'", first); if(first == ':') { _c4dbgp("mapjson[RKCL]: found the colon"); addrem_flags(RVAL, RKCL); _line_progressed(1); } else { _c4err("parse error"); } } else if(has_any(RNXT)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); _c4dbgpf("mapjson[RNXT]: '{}'", rem.str[0]); if(rem.begins_with(',')) { _c4dbgp("mapjson[RNXT]: expect next keyval"); m_evt_handler->add_sibling(); addrem_flags(RKEY, RNXT); _line_progressed(1); } else if(rem.begins_with('}')) { _c4dbgp("mapjson[RNXT]: end!"); m_evt_handler->end_map(); _line_progressed(1); goto mapjson_finish; } else { _c4err("parse error"); } } mapjson_again: _c4dbgt("mapjson: go again", 0); if(_finished_line()) { if(C4_LIKELY(!_finished_file())) { _line_ended(); _scan_line(); _c4dbgnextline(); } else { _c4err("missing terminating }"); } } goto mapjson_start; mapjson_finish: _c4dbgp("mapjson: finish"); } //----------------------------------------------------------------------------- template void ParseEngine::_handle_seq_imap() { seqimap_start: _c4dbgpf("handle2_seq_imap: node_id={} level={} indref={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RSEQIMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RVAL|RNXT|QMRK|RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, 1 == has_all(RVAL) + has_all(RNXT) + has_all(QMRK) + has_all(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_stack.size() >= 3); _handle_flow_skip_whitespace(); csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto seqimap_again; if(has_any(RVAL)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); const char first = rem.str[0]; _c4dbgpf("seqimap[RVAL]: '{}'", _c4prc(first)); ScannedScalar sc; if(first == '\'') { _c4dbgp("seqimap[RVAL]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); m_evt_handler->set_val_scalar_squoted(maybe_filtered); m_evt_handler->end_map(); goto seqimap_finish; } else if(first == '"') { _c4dbgp("seqimap[RVAL]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); m_evt_handler->set_val_scalar_dquoted(maybe_filtered); m_evt_handler->end_map(); goto seqimap_finish; } // block scalars (ie | and >) cannot appear in flow containers else if(_scan_scalar_plain_map_flow(&sc)) { _c4dbgp("seqimap[RVAL]: it's a scalar."); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); m_evt_handler->set_val_scalar_plain(maybe_filtered); m_evt_handler->end_map(); goto seqimap_finish; } else if(first == '[') { _c4dbgp("seqimap[RVAL]: start child seqflow"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_seq_val_flow(); addrem_flags(RVAL, RNXT|RSEQIMAP); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); goto seqimap_finish; } else if(first == '{') { _c4dbgp("seqimap[RVAL]: start child mapflow"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_map_val_flow(); addrem_flags(RMAP|RKEY, RSEQ|RVAL|RSEQIMAP|RNXT); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); goto seqimap_finish; } else if(first == ',' || first == ']') { _c4dbgp("seqimap[RVAL]: finish without val."); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); goto seqimap_finish; } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgp("seqimap[RVAL]: anchor!"); m_evt_handler->set_val_anchor(anchor); } else if(first == '*') { csubstr ref = _scan_ref_seq(); _c4dbgp("seqimap[RVAL]: ref!"); m_evt_handler->set_val_ref(ref); addrem_flags(RNXT, RVAL); } else { _c4err("parse error"); } } else if(has_any(RNXT)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); const char first = rem.str[0]; _c4dbgpf("seqimap[RNXT]: '{}'", _c4prc(first)); if(first == ',' || first == ']') { // we may get here because a map or a seq started and we // return later _c4dbgp("seqimap: done"); m_evt_handler->end_map(); goto seqimap_finish; } else { _c4err("parse error"); } } else if(has_any(QMRK)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(QMRK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); const char first = rem.str[0]; _c4dbgpf("seqimap[QMRK]: '{}'", _c4prc(first)); ScannedScalar sc; if(first == '\'') { _c4dbgp("seqimap[QMRK]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); m_evt_handler->set_key_scalar_squoted(maybe_filtered); addrem_flags(RKCL, QMRK); goto seqimap_again; } else if(first == '"') { _c4dbgp("seqimap[QMRK]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); addrem_flags(RKCL, QMRK); goto seqimap_again; } // block scalars (ie | and >) cannot appear in flow containers else if(_scan_scalar_plain_map_flow(&sc)) { _c4dbgp("seqimap[QMRK]: it's a scalar."); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); m_evt_handler->set_key_scalar_plain(maybe_filtered); addrem_flags(RKCL, QMRK); goto seqimap_again; } else if(first == '[') { _c4dbgp("seqimap[QMRK]: start child seqflow"); addrem_flags(RKCL, QMRK); m_evt_handler->begin_seq_key_flow(); addrem_flags(RSEQ|RVAL, RKCL|RSEQIMAP); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); goto seqimap_finish; } else if(first == '{') { _c4dbgp("seqimap[QMRK]: start child mapflow"); addrem_flags(RKCL, QMRK); m_evt_handler->begin_map_key_flow(); addrem_flags(RMAP|RKEY, RSEQ|RKCL|RSEQIMAP); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); goto seqimap_finish; } else if(first == ',' || first == ']') { _c4dbgp("seqimap[QMRK]: finish without key."); m_evt_handler->set_key_scalar_plain_empty(); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); goto seqimap_finish; } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgp("seqimap[QMRK]: anchor!"); m_evt_handler->set_key_anchor(anchor); } else if(first == '*') { csubstr ref = _scan_ref_seq(); _c4dbgp("seqimap[QMRK]: ref!"); m_evt_handler->set_key_ref(ref); addrem_flags(RKCL, QMRK); } else { _c4err("parse error"); } } else if(has_any(RKCL)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RKCL)); const char first = rem.str[0]; _c4dbgpf("seqimap[RKCL]: '{}'", _c4prc(first)); if(first == ':') { _c4dbgp("seqimap[RKCL]: found ':'"); addrem_flags(RVAL, RKCL); _line_progressed(1); goto seqimap_again; } else if(first == ',' || first == ']') { _c4dbgp("seqimap[RKCL]: found ','. finish without val"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); goto seqimap_finish; } else { _c4err("parse error"); } } seqimap_again: _c4dbgt("seqimap: go again", 0); if(_finished_line()) { if(C4_LIKELY(!_finished_file())) { _line_ended(); _scan_line(); _c4dbgnextline(); } else { _c4err("parse error"); } } goto seqimap_start; seqimap_finish: _c4dbgp("seqimap: finish"); } //----------------------------------------------------------------------------- template void ParseEngine::_handle_seq_flow() { seqflow_start: _c4dbgpf("handle2_seq_flow: node_id={} level={} indentation={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RSEQ)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RVAL|RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RVAL) != has_all(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indref != npos); _handle_flow_skip_whitespace(); // don't assign to csubstr rem: otherwise, gcc12,13,14 -O3 -m32 misbuilds if(!m_evt_handler->m_curr->line_contents.rem.len) goto seqflow_again; if(has_any(RVAL)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; ScannedScalar sc; if(first == '\'') { _c4dbgp("seqflow[RVAL]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); m_evt_handler->set_val_scalar_squoted(maybe_filtered); addrem_flags(RNXT, RVAL); } else if(first == '"') { _c4dbgp("seqflow[RVAL]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); m_evt_handler->set_val_scalar_dquoted(maybe_filtered); addrem_flags(RNXT, RVAL); } // block scalars (ie | and >) cannot appear in flow containers else if(_scan_scalar_plain_seq_flow(&sc)) { _c4dbgp("seqflow[RVAL]: it's a scalar."); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); m_evt_handler->set_val_scalar_plain(maybe_filtered); addrem_flags(RNXT, RVAL); } else if(first == '[') { _c4dbgp("seqflow[RVAL]: start child seqflow"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_seq_val_flow(); _set_indentation(m_evt_handler->m_parent->indref); addrem_flags(RVAL, RNXT); _line_progressed(1); } else if(first == '{') { _c4dbgp("seqflow[RVAL]: start child mapflow"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_map_val_flow(); _set_indentation(m_evt_handler->m_parent->indref); addrem_flags(RMAP|RKEY, RSEQ|RVAL|RNXT); _line_progressed(1); goto seqflow_finish; } else if(first == ']') // this happens on a trailing comma like ", ]" { _c4dbgp("seqflow[RVAL]: end!"); _line_progressed(1); m_evt_handler->end_seq(); goto seqflow_finish; } else if(first == '*') { csubstr ref = _scan_ref_seq(); _c4dbgpf("seqflow[RVAL]: ref! [{}]~~~{}~~~", ref.len, ref); m_evt_handler->set_val_ref(ref); addrem_flags(RNXT, RVAL); } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgpf("seqflow[RVAL]: anchor! [{}]~~~{}~~~", anchor.len, anchor); m_evt_handler->set_val_anchor(anchor); if(_maybe_scan_following_comma()) { _c4dbgp("seqflow[RVAL]: empty scalar!"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); } } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("seqflow[RVAL]: tag! [{}]~~~{}~~~", tag.len, tag); _check_tag(tag); m_evt_handler->set_val_tag(tag); if(_maybe_scan_following_comma()) { _c4dbgp("seqflow[RVAL]: empty scalar!"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); } } else if(first == ':') { _c4dbgpf("seqflow[RVAL]: actually seqimap at node[{}], with empty key", m_evt_handler->m_curr->node_id); addrem_flags(RNXT, RVAL); m_evt_handler->begin_map_val_flow(); _set_indentation(m_evt_handler->m_parent->indref); m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RSEQIMAP|RVAL, RSEQ|RNXT); _line_progressed(1); goto seqflow_finish; } else if(first == '?') { _c4dbgp("seqflow[RVAL]: start child mapflow, explicit key"); addrem_flags(RNXT, RVAL); m_was_inside_qmrk = true; m_evt_handler->begin_map_val_flow(); _set_indentation(m_evt_handler->m_parent->indref); addrem_flags(RSEQIMAP|QMRK, RSEQ|RNXT); _line_progressed(1); _maybe_skip_whitespace_tokens(); goto seqflow_finish; } else { _c4err("parse error"); } } else // RNXT { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); const char first = m_evt_handler->m_curr->line_contents.rem.str[0]; if(first == ',') { _c4dbgp("seqflow[RNXT]: expect next val"); addrem_flags(RVAL, RNXT); m_evt_handler->add_sibling(); _line_progressed(1); } else if(first == ']') { _c4dbgp("seqflow[RNXT]: end!"); m_evt_handler->end_seq(); _line_progressed(1); goto seqflow_finish; } else if(first == ':') { _c4dbgpf("seqflow[RNXT]: actually seqimap at node[{}]", m_evt_handler->m_curr->node_id); m_evt_handler->actually_val_is_first_key_of_new_map_flow(); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); addrem_flags(RSEQIMAP|RVAL, RNXT); goto seqflow_finish; } else { _c4err("parse error"); } } seqflow_again: _c4dbgt("seqflow: go again", 0); if(_finished_line()) { if(C4_LIKELY(!_finished_file())) { _line_ended(); _scan_line(); _c4dbgnextline(); } else { _c4err("missing terminating ]"); } } goto seqflow_start; seqflow_finish: _c4dbgp("seqflow: finish"); } //----------------------------------------------------------------------------- template void ParseEngine::_handle_map_flow() { mapflow_start: _c4dbgpf("handle2_map_flow: node_id={} level={} indentation={}", m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(FLOW)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RKCL|RVAL|RNXT|QMRK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, 1 == (has_any(RKEY) + has_any(RKCL) + has_any(RVAL) + has_any(RNXT) + has_any(QMRK))); _handle_flow_skip_whitespace(); csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapflow_again; if(has_any(RKEY)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); const char first = rem.str[0]; _c4dbgpf("mapflow[RKEY]: '{}'", first); ScannedScalar sc; if(first == '\'') { _c4dbgp("mapflow[RKEY]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); m_evt_handler->set_key_scalar_squoted(maybe_filtered); addrem_flags(RKCL, RKEY|QMRK); } else if(first == '"') { _c4dbgp("mapflow[RKEY]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); addrem_flags(RKCL, RKEY|QMRK); } // block scalars (ie | and >) cannot appear in flow containers else if(_scan_scalar_plain_map_flow(&sc)) { _c4dbgp("mapflow[RKEY]: plain scalar"); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); m_evt_handler->set_key_scalar_plain(maybe_filtered); addrem_flags(RKCL, RKEY|QMRK); } else if(first == '?') { _c4dbgp("mapflow[RKEY]: explicit key"); _line_progressed(1); addrem_flags(QMRK, RKEY); _maybe_skip_whitespace_tokens(); } else if(first == ':') { _c4dbgp("mapflow[RKEY]: setting empty key"); m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RVAL, RKEY|QMRK); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == ',') { _c4dbgp("mapflow[RKEY]: empty key+val!"); m_evt_handler->set_key_scalar_plain_empty(); m_evt_handler->set_val_scalar_plain_empty(); addrem_flags(RNXT, RKEY|QMRK); // keep going in this function } else if(first == '}') // this happens on a trailing comma like ", }" { _c4dbgp("mapflow[RKEY]: end!"); m_evt_handler->end_map(); _line_progressed(1); goto mapflow_finish; } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgpf("mapflow[RKEY]: key anchor! [{}]~~~{}~~~", anchor.len, anchor); m_evt_handler->set_key_anchor(anchor); } else if(first == '*') { csubstr ref = _scan_ref_map(); _c4dbgpf("mapflow[RKEY]: key ref! [{}]~~~{}~~~", ref.len, ref); m_evt_handler->set_key_ref(ref); addrem_flags(RKCL, RKEY); } else if(first == '[') { // RYML's tree cannot store container keys, but that's // handled inside the tree sink. Other sink types may be // able to handle it. _c4dbgp("mapflow[RKEY]: start child seqflow (!)"); addrem_flags(RKCL, RKEY); m_evt_handler->begin_seq_key_flow(); addrem_flags(RSEQ|RVAL, RMAP|RKCL); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); goto mapflow_finish; } else if(first == '{') { // RYML's tree cannot store container keys, but that's // handled inside the tree sink. Other sink types may be // able to handle it. _c4dbgp("mapflow[RKEY]: start child mapflow (!)"); addrem_flags(RKCL, RKEY); m_evt_handler->begin_map_key_flow(); addrem_flags(RKEY, RVAL|RKCL); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); // keep going in this function } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("mapflow[RKEY]: tag! [{}]~~~{}~~~", tag.len, tag); _check_tag(tag); m_evt_handler->set_key_tag(tag); } else { _c4err("parse error"); } } else if(has_any(RKCL)) // read the key colon { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); const char first = rem.str[0]; _c4dbgpf("mapflow[RKCL]: '{}'", first); if(first == ':') { _c4dbgp("mapflow[RKCL]: found the colon"); addrem_flags(RVAL, RKCL); _line_progressed(1); } else if(first == '}') { _c4dbgp("mapflow[RKCL]: end with missing val!"); addrem_flags(RVAL, RKCL); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); _line_progressed(1); goto mapflow_finish; } else if(first == ',') { _c4dbgp("mapflow[RKCL]: got comma. val is missing"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(RKEY, RKCL); _line_progressed(1); } else { _c4err("parse error"); } } else if(has_any(RVAL)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); const char first = rem.str[0]; _c4dbgpf("mapflow[RVAL]: '{}'", first); ScannedScalar sc; if(first == '\'') { _c4dbgp("mapflow[RVAL]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); m_evt_handler->set_val_scalar_squoted(maybe_filtered); addrem_flags(RNXT, RVAL); } else if(first == '"') { _c4dbgp("mapflow[RVAL]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); m_evt_handler->set_val_scalar_dquoted(maybe_filtered); addrem_flags(RNXT, RVAL); } // block scalars (ie | and >) cannot appear in flow containers else if(_scan_scalar_plain_map_flow(&sc)) { _c4dbgp("mapflow[RVAL]: plain scalar."); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); m_evt_handler->set_val_scalar_plain(maybe_filtered); addrem_flags(RNXT, RVAL); } else if(first == '[') { _c4dbgp("mapflow[RVAL]: start val seqflow"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_seq_val_flow(); _set_indentation(m_evt_handler->m_parent->indref); addrem_flags(RSEQ|RVAL, RMAP|RNXT); _line_progressed(1); goto mapflow_finish; } else if(first == '{') { _c4dbgp("mapflow[RVAL]: start val mapflow"); addrem_flags(RNXT, RVAL); m_evt_handler->begin_map_val_flow(); _set_indentation(m_evt_handler->m_parent->indref); addrem_flags(RKEY, RNXT); _line_progressed(1); // keep going in this function } else if(first == '}') { _c4dbgp("mapflow[RVAL]: end!"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); _line_progressed(1); goto mapflow_finish; } else if(first == ',') { _c4dbgp("mapflow[RVAL]: empty val!"); m_evt_handler->set_val_scalar_plain_empty(); addrem_flags(RNXT, RVAL); // keep going in this function } else if(first == '*') { csubstr ref = _scan_ref_map(); _c4dbgpf("mapflow[RVAL]: key ref! [{}]~~~{}~~~", ref.len, ref); m_evt_handler->set_val_ref(ref); addrem_flags(RNXT, RVAL); } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgpf("mapflow[RVAL]: key anchor! [{}]~~~{}~~~", anchor.len, anchor); m_evt_handler->set_val_anchor(anchor); } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("mapflow[RVAL]: tag! [{}]~~~{}~~~", tag.len, tag); _check_tag(tag); m_evt_handler->set_val_tag(tag); } else { _c4err("parse error"); } } else if(has_any(RNXT)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); _c4dbgpf("mapflow[RNXT]: '{}'", rem.str[0]); if(rem.begins_with(',')) { _c4dbgp("mapflow[RNXT]: expect next keyval"); m_evt_handler->add_sibling(); addrem_flags(RKEY, RNXT); _line_progressed(1); } else if(rem.begins_with('}')) { _c4dbgp("mapflow[RNXT]: end!"); m_evt_handler->end_map(); _line_progressed(1); goto mapflow_finish; } else { _c4err("parse error"); } } else if(has_any(QMRK)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); const char first = rem.str[0]; _c4dbgpf("mapflow[QMRK]: '{}'", first); ScannedScalar sc; if(first == '\'') { _c4dbgp("mapflow[QMRK]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); m_evt_handler->set_key_scalar_squoted(maybe_filtered); addrem_flags(RKCL, QMRK); } else if(first == '"') { _c4dbgp("mapflow[QMRK]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); addrem_flags(RKCL, QMRK); } // block scalars (ie | and >) cannot appear in flow containers else if(_scan_scalar_plain_map_flow(&sc)) { _c4dbgp("mapflow[QMRK]: plain scalar"); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); m_evt_handler->set_key_scalar_plain(maybe_filtered); addrem_flags(RKCL, QMRK); } else if(first == ':') { _c4dbgp("mapflow[QMRK]: setting empty key"); m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RVAL, QMRK); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '}') // this happens on a trailing comma like ", }" { _c4dbgp("mapflow[QMRK]: end!"); m_evt_handler->set_key_scalar_plain_empty(); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); _line_progressed(1); goto mapflow_finish; } else if(first == ',') { _c4dbgp("mapflow[QMRK]: empty key+val!"); m_evt_handler->set_key_scalar_plain_empty(); m_evt_handler->set_val_scalar_plain_empty(); addrem_flags(RNXT, QMRK); } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgpf("mapflow[QMRK]: key anchor! [{}]~~~{}~~~", anchor.len, anchor); m_evt_handler->set_key_anchor(anchor); } else if(first == '*') { csubstr ref = _scan_ref_map(); _c4dbgpf("mapflow[QMRK]: key ref! [{}]~~~{}~~~", ref.len, ref); m_evt_handler->set_key_ref(ref); addrem_flags(RKCL, QMRK); } else if(first == '[') { // RYML's tree cannot store container keys, but that's // handled inside the tree sink. Other sink types may be // able to handle it. _c4dbgp("mapflow[QMRK]: start child seqflow (!)"); addrem_flags(RKCL, QMRK); m_evt_handler->begin_seq_key_flow(); addrem_flags(RSEQ|RVAL, RMAP|RKCL); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); goto mapflow_finish; } else if(first == '{') { // RYML's tree cannot store container keys, but that's // handled inside the tree sink. Other sink types may be // able to handle it. _c4dbgp("mapflow[QMRK]: start child mapflow (!)"); addrem_flags(RKCL, QMRK); m_evt_handler->begin_map_key_flow(); _set_indentation(m_evt_handler->m_parent->indref); addrem_flags(RKEY, RKCL); _line_progressed(1); // keep going in this function } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("mapflow[QMRK]: tag! [{}]~~~{}~~~", tag.len, tag); _check_tag(tag); m_evt_handler->set_key_tag(tag); } else { _c4err("parse error"); } } mapflow_again: _c4dbgt("mapflow: go again", 0); if(_finished_line()) { if(C4_LIKELY(!_finished_file())) { _line_ended(); _scan_line(); _c4dbgnextline(); } else { _c4err("missing terminating }"); } } goto mapflow_start; mapflow_finish: _c4dbgp("mapflow: finish"); } //----------------------------------------------------------------------------- template void ParseEngine::_handle_seq_block() { seqblck_start: _c4dbgpf("handle2_seq_block: seq_id={} node_id={} level={} indent={}", m_evt_handler->m_parent->node_id, m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RSEQ)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(BLCK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RVAL|RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, 1 == (has_any(RVAL) + has_any(RNXT))); _maybe_skip_comment(); csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto seqblck_again; if(has_any(RVAL)) { _c4dbgpf("seqblck[RVAL]: col={}", m_evt_handler->m_curr->pos.col); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); if(m_evt_handler->m_curr->at_line_beginning()) { _c4dbgpf("seqblck[RVAL]: indref={} indentation={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->line_contents.indentation); if(m_evt_handler->m_curr->indentation_ge()) { _c4dbgpf("seqblck[RVAL]: skip {} from indentation", m_evt_handler->m_curr->line_contents.indentation); _line_progressed(m_evt_handler->m_curr->line_contents.indentation); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto seqblck_again; } else if(m_evt_handler->m_curr->indentation_lt()) { _c4dbgp("seqblck[RVAL]: smaller indentation!"); _handle_indentation_pop_from_block_seq(); goto seqblck_finish; } else if(m_evt_handler->m_curr->line_contents.indentation == npos) { _c4dbgp("seqblck[RVAL]: empty line!"); _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); goto seqblck_again; } } #ifdef RYML_NO_COVERAGE__TO_BE_DELETED else { // accomodate annotation on the previous line. eg: // - &elm // foo # <-- on this line // - &elm // &foo foo: bar # <-- on this line if(rem.str[0] == ' ') { if(_handle_indentation_from_annotations()) { _c4dbgp("seqblck[RVAL]: annotations!"); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto seqblck_again; } } } #endif _RYML_CB_ASSERT(callbacks(), rem.len); _c4dbgpf("seqblck[RVAL]: '{}' node_id={}", rem.str[0], m_evt_handler->m_curr->node_id); const char first = rem.str[0]; const size_t startline = m_evt_handler->m_curr->pos.line; // warning: the gcc optimizer on x86 builds is brittle with // this function: const size_t startindent = m_evt_handler->m_curr->line_contents.current_col(); ScannedScalar sc; if(first == '\'') { _c4dbgp("seqblck[RVAL]: single-quoted scalar"); sc = _scan_scalar_squot(); if(!_maybe_scan_following_colon()) { _c4dbgp("seqblck[RVAL]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); // VAL! m_evt_handler->set_val_scalar_squoted(maybe_filtered); addrem_flags(RNXT, RVAL); } else { _c4dbgp("seqblck[RVAL]: start mapblck, set scalar as key"); addrem_flags(RNXT, RVAL); _handle_annotations_before_start_mapblck(startline); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); // KEY! m_evt_handler->set_key_scalar_squoted(maybe_filtered); addrem_flags(RMAP|RVAL, RSEQ|RNXT); _maybe_skip_whitespace_tokens(); goto seqblck_finish; } } else if(first == '"') { _c4dbgp("seqblck[RVAL]: double-quoted scalar"); sc = _scan_scalar_dquot(); if(!_maybe_scan_following_colon()) { _c4dbgp("seqblck[RVAL]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); // VAL! m_evt_handler->set_val_scalar_dquoted(maybe_filtered); addrem_flags(RNXT, RVAL); } else { _c4dbgp("seqblck[RVAL]: start mapblck, set scalar as key"); addrem_flags(RNXT, RVAL); _handle_annotations_before_start_mapblck(startline); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); // KEY! m_evt_handler->set_key_scalar_dquoted(maybe_filtered); addrem_flags(RMAP|RVAL, RSEQ|RNXT); _maybe_skip_whitespace_tokens(); goto seqblck_finish; } } // block scalars can only appear as keys when in QMRK scope // (ie, after ? tokens), so no need to scan following colon in // here. else if(first == '|') { _c4dbgp("seqblck[RVAL]: block-literal scalar"); ScannedBlock sb; _scan_block(&sb, m_evt_handler->m_curr->indref + 1); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_literal(sb); m_evt_handler->set_val_scalar_literal(maybe_filtered); addrem_flags(RNXT, RVAL); } else if(first == '>') { _c4dbgp("seqblck[RVAL]: block-folded scalar"); ScannedBlock sb; _scan_block(&sb, m_evt_handler->m_curr->indref + 1); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_folded(sb); m_evt_handler->set_val_scalar_folded(maybe_filtered); addrem_flags(RNXT, RVAL); } else if(_scan_scalar_plain_seq_blck(&sc)) { _c4dbgp("seqblck[RVAL]: plain scalar."); if(!_maybe_scan_following_colon()) { _c4dbgp("seqblck[RVAL]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); // VAL! m_evt_handler->set_val_scalar_plain(maybe_filtered); addrem_flags(RNXT, RVAL); } else { if(startindent > m_evt_handler->m_curr->indref) { _c4dbgp("seqblck[RVAL]: start mapblck, set scalar as key"); addrem_flags(RNXT, RVAL); _handle_annotations_before_start_mapblck(startline); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! m_evt_handler->set_key_scalar_plain(maybe_filtered); addrem_flags(RMAP|RVAL, RSEQ|RNXT); _maybe_skip_whitespace_tokens(); goto seqblck_finish; } else if(m_evt_handler->m_parent && m_evt_handler->m_parent->indref == startindent && has_any(RMAP|BLCK, m_evt_handler->m_parent)) { _c4dbgp("seqblck[RVAL]: empty val + end indentless seq + set key"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_seq(); m_evt_handler->add_sibling(); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! m_evt_handler->set_key_scalar_plain(maybe_filtered); addrem_flags(RVAL, RNXT|RKEY); _maybe_skip_whitespace_tokens(); goto seqblck_finish; } else { _c4err("parse error"); } } } else if(first == '[') { _c4dbgp("seqblck[RVAL]: start child seqflow"); addrem_flags(RNXT, RVAL); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_seq_val_flow(); addrem_flags(FLOW|RVAL, BLCK|RNXT); _line_progressed(1); _set_indentation(m_evt_handler->m_parent->indref + 1u); goto seqblck_finish; } else if(first == '{') { _c4dbgp("seqblck[RVAL]: start child mapflow"); addrem_flags(RNXT, RVAL); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_flow(); addrem_flags(RMAP|RKEY|FLOW, BLCK|RSEQ|RVAL|RNXT); _line_progressed(1); _set_indentation(m_evt_handler->m_parent->indref + 1u); goto seqblck_finish; } else if(first == '-') { if(startindent == m_evt_handler->m_curr->indref) { _c4dbgp("seqblck[RVAL]: prev val was empty"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); // keep in RVAL, but for the next sibling m_evt_handler->add_sibling(); } else { _c4dbgp("seqblck[RVAL]: start child seqblck"); _RYML_CB_ASSERT(this->callbacks(), startindent > m_evt_handler->m_curr->indref); addrem_flags(RNXT, RVAL); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_seq_val_block(); addrem_flags(RVAL, RNXT); _save_indentation(); // keep going on inside this function } _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == ':') { _c4dbgp("seqblck[RVAL]: start child mapblck with empty key"); addrem_flags(RNXT, RVAL); _handle_annotations_before_start_mapblck(startline); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RMAP|RVAL, RSEQ|RNXT); _line_progressed(1); _maybe_skip_whitespace_tokens(); goto seqblck_finish; } else if(first == '&') { const csubstr anchor = _scan_anchor(); _c4dbgpf("seqblck[RVAL]: anchor! [{}]~~~{}~~~", anchor.len, anchor); // we need to buffer the anchors, as there may be two // consecutive anchors in here _add_annotation(&m_pending_anchors, anchor, startindent, startline); } else if(first == '*') { csubstr ref = _scan_ref_seq(); _c4dbgpf("seqblck[RVAL]: ref! [{}]~~~{}~~~", ref.len, ref); if(!_maybe_scan_following_colon()) { _c4dbgp("seqblck[RVAL]: set ref as val!"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_ref(ref); addrem_flags(RNXT, RVAL); } else { _c4dbgp("seqblck[RVAL]: ref is key of map"); addrem_flags(RNXT, RVAL); _handle_annotations_before_start_mapblck(startline); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_ref(ref); addrem_flags(RMAP|RVAL, RSEQ|RNXT); _set_indentation(startindent); _maybe_skip_whitespace_tokens(); goto seqblck_finish; } } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("seqblck[RVAL]: val tag! [{}]~~~{}~~~", tag.len, tag); // we need to buffer the tags, as there may be two // consecutive tags in here _add_annotation(&m_pending_tags, tag, startindent, startline); } else if(first == '?') { _c4dbgp("seqblck[RVAL]: start child mapblck, explicit key"); addrem_flags(RNXT, RVAL); m_was_inside_qmrk = true; m_evt_handler->begin_map_val_block(); addrem_flags(RMAP|QMRK, RSEQ|RNXT); _save_indentation(); _line_progressed(1); _maybe_skip_whitespace_tokens(); goto seqblck_finish; } else { _c4err("parse error"); } } else // RNXT { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); // // handle indentation // _c4dbgpf("seqblck[RNXT]: indref={} indentation={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->line_contents.indentation); if(C4_LIKELY(_at_line_begin())) { _c4dbgp("seqblck[RNXT]: at line begin"); if(m_evt_handler->m_curr->indentation_ge()) { _c4dbgpf("seqblck[RNXT]: skip {} from indref", m_evt_handler->m_curr->indref); _line_progressed(m_evt_handler->m_curr->indref); _maybe_skip_whitespace_tokens(); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto seqblck_again; } else if(m_evt_handler->m_curr->indentation_lt()) { _c4dbgp("seqblck[RNXT]: smaller indentation!"); _handle_indentation_pop_from_block_seq(); if(has_all(RSEQ|BLCK)) { _c4dbgp("seqblck[RNXT]: still seqblck!"); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RNXT)); _line_progressed(m_evt_handler->m_curr->line_contents.indentation); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto seqblck_again; } else { _c4dbgp("seqblck[RNXT]: no longer seqblck!"); goto seqblck_finish; } } else if(m_evt_handler->m_curr->line_contents.indentation == npos) { _c4dbgpf("seqblck[RNXT]: blank line, len={}", m_evt_handler->m_curr->line_contents.rem); _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto seqblck_again; } } else { _c4dbgp("seqblck[RNXT]: NOT at line begin"); if(!rem.begins_with_any(" \t")) { _c4err("parse error"); } else { _skipchars(" \t"); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) { _c4dbgp("seqblck[RNXT]: again"); goto seqblck_again; } } } // // now handle the tokens // const char first = rem.str[0]; _c4dbgpf("seqblck[RNXT]: '{}' node_id={}", first, m_evt_handler->m_curr->node_id); if(first == '-') { if(m_evt_handler->m_curr->indref > 0 || m_evt_handler->m_curr->line_contents.indentation > 0 || !_is_doc_begin_token(rem)) { _c4dbgp("seqblck[RNXT]: expect next val"); addrem_flags(RVAL, RNXT); m_evt_handler->add_sibling(); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else { _c4dbgp("seqblck[RNXT]: start doc"); _start_doc_suddenly(); _line_progressed(3); _maybe_skip_whitespace_tokens(); goto seqblck_finish; } } else if(first == ':') { // This happens for example in `- [a: b]: c` (after // terminating the seq, ie, after `]`). All other cases // (ie colon after scalars) are caught elsewhere (ie, in // RVAL state). auto const *C4_RESTRICT prev_state = m_evt_handler->m_parent; if(C4_LIKELY(prev_state && (prev_state->flags & RMAP))) { _c4dbgp("seqblck[RNXT]: actually this seq was '?' key of parent map"); m_evt_handler->end_seq(); goto seqblck_finish; } else { _c4err("parse error"); } } else if(first == '.') { _c4dbgp("seqblck[RNXT]: maybe doc?"); csubstr rs = rem.sub(1); if(rs == ".." || rs.begins_with(".. ")) { _c4dbgp("seqblck[RNXT]: end+start doc"); _end_doc_suddenly(); _line_progressed(3); _maybe_skip_whitespace_tokens(); goto seqblck_finish; } else { _c4err("parse error"); } } else { // may be an indentless sequence nested in a map... //if(m_evt_handler->m_stack.size() >= 2) #ifdef RYML_DBG char flagbuf_[128]; for(auto const& s : m_evt_handler->m_stack) { _dbg_printf("state[{}]: ind={} node={} flags={}\n", s.level, s.indref, s.node_id, detail::_parser_flags_to_str(flagbuf_, s.flags)); } #endif if(m_evt_handler->m_parent && has_all(RMAP|BLCK, m_evt_handler->m_parent) && m_evt_handler->m_curr->indref == m_evt_handler->m_parent->indref) { _c4dbgpf("seqblck[RNXT]: end indentless seq, go to parent={}. node={}", m_evt_handler->m_parent->node_id, m_evt_handler->m_curr->node_id); _RYML_CB_ASSERT(this->callbacks(), m_evt_handler->m_curr != m_evt_handler->m_parent); _handle_indentation_pop(m_evt_handler->m_parent); _RYML_CB_ASSERT(this->callbacks(), has_all(RMAP|BLCK)); m_evt_handler->add_sibling(); addrem_flags(RKEY, RNXT); goto seqblck_finish; } else //if(first != '*') { _c4err("parse error"); } } } seqblck_again: _c4dbgt("seqblck: go again", 0); if(_finished_line()) { _line_ended(); _scan_line(); if(_finished_file()) { _c4dbgp("seqblck: finish!"); _end_seq_blck(); goto seqblck_finish; } _c4dbgnextline(); } goto seqblck_start; seqblck_finish: _c4dbgp("seqblck: finish"); } //----------------------------------------------------------------------------- template void ParseEngine::_handle_map_block() { mapblck_start: _c4dbgpf("handle2_map_block: map_id={} node_id={} level={} indref={}", m_evt_handler->m_parent->node_id, m_evt_handler->m_curr->node_id, m_evt_handler->m_curr->level, m_evt_handler->m_curr->indref); // states: RKEY|QMRK -> RKCL -> RVAL -> RNXT _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(BLCK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RKEY|RKCL|RVAL|RNXT|QMRK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, 1 == (has_any(RKEY) + has_any(RKCL) + has_any(RVAL) + has_any(RNXT) + has_any(QMRK))); _maybe_skip_comment(); csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; if(has_any(RKEY)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); // // handle indentation // if(m_evt_handler->m_curr->at_line_beginning()) { if(m_evt_handler->m_curr->indentation_eq()) { _c4dbgpf("mapblck[RKEY]: skip {} from indref", m_evt_handler->m_curr->indref); _line_progressed(m_evt_handler->m_curr->indref); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; } else if(m_evt_handler->m_curr->indentation_lt()) { _c4dbgp("mapblck[RKEY]: smaller indentation!"); _handle_indentation_pop_from_block_map(); _line_progressed(m_evt_handler->m_curr->line_contents.indentation); if(has_all(RMAP|BLCK)) { _c4dbgp("mapblck[RKEY]: still mapblck!"); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(RKEY)); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; } else { _c4dbgp("mapblck[RKEY]: no longer mapblck!"); goto mapblck_finish; } } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indentation_gt()); _c4err("invalid indentation"); } } // // now handle the tokens // const char first = rem.str[0]; const size_t startline = m_evt_handler->m_curr->pos.line; const size_t startindent = m_evt_handler->m_curr->line_contents.current_col(); _c4dbgpf("mapblck[RKEY]: '{}'", first); ScannedScalar sc; if(first == '\'') { _c4dbgp("mapblck[RKEY]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_squoted(maybe_filtered); addrem_flags(RVAL, RKEY); if(!_maybe_scan_following_colon()) _c4err("could not find ':' colon after key"); _maybe_skip_whitespace_tokens(); } else if(first == '"') { _c4dbgp("mapblck[RKEY]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); addrem_flags(RVAL, RKEY); if(!_maybe_scan_following_colon()) _c4err("could not find ':' colon after key"); _maybe_skip_whitespace_tokens(); } // block scalars (| and >) can not be used as keys unless they // appear in an explicit QMRK scope (ie, after the ? token), else if(C4_UNLIKELY(first == '|')) { _c4err("block literal keys must be enclosed in '?'"); } else if(C4_UNLIKELY(first == '>')) { _c4err("block literal keys must be enclosed in '?'"); } else if(_scan_scalar_plain_map_blck(&sc)) { _c4dbgp("mapblck[RKEY]: plain scalar"); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_plain(maybe_filtered); addrem_flags(RVAL, RKEY); if(!_maybe_scan_following_colon()) _c4err("could not find ':' colon after key"); _maybe_skip_whitespace_tokens(); } else if(first == '?') { _c4dbgp("mapblck[RKEY]: key token!"); addrem_flags(QMRK, RKEY); _line_progressed(1); _maybe_skip_whitespace_tokens(); m_was_inside_qmrk = true; goto mapblck_again; } else if(first == ':') { _c4dbgp("mapblck[RKEY]: setting empty key"); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RVAL, RKEY); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '*') { csubstr ref = _scan_ref_map(); _c4dbgpf("mapblck[RKEY]: key ref! [{}]~~~{}~~~", ref.len, ref); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_ref(ref); addrem_flags(RVAL, RKEY); if(!_maybe_scan_following_colon()) _c4err("could not find ':' colon after key"); _maybe_skip_whitespace_tokens(); } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgpf("mapblck[RKEY]: key anchor! [{}]~~~{}~~~", anchor.len, anchor); _add_annotation(&m_pending_anchors, anchor, startindent, startline); } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("mapblck[RKEY]: key tag! [{}]~~~{}~~~", tag.len, tag); _add_annotation(&m_pending_tags, tag, startindent, startline); } else if(first == '[') { // RYML's tree cannot store container keys, but that's // handled inside the tree handler. Other handlers may be // able to handle it. _c4dbgp("mapblck[RKEY]: start child seqflow (!)"); addrem_flags(RKCL, RKEY); _handle_annotations_before_blck_key_scalar(); m_evt_handler->begin_seq_key_flow(); addrem_flags(RSEQ|FLOW|RVAL, RMAP|BLCK|RKCL); _line_progressed(1); _set_indentation(startindent); goto mapblck_finish; } else if(first == '{') { // RYML's tree cannot store container keys, but that's // handled inside the tree handler. Other handlers may be // able to handle it. _c4dbgp("mapblck[RKEY]: start child mapflow (!)"); addrem_flags(RKCL, RKEY); _handle_annotations_before_blck_key_scalar(); m_evt_handler->begin_map_key_flow(); addrem_flags(FLOW|RKEY, BLCK|RKCL); _line_progressed(1); _set_indentation(startindent); goto mapblck_finish; } else if(first == '-') { _c4dbgp("mapblck[RKEY]: maybe doc?"); if(m_evt_handler->m_curr->line_contents.indentation == 0 && _is_doc_begin_token(rem)) { _c4dbgp("mapblck[RKEY]: end+start doc"); _start_doc_suddenly(); _line_progressed(3); _maybe_skip_whitespace_tokens(); goto mapblck_finish; } else { _c4err("parse error"); } } else if(first == '.') { _c4dbgp("mapblck[RKEY]: maybe end doc?"); if(m_evt_handler->m_curr->line_contents.indentation == 0 && _is_doc_end_token(rem)) { _c4dbgp("mapblck[RKEY]: end doc"); _end_doc_suddenly(); _line_progressed(3); _maybe_skip_whitespace_tokens(); goto mapblck_finish; } else { _c4err("parse error"); } } _RYML_WITH_TAB_TOKENS( else if(first == '\t') { _c4dbgp("mapblck[RKEY]: skip tabs"); _maybe_skipchars('\t'); }) else { _c4err("parse error"); } } else if(has_any(RKCL)) // read the key colon { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); // // handle indentation // if(m_evt_handler->m_curr->at_line_beginning()) { if(m_evt_handler->m_curr->indentation_eq()) { _c4dbgpf("mapblck[RKCL]: skip {} from indref", m_evt_handler->m_curr->indref); _line_progressed(m_evt_handler->m_curr->indref); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; } else if(C4_UNLIKELY(m_evt_handler->m_curr->indentation_lt())) { _c4err("invalid indentation"); } } const char first = rem.str[0]; _c4dbgpf("mapblck[RKCL]: '{}'", first); if(first == ':') { _c4dbgp("mapblck[RKCL]: found the colon"); addrem_flags(RVAL, RKCL); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '?') { _c4dbgp("mapblck[RKCL]: got '?'. val was empty"); _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, m_was_inside_qmrk); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(QMRK, RKCL); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '-') { if(m_evt_handler->m_curr->indref == 0 || m_evt_handler->m_curr->line_contents.indentation == 0 || _is_doc_begin_token(rem)) { _c4dbgp("mapblck[RKCL]: end+start doc"); _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, _is_doc_begin_token(rem)); _start_doc_suddenly(); _line_progressed(3); _maybe_skip_whitespace_tokens(); goto mapblck_finish; } else { _c4err("parse error"); } } else if(first == '.') { _c4dbgp("mapblck[RKCL]: maybe end doc?"); csubstr rs = rem.sub(1); if(rs == ".." || rs.begins_with(".. ")) { _c4dbgp("mapblck[RKCL]: end+start doc"); _end_doc_suddenly(); _line_progressed(3); goto mapblck_finish; } else { _c4err("parse error"); } } else if(m_was_inside_qmrk) { _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indentation_eq()); _c4dbgp("mapblck[RKCL]: missing :"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); m_was_inside_qmrk = false; addrem_flags(RKEY, RKCL); } else { _c4err("parse error"); } } else if(has_any(RVAL)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); // // handle indentation // if(m_evt_handler->m_curr->at_line_beginning()) { _c4dbgpf("mapblck[RVAL]: indref={} indentation={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->line_contents.indentation); m_evt_handler->m_curr->more_indented = false; if(m_evt_handler->m_curr->indref == npos) { _c4dbgpf("mapblck[RVAL]: setting indentation={}", m_evt_handler->m_parent->indref); _set_indentation(m_evt_handler->m_curr->line_contents.indentation); _line_progressed(m_evt_handler->m_curr->indref); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; } else if(m_evt_handler->m_curr->indentation_eq()) { _c4dbgp("mapblck[RVAL]: skip indentation!"); _line_progressed(m_evt_handler->m_curr->indref); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; // TODO: this is valid: // // ```yaml // a: // b: // --- // a: // b // --- // a: // b: c // ``` // // ... but this is not: // // ```yaml // a: // v // --- // a: b: c // ``` // // here, we probably need to set a boolean on the state // to disambiguate between these cases. } else if(m_evt_handler->m_curr->indentation_gt()) { _c4dbgp("mapblck[RVAL]: more indented!"); m_evt_handler->m_curr->more_indented = true; _line_progressed(m_evt_handler->m_curr->line_contents.indentation); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; } else if(m_evt_handler->m_curr->indentation_lt()) { _c4dbgp("mapblck[RVAL]: smaller indentation!"); _handle_indentation_pop_from_block_map(); if(has_all(RMAP|BLCK)) { _c4dbgp("mapblck[RVAL]: still mapblck!"); _line_progressed(m_evt_handler->m_curr->line_contents.indentation); if(has_any(RNXT)) { _c4dbgp("mapblck[RVAL]: speculatively expect next keyval"); m_evt_handler->add_sibling(); addrem_flags(RKEY, RNXT); } goto mapblck_again; } else { _c4dbgp("mapblck[RVAL]: no longer mapblck!"); goto mapblck_finish; } } else if(m_evt_handler->m_curr->line_contents.indentation == npos) { _c4dbgp("mapblck[RVAL]: empty line!"); _line_progressed(m_evt_handler->m_curr->line_contents.rem.len); goto mapblck_again; } } // // now handle the tokens // const char first = rem.str[0]; const size_t startline = m_evt_handler->m_curr->pos.line; const size_t startindent = m_evt_handler->m_curr->line_contents.current_col(); _c4dbgpf("mapblck[RVAL]: '{}'", first); ScannedScalar sc; if(first == '\'') { _c4dbgp("mapblck[RVAL]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); if(!_maybe_scan_following_colon()) { _c4dbgp("mapblck[RVAL]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); // VAL! m_evt_handler->set_val_scalar_squoted(maybe_filtered); addrem_flags(RNXT, RVAL); } else { if(startindent != m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: start new block map, set scalar as key"); _handle_annotations_before_start_mapblck(startline); addrem_flags(RNXT, RVAL); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); // KEY! m_evt_handler->set_key_scalar_squoted(maybe_filtered); _maybe_skip_whitespace_tokens(); // keep the child state on RVAL addrem_flags(RVAL, RNXT); } else { _c4dbgp("mapblck[RVAL]: prev val empty+this is a key"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); // KEY! m_evt_handler->set_key_scalar_squoted(maybe_filtered); // keep going on RVAL _maybe_skip_whitespace_tokens(); } } } else if(first == '"') { _c4dbgp("mapblck[RVAL]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); if(!_maybe_scan_following_colon()) { _c4dbgp("mapblck[RVAL]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); // VAL! m_evt_handler->set_val_scalar_dquoted(maybe_filtered); addrem_flags(RNXT, RVAL); } else { if(startindent != m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: start new block map, set scalar as key"); _handle_annotations_before_start_mapblck(startline); addrem_flags(RNXT, RVAL); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); // KEY! m_evt_handler->set_key_scalar_dquoted(maybe_filtered); _maybe_skip_whitespace_tokens(); // keep the child state on RVAL addrem_flags(RVAL, RNXT); } else { _c4dbgp("mapblck[RVAL]: prev val empty+this is a key"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); // KEY! m_evt_handler->set_key_scalar_dquoted(maybe_filtered); // keep going on RVAL _maybe_skip_whitespace_tokens(); } } } // block scalars can only appear as keys when in QMRK scope // (ie, after ? tokens), so no need to scan following colon else if(first == '|') { _c4dbgp("mapblck[RVAL]: scanning block-literal scalar"); ScannedBlock sb; _scan_block(&sb, m_evt_handler->m_curr->indref + 1); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_literal(sb); m_evt_handler->set_val_scalar_literal(maybe_filtered); addrem_flags(RNXT, RVAL); } else if(first == '>') { _c4dbgp("mapblck[RVAL]: scanning block-folded scalar"); ScannedBlock sb; _scan_block(&sb, m_evt_handler->m_curr->indref + 1); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_folded(sb); m_evt_handler->set_val_scalar_folded(maybe_filtered); addrem_flags(RNXT, RVAL); } else if(_scan_scalar_plain_map_blck(&sc)) { _c4dbgp("mapblck[RVAL]: plain scalar."); if(!_maybe_scan_following_colon()) { _c4dbgp("mapblck[RVAL]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, m_evt_handler->m_curr->indref); // VAL! m_evt_handler->set_val_scalar_plain(maybe_filtered); addrem_flags(RNXT, RVAL); } else { if(startindent != m_evt_handler->m_curr->indref) { _c4dbgpf("mapblck[RVAL]: start new block map, set scalar as key {}", m_evt_handler->m_curr->indref); addrem_flags(RNXT, RVAL); _handle_annotations_before_start_mapblck(startline); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! m_evt_handler->set_key_scalar_plain(maybe_filtered); _maybe_skip_whitespace_tokens(); // keep the child state on RVAL addrem_flags(RVAL, RNXT); } else { _c4dbgp("mapblck[RVAL]: prev val empty+this is a key"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! m_evt_handler->set_key_scalar_plain(maybe_filtered); // keep going on RVAL _maybe_skip_whitespace_tokens(); } } } else if(first == '-') { if(rem.len == 1 || rem.str[1] == ' ' _RYML_WITH_TAB_TOKENS(|| rem.str[1] == '\t')) { _c4dbgp("mapblck[RVAL]: start val seqblck"); addrem_flags(RNXT, RVAL); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_seq_val_block(); addrem_flags(RSEQ|RVAL, RMAP|RNXT); _set_indentation(startindent); _line_progressed(1); _maybe_skip_whitespace_tokens(); goto mapblck_finish; } else if(m_evt_handler->m_curr->indref == 0 || m_evt_handler->m_curr->line_contents.indentation == 0 || _is_doc_begin_token(rem)) { _c4dbgp("mapblck[RVAL]: end+start doc"); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, _is_doc_begin_token(rem)); _start_doc_suddenly(); _line_progressed(3); _maybe_skip_whitespace_tokens(); goto mapblck_finish; } else { _c4err("parse error"); } } else if(first == '[') { _c4dbgp("mapblck[RVAL]: start val seqflow"); addrem_flags(RNXT, RVAL); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_seq_val_flow(); addrem_flags(RSEQ|FLOW|RVAL, RMAP|BLCK|RNXT); _set_indentation(m_evt_handler->m_curr->indref + 1u); _line_progressed(1); goto mapblck_finish; } else if(first == '{') { _c4dbgp("mapblck[RVAL]: start val mapflow"); addrem_flags(RNXT, RVAL); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_flow(); addrem_flags(RKEY|FLOW, BLCK|RVAL|RNXT); m_evt_handler->m_curr->scalar_col = m_evt_handler->m_curr->line_contents.indentation; _set_indentation(m_evt_handler->m_curr->indref + 1u); _line_progressed(1); goto mapblck_finish; } else if(first == '*') { csubstr ref = _scan_ref_map(); _c4dbgpf("mapblck[RVAL]: ref! [{}]~~~{}~~~", ref.len, ref); if(startindent == m_evt_handler->m_curr->indref) { _c4dbgpf("mapblck[RVAL]: same indentation {}", startindent); m_evt_handler->set_val_ref(ref); addrem_flags(RNXT, RVAL); } else { _c4dbgpf("mapblck[RVAL]: larger indentation {}>{}", startindent, m_evt_handler->m_curr->indref); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, startindent > m_evt_handler->m_curr->indref); if(_maybe_scan_following_colon()) { _c4dbgp("mapblck[RVAL]: start child map, block"); addrem_flags(RNXT, RVAL); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_block(); m_evt_handler->set_key_ref(ref); _set_indentation(startindent); // keep going in RVAL addrem_flags(RVAL, RNXT); } else { _c4dbgp("mapblck[RVAL]: was val ref"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_ref(ref); addrem_flags(RNXT, RVAL); } } _maybe_skip_whitespace_tokens(); } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgpf("mapblck[RVAL]: anchor! [{}]~~~{}~~~", anchor.len, anchor); if(startindent == m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: anchor for next key. val is missing!"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(RKEY, RVAL); } // we need to buffer the anchors, as there may be two // consecutive anchors in here _add_annotation(&m_pending_anchors, anchor, startindent, startline); } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("mapblck[RVAL]: tag! [{}]~~~{}~~~", tag.len, tag); if(startindent == m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: tag for next key. val is missing!"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(RKEY, RVAL); } // we need to buffer the tags, as there may be two // consecutive tags in here _add_annotation(&m_pending_tags, tag, startindent, startline); } else if(first == '?') { if(startindent == m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: got '?'. val was empty"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(QMRK, RVAL); } else if(startindent > m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: start val mapblck"); addrem_flags(RNXT, RVAL); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_block(); addrem_flags(QMRK|BLCK, RNXT); _set_indentation(startindent); } else { _c4err("parse error"); } m_was_inside_qmrk = true; _line_progressed(1); _maybe_skip_whitespace_tokens(); goto mapblck_again; } else if(first == ':') { if(startindent == m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: got ':'. val was empty, next key as well"); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); m_evt_handler->set_key_scalar_plain_empty(); } else if(startindent > m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: start val mapblck"); addrem_flags(RNXT, RVAL); _handle_annotations_before_start_mapblck(startline); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_scalar_plain_empty(); // keep the child state on RVAL addrem_flags(RVAL, RNXT); } else { _c4err("parse error"); } _line_progressed(1); _maybe_skip_whitespace_tokens(); goto mapblck_again; } else if(first == '.') { _c4dbgp("mapblck[RVAL]: maybe doc?"); csubstr rs = rem.sub(1); if(rs == ".." || rs.begins_with(".. ")) { _c4dbgp("seqblck[RVAL]: end doc expl"); _end_doc_suddenly(); _line_progressed(3); _maybe_skip_whitespace_tokens(); goto mapblck_finish; } else { _c4err("parse error"); } } _RYML_WITH_TAB_TOKENS( else if(first == '\t') { _c4dbgp("mapblck[RVAL]: skip tabs"); _maybe_skipchars('\t'); }) else { _c4err("parse error"); } } else if(has_any(RNXT)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(QMRK)); // // handle indentation // if(m_evt_handler->m_curr->at_line_beginning()) { _c4dbgpf("mapblck[RNXT]: indref={} indentation={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->line_contents.indentation); if(m_evt_handler->m_curr->indentation_eq()) { _c4dbgpf("mapblck[RNXT]: skip {} from indref", m_evt_handler->m_curr->indref); _line_progressed(m_evt_handler->m_curr->indref); _c4dbgp("mapblck[RNXT]: speculatively expect next keyval"); m_evt_handler->add_sibling(); addrem_flags(RKEY, RNXT); goto mapblck_again; } else if(m_evt_handler->m_curr->indentation_lt()) { _c4dbgp("mapblck[RNXT]: smaller indentation!"); _handle_indentation_pop_from_block_map(); if(has_all(RMAP|BLCK)) { _line_progressed(m_evt_handler->m_curr->line_contents.indentation); if(!has_any(RKCL)) { _c4dbgp("mapblck[RNXT]: speculatively expect next keyval"); m_evt_handler->add_sibling(); addrem_flags(RKEY, RNXT); } goto mapblck_again; } else { goto mapblck_finish; } } } else { _c4dbgp("mapblck[RNXT]: NOT at line begin"); if(!rem.begins_with_any(" \t")) { _c4err("parse error"); } else { _skipchars(" \t"); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) { _c4dbgp("seqblck[RNXT]: again"); goto mapblck_again; } } } // // handle tokens // _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, rem.len > 0); const char first = rem.str[0]; _c4dbgpf("mapblck[RNXT]: '{}'", _c4prc(first)); if(first == ':') { if(m_evt_handler->m_curr->more_indented) { _c4dbgp("mapblck[RNXT]: start child block map"); C4_NOT_IMPLEMENTED(); //m_evt_handler->actually_as_block_map(); _line_progressed(1); _set_indentation(m_evt_handler->m_curr->scalar_col); m_evt_handler->m_curr->more_indented = false; goto mapblck_again; } else { _c4err("parse error"); } } else if(first == ' ') { _c4dbgp("mapblck[RNXT]: skip spaces"); _maybe_skip_whitespace_tokens(); } else { _c4err("parse error"); } } else if(has_any(QMRK)) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKEY)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RKCL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RVAL)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT)); // // handle indentation // if(m_evt_handler->m_curr->at_line_beginning()) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->line_contents.indentation != npos); if(m_evt_handler->m_curr->indentation_eq()) { _c4dbgpf("mapblck[QMRK]: skip {} from indref", m_evt_handler->m_curr->indref); _line_progressed(m_evt_handler->m_curr->indref); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; } else if(m_evt_handler->m_curr->indentation_lt()) { _c4dbgp("mapblck[QMRK]: smaller indentation!"); _handle_indentation_pop_from_block_map(); _line_progressed(m_evt_handler->m_curr->line_contents.indentation); if(has_all(RMAP|BLCK)) { _c4dbgp("mapblck[QMRK]: still mapblck!"); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_any(QMRK)); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; } else { _c4dbgp("mapblck[QMRK]: no longer mapblck!"); goto mapblck_finish; } } // indentation can be larger in QMRK state else { _c4dbgp("mapblck[QMRK]: larger indentation !"); _line_progressed(m_evt_handler->m_curr->line_contents.indentation); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) goto mapblck_again; } } // // now handle the tokens // const char first = rem.str[0]; const size_t startline = m_evt_handler->m_curr->pos.line; const size_t startindent = m_evt_handler->m_curr->line_contents.current_col(); _c4dbgpf("mapblck[QMRK]: '{}'", first); ScannedScalar sc; if(first == '\'') { _c4dbgp("mapblck[QMRK]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); // KEY! if(!_maybe_scan_following_colon()) { _c4dbgp("mapblck[QMRK]: set as key"); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_squoted(maybe_filtered); addrem_flags(RKCL, QMRK); } else { _c4dbgp("mapblck[QMRK]: start new block map as key (!), set scalar as key"); addrem_flags(RKCL, QMRK); _handle_annotations_before_start_mapblck_as_key(); m_evt_handler->begin_map_key_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_scalar_squoted(maybe_filtered); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); // keep the child state on RVAL addrem_flags(RVAL, RKCL|QMRK); } } else if(first == '"') { _c4dbgp("mapblck[QMRK]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); // KEY! if(!_maybe_scan_following_colon()) { _c4dbgp("mapblck[QMRK]: set as key"); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); addrem_flags(RKCL, QMRK); } else { _c4dbgp("mapblck[QMRK]: start new block map as key (!), set scalar as key"); addrem_flags(RKCL, QMRK); _handle_annotations_before_start_mapblck_as_key(); m_evt_handler->begin_map_key_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); // keep the child state on RVAL addrem_flags(RVAL, RKCL|QMRK); } } else if(first == '|') { _c4dbgp("mapblck[QMRK]: scanning block-literal scalar"); ScannedBlock sb; _scan_block(&sb, m_evt_handler->m_curr->indref + 1); csubstr maybe_filtered = _maybe_filter_key_scalar_literal(sb); // KEY! _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_literal(maybe_filtered); addrem_flags(RKCL, QMRK); } else if(first == '>') { _c4dbgp("mapblck[QMRK]: scanning block-literal scalar"); ScannedBlock sb; _scan_block(&sb, m_evt_handler->m_curr->indref + 1); csubstr maybe_filtered = _maybe_filter_key_scalar_folded(sb); // KEY! _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_folded(maybe_filtered); addrem_flags(RKCL, QMRK); } else if(_scan_scalar_plain_map_blck(&sc)) { _c4dbgp("mapblck[QMRK]: plain scalar"); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! if(!_maybe_scan_following_colon()) { _c4dbgp("mapblck[QMRK]: set as key"); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_plain(maybe_filtered); addrem_flags(RKCL, QMRK); } else { _c4dbgp("mapblck[QMRK]: start new block map as key (!), set scalar as key"); addrem_flags(RKCL, QMRK); _handle_annotations_before_start_mapblck_as_key(); m_evt_handler->begin_map_key_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_scalar_plain(maybe_filtered); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); // keep the child state on RVAL addrem_flags(RVAL, RKCL|QMRK); } } else if(first == ':') { if(startindent == m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[QMRK]: empty key"); addrem_flags(RVAL, QMRK); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_scalar_plain_empty(); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else { _c4dbgp("mapblck[QMRK]: start new block map as key (!), empty key"); addrem_flags(RKCL, QMRK); _handle_annotations_before_start_mapblck_as_key(); m_evt_handler->begin_map_key_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_scalar_plain_empty(); _line_progressed(1); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); // keep the child state on RVAL addrem_flags(RVAL, RKCL|QMRK); } } else if(first == '*') { csubstr ref = _scan_ref_map(); _c4dbgpf("mapblck[QMRK]: key ref! [{}]~~~{}~~~", ref.len, ref); if(!_maybe_scan_following_colon()) { _c4dbgp("mapblck[QMRK]: set ref as key"); _handle_annotations_before_blck_key_scalar(); m_evt_handler->set_key_ref(ref); addrem_flags(RKCL, QMRK); } else { _c4dbgp("mapblck[QMRK]: start new block map as key (!), set ref as key"); addrem_flags(RKCL, QMRK); _handle_annotations_before_blck_key_scalar(); m_evt_handler->begin_map_key_block(); m_evt_handler->set_key_ref(ref); _set_indentation(startindent); // keep the child state on RVAL addrem_flags(RVAL, RKCL|QMRK); } _maybe_skip_whitespace_tokens(); } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgpf("mapblck[QMRK]: key anchor! [{}]~~~{}~~~", anchor.len, anchor); _add_annotation(&m_pending_anchors, anchor, startindent, startline); } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("mapblck[QMRK]: key tag! [{}]~~~{}~~~", tag.len, tag); _add_annotation(&m_pending_tags, tag, startindent, startline); } else if(first == '-') { _c4dbgp("mapblck[QMRK]: maybe doc?"); csubstr rs = rem.sub(1); if(rs == "--" || rs.begins_with("-- ")) { _c4dbgp("mapblck[QMRK]: end+start doc"); _start_doc_suddenly(); _line_progressed(3); } else { _c4dbgp("mapblck[QMRK]: start child seqblck (!)"); addrem_flags(RKCL, RKEY|QMRK); _handle_annotations_before_blck_key_scalar(); m_evt_handler->begin_seq_key_block(); addrem_flags(RVAL|RSEQ, RMAP|RKCL|QMRK); _set_indentation(startindent); _line_progressed(1); } _maybe_skip_whitespace_tokens(); goto mapblck_finish; } else if(first == '[') { _c4dbgp("mapblck[QMRK]: start child seqflow (!)"); addrem_flags(RKCL, RKEY|QMRK); m_evt_handler->begin_seq_key_flow(); addrem_flags(RVAL|RSEQ|FLOW, RMAP|RKCL|QMRK|BLCK); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); goto mapblck_finish; } else if(first == '{') { _c4dbgp("mapblck[QMRK]: start child mapblck (!)"); addrem_flags(RKCL, RKEY|QMRK); m_evt_handler->begin_map_key_flow(); addrem_flags(RKEY|FLOW, RVAL|RKCL|QMRK|BLCK); _set_indentation(m_evt_handler->m_parent->indref); _line_progressed(1); goto mapblck_finish; } else if(first == '?') { _c4dbgp("mapblck[QMRK]: another QMRK '?'"); m_evt_handler->set_key_scalar_plain_empty(); m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); _line_progressed(1); } else if(first == '.') { _c4dbgp("mapblck[QMRK]: maybe end doc?"); csubstr rs = rem.sub(1); if(rs == ".." || rs.begins_with(".. ")) { _c4dbgp("mapblck[QMRK]: end+start doc"); _end_doc_suddenly(); _line_progressed(3); goto mapblck_finish; } else { _c4err("parse error"); } } else { _c4err("parse error"); } } mapblck_again: _c4dbgt("mapblck: again", 0); if(_finished_line()) { _line_ended(); _scan_line(); if(_finished_file()) { _c4dbgp("mapblck: file finished!"); _end_map_blck(); goto mapblck_finish; } _c4dbgnextline(); } goto mapblck_start; mapblck_finish: _c4dbgp("mapblck: finish"); } //----------------------------------------------------------------------------- template void ParseEngine::_handle_unk_json() { _c4dbgpf("handle_unk_json indref={} target={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->node_id); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT|RSEQ|RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RTOP)); _maybe_skip_comment(); csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) return; size_t pos = rem.first_not_of(" \t"); if(pos) { pos = pos != npos ? pos : rem.len; _c4dbgpf("skipping indentation of {}", pos); _line_progressed(pos); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) return; _c4dbgpf("rem is now [{}]~~~{}~~~", rem.len, rem); } if(rem.begins_with('[')) { _c4dbgp("it's a seq"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); m_evt_handler->begin_seq_val_flow(); addrem_flags(RSEQ|FLOW|RVAL, RUNK|RTOP|RDOC); _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); m_doc_empty = false; _line_progressed(1); } else if(rem.begins_with('{')) { _c4dbgp("it's a map"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); m_evt_handler->begin_map_val_flow(); addrem_flags(RMAP|FLOW|RKEY, RVAL|RTOP|RUNK|RDOC); m_doc_empty = false; _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); _line_progressed(1); } else if(_handle_bom()) { _c4dbgp("byte order mark"); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, ! has_any(SSCL)); _maybe_skip_whitespace_tokens(); csubstr s = m_evt_handler->m_curr->line_contents.rem; if(!s.len) return; const size_t startindent = m_evt_handler->m_curr->line_contents.indentation; // save const char first = s.str[0]; ScannedScalar sc; if(first == '"') { _c4dbgp("runk_json: scanning double-quoted scalar"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); add_flags(RDOC); m_doc_empty = false; sc = _scan_scalar_dquot(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); if(!_maybe_scan_following_colon()) { _c4dbgp("runk_json: set as val"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_scalar_dquoted(maybe_filtered); } else { _c4err("parse error"); } } else if(_scan_scalar_plain_unk(&sc)) { _c4dbgp("runk_json: got a plain scalar"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); add_flags(RDOC); m_doc_empty = false; if(!_maybe_scan_following_colon()) { _c4dbgp("runk_json: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, startindent); m_evt_handler->set_val_scalar_plain(maybe_filtered); } else { _c4err("parse error"); } } else { _c4err("parse error"); } } } //----------------------------------------------------------------------------- template void ParseEngine::_handle_unk() { _c4dbgpf("handle_unk indref={} target={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->node_id); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(RNXT|RSEQ|RMAP)); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RTOP)); _maybe_skip_comment(); csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) return; size_t pos = rem.first_not_of(" \t"); if(pos) { pos = pos != npos ? pos : rem.len; _c4dbgpf("skipping {} whitespace characters", pos); _line_progressed(pos); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) return; _c4dbgpf("rem is now [{}]~~~{}~~~", rem.len, rem); } if(m_evt_handler->m_curr->line_contents.indentation == 0u && _at_line_begin()) { _c4dbgp("rtop: zero indent + at line begin"); if(_handle_bom()) { _c4dbgp("byte order mark!"); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) return; } const char first = rem.str[0]; if(first == '-') { _c4dbgp("rtop: suspecting doc"); if(_is_doc_begin_token(rem)) { _c4dbgp("rtop: begin doc"); _maybe_end_doc(); _begin2_doc_expl(); _set_indentation(0); addrem_flags(RDOC|RUNK, NDOC); _line_progressed(3u); _maybe_skip_whitespace_tokens(); return; } } else if(first == '.') { _c4dbgp("rtop: suspecting doc end"); if(_is_doc_end_token(rem)) { _c4dbgp("rtop: end doc"); if(has_any(RDOC)) { _end2_doc_expl(); } else { _c4dbgp("rtop: ignore end doc"); } addrem_flags(NDOC|RUNK, RDOC); _line_progressed(3u); _maybe_skip_whitespace_tokens(); return; } } else if(first == '%') { _c4dbgpf("directive: {}", rem); if(C4_UNLIKELY(!m_doc_empty && has_none(NDOC))) _RYML_CB_ERR(m_evt_handler->m_stack.m_callbacks, "need document footer before directives"); _handle_directive(rem); return; } } /* no else-if! */ char first = rem.str[0]; if(first == '[') { m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); m_doc_empty = false; const size_t startindent = m_evt_handler->m_curr->line_contents.current_col(rem); if(C4_LIKELY( ! _annotations_require_key_container())) { _c4dbgp("it's a seq, flow"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_seq_val_flow(); addrem_flags(RSEQ|FLOW|RVAL, RUNK|RTOP|RDOC); _set_indentation(startindent); } else { _c4dbgp("start new block map, set flow seq as key (!)"); _handle_annotations_before_start_mapblck(m_evt_handler->m_curr->pos.line); m_evt_handler->begin_map_val_block(); addrem_flags(RMAP|BLCK|RKCL, RUNK|RTOP|RDOC); _handle_annotations_and_indentation_after_start_mapblck(startindent, m_evt_handler->m_curr->pos.line); m_evt_handler->begin_seq_key_flow(); addrem_flags(RSEQ|FLOW|RVAL, RMAP|BLCK|RKCL); _set_indentation(startindent); } _line_progressed(1); } else if(first == '{') { m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); m_doc_empty = false; const size_t startindent = m_evt_handler->m_curr->line_contents.current_col(rem); if(C4_LIKELY( ! _annotations_require_key_container())) { _c4dbgp("it's a map, flow"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_flow(); addrem_flags(RMAP|FLOW|RKEY, RVAL|RTOP|RUNK|RDOC); _set_indentation(startindent); } else { _c4dbgp("start new block map, set flow map as key (!)"); _handle_annotations_before_start_mapblck(m_evt_handler->m_curr->pos.line); m_evt_handler->begin_map_val_block(); addrem_flags(RMAP|BLCK|RKCL, RUNK|RTOP|RDOC); _handle_annotations_and_indentation_after_start_mapblck(startindent, m_evt_handler->m_curr->pos.line); m_evt_handler->begin_map_key_flow(); addrem_flags(RMAP|FLOW|RKEY, BLCK|RKCL); _set_indentation(startindent); } _line_progressed(1); } else if(first == '-' && _is_blck_token(rem)) { _c4dbgp("it's a seq, block"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_seq_val_block(); addrem_flags(RSEQ|BLCK|RVAL, RNXT|RTOP|RUNK|RDOC); m_doc_empty = false; _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '?' && _is_blck_token(rem)) { _c4dbgp("it's a map + this key is complex"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_block(); addrem_flags(RMAP|BLCK|QMRK, RKEY|RVAL|RTOP|RUNK); m_doc_empty = false; m_was_inside_qmrk = true; _save_indentation(); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == ':' && _is_blck_token(rem)) { if(m_doc_empty) { _c4dbgp("it's a map with an empty key"); const size_t startindent = m_evt_handler->m_curr->line_contents.indentation; // save const size_t startline = m_evt_handler->m_curr->pos.line; // save m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); _handle_annotations_before_start_mapblck(startline); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_scalar_plain_empty(); m_doc_empty = false; _set_indentation(startindent); } else { _c4dbgp("actually prev val is a key!"); size_t prev_indentation = m_evt_handler->m_curr->indref; m_evt_handler->actually_val_is_first_key_of_new_map_block(); _set_indentation(prev_indentation); } addrem_flags(RMAP|BLCK|RVAL, RTOP|RUNK|RDOC); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgpf("anchor! [{}]~~~{}~~~", anchor.len, anchor); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); const size_t line = m_evt_handler->m_curr->pos.line; _add_annotation(&m_pending_anchors, anchor, indentation, line); _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); m_doc_empty = false; } else if(first == '*') { csubstr ref = _scan_ref_map(); _c4dbgpf("ref! [{}]~~~{}~~~", ref.len, ref); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); m_doc_empty = false; if(!_maybe_scan_following_colon()) { _c4dbgp("runk: set val ref"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_ref(ref); } else { _c4dbgp("runk: start new block map, set ref as key"); const size_t startindent = m_evt_handler->m_curr->line_contents.indentation; // save const size_t startline = m_evt_handler->m_curr->pos.line; // save _handle_annotations_before_start_mapblck(startline); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_ref(ref); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RTOP|RUNK|RDOC); } } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("unk: val tag! [{}]~~~{}~~~", tag.len, tag); // we need to buffer the tags, as there may be two // consecutive tags in here const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); const size_t line = m_evt_handler->m_curr->pos.line; _add_annotation(&m_pending_tags, tag, indentation, line); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, ! has_any(SSCL)); _maybe_skip_whitespace_tokens(); csubstr s = m_evt_handler->m_curr->line_contents.rem; if(!s.len) return; const size_t startindent = m_evt_handler->m_curr->line_contents.indentation; // save const size_t startline = m_evt_handler->m_curr->pos.line; // save first = s.str[0]; ScannedScalar sc; if(first == '\'') { _c4dbgp("runk: scanning single-quoted scalar"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); add_flags(RDOC); m_doc_empty = false; sc = _scan_scalar_squot(); if(!_maybe_scan_following_colon()) { _c4dbgp("runk: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); m_evt_handler->set_val_scalar_squoted(maybe_filtered); } else { _c4dbgp("runk: start new block map, set scalar as key"); _handle_annotations_before_start_mapblck(startline); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); m_evt_handler->set_key_scalar_squoted(maybe_filtered); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RTOP|RUNK|RDOC); } } else if(first == '"') { _c4dbgp("runk: scanning double-quoted scalar"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); add_flags(RDOC); m_doc_empty = false; sc = _scan_scalar_dquot(); if(!_maybe_scan_following_colon()) { _c4dbgp("runk: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); m_evt_handler->set_val_scalar_dquoted(maybe_filtered); } else { _c4dbgp("runk: start new block map, set double-quoted scalar as key"); _handle_annotations_before_start_mapblck(startline); m_evt_handler->begin_map_val_block(); _handle_colon(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RTOP|RUNK|RDOC); } } else if(first == '|') { _c4dbgp("runk: scanning block-literal scalar"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); add_flags(RDOC); m_doc_empty = false; ScannedBlock sb; _scan_block(&sb, startindent); if(C4_LIKELY(!_maybe_scan_following_colon())) { _c4dbgp("runk: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_literal(sb); m_evt_handler->set_val_scalar_literal(maybe_filtered); } else { _c4err("block literal keys must be enclosed in '?'"); } } else if(first == '>') { _c4dbgp("runk: scanning block-folded scalar"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); add_flags(RDOC); m_doc_empty = false; ScannedBlock sb; _scan_block(&sb, startindent); if(C4_LIKELY(!_maybe_scan_following_colon())) { _c4dbgp("runk: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_folded(sb); m_evt_handler->set_val_scalar_folded(maybe_filtered); } else { _c4err("block folded keys must be enclosed in '?'"); } } else if(_scan_scalar_plain_unk(&sc)) { _c4dbgp("runk: got a plain scalar"); m_evt_handler->check_trailing_doc_token(); _maybe_begin_doc(); add_flags(RDOC); m_doc_empty = false; if(!_maybe_scan_following_colon()) { _c4dbgp("runk: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, startindent); m_evt_handler->set_val_scalar_plain(maybe_filtered); } else { _c4dbgp("runk: start new block map, set scalar as key"); _handle_annotations_before_start_mapblck(startline); _handle_colon(); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, startindent); m_evt_handler->set_key_scalar_plain(maybe_filtered); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RTOP|RUNK|RDOC); } } } } //----------------------------------------------------------------------------- template C4_COLD void ParseEngine::_handle_usty() { _c4dbgpf("handle_usty target={}", m_evt_handler->m_curr->indref, m_evt_handler->m_curr->node_id); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_none(BLCK|FLOW)); #ifdef RYML_NO_COVERAGE__TO_BE_DELETED if(has_any(RNXT)) { _c4dbgp("usty[RNXT]: finishing!"); _end_stream(); } #endif _maybe_skip_comment(); csubstr rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) return; size_t pos = rem.first_not_of(" \t"); if(pos) { pos = pos != npos ? pos : rem.len; _c4dbgpf("skipping indentation of {}", pos); _line_progressed(pos); rem = m_evt_handler->m_curr->line_contents.rem; if(!rem.len) return; _c4dbgpf("rem is now [{}]~~~{}~~~", rem.len, rem); } _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, rem.len > 0); size_t startindent = m_evt_handler->m_curr->line_contents.indentation; // save char first = rem.str[0]; if(has_any(RSEQ)) // destination is a sequence { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, ! has_any(RMAP)); _c4dbgpf("usty[RSEQ]: first='{}'", _c4prc(first)); if(first == '[') { _c4dbgp("usty[RSEQ]: it's a flow seq. merging it"); add_flags(RNXT); m_evt_handler->_push(); addrem_flags(FLOW|RVAL, RNXT|USTY); _set_indentation(startindent); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '-' && _is_blck_token(rem)) { _c4dbgp("usty[RSEQ]: it's a block seq. merging it"); add_flags(RNXT); m_evt_handler->_push(); addrem_flags(BLCK|RVAL, RNXT|USTY); _set_indentation(startindent); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else { _c4err("can only parse a seq into an existing seq"); } } else if(has_any(RMAP)) // destination is a map { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, ! has_any(RSEQ)); _c4dbgpf("usty[RMAP]: first='{}'", _c4prc(first)); if(first == '{') { _c4dbgp("usty[RMAP]: it's a flow map. merging it"); add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->_push(); addrem_flags(RMAP|FLOW|RKEY, RNXT|USTY); _set_indentation(startindent); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '?' && _is_blck_token(rem)) { _c4dbgp("usty[RMAP]: it's a block map + this key is complex"); add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->_push(); addrem_flags(RMAP|BLCK|QMRK, RNXT|USTY); m_was_inside_qmrk = true; _save_indentation(); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == ':' && _is_blck_token(rem)) { _c4dbgp("usty[RMAP]: it's a map with an empty key"); add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->_push(); m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _save_indentation(); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(rem.begins_with('&')) { csubstr anchor = _scan_anchor(); _c4dbgpf("usty[RMAP]: anchor! [{}]~~~{}~~~", anchor.len, anchor); const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); const size_t line = m_evt_handler->m_curr->pos.line; _add_annotation(&m_pending_anchors, anchor, indentation, line); _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); } else if(first == '*') { csubstr ref = _scan_ref_map(); _c4dbgpf("usty[RMAP]: ref! [{}]~~~{}~~~", ref.len, ref); if(!_maybe_scan_following_colon()) { _c4err("cannot read a VAL to a map"); } else { _c4dbgp("usty[RMAP]: start new block map, set ref as key"); const size_t startline = m_evt_handler->m_curr->pos.line; // save add_flags(RNXT); _handle_annotations_before_start_mapblck(startline); m_evt_handler->_push(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_ref(ref); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); } } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("usty[RMAP]: val tag! [{}]~~~{}~~~", tag.len, tag); // we need to buffer the tags, as there may be two // consecutive tags in here const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); const size_t line = m_evt_handler->m_curr->pos.line; _add_annotation(&m_pending_tags, tag, indentation, line); } else if(first == '[' || (first == '-' && _is_blck_token(rem))) { _c4err("cannot parse a seq into an existing map"); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, ! has_any(SSCL)); startindent = m_evt_handler->m_curr->line_contents.indentation; // save const size_t startline = m_evt_handler->m_curr->pos.line; // save ScannedScalar sc; _c4dbgpf("usty[RMAP]: maybe scalar. first='{}'", _c4prc(first)); if(first == '\'') { _c4dbgp("usty[RMAP]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); if(!_maybe_scan_following_colon()) { _c4err("cannot read a VAL to a map"); } else { _c4dbgp("usty[RMAP]: start new block map, set scalar as key"); add_flags(RNXT); _handle_annotations_before_start_mapblck(startline); m_evt_handler->_push(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); m_evt_handler->set_key_scalar_squoted(maybe_filtered); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _maybe_skip_whitespace_tokens(); } } else if(first == '"') { _c4dbgp("usty[RMAP]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); if(!_maybe_scan_following_colon()) { _c4err("cannot read a VAL to a map"); } else { _c4dbgp("usty[RMAP]: start new block map, set double-quoted scalar as key"); add_flags(RNXT); _handle_annotations_before_start_mapblck(startline); m_evt_handler->_push(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _maybe_skip_whitespace_tokens(); } } else if(first == '|') { _c4err("block literal keys must be enclosed in '?'"); } else if(first == '>') { _c4err("block literal keys must be enclosed in '?'"); } else if(_scan_scalar_plain_unk(&sc)) { _c4dbgp("usty[RMAP]: got a plain scalar"); if(!_maybe_scan_following_colon()) { _c4err("cannot read a VAL to a map"); } else { _c4dbgp("usty[RMAP]: start new block map, set scalar as key"); add_flags(RNXT); _handle_annotations_before_start_mapblck(startline); m_evt_handler->_push(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, startindent); m_evt_handler->set_key_scalar_plain(maybe_filtered); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _maybe_skip_whitespace_tokens(); } } else { _c4err("parse error"); } } } else // destination is unknown { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, ! has_any(RSEQ)); _c4dbgpf("usty[UNK]: first='{}'", _c4prc(first)); if(first == '[') { _c4dbgp("usty[UNK]: it's a flow seq"); add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_seq_val_flow(); addrem_flags(RSEQ|FLOW|RVAL, RNXT|USTY); _set_indentation(startindent); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '-' && _is_blck_token(rem)) { _c4dbgp("usty[UNK]: it's a block seq"); add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_seq_val_block(); addrem_flags(RSEQ|BLCK|RVAL, RNXT|USTY); _set_indentation(startindent); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '{') { _c4dbgp("usty[UNK]: it's a flow map"); add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_flow(); addrem_flags(RMAP|FLOW|RKEY, RNXT|USTY); _set_indentation(startindent); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '?' && _is_blck_token(rem)) { _c4dbgp("usty[UNK]: it's a map + this key is complex"); add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_block(); addrem_flags(RMAP|BLCK|QMRK, RNXT|USTY); m_was_inside_qmrk = true; _save_indentation(); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == ':' && _is_blck_token(rem)) { _c4dbgp("usty[UNK]: it's a map with an empty key"); add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_block(); m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _save_indentation(); _line_progressed(1); _maybe_skip_whitespace_tokens(); } else if(first == '&') { csubstr anchor = _scan_anchor(); _c4dbgpf("usty[UNK]: anchor! [{}]~~~{}~~~", anchor.len, anchor); const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); const size_t line = m_evt_handler->m_curr->pos.line; _add_annotation(&m_pending_anchors, anchor, indentation, line); _set_indentation(m_evt_handler->m_curr->line_contents.current_col(rem)); } else if(first == '*') { csubstr ref = _scan_ref_map(); _c4dbgpf("usty[UNK]: ref! [{}]~~~{}~~~", ref.len, ref); if(!_maybe_scan_following_colon()) { _c4dbgp("usty[UNK]: set val ref"); _handle_annotations_before_blck_val_scalar(); m_evt_handler->set_val_ref(ref); } else { _c4dbgp("usty[UNK]: start new block map, set ref as key"); const size_t startline = m_evt_handler->m_curr->pos.line; // save add_flags(RNXT); _handle_annotations_before_start_mapblck(startline); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); m_evt_handler->set_key_ref(ref); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); } } else if(first == '!') { csubstr tag = _scan_tag(); _c4dbgpf("usty[UNK]: val tag! [{}]~~~{}~~~", tag.len, tag); // we need to buffer the tags, as there may be two // consecutive tags in here const size_t indentation = m_evt_handler->m_curr->line_contents.current_col(rem); const size_t line = m_evt_handler->m_curr->pos.line; _add_annotation(&m_pending_tags, tag, indentation, line); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, ! has_any(SSCL)); startindent = m_evt_handler->m_curr->line_contents.indentation; // save const size_t startline = m_evt_handler->m_curr->pos.line; // save first = rem.str[0]; ScannedScalar sc; _c4dbgpf("usty[UNK]: maybe scalar. first='{}'", _c4prc(first)); if(first == '\'') { _c4dbgp("usty[UNK]: scanning single-quoted scalar"); sc = _scan_scalar_squot(); if(!_maybe_scan_following_colon()) { _c4dbgp("usty[UNK]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_squot(sc); m_evt_handler->set_val_scalar_squoted(maybe_filtered); _end_stream(); } else { _c4dbgp("usty[UNK]: start new block map, set scalar as key"); add_flags(RNXT); _handle_annotations_before_start_mapblck(startline); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); m_evt_handler->set_key_scalar_squoted(maybe_filtered); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _maybe_skip_whitespace_tokens(); } } else if(first == '"') { _c4dbgp("usty[UNK]: scanning double-quoted scalar"); sc = _scan_scalar_dquot(); if(!_maybe_scan_following_colon()) { _c4dbgp("usty[UNK]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_dquot(sc); m_evt_handler->set_val_scalar_dquoted(maybe_filtered); _end_stream(); } else { _c4dbgp("usty[UNK]: start new block map, set double-quoted scalar as key"); add_flags(RNXT); _handle_annotations_before_start_mapblck(startline); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); m_evt_handler->set_key_scalar_dquoted(maybe_filtered); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _maybe_skip_whitespace_tokens(); } } else if(first == '|') { _c4dbgp("usty[UNK]: scanning block-literal scalar"); ScannedBlock sb; _scan_block(&sb, startindent); _c4dbgp("usty[UNK]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_literal(sb); m_evt_handler->set_val_scalar_literal(maybe_filtered); _end_stream(); } else if(first == '>') { _c4dbgp("usty[UNK]: scanning block-folded scalar"); ScannedBlock sb; _scan_block(&sb, startindent); _c4dbgp("usty[UNK]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_folded(sb); m_evt_handler->set_val_scalar_folded(maybe_filtered); _end_stream(); } else if(_scan_scalar_plain_unk(&sc)) { _c4dbgp("usty[UNK]: got a plain scalar"); if(!_maybe_scan_following_colon()) { _c4dbgp("usty[UNK]: set as val"); _handle_annotations_before_blck_val_scalar(); csubstr maybe_filtered = _maybe_filter_val_scalar_plain(sc, startindent); m_evt_handler->set_val_scalar_plain(maybe_filtered); _end_stream(); } else { _c4dbgp("usty[UNK]: start new block map, set scalar as key"); add_flags(RNXT); _handle_annotations_before_start_mapblck(startline); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, startindent); m_evt_handler->set_key_scalar_plain(maybe_filtered); _set_indentation(startindent); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _maybe_skip_whitespace_tokens(); } } else { _c4err("parse error"); } } } } //----------------------------------------------------------------------------- template void ParseEngine::parse_json_in_place_ev(csubstr filename, substr src) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_stack.size() >= 1); m_file = filename; m_buf = src; _reset(); m_evt_handler->start_parse(filename.str, &_s_relocate_arena, this); m_evt_handler->begin_stream(); while( ! _finished_file()) { _scan_line(); while( ! _finished_line()) { _c4dbgnextline(); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, ! m_evt_handler->m_curr->line_contents.rem.empty()); if(has_any(RSEQ)) { _handle_seq_json(); } else if(has_any(RMAP)) { _handle_map_json(); } else if(has_any(RUNK)) { _handle_unk_json(); } else { _c4err("internal error"); } } if(_finished_file()) break; // it may have finished because of multiline blocks _line_ended(); } _end_stream(); m_evt_handler->finish_parse(); } //----------------------------------------------------------------------------- template void ParseEngine::parse_in_place_ev(csubstr filename, substr src) { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_stack.size() >= 1); m_file = filename; m_buf = src; _reset(); m_evt_handler->start_parse(filename.str, &_s_relocate_arena, this); m_evt_handler->begin_stream(); while( ! _finished_file()) { _scan_line(); while( ! _finished_line()) { _c4dbgnextline(); _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, ! m_evt_handler->m_curr->line_contents.rem.empty()); if(has_any(FLOW)) { if(has_none(RSEQIMAP)) { if(has_any(RSEQ)) { _handle_seq_flow(); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RMAP)); _handle_map_flow(); } } else { _handle_seq_imap(); } } else if(has_any(BLCK)) { if(has_any(RSEQ)) { _handle_seq_block(); } else { _RYML_CB_ASSERT(m_evt_handler->m_stack.m_callbacks, has_all(RMAP)); _handle_map_block(); } } else if(has_any(RUNK)) { _handle_unk(); } else if(has_any(USTY)) { _handle_usty(); } else { _c4err("internal error"); } } if(_finished_file()) break; // it may have finished because of multiline blocks _line_ended(); } _end_stream(); m_evt_handler->finish_parse(); } /** @endcond */ } // namespace yml } // namespace c4 // NOLINTEND(hicpp-signed-bitwise,cppcoreguidelines-avoid-goto,hicpp-avoid-goto,hicpp-multiway-paths-covered) #undef _c4dbgnextline #if defined(_MSC_VER) # pragma warning(pop) #elif defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif // _C4_YML_PARSE_ENGINE_DEF_HPP_ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/parse_engine.def.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/reference_resolver.cpp // https://github.com/biojppm/rapidyaml/src/c4/yml/reference_resolver.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef RYML_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/reference_resolver.hpp //#include "c4/yml/reference_resolver.hpp" #if !defined(C4_YML_REFERENCE_RESOLVER_HPP_) && !defined(_C4_YML_REFERENCE_RESOLVER_HPP_) #error "amalgamate: file c4/yml/reference_resolver.hpp must have been included at this point" #endif /* C4_YML_REFERENCE_RESOLVER_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp //#include "c4/yml/common.hpp" #if !defined(C4_YML_COMMON_HPP_) && !defined(_C4_YML_COMMON_HPP_) #error "amalgamate: file c4/yml/common.hpp must have been included at this point" #endif /* C4_YML_COMMON_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //#include "c4/yml/detail/parser_dbg.hpp" #if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) #error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ #ifdef RYML_DBG // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/print.hpp //#include "c4/yml/detail/print.hpp" #if !defined(C4_YML_DETAIL_PRINT_HPP_) && !defined(_C4_YML_DETAIL_PRINT_HPP_) #error "amalgamate: file c4/yml/detail/print.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PRINT_HPP_ */ #else #define _c4dbg_tree(...) #define _c4dbg_node(...) #endif namespace c4 { namespace yml { /** @cond dev */ id_type ReferenceResolver::count_anchors_and_refs_(id_type n) { id_type c = 0; c += m_tree->has_key_anchor(n); c += m_tree->has_val_anchor(n); c += m_tree->is_key_ref(n); c += m_tree->is_val_ref(n); c += m_tree->has_key(n) && m_tree->key(n) == "<<"; for(id_type ch = m_tree->first_child(n); ch != NONE; ch = m_tree->next_sibling(ch)) c += count_anchors_and_refs_(ch); return c; } void ReferenceResolver::gather_anchors_and_refs__(id_type n) { // insert key refs BEFORE inserting val refs if(m_tree->has_key(n)) { if(m_tree->key(n) == "<<") { _c4dbgpf("node[{}]: key is <<", n); if(m_tree->has_val(n)) { if(m_tree->is_val_ref(n)) { _c4dbgpf("node[{}]: instance[{}]: val ref, inheriting! '{}'", n, m_refs.size(), m_tree->val_ref(n)); m_refs.push({VALREF, n, NONE, NONE, NONE, NONE}); //m_refs.push({KEYREF, n, NONE, NONE, NONE, NONE}); } else { _c4dbgpf("node[{}]: not ref!", n); } } else if(m_tree->is_seq(n)) { // for merging multiple inheritance targets // <<: [ *CENTER, *BIG ] _c4dbgpf("node[{}]: is seq!", n); for(id_type ich = m_tree->first_child(n); ich != NONE; ich = m_tree->next_sibling(ich)) { _c4dbgpf("node[{}]: instance [{}]: val ref, inheriting multiple: {} '{}'", n, m_refs.size(), ich, m_tree->val_ref(ich)); if(m_tree->is_container(ich)) { detail::_report_err(m_tree->m_callbacks, "ERROR: node {} child {}: refs for << cannot be containers.'", n, ich); C4_UNREACHABLE_AFTER_ERR(); } m_refs.push({VALREF, ich, NONE, NONE, n, m_tree->next_sibling(n)}); } return; // don't descend into the seq } else { detail::_report_err(m_tree->m_callbacks, "ERROR: node {}: refs for << must be either val or seq", n); C4_UNREACHABLE_AFTER_ERR(); } } else if(m_tree->is_key_ref(n)) { _c4dbgpf("node[{}]: instance[{}]: key ref: '{}', key='{}'", n, m_refs.size(), m_tree->key_ref(n), m_tree->has_key(n) ? m_tree->key(n) : csubstr{"-"}); _RYML_CB_ASSERT(m_tree->m_callbacks, m_tree->key(n) != "<<"); _RYML_CB_CHECK(m_tree->m_callbacks, (!m_tree->has_key(n)) || m_tree->key(n).ends_with(m_tree->key_ref(n))); m_refs.push({KEYREF, n, NONE, NONE, NONE, NONE}); } } // val ref if(m_tree->is_val_ref(n) && (!m_tree->has_key(n) || m_tree->key(n) != "<<")) { _c4dbgpf("node[{}]: instance[{}]: val ref: '{}'", n, m_refs.size(), m_tree->val_ref(n)); RYML_CHECK((!m_tree->has_val(n)) || m_tree->val(n).ends_with(m_tree->val_ref(n))); m_refs.push({VALREF, n, NONE, NONE, NONE, NONE}); } // anchors if(m_tree->has_key_anchor(n)) { _c4dbgpf("node[{}]: instance[{}]: key anchor: '{}'", n, m_refs.size(), m_tree->key_anchor(n)); RYML_CHECK(m_tree->has_key(n)); m_refs.push({KEYANCH, n, NONE, NONE, NONE, NONE}); } if(m_tree->has_val_anchor(n)) { _c4dbgpf("node[{}]: instance[{}]: val anchor: '{}'", n, m_refs.size(), m_tree->val_anchor(n)); RYML_CHECK(m_tree->has_val(n) || m_tree->is_container(n)); m_refs.push({VALANCH, n, NONE, NONE, NONE, NONE}); } // recurse for(id_type ch = m_tree->first_child(n); ch != NONE; ch = m_tree->next_sibling(ch)) gather_anchors_and_refs__(ch); } void ReferenceResolver::gather_anchors_and_refs_() { _c4dbgp("gathering anchors and refs..."); // minimize (re-)allocations by counting first id_type num_anchors_and_refs = count_anchors_and_refs_(m_tree->root_id()); if(!num_anchors_and_refs) return; m_refs.reserve(num_anchors_and_refs); m_refs.clear(); // now descend through the hierarchy gather_anchors_and_refs__(m_tree->root_id()); _c4dbgpf("found {} anchors/refs", m_refs.size()); // finally connect the reference list id_type prev_anchor = NONE; id_type count = 0; for(auto &rd : m_refs) { rd.prev_anchor = prev_anchor; if(rd.type.has_anchor()) prev_anchor = count; ++count; } _c4dbgp("gathering anchors and refs: finished"); } id_type ReferenceResolver::lookup_(RefData const* C4_RESTRICT ra) { #ifdef RYML_DBG id_type instance = static_cast(ra-m_refs.m_stack); id_type node = ra->node; #endif RYML_ASSERT(ra->type.is_key_ref() || ra->type.is_val_ref()); RYML_ASSERT(ra->type.is_key_ref() != ra->type.is_val_ref()); csubstr refname; _c4dbgpf("instance[{}:node{}]: lookup from node={}...", instance, node, ra->node); if(ra->type.is_val_ref()) { refname = m_tree->val_ref(ra->node); _c4dbgpf("instance[{}:node{}]: valref: '{}'", instance, node, refname); } else { RYML_ASSERT(ra->type.is_key_ref()); refname = m_tree->key_ref(ra->node); _c4dbgpf("instance[{}:node{}]: keyref: '{}'", instance, node, refname); } while(ra->prev_anchor != NONE) { ra = &m_refs[ra->prev_anchor]; _c4dbgpf("instance[{}:node{}]: lookup '{}' at [{}:node{}]: keyref='{}' valref='{}'", instance, node, refname, ra-m_refs.m_stack, ra->node, (m_tree->has_key_anchor(ra->node) ? m_tree->key_anchor(ra->node) : csubstr("~")), (m_tree->has_val_anchor(ra->node) ? m_tree->val_anchor(ra->node) : csubstr("~"))); if(m_tree->has_anchor(ra->node, refname)) { _c4dbgpf("instance[{}:node{}]: got it at [{}:node{}]!", instance, node, ra-m_refs.m_stack, ra->node); return ra->node; } } detail::_report_err(m_tree->m_callbacks, "ERROR: anchor not found: '{}'", refname); C4_UNREACHABLE_AFTER_ERR(); } void ReferenceResolver::reset_(Tree *t_) { if(t_->callbacks() != m_refs.m_callbacks) { m_refs.m_callbacks = t_->callbacks(); } m_tree = t_; m_refs.clear(); } void ReferenceResolver::resolve_() { /* from the specs: "an alias node refers to the most recent * node in the serialization having the specified anchor". So * we need to start looking upward from ref nodes. * * @see http://yaml.org/spec/1.2/spec.html#id2765878 */ _c4dbgp("matching anchors/refs..."); for(id_type i = 0, e = m_refs.size(); i < e; ++i) { RefData &C4_RESTRICT refdata = m_refs.top(i); if( ! refdata.type.is_ref()) continue; refdata.target = lookup_(&refdata); } _c4dbgp("matching anchors/refs: finished"); // insert the resolved references _c4dbgp("modifying tree..."); id_type prev_parent_ref = NONE; id_type prev_parent_ref_after = NONE; for(id_type i = 0, e = m_refs.size(); i < e; ++i) { RefData const& C4_RESTRICT refdata = m_refs[i]; _c4dbgpf("instance[{}:node{}]: {}/{}...", i, refdata.node, i+1, e); if( ! refdata.type.is_ref()) continue; _c4dbgpf("instance[{}:node{}]: is reference!", i, refdata.node); if(refdata.parent_ref != NONE) { _c4dbgpf("instance[{}:node{}] has parent: {}", i, refdata.node, refdata.parent_ref); _RYML_CB_ASSERT(m_tree->m_callbacks, m_tree->is_seq(refdata.parent_ref)); const id_type p = m_tree->parent(refdata.parent_ref); const id_type after = (prev_parent_ref != refdata.parent_ref) ? refdata.parent_ref//prev_sibling(rd.parent_ref_sibling) : prev_parent_ref_after; prev_parent_ref = refdata.parent_ref; prev_parent_ref_after = m_tree->duplicate_children_no_rep(refdata.target, p, after); m_tree->remove(refdata.node); } else { _c4dbgpf("instance[{}:node{}] has no parent", i, refdata.node, refdata.parent_ref); if(m_tree->has_key(refdata.node) && m_tree->key(refdata.node) == "<<") { _c4dbgpf("instance[{}:node{}] is inheriting", i, refdata.node); _RYML_CB_ASSERT(m_tree->m_callbacks, m_tree->is_keyval(refdata.node)); const id_type p = m_tree->parent(refdata.node); const id_type after = m_tree->prev_sibling(refdata.node); _c4dbgpf("instance[{}:node{}] p={} after={}", i, refdata.node, p, after); m_tree->duplicate_children_no_rep(refdata.target, p, after); m_tree->remove(refdata.node); } else if(refdata.type.is_key_ref()) { _c4dbgpf("instance[{}:node{}] is key ref", i, refdata.node); _RYML_CB_ASSERT(m_tree->m_callbacks, m_tree->is_key_ref(refdata.node)); _RYML_CB_ASSERT(m_tree->m_callbacks, m_tree->has_key_anchor(refdata.target) || m_tree->has_val_anchor(refdata.target)); if(m_tree->has_val_anchor(refdata.target) && m_tree->val_anchor(refdata.target) == m_tree->key_ref(refdata.node)) { _c4dbgpf("instance[{}:node{}] target.anchor==val.anchor=={}", i, refdata.node, m_tree->val_anchor(refdata.target)); _RYML_CB_CHECK(m_tree->m_callbacks, !m_tree->is_container(refdata.target)); _RYML_CB_CHECK(m_tree->m_callbacks, m_tree->has_val(refdata.target)); const type_bits existing_style_flags = VAL_STYLE & m_tree->_p(refdata.target)->m_type.type; static_assert((VAL_STYLE >> 1u) == (KEY_STYLE), "bad flags"); m_tree->_p(refdata.node)->m_key.scalar = m_tree->val(refdata.target); m_tree->_add_flags(refdata.node, KEY | (existing_style_flags >> 1u)); } else { _c4dbgpf("instance[{}:node{}] don't inherit container flags", i, refdata.node); _RYML_CB_CHECK(m_tree->m_callbacks, m_tree->key_anchor(refdata.target) == m_tree->key_ref(refdata.node)); m_tree->_p(refdata.node)->m_key.scalar = m_tree->key(refdata.target); // keys cannot be containers, so don't inherit container flags const type_bits existing_style_flags = KEY_STYLE & m_tree->_p(refdata.target)->m_type.type; m_tree->_add_flags(refdata.node, KEY | existing_style_flags); } } else // val ref { _c4dbgpf("instance[{}:node{}] is val ref", i, refdata.node); _RYML_CB_ASSERT(m_tree->m_callbacks, refdata.type.is_val_ref()); if(m_tree->has_key_anchor(refdata.target) && m_tree->key_anchor(refdata.target) == m_tree->val_ref(refdata.node)) { _c4dbgpf("instance[{}:node{}] target.anchor==key.anchor=={}", i, refdata.node, m_tree->val_anchor(refdata.target)); _RYML_CB_CHECK(m_tree->m_callbacks, !m_tree->is_container(refdata.target)); _RYML_CB_CHECK(m_tree->m_callbacks, m_tree->has_val(refdata.target)); // keys cannot be containers, so don't inherit container flags const type_bits existing_style_flags = (KEY_STYLE) & m_tree->_p(refdata.target)->m_type.type; static_assert((KEY_STYLE << 1u) == (VAL_STYLE), "bad flags"); m_tree->_p(refdata.node)->m_val.scalar = m_tree->key(refdata.target); m_tree->_add_flags(refdata.node, VAL | (existing_style_flags << 1u)); } else { _c4dbgpf("instance[{}:node{}] duplicate contents", i, refdata.node); m_tree->duplicate_contents(refdata.target, refdata.node); } } } _c4dbg_tree("after insertion", *m_tree); } } void ReferenceResolver::resolve(Tree *t_, bool clear_anchors) { _c4dbgp("resolving references..."); reset_(t_); _c4dbg_tree("unresolved tree", *m_tree); gather_anchors_and_refs_(); if(m_refs.empty()) return; resolve_(); _c4dbg_tree("resolved tree", *m_tree); // clear anchors and refs if(clear_anchors) { _c4dbgp("clearing anchors/refs"); auto clear_ = [this]{ for(auto const& C4_RESTRICT ar : m_refs) { m_tree->rem_anchor_ref(ar.node); if(ar.parent_ref != NONE) if(m_tree->type(ar.parent_ref) != NOTYPE) m_tree->remove(ar.parent_ref); } }; clear_(); // some of the elements injected during the resolution may // have nested anchors; these anchors will have been newly // injected during the resolution; collect again, and clear // again, to ensure those are also cleared: gather_anchors_and_refs_(); clear_(); _c4dbgp("clearing anchors/refs: finished"); } _c4dbg_tree("final resolved tree", *m_tree); m_tree = nullptr; _c4dbgp("resolving references: finished"); } /** @endcond */ } // namespace ryml } // namespace c4 #endif /* RYML_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/reference_resolver.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/parse.cpp // https://github.com/biojppm/rapidyaml/src/c4/yml/parse.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef RYML_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/parse.hpp //#include "c4/yml/parse.hpp" #if !defined(C4_YML_PARSE_HPP_) && !defined(_C4_YML_PARSE_HPP_) #error "amalgamate: file c4/yml/parse.hpp must have been included at this point" #endif /* C4_YML_PARSE_HPP_ */ #ifndef _C4_YML_NODE_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp //#include "c4/yml/node.hpp" #if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) #error "amalgamate: file c4/yml/node.hpp must have been included at this point" #endif /* C4_YML_NODE_HPP_ */ #endif #ifndef _C4_YML_PARSE_ENGINE_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/parse_engine.hpp //#include "c4/yml/parse_engine.hpp" #if !defined(C4_YML_PARSE_ENGINE_HPP_) && !defined(_C4_YML_PARSE_ENGINE_HPP_) #error "amalgamate: file c4/yml/parse_engine.hpp must have been included at this point" #endif /* C4_YML_PARSE_ENGINE_HPP_ */ #endif #ifndef _C4_YML_PARSE_ENGINE_DEF_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/parse_engine.def.hpp //#include "c4/yml/parse_engine.def.hpp" #if !defined(C4_YML_PARSE_ENGINE_DEF_HPP_) && !defined(_C4_YML_PARSE_ENGINE_DEF_HPP_) #error "amalgamate: file c4/yml/parse_engine.def.hpp must have been included at this point" #endif /* C4_YML_PARSE_ENGINE_DEF_HPP_ */ #endif #ifndef _C4_YML_EVENT_HANDLER_TREE_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/event_handler_tree.hpp //#include "c4/yml/event_handler_tree.hpp" #if !defined(C4_YML_EVENT_HANDLER_TREE_HPP_) && !defined(_C4_YML_EVENT_HANDLER_TREE_HPP_) #error "amalgamate: file c4/yml/event_handler_tree.hpp must have been included at this point" #endif /* C4_YML_EVENT_HANDLER_TREE_HPP_ */ #endif //----------------------------------------------------------------------------- namespace c4 { namespace yml { // instantiate the parser class template class ParseEngine; namespace { inline void _reset_tree_handler(Parser *parser, Tree *t, id_type node_id) { RYML_ASSERT(parser); RYML_ASSERT(t); if(!parser->m_evt_handler) _RYML_CB_ERR(t->m_callbacks, "event handler is not set"); parser->m_evt_handler->reset(t, node_id); RYML_ASSERT(parser->m_evt_handler->m_tree == t); } } // namespace void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *t, id_type node_id) { _reset_tree_handler(parser, t, node_id); parser->parse_in_place_ev(filename, yaml); } void parse_json_in_place(Parser *parser, csubstr filename, substr json, Tree *t, id_type node_id) { _reset_tree_handler(parser, t, node_id); parser->parse_json_in_place_ev(filename, json); } // this is vertically aligned to highlight the parameter differences. void parse_in_place(Parser *parser, substr yaml, Tree *t, id_type node_id) { parse_in_place(parser, {}, yaml, t, node_id); } void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *t ) { RYML_CHECK(t); parse_in_place(parser, filename, yaml, t, t->root_id()); } void parse_in_place(Parser *parser, substr yaml, Tree *t ) { RYML_CHECK(t); parse_in_place(parser, {} , yaml, t, t->root_id()); } void parse_in_place(Parser *parser, csubstr filename, substr yaml, NodeRef node ) { RYML_CHECK(!node.invalid()); parse_in_place(parser, filename, yaml, node.tree(), node.id()); } void parse_in_place(Parser *parser, substr yaml, NodeRef node ) { RYML_CHECK(!node.invalid()); parse_in_place(parser, {} , yaml, node.tree(), node.id()); } Tree parse_in_place(Parser *parser, csubstr filename, substr yaml ) { RYML_CHECK(parser); RYML_CHECK(parser->m_evt_handler); Tree tree(parser->callbacks()); parse_in_place(parser, filename, yaml, &tree, tree.root_id()); return tree; } Tree parse_in_place(Parser *parser, substr yaml ) { RYML_CHECK(parser); RYML_CHECK(parser->m_evt_handler); Tree tree(parser->callbacks()); parse_in_place(parser, {} , yaml, &tree, tree.root_id()); return tree; } // this is vertically aligned to highlight the parameter differences. void parse_in_place(csubstr filename, substr yaml, Tree *t, id_type node_id) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); parse_in_place(&parser, filename, yaml, t, node_id); } void parse_in_place( substr yaml, Tree *t, id_type node_id) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); parse_in_place(&parser, {} , yaml, t, node_id); } void parse_in_place(csubstr filename, substr yaml, Tree *t ) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); parse_in_place(&parser, filename, yaml, t, t->root_id()); } void parse_in_place( substr yaml, Tree *t ) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); parse_in_place(&parser, {} , yaml, t, t->root_id()); } void parse_in_place(csubstr filename, substr yaml, NodeRef node ) { RYML_CHECK(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler); parse_in_place(&parser, filename, yaml, node.tree(), node.id()); } void parse_in_place( substr yaml, NodeRef node ) { RYML_CHECK(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler); parse_in_place(&parser, {} , yaml, node.tree(), node.id()); } Tree parse_in_place(csubstr filename, substr yaml ) { Parser::handler_type event_handler; Parser parser(&event_handler); Tree tree(parser.callbacks()); parse_in_place(&parser, filename, yaml, &tree, tree.root_id()); return tree; } Tree parse_in_place( substr yaml ) { Parser::handler_type event_handler; Parser parser(&event_handler); Tree tree(parser.callbacks()); parse_in_place(&parser, {} , yaml, &tree, tree.root_id()); return tree; } // this is vertically aligned to highlight the parameter differences. void parse_json_in_place(Parser *parser, substr json, Tree *t, id_type node_id) { parse_json_in_place(parser, {}, json, t, node_id); } void parse_json_in_place(Parser *parser, csubstr filename, substr json, Tree *t ) { RYML_CHECK(t); parse_json_in_place(parser, filename, json, t, t->root_id()); } void parse_json_in_place(Parser *parser, substr json, Tree *t ) { RYML_CHECK(t); parse_json_in_place(parser, {} , json, t, t->root_id()); } void parse_json_in_place(Parser *parser, csubstr filename, substr json, NodeRef node ) { RYML_CHECK(!node.invalid()); parse_json_in_place(parser, filename, json, node.tree(), node.id()); } void parse_json_in_place(Parser *parser, substr json, NodeRef node ) { RYML_CHECK(!node.invalid()); parse_json_in_place(parser, {} , json, node.tree(), node.id()); } Tree parse_json_in_place(Parser *parser, csubstr filename, substr json ) { RYML_CHECK(parser); RYML_CHECK(parser->m_evt_handler); Tree tree(parser->callbacks()); parse_json_in_place(parser, filename, json, &tree, tree.root_id()); return tree; } Tree parse_json_in_place(Parser *parser, substr json ) { RYML_CHECK(parser); RYML_CHECK(parser->m_evt_handler); Tree tree(parser->callbacks()); parse_json_in_place(parser, {} , json, &tree, tree.root_id()); return tree; } // this is vertically aligned to highlight the parameter differences. void parse_json_in_place(csubstr filename, substr json, Tree *t, id_type node_id) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); parse_json_in_place(&parser, filename, json, t, node_id); } void parse_json_in_place( substr json, Tree *t, id_type node_id) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); parse_json_in_place(&parser, {} , json, t, node_id); } void parse_json_in_place(csubstr filename, substr json, Tree *t ) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); parse_json_in_place(&parser, filename, json, t, t->root_id()); } void parse_json_in_place( substr json, Tree *t ) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); parse_json_in_place(&parser, {} , json, t, t->root_id()); } void parse_json_in_place(csubstr filename, substr json, NodeRef node ) { RYML_CHECK(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler); parse_json_in_place(&parser, filename, json, node.tree(), node.id()); } void parse_json_in_place( substr json, NodeRef node ) { RYML_CHECK(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler); parse_json_in_place(&parser, {} , json, node.tree(), node.id()); } Tree parse_json_in_place(csubstr filename, substr json ) { Parser::handler_type event_handler; Parser parser(&event_handler); Tree tree(parser.callbacks()); parse_json_in_place(&parser, filename, json, &tree, tree.root_id()); return tree; } Tree parse_json_in_place( substr json ) { Parser::handler_type event_handler; Parser parser(&event_handler); Tree tree(parser.callbacks()); parse_json_in_place(&parser, {} , json, &tree, tree.root_id()); return tree; } // this is vertically aligned to highlight the parameter differences. void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *t, id_type node_id) { RYML_CHECK(t); substr src = t->copy_to_arena(yaml); parse_in_place(parser, filename, src, t, node_id); } void parse_in_arena(Parser *parser, csubstr yaml, Tree *t, id_type node_id) { RYML_CHECK(t); substr src = t->copy_to_arena(yaml); parse_in_place(parser, {} , src, t, node_id); } void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *t ) { RYML_CHECK(t); substr src = t->copy_to_arena(yaml); parse_in_place(parser, filename, src, t, t->root_id()); } void parse_in_arena(Parser *parser, csubstr yaml, Tree *t ) { RYML_CHECK(t); substr src = t->copy_to_arena(yaml); parse_in_place(parser, {} , src, t, t->root_id()); } void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, NodeRef node ) { RYML_CHECK(!node.invalid()); substr src = node.tree()->copy_to_arena(yaml); parse_in_place(parser, filename, src, node.tree(), node.id()); } void parse_in_arena(Parser *parser, csubstr yaml, NodeRef node ) { RYML_CHECK(!node.invalid()); substr src = node.tree()->copy_to_arena(yaml); parse_in_place(parser, {} , src, node.tree(), node.id()); } Tree parse_in_arena(Parser *parser, csubstr filename, csubstr yaml ) { RYML_CHECK(parser); RYML_CHECK(parser->m_evt_handler); Tree tree(parser->callbacks()); substr src = tree.copy_to_arena(yaml); parse_in_place(parser, filename, src, &tree, tree.root_id()); return tree; } Tree parse_in_arena(Parser *parser, csubstr yaml ) { RYML_CHECK(parser); RYML_CHECK(parser->m_evt_handler); Tree tree(parser->callbacks()); substr src = tree.copy_to_arena(yaml); parse_in_place(parser, {} , src, &tree, tree.root_id()); return tree; } // this is vertically aligned to highlight the parameter differences. void parse_in_arena(csubstr filename, csubstr yaml, Tree *t, id_type node_id) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); substr src = t->copy_to_arena(yaml); parse_in_place(&parser, filename, src, t, node_id); } void parse_in_arena( csubstr yaml, Tree *t, id_type node_id) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); substr src = t->copy_to_arena(yaml); parse_in_place(&parser, {} , src, t, node_id); } void parse_in_arena(csubstr filename, csubstr yaml, Tree *t ) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); substr src = t->copy_to_arena(yaml); parse_in_place(&parser, filename, src, t, t->root_id()); } void parse_in_arena( csubstr yaml, Tree *t ) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); substr src = t->copy_to_arena(yaml); parse_in_place(&parser, {} , src, t, t->root_id()); } void parse_in_arena(csubstr filename, csubstr yaml, NodeRef node ) { RYML_CHECK(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler); substr src = node.tree()->copy_to_arena(yaml); parse_in_place(&parser, filename, src, node.tree(), node.id()); } void parse_in_arena( csubstr yaml, NodeRef node ) { RYML_CHECK(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler); substr src = node.tree()->copy_to_arena(yaml); parse_in_place(&parser, {} , src, node.tree(), node.id()); } Tree parse_in_arena(csubstr filename, csubstr yaml ) { Parser::handler_type event_handler; Parser parser(&event_handler); Tree tree(parser.callbacks()); substr src = tree.copy_to_arena(yaml); parse_in_place(&parser, filename, src, &tree, tree.root_id()); return tree; } Tree parse_in_arena( csubstr yaml ) { Parser::handler_type event_handler; Parser parser(&event_handler); Tree tree(parser.callbacks()); substr src = tree.copy_to_arena(yaml); parse_in_place(&parser, {} , src, &tree, tree.root_id()); return tree; } // this is vertically aligned to highlight the parameter differences. void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t, id_type node_id) { RYML_CHECK(t); substr src = t->copy_to_arena(json); parse_json_in_place(parser, filename, src, t, node_id); } void parse_json_in_arena(Parser *parser, csubstr json, Tree *t, id_type node_id) { RYML_CHECK(t); substr src = t->copy_to_arena(json); parse_json_in_place(parser, {} , src, t, node_id); } void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t ) { RYML_CHECK(t); substr src = t->copy_to_arena(json); parse_json_in_place(parser, filename, src, t, t->root_id()); } void parse_json_in_arena(Parser *parser, csubstr json, Tree *t ) { RYML_CHECK(t); substr src = t->copy_to_arena(json); parse_json_in_place(parser, {} , src, t, t->root_id()); } void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, NodeRef node ) { RYML_CHECK(!node.invalid()); substr src = node.tree()->copy_to_arena(json); parse_json_in_place(parser, filename, src, node.tree(), node.id()); } void parse_json_in_arena(Parser *parser, csubstr json, NodeRef node ) { RYML_CHECK(!node.invalid()); substr src = node.tree()->copy_to_arena(json); parse_json_in_place(parser, {} , src, node.tree(), node.id()); } Tree parse_json_in_arena(Parser *parser, csubstr filename, csubstr json ) { RYML_CHECK(parser); RYML_CHECK(parser->m_evt_handler); Tree tree(parser->callbacks()); substr src = tree.copy_to_arena(json); parse_json_in_place(parser, filename, src, &tree, tree.root_id()); return tree; } Tree parse_json_in_arena(Parser *parser, csubstr json ) { RYML_CHECK(parser); RYML_CHECK(parser->m_evt_handler); Tree tree(parser->callbacks()); substr src = tree.copy_to_arena(json); parse_json_in_place(parser, {} , src, &tree, tree.root_id()); return tree; } // this is vertically aligned to highlight the parameter differences. void parse_json_in_arena(csubstr filename, csubstr json, Tree *t, id_type node_id) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); substr src = t->copy_to_arena(json); parse_json_in_place(&parser, filename, src, t, node_id); } void parse_json_in_arena( csubstr json, Tree *t, id_type node_id) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); substr src = t->copy_to_arena(json); parse_json_in_place(&parser, {} , src, t, node_id); } void parse_json_in_arena(csubstr filename, csubstr json, Tree *t ) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); substr src = t->copy_to_arena(json); parse_json_in_place(&parser, filename, src, t, t->root_id()); } void parse_json_in_arena( csubstr json, Tree *t ) { RYML_CHECK(t); Parser::handler_type event_handler(t->callbacks()); Parser parser(&event_handler); substr src = t->copy_to_arena(json); parse_json_in_place(&parser, {} , src, t, t->root_id()); } void parse_json_in_arena(csubstr filename, csubstr json, NodeRef node ) { RYML_CHECK(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler); substr src = node.tree()->copy_to_arena(json); parse_json_in_place(&parser, filename, src, node.tree(), node.id()); } void parse_json_in_arena( csubstr json, NodeRef node ) { RYML_CHECK(!node.invalid()); Parser::handler_type event_handler(node.tree()->callbacks()); Parser parser(&event_handler); substr src = node.tree()->copy_to_arena(json); parse_json_in_place(&parser, {} , src, node.tree(), node.id()); } Tree parse_json_in_arena(csubstr filename, csubstr json ) { Parser::handler_type event_handler; Parser parser(&event_handler); Tree tree(parser.callbacks()); substr src = tree.copy_to_arena(json); parse_json_in_place(&parser, filename, src, &tree, tree.root_id()); return tree; } Tree parse_json_in_arena( csubstr json ) { Parser::handler_type event_handler; Parser parser(&event_handler); Tree tree(parser.callbacks()); substr src = tree.copy_to_arena(json); parse_json_in_place(&parser, {} , src, &tree, tree.root_id()); return tree; } RYML_EXPORT C4_NO_INLINE size_t _find_last_newline_and_larger_indentation(csubstr s, size_t indentation) noexcept { if(indentation + 1 > s.len) return npos; for(size_t i = s.len-indentation-1; i != size_t(-1); --i) { if(s.str[i] == '\n') { csubstr rem = s.sub(i + 1); size_t first = rem.first_not_of(' '); first = (first != npos) ? first : rem.len; if(first > indentation) return i; } } return npos; } //----------------------------------------------------------------------------- RYML_EXPORT id_type estimate_tree_capacity(csubstr src) { id_type num_nodes = 1; // root for(size_t i = 0; i < src.len; ++i) { const char c = src.str[i]; num_nodes += (c == '\n') || (c == ',') || (c == '[') || (c == '{'); } return num_nodes; } } // namespace yml } // namespace c4 #endif /* RYML_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/parse.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/node.cpp // https://github.com/biojppm/rapidyaml/src/c4/yml/node.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef RYML_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp //#include "c4/yml/node.hpp" #if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) #error "amalgamate: file c4/yml/node.hpp must have been included at this point" #endif /* C4_YML_NODE_HPP_ */ namespace c4 { namespace yml { //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- size_t NodeRef::set_key_serialized(c4::fmt::const_base64_wrapper w) { _apply_seed(); csubstr encoded = this->to_arena(w); this->set_key(encoded); return encoded.len; } size_t NodeRef::set_val_serialized(c4::fmt::const_base64_wrapper w) { _apply_seed(); csubstr encoded = this->to_arena(w); this->set_val(encoded); return encoded.len; } } // namespace yml } // namespace c4 #endif /* RYML_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/node.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/preprocess.cpp // https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.cpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifdef RYML_SINGLE_HDR_DEFINE_NOW // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.hpp //#include "c4/yml/preprocess.hpp" #if !defined(C4_YML_PREPROCESS_HPP_) && !defined(_C4_YML_PREPROCESS_HPP_) #error "amalgamate: file c4/yml/preprocess.hpp must have been included at this point" #endif /* C4_YML_PREPROCESS_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/parser_dbg.hpp //#include "c4/yml/detail/parser_dbg.hpp" #if !defined(C4_YML_DETAIL_PARSER_DBG_HPP_) && !defined(_C4_YML_DETAIL_PARSER_DBG_HPP_) #error "amalgamate: file c4/yml/detail/parser_dbg.hpp must have been included at this point" #endif /* C4_YML_DETAIL_PARSER_DBG_HPP_ */ /** @file preprocess.hpp Functions for preprocessing YAML prior to parsing. */ namespace c4 { namespace yml { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- namespace { C4_ALWAYS_INLINE bool _is_idchar(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_' || c == '-' || c == '~' || c == '$'); } enum _ppstate : int { kReadPending = 0, kKeyPending = 1, kValPending = 2 }; C4_ALWAYS_INLINE _ppstate _next(_ppstate s) { int n = (int)s + 1; return (_ppstate)(n <= (int)kValPending ? n : 0); } } // empty namespace //----------------------------------------------------------------------------- size_t preprocess_rxmap(csubstr s, substr buf) { detail::_SubstrWriter writer(buf); _ppstate state = kReadPending; size_t last = 0; if(s.begins_with('{')) { RYML_CHECK(s.ends_with('}')); s = s.offs(1, 1); } writer.append('{'); for(size_t i = 0; i < s.len; ++i) { const char curr = s[i]; const char next = i+1 < s.len ? s[i+1] : '\0'; if(curr == '\'' || curr == '"') { csubstr ss = s.sub(i).pair_range_esc(curr, '\\'); i += static_cast(ss.end() - (s.str + i)); state = _next(state); } else if(state == kReadPending && _is_idchar(curr)) { state = _next(state); } switch(state) { case kKeyPending: { if(curr == ':' && next == ' ') { state = _next(state); } else if(curr == ',' && next == ' ') { writer.append(s.range(last, i)); writer.append(": 1, "); last = i + 2; } break; } case kValPending: { if(curr == '[' || curr == '{' || curr == '(') { csubstr ss = s.sub(i).pair_range_nested(curr, '\\'); i += static_cast(ss.end() - (s.str + i)); state = _next(state); } else if(curr == ',' && next == ' ') { state = _next(state); } break; } default: // nothing to do break; } } writer.append(s.sub(last)); if(state == kKeyPending) writer.append(": 1"); writer.append('}'); return writer.pos; } C4_SUPPRESS_WARNING_GCC_CLANG_POP } // namespace yml } // namespace c4 #endif /* RYML_SINGLE_HDR_DEFINE_NOW */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.cpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/detail/checks.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/checks.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef C4_YML_DETAIL_CHECKS_HPP_ #define C4_YML_DETAIL_CHECKS_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp //#include "c4/yml/tree.hpp" #if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) #error "amalgamate: file c4/yml/tree.hpp must have been included at this point" #endif /* C4_YML_TREE_HPP_ */ #ifdef __clang__ # pragma clang diagnostic push #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wtype-limits" // error: comparison of unsigned expression >= 0 is always true #elif defined(_MSC_VER) # pragma warning(push) # pragma warning(disable: 4296/*expression is always 'boolean_value'*/) #endif namespace c4 { namespace yml { void check_invariants(Tree const& t, id_type node=NONE); void check_free_list(Tree const& t); void check_arena(Tree const& t); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- inline void check_invariants(Tree const& t, id_type node) { if(node == NONE) { if(t.empty()) return; node = t.root_id(); } NodeData const& n = *t._p(node); #if defined(RYML_DBG) && 0 if(n.m_first_child != NONE || n.m_last_child != NONE) { printf("check(%zu): fc=%zu lc=%zu\n", node, n.m_first_child, n.m_last_child); } else { printf("check(%zu)\n", node); } #endif C4_CHECK(n.m_parent != node); if(n.m_parent == NONE) { C4_CHECK(t.is_root(node)); } else //if(n.m_parent != NONE) { C4_CHECK(t.has_child(n.m_parent, node)); auto const& p = *t._p(n.m_parent); if(n.m_prev_sibling == NONE) { C4_CHECK(p.m_first_child == node); C4_CHECK(t.first_sibling(node) == node); } else { C4_CHECK(p.m_first_child != node); C4_CHECK(t.first_sibling(node) != node); } if(n.m_next_sibling == NONE) { C4_CHECK(p.m_last_child == node); C4_CHECK(t.last_sibling(node) == node); } else { C4_CHECK(p.m_last_child != node); C4_CHECK(t.last_sibling(node) != node); } } C4_CHECK(n.m_first_child != node); C4_CHECK(n.m_last_child != node); if(n.m_first_child != NONE || n.m_last_child != NONE) { C4_CHECK(n.m_first_child != NONE); C4_CHECK(n.m_last_child != NONE); } C4_CHECK(n.m_prev_sibling != node); C4_CHECK(n.m_next_sibling != node); if(n.m_prev_sibling != NONE) { C4_CHECK(t._p(n.m_prev_sibling)->m_next_sibling == node); C4_CHECK(t._p(n.m_prev_sibling)->m_prev_sibling != node); } if(n.m_next_sibling != NONE) { C4_CHECK(t._p(n.m_next_sibling)->m_prev_sibling == node); C4_CHECK(t._p(n.m_next_sibling)->m_next_sibling != node); } id_type count = 0; for(id_type i = n.m_first_child; i != NONE; i = t.next_sibling(i)) { #if defined(RYML_DBG) && 0 printf("check(%zu): descend to child[%zu]=%zu\n", node, count, i); #endif auto const& ch = *t._p(i); C4_CHECK(ch.m_parent == node); C4_CHECK(ch.m_next_sibling != i); ++count; } C4_CHECK(count == t.num_children(node)); if(n.m_prev_sibling == NONE && n.m_next_sibling == NONE) { if(n.m_parent != NONE) { C4_CHECK(t.num_children(n.m_parent) == 1); C4_CHECK(t.num_siblings(node) == 1); } } if(node == t.root_id()) { C4_CHECK(t.size() == t.m_size); C4_CHECK(t.capacity() == t.m_cap); C4_CHECK(t.m_cap == t.m_size + t.slack()); check_free_list(t); check_arena(t); } for(id_type i = t.first_child(node); i != NONE; i = t.next_sibling(i)) { check_invariants(t, i); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- inline void check_free_list(Tree const& t) { if(t.m_free_head == NONE) { C4_CHECK(t.m_free_tail == t.m_free_head); return; } C4_CHECK(t.m_free_head >= 0 && t.m_free_head < t.m_cap); C4_CHECK(t.m_free_tail >= 0 && t.m_free_tail < t.m_cap); auto const& head = *t._p(t.m_free_head); //auto const& tail = *t._p(t.m_free_tail); //C4_CHECK(head.m_prev_sibling == NONE); //C4_CHECK(tail.m_next_sibling == NONE); id_type count = 0; for(id_type i = t.m_free_head, prev = NONE; i != NONE; i = t._p(i)->m_next_sibling) { auto const& elm = *t._p(i); if(&elm != &head) { C4_CHECK(elm.m_prev_sibling == prev); } prev = i; ++count; } C4_CHECK(count == t.slack()); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- inline void check_arena(Tree const& t) { C4_CHECK(t.m_arena.len == 0 || (t.m_arena_pos >= 0 && t.m_arena_pos <= t.m_arena.len)); C4_CHECK(t.arena_size() == t.m_arena_pos); C4_CHECK(t.arena_slack() + t.m_arena_pos == t.m_arena.len); } } /* namespace yml */ } /* namespace c4 */ #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #elif defined(_MSC_VER) # pragma warning(pop) #endif #endif /* C4_YML_DETAIL_CHECKS_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/detail/checks.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/detail/print.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/detail/print.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef C4_YML_DETAIL_PRINT_HPP_ #define C4_YML_DETAIL_PRINT_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp //#include "c4/yml/tree.hpp" #if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) #error "amalgamate: file c4/yml/tree.hpp must have been included at this point" #endif /* C4_YML_TREE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp //#include "c4/yml/node.hpp" #if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) #error "amalgamate: file c4/yml/node.hpp must have been included at this point" #endif /* C4_YML_NODE_HPP_ */ #ifdef RYML_DBG #define _c4dbg_tree(...) print_tree(__VA_ARGS__) #define _c4dbg_node(...) print_tree(__VA_ARGS__) #else #define _c4dbg_tree(...) #define _c4dbg_node(...) #endif namespace c4 { namespace yml { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wold-style-cast") C4_SUPPRESS_WARNING_GCC("-Wuseless-cast") inline const char* _container_style_code(Tree const& p, id_type node) { if(p.is_container(node)) { if(p._p(node)->m_type & (FLOW_SL|FLOW_ML)) { return "[FLOW]"; } if(p._p(node)->m_type & (BLOCK)) { return "[BLCK]"; } } return ""; } inline char _scalar_code(NodeType masked) { if(masked & (KEY_LITERAL|VAL_LITERAL)) return '|'; if(masked & (KEY_FOLDED|VAL_FOLDED)) return '>'; if(masked & (KEY_SQUO|VAL_SQUO)) return '\''; if(masked & (KEY_DQUO|VAL_DQUO)) return '"'; if(masked & (KEY_PLAIN|VAL_PLAIN)) return '~'; return '@'; } inline char _scalar_code_key(NodeType t) { return _scalar_code(t & KEY_STYLE); } inline char _scalar_code_val(NodeType t) { return _scalar_code(t & VAL_STYLE); } inline char _scalar_code_key(Tree const& p, id_type node) { return _scalar_code_key(p._p(node)->m_type); } inline char _scalar_code_val(Tree const& p, id_type node) { return _scalar_code_key(p._p(node)->m_type); } inline id_type print_node(Tree const& p, id_type node, int level, id_type count, bool print_children) { printf("[%zu]%*s[%zu] %p", (size_t)count, (2*level), "", (size_t)node, (void const*)p.get(node)); if(p.is_root(node)) { printf(" [ROOT]"); } char typebuf[128]; csubstr typestr = p.type(node).type_str(typebuf); RYML_CHECK(typestr.str); printf(" %.*s", (int)typestr.len, typestr.str); if(p.has_key(node)) { if(p.has_key_anchor(node)) { csubstr ka = p.key_anchor(node); printf(" &%.*s", (int)ka.len, ka.str); } if(p.has_key_tag(node)) { csubstr kt = p.key_tag(node); printf(" <%.*s>", (int)kt.len, kt.str); } const char code = _scalar_code_key(p, node); csubstr k = p.key(node); printf(" %c%.*s%c :", code, (int)k.len, k.str, code); } if(p.has_val_anchor(node)) { csubstr a = p.val_anchor(node); printf(" &%.*s'", (int)a.len, a.str); } if(p.has_val_tag(node)) { csubstr vt = p.val_tag(node); printf(" <%.*s>", (int)vt.len, vt.str); } if(p.has_val(node)) { const char code = _scalar_code_val(p, node); csubstr v = p.val(node); printf(" %c%.*s%c", code, (int)v.len, v.str, code); } printf(" (%zu sibs)", (size_t)p.num_siblings(node)); ++count; if(!p.is_container(node)) { printf("\n"); } else { printf(" (%zu children)\n", (size_t)p.num_children(node)); if(print_children) { for(id_type i = p.first_child(node); i != NONE; i = p.next_sibling(i)) { count = print_node(p, i, level+1, count, print_children); } } } return count; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- inline void print_node(ConstNodeRef const& p, int level=0) { print_node(*p.tree(), p.id(), level, 0, true); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- inline id_type print_tree(const char *message, Tree const& p, id_type node=NONE) { printf("--------------------------------------\n"); if(message != nullptr) printf("%s:\n", message); id_type ret = 0; if(!p.empty()) { if(node == NONE) node = p.root_id(); ret = print_node(p, node, 0, 0, true); } printf("#nodes=%zu vs #printed=%zu\n", (size_t)p.size(), (size_t)ret); printf("--------------------------------------\n"); return ret; } inline id_type print_tree(Tree const& p, id_type node=NONE) { return print_tree(nullptr, p, node); } inline void print_tree(ConstNodeRef const& p, int level) { print_node(p, level); for(ConstNodeRef ch : p.children()) { print_tree(ch, level+1); } } C4_SUPPRESS_WARNING_GCC_CLANG_POP } /* namespace yml */ } /* namespace c4 */ #endif /* C4_YML_DETAIL_PRINT_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/detail/print.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/c4/yml/yml.hpp // https://github.com/biojppm/rapidyaml/src/c4/yml/yml.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _C4_YML_YML_HPP_ #define _C4_YML_YML_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/version.hpp //#include "c4/yml/version.hpp" #if !defined(C4_YML_VERSION_HPP_) && !defined(_C4_YML_VERSION_HPP_) #error "amalgamate: file c4/yml/version.hpp must have been included at this point" #endif /* C4_YML_VERSION_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tree.hpp //#include "c4/yml/tree.hpp" #if !defined(C4_YML_TREE_HPP_) && !defined(_C4_YML_TREE_HPP_) #error "amalgamate: file c4/yml/tree.hpp must have been included at this point" #endif /* C4_YML_TREE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/node.hpp //#include "c4/yml/node.hpp" #if !defined(C4_YML_NODE_HPP_) && !defined(_C4_YML_NODE_HPP_) #error "amalgamate: file c4/yml/node.hpp must have been included at this point" #endif /* C4_YML_NODE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/emit.hpp //#include "c4/yml/emit.hpp" #if !defined(C4_YML_EMIT_HPP_) && !defined(_C4_YML_EMIT_HPP_) #error "amalgamate: file c4/yml/emit.hpp must have been included at this point" #endif /* C4_YML_EMIT_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/event_handler_tree.hpp //#include "c4/yml/event_handler_tree.hpp" #if !defined(C4_YML_EVENT_HANDLER_TREE_HPP_) && !defined(_C4_YML_EVENT_HANDLER_TREE_HPP_) #error "amalgamate: file c4/yml/event_handler_tree.hpp must have been included at this point" #endif /* C4_YML_EVENT_HANDLER_TREE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/parse_engine.hpp //#include "c4/yml/parse_engine.hpp" #if !defined(C4_YML_PARSE_ENGINE_HPP_) && !defined(_C4_YML_PARSE_ENGINE_HPP_) #error "amalgamate: file c4/yml/parse_engine.hpp must have been included at this point" #endif /* C4_YML_PARSE_ENGINE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/filter_processor.hpp //#include "c4/yml/filter_processor.hpp" #if !defined(C4_YML_FILTER_PROCESSOR_HPP_) && !defined(_C4_YML_FILTER_PROCESSOR_HPP_) #error "amalgamate: file c4/yml/filter_processor.hpp must have been included at this point" #endif /* C4_YML_FILTER_PROCESSOR_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/parse.hpp //#include "c4/yml/parse.hpp" #if !defined(C4_YML_PARSE_HPP_) && !defined(_C4_YML_PARSE_HPP_) #error "amalgamate: file c4/yml/parse.hpp must have been included at this point" #endif /* C4_YML_PARSE_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.hpp //#include "c4/yml/preprocess.hpp" #if !defined(C4_YML_PREPROCESS_HPP_) && !defined(_C4_YML_PREPROCESS_HPP_) #error "amalgamate: file c4/yml/preprocess.hpp must have been included at this point" #endif /* C4_YML_PREPROCESS_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/reference_resolver.hpp //#include "c4/yml/reference_resolver.hpp" #if !defined(C4_YML_REFERENCE_RESOLVER_HPP_) && !defined(_C4_YML_REFERENCE_RESOLVER_HPP_) #error "amalgamate: file c4/yml/reference_resolver.hpp must have been included at this point" #endif /* C4_YML_REFERENCE_RESOLVER_HPP_ */ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/tag.hpp //#include "c4/yml/tag.hpp" #if !defined(C4_YML_TAG_HPP_) && !defined(_C4_YML_TAG_HPP_) #error "amalgamate: file c4/yml/tag.hpp must have been included at this point" #endif /* C4_YML_TAG_HPP_ */ #endif // _C4_YML_YML_HPP_ // (end https://github.com/biojppm/rapidyaml/src/c4/yml/yml.hpp) //******************************************************************************** //-------------------------------------------------------------------------------- // src/ryml.hpp // https://github.com/biojppm/rapidyaml/src/ryml.hpp //-------------------------------------------------------------------------------- //******************************************************************************** #ifndef _RYML_HPP_ #define _RYML_HPP_ // amalgamate: removed include of // https://github.com/biojppm/rapidyaml/src/c4/yml/yml.hpp //#include "c4/yml/yml.hpp" #if !defined(C4_YML_YML_HPP_) && !defined(_C4_YML_YML_HPP_) #error "amalgamate: file c4/yml/yml.hpp must have been included at this point" #endif /* C4_YML_YML_HPP_ */ namespace ryml { using namespace c4::yml; using namespace c4; } #endif /* _RYML_HPP_ */ // (end https://github.com/biojppm/rapidyaml/src/ryml.hpp) #endif /* _RYML_SINGLE_HEADER_AMALGAMATED_HPP_ */