summaryrefslogtreecommitdiffstats
path: root/src/third-party
diff options
context:
space:
mode:
Diffstat (limited to 'src/third-party')
-rw-r--r--src/third-party/ArenaAlloc/arenaalloc.h186
-rw-r--r--src/third-party/ArenaAlloc/arenaallocimpl.h286
-rw-r--r--src/third-party/ArenaAlloc/recyclealloc.h184
-rw-r--r--src/third-party/CLI/App.hpp3246
-rw-r--r--src/third-party/CLI/CLI.hpp36
-rw-r--r--src/third-party/CLI/Config.hpp396
-rw-r--r--src/third-party/CLI/ConfigFwd.hpp185
-rw-r--r--src/third-party/CLI/Error.hpp355
-rw-r--r--src/third-party/CLI/Formatter.hpp292
-rw-r--r--src/third-party/CLI/FormatterFwd.hpp184
-rw-r--r--src/third-party/CLI/Macros.hpp60
-rw-r--r--src/third-party/CLI/Option.hpp1362
-rw-r--r--src/third-party/CLI/Split.hpp143
-rw-r--r--src/third-party/CLI/StringTools.hpp430
-rw-r--r--src/third-party/CLI/Timer.hpp134
-rw-r--r--src/third-party/CLI/TypeTools.hpp1558
-rw-r--r--src/third-party/CLI/Validators.hpp1175
-rw-r--r--src/third-party/CLI/Version.hpp16
-rw-r--r--src/third-party/backward-cpp/backward.hpp4460
-rw-r--r--src/third-party/base64/LICENSE28
-rw-r--r--src/third-party/base64/include/libbase64.h133
-rw-r--r--src/third-party/base64/lib/Makefile.am23
-rw-r--r--src/third-party/base64/lib/arch/avx/codec.c42
-rw-r--r--src/third-party/base64/lib/arch/avx2/codec.c42
-rw-r--r--src/third-party/base64/lib/arch/avx2/dec_loop.c110
-rw-r--r--src/third-party/base64/lib/arch/avx2/dec_reshuffle.c34
-rw-r--r--src/third-party/base64/lib/arch/avx2/enc_loop.c89
-rw-r--r--src/third-party/base64/lib/arch/avx2/enc_reshuffle.c83
-rw-r--r--src/third-party/base64/lib/arch/avx2/enc_translate.c30
-rw-r--r--src/third-party/base64/lib/arch/generic/32/dec_loop.c86
-rw-r--r--src/third-party/base64/lib/arch/generic/32/enc_loop.c73
-rw-r--r--src/third-party/base64/lib/arch/generic/64/enc_loop.c77
-rw-r--r--src/third-party/base64/lib/arch/generic/codec.c39
-rw-r--r--src/third-party/base64/lib/arch/generic/dec_head.c37
-rw-r--r--src/third-party/base64/lib/arch/generic/dec_tail.c91
-rw-r--r--src/third-party/base64/lib/arch/generic/enc_head.c24
-rw-r--r--src/third-party/base64/lib/arch/generic/enc_tail.c34
-rw-r--r--src/third-party/base64/lib/arch/neon32/codec.c77
-rw-r--r--src/third-party/base64/lib/arch/neon32/dec_loop.c106
-rw-r--r--src/third-party/base64/lib/arch/neon32/enc_loop.c169
-rw-r--r--src/third-party/base64/lib/arch/neon32/enc_reshuffle.c31
-rw-r--r--src/third-party/base64/lib/arch/neon32/enc_translate.c57
-rw-r--r--src/third-party/base64/lib/arch/neon64/codec.c92
-rw-r--r--src/third-party/base64/lib/arch/neon64/dec_loop.c129
-rw-r--r--src/third-party/base64/lib/arch/neon64/enc_loop.c133
-rw-r--r--src/third-party/base64/lib/arch/neon64/enc_reshuffle.c31
-rw-r--r--src/third-party/base64/lib/arch/sse41/codec.c42
-rw-r--r--src/third-party/base64/lib/arch/sse42/codec.c42
-rw-r--r--src/third-party/base64/lib/arch/ssse3/codec.c42
-rw-r--r--src/third-party/base64/lib/arch/ssse3/dec_loop.c173
-rw-r--r--src/third-party/base64/lib/arch/ssse3/dec_reshuffle.c33
-rw-r--r--src/third-party/base64/lib/arch/ssse3/enc_loop.c67
-rw-r--r--src/third-party/base64/lib/arch/ssse3/enc_reshuffle.c48
-rw-r--r--src/third-party/base64/lib/arch/ssse3/enc_translate.c33
-rw-r--r--src/third-party/base64/lib/codec_choose.c281
-rw-r--r--src/third-party/base64/lib/codecs.h65
-rw-r--r--src/third-party/base64/lib/config.h7
-rw-r--r--src/third-party/base64/lib/env.h74
-rw-r--r--src/third-party/base64/lib/lib.c175
-rw-r--r--src/third-party/base64/lib/lib_openmp.c149
-rw-r--r--src/third-party/base64/lib/tables/table_dec_32bit.h393
-rw-r--r--src/third-party/base64/lib/tables/table_enc_12bit.h1031
-rw-r--r--src/third-party/base64/lib/tables/tables.c40
-rw-r--r--src/third-party/base64/lib/tables/tables.h23
-rw-r--r--src/third-party/doctest-root/doctest/doctest.h7019
-rw-r--r--src/third-party/intervaltree/IntervalTree.h346
-rw-r--r--src/third-party/md4c/md4c.c6410
-rw-r--r--src/third-party/md4c/md4c.h405
-rw-r--r--src/third-party/rapidyaml/ryml_all.hpp30945
-rw-r--r--src/third-party/robin_hood/robin_hood.h2544
-rw-r--r--src/third-party/scnlib/include/scn/all.h26
-rw-r--r--src/third-party/scnlib/include/scn/detail/args.h619
-rw-r--r--src/third-party/scnlib/include/scn/detail/config.h466
-rw-r--r--src/third-party/scnlib/include/scn/detail/context.h126
-rw-r--r--src/third-party/scnlib/include/scn/detail/error.h136
-rw-r--r--src/third-party/scnlib/include/scn/detail/file.h568
-rw-r--r--src/third-party/scnlib/include/scn/detail/fwd.h204
-rw-r--r--src/third-party/scnlib/include/scn/detail/locale.h595
-rw-r--r--src/third-party/scnlib/include/scn/detail/parse_context.h581
-rw-r--r--src/third-party/scnlib/include/scn/detail/range.h598
-rw-r--r--src/third-party/scnlib/include/scn/detail/result.h595
-rw-r--r--src/third-party/scnlib/include/scn/detail/vectored.h166
-rw-r--r--src/third-party/scnlib/include/scn/detail/visitor.h248
-rw-r--r--src/third-party/scnlib/include/scn/fwd.h23
-rw-r--r--src/third-party/scnlib/include/scn/istream.h23
-rw-r--r--src/third-party/scnlib/include/scn/ranges/custom_impl.h1632
-rw-r--r--src/third-party/scnlib/include/scn/ranges/ranges.h49
-rw-r--r--src/third-party/scnlib/include/scn/ranges/std_impl.h67
-rw-r--r--src/third-party/scnlib/include/scn/ranges/util.h419
-rw-r--r--src/third-party/scnlib/include/scn/reader/common.h1663
-rw-r--r--src/third-party/scnlib/include/scn/reader/float.h246
-rw-r--r--src/third-party/scnlib/include/scn/reader/int.h537
-rw-r--r--src/third-party/scnlib/include/scn/reader/reader.h111
-rw-r--r--src/third-party/scnlib/include/scn/reader/string.h1336
-rw-r--r--src/third-party/scnlib/include/scn/reader/types.h220
-rw-r--r--src/third-party/scnlib/include/scn/scan/common.h131
-rw-r--r--src/third-party/scnlib/include/scn/scan/getline.h186
-rw-r--r--src/third-party/scnlib/include/scn/scan/ignore.h189
-rw-r--r--src/third-party/scnlib/include/scn/scan/istream.h147
-rw-r--r--src/third-party/scnlib/include/scn/scan/list.h450
-rw-r--r--src/third-party/scnlib/include/scn/scan/scan.h444
-rw-r--r--src/third-party/scnlib/include/scn/scan/vscan.h208
-rw-r--r--src/third-party/scnlib/include/scn/scn.h26
-rw-r--r--src/third-party/scnlib/include/scn/tuple_return.h23
-rw-r--r--src/third-party/scnlib/include/scn/tuple_return/tuple_return.h123
-rw-r--r--src/third-party/scnlib/include/scn/tuple_return/util.h176
-rw-r--r--src/third-party/scnlib/include/scn/unicode/common.h139
-rw-r--r--src/third-party/scnlib/include/scn/unicode/unicode.h243
-rw-r--r--src/third-party/scnlib/include/scn/unicode/utf16.h139
-rw-r--r--src/third-party/scnlib/include/scn/unicode/utf8.h297
-rw-r--r--src/third-party/scnlib/include/scn/util/algorithm.h80
-rw-r--r--src/third-party/scnlib/include/scn/util/array.h105
-rw-r--r--src/third-party/scnlib/include/scn/util/expected.h158
-rw-r--r--src/third-party/scnlib/include/scn/util/math.h121
-rw-r--r--src/third-party/scnlib/include/scn/util/memory.h404
-rw-r--r--src/third-party/scnlib/include/scn/util/meta.h77
-rw-r--r--src/third-party/scnlib/include/scn/util/optional.h105
-rw-r--r--src/third-party/scnlib/include/scn/util/small_vector.h788
-rw-r--r--src/third-party/scnlib/include/scn/util/span.h240
-rw-r--r--src/third-party/scnlib/include/scn/util/string_view.h270
-rw-r--r--src/third-party/scnlib/include/scn/util/unique_ptr.h118
-rw-r--r--src/third-party/scnlib/licenses/README.md25
-rw-r--r--src/third-party/scnlib/licenses/fast_float-apache.txt190
-rw-r--r--src/third-party/scnlib/licenses/fast_float-mit.txt27
-rw-r--r--src/third-party/scnlib/licenses/fmt.rst27
-rw-r--r--src/third-party/scnlib/licenses/nanorange.txt23
-rw-r--r--src/third-party/scnlib/licenses/utfcpp.txt23
-rw-r--r--src/third-party/scnlib/src/Makefile.am65
-rw-r--r--src/third-party/scnlib/src/deps/fast_float/single_include/fast_float/fast_float.h2981
-rw-r--r--src/third-party/scnlib/src/file.cpp311
-rw-r--r--src/third-party/scnlib/src/locale.cpp668
-rw-r--r--src/third-party/scnlib/src/reader_float.cpp433
-rw-r--r--src/third-party/scnlib/src/reader_int.cpp372
-rw-r--r--src/third-party/scnlib/src/vscan.cpp80
-rw-r--r--src/third-party/sqlite/ext/dbdump.c730
-rw-r--r--src/third-party/sqlite/ext/series.c448
-rw-r--r--src/third-party/xxHash/xxh_x86dispatch.c770
-rw-r--r--src/third-party/xxHash/xxh_x86dispatch.h85
-rw-r--r--src/third-party/xxHash/xxhash.c43
-rw-r--r--src/third-party/xxHash/xxhash.h6075
140 files changed, 96992 insertions, 0 deletions
diff --git a/src/third-party/ArenaAlloc/arenaalloc.h b/src/third-party/ArenaAlloc/arenaalloc.h
new file mode 100644
index 0000000..dfd648d
--- /dev/null
+++ b/src/third-party/ArenaAlloc/arenaalloc.h
@@ -0,0 +1,186 @@
+// -*- c++ -*-
+/******************************************************************************
+ * arenaalloc.h
+ *
+ * Arena allocator based on the example logic provided by Nicolai Josuttis
+ * and available at http://www.josuttis.com/libbook/examples.html.
+ * This enhanced work is provided under the terms of the MIT license.
+ *
+ *****************************************************************************/
+
+#ifndef _ARENA_ALLOC_H
+#define _ARENA_ALLOC_H
+
+#include <limits>
+#include <memory>
+
+#if __cplusplus >= 201103L
+#include <type_traits>
+#include <utility>
+#endif
+
+// Define macro ARENA_ALLOC_DEBUG to enable some tracing of the allocator
+#include "arenaallocimpl.h"
+
+namespace ArenaAlloc
+{
+
+ struct _newAllocatorImpl
+ {
+ // these two functions should be supported by a specialized
+ // allocator for shared memory or another source of specialized
+ // memory such as device mapped memory.
+ void* allocate( size_t numBytes ) { return new char[ numBytes ]; }
+ void deallocate( void* ptr ) { delete[]( (char*)ptr ); }
+ };
+
+ template <class T,
+ class AllocatorImpl = _newAllocatorImpl,
+ class MemblockImpl = _memblockimpl<AllocatorImpl> >
+ class Alloc {
+
+ private:
+ MemblockImpl* m_impl;
+
+ public:
+ // type definitions
+ typedef T value_type;
+ typedef T* pointer;
+ typedef const T* const_pointer;
+ typedef T& reference;
+ typedef const T& const_reference;
+ typedef std::size_t size_type;
+ typedef std::ptrdiff_t difference_type;
+
+#if __cplusplus >= 201103L
+ // when containers are swapped, (i.e. vector.swap)
+ // swap the allocators also. This was not specified in c++98
+ // thus users of this code not using c++11 must
+ // exercise caution when using the swap algorithm or
+ // specialized swap member function. Specifically,
+ // don't swap containers not sharing the same
+ // allocator internal implementation in c++98. This is ok
+ // in c++11.
+ typedef std::true_type propagate_on_container_swap;
+
+ // container moves should move the allocator also.
+ typedef std::true_type propagate_on_container_move_assignment;
+#endif
+
+ // rebind allocator to type U
+ template <class U>
+ struct rebind {
+ typedef Alloc<U,AllocatorImpl,MemblockImpl> other;
+ };
+
+ // return address of values
+ pointer address (reference value) const {
+ return &value;
+ }
+ const_pointer address (const_reference value) const {
+ return &value;
+ }
+
+ Alloc( std::size_t defaultSize = 32768, AllocatorImpl allocImpl = AllocatorImpl() ) throw():
+ m_impl( MemblockImpl::create( defaultSize, allocImpl ) )
+ {
+ }
+
+ Alloc(const Alloc& src) throw():
+ m_impl( src.m_impl )
+ {
+ m_impl->incrementRefCount();
+ }
+
+ template <class U>
+ Alloc (const Alloc<U,AllocatorImpl,MemblockImpl>& src) throw():
+ m_impl( 0 )
+ {
+ MemblockImpl::assign( src, m_impl );
+ m_impl->incrementRefCount();
+ }
+
+ ~Alloc() throw()
+ {
+ m_impl->decrementRefCount();
+ }
+
+ // return maximum number of elements that can be allocated
+ size_type max_size () const throw()
+ {
+ return std::numeric_limits<std::size_t>::max() / sizeof(T);
+ }
+
+ // allocate but don't initialize num elements of type T
+ pointer allocate (size_type num, const void* = 0)
+ {
+ return reinterpret_cast<pointer>( m_impl->allocate(num*sizeof(T)) );
+ }
+
+ // initialize elements of allocated storage p with value value
+#if __cplusplus >= 201103L
+
+ // use c++11 style forwarding to construct the object
+ template< typename P, typename... Args>
+ void construct( P* obj, Args&&... args )
+ {
+ ::new((void*) obj ) P( std::forward<Args>( args )... );
+ }
+
+ template< typename P >
+ void destroy( P* obj ) { obj->~P(); }
+
+#else
+ void construct (pointer p, const T& value)
+ {
+ new((void*)p)T(value);
+ }
+ void destroy (pointer p) { p->~T(); }
+#endif
+
+ // deallocate storage p of deleted elements
+ void deallocate (pointer p, size_type num)
+ {
+ m_impl->deallocate( p );
+ }
+
+ bool equals( const MemblockImpl * impl ) const
+ {
+ return impl == m_impl;
+ }
+
+ bool operator == ( const Alloc& t2 ) const
+ {
+ return m_impl == t2.m_impl;
+ }
+
+ friend MemblockImpl;
+
+ template< typename Other >
+ bool operator == ( const Alloc< Other, AllocatorImpl, MemblockImpl >& t2 )
+ {
+ return t2.equals( m_impl );
+ }
+
+ template< typename Other >
+ bool operator != ( const Alloc< Other, AllocatorImpl, MemblockImpl >& t2 )
+ {
+ return !t2.equals( m_impl );
+ }
+
+ // These are extension functions not required for an stl allocator
+ size_t getNumAllocations() { return m_impl->getNumAllocations(); }
+ size_t getNumDeallocations() { return m_impl->getNumDeallocations(); }
+ size_t getNumBytesAllocated() { return m_impl->getNumBytesAllocated(); }
+ };
+
+ template<typename A>
+ template<typename T>
+ void _memblockimpl<A>::assign( const Alloc<T,A, _memblockimpl<A> >& src, _memblockimpl<A> *& dest )
+ {
+ dest = const_cast<_memblockimpl<A>* >(src.m_impl);
+ }
+
+}
+
+#endif
diff --git a/src/third-party/ArenaAlloc/arenaallocimpl.h b/src/third-party/ArenaAlloc/arenaallocimpl.h
new file mode 100644
index 0000000..12484f0
--- /dev/null
+++ b/src/third-party/ArenaAlloc/arenaallocimpl.h
@@ -0,0 +1,286 @@
+// -*- c++ -*-
+/******************************************************************************
+ ** arenaallocimpl.h
+ **
+ ** Internal implementation types of the arena allocator
+ ** MIT license
+ *****************************************************************************/
+
+#ifndef _ARENA_ALLOC_IMPL_H
+#define _ARENA_ALLOC_IMPL_H
+
+#ifdef ARENA_ALLOC_DEBUG
+#include <stdio.h>
+#endif
+
+namespace ArenaAlloc
+{
+
+ template< typename T, typename A, typename M >
+ class Alloc;
+
+ // internal structure for tracking memory blocks
+ template < typename AllocImpl >
+ struct _memblock
+ {
+ // allocations are rounded up to a multiple of the size of this
+ // struct to maintain proper alignment for any pointer and double
+ // values stored in the allocation.
+ // A future goal is to support even stricter alignment for example
+ // to support cache alignment, special device dependent mappings,
+ // or GPU ops.
+ union _roundsize {
+ double d;
+ void* p;
+ };
+
+ _memblock* m_next{nullptr}; // blocks kept link listed for cleanup at end
+ std::size_t m_bufferSize; // size of the buffer
+ std::size_t m_index; // index of next allocatable byte in the block
+ char* m_buffer; // pointer to large block to allocate from
+
+ _memblock(std::size_t bufferSize, AllocImpl& allocImpl)
+ : m_bufferSize(roundSize(bufferSize)), m_index(0),
+ m_buffer(reinterpret_cast<char*>(allocImpl.allocate(
+ bufferSize))) // this works b/c of order of decl
+ {
+ }
+
+ std::size_t roundSize( std::size_t numBytes )
+ {
+ // this is subject to overflow. calling logic should not permit
+ // an attempt to allocate a really massive size.
+ // i.e. an attempt to allocate 10s of terabytes should be an error
+ return ( ( numBytes + sizeof( _roundsize ) - 1 ) /
+ sizeof( _roundsize ) ) * sizeof( _roundsize );
+ }
+
+ char * allocate( std::size_t numBytes )
+ {
+ std::size_t roundedSize = roundSize( numBytes );
+ if( roundedSize + m_index > m_bufferSize )
+ return 0;
+
+ char * ptrToReturn = &m_buffer[ m_index ];
+ m_index += roundedSize;
+ return ptrToReturn;
+ }
+
+ void dispose( AllocImpl& impl )
+ {
+ impl.deallocate( m_buffer );
+ }
+
+ ~_memblock()
+ {
+ }
+ };
+
+ template< typename AllocatorImpl, typename Derived >
+ struct _memblockimplbase
+ {
+ AllocatorImpl m_alloc;
+ std::size_t m_refCount; // when refs -> 0 delete this
+ std::size_t m_defaultSize;
+
+ std::size_t m_numAllocate; // number of times allocate called
+ std::size_t m_numDeallocate; // number of time deallocate called
+ std::size_t m_numBytesAllocated; // A good estimate of amount of space used
+
+ _memblock<AllocatorImpl> * m_head;
+ _memblock<AllocatorImpl> * m_current;
+
+ // round up 2 next power of 2 if not already
+ // a power of 2
+ std::size_t roundpow2( std::size_t value )
+ {
+ // note this works because subtracting 1 is equivalent to
+ // inverting the lowest set bit and complementing any
+ // bits lower than that. only a power of 2
+ // will yield 0 in the following check
+ if( 0 == ( value & ( value - 1 ) ) )
+ return value; // already a power of 2
+
+ // fold t over itself. This will set all bits after the highest set bit of t to 1
+ // who said bit twiddling wasn't practical?
+ value |= value >> 1;
+ value |= value >> 2;
+ value |= value >> 4;
+ value |= value >> 8;
+ value |= value >> 16;
+ value |= value >> 32;
+
+ return value + 1;
+ }
+
+ _memblockimplbase( std::size_t defaultSize, AllocatorImpl& allocator ):
+ m_alloc( allocator ),
+ m_refCount( 1 ),
+ m_defaultSize( defaultSize ),
+ m_numAllocate( 0 ),
+ m_numDeallocate( 0 ),
+ m_numBytesAllocated( 0 ),
+ m_head( 0 ),
+ m_current( 0 )
+ {
+ if( m_defaultSize < 256 )
+ {
+ m_defaultSize = 256; // anything less is academic. a more practical size is 4k or more
+ }
+ else if ( m_defaultSize > 1024UL*1024*1024*16 )
+ {
+ // when this becomes a problem, this package has succeeded beyond my wildest expectations
+ m_defaultSize = 1024UL*1024*1024*16;
+ }
+
+ // for convenience block size should be a power of 2
+ // round up to next power of 2
+ m_defaultSize = roundpow2( m_defaultSize );
+ allocateNewBlock( m_defaultSize );
+ }
+
+ char * allocate( std::size_t numBytes )
+ {
+ char * ptrToReturn = m_current->allocate( numBytes );
+ if( !ptrToReturn )
+ {
+ allocateNewBlock( numBytes > m_defaultSize / 2 ? roundpow2( numBytes*2 ) :
+ m_defaultSize );
+
+ ptrToReturn = m_current->allocate( numBytes );
+ }
+
+#ifdef ARENA_ALLOC_DEBUG
+ fprintf( stdout, "_memblockimpl=%p allocated %ld bytes at address=%p\n", this, numBytes, ptrToReturn );
+#endif
+
+ ++ m_numAllocate;
+ m_numBytesAllocated += numBytes; // does not account for the small overhead in tracking the allocation
+
+ return ptrToReturn;
+ }
+
+ void allocateNewBlock( std::size_t blockSize )
+ {
+ _memblock<AllocatorImpl> * newBlock = new ( m_alloc.allocate( sizeof( _memblock<AllocatorImpl> ) ) )
+ _memblock<AllocatorImpl>( blockSize, m_alloc );
+
+#ifdef ARENA_ALLOC_DEBUG
+ fprintf( stdout, "_memblockimplbase=%p allocating a new block of size=%ld\n", this, blockSize );
+#endif
+
+ if( m_head == 0 )
+ {
+ m_head = m_current = newBlock;
+ }
+ else
+ {
+ m_current->m_next = newBlock;
+ m_current = newBlock;
+ }
+ }
+
+ void deallocate( void * ptr )
+ {
+ ++ m_numDeallocate;
+ }
+
+ size_t getNumAllocations() { return m_numAllocate; }
+ size_t getNumDeallocations() { return m_numDeallocate; }
+ size_t getNumBytesAllocated() { return m_numBytesAllocated; }
+
+ void clear()
+ {
+ _memblock<AllocatorImpl> * block = m_head;
+ while( block )
+ {
+ _memblock<AllocatorImpl> * curr = block;
+ block = block->m_next;
+ curr->dispose( m_alloc );
+ curr->~_memblock<AllocatorImpl>();
+ m_alloc.deallocate( curr );
+ }
+ }
+
+ // The ref counting model does not permit the sharing of
+ // this object across multiple threads unless an external locking mechanism is applied
+ // to ensure the atomicity of the reference count.
+ void incrementRefCount()
+ {
+ ++m_refCount;
+#ifdef ARENA_ALLOC_DEBUG
+ fprintf( stdout, "ref count on _memblockimplbase=%p incremented to %ld\n", this, m_refCount );
+#endif
+ }
+
+ void decrementRefCount()
+ {
+ --m_refCount;
+#ifdef ARENA_ALLOC_DEBUG
+ fprintf( stdout, "ref count on _memblockimplbase=%p decremented to %ld\n", this, m_refCount );
+#endif
+
+ if( m_refCount == 0 )
+ {
+ Derived::destroy( static_cast<Derived*>(this) );
+ }
+ }
+ };
+
+
+ // Each allocator points to an instance of _memblockimpl which
+ // contains the list of _memblock objects and other tracking info
+ // including a refcount.
+ // This object is instantiated in space obtained from the allocator
+ // implementation. The allocator implementation is the component
+ // on which allocate/deallocate are called to obtain storage from.
+ template< typename AllocatorImpl >
+ struct _memblockimpl : public _memblockimplbase<AllocatorImpl, _memblockimpl<AllocatorImpl> >
+ {
+ private:
+
+ typedef struct _memblockimplbase< AllocatorImpl, _memblockimpl<AllocatorImpl> > base_t;
+ friend struct _memblockimplbase< AllocatorImpl, _memblockimpl<AllocatorImpl> >;
+
+ // to get around some sticky access issues between Alloc<T1> and Alloc<T2> when sharing
+ // the implementation.
+ template <typename U, typename A, typename M >
+ friend class Alloc;
+
+ template< typename T >
+ static void assign( const Alloc<T,AllocatorImpl, _memblockimpl<AllocatorImpl> >& src,
+ _memblockimpl *& dest );
+
+ static _memblockimpl<AllocatorImpl> * create( size_t defaultSize, AllocatorImpl& alloc )
+ {
+ return new ( alloc.allocate( sizeof( _memblockimpl ) ) ) _memblockimpl<AllocatorImpl>( defaultSize,
+ alloc );
+ }
+
+ static void destroy( _memblockimpl<AllocatorImpl> * objToDestroy )
+ {
+ AllocatorImpl allocImpl = objToDestroy->m_alloc;
+ objToDestroy-> ~_memblockimpl<AllocatorImpl>();
+ allocImpl.deallocate( objToDestroy );
+ }
+
+ _memblockimpl( std::size_t defaultSize, AllocatorImpl& allocImpl ):
+ _memblockimplbase<AllocatorImpl, _memblockimpl<AllocatorImpl> >( defaultSize, allocImpl )
+ {
+#ifdef ARENA_ALLOC_DEBUG
+ fprintf( stdout, "_memblockimpl=%p constructed with default size=%ld\n", this,
+ base_t::m_defaultSize );
+#endif
+ }
+
+ ~_memblockimpl( )
+ {
+#ifdef ARENA_ALLOC_DEBUG
+ fprintf( stdout, "~memblockimpl() called on _memblockimpl=%p\n", this );
+#endif
+ base_t::clear();
+ }
+ };
+}
+
+#endif
diff --git a/src/third-party/ArenaAlloc/recyclealloc.h b/src/third-party/ArenaAlloc/recyclealloc.h
new file mode 100644
index 0000000..129e43b
--- /dev/null
+++ b/src/third-party/ArenaAlloc/recyclealloc.h
@@ -0,0 +1,184 @@
+// -*- c++ -*-
+/******************************************************************************
+ ** recyclealloc.h
+ **
+ ** Arena allocator with some modest recycling of freed resources.
+ ** MIT license
+ **
+ *****************************************************************************/
+#ifndef _RECYCLE_ALLOC_H
+#define _RECYCLE_ALLOC_H
+
+#include "arenaalloc.h"
+#include <string.h>
+#include <inttypes.h>
+
+namespace ArenaAlloc
+{
+
+ // todo:
+ // attempt refactor of boilerplate in _memblockimpl and _recycleallocimpl
+ template< typename AllocatorImpl, uint16_t StepSize = 16, uint16_t NumBuckets = 256 >
+ struct _recycleallocimpl : public _memblockimplbase<AllocatorImpl, _recycleallocimpl<AllocatorImpl> >
+ {
+ private:
+
+ static_assert( ( StepSize >= 16 && NumBuckets >= 16 ), "Min step size=16, Min num buckets=16" );
+ static_assert( !( StepSize & ( StepSize - 1 ) ), "Step size must be a power of 2" );
+
+ struct _freeEntry
+ {
+ // note: order of declaration matters
+ std::size_t m_size;
+ _freeEntry * m_next;
+ };
+
+ _freeEntry * m_buckets[ NumBuckets ]; // m_buckets[ NumBuckets - 1 ] is the oversize bucket
+
+ typedef struct _memblockimplbase< AllocatorImpl, _recycleallocimpl<AllocatorImpl> > base_t;
+ friend struct _memblockimplbase< AllocatorImpl, _recycleallocimpl<AllocatorImpl> >;
+
+ // to get around some sticky access issues between Alloc<T1> and Alloc<T2> when sharing
+ // the implementation.
+ template <typename U, typename A, typename M >
+ friend class Alloc;
+
+ template< typename T >
+ static void assign( const Alloc<T,AllocatorImpl, _recycleallocimpl<AllocatorImpl> >& src,
+ _recycleallocimpl *& dest )
+ {
+ dest = const_cast< _recycleallocimpl<AllocatorImpl>* >( src.m_impl );
+ }
+
+ static _recycleallocimpl<AllocatorImpl> * create( std::size_t defaultSize, AllocatorImpl& alloc )
+ {
+ return new (
+ alloc.allocate( sizeof( _recycleallocimpl ) ) ) _recycleallocimpl<AllocatorImpl>( defaultSize,
+ alloc );
+ }
+
+ static void destroy( _recycleallocimpl<AllocatorImpl> * objToDestroy )
+ {
+ AllocatorImpl allocImpl = objToDestroy->m_alloc;
+ objToDestroy-> ~_recycleallocimpl<AllocatorImpl>();
+ allocImpl.deallocate( objToDestroy );
+ }
+
+ _recycleallocimpl( std::size_t defaultSize, AllocatorImpl& allocImpl ):
+ _memblockimplbase<AllocatorImpl, _recycleallocimpl<AllocatorImpl> >( defaultSize, allocImpl )
+ {
+ memset( m_buckets, 0, sizeof( m_buckets ) );
+
+#ifdef ARENA_ALLOC_DEBUG
+ fprintf( stdout, "_recycleallocimpl=%p constructed with default size=%ld\n", this,
+ base_t::m_defaultSize );
+#endif
+ }
+
+ ~_recycleallocimpl( )
+ {
+#ifdef ARENA_ALLOC_DEBUG
+ fprintf( stdout, "~_recycleallocimpl() called on _recycleallocimpl=%p\n", this );
+#endif
+ base_t::clear();
+ }
+
+ char * allocate( std::size_t numBytes )
+ {
+
+ numBytes = ( (numBytes + sizeof( std::size_t ) + StepSize - 1) / StepSize ) * StepSize;
+
+ char * returnValue = allocateInternal( numBytes );
+ if( !returnValue )
+ {
+ char * allocValue = base_t::allocate( numBytes );
+
+ if( !allocValue )
+ return 0; //allocation failure
+
+ *((std::size_t*)allocValue ) = numBytes; // that includes the header
+ return allocValue + sizeof( std::size_t );
+ }
+
+ return returnValue;
+ }
+
+ void deallocate( void * ptr )
+ {
+ deallocateInternal( reinterpret_cast<char*>(ptr) );
+ base_t::deallocate( ptr ); // this is called b/c it is known this just updates stats
+ }
+
+ char * allocateInternal( std::size_t numBytes )
+ {
+ // numBytes must already be rounded to a multiple of stepsize and have an
+ // extra sizeof( std::size_t ) bytes tacked on for the header
+ // pointer returned points sizeof( std::size_t ) bytes into the allocation
+ // bucket 0 is always null in this scheme.
+
+ uint16_t bucketNumber = numBytes / StepSize;
+
+ if( bucketNumber > NumBuckets - 1 )
+ bucketNumber = NumBuckets - 1; // oversize alloc
+
+ // search max 3 consecutive buckets for an item large enough.
+ // in the oversize bucket and only in the oversize bucket,
+ // search upto 3 items into the linked list for an entry
+ // large enough for the specified size
+ for( uint16_t bkt = bucketNumber, i = 0; i < 3 && bkt < NumBuckets; ++i, ++bkt )
+ {
+ if( m_buckets[ bkt ] )
+ return allocateFrom( numBytes, m_buckets[ bkt ] );
+ }
+
+ return 0;
+ }
+
+ char * allocateFrom( std::size_t numBytes, _freeEntry *& head )
+ {
+ _freeEntry * current = head;
+ _freeEntry * prev = 0;
+
+ int count = 0;
+
+ while( current && count < 3 )
+ {
+ if( current->m_size >= numBytes )
+ {
+ if( prev == 0 )
+ head = current->m_next;
+ else
+ prev->m_next = current->m_next;
+
+ return reinterpret_cast<char*>(&current->m_next);
+ }
+
+ ++count;
+ prev = current;
+ current = current->m_next;
+ }
+
+ return 0;
+ }
+
+ void deallocateInternal( char * ptr )
+ {
+ _freeEntry * v = reinterpret_cast< _freeEntry* >( ptr - sizeof( std::size_t ) );
+ uint16_t bucketNumber = v->m_size / StepSize;
+
+ if( bucketNumber > NumBuckets - 1 )
+ bucketNumber = NumBuckets - 1;
+
+ _freeEntry * next = m_buckets[ bucketNumber ];
+ v->m_next = next;
+ m_buckets[ bucketNumber ] = v;
+ }
+
+ };
+
+ template< typename T, typename Allocator = _newAllocatorImpl >
+ using RecycleAlloc = Alloc< T, Allocator, _recycleallocimpl<Allocator> >;
+
+}
+
+#endif
diff --git a/src/third-party/CLI/App.hpp b/src/third-party/CLI/App.hpp
new file mode 100644
index 0000000..0a70bac
--- /dev/null
+++ b/src/third-party/CLI/App.hpp
@@ -0,0 +1,3246 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <set>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+// CLI Library includes
+#include "ConfigFwd.hpp"
+#include "Error.hpp"
+#include "FormatterFwd.hpp"
+#include "Macros.hpp"
+#include "Option.hpp"
+#include "Split.hpp"
+#include "StringTools.hpp"
+#include "TypeTools.hpp"
+
+namespace CLI {
+// [CLI11:app_hpp:verbatim]
+
+#ifndef CLI11_PARSE
+#define CLI11_PARSE(app, argc, argv) \
+ try { \
+ (app).parse((argc), (argv)); \
+ } catch(const CLI::ParseError &e) { \
+ return (app).exit(e); \
+ }
+#endif
+
+namespace detail {
+enum class Classifier { NONE, POSITIONAL_MARK, SHORT, LONG, WINDOWS_STYLE, SUBCOMMAND, SUBCOMMAND_TERMINATOR };
+struct AppFriend;
+} // namespace detail
+
+namespace FailureMessage {
+std::string simple(const App *app, const Error &e);
+std::string help(const App *app, const Error &e);
+} // namespace FailureMessage
+
+/// enumeration of modes of how to deal with extras in config files
+
+enum class config_extras_mode : char { error = 0, ignore, ignore_all, capture };
+
+class App;
+
+using App_p = std::shared_ptr<App>;
+
+namespace detail {
+/// helper functions for adding in appropriate flag modifiers for add_flag
+
+template <typename T, enable_if_t<!std::is_integral<T>::value || (sizeof(T) <= 1U), detail::enabler> = detail::dummy>
+Option *default_flag_modifiers(Option *opt) {
+ return opt->always_capture_default();
+}
+
+/// summing modifiers
+template <typename T, enable_if_t<std::is_integral<T>::value && (sizeof(T) > 1U), detail::enabler> = detail::dummy>
+Option *default_flag_modifiers(Option *opt) {
+ return opt->multi_option_policy(MultiOptionPolicy::Sum)->default_str("0")->force_callback();
+}
+
+} // namespace detail
+
+class Option_group;
+/// Creates a command line program, with very few defaults.
+/** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
+ * add_option methods make it easy to prepare options. Remember to call `.start` before starting your
+ * program, so that the options can be evaluated and the help option doesn't accidentally run your program. */
+class App {
+ friend Option;
+ friend detail::AppFriend;
+
+ protected:
+ // This library follows the Google style guide for member names ending in underscores
+
+ /// @name Basics
+ ///@{
+
+ /// Subcommand name or program name (from parser if name is empty)
+ std::string name_{};
+
+ /// Description of the current program/subcommand
+ std::string description_{};
+
+ /// If true, allow extra arguments (ie, don't throw an error). INHERITABLE
+ bool allow_extras_{false};
+
+ /// If ignore, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE
+ /// if error error on an extra argument, and if capture feed it to the app
+ config_extras_mode allow_config_extras_{config_extras_mode::ignore};
+
+ /// If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE
+ bool prefix_command_{false};
+
+ /// If set to true the name was automatically generated from the command line vs a user set name
+ bool has_automatic_name_{false};
+
+ /// If set to true the subcommand is required to be processed and used, ignored for main app
+ bool required_{false};
+
+ /// If set to true the subcommand is disabled and cannot be used, ignored for main app
+ bool disabled_{false};
+
+ /// Flag indicating that the pre_parse_callback has been triggered
+ bool pre_parse_called_{false};
+
+ /// Flag indicating that the callback for the subcommand should be executed immediately on parse completion which is
+ /// before help or ini files are processed. INHERITABLE
+ bool immediate_callback_{false};
+
+ /// This is a function that runs prior to the start of parsing
+ std::function<void(std::size_t)> pre_parse_callback_{};
+
+ /// This is a function that runs when parsing has finished.
+ std::function<void()> parse_complete_callback_{};
+
+ /// This is a function that runs when all processing has completed
+ std::function<void()> final_callback_{};
+
+ ///@}
+ /// @name Options
+ ///@{
+
+ /// The default values for options, customizable and changeable INHERITABLE
+ OptionDefaults option_defaults_{};
+
+ /// The list of options, stored locally
+ std::vector<Option_p> options_{};
+
+ ///@}
+ /// @name Help
+ ///@{
+
+ /// Footer to put after all options in the help output INHERITABLE
+ std::string footer_{};
+
+ /// This is a function that generates a footer to put after all other options in help output
+ std::function<std::string()> footer_callback_{};
+
+ /// A pointer to the help flag if there is one INHERITABLE
+ Option *help_ptr_{nullptr};
+
+ /// A pointer to the help all flag if there is one INHERITABLE
+ Option *help_all_ptr_{nullptr};
+
+ /// A pointer to a version flag if there is one
+ Option *version_ptr_{nullptr};
+
+ /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)
+ std::shared_ptr<FormatterBase> formatter_{new Formatter()};
+
+ /// The error message printing function INHERITABLE
+ std::function<std::string(const App *, const Error &e)> failure_message_{FailureMessage::simple};
+
+ ///@}
+ /// @name Parsing
+ ///@{
+
+ using missing_t = std::vector<std::pair<detail::Classifier, std::string>>;
+
+ /// Pair of classifier, string for missing options. (extra detail is removed on returning from parse)
+ ///
+ /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator.
+ missing_t missing_{};
+
+ /// This is a list of pointers to options with the original parse order
+ std::vector<Option *> parse_order_{};
+
+ /// This is a list of the subcommands collected, in order
+ std::vector<App *> parsed_subcommands_{};
+
+ /// this is a list of subcommands that are exclusionary to this one
+ std::set<App *> exclude_subcommands_{};
+
+ /// This is a list of options which are exclusionary to this App, if the options were used this subcommand should
+ /// not be
+ std::set<Option *> exclude_options_{};
+
+ /// this is a list of subcommands or option groups that are required by this one, the list is not mutual, the
+ /// listed subcommands do not require this one
+ std::set<App *> need_subcommands_{};
+
+ /// This is a list of options which are required by this app, the list is not mutual, listed options do not need the
+ /// subcommand not be
+ std::set<Option *> need_options_{};
+
+ ///@}
+ /// @name Subcommands
+ ///@{
+
+ /// Storage for subcommand list
+ std::vector<App_p> subcommands_{};
+
+ /// If true, the program name is not case sensitive INHERITABLE
+ bool ignore_case_{false};
+
+ /// If true, the program should ignore underscores INHERITABLE
+ bool ignore_underscore_{false};
+
+ /// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand. INHERITABLE
+ bool fallthrough_{false};
+
+ /// Allow '/' for options for Windows like options. Defaults to true on Windows, false otherwise. INHERITABLE
+ bool allow_windows_style_options_{
+#ifdef _WIN32
+ true
+#else
+ false
+#endif
+ };
+ /// specify that positional arguments come at the end of the argument sequence not inheritable
+ bool positionals_at_end_{false};
+
+ enum class startup_mode : char { stable, enabled, disabled };
+ /// specify the startup mode for the app
+ /// stable=no change, enabled= startup enabled, disabled=startup disabled
+ startup_mode default_startup{startup_mode::stable};
+
+ /// if set to true the subcommand can be triggered via configuration files INHERITABLE
+ bool configurable_{false};
+
+ /// If set to true positional options are validated before assigning INHERITABLE
+ bool validate_positionals_{false};
+
+ /// If set to true optional vector arguments are validated before assigning INHERITABLE
+ bool validate_optional_arguments_{false};
+
+ /// indicator that the subcommand is silent and won't show up in subcommands list
+ /// This is potentially useful as a modifier subcommand
+ bool silent_{false};
+
+ /// Counts the number of times this command/subcommand was parsed
+ std::uint32_t parsed_{0U};
+
+ /// Minimum required subcommands (not inheritable!)
+ std::size_t require_subcommand_min_{0};
+
+ /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE
+ std::size_t require_subcommand_max_{0};
+
+ /// Minimum required options (not inheritable!)
+ std::size_t require_option_min_{0};
+
+ /// Max number of options allowed. 0 is unlimited (not inheritable)
+ std::size_t require_option_max_{0};
+
+ /// A pointer to the parent if this is a subcommand
+ App *parent_{nullptr};
+
+ /// The group membership INHERITABLE
+ std::string group_{"Subcommands"};
+
+ /// Alias names for the subcommand
+ std::vector<std::string> aliases_{};
+
+ ///@}
+ /// @name Config
+ ///@{
+
+ /// Pointer to the config option
+ Option *config_ptr_{nullptr};
+
+ /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)
+ std::shared_ptr<Config> config_formatter_{new ConfigTOML()};
+
+ ///@}
+
+ /// Special private constructor for subcommand
+ App(std::string app_description, std::string app_name, App *parent)
+ : name_(std::move(app_name)), description_(std::move(app_description)), parent_(parent) {
+ // Inherit if not from a nullptr
+ if(parent_ != nullptr) {
+ if(parent_->help_ptr_ != nullptr)
+ set_help_flag(parent_->help_ptr_->get_name(false, true), parent_->help_ptr_->get_description());
+ if(parent_->help_all_ptr_ != nullptr)
+ set_help_all_flag(parent_->help_all_ptr_->get_name(false, true),
+ parent_->help_all_ptr_->get_description());
+
+ /// OptionDefaults
+ option_defaults_ = parent_->option_defaults_;
+
+ // INHERITABLE
+ failure_message_ = parent_->failure_message_;
+ allow_extras_ = parent_->allow_extras_;
+ allow_config_extras_ = parent_->allow_config_extras_;
+ prefix_command_ = parent_->prefix_command_;
+ immediate_callback_ = parent_->immediate_callback_;
+ ignore_case_ = parent_->ignore_case_;
+ ignore_underscore_ = parent_->ignore_underscore_;
+ fallthrough_ = parent_->fallthrough_;
+ validate_positionals_ = parent_->validate_positionals_;
+ validate_optional_arguments_ = parent_->validate_optional_arguments_;
+ configurable_ = parent_->configurable_;
+ allow_windows_style_options_ = parent_->allow_windows_style_options_;
+ group_ = parent_->group_;
+ footer_ = parent_->footer_;
+ formatter_ = parent_->formatter_;
+ config_formatter_ = parent_->config_formatter_;
+ require_subcommand_max_ = parent_->require_subcommand_max_;
+ }
+ }
+
+ public:
+ /// @name Basic
+ ///@{
+
+ /// Create a new program. Pass in the same arguments as main(), along with a help string.
+ explicit App(std::string app_description = "", std::string app_name = "")
+ : App(app_description, app_name, nullptr) {
+ set_help_flag("-h,--help", "Print this help message and exit");
+ }
+
+ App(const App &) = delete;
+ App &operator=(const App &) = delete;
+
+ /// virtual destructor
+ virtual ~App() = default;
+
+ /// Set a callback for execution when all parsing and processing has completed
+ ///
+ /// Due to a bug in c++11,
+ /// it is not possible to overload on std::function (fixed in c++14
+ /// and backported to c++11 on newer compilers). Use capture by reference
+ /// to get a pointer to App if needed.
+ App *callback(std::function<void()> app_callback) {
+ if(immediate_callback_) {
+ parse_complete_callback_ = std::move(app_callback);
+ } else {
+ final_callback_ = std::move(app_callback);
+ }
+ return this;
+ }
+
+ /// Set a callback for execution when all parsing and processing has completed
+ /// aliased as callback
+ App *final_callback(std::function<void()> app_callback) {
+ final_callback_ = std::move(app_callback);
+ return this;
+ }
+
+ /// Set a callback to execute when parsing has completed for the app
+ ///
+ App *parse_complete_callback(std::function<void()> pc_callback) {
+ parse_complete_callback_ = std::move(pc_callback);
+ return this;
+ }
+
+ /// Set a callback to execute prior to parsing.
+ ///
+ App *preparse_callback(std::function<void(std::size_t)> pp_callback) {
+ pre_parse_callback_ = std::move(pp_callback);
+ return this;
+ }
+
+ /// Set a name for the app (empty will use parser to set the name)
+ App *name(std::string app_name = "") {
+
+ if(parent_ != nullptr) {
+ auto oname = name_;
+ name_ = app_name;
+ auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
+ if(!res.empty()) {
+ name_ = oname;
+ throw(OptionAlreadyAdded(app_name + " conflicts with existing subcommand names"));
+ }
+ } else {
+ name_ = app_name;
+ }
+ has_automatic_name_ = false;
+ return this;
+ }
+
+ /// Set an alias for the app
+ App *alias(std::string app_name) {
+ if(app_name.empty() || !detail::valid_alias_name_string(app_name)) {
+ throw IncorrectConstruction("Aliases may not be empty or contain newlines or null characters");
+ }
+ if(parent_ != nullptr) {
+ aliases_.push_back(app_name);
+ auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
+ if(!res.empty()) {
+ aliases_.pop_back();
+ throw(OptionAlreadyAdded("alias already matches an existing subcommand: " + app_name));
+ }
+ } else {
+ aliases_.push_back(app_name);
+ }
+
+ return this;
+ }
+
+ /// Remove the error when extras are left over on the command line.
+ App *allow_extras(bool allow = true) {
+ allow_extras_ = allow;
+ return this;
+ }
+
+ /// Remove the error when extras are left over on the command line.
+ App *required(bool require = true) {
+ required_ = require;
+ return this;
+ }
+
+ /// Disable the subcommand or option group
+ App *disabled(bool disable = true) {
+ disabled_ = disable;
+ return this;
+ }
+
+ /// silence the subcommand from showing up in the processed list
+ App *silent(bool silence = true) {
+ silent_ = silence;
+ return this;
+ }
+
+ /// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled
+ App *disabled_by_default(bool disable = true) {
+ if(disable) {
+ default_startup = startup_mode::disabled;
+ } else {
+ default_startup = (default_startup == startup_mode::enabled) ? startup_mode::enabled : startup_mode::stable;
+ }
+ return this;
+ }
+
+ /// Set the subcommand to be enabled by default, so on clear(), at the start of each parse it is enabled (not
+ /// disabled)
+ App *enabled_by_default(bool enable = true) {
+ if(enable) {
+ default_startup = startup_mode::enabled;
+ } else {
+ default_startup =
+ (default_startup == startup_mode::disabled) ? startup_mode::disabled : startup_mode::stable;
+ }
+ return this;
+ }
+
+ /// Set the subcommand callback to be executed immediately on subcommand completion
+ App *immediate_callback(bool immediate = true) {
+ immediate_callback_ = immediate;
+ if(immediate_callback_) {
+ if(final_callback_ && !(parse_complete_callback_)) {
+ std::swap(final_callback_, parse_complete_callback_);
+ }
+ } else if(!(final_callback_) && parse_complete_callback_) {
+ std::swap(final_callback_, parse_complete_callback_);
+ }
+ return this;
+ }
+
+ /// Set the subcommand to validate positional arguments before assigning
+ App *validate_positionals(bool validate = true) {
+ validate_positionals_ = validate;
+ return this;
+ }
+
+ /// Set the subcommand to validate optional vector arguments before assigning
+ App *validate_optional_arguments(bool validate = true) {
+ validate_optional_arguments_ = validate;
+ return this;
+ }
+
+ /// ignore extras in config files
+ App *allow_config_extras(bool allow = true) {
+ if(allow) {
+ allow_config_extras_ = config_extras_mode::capture;
+ allow_extras_ = true;
+ } else {
+ allow_config_extras_ = config_extras_mode::error;
+ }
+ return this;
+ }
+
+ /// ignore extras in config files
+ App *allow_config_extras(config_extras_mode mode) {
+ allow_config_extras_ = mode;
+ return this;
+ }
+
+ /// Do not parse anything after the first unrecognized option and return
+ App *prefix_command(bool allow = true) {
+ prefix_command_ = allow;
+ return this;
+ }
+
+ /// Ignore case. Subcommands inherit value.
+ App *ignore_case(bool value = true) {
+ if(value && !ignore_case_) {
+ ignore_case_ = true;
+ auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
+ auto &match = _compare_subcommand_names(*this, *p);
+ if(!match.empty()) {
+ ignore_case_ = false; // we are throwing so need to be exception invariant
+ throw OptionAlreadyAdded("ignore case would cause subcommand name conflicts: " + match);
+ }
+ }
+ ignore_case_ = value;
+ return this;
+ }
+
+ /// Allow windows style options, such as `/opt`. First matching short or long name used. Subcommands inherit
+ /// value.
+ App *allow_windows_style_options(bool value = true) {
+ allow_windows_style_options_ = value;
+ return this;
+ }
+
+ /// Specify that the positional arguments are only at the end of the sequence
+ App *positionals_at_end(bool value = true) {
+ positionals_at_end_ = value;
+ return this;
+ }
+
+ /// Specify that the subcommand can be triggered by a config file
+ App *configurable(bool value = true) {
+ configurable_ = value;
+ return this;
+ }
+
+ /// Ignore underscore. Subcommands inherit value.
+ App *ignore_underscore(bool value = true) {
+ if(value && !ignore_underscore_) {
+ ignore_underscore_ = true;
+ auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
+ auto &match = _compare_subcommand_names(*this, *p);
+ if(!match.empty()) {
+ ignore_underscore_ = false;
+ throw OptionAlreadyAdded("ignore underscore would cause subcommand name conflicts: " + match);
+ }
+ }
+ ignore_underscore_ = value;
+ return this;
+ }
+
+ /// Set the help formatter
+ App *formatter(std::shared_ptr<FormatterBase> fmt) {
+ formatter_ = fmt;
+ return this;
+ }
+
+ /// Set the help formatter
+ App *formatter_fn(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) {
+ formatter_ = std::make_shared<FormatterLambda>(fmt);
+ return this;
+ }
+
+ /// Set the config formatter
+ App *config_formatter(std::shared_ptr<Config> fmt) {
+ config_formatter_ = fmt;
+ return this;
+ }
+
+ /// Check to see if this subcommand was parsed, true only if received on command line.
+ bool parsed() const { return parsed_ > 0; }
+
+ /// Get the OptionDefault object, to set option defaults
+ OptionDefaults *option_defaults() { return &option_defaults_; }
+
+ ///@}
+ /// @name Adding options
+ ///@{
+
+ /// Add an option, will automatically understand the type for common types.
+ ///
+ /// To use, create a variable with the expected type, and pass it in after the name.
+ /// After start is called, you can use count to see if the value was passed, and
+ /// the value will be initialized properly. Numbers, vectors, and strings are supported.
+ ///
+ /// ->required(), ->default, and the validators are options,
+ /// The positional options take an optional number of arguments.
+ ///
+ /// For example,
+ ///
+ /// std::string filename;
+ /// program.add_option("filename", filename, "description of filename");
+ ///
+ Option *add_option(std::string option_name,
+ callback_t option_callback,
+ std::string option_description = "",
+ bool defaulted = false,
+ std::function<std::string()> func = {}) {
+ Option myopt{option_name, option_description, option_callback, this};
+
+ if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) {
+ return *v == myopt;
+ }) == std::end(options_)) {
+ options_.emplace_back();
+ Option_p &option = options_.back();
+ option.reset(new Option(option_name, option_description, option_callback, this));
+
+ // Set the default string capture function
+ option->default_function(func);
+
+ // For compatibility with CLI11 1.7 and before, capture the default string here
+ if(defaulted)
+ option->capture_default_str();
+
+ // Transfer defaults to the new option
+ option_defaults_.copy_to(option.get());
+
+ // Don't bother to capture if we already did
+ if(!defaulted && option->get_always_capture_default())
+ option->capture_default_str();
+
+ return option.get();
+ }
+ // we know something matches now find what it is so we can produce more error information
+ for(auto &opt : options_) {
+ auto &matchname = opt->matching_name(myopt);
+ if(!matchname.empty()) {
+ throw(OptionAlreadyAdded("added option matched existing option name: " + matchname));
+ }
+ }
+ // this line should not be reached the above loop should trigger the throw
+ throw(OptionAlreadyAdded("added option matched existing option name")); // LCOV_EXCL_LINE
+ }
+
+ /// Add option for assigning to a variable
+ template <typename AssignTo,
+ typename ConvertTo = AssignTo,
+ enable_if_t<!std::is_const<ConvertTo>::value, detail::enabler> = detail::dummy>
+ Option *add_option(std::string option_name,
+ AssignTo &variable, ///< The variable to set
+ std::string option_description = "") {
+
+ auto fun = [&variable](const CLI::results_t &res) { // comment for spacing
+ return detail::lexical_conversion<AssignTo, ConvertTo>(res, variable);
+ };
+
+ Option *opt = add_option(option_name, fun, option_description, false, [&variable]() {
+ return CLI::detail::checked_to_string<AssignTo, ConvertTo>(variable);
+ });
+ opt->type_name(detail::type_name<ConvertTo>());
+ // these must be actual lvalues since (std::max) sometimes is defined in terms of references and references
+ // to structs used in the evaluation can be temporary so that would cause issues.
+ auto Tcount = detail::type_count<AssignTo>::value;
+ auto XCcount = detail::type_count<ConvertTo>::value;
+ opt->type_size(detail::type_count_min<ConvertTo>::value, (std::max)(Tcount, XCcount));
+ opt->expected(detail::expected_count<ConvertTo>::value);
+ opt->run_callback_for_default();
+ return opt;
+ }
+
+ /// Add option for assigning to a variable
+ template <typename AssignTo, enable_if_t<!std::is_const<AssignTo>::value, detail::enabler> = detail::dummy>
+ Option *add_option_no_stream(std::string option_name,
+ AssignTo &variable, ///< The variable to set
+ std::string option_description = "") {
+
+ auto fun = [&variable](const CLI::results_t &res) { // comment for spacing
+ return detail::lexical_conversion<AssignTo, AssignTo>(res, variable);
+ };
+
+ Option *opt = add_option(option_name, fun, option_description, false, []() { return std::string{}; });
+ opt->type_name(detail::type_name<AssignTo>());
+ opt->type_size(detail::type_count_min<AssignTo>::value, detail::type_count<AssignTo>::value);
+ opt->expected(detail::expected_count<AssignTo>::value);
+ opt->run_callback_for_default();
+ return opt;
+ }
+
+ /// Add option for a callback of a specific type
+ template <typename ArgType>
+ Option *add_option_function(std::string option_name,
+ const std::function<void(const ArgType &)> &func, ///< the callback to execute
+ std::string option_description = "") {
+
+ auto fun = [func](const CLI::results_t &res) {
+ ArgType variable;
+ bool result = detail::lexical_conversion<ArgType, ArgType>(res, variable);
+ if(result) {
+ func(variable);
+ }
+ return result;
+ };
+
+ Option *opt = add_option(option_name, std::move(fun), option_description, false);
+ opt->type_name(detail::type_name<ArgType>());
+ opt->type_size(detail::type_count_min<ArgType>::value, detail::type_count<ArgType>::value);
+ opt->expected(detail::expected_count<ArgType>::value);
+ return opt;
+ }
+
+ /// Add option with no description or variable assignment
+ Option *add_option(std::string option_name) {
+ return add_option(option_name, CLI::callback_t{}, std::string{}, false);
+ }
+
+ /// Add option with description but with no variable assignment or callback
+ template <typename T,
+ enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> =
+ detail::dummy>
+ Option *add_option(std::string option_name, T &option_description) {
+ return add_option(option_name, CLI::callback_t(), option_description, false);
+ }
+
+ /// Set a help flag, replace the existing one if present
+ Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") {
+ // take flag_description by const reference otherwise add_flag tries to assign to help_description
+ if(help_ptr_ != nullptr) {
+ remove_option(help_ptr_);
+ help_ptr_ = nullptr;
+ }
+
+ // Empty name will simply remove the help flag
+ if(!flag_name.empty()) {
+ help_ptr_ = add_flag(flag_name, help_description);
+ help_ptr_->configurable(false);
+ }
+
+ return help_ptr_;
+ }
+
+ /// Set a help all flag, replaced the existing one if present
+ Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = "") {
+ // take flag_description by const reference otherwise add_flag tries to assign to flag_description
+ if(help_all_ptr_ != nullptr) {
+ remove_option(help_all_ptr_);
+ help_all_ptr_ = nullptr;
+ }
+
+ // Empty name will simply remove the help all flag
+ if(!help_name.empty()) {
+ help_all_ptr_ = add_flag(help_name, help_description);
+ help_all_ptr_->configurable(false);
+ }
+
+ return help_all_ptr_;
+ }
+
+ /// Set a version flag and version display string, replace the existing one if present
+ Option *set_version_flag(std::string flag_name = "",
+ const std::string &versionString = "",
+ const std::string &version_help = "Display program version information and exit") {
+ // take flag_description by const reference otherwise add_flag tries to assign to version_description
+ if(version_ptr_ != nullptr) {
+ remove_option(version_ptr_);
+ version_ptr_ = nullptr;
+ }
+
+ // Empty name will simply remove the version flag
+ if(!flag_name.empty()) {
+ version_ptr_ = add_flag_callback(
+ flag_name, [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, version_help);
+ version_ptr_->configurable(false);
+ }
+
+ return version_ptr_;
+ }
+ /// Generate the version string through a callback function
+ Option *set_version_flag(std::string flag_name,
+ std::function<std::string()> vfunc,
+ const std::string &version_help = "Display program version information and exit") {
+ if(version_ptr_ != nullptr) {
+ remove_option(version_ptr_);
+ version_ptr_ = nullptr;
+ }
+
+ // Empty name will simply remove the version flag
+ if(!flag_name.empty()) {
+ version_ptr_ = add_flag_callback(
+ flag_name, [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, version_help);
+ version_ptr_->configurable(false);
+ }
+
+ return version_ptr_;
+ }
+
+ private:
+ /// Internal function for adding a flag
+ Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) {
+ Option *opt;
+ if(detail::has_default_flag_values(flag_name)) {
+ // check for default values and if it has them
+ auto flag_defaults = detail::get_default_flag_values(flag_name);
+ detail::remove_default_flag_values(flag_name);
+ opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
+ for(const auto &fname : flag_defaults)
+ opt->fnames_.push_back(fname.first);
+ opt->default_flag_values_ = std::move(flag_defaults);
+ } else {
+ opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
+ }
+ // flags cannot have positional values
+ if(opt->get_positional()) {
+ auto pos_name = opt->get_name(true);
+ remove_option(opt);
+ throw IncorrectConstruction::PositionalFlag(pos_name);
+ }
+ opt->multi_option_policy(MultiOptionPolicy::TakeLast);
+ opt->expected(0);
+ opt->required(false);
+ return opt;
+ }
+
+ public:
+ /// Add a flag with no description or variable assignment
+ Option *add_flag(std::string flag_name) { return _add_flag_internal(flag_name, CLI::callback_t(), std::string{}); }
+
+ /// Add flag with description but with no variable assignment or callback
+ /// takes a constant string, if a variable string is passed that variable will be assigned the results from the
+ /// flag
+ template <typename T,
+ enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> =
+ detail::dummy>
+ Option *add_flag(std::string flag_name, T &flag_description) {
+ return _add_flag_internal(flag_name, CLI::callback_t(), flag_description);
+ }
+
+ /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes
+ /// that can be converted from a string
+ template <typename T,
+ enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value &&
+ !std::is_constructible<std::function<void(int)>, T>::value,
+ detail::enabler> = detail::dummy>
+ Option *add_flag(std::string flag_name,
+ T &flag_result, ///< A variable holding the flag result
+ std::string flag_description = "") {
+
+ CLI::callback_t fun = [&flag_result](const CLI::results_t &res) {
+ return CLI::detail::lexical_cast(res[0], flag_result);
+ };
+ auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
+ return detail::default_flag_modifiers<T>(opt);
+ }
+
+ /// Vector version to capture multiple flags.
+ template <typename T,
+ enable_if_t<!std::is_assignable<std::function<void(std::int64_t)> &, T>::value, detail::enabler> =
+ detail::dummy>
+ Option *add_flag(std::string flag_name,
+ std::vector<T> &flag_results, ///< A vector of values with the flag results
+ std::string flag_description = "") {
+ CLI::callback_t fun = [&flag_results](const CLI::results_t &res) {
+ bool retval = true;
+ for(const auto &elem : res) {
+ flag_results.emplace_back();
+ retval &= detail::lexical_cast(elem, flag_results.back());
+ }
+ return retval;
+ };
+ return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
+ ->multi_option_policy(MultiOptionPolicy::TakeAll)
+ ->run_callback_for_default();
+ }
+
+ /// Add option for callback that is triggered with a true flag and takes no arguments
+ Option *add_flag_callback(std::string flag_name,
+ std::function<void(void)> function, ///< A function to call, void(void)
+ std::string flag_description = "") {
+
+ CLI::callback_t fun = [function](const CLI::results_t &res) {
+ bool trigger{false};
+ auto result = CLI::detail::lexical_cast(res[0], trigger);
+ if(result && trigger) {
+ function();
+ }
+ return result;
+ };
+ return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
+ }
+
+ /// Add option for callback with an integer value
+ Option *add_flag_function(std::string flag_name,
+ std::function<void(std::int64_t)> function, ///< A function to call, void(int)
+ std::string flag_description = "") {
+
+ CLI::callback_t fun = [function](const CLI::results_t &res) {
+ std::int64_t flag_count{0};
+ CLI::detail::lexical_cast(res[0], flag_count);
+ function(flag_count);
+ return true;
+ };
+ return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
+ ->multi_option_policy(MultiOptionPolicy::Sum);
+ }
+
+#ifdef CLI11_CPP14
+ /// Add option for callback (C++14 or better only)
+ Option *add_flag(std::string flag_name,
+ std::function<void(std::int64_t)> function, ///< A function to call, void(std::int64_t)
+ std::string flag_description = "") {
+ return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description));
+ }
+#endif
+
+ /// Set a configuration ini file option, or clear it if no name passed
+ Option *set_config(std::string option_name = "",
+ std::string default_filename = "",
+ const std::string &help_message = "Read an ini file",
+ bool config_required = false) {
+
+ // Remove existing config if present
+ if(config_ptr_ != nullptr) {
+ remove_option(config_ptr_);
+ config_ptr_ = nullptr; // need to remove the config_ptr completely
+ }
+
+ // Only add config if option passed
+ if(!option_name.empty()) {
+ config_ptr_ = add_option(option_name, help_message);
+ if(config_required) {
+ config_ptr_->required();
+ }
+ if(!default_filename.empty()) {
+ config_ptr_->default_str(std::move(default_filename));
+ }
+ config_ptr_->configurable(false);
+ }
+
+ return config_ptr_;
+ }
+
+ /// Removes an option from the App. Takes an option pointer. Returns true if found and removed.
+ bool remove_option(Option *opt) {
+ // Make sure no links exist
+ for(Option_p &op : options_) {
+ op->remove_needs(opt);
+ op->remove_excludes(opt);
+ }
+
+ if(help_ptr_ == opt)
+ help_ptr_ = nullptr;
+ if(help_all_ptr_ == opt)
+ help_all_ptr_ = nullptr;
+
+ auto iterator =
+ std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
+ if(iterator != std::end(options_)) {
+ options_.erase(iterator);
+ return true;
+ }
+ return false;
+ }
+
+ /// creates an option group as part of the given app
+ template <typename T = Option_group>
+ T *add_option_group(std::string group_name, std::string group_description = "") {
+ if(!detail::valid_alias_name_string(group_name)) {
+ throw IncorrectConstruction("option group names may not contain newlines or null characters");
+ }
+ auto option_group = std::make_shared<T>(std::move(group_description), group_name, this);
+ auto ptr = option_group.get();
+ // move to App_p for overload resolution on older gcc versions
+ App_p app_ptr = std::dynamic_pointer_cast<App>(option_group);
+ add_subcommand(std::move(app_ptr));
+ return ptr;
+ }
+
+ ///@}
+ /// @name Subcommands
+ ///@{
+
+ /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag
+ App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = "") {
+ if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) {
+ if(!detail::valid_first_char(subcommand_name[0])) {
+ throw IncorrectConstruction(
+ "Subcommand name starts with invalid character, '!' and '-' are not allowed");
+ }
+ for(auto c : subcommand_name) {
+ if(!detail::valid_later_char(c)) {
+ throw IncorrectConstruction(std::string("Subcommand name contains invalid character ('") + c +
+ "'), all characters are allowed except"
+ "'=',':','{','}', and ' '");
+ }
+ }
+ }
+ CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(subcommand_description), subcommand_name, this));
+ return add_subcommand(std::move(subcom));
+ }
+
+ /// Add a previously created app as a subcommand
+ App *add_subcommand(CLI::App_p subcom) {
+ if(!subcom)
+ throw IncorrectConstruction("passed App is not valid");
+ auto ckapp = (name_.empty() && parent_ != nullptr) ? _get_fallthrough_parent() : this;
+ auto &mstrg = _compare_subcommand_names(*subcom, *ckapp);
+ if(!mstrg.empty()) {
+ throw(OptionAlreadyAdded("subcommand name or alias matches existing subcommand: " + mstrg));
+ }
+ subcom->parent_ = this;
+ subcommands_.push_back(std::move(subcom));
+ return subcommands_.back().get();
+ }
+
+ /// Removes a subcommand from the App. Takes a subcommand pointer. Returns true if found and removed.
+ bool remove_subcommand(App *subcom) {
+ // Make sure no links exist
+ for(App_p &sub : subcommands_) {
+ sub->remove_excludes(subcom);
+ sub->remove_needs(subcom);
+ }
+
+ auto iterator = std::find_if(
+ std::begin(subcommands_), std::end(subcommands_), [subcom](const App_p &v) { return v.get() == subcom; });
+ if(iterator != std::end(subcommands_)) {
+ subcommands_.erase(iterator);
+ return true;
+ }
+ return false;
+ }
+ /// Check to see if a subcommand is part of this command (doesn't have to be in command line)
+ /// returns the first subcommand if passed a nullptr
+ App *get_subcommand(const App *subcom) const {
+ if(subcom == nullptr)
+ throw OptionNotFound("nullptr passed");
+ for(const App_p &subcomptr : subcommands_)
+ if(subcomptr.get() == subcom)
+ return subcomptr.get();
+ throw OptionNotFound(subcom->get_name());
+ }
+
+ /// Check to see if a subcommand is part of this command (text version)
+ App *get_subcommand(std::string subcom) const {
+ auto subc = _find_subcommand(subcom, false, false);
+ if(subc == nullptr)
+ throw OptionNotFound(subcom);
+ return subc;
+ }
+ /// Get a pointer to subcommand by index
+ App *get_subcommand(int index = 0) const {
+ if(index >= 0) {
+ auto uindex = static_cast<unsigned>(index);
+ if(uindex < subcommands_.size())
+ return subcommands_[uindex].get();
+ }
+ throw OptionNotFound(std::to_string(index));
+ }
+
+ /// Check to see if a subcommand is part of this command and get a shared_ptr to it
+ CLI::App_p get_subcommand_ptr(App *subcom) const {
+ if(subcom == nullptr)
+ throw OptionNotFound("nullptr passed");
+ for(const App_p &subcomptr : subcommands_)
+ if(subcomptr.get() == subcom)
+ return subcomptr;
+ throw OptionNotFound(subcom->get_name());
+ }
+
+ /// Check to see if a subcommand is part of this command (text version)
+ CLI::App_p get_subcommand_ptr(std::string subcom) const {
+ for(const App_p &subcomptr : subcommands_)
+ if(subcomptr->check_name(subcom))
+ return subcomptr;
+ throw OptionNotFound(subcom);
+ }
+
+ /// Get an owning pointer to subcommand by index
+ CLI::App_p get_subcommand_ptr(int index = 0) const {
+ if(index >= 0) {
+ auto uindex = static_cast<unsigned>(index);
+ if(uindex < subcommands_.size())
+ return subcommands_[uindex];
+ }
+ throw OptionNotFound(std::to_string(index));
+ }
+
+ /// Check to see if an option group is part of this App
+ App *get_option_group(std::string group_name) const {
+ for(const App_p &app : subcommands_) {
+ if(app->name_.empty() && app->group_ == group_name) {
+ return app.get();
+ }
+ }
+ throw OptionNotFound(group_name);
+ }
+
+ /// No argument version of count counts the number of times this subcommand was
+ /// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless
+ /// otherwise modified in a callback
+ std::size_t count() const { return parsed_; }
+
+ /// Get a count of all the arguments processed in options and subcommands, this excludes arguments which were
+ /// treated as extras.
+ std::size_t count_all() const {
+ std::size_t cnt{0};
+ for(auto &opt : options_) {
+ cnt += opt->count();
+ }
+ for(auto &sub : subcommands_) {
+ cnt += sub->count_all();
+ }
+ if(!get_name().empty()) { // for named subcommands add the number of times the subcommand was called
+ cnt += parsed_;
+ }
+ return cnt;
+ }
+
+ /// Changes the group membership
+ App *group(std::string group_name) {
+ group_ = group_name;
+ return this;
+ }
+
+ /// The argumentless form of require subcommand requires 1 or more subcommands
+ App *require_subcommand() {
+ require_subcommand_min_ = 1;
+ require_subcommand_max_ = 0;
+ return this;
+ }
+
+ /// Require a subcommand to be given (does not affect help call)
+ /// The number required can be given. Negative values indicate maximum
+ /// number allowed (0 for any number). Max number inheritable.
+ App *require_subcommand(int value) {
+ if(value < 0) {
+ require_subcommand_min_ = 0;
+ require_subcommand_max_ = static_cast<std::size_t>(-value);
+ } else {
+ require_subcommand_min_ = static_cast<std::size_t>(value);
+ require_subcommand_max_ = static_cast<std::size_t>(value);
+ }
+ return this;
+ }
+
+ /// Explicitly control the number of subcommands required. Setting 0
+ /// for the max means unlimited number allowed. Max number inheritable.
+ App *require_subcommand(std::size_t min, std::size_t max) {
+ require_subcommand_min_ = min;
+ require_subcommand_max_ = max;
+ return this;
+ }
+
+ /// The argumentless form of require option requires 1 or more options be used
+ App *require_option() {
+ require_option_min_ = 1;
+ require_option_max_ = 0;
+ return this;
+ }
+
+ /// Require an option to be given (does not affect help call)
+ /// The number required can be given. Negative values indicate maximum
+ /// number allowed (0 for any number).
+ App *require_option(int value) {
+ if(value < 0) {
+ require_option_min_ = 0;
+ require_option_max_ = static_cast<std::size_t>(-value);
+ } else {
+ require_option_min_ = static_cast<std::size_t>(value);
+ require_option_max_ = static_cast<std::size_t>(value);
+ }
+ return this;
+ }
+
+ /// Explicitly control the number of options required. Setting 0
+ /// for the max means unlimited number allowed. Max number inheritable.
+ App *require_option(std::size_t min, std::size_t max) {
+ require_option_min_ = min;
+ require_option_max_ = max;
+ return this;
+ }
+
+ /// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand.
+ /// Default from parent, usually set on parent.
+ App *fallthrough(bool value = true) {
+ fallthrough_ = value;
+ return this;
+ }
+
+ /// Check to see if this subcommand was parsed, true only if received on command line.
+ /// This allows the subcommand to be directly checked.
+ explicit operator bool() const { return parsed_ > 0; }
+
+ ///@}
+ /// @name Extras for subclassing
+ ///@{
+
+ /// This allows subclasses to inject code before callbacks but after parse.
+ ///
+ /// This does not run if any errors or help is thrown.
+ virtual void pre_callback() {}
+
+ ///@}
+ /// @name Parsing
+ ///@{
+ //
+ /// Reset the parsed data
+ void clear() {
+
+ parsed_ = 0;
+ pre_parse_called_ = false;
+
+ missing_.clear();
+ parsed_subcommands_.clear();
+ for(const Option_p &opt : options_) {
+ opt->clear();
+ }
+ for(const App_p &subc : subcommands_) {
+ subc->clear();
+ }
+ }
+
+ /// Parses the command line - throws errors.
+ /// This must be called after the options are in but before the rest of the program.
+ void parse(int argc, const char *const *argv) {
+ // If the name is not set, read from command line
+ if(name_.empty() || has_automatic_name_) {
+ has_automatic_name_ = true;
+ name_ = argv[0];
+ }
+
+ std::vector<std::string> args;
+ args.reserve(static_cast<std::size_t>(argc) - 1U);
+ for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i)
+ args.emplace_back(argv[i]);
+ parse(std::move(args));
+ }
+
+ /// Parse a single string as if it contained command line arguments.
+ /// This function splits the string into arguments then calls parse(std::vector<std::string> &)
+ /// the function takes an optional boolean argument specifying if the programName is included in the string to
+ /// process
+ void parse(std::string commandline, bool program_name_included = false) {
+
+ if(program_name_included) {
+ auto nstr = detail::split_program_name(commandline);
+ if((name_.empty()) || (has_automatic_name_)) {
+ has_automatic_name_ = true;
+ name_ = nstr.first;
+ }
+ commandline = std::move(nstr.second);
+ } else {
+ detail::trim(commandline);
+ }
+ // the next section of code is to deal with quoted arguments after an '=' or ':' for windows like operations
+ if(!commandline.empty()) {
+ commandline = detail::find_and_modify(commandline, "=", detail::escape_detect);
+ if(allow_windows_style_options_)
+ commandline = detail::find_and_modify(commandline, ":", detail::escape_detect);
+ }
+
+ auto args = detail::split_up(std::move(commandline));
+ // remove all empty strings
+ args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end());
+ std::reverse(args.begin(), args.end());
+
+ parse(std::move(args));
+ }
+
+ /// The real work is done here. Expects a reversed vector.
+ /// Changes the vector to the remaining options.
+ void parse(std::vector<std::string> &args) {
+ // Clear if parsed
+ if(parsed_ > 0)
+ clear();
+
+ // parsed_ is incremented in commands/subcommands,
+ // but placed here to make sure this is cleared when
+ // running parse after an error is thrown, even by _validate or _configure.
+ parsed_ = 1;
+ _validate();
+ _configure();
+ // set the parent as nullptr as this object should be the top now
+ parent_ = nullptr;
+ parsed_ = 0;
+
+ _parse(args);
+ run_callback();
+ }
+
+ /// The real work is done here. Expects a reversed vector.
+ void parse(std::vector<std::string> &&args) {
+ // Clear if parsed
+ if(parsed_ > 0)
+ clear();
+
+ // parsed_ is incremented in commands/subcommands,
+ // but placed here to make sure this is cleared when
+ // running parse after an error is thrown, even by _validate or _configure.
+ parsed_ = 1;
+ _validate();
+ _configure();
+ // set the parent as nullptr as this object should be the top now
+ parent_ = nullptr;
+ parsed_ = 0;
+
+ _parse(std::move(args));
+ run_callback();
+ }
+
+ void parse_from_stream(std::istream &input) {
+ if(parsed_ == 0) {
+ _validate();
+ _configure();
+ // set the parent as nullptr as this object should be the top now
+ }
+
+ _parse_stream(input);
+ run_callback();
+ }
+ /// Provide a function to print a help message. The function gets access to the App pointer and error.
+ void failure_message(std::function<std::string(const App *, const Error &e)> function) {
+ failure_message_ = function;
+ }
+
+ /// Print a nice error message and return the exit code
+ int exit(const Error &e, std::ostream &out = std::cout, std::ostream &err = std::cerr) const {
+
+ /// Avoid printing anything if this is a CLI::RuntimeError
+ if(e.get_name() == "RuntimeError")
+ return e.get_exit_code();
+
+ if(e.get_name() == "CallForHelp") {
+ out << help();
+ return e.get_exit_code();
+ }
+
+ if(e.get_name() == "CallForAllHelp") {
+ out << help("", AppFormatMode::All);
+ return e.get_exit_code();
+ }
+
+ if(e.get_name() == "CallForVersion") {
+ out << e.what() << std::endl;
+ return e.get_exit_code();
+ }
+
+ if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) {
+ if(failure_message_)
+ err << failure_message_(this, e) << std::flush;
+ }
+
+ return e.get_exit_code();
+ }
+
+ ///@}
+ /// @name Post parsing
+ ///@{
+
+ /// Counts the number of times the given option was passed.
+ std::size_t count(std::string option_name) const { return get_option(option_name)->count(); }
+
+ /// Get a subcommand pointer list to the currently selected subcommands (after parsing by default, in command
+ /// line order; use parsed = false to get the original definition list.)
+ std::vector<App *> get_subcommands() const { return parsed_subcommands_; }
+
+ /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all
+ /// subcommands (const)
+ std::vector<const App *> get_subcommands(const std::function<bool(const App *)> &filter) const {
+ std::vector<const App *> subcomms(subcommands_.size());
+ std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) {
+ return v.get();
+ });
+
+ if(filter) {
+ subcomms.erase(std::remove_if(std::begin(subcomms),
+ std::end(subcomms),
+ [&filter](const App *app) { return !filter(app); }),
+ std::end(subcomms));
+ }
+
+ return subcomms;
+ }
+
+ /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all
+ /// subcommands
+ std::vector<App *> get_subcommands(const std::function<bool(App *)> &filter) {
+ std::vector<App *> subcomms(subcommands_.size());
+ std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) {
+ return v.get();
+ });
+
+ if(filter) {
+ subcomms.erase(
+ std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App *app) { return !filter(app); }),
+ std::end(subcomms));
+ }
+
+ return subcomms;
+ }
+
+ /// Check to see if given subcommand was selected
+ bool got_subcommand(const App *subcom) const {
+ // get subcom needed to verify that this was a real subcommand
+ return get_subcommand(subcom)->parsed_ > 0;
+ }
+
+ /// Check with name instead of pointer to see if subcommand was selected
+ bool got_subcommand(std::string subcommand_name) const { return get_subcommand(subcommand_name)->parsed_ > 0; }
+
+ /// Sets excluded options for the subcommand
+ App *excludes(Option *opt) {
+ if(opt == nullptr) {
+ throw OptionNotFound("nullptr passed");
+ }
+ exclude_options_.insert(opt);
+ return this;
+ }
+
+ /// Sets excluded subcommands for the subcommand
+ App *excludes(App *app) {
+ if(app == nullptr) {
+ throw OptionNotFound("nullptr passed");
+ }
+ if(app == this) {
+ throw OptionNotFound("cannot self reference in needs");
+ }
+ auto res = exclude_subcommands_.insert(app);
+ // subcommand exclusion should be symmetric
+ if(res.second) {
+ app->exclude_subcommands_.insert(this);
+ }
+ return this;
+ }
+
+ App *needs(Option *opt) {
+ if(opt == nullptr) {
+ throw OptionNotFound("nullptr passed");
+ }
+ need_options_.insert(opt);
+ return this;
+ }
+
+ App *needs(App *app) {
+ if(app == nullptr) {
+ throw OptionNotFound("nullptr passed");
+ }
+ if(app == this) {
+ throw OptionNotFound("cannot self reference in needs");
+ }
+ need_subcommands_.insert(app);
+ return this;
+ }
+
+ /// Removes an option from the excludes list of this subcommand
+ bool remove_excludes(Option *opt) {
+ auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt);
+ if(iterator == std::end(exclude_options_)) {
+ return false;
+ }
+ exclude_options_.erase(iterator);
+ return true;
+ }
+
+ /// Removes a subcommand from the excludes list of this subcommand
+ bool remove_excludes(App *app) {
+ auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app);
+ if(iterator == std::end(exclude_subcommands_)) {
+ return false;
+ }
+ auto other_app = *iterator;
+ exclude_subcommands_.erase(iterator);
+ other_app->remove_excludes(this);
+ return true;
+ }
+
+ /// Removes an option from the needs list of this subcommand
+ bool remove_needs(Option *opt) {
+ auto iterator = std::find(std::begin(need_options_), std::end(need_options_), opt);
+ if(iterator == std::end(need_options_)) {
+ return false;
+ }
+ need_options_.erase(iterator);
+ return true;
+ }
+
+ /// Removes a subcommand from the needs list of this subcommand
+ bool remove_needs(App *app) {
+ auto iterator = std::find(std::begin(need_subcommands_), std::end(need_subcommands_), app);
+ if(iterator == std::end(need_subcommands_)) {
+ return false;
+ }
+ need_subcommands_.erase(iterator);
+ return true;
+ }
+
+ ///@}
+ /// @name Help
+ ///@{
+
+ /// Set footer.
+ App *footer(std::string footer_string) {
+ footer_ = std::move(footer_string);
+ return this;
+ }
+ /// Set footer.
+ App *footer(std::function<std::string()> footer_function) {
+ footer_callback_ = std::move(footer_function);
+ return this;
+ }
+ /// Produce a string that could be read in as a config of the current values of the App. Set default_also to
+ /// include default arguments. write_descriptions will print a description for the App and for each option.
+ std::string config_to_str(bool default_also = false, bool write_description = false) const {
+ return config_formatter_->to_config(this, default_also, write_description, "");
+ }
+
+ /// Makes a help message, using the currently configured formatter
+ /// Will only do one subcommand at a time
+ std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const {
+ if(prev.empty())
+ prev = get_name();
+ else
+ prev += " " + get_name();
+
+ // Delegate to subcommand if needed
+ auto selected_subcommands = get_subcommands();
+ if(!selected_subcommands.empty()) {
+ return selected_subcommands.at(0)->help(prev, mode);
+ }
+ return formatter_->make_help(this, prev, mode);
+ }
+
+ /// Displays a version string
+ std::string version() const {
+ std::string val;
+ if(version_ptr_ != nullptr) {
+ auto rv = version_ptr_->results();
+ version_ptr_->clear();
+ version_ptr_->add_result("true");
+ try {
+ version_ptr_->run_callback();
+ } catch(const CLI::CallForVersion &cfv) {
+ val = cfv.what();
+ }
+ version_ptr_->clear();
+ version_ptr_->add_result(rv);
+ }
+ return val;
+ }
+ ///@}
+ /// @name Getters
+ ///@{
+
+ /// Access the formatter
+ std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; }
+
+ /// Access the config formatter
+ std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; }
+
+ /// Access the config formatter as a configBase pointer
+ std::shared_ptr<ConfigBase> get_config_formatter_base() const {
+ // This is safer as a dynamic_cast if we have RTTI, as Config -> ConfigBase
+#if CLI11_USE_STATIC_RTTI == 0
+ return std::dynamic_pointer_cast<ConfigBase>(config_formatter_);
+#else
+ return std::static_pointer_cast<ConfigBase>(config_formatter_);
+#endif
+ }
+
+ /// Get the app or subcommand description
+ std::string get_description() const { return description_; }
+
+ /// Set the description of the app
+ App *description(std::string app_description) {
+ description_ = std::move(app_description);
+ return this;
+ }
+
+ /// Get the list of options (user facing function, so returns raw pointers), has optional filter function
+ std::vector<const Option *> get_options(const std::function<bool(const Option *)> filter = {}) const {
+ std::vector<const Option *> options(options_.size());
+ std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) {
+ return val.get();
+ });
+
+ if(filter) {
+ options.erase(std::remove_if(std::begin(options),
+ std::end(options),
+ [&filter](const Option *opt) { return !filter(opt); }),
+ std::end(options));
+ }
+
+ return options;
+ }
+
+ /// Non-const version of the above
+ std::vector<Option *> get_options(const std::function<bool(Option *)> filter = {}) {
+ std::vector<Option *> options(options_.size());
+ std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) {
+ return val.get();
+ });
+
+ if(filter) {
+ options.erase(
+ std::remove_if(std::begin(options), std::end(options), [&filter](Option *opt) { return !filter(opt); }),
+ std::end(options));
+ }
+
+ return options;
+ }
+
+ /// Get an option by name (noexcept non-const version)
+ Option *get_option_no_throw(std::string option_name) noexcept {
+ for(Option_p &opt : options_) {
+ if(opt->check_name(option_name)) {
+ return opt.get();
+ }
+ }
+ for(auto &subc : subcommands_) {
+ // also check down into nameless subcommands
+ if(subc->get_name().empty()) {
+ auto opt = subc->get_option_no_throw(option_name);
+ if(opt != nullptr) {
+ return opt;
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ /// Get an option by name (noexcept const version)
+ const Option *get_option_no_throw(std::string option_name) const noexcept {
+ for(const Option_p &opt : options_) {
+ if(opt->check_name(option_name)) {
+ return opt.get();
+ }
+ }
+ for(const auto &subc : subcommands_) {
+ // also check down into nameless subcommands
+ if(subc->get_name().empty()) {
+ auto opt = subc->get_option_no_throw(option_name);
+ if(opt != nullptr) {
+ return opt;
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ /// Get an option by name
+ const Option *get_option(std::string option_name) const {
+ auto opt = get_option_no_throw(option_name);
+ if(opt == nullptr) {
+ throw OptionNotFound(option_name);
+ }
+ return opt;
+ }
+
+ /// Get an option by name (non-const version)
+ Option *get_option(std::string option_name) {
+ auto opt = get_option_no_throw(option_name);
+ if(opt == nullptr) {
+ throw OptionNotFound(option_name);
+ }
+ return opt;
+ }
+
+ /// Shortcut bracket operator for getting a pointer to an option
+ const Option *operator[](const std::string &option_name) const { return get_option(option_name); }
+
+ /// Shortcut bracket operator for getting a pointer to an option
+ const Option *operator[](const char *option_name) const { return get_option(option_name); }
+
+ /// Check the status of ignore_case
+ bool get_ignore_case() const { return ignore_case_; }
+
+ /// Check the status of ignore_underscore
+ bool get_ignore_underscore() const { return ignore_underscore_; }
+
+ /// Check the status of fallthrough
+ bool get_fallthrough() const { return fallthrough_; }
+
+ /// Check the status of the allow windows style options
+ bool get_allow_windows_style_options() const { return allow_windows_style_options_; }
+
+ /// Check the status of the allow windows style options
+ bool get_positionals_at_end() const { return positionals_at_end_; }
+
+ /// Check the status of the allow windows style options
+ bool get_configurable() const { return configurable_; }
+
+ /// Get the group of this subcommand
+ const std::string &get_group() const { return group_; }
+
+ /// Generate and return the footer.
+ std::string get_footer() const { return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_; }
+
+ /// Get the required min subcommand value
+ std::size_t get_require_subcommand_min() const { return require_subcommand_min_; }
+
+ /// Get the required max subcommand value
+ std::size_t get_require_subcommand_max() const { return require_subcommand_max_; }
+
+ /// Get the required min option value
+ std::size_t get_require_option_min() const { return require_option_min_; }
+
+ /// Get the required max option value
+ std::size_t get_require_option_max() const { return require_option_max_; }
+
+ /// Get the prefix command status
+ bool get_prefix_command() const { return prefix_command_; }
+
+ /// Get the status of allow extras
+ bool get_allow_extras() const { return allow_extras_; }
+
+ /// Get the status of required
+ bool get_required() const { return required_; }
+
+ /// Get the status of disabled
+ bool get_disabled() const { return disabled_; }
+
+ /// Get the status of silence
+ bool get_silent() const { return silent_; }
+
+ /// Get the status of disabled
+ bool get_immediate_callback() const { return immediate_callback_; }
+
+ /// Get the status of disabled by default
+ bool get_disabled_by_default() const { return (default_startup == startup_mode::disabled); }
+
+ /// Get the status of disabled by default
+ bool get_enabled_by_default() const { return (default_startup == startup_mode::enabled); }
+ /// Get the status of validating positionals
+ bool get_validate_positionals() const { return validate_positionals_; }
+ /// Get the status of validating optional vector arguments
+ bool get_validate_optional_arguments() const { return validate_optional_arguments_; }
+
+ /// Get the status of allow extras
+ config_extras_mode get_allow_config_extras() const { return allow_config_extras_; }
+
+ /// Get a pointer to the help flag.
+ Option *get_help_ptr() { return help_ptr_; }
+
+ /// Get a pointer to the help flag. (const)
+ const Option *get_help_ptr() const { return help_ptr_; }
+
+ /// Get a pointer to the help all flag. (const)
+ const Option *get_help_all_ptr() const { return help_all_ptr_; }
+
+ /// Get a pointer to the config option.
+ Option *get_config_ptr() { return config_ptr_; }
+
+ /// Get a pointer to the config option. (const)
+ const Option *get_config_ptr() const { return config_ptr_; }
+
+ /// Get a pointer to the version option.
+ Option *get_version_ptr() { return version_ptr_; }
+
+ /// Get a pointer to the version option. (const)
+ const Option *get_version_ptr() const { return version_ptr_; }
+
+ /// Get the parent of this subcommand (or nullptr if main app)
+ App *get_parent() { return parent_; }
+
+ /// Get the parent of this subcommand (or nullptr if main app) (const version)
+ const App *get_parent() const { return parent_; }
+
+ /// Get the name of the current app
+ const std::string &get_name() const { return name_; }
+
+ /// Get the aliases of the current app
+ const std::vector<std::string> &get_aliases() const { return aliases_; }
+
+ /// clear all the aliases of the current App
+ App *clear_aliases() {
+ aliases_.clear();
+ return this;
+ }
+
+ /// Get a display name for an app
+ std::string get_display_name(bool with_aliases = false) const {
+ if(name_.empty()) {
+ return std::string("[Option Group: ") + get_group() + "]";
+ }
+ if(aliases_.empty() || !with_aliases) {
+ return name_;
+ }
+ std::string dispname = name_;
+ for(const auto &lalias : aliases_) {
+ dispname.push_back(',');
+ dispname.push_back(' ');
+ dispname.append(lalias);
+ }
+ return dispname;
+ }
+
+ /// Check the name, case insensitive and underscore insensitive if set
+ bool check_name(std::string name_to_check) const {
+ std::string local_name = name_;
+ if(ignore_underscore_) {
+ local_name = detail::remove_underscore(name_);
+ name_to_check = detail::remove_underscore(name_to_check);
+ }
+ if(ignore_case_) {
+ local_name = detail::to_lower(name_);
+ name_to_check = detail::to_lower(name_to_check);
+ }
+
+ if(local_name == name_to_check) {
+ return true;
+ }
+ for(auto les : aliases_) {
+ if(ignore_underscore_) {
+ les = detail::remove_underscore(les);
+ }
+ if(ignore_case_) {
+ les = detail::to_lower(les);
+ }
+ if(les == name_to_check) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// Get the groups available directly from this option (in order)
+ std::vector<std::string> get_groups() const {
+ std::vector<std::string> groups;
+
+ for(const Option_p &opt : options_) {
+ // Add group if it is not already in there
+ if(std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) {
+ groups.push_back(opt->get_group());
+ }
+ }
+
+ return groups;
+ }
+
+ /// This gets a vector of pointers with the original parse order
+ const std::vector<Option *> &parse_order() const { return parse_order_; }
+
+ /// This returns the missing options from the current subcommand
+ std::vector<std::string> remaining(bool recurse = false) const {
+ std::vector<std::string> miss_list;
+ for(const std::pair<detail::Classifier, std::string> &miss : missing_) {
+ miss_list.push_back(std::get<1>(miss));
+ }
+ // Get from a subcommand that may allow extras
+ if(recurse) {
+ if(!allow_extras_) {
+ for(const auto &sub : subcommands_) {
+ if(sub->name_.empty() && !sub->missing_.empty()) {
+ for(const std::pair<detail::Classifier, std::string> &miss : sub->missing_) {
+ miss_list.push_back(std::get<1>(miss));
+ }
+ }
+ }
+ }
+ // Recurse into subcommands
+
+ for(const App *sub : parsed_subcommands_) {
+ std::vector<std::string> output = sub->remaining(recurse);
+ std::copy(std::begin(output), std::end(output), std::back_inserter(miss_list));
+ }
+ }
+ return miss_list;
+ }
+
+ /// This returns the missing options in a form ready for processing by another command line program
+ std::vector<std::string> remaining_for_passthrough(bool recurse = false) const {
+ std::vector<std::string> miss_list = remaining(recurse);
+ std::reverse(std::begin(miss_list), std::end(miss_list));
+ return miss_list;
+ }
+
+ /// This returns the number of remaining options, minus the -- separator
+ std::size_t remaining_size(bool recurse = false) const {
+ auto remaining_options = static_cast<std::size_t>(std::count_if(
+ std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifier, std::string> &val) {
+ return val.first != detail::Classifier::POSITIONAL_MARK;
+ }));
+
+ if(recurse) {
+ for(const App_p &sub : subcommands_) {
+ remaining_options += sub->remaining_size(recurse);
+ }
+ }
+ return remaining_options;
+ }
+
+ ///@}
+
+ protected:
+ /// Check the options to make sure there are no conflicts.
+ ///
+ /// Currently checks to see if multiple positionals exist with unlimited args and checks if the min and max options
+ /// are feasible
+ void _validate() const {
+ // count the number of positional only args
+ auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
+ return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional();
+ });
+ if(pcount > 1) {
+ auto pcount_req = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
+ return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional() &&
+ opt->get_required();
+ });
+ if(pcount - pcount_req > 1) {
+ throw InvalidError(name_);
+ }
+ }
+
+ std::size_t nameless_subs{0};
+ for(const App_p &app : subcommands_) {
+ app->_validate();
+ if(app->get_name().empty())
+ ++nameless_subs;
+ }
+
+ if(require_option_min_ > 0) {
+ if(require_option_max_ > 0) {
+ if(require_option_max_ < require_option_min_) {
+ throw(InvalidError("Required min options greater than required max options",
+ ExitCodes::InvalidError));
+ }
+ }
+ if(require_option_min_ > (options_.size() + nameless_subs)) {
+ throw(InvalidError("Required min options greater than number of available options",
+ ExitCodes::InvalidError));
+ }
+ }
+ }
+
+ /// configure subcommands to enable parsing through the current object
+ /// set the correct fallthrough and prefix for nameless subcommands and manage the automatic enable or disable
+ /// makes sure parent is set correctly
+ void _configure() {
+ if(default_startup == startup_mode::enabled) {
+ disabled_ = false;
+ } else if(default_startup == startup_mode::disabled) {
+ disabled_ = true;
+ }
+ for(const App_p &app : subcommands_) {
+ if(app->has_automatic_name_) {
+ app->name_.clear();
+ }
+ if(app->name_.empty()) {
+ app->fallthrough_ = false; // make sure fallthrough_ is false to prevent infinite loop
+ app->prefix_command_ = false;
+ }
+ // make sure the parent is set to be this object in preparation for parse
+ app->parent_ = this;
+ app->_configure();
+ }
+ }
+
+ /// Internal function to run (App) callback, bottom up
+ void run_callback(bool final_mode = false, bool suppress_final_callback = false) {
+ pre_callback();
+ // in the main app if immediate_callback_ is set it runs the main callback before the used subcommands
+ if(!final_mode && parse_complete_callback_) {
+ parse_complete_callback_();
+ }
+ // run the callbacks for the received subcommands
+ for(App *subc : get_subcommands()) {
+ if(subc->parent_ == this) {
+ subc->run_callback(true, suppress_final_callback);
+ }
+ }
+ // now run callbacks for option_groups
+ for(auto &subc : subcommands_) {
+ if(subc->name_.empty() && subc->count_all() > 0) {
+ subc->run_callback(true, suppress_final_callback);
+ }
+ }
+
+ // finally run the main callback
+ if(final_callback_ && (parsed_ > 0) && (!suppress_final_callback)) {
+ if(!name_.empty() || count_all() > 0 || parent_ == nullptr) {
+ final_callback_();
+ }
+ }
+ }
+
+ /// Check to see if a subcommand is valid. Give up immediately if subcommand max has been reached.
+ bool _valid_subcommand(const std::string &current, bool ignore_used = true) const {
+ // Don't match if max has been reached - but still check parents
+ if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_) {
+ return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
+ }
+ auto com = _find_subcommand(current, true, ignore_used);
+ if(com != nullptr) {
+ return true;
+ }
+ // Check parent if exists, else return false
+ return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
+ }
+
+ /// Selects a Classifier enum based on the type of the current argument
+ detail::Classifier _recognize(const std::string &current, bool ignore_used_subcommands = true) const {
+ std::string dummy1, dummy2;
+
+ if(current == "--")
+ return detail::Classifier::POSITIONAL_MARK;
+ if(_valid_subcommand(current, ignore_used_subcommands))
+ return detail::Classifier::SUBCOMMAND;
+ if(detail::split_long(current, dummy1, dummy2))
+ return detail::Classifier::LONG;
+ if(detail::split_short(current, dummy1, dummy2)) {
+ if(dummy1[0] >= '0' && dummy1[0] <= '9') {
+ if(get_option_no_throw(std::string{'-', dummy1[0]}) == nullptr) {
+ return detail::Classifier::NONE;
+ }
+ }
+ return detail::Classifier::SHORT;
+ }
+ if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
+ return detail::Classifier::WINDOWS_STYLE;
+ if((current == "++") && !name_.empty() && parent_ != nullptr)
+ return detail::Classifier::SUBCOMMAND_TERMINATOR;
+ return detail::Classifier::NONE;
+ }
+
+ // The parse function is now broken into several parts, and part of process
+
+ /// Read and process a configuration file (main app only)
+ void _process_config_file() {
+ if(config_ptr_ != nullptr) {
+ bool config_required = config_ptr_->get_required();
+ auto file_given = config_ptr_->count() > 0;
+ auto config_files = config_ptr_->as<std::vector<std::string>>();
+ if(config_files.empty() || config_files.front().empty()) {
+ if(config_required) {
+ throw FileError::Missing("no specified config file");
+ }
+ return;
+ }
+ for(auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit) {
+ const auto &config_file = *rit;
+ auto path_result = detail::check_path(config_file.c_str());
+ if(path_result == detail::path_type::file) {
+ try {
+ std::vector<ConfigItem> values = config_formatter_->from_file(config_file);
+ _parse_config(values);
+ if(!file_given) {
+ config_ptr_->add_result(config_file);
+ }
+ } catch(const FileError &) {
+ if(config_required || file_given)
+ throw;
+ }
+ } else if(config_required || file_given) {
+ throw FileError::Missing(config_file);
+ }
+ }
+ }
+ }
+
+ /// Get envname options if not yet passed. Runs on *all* subcommands.
+ void _process_env() {
+ for(const Option_p &opt : options_) {
+ if(opt->count() == 0 && !opt->envname_.empty()) {
+ char *buffer = nullptr;
+ std::string ename_string;
+
+#ifdef _MSC_VER
+ // Windows version
+ std::size_t sz = 0;
+ if(_dupenv_s(&buffer, &sz, opt->envname_.c_str()) == 0 && buffer != nullptr) {
+ ename_string = std::string(buffer);
+ free(buffer);
+ }
+#else
+ // This also works on Windows, but gives a warning
+ buffer = std::getenv(opt->envname_.c_str());
+ if(buffer != nullptr)
+ ename_string = std::string(buffer);
+#endif
+
+ if(!ename_string.empty()) {
+ opt->add_result(ename_string);
+ }
+ }
+ }
+
+ for(App_p &sub : subcommands_) {
+ if(sub->get_name().empty() || !sub->parse_complete_callback_)
+ sub->_process_env();
+ }
+ }
+
+ /// Process callbacks. Runs on *all* subcommands.
+ void _process_callbacks() {
+
+ for(App_p &sub : subcommands_) {
+ // process the priority option_groups first
+ if(sub->get_name().empty() && sub->parse_complete_callback_) {
+ if(sub->count_all() > 0) {
+ sub->_process_callbacks();
+ sub->run_callback();
+ }
+ }
+ }
+
+ for(const Option_p &opt : options_) {
+ if((*opt) && !opt->get_callback_run()) {
+ opt->run_callback();
+ }
+ }
+ for(App_p &sub : subcommands_) {
+ if(!sub->parse_complete_callback_) {
+ sub->_process_callbacks();
+ }
+ }
+ }
+
+ /// Run help flag processing if any are found.
+ ///
+ /// The flags allow recursive calls to remember if there was a help flag on a parent.
+ void _process_help_flags(bool trigger_help = false, bool trigger_all_help = false) const {
+ const Option *help_ptr = get_help_ptr();
+ const Option *help_all_ptr = get_help_all_ptr();
+
+ if(help_ptr != nullptr && help_ptr->count() > 0)
+ trigger_help = true;
+ if(help_all_ptr != nullptr && help_all_ptr->count() > 0)
+ trigger_all_help = true;
+
+ // If there were parsed subcommands, call those. First subcommand wins if there are multiple ones.
+ if(!parsed_subcommands_.empty()) {
+ for(const App *sub : parsed_subcommands_)
+ sub->_process_help_flags(trigger_help, trigger_all_help);
+
+ // Only the final subcommand should call for help. All help wins over help.
+ } else if(trigger_all_help) {
+ throw CallForAllHelp();
+ } else if(trigger_help) {
+ throw CallForHelp();
+ }
+ }
+
+ /// Verify required options and cross requirements. Subcommands too (only if selected).
+ void _process_requirements() {
+ // check excludes
+ bool excluded{false};
+ std::string excluder;
+ for(auto &opt : exclude_options_) {
+ if(opt->count() > 0) {
+ excluded = true;
+ excluder = opt->get_name();
+ }
+ }
+ for(auto &subc : exclude_subcommands_) {
+ if(subc->count_all() > 0) {
+ excluded = true;
+ excluder = subc->get_display_name();
+ }
+ }
+ if(excluded) {
+ if(count_all() > 0) {
+ throw ExcludesError(get_display_name(), excluder);
+ }
+ // if we are excluded but didn't receive anything, just return
+ return;
+ }
+
+ // check excludes
+ bool missing_needed{false};
+ std::string missing_need;
+ for(auto &opt : need_options_) {
+ if(opt->count() == 0) {
+ missing_needed = true;
+ missing_need = opt->get_name();
+ }
+ }
+ for(auto &subc : need_subcommands_) {
+ if(subc->count_all() == 0) {
+ missing_needed = true;
+ missing_need = subc->get_display_name();
+ }
+ }
+ if(missing_needed) {
+ if(count_all() > 0) {
+ throw RequiresError(get_display_name(), missing_need);
+ }
+ // if we missing something but didn't have any options, just return
+ return;
+ }
+
+ std::size_t used_options = 0;
+ for(const Option_p &opt : options_) {
+
+ if(opt->count() != 0) {
+ ++used_options;
+ }
+ // Required but empty
+ if(opt->get_required() && opt->count() == 0) {
+ throw RequiredError(opt->get_name());
+ }
+ // Requires
+ for(const Option *opt_req : opt->needs_)
+ if(opt->count() > 0 && opt_req->count() == 0)
+ throw RequiresError(opt->get_name(), opt_req->get_name());
+ // Excludes
+ for(const Option *opt_ex : opt->excludes_)
+ if(opt->count() > 0 && opt_ex->count() != 0)
+ throw ExcludesError(opt->get_name(), opt_ex->get_name());
+ }
+ // check for the required number of subcommands
+ if(require_subcommand_min_ > 0) {
+ auto selected_subcommands = get_subcommands();
+ if(require_subcommand_min_ > selected_subcommands.size())
+ throw RequiredError::Subcommand(require_subcommand_min_);
+ }
+
+ // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.
+
+ // run this loop to check how many unnamed subcommands were actually used since they are considered options
+ // from the perspective of an App
+ for(App_p &sub : subcommands_) {
+ if(sub->disabled_)
+ continue;
+ if(sub->name_.empty() && sub->count_all() > 0) {
+ ++used_options;
+ }
+ }
+
+ if(require_option_min_ > used_options || (require_option_max_ > 0 && require_option_max_ < used_options)) {
+ auto option_list = detail::join(options_, [this](const Option_p &ptr) {
+ if(ptr.get() == help_ptr_ || ptr.get() == help_all_ptr_) {
+ return std::string{};
+ }
+ return ptr->get_name(false, true);
+ });
+
+ auto subc_list = get_subcommands([](App *app) { return ((app->get_name().empty()) && (!app->disabled_)); });
+ if(!subc_list.empty()) {
+ option_list += "," + detail::join(subc_list, [](const App *app) { return app->get_display_name(); });
+ }
+ throw RequiredError::Option(require_option_min_, require_option_max_, used_options, option_list);
+ }
+
+ // now process the requirements for subcommands if needed
+ for(App_p &sub : subcommands_) {
+ if(sub->disabled_)
+ continue;
+ if(sub->name_.empty() && sub->required_ == false) {
+ if(sub->count_all() == 0) {
+ if(require_option_min_ > 0 && require_option_min_ <= used_options) {
+ continue;
+ // if we have met the requirement and there is nothing in this option group skip checking
+ // requirements
+ }
+ if(require_option_max_ > 0 && used_options >= require_option_min_) {
+ continue;
+ // if we have met the requirement and there is nothing in this option group skip checking
+ // requirements
+ }
+ }
+ }
+ if(sub->count() > 0 || sub->name_.empty()) {
+ sub->_process_requirements();
+ }
+
+ if(sub->required_ && sub->count_all() == 0) {
+ throw(CLI::RequiredError(sub->get_display_name()));
+ }
+ }
+ }
+
+ /// Process callbacks and such.
+ void _process() {
+ try {
+ // the config file might generate a FileError but that should not be processed until later in the process
+ // to allow for help, version and other errors to generate first.
+ _process_config_file();
+
+ // process env shouldn't throw but no reason to process it if config generated an error
+ _process_env();
+ } catch(const CLI::FileError &) {
+ // callbacks and help_flags can generate exceptions which should take priority
+ // over the config file error if one exists.
+ _process_callbacks();
+ _process_help_flags();
+ throw;
+ }
+
+ _process_callbacks();
+ _process_help_flags();
+
+ _process_requirements();
+ }
+
+ /// Throw an error if anything is left over and should not be.
+ void _process_extras() {
+ if(!(allow_extras_ || prefix_command_)) {
+ std::size_t num_left_over = remaining_size();
+ if(num_left_over > 0) {
+ throw ExtrasError(name_, remaining(false));
+ }
+ }
+
+ for(App_p &sub : subcommands_) {
+ if(sub->count() > 0)
+ sub->_process_extras();
+ }
+ }
+
+ /// Throw an error if anything is left over and should not be.
+ /// Modifies the args to fill in the missing items before throwing.
+ void _process_extras(std::vector<std::string> &args) {
+ if(!(allow_extras_ || prefix_command_)) {
+ std::size_t num_left_over = remaining_size();
+ if(num_left_over > 0) {
+ args = remaining(false);
+ throw ExtrasError(name_, args);
+ }
+ }
+
+ for(App_p &sub : subcommands_) {
+ if(sub->count() > 0)
+ sub->_process_extras(args);
+ }
+ }
+
+ /// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands
+ void increment_parsed() {
+ ++parsed_;
+ for(App_p &sub : subcommands_) {
+ if(sub->get_name().empty())
+ sub->increment_parsed();
+ }
+ }
+ /// Internal parse function
+ void _parse(std::vector<std::string> &args) {
+ increment_parsed();
+ _trigger_pre_parse(args.size());
+ bool positional_only = false;
+
+ while(!args.empty()) {
+ if(!_parse_single(args, positional_only)) {
+ break;
+ }
+ }
+
+ if(parent_ == nullptr) {
+ _process();
+
+ // Throw error if any items are left over (depending on settings)
+ _process_extras(args);
+
+ // Convert missing (pairs) to extras (string only) ready for processing in another app
+ args = remaining_for_passthrough(false);
+ } else if(parse_complete_callback_) {
+ _process_env();
+ _process_callbacks();
+ _process_help_flags();
+ _process_requirements();
+ run_callback(false, true);
+ }
+ }
+
+ /// Internal parse function
+ void _parse(std::vector<std::string> &&args) {
+ // this can only be called by the top level in which case parent == nullptr by definition
+ // operation is simplified
+ increment_parsed();
+ _trigger_pre_parse(args.size());
+ bool positional_only = false;
+
+ while(!args.empty()) {
+ _parse_single(args, positional_only);
+ }
+ _process();
+
+ // Throw error if any items are left over (depending on settings)
+ _process_extras();
+ }
+
+ /// Internal function to parse a stream
+ void _parse_stream(std::istream &input) {
+ auto values = config_formatter_->from_config(input);
+ _parse_config(values);
+ increment_parsed();
+ _trigger_pre_parse(values.size());
+ _process();
+
+ // Throw error if any items are left over (depending on settings)
+ _process_extras();
+ }
+
+ /// Parse one config param, return false if not found in any subcommand, remove if it is
+ ///
+ /// If this has more than one dot.separated.name, go into the subcommand matching it
+ /// Returns true if it managed to find the option, if false you'll need to remove the arg manually.
+ void _parse_config(const std::vector<ConfigItem> &args) {
+ for(const ConfigItem &item : args) {
+ if(!_parse_single_config(item) && allow_config_extras_ == config_extras_mode::error)
+ throw ConfigError::Extras(item.fullname());
+ }
+ }
+
+ /// Fill in a single config option
+ bool _parse_single_config(const ConfigItem &item, std::size_t level = 0) {
+ if(level < item.parents.size()) {
+ try {
+ auto subcom = get_subcommand(item.parents.at(level));
+ auto result = subcom->_parse_single_config(item, level + 1);
+
+ return result;
+ } catch(const OptionNotFound &) {
+ return false;
+ }
+ }
+ // check for section open
+ if(item.name == "++") {
+ if(configurable_) {
+ increment_parsed();
+ _trigger_pre_parse(2);
+ if(parent_ != nullptr) {
+ parent_->parsed_subcommands_.push_back(this);
+ }
+ }
+ return true;
+ }
+ // check for section close
+ if(item.name == "--") {
+ if(configurable_) {
+ _process_callbacks();
+ _process_requirements();
+ run_callback();
+ }
+ return true;
+ }
+ Option *op = get_option_no_throw("--" + item.name);
+ if(op == nullptr) {
+ if(item.name.size() == 1) {
+ op = get_option_no_throw("-" + item.name);
+ }
+ }
+ if(op == nullptr) {
+ op = get_option_no_throw(item.name);
+ }
+ if(op == nullptr) {
+ // If the option was not present
+ if(get_allow_config_extras() == config_extras_mode::capture)
+ // Should we worry about classifying the extras properly?
+ missing_.emplace_back(detail::Classifier::NONE, item.fullname());
+ return false;
+ }
+
+ if(!op->get_configurable()) {
+ if(get_allow_config_extras() == config_extras_mode::ignore_all) {
+ return false;
+ }
+ throw ConfigError::NotConfigurable(item.fullname());
+ }
+
+ if(op->empty()) {
+
+ if(op->get_expected_min() == 0) {
+ // Flag parsing
+ auto res = config_formatter_->to_flag(item);
+ res = op->get_flag_value(item.name, res);
+
+ op->add_result(res);
+
+ } else {
+ op->add_result(item.inputs);
+ op->run_callback();
+ }
+ }
+
+ return true;
+ }
+
+ /// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing
+ /// from main return false if the parse has failed and needs to return to parent
+ bool _parse_single(std::vector<std::string> &args, bool &positional_only) {
+ bool retval = true;
+ detail::Classifier classifier = positional_only ? detail::Classifier::NONE : _recognize(args.back());
+ switch(classifier) {
+ case detail::Classifier::POSITIONAL_MARK:
+ args.pop_back();
+ positional_only = true;
+ if((!_has_remaining_positionals()) && (parent_ != nullptr)) {
+ retval = false;
+ } else {
+ _move_to_missing(classifier, "--");
+ }
+ break;
+ case detail::Classifier::SUBCOMMAND_TERMINATOR:
+ // treat this like a positional mark if in the parent app
+ args.pop_back();
+ retval = false;
+ break;
+ case detail::Classifier::SUBCOMMAND:
+ retval = _parse_subcommand(args);
+ break;
+ case detail::Classifier::LONG:
+ case detail::Classifier::SHORT:
+ case detail::Classifier::WINDOWS_STYLE:
+ // If already parsed a subcommand, don't accept options_
+ _parse_arg(args, classifier);
+ break;
+ case detail::Classifier::NONE:
+ // Probably a positional or something for a parent (sub)command
+ retval = _parse_positional(args, false);
+ if(retval && positionals_at_end_) {
+ positional_only = true;
+ }
+ break;
+ // LCOV_EXCL_START
+ default:
+ throw HorribleError("unrecognized classifier (you should not see this!)");
+ // LCOV_EXCL_STOP
+ }
+ return retval;
+ }
+
+ /// Count the required remaining positional arguments
+ std::size_t _count_remaining_positionals(bool required_only = false) const {
+ std::size_t retval = 0;
+ for(const Option_p &opt : options_) {
+ if(opt->get_positional() && (!required_only || opt->get_required())) {
+ if(opt->get_items_expected_min() > 0 &&
+ static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
+ retval += static_cast<std::size_t>(opt->get_items_expected_min()) - opt->count();
+ }
+ }
+ }
+ return retval;
+ }
+
+ /// Count the required remaining positional arguments
+ bool _has_remaining_positionals() const {
+ for(const Option_p &opt : options_) {
+ if(opt->get_positional() && ((static_cast<int>(opt->count()) < opt->get_items_expected_min()))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// Parse a positional, go up the tree to check
+ /// @param haltOnSubcommand if set to true the operation will not process subcommands merely return false
+ /// Return true if the positional was used false otherwise
+ bool _parse_positional(std::vector<std::string> &args, bool haltOnSubcommand) {
+
+ const std::string &positional = args.back();
+
+ if(positionals_at_end_) {
+ // deal with the case of required arguments at the end which should take precedence over other arguments
+ auto arg_rem = args.size();
+ auto remreq = _count_remaining_positionals(true);
+ if(arg_rem <= remreq) {
+ for(const Option_p &opt : options_) {
+ if(opt->get_positional() && opt->required_) {
+ if(static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
+ if(validate_positionals_) {
+ std::string pos = positional;
+ pos = opt->_validate(pos, 0);
+ if(!pos.empty()) {
+ continue;
+ }
+ }
+
+ parse_order_.push_back(opt.get());
+ /// if we require a separator add it here
+ if(opt->get_inject_separator()) {
+ if(!opt->results().empty() && !opt->results().back().empty()) {
+ opt->add_result(std::string{});
+ }
+ }
+ if(opt->get_trigger_on_parse() &&
+ opt->current_option_state_ == Option::option_state::callback_run) {
+ opt->clear();
+ }
+ opt->add_result(positional);
+ if(opt->get_trigger_on_parse()) {
+ opt->run_callback();
+ }
+ args.pop_back();
+ return true;
+ }
+ }
+ }
+ }
+ }
+ for(const Option_p &opt : options_) {
+ // Eat options, one by one, until done
+ if(opt->get_positional() &&
+ (static_cast<int>(opt->count()) < opt->get_items_expected_min() || opt->get_allow_extra_args())) {
+ if(validate_positionals_) {
+ std::string pos = positional;
+ pos = opt->_validate(pos, 0);
+ if(!pos.empty()) {
+ continue;
+ }
+ }
+ if(opt->get_inject_separator()) {
+ if(!opt->results().empty() && !opt->results().back().empty()) {
+ opt->add_result(std::string{});
+ }
+ }
+ if(opt->get_trigger_on_parse() && opt->current_option_state_ == Option::option_state::callback_run) {
+ opt->clear();
+ }
+ opt->add_result(positional);
+ if(opt->get_trigger_on_parse()) {
+ opt->run_callback();
+ }
+ parse_order_.push_back(opt.get());
+ args.pop_back();
+ return true;
+ }
+ }
+
+ for(auto &subc : subcommands_) {
+ if((subc->name_.empty()) && (!subc->disabled_)) {
+ if(subc->_parse_positional(args, false)) {
+ if(!subc->pre_parse_called_) {
+ subc->_trigger_pre_parse(args.size());
+ }
+ return true;
+ }
+ }
+ }
+ // let the parent deal with it if possible
+ if(parent_ != nullptr && fallthrough_)
+ return _get_fallthrough_parent()->_parse_positional(args, static_cast<bool>(parse_complete_callback_));
+
+ /// Try to find a local subcommand that is repeated
+ auto com = _find_subcommand(args.back(), true, false);
+ if(com != nullptr && (require_subcommand_max_ == 0 || require_subcommand_max_ > parsed_subcommands_.size())) {
+ if(haltOnSubcommand) {
+ return false;
+ }
+ args.pop_back();
+ com->_parse(args);
+ return true;
+ }
+ /// now try one last gasp at subcommands that have been executed before, go to root app and try to find a
+ /// subcommand in a broader way, if one exists let the parent deal with it
+ auto parent_app = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
+ com = parent_app->_find_subcommand(args.back(), true, false);
+ if(com != nullptr && (com->parent_->require_subcommand_max_ == 0 ||
+ com->parent_->require_subcommand_max_ > com->parent_->parsed_subcommands_.size())) {
+ return false;
+ }
+
+ if(positionals_at_end_) {
+ throw CLI::ExtrasError(name_, args);
+ }
+ /// If this is an option group don't deal with it
+ if(parent_ != nullptr && name_.empty()) {
+ return false;
+ }
+ /// We are out of other options this goes to missing
+ _move_to_missing(detail::Classifier::NONE, positional);
+ args.pop_back();
+ if(prefix_command_) {
+ while(!args.empty()) {
+ _move_to_missing(detail::Classifier::NONE, args.back());
+ args.pop_back();
+ }
+ }
+
+ return true;
+ }
+
+ /// Locate a subcommand by name with two conditions, should disabled subcommands be ignored, and should used
+ /// subcommands be ignored
+ App *_find_subcommand(const std::string &subc_name, bool ignore_disabled, bool ignore_used) const noexcept {
+ for(const App_p &com : subcommands_) {
+ if(com->disabled_ && ignore_disabled)
+ continue;
+ if(com->get_name().empty()) {
+ auto subc = com->_find_subcommand(subc_name, ignore_disabled, ignore_used);
+ if(subc != nullptr) {
+ return subc;
+ }
+ }
+ if(com->check_name(subc_name)) {
+ if((!*com) || !ignore_used)
+ return com.get();
+ }
+ }
+ return nullptr;
+ }
+
+ /// Parse a subcommand, modify args and continue
+ ///
+ /// Unlike the others, this one will always allow fallthrough
+ /// return true if the subcommand was processed false otherwise
+ bool _parse_subcommand(std::vector<std::string> &args) {
+ if(_count_remaining_positionals(/* required */ true) > 0) {
+ _parse_positional(args, false);
+ return true;
+ }
+ auto com = _find_subcommand(args.back(), true, true);
+ if(com != nullptr) {
+ args.pop_back();
+ if(!com->silent_) {
+ parsed_subcommands_.push_back(com);
+ }
+ com->_parse(args);
+ auto parent_app = com->parent_;
+ while(parent_app != this) {
+ parent_app->_trigger_pre_parse(args.size());
+ if(!com->silent_) {
+ parent_app->parsed_subcommands_.push_back(com);
+ }
+ parent_app = parent_app->parent_;
+ }
+ return true;
+ }
+
+ if(parent_ == nullptr)
+ throw HorribleError("Subcommand " + args.back() + " missing");
+ return false;
+ }
+
+ /// Parse a short (false) or long (true) argument, must be at the top of the list
+ /// return true if the argument was processed or false if nothing was done
+ bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type) {
+
+ std::string current = args.back();
+
+ std::string arg_name;
+ std::string value;
+ std::string rest;
+
+ switch(current_type) {
+ case detail::Classifier::LONG:
+ if(!detail::split_long(current, arg_name, value))
+ throw HorribleError("Long parsed but missing (you should not see this):" + args.back());
+ break;
+ case detail::Classifier::SHORT:
+ if(!detail::split_short(current, arg_name, rest))
+ throw HorribleError("Short parsed but missing! You should not see this");
+ break;
+ case detail::Classifier::WINDOWS_STYLE:
+ if(!detail::split_windows_style(current, arg_name, value))
+ throw HorribleError("windows option parsed but missing! You should not see this");
+ break;
+ case detail::Classifier::SUBCOMMAND:
+ case detail::Classifier::SUBCOMMAND_TERMINATOR:
+ case detail::Classifier::POSITIONAL_MARK:
+ case detail::Classifier::NONE:
+ default:
+ throw HorribleError("parsing got called with invalid option! You should not see this");
+ }
+
+ auto op_ptr =
+ std::find_if(std::begin(options_), std::end(options_), [arg_name, current_type](const Option_p &opt) {
+ if(current_type == detail::Classifier::LONG)
+ return opt->check_lname(arg_name);
+ if(current_type == detail::Classifier::SHORT)
+ return opt->check_sname(arg_name);
+ // this will only get called for detail::Classifier::WINDOWS_STYLE
+ return opt->check_lname(arg_name) || opt->check_sname(arg_name);
+ });
+
+ // Option not found
+ if(op_ptr == std::end(options_)) {
+ for(auto &subc : subcommands_) {
+ if(subc->name_.empty() && !subc->disabled_) {
+ if(subc->_parse_arg(args, current_type)) {
+ if(!subc->pre_parse_called_) {
+ subc->_trigger_pre_parse(args.size());
+ }
+ return true;
+ }
+ }
+ }
+
+ // don't capture missing if this is a nameless subcommand and nameless subcommands can't fallthrough
+ if(parent_ != nullptr && name_.empty()) {
+ return false;
+ }
+
+ // If a subcommand, try the main command
+ if(parent_ != nullptr && fallthrough_)
+ return _get_fallthrough_parent()->_parse_arg(args, current_type);
+
+ // Otherwise, add to missing
+ args.pop_back();
+ _move_to_missing(current_type, current);
+ return true;
+ }
+
+ args.pop_back();
+
+ // Get a reference to the pointer to make syntax bearable
+ Option_p &op = *op_ptr;
+ /// if we require a separator add it here
+ if(op->get_inject_separator()) {
+ if(!op->results().empty() && !op->results().back().empty()) {
+ op->add_result(std::string{});
+ }
+ }
+ if(op->get_trigger_on_parse() && op->current_option_state_ == Option::option_state::callback_run) {
+ op->clear();
+ }
+ int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min());
+ int max_num = op->get_items_expected_max();
+ // check container like options to limit the argument size to a single type if the allow_extra_flags argument is
+ // set. 16 is somewhat arbitrary (needs to be at least 4)
+ if(max_num >= detail::expected_max_vector_size / 16 && !op->get_allow_extra_args()) {
+ auto tmax = op->get_type_size_max();
+ max_num = detail::checked_multiply(tmax, op->get_expected_min()) ? tmax : detail::expected_max_vector_size;
+ }
+ // Make sure we always eat the minimum for unlimited vectors
+ int collected = 0; // total number of arguments collected
+ int result_count = 0; // local variable for number of results in a single arg string
+ // deal with purely flag like things
+ if(max_num == 0) {
+ auto res = op->get_flag_value(arg_name, value);
+ op->add_result(res);
+ parse_order_.push_back(op.get());
+ } else if(!value.empty()) { // --this=value
+ op->add_result(value, result_count);
+ parse_order_.push_back(op.get());
+ collected += result_count;
+ // -Trest
+ } else if(!rest.empty()) {
+ op->add_result(rest, result_count);
+ parse_order_.push_back(op.get());
+ rest = "";
+ collected += result_count;
+ }
+
+ // gather the minimum number of arguments
+ while(min_num > collected && !args.empty()) {
+ std::string current_ = args.back();
+ args.pop_back();
+ op->add_result(current_, result_count);
+ parse_order_.push_back(op.get());
+ collected += result_count;
+ }
+
+ if(min_num > collected) { // if we have run out of arguments and the minimum was not met
+ throw ArgumentMismatch::TypedAtLeast(op->get_name(), min_num, op->get_type_name());
+ }
+
+ // now check for optional arguments
+ if(max_num > collected || op->get_allow_extra_args()) { // we allow optional arguments
+ auto remreqpos = _count_remaining_positionals(true);
+ // we have met the minimum now optionally check up to the maximum
+ while((collected < max_num || op->get_allow_extra_args()) && !args.empty() &&
+ _recognize(args.back(), false) == detail::Classifier::NONE) {
+ // If any required positionals remain, don't keep eating
+ if(remreqpos >= args.size()) {
+ break;
+ }
+ if(validate_optional_arguments_) {
+ std::string optarg = args.back();
+ optarg = op->_validate(optarg, 0);
+ if(!optarg.empty()) {
+ break;
+ }
+ }
+ op->add_result(args.back(), result_count);
+ parse_order_.push_back(op.get());
+ args.pop_back();
+ collected += result_count;
+ }
+
+ // Allow -- to end an unlimited list and "eat" it
+ if(!args.empty() && _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK)
+ args.pop_back();
+ // optional flag that didn't receive anything now get the default value
+ if(min_num == 0 && max_num > 0 && collected == 0) {
+ auto res = op->get_flag_value(arg_name, std::string{});
+ op->add_result(res);
+ parse_order_.push_back(op.get());
+ }
+ }
+ // if we only partially completed a type then add an empty string if allowed for later processing
+ if(min_num > 0 && (collected % op->get_type_size_max()) != 0) {
+ if(op->get_type_size_max() != op->get_type_size_min()) {
+ op->add_result(std::string{});
+ } else {
+ throw ArgumentMismatch::PartialType(op->get_name(), op->get_type_size_min(), op->get_type_name());
+ }
+ }
+ if(op->get_trigger_on_parse()) {
+ op->run_callback();
+ }
+ if(!rest.empty()) {
+ rest = "-" + rest;
+ args.push_back(rest);
+ }
+ return true;
+ }
+
+ /// Trigger the pre_parse callback if needed
+ void _trigger_pre_parse(std::size_t remaining_args) {
+ if(!pre_parse_called_) {
+ pre_parse_called_ = true;
+ if(pre_parse_callback_) {
+ pre_parse_callback_(remaining_args);
+ }
+ } else if(immediate_callback_) {
+ if(!name_.empty()) {
+ auto pcnt = parsed_;
+ auto extras = std::move(missing_);
+ clear();
+ parsed_ = pcnt;
+ pre_parse_called_ = true;
+ missing_ = std::move(extras);
+ }
+ }
+ }
+
+ /// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app
+ App *_get_fallthrough_parent() {
+ if(parent_ == nullptr) {
+ throw(HorribleError("No Valid parent"));
+ }
+ auto fallthrough_parent = parent_;
+ while((fallthrough_parent->parent_ != nullptr) && (fallthrough_parent->get_name().empty())) {
+ fallthrough_parent = fallthrough_parent->parent_;
+ }
+ return fallthrough_parent;
+ }
+
+ /// Helper function to run through all possible comparisons of subcommand names to check there is no overlap
+ const std::string &_compare_subcommand_names(const App &subcom, const App &base) const {
+ static const std::string estring;
+ if(subcom.disabled_) {
+ return estring;
+ }
+ for(auto &subc : base.subcommands_) {
+ if(subc.get() != &subcom) {
+ if(subc->disabled_) {
+ continue;
+ }
+ if(!subcom.get_name().empty()) {
+ if(subc->check_name(subcom.get_name())) {
+ return subcom.get_name();
+ }
+ }
+ if(!subc->get_name().empty()) {
+ if(subcom.check_name(subc->get_name())) {
+ return subc->get_name();
+ }
+ }
+ for(const auto &les : subcom.aliases_) {
+ if(subc->check_name(les)) {
+ return les;
+ }
+ }
+ // this loop is needed in case of ignore_underscore or ignore_case on one but not the other
+ for(const auto &les : subc->aliases_) {
+ if(subcom.check_name(les)) {
+ return les;
+ }
+ }
+ // if the subcommand is an option group we need to check deeper
+ if(subc->get_name().empty()) {
+ auto &cmpres = _compare_subcommand_names(subcom, *subc);
+ if(!cmpres.empty()) {
+ return cmpres;
+ }
+ }
+ // if the test subcommand is an option group we need to check deeper
+ if(subcom.get_name().empty()) {
+ auto &cmpres = _compare_subcommand_names(*subc, subcom);
+ if(!cmpres.empty()) {
+ return cmpres;
+ }
+ }
+ }
+ }
+ return estring;
+ }
+ /// Helper function to place extra values in the most appropriate position
+ void _move_to_missing(detail::Classifier val_type, const std::string &val) {
+ if(allow_extras_ || subcommands_.empty()) {
+ missing_.emplace_back(val_type, val);
+ return;
+ }
+ // allow extra arguments to be places in an option group if it is allowed there
+ for(auto &subc : subcommands_) {
+ if(subc->name_.empty() && subc->allow_extras_) {
+ subc->missing_.emplace_back(val_type, val);
+ return;
+ }
+ }
+ // if we haven't found any place to put them yet put them in missing
+ missing_.emplace_back(val_type, val);
+ }
+
+ public:
+ /// function that could be used by subclasses of App to shift options around into subcommands
+ void _move_option(Option *opt, App *app) {
+ if(opt == nullptr) {
+ throw OptionNotFound("the option is NULL");
+ }
+ // verify that the give app is actually a subcommand
+ bool found = false;
+ for(auto &subc : subcommands_) {
+ if(app == subc.get()) {
+ found = true;
+ }
+ }
+ if(!found) {
+ throw OptionNotFound("The Given app is not a subcommand");
+ }
+
+ if((help_ptr_ == opt) || (help_all_ptr_ == opt))
+ throw OptionAlreadyAdded("cannot move help options");
+
+ if(config_ptr_ == opt)
+ throw OptionAlreadyAdded("cannot move config file options");
+
+ auto iterator =
+ std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
+ if(iterator != std::end(options_)) {
+ const auto &opt_p = *iterator;
+ if(std::find_if(std::begin(app->options_), std::end(app->options_), [&opt_p](const Option_p &v) {
+ return (*v == *opt_p);
+ }) == std::end(app->options_)) {
+ // only erase after the insertion was successful
+ app->options_.push_back(std::move(*iterator));
+ options_.erase(iterator);
+ } else {
+ throw OptionAlreadyAdded("option was not located: " + opt->get_name());
+ }
+ } else {
+ throw OptionNotFound("could not locate the given Option");
+ }
+ }
+}; // namespace CLI
+
+/// Extension of App to better manage groups of options
+class Option_group : public App {
+ public:
+ Option_group(std::string group_description, std::string group_name, App *parent)
+ : App(std::move(group_description), "", parent) {
+ group(group_name);
+ // option groups should have automatic fallthrough
+ }
+ using App::add_option;
+ /// Add an existing option to the Option_group
+ Option *add_option(Option *opt) {
+ if(get_parent() == nullptr) {
+ throw OptionNotFound("Unable to locate the specified option");
+ }
+ get_parent()->_move_option(opt, this);
+ return opt;
+ }
+ /// Add an existing option to the Option_group
+ void add_options(Option *opt) { add_option(opt); }
+ /// Add a bunch of options to the group
+ template <typename... Args> void add_options(Option *opt, Args... args) {
+ add_option(opt);
+ add_options(args...);
+ }
+ using App::add_subcommand;
+ /// Add an existing subcommand to be a member of an option_group
+ App *add_subcommand(App *subcom) {
+ App_p subc = subcom->get_parent()->get_subcommand_ptr(subcom);
+ subc->get_parent()->remove_subcommand(subcom);
+ add_subcommand(std::move(subc));
+ return subcom;
+ }
+};
+/// Helper function to enable one option group/subcommand when another is used
+inline void TriggerOn(App *trigger_app, App *app_to_enable) {
+ app_to_enable->enabled_by_default(false);
+ app_to_enable->disabled_by_default();
+ trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(false); });
+}
+
+/// Helper function to enable one option group/subcommand when another is used
+inline void TriggerOn(App *trigger_app, std::vector<App *> apps_to_enable) {
+ for(auto &app : apps_to_enable) {
+ app->enabled_by_default(false);
+ app->disabled_by_default();
+ }
+
+ trigger_app->preparse_callback([apps_to_enable](std::size_t) {
+ for(auto &app : apps_to_enable) {
+ app->disabled(false);
+ }
+ });
+}
+
+/// Helper function to disable one option group/subcommand when another is used
+inline void TriggerOff(App *trigger_app, App *app_to_enable) {
+ app_to_enable->disabled_by_default(false);
+ app_to_enable->enabled_by_default();
+ trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(); });
+}
+
+/// Helper function to disable one option group/subcommand when another is used
+inline void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable) {
+ for(auto &app : apps_to_enable) {
+ app->disabled_by_default(false);
+ app->enabled_by_default();
+ }
+
+ trigger_app->preparse_callback([apps_to_enable](std::size_t) {
+ for(auto &app : apps_to_enable) {
+ app->disabled();
+ }
+ });
+}
+
+/// Helper function to mark an option as deprecated
+inline void deprecate_option(Option *opt, const std::string &replacement = "") {
+ Validator deprecate_warning{[opt, replacement](std::string &) {
+ std::cout << opt->get_name() << " is deprecated please use '" << replacement
+ << "' instead\n";
+ return std::string();
+ },
+ "DEPRECATED"};
+ deprecate_warning.application_index(0);
+ opt->check(deprecate_warning);
+ if(!replacement.empty()) {
+ opt->description(opt->get_description() + " DEPRECATED: please use '" + replacement + "' instead");
+ }
+}
+
+/// Helper function to mark an option as deprecated
+inline void deprecate_option(App *app, const std::string &option_name, const std::string &replacement = "") {
+ auto opt = app->get_option(option_name);
+ deprecate_option(opt, replacement);
+}
+
+/// Helper function to mark an option as deprecated
+inline void deprecate_option(App &app, const std::string &option_name, const std::string &replacement = "") {
+ auto opt = app.get_option(option_name);
+ deprecate_option(opt, replacement);
+}
+
+/// Helper function to mark an option as retired
+inline void retire_option(App *app, Option *opt) {
+ App temp;
+ auto option_copy = temp.add_option(opt->get_name(false, true))
+ ->type_size(opt->get_type_size_min(), opt->get_type_size_max())
+ ->expected(opt->get_expected_min(), opt->get_expected_max())
+ ->allow_extra_args(opt->get_allow_extra_args());
+
+ app->remove_option(opt);
+ auto opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect")
+ ->type_name("RETIRED")
+ ->default_str("RETIRED")
+ ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max())
+ ->expected(option_copy->get_expected_min(), option_copy->get_expected_max())
+ ->allow_extra_args(option_copy->get_allow_extra_args());
+
+ Validator retired_warning{[opt2](std::string &) {
+ std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
+ return std::string();
+ },
+ ""};
+ retired_warning.application_index(0);
+ opt2->check(retired_warning);
+}
+
+/// Helper function to mark an option as retired
+inline void retire_option(App &app, Option *opt) { retire_option(&app, opt); }
+
+/// Helper function to mark an option as retired
+inline void retire_option(App *app, const std::string &option_name) {
+
+ auto opt = app->get_option_no_throw(option_name);
+ if(opt != nullptr) {
+ retire_option(app, opt);
+ return;
+ }
+ auto opt2 = app->add_option(option_name, "option has been retired and has no effect")
+ ->type_name("RETIRED")
+ ->expected(0, 1)
+ ->default_str("RETIRED");
+ Validator retired_warning{[opt2](std::string &) {
+ std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
+ return std::string();
+ },
+ ""};
+ retired_warning.application_index(0);
+ opt2->check(retired_warning);
+}
+
+/// Helper function to mark an option as retired
+inline void retire_option(App &app, const std::string &option_name) { retire_option(&app, option_name); }
+
+namespace FailureMessage {
+
+/// Printout a clean, simple message on error (the default in CLI11 1.5+)
+inline std::string simple(const App *app, const Error &e) {
+ std::string header = std::string(e.what()) + "\n";
+ std::vector<std::string> names;
+
+ // Collect names
+ if(app->get_help_ptr() != nullptr)
+ names.push_back(app->get_help_ptr()->get_name());
+
+ if(app->get_help_all_ptr() != nullptr)
+ names.push_back(app->get_help_all_ptr()->get_name());
+
+ // If any names found, suggest those
+ if(!names.empty())
+ header += "Run with " + detail::join(names, " or ") + " for more information.\n";
+
+ return header;
+}
+
+/// Printout the full help string on error (if this fn is set, the old default for CLI11)
+inline std::string help(const App *app, const Error &e) {
+ std::string header = std::string("ERROR: ") + e.get_name() + ": " + e.what() + "\n";
+ header += app->help();
+ return header;
+}
+
+} // namespace FailureMessage
+
+namespace detail {
+/// This class is simply to allow tests access to App's protected functions
+struct AppFriend {
+#ifdef CLI11_CPP14
+
+ /// Wrap _parse_short, perfectly forward arguments and return
+ template <typename... Args> static decltype(auto) parse_arg(App *app, Args &&...args) {
+ return app->_parse_arg(std::forward<Args>(args)...);
+ }
+
+ /// Wrap _parse_subcommand, perfectly forward arguments and return
+ template <typename... Args> static decltype(auto) parse_subcommand(App *app, Args &&...args) {
+ return app->_parse_subcommand(std::forward<Args>(args)...);
+ }
+#else
+ /// Wrap _parse_short, perfectly forward arguments and return
+ template <typename... Args>
+ static auto parse_arg(App *app, Args &&...args) ->
+ typename std::result_of<decltype (&App::_parse_arg)(App, Args...)>::type {
+ return app->_parse_arg(std::forward<Args>(args)...);
+ }
+
+ /// Wrap _parse_subcommand, perfectly forward arguments and return
+ template <typename... Args>
+ static auto parse_subcommand(App *app, Args &&...args) ->
+ typename std::result_of<decltype (&App::_parse_subcommand)(App, Args...)>::type {
+ return app->_parse_subcommand(std::forward<Args>(args)...);
+ }
+#endif
+ /// Wrap the fallthrough parent function to make sure that is working correctly
+ static App *get_fallthrough_parent(App *app) { return app->_get_fallthrough_parent(); }
+};
+} // namespace detail
+
+// [CLI11:app_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/CLI.hpp b/src/third-party/CLI/CLI.hpp
new file mode 100644
index 0000000..0b6c344
--- /dev/null
+++ b/src/third-party/CLI/CLI.hpp
@@ -0,0 +1,36 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// CLI Library includes
+// Order is important for combiner script
+
+#include "Version.hpp"
+
+#include "Macros.hpp"
+
+#include "StringTools.hpp"
+
+#include "Error.hpp"
+
+#include "TypeTools.hpp"
+
+#include "Split.hpp"
+
+#include "ConfigFwd.hpp"
+
+#include "Validators.hpp"
+
+#include "FormatterFwd.hpp"
+
+#include "Option.hpp"
+
+#include "App.hpp"
+
+#include "Config.hpp"
+
+#include "Formatter.hpp"
diff --git a/src/third-party/CLI/Config.hpp b/src/third-party/CLI/Config.hpp
new file mode 100644
index 0000000..9555173
--- /dev/null
+++ b/src/third-party/CLI/Config.hpp
@@ -0,0 +1,396 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:set]
+
+#include "App.hpp"
+#include "ConfigFwd.hpp"
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:config_hpp:verbatim]
+namespace detail {
+
+inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') {
+ if(arg.empty()) {
+ return std::string(2, stringQuote);
+ }
+ // some specifically supported strings
+ if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
+ return arg;
+ }
+ // floating point conversion can convert some hex codes, but don't try that here
+ if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
+ double val;
+ if(detail::lexical_cast(arg, val)) {
+ return arg;
+ }
+ }
+ // just quote a single non numeric character
+ if(arg.size() == 1) {
+ return std::string(1, characterQuote) + arg + characterQuote;
+ }
+ // handle hex, binary or octal arguments
+ if(arg.front() == '0') {
+ if(arg[1] == 'x') {
+ if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
+ return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
+ })) {
+ return arg;
+ }
+ } else if(arg[1] == 'o') {
+ if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
+ return arg;
+ }
+ } else if(arg[1] == 'b') {
+ if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
+ return arg;
+ }
+ }
+ }
+ if(arg.find_first_of(stringQuote) == std::string::npos) {
+ return std::string(1, stringQuote) + arg + stringQuote;
+ } else {
+ return characterQuote + arg + characterQuote;
+ }
+}
+
+/// Comma separated join, adds quotes if needed
+inline std::string ini_join(const std::vector<std::string> &args,
+ char sepChar = ',',
+ char arrayStart = '[',
+ char arrayEnd = ']',
+ char stringQuote = '"',
+ char characterQuote = '\'') {
+ std::string joined;
+ if(args.size() > 1 && arrayStart != '\0') {
+ joined.push_back(arrayStart);
+ }
+ std::size_t start = 0;
+ for(const auto &arg : args) {
+ if(start++ > 0) {
+ joined.push_back(sepChar);
+ if(isspace(sepChar) == 0) {
+ joined.push_back(' ');
+ }
+ }
+ joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote));
+ }
+ if(args.size() > 1 && arrayEnd != '\0') {
+ joined.push_back(arrayEnd);
+ }
+ return joined;
+}
+
+inline std::vector<std::string> generate_parents(const std::string &section, std::string &name, char parentSeparator) {
+ std::vector<std::string> parents;
+ if(detail::to_lower(section) != "default") {
+ if(section.find(parentSeparator) != std::string::npos) {
+ parents = detail::split(section, parentSeparator);
+ } else {
+ parents = {section};
+ }
+ }
+ if(name.find(parentSeparator) != std::string::npos) {
+ std::vector<std::string> plist = detail::split(name, parentSeparator);
+ name = plist.back();
+ detail::remove_quotes(name);
+ plist.pop_back();
+ parents.insert(parents.end(), plist.begin(), plist.end());
+ }
+
+ // clean up quotes on the parents
+ for(auto &parent : parents) {
+ detail::remove_quotes(parent);
+ }
+ return parents;
+}
+
+/// assuming non default segments do a check on the close and open of the segments in a configItem structure
+inline void
+checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentSection, char parentSeparator) {
+
+ std::string estring;
+ auto parents = detail::generate_parents(currentSection, estring, parentSeparator);
+ if(!output.empty() && output.back().name == "--") {
+ std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
+ while(output.back().parents.size() >= msize) {
+ output.push_back(output.back());
+ output.back().parents.pop_back();
+ }
+
+ if(parents.size() > 1) {
+ std::size_t common = 0;
+ std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
+ for(std::size_t ii = 0; ii < mpair; ++ii) {
+ if(output.back().parents[ii] != parents[ii]) {
+ break;
+ }
+ ++common;
+ }
+ if(common == mpair) {
+ output.pop_back();
+ } else {
+ while(output.back().parents.size() > common + 1) {
+ output.push_back(output.back());
+ output.back().parents.pop_back();
+ }
+ }
+ for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
+ output.emplace_back();
+ output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
+ output.back().name = "++";
+ }
+ }
+ } else if(parents.size() > 1) {
+ for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
+ output.emplace_back();
+ output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
+ output.back().name = "++";
+ }
+ }
+
+ // insert a section end which is just an empty items_buffer
+ output.emplace_back();
+ output.back().parents = std::move(parents);
+ output.back().name = "++";
+}
+} // namespace detail
+
+inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
+ std::string line;
+ std::string currentSection = "default";
+ std::string previousSection = "default";
+ std::vector<ConfigItem> output;
+ bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
+ bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
+ bool inSection{false};
+ char aStart = (isINIArray) ? '[' : arrayStart;
+ char aEnd = (isINIArray) ? ']' : arrayEnd;
+ char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
+ int currentSectionIndex{0};
+ while(getline(input, line)) {
+ std::vector<std::string> items_buffer;
+ std::string name;
+
+ detail::trim(line);
+ std::size_t len = line.length();
+ // lines have to be at least 3 characters to have any meaning to CLI just skip the rest
+ if(len < 3) {
+ continue;
+ }
+ if(line.front() == '[' && line.back() == ']') {
+ if(currentSection != "default") {
+ // insert a section end which is just an empty items_buffer
+ output.emplace_back();
+ output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
+ output.back().name = "--";
+ }
+ currentSection = line.substr(1, len - 2);
+ // deal with double brackets for TOML
+ if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') {
+ currentSection = currentSection.substr(1, currentSection.size() - 2);
+ }
+ if(detail::to_lower(currentSection) == "default") {
+ currentSection = "default";
+ } else {
+ detail::checkParentSegments(output, currentSection, parentSeparatorChar);
+ }
+ inSection = false;
+ if(currentSection == previousSection) {
+ ++currentSectionIndex;
+ } else {
+ currentSectionIndex = 0;
+ previousSection = currentSection;
+ }
+ continue;
+ }
+
+ // comment lines
+ if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
+ continue;
+ }
+
+ // Find = in string, split and recombine
+ auto pos = line.find(valueDelimiter);
+ if(pos != std::string::npos) {
+ name = detail::trim_copy(line.substr(0, pos));
+ std::string item = detail::trim_copy(line.substr(pos + 1));
+ auto cloc = item.find(commentChar);
+ if(cloc != std::string::npos) {
+ item.erase(cloc, std::string::npos);
+ detail::trim(item);
+ }
+ if(item.size() > 1 && item.front() == aStart) {
+ for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) {
+ detail::trim(multiline);
+ item += multiline;
+ }
+ items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
+ } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
+ items_buffer = detail::split_up(item, aSep);
+ } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
+ items_buffer = detail::split_up(item);
+ } else {
+ items_buffer = {item};
+ }
+ } else {
+ name = detail::trim_copy(line);
+ auto cloc = name.find(commentChar);
+ if(cloc != std::string::npos) {
+ name.erase(cloc, std::string::npos);
+ detail::trim(name);
+ }
+
+ items_buffer = {"true"};
+ }
+ if(name.find(parentSeparatorChar) == std::string::npos) {
+ detail::remove_quotes(name);
+ }
+ // clean up quotes on the items
+ for(auto &it : items_buffer) {
+ detail::remove_quotes(it);
+ }
+
+ std::vector<std::string> parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
+ if(parents.size() > maximumLayers) {
+ continue;
+ }
+ if(!configSection.empty() && !inSection) {
+ if(parents.empty() || parents.front() != configSection) {
+ continue;
+ }
+ if(configIndex >= 0 && currentSectionIndex != configIndex) {
+ continue;
+ }
+ parents.erase(parents.begin());
+ inSection = true;
+ }
+ if(!output.empty() && name == output.back().name && parents == output.back().parents) {
+ output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end());
+ } else {
+ output.emplace_back();
+ output.back().parents = std::move(parents);
+ output.back().name = std::move(name);
+ output.back().inputs = std::move(items_buffer);
+ }
+ }
+ if(currentSection != "default") {
+ // insert a section end which is just an empty items_buffer
+ std::string ename;
+ output.emplace_back();
+ output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar);
+ output.back().name = "--";
+ while(output.back().parents.size() > 1) {
+ output.push_back(output.back());
+ output.back().parents.pop_back();
+ }
+ }
+ return output;
+}
+
+inline std::string
+ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
+ std::stringstream out;
+ std::string commentLead;
+ commentLead.push_back(commentChar);
+ commentLead.push_back(' ');
+
+ std::vector<std::string> groups = app->get_groups();
+ bool defaultUsed = false;
+ groups.insert(groups.begin(), std::string("Options"));
+ if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) {
+ out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n';
+ }
+ for(auto &group : groups) {
+ if(group == "Options" || group.empty()) {
+ if(defaultUsed) {
+ continue;
+ }
+ defaultUsed = true;
+ }
+ if(write_description && group != "Options" && !group.empty()) {
+ out << '\n' << commentLead << group << " Options\n";
+ }
+ for(const Option *opt : app->get_options({})) {
+
+ // Only process options that are configurable
+ if(opt->get_configurable()) {
+ if(opt->get_group() != group) {
+ if(!(group == "Options" && opt->get_group().empty())) {
+ continue;
+ }
+ }
+ std::string name = prefix + opt->get_single_name();
+ std::string value = detail::ini_join(
+ opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote);
+
+ if(value.empty() && default_also) {
+ if(!opt->get_default_str().empty()) {
+ value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote);
+ } else if(opt->get_expected_min() == 0) {
+ value = "false";
+ } else if(opt->get_run_callback_for_default()) {
+ value = "\"\""; // empty string default value
+ }
+ }
+
+ if(!value.empty()) {
+ if(write_description && opt->has_description()) {
+ out << '\n';
+ out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
+ }
+ out << name << valueDelimiter << value << '\n';
+ }
+ }
+ }
+ }
+ auto subcommands = app->get_subcommands({});
+ for(const App *subcom : subcommands) {
+ if(subcom->get_name().empty()) {
+ if(write_description && !subcom->get_group().empty()) {
+ out << '\n' << commentLead << subcom->get_group() << " Options\n";
+ }
+ out << to_config(subcom, default_also, write_description, prefix);
+ }
+ }
+
+ for(const App *subcom : subcommands) {
+ if(!subcom->get_name().empty()) {
+ if(subcom->get_configurable() && app->got_subcommand(subcom)) {
+ if(!prefix.empty() || app->get_parent() == nullptr) {
+ out << '[' << prefix << subcom->get_name() << "]\n";
+ } else {
+ std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name();
+ auto p = app->get_parent();
+ while(p->get_parent() != nullptr) {
+ subname = p->get_name() + parentSeparatorChar + subname;
+ p = p->get_parent();
+ }
+ out << '[' << subname << "]\n";
+ }
+ out << to_config(subcom, default_also, write_description, "");
+ } else {
+ out << to_config(
+ subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar);
+ }
+ }
+ }
+
+ return out.str();
+}
+
+// [CLI11:config_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/ConfigFwd.hpp b/src/third-party/CLI/ConfigFwd.hpp
new file mode 100644
index 0000000..b47133c
--- /dev/null
+++ b/src/third-party/CLI/ConfigFwd.hpp
@@ -0,0 +1,185 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "Error.hpp"
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:config_fwd_hpp:verbatim]
+
+class App;
+
+/// Holds values to load into Options
+struct ConfigItem {
+ /// This is the list of parents
+ std::vector<std::string> parents{};
+
+ /// This is the name
+ std::string name{};
+
+ /// Listing of inputs
+ std::vector<std::string> inputs{};
+
+ /// The list of parents and name joined by "."
+ std::string fullname() const {
+ std::vector<std::string> tmp = parents;
+ tmp.emplace_back(name);
+ return detail::join(tmp, ".");
+ }
+};
+
+/// This class provides a converter for configuration files.
+class Config {
+ protected:
+ std::vector<ConfigItem> items{};
+
+ public:
+ /// Convert an app into a configuration
+ virtual std::string to_config(const App *, bool, bool, std::string) const = 0;
+
+ /// Convert a configuration into an app
+ virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
+
+ /// Get a flag value
+ virtual std::string to_flag(const ConfigItem &item) const {
+ if(item.inputs.size() == 1) {
+ return item.inputs.at(0);
+ }
+ if(item.inputs.empty()) {
+ return "{}";
+ }
+ throw ConversionError::TooManyInputsFlag(item.fullname());
+ }
+
+ /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
+ std::vector<ConfigItem> from_file(const std::string &name) {
+ std::ifstream input{name};
+ if(!input.good())
+ throw FileError::Missing(name);
+
+ return from_config(input);
+ }
+
+ /// Virtual destructor
+ virtual ~Config() = default;
+};
+
+/// This converter works with INI/TOML files; to write INI files use ConfigINI
+class ConfigBase : public Config {
+ protected:
+ /// the character used for comments
+ char commentChar = '#';
+ /// the character used to start an array '\0' is a default to not use
+ char arrayStart = '[';
+ /// the character used to end an array '\0' is a default to not use
+ char arrayEnd = ']';
+ /// the character used to separate elements in an array
+ char arraySeparator = ',';
+ /// the character used separate the name from the value
+ char valueDelimiter = '=';
+ /// the character to use around strings
+ char stringQuote = '"';
+ /// the character to use around single characters
+ char characterQuote = '\'';
+ /// the maximum number of layers to allow
+ uint8_t maximumLayers{255};
+ /// the separator used to separator parent layers
+ char parentSeparatorChar{'.'};
+ /// Specify the configuration index to use for arrayed sections
+ int16_t configIndex{-1};
+ /// Specify the configuration section that should be used
+ std::string configSection{};
+
+ public:
+ std::string
+ to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override;
+
+ std::vector<ConfigItem> from_config(std::istream &input) const override;
+ /// Specify the configuration for comment characters
+ ConfigBase *comment(char cchar) {
+ commentChar = cchar;
+ return this;
+ }
+ /// Specify the start and end characters for an array
+ ConfigBase *arrayBounds(char aStart, char aEnd) {
+ arrayStart = aStart;
+ arrayEnd = aEnd;
+ return this;
+ }
+ /// Specify the delimiter character for an array
+ ConfigBase *arrayDelimiter(char aSep) {
+ arraySeparator = aSep;
+ return this;
+ }
+ /// Specify the delimiter between a name and value
+ ConfigBase *valueSeparator(char vSep) {
+ valueDelimiter = vSep;
+ return this;
+ }
+ /// Specify the quote characters used around strings and characters
+ ConfigBase *quoteCharacter(char qString, char qChar) {
+ stringQuote = qString;
+ characterQuote = qChar;
+ return this;
+ }
+ /// Specify the maximum number of parents
+ ConfigBase *maxLayers(uint8_t layers) {
+ maximumLayers = layers;
+ return this;
+ }
+ /// Specify the separator to use for parent layers
+ ConfigBase *parentSeparator(char sep) {
+ parentSeparatorChar = sep;
+ return this;
+ }
+ /// get a reference to the configuration section
+ std::string &sectionRef() { return configSection; }
+ /// get the section
+ const std::string &section() const { return configSection; }
+ /// specify a particular section of the configuration file to use
+ ConfigBase *section(const std::string &sectionName) {
+ configSection = sectionName;
+ return this;
+ }
+
+ /// get a reference to the configuration index
+ int16_t &indexRef() { return configIndex; }
+ /// get the section index
+ int16_t index() const { return configIndex; }
+ /// specify a particular index in the section to use (-1) for all sections to use
+ ConfigBase *index(int16_t sectionIndex) {
+ configIndex = sectionIndex;
+ return this;
+ }
+};
+
+/// the default Config is the TOML file format
+using ConfigTOML = ConfigBase;
+
+/// ConfigINI generates a "standard" INI compliant output
+class ConfigINI : public ConfigTOML {
+
+ public:
+ ConfigINI() {
+ commentChar = ';';
+ arrayStart = '\0';
+ arrayEnd = '\0';
+ arraySeparator = ' ';
+ valueDelimiter = '=';
+ }
+};
+// [CLI11:config_fwd_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/Error.hpp b/src/third-party/CLI/Error.hpp
new file mode 100644
index 0000000..b2c078d
--- /dev/null
+++ b/src/third-party/CLI/Error.hpp
@@ -0,0 +1,355 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <exception>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+// CLI library includes
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:error_hpp:verbatim]
+
+// Use one of these on all error classes.
+// These are temporary and are undef'd at the end of this file.
+#define CLI11_ERROR_DEF(parent, name) \
+ protected: \
+ name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \
+ name(std::string ename, std::string msg, ExitCodes exit_code) \
+ : parent(std::move(ename), std::move(msg), exit_code) {} \
+ \
+ public: \
+ name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \
+ name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {}
+
+// This is added after the one above if a class is used directly and builds its own message
+#define CLI11_ERROR_SIMPLE(name) \
+ explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {}
+
+/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut,
+/// int values from e.get_error_code().
+enum class ExitCodes {
+ Success = 0,
+ IncorrectConstruction = 100,
+ BadNameString,
+ OptionAlreadyAdded,
+ FileError,
+ ConversionError,
+ ValidationError,
+ RequiredError,
+ RequiresError,
+ ExcludesError,
+ ExtrasError,
+ ConfigError,
+ InvalidError,
+ HorribleError,
+ OptionNotFound,
+ ArgumentMismatch,
+ BaseClass = 127
+};
+
+// Error definitions
+
+/// @defgroup error_group Errors
+/// @brief Errors thrown by CLI11
+///
+/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors.
+/// @{
+
+/// All errors derive from this one
+class Error : public std::runtime_error {
+ int actual_exit_code;
+ std::string error_name{"Error"};
+
+ public:
+ int get_exit_code() const { return actual_exit_code; }
+
+ std::string get_name() const { return error_name; }
+
+ Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
+ : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {}
+
+ Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {}
+};
+
+// Note: Using Error::Error constructors does not work on GCC 4.7
+
+/// Construction errors (not in parsing)
+class ConstructionError : public Error {
+ CLI11_ERROR_DEF(Error, ConstructionError)
+};
+
+/// Thrown when an option is set to conflicting values (non-vector and multi args, for example)
+class IncorrectConstruction : public ConstructionError {
+ CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction)
+ CLI11_ERROR_SIMPLE(IncorrectConstruction)
+ static IncorrectConstruction PositionalFlag(std::string name) {
+ return IncorrectConstruction(name + ": Flags cannot be positional");
+ }
+ static IncorrectConstruction Set0Opt(std::string name) {
+ return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead");
+ }
+ static IncorrectConstruction SetFlag(std::string name) {
+ return IncorrectConstruction(name + ": Cannot set an expected number for flags");
+ }
+ static IncorrectConstruction ChangeNotVector(std::string name) {
+ return IncorrectConstruction(name + ": You can only change the expected arguments for vectors");
+ }
+ static IncorrectConstruction AfterMultiOpt(std::string name) {
+ return IncorrectConstruction(
+ name + ": You can't change expected arguments after you've changed the multi option policy!");
+ }
+ static IncorrectConstruction MissingOption(std::string name) {
+ return IncorrectConstruction("Option " + name + " is not defined");
+ }
+ static IncorrectConstruction MultiOptionPolicy(std::string name) {
+ return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options");
+ }
+};
+
+/// Thrown on construction of a bad name
+class BadNameString : public ConstructionError {
+ CLI11_ERROR_DEF(ConstructionError, BadNameString)
+ CLI11_ERROR_SIMPLE(BadNameString)
+ static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); }
+ static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
+ static BadNameString DashesOnly(std::string name) {
+ return BadNameString("Must have a name, not just dashes: " + name);
+ }
+ static BadNameString MultiPositionalNames(std::string name) {
+ return BadNameString("Only one positional name allowed, remove: " + name);
+ }
+};
+
+/// Thrown when an option already exists
+class OptionAlreadyAdded : public ConstructionError {
+ CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded)
+ explicit OptionAlreadyAdded(std::string name)
+ : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {}
+ static OptionAlreadyAdded Requires(std::string name, std::string other) {
+ return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded);
+ }
+ static OptionAlreadyAdded Excludes(std::string name, std::string other) {
+ return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded);
+ }
+};
+
+// Parsing errors
+
+/// Anything that can error in Parse
+class ParseError : public Error {
+ CLI11_ERROR_DEF(Error, ParseError)
+};
+
+// Not really "errors"
+
+/// This is a successful completion on parsing, supposed to exit
+class Success : public ParseError {
+ CLI11_ERROR_DEF(ParseError, Success)
+ Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {}
+};
+
+/// -h or --help on command line
+class CallForHelp : public Success {
+ CLI11_ERROR_DEF(Success, CallForHelp)
+ CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
+};
+
+/// Usually something like --help-all on command line
+class CallForAllHelp : public Success {
+ CLI11_ERROR_DEF(Success, CallForAllHelp)
+ CallForAllHelp()
+ : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
+};
+
+/// -v or --version on command line
+class CallForVersion : public Success {
+ CLI11_ERROR_DEF(Success, CallForVersion)
+ CallForVersion()
+ : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {}
+};
+
+/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code.
+class RuntimeError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, RuntimeError)
+ explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {}
+};
+
+/// Thrown when parsing an INI file and it is missing
+class FileError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, FileError)
+ CLI11_ERROR_SIMPLE(FileError)
+ static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); }
+};
+
+/// Thrown when conversion call back fails, such as when an int fails to coerce to a string
+class ConversionError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, ConversionError)
+ CLI11_ERROR_SIMPLE(ConversionError)
+ ConversionError(std::string member, std::string name)
+ : ConversionError("The value " + member + " is not an allowed value for " + name) {}
+ ConversionError(std::string name, std::vector<std::string> results)
+ : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {}
+ static ConversionError TooManyInputsFlag(std::string name) {
+ return ConversionError(name + ": too many inputs for a flag");
+ }
+ static ConversionError TrueFalse(std::string name) {
+ return ConversionError(name + ": Should be true/false or a number");
+ }
+};
+
+/// Thrown when validation of results fails
+class ValidationError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, ValidationError)
+ CLI11_ERROR_SIMPLE(ValidationError)
+ explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {}
+};
+
+/// Thrown when a required option is missing
+class RequiredError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, RequiredError)
+ explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {}
+ static RequiredError Subcommand(std::size_t min_subcom) {
+ if(min_subcom == 1) {
+ return RequiredError("A subcommand");
+ }
+ return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
+ ExitCodes::RequiredError);
+ }
+ static RequiredError
+ Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
+ if((min_option == 1) && (max_option == 1) && (used == 0))
+ return RequiredError("Exactly 1 option from [" + option_list + "]");
+ if((min_option == 1) && (max_option == 1) && (used > 1)) {
+ return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
+ " were given",
+ ExitCodes::RequiredError);
+ }
+ if((min_option == 1) && (used == 0))
+ return RequiredError("At least 1 option from [" + option_list + "]");
+ if(used < min_option) {
+ return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " +
+ std::to_string(used) + "were given from [" + option_list + "]",
+ ExitCodes::RequiredError);
+ }
+ if(max_option == 1)
+ return RequiredError("Requires at most 1 options be given from [" + option_list + "]",
+ ExitCodes::RequiredError);
+
+ return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " +
+ std::to_string(used) + "were given from [" + option_list + "]",
+ ExitCodes::RequiredError);
+ }
+};
+
+/// Thrown when the wrong number of arguments has been received
+class ArgumentMismatch : public ParseError {
+ CLI11_ERROR_DEF(ParseError, ArgumentMismatch)
+ CLI11_ERROR_SIMPLE(ArgumentMismatch)
+ ArgumentMismatch(std::string name, int expected, std::size_t received)
+ : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name +
+ ", got " + std::to_string(received))
+ : ("Expected at least " + std::to_string(-expected) + " arguments to " + name +
+ ", got " + std::to_string(received)),
+ ExitCodes::ArgumentMismatch) {}
+
+ static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) {
+ return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " +
+ std::to_string(received));
+ }
+ static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) {
+ return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " +
+ std::to_string(received));
+ }
+ static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
+ return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
+ }
+ static ArgumentMismatch FlagOverride(std::string name) {
+ return ArgumentMismatch(name + " was given a disallowed flag override");
+ }
+ static ArgumentMismatch PartialType(std::string name, int num, std::string type) {
+ return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) +
+ " required for each element");
+ }
+};
+
+/// Thrown when a requires option is missing
+class RequiresError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, RequiresError)
+ RequiresError(std::string curname, std::string subname)
+ : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {}
+};
+
+/// Thrown when an excludes option is present
+class ExcludesError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, ExcludesError)
+ ExcludesError(std::string curname, std::string subname)
+ : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {}
+};
+
+/// Thrown when too many positionals or options are found
+class ExtrasError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, ExtrasError)
+ explicit ExtrasError(std::vector<std::string> args)
+ : ExtrasError((args.size() > 1 ? "The following arguments were not expected: "
+ : "The following argument was not expected: ") +
+ detail::rjoin(args, " "),
+ ExitCodes::ExtrasError) {}
+ ExtrasError(const std::string &name, std::vector<std::string> args)
+ : ExtrasError(name,
+ (args.size() > 1 ? "The following arguments were not expected: "
+ : "The following argument was not expected: ") +
+ detail::rjoin(args, " "),
+ ExitCodes::ExtrasError) {}
+};
+
+/// Thrown when extra values are found in an INI file
+class ConfigError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, ConfigError)
+ CLI11_ERROR_SIMPLE(ConfigError)
+ static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); }
+ static ConfigError NotConfigurable(std::string item) {
+ return ConfigError(item + ": This option is not allowed in a configuration file");
+ }
+};
+
+/// Thrown when validation fails before parsing
+class InvalidError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, InvalidError)
+ explicit InvalidError(std::string name)
+ : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) {
+ }
+};
+
+/// This is just a safety check to verify selection and parsing match - you should not ever see it
+/// Strings are directly added to this error, but again, it should never be seen.
+class HorribleError : public ParseError {
+ CLI11_ERROR_DEF(ParseError, HorribleError)
+ CLI11_ERROR_SIMPLE(HorribleError)
+};
+
+// After parsing
+
+/// Thrown when counting a non-existent option
+class OptionNotFound : public Error {
+ CLI11_ERROR_DEF(Error, OptionNotFound)
+ explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {}
+};
+
+#undef CLI11_ERROR_DEF
+#undef CLI11_ERROR_SIMPLE
+
+/// @}
+
+// [CLI11:error_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/Formatter.hpp b/src/third-party/CLI/Formatter.hpp
new file mode 100644
index 0000000..4d2b5fa
--- /dev/null
+++ b/src/third-party/CLI/Formatter.hpp
@@ -0,0 +1,292 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <string>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "App.hpp"
+#include "FormatterFwd.hpp"
+
+namespace CLI {
+// [CLI11:formatter_hpp:verbatim]
+
+inline std::string
+Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
+ std::stringstream out;
+
+ out << "\n" << group << ":\n";
+ for(const Option *opt : opts) {
+ out << make_option(opt, is_positional);
+ }
+
+ return out.str();
+}
+
+inline std::string Formatter::make_positionals(const App *app) const {
+ std::vector<const Option *> opts =
+ app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
+
+ if(opts.empty())
+ return std::string();
+
+ return make_group(get_label("Positionals"), true, opts);
+}
+
+inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
+ std::stringstream out;
+ std::vector<std::string> groups = app->get_groups();
+
+ // Options
+ for(const std::string &group : groups) {
+ std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) {
+ return opt->get_group() == group // Must be in the right group
+ && opt->nonpositional() // Must not be a positional
+ && (mode != AppFormatMode::Sub // If mode is Sub, then
+ || (app->get_help_ptr() != opt // Ignore help pointer
+ && app->get_help_all_ptr() != opt)); // Ignore help all pointer
+ });
+ if(!group.empty() && !opts.empty()) {
+ out << make_group(group, false, opts);
+
+ if(group != groups.back())
+ out << "\n";
+ }
+ }
+
+ return out.str();
+}
+
+inline std::string Formatter::make_description(const App *app) const {
+ std::string desc = app->get_description();
+ auto min_options = app->get_require_option_min();
+ auto max_options = app->get_require_option_max();
+ if(app->get_required()) {
+ desc += " REQUIRED ";
+ }
+ if((max_options == min_options) && (min_options > 0)) {
+ if(min_options == 1) {
+ desc += " \n[Exactly 1 of the following options is required]";
+ } else {
+ desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]";
+ }
+ } else if(max_options > 0) {
+ if(min_options > 0) {
+ desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
+ " of the follow options are required]";
+ } else {
+ desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
+ }
+ } else if(min_options > 0) {
+ desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
+ }
+ return (!desc.empty()) ? desc + "\n" : std::string{};
+}
+
+inline std::string Formatter::make_usage(const App *app, std::string name) const {
+ std::stringstream out;
+
+ out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
+
+ std::vector<std::string> groups = app->get_groups();
+
+ // Print an Options badge if any options exist
+ std::vector<const Option *> non_pos_options =
+ app->get_options([](const Option *opt) { return opt->nonpositional(); });
+ if(!non_pos_options.empty())
+ out << " [" << get_label("OPTIONS") << "]";
+
+ // Positionals need to be listed here
+ std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
+
+ // Print out positionals if any are left
+ if(!positionals.empty()) {
+ // Convert to help names
+ std::vector<std::string> positional_names(positionals.size());
+ std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) {
+ return make_option_usage(opt);
+ });
+
+ out << " " << detail::join(positional_names, " ");
+ }
+
+ // Add a marker if subcommands are expected or optional
+ if(!app->get_subcommands(
+ [](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
+ .empty()) {
+ out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
+ << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
+ : "SUBCOMMANDS")
+ << (app->get_require_subcommand_min() == 0 ? "]" : "");
+ }
+
+ out << std::endl;
+
+ return out.str();
+}
+
+inline std::string Formatter::make_footer(const App *app) const {
+ std::string footer = app->get_footer();
+ if(footer.empty()) {
+ return std::string{};
+ }
+ return footer + "\n";
+}
+
+inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
+
+ // This immediately forwards to the make_expanded method. This is done this way so that subcommands can
+ // have overridden formatters
+ if(mode == AppFormatMode::Sub)
+ return make_expanded(app);
+
+ std::stringstream out;
+ if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
+ if(app->get_group() != "Subcommands") {
+ out << app->get_group() << ':';
+ }
+ }
+
+ out << make_description(app);
+ out << make_usage(app, name);
+ out << make_positionals(app);
+ out << make_groups(app, mode);
+ out << make_subcommands(app, mode);
+ out << '\n' << make_footer(app);
+
+ return out.str();
+}
+
+inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
+ std::stringstream out;
+
+ std::vector<const App *> subcommands = app->get_subcommands({});
+
+ // Make a list in definition order of the groups seen
+ std::vector<std::string> subcmd_groups_seen;
+ for(const App *com : subcommands) {
+ if(com->get_name().empty()) {
+ if(!com->get_group().empty()) {
+ out << make_expanded(com);
+ }
+ continue;
+ }
+ std::string group_key = com->get_group();
+ if(!group_key.empty() &&
+ std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
+ return detail::to_lower(a) == detail::to_lower(group_key);
+ }) == subcmd_groups_seen.end())
+ subcmd_groups_seen.push_back(group_key);
+ }
+
+ // For each group, filter out and print subcommands
+ for(const std::string &group : subcmd_groups_seen) {
+ out << "\n" << group << ":\n";
+ std::vector<const App *> subcommands_group = app->get_subcommands(
+ [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
+ for(const App *new_com : subcommands_group) {
+ if(new_com->get_name().empty())
+ continue;
+ if(mode != AppFormatMode::All) {
+ out << make_subcommand(new_com);
+ } else {
+ out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
+ out << "\n";
+ }
+ }
+ }
+
+ return out.str();
+}
+
+inline std::string Formatter::make_subcommand(const App *sub) const {
+ std::stringstream out;
+ detail::format_help(out, sub->get_display_name(true), sub->get_description(), column_width_);
+ return out.str();
+}
+
+inline std::string Formatter::make_expanded(const App *sub) const {
+ std::stringstream out;
+ out << sub->get_display_name(true) << "\n";
+
+ out << make_description(sub);
+ if(sub->get_name().empty() && !sub->get_aliases().empty()) {
+ detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
+ }
+ out << make_positionals(sub);
+ out << make_groups(sub, AppFormatMode::Sub);
+ out << make_subcommands(sub, AppFormatMode::Sub);
+
+ // Drop blank spaces
+ std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n");
+ tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n'
+
+ // Indent all but the first line (the name)
+ return detail::find_and_replace(tmp, "\n", "\n ") + "\n";
+}
+
+inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
+ if(is_positional)
+ return opt->get_name(true, false);
+
+ return opt->get_name(false, true);
+}
+
+inline std::string Formatter::make_option_opts(const Option *opt) const {
+ std::stringstream out;
+
+ if(!opt->get_option_text().empty()) {
+ out << " " << opt->get_option_text();
+ } else {
+ if(opt->get_type_size() != 0) {
+ if(!opt->get_type_name().empty())
+ out << " " << get_label(opt->get_type_name());
+ if(!opt->get_default_str().empty())
+ out << " [" << opt->get_default_str() << "] ";
+ if(opt->get_expected_max() == detail::expected_max_vector_size)
+ out << " ...";
+ else if(opt->get_expected_min() > 1)
+ out << " x " << opt->get_expected();
+
+ if(opt->get_required())
+ out << " " << get_label("REQUIRED");
+ }
+ if(!opt->get_envname().empty())
+ out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
+ if(!opt->get_needs().empty()) {
+ out << " " << get_label("Needs") << ":";
+ for(const Option *op : opt->get_needs())
+ out << " " << op->get_name();
+ }
+ if(!opt->get_excludes().empty()) {
+ out << " " << get_label("Excludes") << ":";
+ for(const Option *op : opt->get_excludes())
+ out << " " << op->get_name();
+ }
+ }
+ return out.str();
+}
+
+inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
+
+inline std::string Formatter::make_option_usage(const Option *opt) const {
+ // Note that these are positionals usages
+ std::stringstream out;
+ out << make_option_name(opt, true);
+ if(opt->get_expected_max() >= detail::expected_max_vector_size)
+ out << "...";
+ else if(opt->get_expected_max() > 1)
+ out << "(" << opt->get_expected() << "x)";
+
+ return opt->get_required() ? out.str() : "[" + out.str() + "]";
+}
+
+// [CLI11:formatter_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/FormatterFwd.hpp b/src/third-party/CLI/FormatterFwd.hpp
new file mode 100644
index 0000000..f71b3bb
--- /dev/null
+++ b/src/third-party/CLI/FormatterFwd.hpp
@@ -0,0 +1,184 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:formatter_fwd_hpp:verbatim]
+
+class Option;
+class App;
+
+/// This enum signifies the type of help requested
+///
+/// This is passed in by App; all user classes must accept this as
+/// the second argument.
+
+enum class AppFormatMode {
+ Normal, ///< The normal, detailed help
+ All, ///< A fully expanded help
+ Sub, ///< Used when printed as part of expanded subcommand
+};
+
+/// This is the minimum requirements to run a formatter.
+///
+/// A user can subclass this is if they do not care at all
+/// about the structure in CLI::Formatter.
+class FormatterBase {
+ protected:
+ /// @name Options
+ ///@{
+
+ /// The width of the first column
+ std::size_t column_width_{30};
+
+ /// @brief The required help printout labels (user changeable)
+ /// Values are Needs, Excludes, etc.
+ std::map<std::string, std::string> labels_{};
+
+ ///@}
+ /// @name Basic
+ ///@{
+
+ public:
+ FormatterBase() = default;
+ FormatterBase(const FormatterBase &) = default;
+ FormatterBase(FormatterBase &&) = default;
+
+ /// Adding a destructor in this form to work around bug in GCC 4.7
+ virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default)
+
+ /// This is the key method that puts together help
+ virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0;
+
+ ///@}
+ /// @name Setters
+ ///@{
+
+ /// Set the "REQUIRED" label
+ void label(std::string key, std::string val) { labels_[key] = val; }
+
+ /// Set the column width
+ void column_width(std::size_t val) { column_width_ = val; }
+
+ ///@}
+ /// @name Getters
+ ///@{
+
+ /// Get the current value of a name (REQUIRED, etc.)
+ std::string get_label(std::string key) const {
+ if(labels_.find(key) == labels_.end())
+ return key;
+ else
+ return labels_.at(key);
+ }
+
+ /// Get the current column width
+ std::size_t get_column_width() const { return column_width_; }
+
+ ///@}
+};
+
+/// This is a specialty override for lambda functions
+class FormatterLambda final : public FormatterBase {
+ using funct_t = std::function<std::string(const App *, std::string, AppFormatMode)>;
+
+ /// The lambda to hold and run
+ funct_t lambda_;
+
+ public:
+ /// Create a FormatterLambda with a lambda function
+ explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}
+
+ /// Adding a destructor (mostly to make GCC 4.7 happy)
+ ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default)
+
+ /// This will simply call the lambda function
+ std::string make_help(const App *app, std::string name, AppFormatMode mode) const override {
+ return lambda_(app, name, mode);
+ }
+};
+
+/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few
+/// overridable methods, to be highly customizable with minimal effort.
+class Formatter : public FormatterBase {
+ public:
+ Formatter() = default;
+ Formatter(const Formatter &) = default;
+ Formatter(Formatter &&) = default;
+
+ /// @name Overridables
+ ///@{
+
+ /// This prints out a group of options with title
+ ///
+ virtual std::string make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const;
+
+ /// This prints out just the positionals "group"
+ virtual std::string make_positionals(const App *app) const;
+
+ /// This prints out all the groups of options
+ std::string make_groups(const App *app, AppFormatMode mode) const;
+
+ /// This prints out all the subcommands
+ virtual std::string make_subcommands(const App *app, AppFormatMode mode) const;
+
+ /// This prints out a subcommand
+ virtual std::string make_subcommand(const App *sub) const;
+
+ /// This prints out a subcommand in help-all
+ virtual std::string make_expanded(const App *sub) const;
+
+ /// This prints out all the groups of options
+ virtual std::string make_footer(const App *app) const;
+
+ /// This displays the description line
+ virtual std::string make_description(const App *app) const;
+
+ /// This displays the usage line
+ virtual std::string make_usage(const App *app, std::string name) const;
+
+ /// This puts everything together
+ std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override;
+
+ ///@}
+ /// @name Options
+ ///@{
+
+ /// This prints out an option help line, either positional or optional form
+ virtual std::string make_option(const Option *opt, bool is_positional) const {
+ std::stringstream out;
+ detail::format_help(
+ out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_);
+ return out.str();
+ }
+
+ /// @brief This is the name part of an option, Default: left column
+ virtual std::string make_option_name(const Option *, bool) const;
+
+ /// @brief This is the options part of the name, Default: combined into left column
+ virtual std::string make_option_opts(const Option *) const;
+
+ /// @brief This is the description. Default: Right column, on new line if left column too large
+ virtual std::string make_option_desc(const Option *) const;
+
+ /// @brief This is used to print the name on the USAGE line
+ virtual std::string make_option_usage(const Option *opt) const;
+
+ ///@}
+};
+
+// [CLI11:formatter_fwd_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/Macros.hpp b/src/third-party/CLI/Macros.hpp
new file mode 100644
index 0000000..8d7663b
--- /dev/null
+++ b/src/third-party/CLI/Macros.hpp
@@ -0,0 +1,60 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:macros_hpp:verbatim]
+
+// The following version macro is very similar to the one in pybind11
+#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER)
+#if __cplusplus >= 201402L
+#define CLI11_CPP14
+#if __cplusplus >= 201703L
+#define CLI11_CPP17
+#if __cplusplus > 201703L
+#define CLI11_CPP20
+#endif
+#endif
+#endif
+#elif defined(_MSC_VER) && __cplusplus == 199711L
+// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented)
+// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer
+#if _MSVC_LANG >= 201402L
+#define CLI11_CPP14
+#if _MSVC_LANG > 201402L && _MSC_VER >= 1910
+#define CLI11_CPP17
+#if _MSVC_LANG > 201703L && _MSC_VER >= 1910
+#define CLI11_CPP20
+#endif
+#endif
+#endif
+#endif
+
+#if defined(CLI11_CPP14)
+#define CLI11_DEPRECATED(reason) [[deprecated(reason)]]
+#elif defined(_MSC_VER)
+#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason))
+#else
+#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
+#endif
+
+/** detection of rtti */
+#ifndef CLI11_USE_STATIC_RTTI
+#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI)
+#define CLI11_USE_STATIC_RTTI 1
+#elif defined(__cpp_rtti)
+#if(defined(_CPPRTTI) && _CPPRTTI == 0)
+#define CLI11_USE_STATIC_RTTI 1
+#else
+#define CLI11_USE_STATIC_RTTI 0
+#endif
+#elif(defined(__GCC_RTTI) && __GXX_RTTI)
+#define CLI11_USE_STATIC_RTTI 0
+#else
+#define CLI11_USE_STATIC_RTTI 1
+#endif
+#endif
+// [CLI11:macros_hpp:end]
diff --git a/src/third-party/CLI/Option.hpp b/src/third-party/CLI/Option.hpp
new file mode 100644
index 0000000..b075bbc
--- /dev/null
+++ b/src/third-party/CLI/Option.hpp
@@ -0,0 +1,1362 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <set>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "Error.hpp"
+#include "Macros.hpp"
+#include "Split.hpp"
+#include "StringTools.hpp"
+#include "Validators.hpp"
+
+namespace CLI {
+// [CLI11:option_hpp:verbatim]
+
+using results_t = std::vector<std::string>;
+/// callback function definition
+using callback_t = std::function<bool(const results_t &)>;
+
+class Option;
+class App;
+
+using Option_p = std::unique_ptr<Option>;
+/// Enumeration of the multiOption Policy selection
+enum class MultiOptionPolicy : char {
+ Throw, //!< Throw an error if any extra arguments were given
+ TakeLast, //!< take only the last Expected number of arguments
+ TakeFirst, //!< take only the first Expected number of arguments
+ Join, //!< merge all the arguments together into a single string via the delimiter character default('\n')
+ TakeAll, //!< just get all the passed argument regardless
+ Sum //!< sum all the arguments together if numerical or concatenate directly without delimiter
+};
+
+/// This is the CRTP base class for Option and OptionDefaults. It was designed this way
+/// to share parts of the class; an OptionDefaults can copy to an Option.
+template <typename CRTP> class OptionBase {
+ friend App;
+
+ protected:
+ /// The group membership
+ std::string group_ = std::string("Options");
+
+ /// True if this is a required option
+ bool required_{false};
+
+ /// Ignore the case when matching (option, not value)
+ bool ignore_case_{false};
+
+ /// Ignore underscores when matching (option, not value)
+ bool ignore_underscore_{false};
+
+ /// Allow this option to be given in a configuration file
+ bool configurable_{true};
+
+ /// Disable overriding flag values with '=value'
+ bool disable_flag_override_{false};
+
+ /// Specify a delimiter character for vector arguments
+ char delimiter_{'\0'};
+
+ /// Automatically capture default value
+ bool always_capture_default_{false};
+
+ /// Policy for handling multiple arguments beyond the expected Max
+ MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
+
+ /// Copy the contents to another similar class (one based on OptionBase)
+ template <typename T> void copy_to(T *other) const {
+ other->group(group_);
+ other->required(required_);
+ other->ignore_case(ignore_case_);
+ other->ignore_underscore(ignore_underscore_);
+ other->configurable(configurable_);
+ other->disable_flag_override(disable_flag_override_);
+ other->delimiter(delimiter_);
+ other->always_capture_default(always_capture_default_);
+ other->multi_option_policy(multi_option_policy_);
+ }
+
+ public:
+ // setters
+
+ /// Changes the group membership
+ CRTP *group(const std::string &name) {
+ if(!detail::valid_alias_name_string(name)) {
+ throw IncorrectConstruction("Group names may not contain newlines or null characters");
+ }
+ group_ = name;
+ return static_cast<CRTP *>(this);
+ }
+
+ /// Set the option as required
+ CRTP *required(bool value = true) {
+ required_ = value;
+ return static_cast<CRTP *>(this);
+ }
+
+ /// Support Plumbum term
+ CRTP *mandatory(bool value = true) { return required(value); }
+
+ CRTP *always_capture_default(bool value = true) {
+ always_capture_default_ = value;
+ return static_cast<CRTP *>(this);
+ }
+
+ // Getters
+
+ /// Get the group of this option
+ const std::string &get_group() const { return group_; }
+
+ /// True if this is a required option
+ bool get_required() const { return required_; }
+
+ /// The status of ignore case
+ bool get_ignore_case() const { return ignore_case_; }
+
+ /// The status of ignore_underscore
+ bool get_ignore_underscore() const { return ignore_underscore_; }
+
+ /// The status of configurable
+ bool get_configurable() const { return configurable_; }
+
+ /// The status of configurable
+ bool get_disable_flag_override() const { return disable_flag_override_; }
+
+ /// Get the current delimiter char
+ char get_delimiter() const { return delimiter_; }
+
+ /// Return true if this will automatically capture the default value for help printing
+ bool get_always_capture_default() const { return always_capture_default_; }
+
+ /// The status of the multi option policy
+ MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
+
+ // Shortcuts for multi option policy
+
+ /// Set the multi option policy to take last
+ CRTP *take_last() {
+ auto self = static_cast<CRTP *>(this);
+ self->multi_option_policy(MultiOptionPolicy::TakeLast);
+ return self;
+ }
+
+ /// Set the multi option policy to take last
+ CRTP *take_first() {
+ auto self = static_cast<CRTP *>(this);
+ self->multi_option_policy(MultiOptionPolicy::TakeFirst);
+ return self;
+ }
+
+ /// Set the multi option policy to take all arguments
+ CRTP *take_all() {
+ auto self = static_cast<CRTP *>(this);
+ self->multi_option_policy(MultiOptionPolicy::TakeAll);
+ return self;
+ }
+
+ /// Set the multi option policy to join
+ CRTP *join() {
+ auto self = static_cast<CRTP *>(this);
+ self->multi_option_policy(MultiOptionPolicy::Join);
+ return self;
+ }
+
+ /// Set the multi option policy to join with a specific delimiter
+ CRTP *join(char delim) {
+ auto self = static_cast<CRTP *>(this);
+ self->delimiter_ = delim;
+ self->multi_option_policy(MultiOptionPolicy::Join);
+ return self;
+ }
+
+ /// Allow in a configuration file
+ CRTP *configurable(bool value = true) {
+ configurable_ = value;
+ return static_cast<CRTP *>(this);
+ }
+
+ /// Allow in a configuration file
+ CRTP *delimiter(char value = '\0') {
+ delimiter_ = value;
+ return static_cast<CRTP *>(this);
+ }
+};
+
+/// This is a version of OptionBase that only supports setting values,
+/// for defaults. It is stored as the default option in an App.
+class OptionDefaults : public OptionBase<OptionDefaults> {
+ public:
+ OptionDefaults() = default;
+
+ // Methods here need a different implementation if they are Option vs. OptionDefault
+
+ /// Take the last argument if given multiple times
+ OptionDefaults *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
+ multi_option_policy_ = value;
+ return this;
+ }
+
+ /// Ignore the case of the option name
+ OptionDefaults *ignore_case(bool value = true) {
+ ignore_case_ = value;
+ return this;
+ }
+
+ /// Ignore underscores in the option name
+ OptionDefaults *ignore_underscore(bool value = true) {
+ ignore_underscore_ = value;
+ return this;
+ }
+
+ /// Disable overriding flag values with an '=<value>' segment
+ OptionDefaults *disable_flag_override(bool value = true) {
+ disable_flag_override_ = value;
+ return this;
+ }
+
+ /// set a delimiter character to split up single arguments to treat as multiple inputs
+ OptionDefaults *delimiter(char value = '\0') {
+ delimiter_ = value;
+ return this;
+ }
+};
+
+class Option : public OptionBase<Option> {
+ friend App;
+
+ protected:
+ /// @name Names
+ ///@{
+
+ /// A list of the short names (`-a`) without the leading dashes
+ std::vector<std::string> snames_{};
+
+ /// A list of the long names (`--long`) without the leading dashes
+ std::vector<std::string> lnames_{};
+
+ /// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of
+ /// what is in snames or lnames but will trigger a particular response on a flag
+ std::vector<std::pair<std::string, std::string>> default_flag_values_{};
+
+ /// a list of flag names with specified default values;
+ std::vector<std::string> fnames_{};
+
+ /// A positional name
+ std::string pname_{};
+
+ /// If given, check the environment for this option
+ std::string envname_{};
+
+ ///@}
+ /// @name Help
+ ///@{
+
+ /// The description for help strings
+ std::string description_{};
+
+ /// A human readable default value, either manually set, captured, or captured by default
+ std::string default_str_{};
+
+ /// If given, replace the text that describes the option type and usage in the help text
+ std::string option_text_{};
+
+ /// A human readable type value, set when App creates this
+ ///
+ /// This is a lambda function so "types" can be dynamic, such as when a set prints its contents.
+ std::function<std::string()> type_name_{[]() { return std::string(); }};
+
+ /// Run this function to capture a default (ignore if empty)
+ std::function<std::string()> default_function_{};
+
+ ///@}
+ /// @name Configuration
+ ///@{
+
+ /// The number of arguments that make up one option. max is the nominal type size, min is the minimum number of
+ /// strings
+ int type_size_max_{1};
+ /// The minimum number of arguments an option should be expecting
+ int type_size_min_{1};
+
+ /// The minimum number of expected values
+ int expected_min_{1};
+ /// The maximum number of expected values
+ int expected_max_{1};
+
+ /// A list of Validators to run on each value parsed
+ std::vector<Validator> validators_{};
+
+ /// A list of options that are required with this option
+ std::set<Option *> needs_{};
+
+ /// A list of options that are excluded with this option
+ std::set<Option *> excludes_{};
+
+ ///@}
+ /// @name Other
+ ///@{
+
+ /// link back up to the parent App for fallthrough
+ App *parent_{nullptr};
+
+ /// Options store a callback to do all the work
+ callback_t callback_{};
+
+ ///@}
+ /// @name Parsing results
+ ///@{
+
+ /// complete Results of parsing
+ results_t results_{};
+ /// results after reduction
+ results_t proc_results_{};
+ /// enumeration for the option state machine
+ enum class option_state : char {
+ parsing = 0, //!< The option is currently collecting parsed results
+ validated = 2, //!< the results have been validated
+ reduced = 4, //!< a subset of results has been generated
+ callback_run = 6, //!< the callback has been executed
+ };
+ /// Whether the callback has run (needed for INI parsing)
+ option_state current_option_state_{option_state::parsing};
+ /// Specify that extra args beyond type_size_max should be allowed
+ bool allow_extra_args_{false};
+ /// Specify that the option should act like a flag vs regular option
+ bool flag_like_{false};
+ /// Control option to run the callback to set the default
+ bool run_callback_for_default_{false};
+ /// flag indicating a separator needs to be injected after each argument call
+ bool inject_separator_{false};
+ /// flag indicating that the option should trigger the validation and callback chain on each result when loaded
+ bool trigger_on_result_{false};
+ /// flag indicating that the option should force the callback regardless if any results present
+ bool force_callback_{false};
+ ///@}
+
+ /// Making an option by hand is not defined, it must be made by the App class
+ Option(std::string option_name, std::string option_description, callback_t callback, App *parent)
+ : description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) {
+ std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
+ }
+
+ public:
+ /// @name Basic
+ ///@{
+
+ Option(const Option &) = delete;
+ Option &operator=(const Option &) = delete;
+
+ /// Count the total number of times an option was passed
+ std::size_t count() const { return results_.size(); }
+
+ /// True if the option was not passed
+ bool empty() const { return results_.empty(); }
+
+ /// This bool operator returns true if any arguments were passed or the option callback is forced
+ explicit operator bool() const { return !empty() || force_callback_; }
+
+ /// Clear the parsed results (mostly for testing)
+ void clear() {
+ results_.clear();
+ current_option_state_ = option_state::parsing;
+ }
+
+ ///@}
+ /// @name Setting options
+ ///@{
+
+ /// Set the number of expected arguments
+ Option *expected(int value) {
+ if(value < 0) {
+ expected_min_ = -value;
+ if(expected_max_ < expected_min_) {
+ expected_max_ = expected_min_;
+ }
+ allow_extra_args_ = true;
+ flag_like_ = false;
+ } else if(value == detail::expected_max_vector_size) {
+ expected_min_ = 1;
+ expected_max_ = detail::expected_max_vector_size;
+ allow_extra_args_ = true;
+ flag_like_ = false;
+ } else {
+ expected_min_ = value;
+ expected_max_ = value;
+ flag_like_ = (expected_min_ == 0);
+ }
+ return this;
+ }
+
+ /// Set the range of expected arguments
+ Option *expected(int value_min, int value_max) {
+ if(value_min < 0) {
+ value_min = -value_min;
+ }
+
+ if(value_max < 0) {
+ value_max = detail::expected_max_vector_size;
+ }
+ if(value_max < value_min) {
+ expected_min_ = value_max;
+ expected_max_ = value_min;
+ } else {
+ expected_max_ = value_max;
+ expected_min_ = value_min;
+ }
+
+ return this;
+ }
+ /// Set the value of allow_extra_args which allows extra value arguments on the flag or option to be included
+ /// with each instance
+ Option *allow_extra_args(bool value = true) {
+ allow_extra_args_ = value;
+ return this;
+ }
+ /// Get the current value of allow extra args
+ bool get_allow_extra_args() const { return allow_extra_args_; }
+ /// Set the value of trigger_on_parse which specifies that the option callback should be triggered on every parse
+ Option *trigger_on_parse(bool value = true) {
+ trigger_on_result_ = value;
+ return this;
+ }
+ /// The status of trigger on parse
+ bool get_trigger_on_parse() const { return trigger_on_result_; }
+
+ /// Set the value of force_callback
+ Option *force_callback(bool value = true) {
+ force_callback_ = value;
+ return this;
+ }
+ /// The status of force_callback
+ bool get_force_callback() const { return force_callback_; }
+
+ /// Set the value of run_callback_for_default which controls whether the callback function should be called to set
+ /// the default This is controlled automatically but could be manipulated by the user.
+ Option *run_callback_for_default(bool value = true) {
+ run_callback_for_default_ = value;
+ return this;
+ }
+ /// Get the current value of run_callback_for_default
+ bool get_run_callback_for_default() const { return run_callback_for_default_; }
+
+ /// Adds a Validator with a built in type name
+ Option *check(Validator validator, const std::string &validator_name = "") {
+ validator.non_modifying();
+ validators_.push_back(std::move(validator));
+ if(!validator_name.empty())
+ validators_.back().name(validator_name);
+ return this;
+ }
+
+ /// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
+ Option *check(std::function<std::string(const std::string &)> Validator,
+ std::string Validator_description = "",
+ std::string Validator_name = "") {
+ validators_.emplace_back(Validator, std::move(Validator_description), std::move(Validator_name));
+ validators_.back().non_modifying();
+ return this;
+ }
+
+ /// Adds a transforming Validator with a built in type name
+ Option *transform(Validator Validator, const std::string &Validator_name = "") {
+ validators_.insert(validators_.begin(), std::move(Validator));
+ if(!Validator_name.empty())
+ validators_.front().name(Validator_name);
+ return this;
+ }
+
+ /// Adds a Validator-like function that can change result
+ Option *transform(const std::function<std::string(std::string)> &func,
+ std::string transform_description = "",
+ std::string transform_name = "") {
+ validators_.insert(validators_.begin(),
+ Validator(
+ [func](std::string &val) {
+ val = func(val);
+ return std::string{};
+ },
+ std::move(transform_description),
+ std::move(transform_name)));
+
+ return this;
+ }
+
+ /// Adds a user supplied function to run on each item passed in (communicate though lambda capture)
+ Option *each(const std::function<void(std::string)> &func) {
+ validators_.emplace_back(
+ [func](std::string &inout) {
+ func(inout);
+ return std::string{};
+ },
+ std::string{});
+ return this;
+ }
+ /// Get a named Validator
+ Validator *get_validator(const std::string &Validator_name = "") {
+ for(auto &Validator : validators_) {
+ if(Validator_name == Validator.get_name()) {
+ return &Validator;
+ }
+ }
+ if((Validator_name.empty()) && (!validators_.empty())) {
+ return &(validators_.front());
+ }
+ throw OptionNotFound(std::string{"Validator "} + Validator_name + " Not Found");
+ }
+
+ /// Get a Validator by index NOTE: this may not be the order of definition
+ Validator *get_validator(int index) {
+ // This is an signed int so that it is not equivalent to a pointer.
+ if(index >= 0 && index < static_cast<int>(validators_.size())) {
+ return &(validators_[static_cast<decltype(validators_)::size_type>(index)]);
+ }
+ throw OptionNotFound("Validator index is not valid");
+ }
+
+ /// Sets required options
+ Option *needs(Option *opt) {
+ if(opt != this) {
+ needs_.insert(opt);
+ }
+ return this;
+ }
+
+ /// Can find a string if needed
+ template <typename T = App> Option *needs(std::string opt_name) {
+ auto opt = static_cast<T *>(parent_)->get_option_no_throw(opt_name);
+ if(opt == nullptr) {
+ throw IncorrectConstruction::MissingOption(opt_name);
+ }
+ return needs(opt);
+ }
+
+ /// Any number supported, any mix of string and Opt
+ template <typename A, typename B, typename... ARG> Option *needs(A opt, B opt1, ARG... args) {
+ needs(opt);
+ return needs(opt1, args...);
+ }
+
+ /// Remove needs link from an option. Returns true if the option really was in the needs list.
+ bool remove_needs(Option *opt) {
+ auto iterator = std::find(std::begin(needs_), std::end(needs_), opt);
+
+ if(iterator == std::end(needs_)) {
+ return false;
+ }
+ needs_.erase(iterator);
+ return true;
+ }
+
+ /// Sets excluded options
+ Option *excludes(Option *opt) {
+ if(opt == this) {
+ throw(IncorrectConstruction("and option cannot exclude itself"));
+ }
+ excludes_.insert(opt);
+
+ // Help text should be symmetric - excluding a should exclude b
+ opt->excludes_.insert(this);
+
+ // Ignoring the insert return value, excluding twice is now allowed.
+ // (Mostly to allow both directions to be excluded by user, even though the library does it for you.)
+
+ return this;
+ }
+
+ /// Can find a string if needed
+ template <typename T = App> Option *excludes(std::string opt_name) {
+ auto opt = static_cast<T *>(parent_)->get_option_no_throw(opt_name);
+ if(opt == nullptr) {
+ throw IncorrectConstruction::MissingOption(opt_name);
+ }
+ return excludes(opt);
+ }
+
+ /// Any number supported, any mix of string and Opt
+ template <typename A, typename B, typename... ARG> Option *excludes(A opt, B opt1, ARG... args) {
+ excludes(opt);
+ return excludes(opt1, args...);
+ }
+
+ /// Remove needs link from an option. Returns true if the option really was in the needs list.
+ bool remove_excludes(Option *opt) {
+ auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt);
+
+ if(iterator == std::end(excludes_)) {
+ return false;
+ }
+ excludes_.erase(iterator);
+ return true;
+ }
+
+ /// Sets environment variable to read if no option given
+ Option *envname(std::string name) {
+ envname_ = std::move(name);
+ return this;
+ }
+
+ /// Ignore case
+ ///
+ /// The template hides the fact that we don't have the definition of App yet.
+ /// You are never expected to add an argument to the template here.
+ template <typename T = App> Option *ignore_case(bool value = true) {
+ if(!ignore_case_ && value) {
+ ignore_case_ = value;
+ auto *parent = static_cast<T *>(parent_);
+ for(const Option_p &opt : parent->options_) {
+ if(opt.get() == this) {
+ continue;
+ }
+ auto &omatch = opt->matching_name(*this);
+ if(!omatch.empty()) {
+ ignore_case_ = false;
+ throw OptionAlreadyAdded("adding ignore case caused a name conflict with " + omatch);
+ }
+ }
+ } else {
+ ignore_case_ = value;
+ }
+ return this;
+ }
+
+ /// Ignore underscores in the option names
+ ///
+ /// The template hides the fact that we don't have the definition of App yet.
+ /// You are never expected to add an argument to the template here.
+ template <typename T = App> Option *ignore_underscore(bool value = true) {
+
+ if(!ignore_underscore_ && value) {
+ ignore_underscore_ = value;
+ auto *parent = static_cast<T *>(parent_);
+ for(const Option_p &opt : parent->options_) {
+ if(opt.get() == this) {
+ continue;
+ }
+ auto &omatch = opt->matching_name(*this);
+ if(!omatch.empty()) {
+ ignore_underscore_ = false;
+ throw OptionAlreadyAdded("adding ignore underscore caused a name conflict with " + omatch);
+ }
+ }
+ } else {
+ ignore_underscore_ = value;
+ }
+ return this;
+ }
+
+ /// Take the last argument if given multiple times (or another policy)
+ Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
+ if(value != multi_option_policy_) {
+ if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size &&
+ expected_min_ > 1) { // this bizarre condition is to maintain backwards compatibility
+ // with the previous behavior of expected_ with vectors
+ expected_max_ = expected_min_;
+ }
+ multi_option_policy_ = value;
+ current_option_state_ = option_state::parsing;
+ }
+ return this;
+ }
+
+ /// Disable flag overrides values, e.g. --flag=<value> is not allowed
+ Option *disable_flag_override(bool value = true) {
+ disable_flag_override_ = value;
+ return this;
+ }
+ ///@}
+ /// @name Accessors
+ ///@{
+
+ /// The number of arguments the option expects
+ int get_type_size() const { return type_size_min_; }
+
+ /// The minimum number of arguments the option expects
+ int get_type_size_min() const { return type_size_min_; }
+ /// The maximum number of arguments the option expects
+ int get_type_size_max() const { return type_size_max_; }
+
+ /// Return the inject_separator flag
+ int get_inject_separator() const { return inject_separator_; }
+
+ /// The environment variable associated to this value
+ std::string get_envname() const { return envname_; }
+
+ /// The set of options needed
+ std::set<Option *> get_needs() const { return needs_; }
+
+ /// The set of options excluded
+ std::set<Option *> get_excludes() const { return excludes_; }
+
+ /// The default value (for help printing)
+ std::string get_default_str() const { return default_str_; }
+
+ /// Get the callback function
+ callback_t get_callback() const { return callback_; }
+
+ /// Get the long names
+ const std::vector<std::string> &get_lnames() const { return lnames_; }
+
+ /// Get the short names
+ const std::vector<std::string> &get_snames() const { return snames_; }
+
+ /// Get the flag names with specified default values
+ const std::vector<std::string> &get_fnames() const { return fnames_; }
+ /// Get a single name for the option, first of lname, pname, sname, envname
+ const std::string &get_single_name() const {
+ if(!lnames_.empty()) {
+ return lnames_[0];
+ }
+ if(!pname_.empty()) {
+ return pname_;
+ }
+ if(!snames_.empty()) {
+ return snames_[0];
+ }
+ return envname_;
+ }
+ /// The number of times the option expects to be included
+ int get_expected() const { return expected_min_; }
+
+ /// The number of times the option expects to be included
+ int get_expected_min() const { return expected_min_; }
+ /// The max number of times the option expects to be included
+ int get_expected_max() const { return expected_max_; }
+
+ /// The total min number of expected string values to be used
+ int get_items_expected_min() const { return type_size_min_ * expected_min_; }
+
+ /// Get the maximum number of items expected to be returned and used for the callback
+ int get_items_expected_max() const {
+ int t = type_size_max_;
+ return detail::checked_multiply(t, expected_max_) ? t : detail::expected_max_vector_size;
+ }
+ /// The total min number of expected string values to be used
+ int get_items_expected() const { return get_items_expected_min(); }
+
+ /// True if the argument can be given directly
+ bool get_positional() const { return pname_.length() > 0; }
+
+ /// True if option has at least one non-positional name
+ bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; }
+
+ /// True if option has description
+ bool has_description() const { return description_.length() > 0; }
+
+ /// Get the description
+ const std::string &get_description() const { return description_; }
+
+ /// Set the description
+ Option *description(std::string option_description) {
+ description_ = std::move(option_description);
+ return this;
+ }
+
+ Option *option_text(std::string text) {
+ option_text_ = std::move(text);
+ return this;
+ }
+
+ const std::string &get_option_text() const { return option_text_; }
+
+ ///@}
+ /// @name Help tools
+ ///@{
+
+ /// \brief Gets a comma separated list of names.
+ /// Will include / prefer the positional name if positional is true.
+ /// If all_options is false, pick just the most descriptive name to show.
+ /// Use `get_name(true)` to get the positional name (replaces `get_pname`)
+ std::string get_name(bool positional = false, ///< Show the positional name
+ bool all_options = false ///< Show every option
+ ) const {
+ if(get_group().empty())
+ return {}; // Hidden
+
+ if(all_options) {
+
+ std::vector<std::string> name_list;
+
+ /// The all list will never include a positional unless asked or that's the only name.
+ if((positional && (!pname_.empty())) || (snames_.empty() && lnames_.empty())) {
+ name_list.push_back(pname_);
+ }
+ if((get_items_expected() == 0) && (!fnames_.empty())) {
+ for(const std::string &sname : snames_) {
+ name_list.push_back("-" + sname);
+ if(check_fname(sname)) {
+ name_list.back() += "{" + get_flag_value(sname, "") + "}";
+ }
+ }
+
+ for(const std::string &lname : lnames_) {
+ name_list.push_back("--" + lname);
+ if(check_fname(lname)) {
+ name_list.back() += "{" + get_flag_value(lname, "") + "}";
+ }
+ }
+ } else {
+ for(const std::string &sname : snames_)
+ name_list.push_back("-" + sname);
+
+ for(const std::string &lname : lnames_)
+ name_list.push_back("--" + lname);
+ }
+
+ return detail::join(name_list);
+ }
+
+ // This returns the positional name no matter what
+ if(positional)
+ return pname_;
+
+ // Prefer long name
+ if(!lnames_.empty())
+ return std::string(2, '-') + lnames_[0];
+
+ // Or short name if no long name
+ if(!snames_.empty())
+ return std::string(1, '-') + snames_[0];
+
+ // If positional is the only name, it's okay to use that
+ return pname_;
+ }
+
+ ///@}
+ /// @name Parser tools
+ ///@{
+
+ /// Process the callback
+ void run_callback() {
+ if(force_callback_ && results_.empty()) {
+ add_result(default_str_);
+ }
+ if(current_option_state_ == option_state::parsing) {
+ _validate_results(results_);
+ current_option_state_ = option_state::validated;
+ }
+
+ if(current_option_state_ < option_state::reduced) {
+ _reduce_results(proc_results_, results_);
+ current_option_state_ = option_state::reduced;
+ }
+ if(current_option_state_ >= option_state::reduced) {
+ current_option_state_ = option_state::callback_run;
+ if(!(callback_)) {
+ return;
+ }
+ const results_t &send_results = proc_results_.empty() ? results_ : proc_results_;
+ bool local_result = callback_(send_results);
+
+ if(!local_result)
+ throw ConversionError(get_name(), results_);
+ }
+ }
+
+ /// If options share any of the same names, find it
+ const std::string &matching_name(const Option &other) const {
+ static const std::string estring;
+ for(const std::string &sname : snames_)
+ if(other.check_sname(sname))
+ return sname;
+ for(const std::string &lname : lnames_)
+ if(other.check_lname(lname))
+ return lname;
+
+ if(ignore_case_ ||
+ ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore
+ for(const std::string &sname : other.snames_)
+ if(check_sname(sname))
+ return sname;
+ for(const std::string &lname : other.lnames_)
+ if(check_lname(lname))
+ return lname;
+ }
+ return estring;
+ }
+ /// If options share any of the same names, they are equal (not counting positional)
+ bool operator==(const Option &other) const { return !matching_name(other).empty(); }
+
+ /// Check a name. Requires "-" or "--" for short / long, supports positional name
+ bool check_name(const std::string &name) const {
+
+ if(name.length() > 2 && name[0] == '-' && name[1] == '-')
+ return check_lname(name.substr(2));
+ if(name.length() > 1 && name.front() == '-')
+ return check_sname(name.substr(1));
+ if(!pname_.empty()) {
+ std::string local_pname = pname_;
+ std::string local_name = name;
+ if(ignore_underscore_) {
+ local_pname = detail::remove_underscore(local_pname);
+ local_name = detail::remove_underscore(local_name);
+ }
+ if(ignore_case_) {
+ local_pname = detail::to_lower(local_pname);
+ local_name = detail::to_lower(local_name);
+ }
+ if(local_name == local_pname) {
+ return true;
+ }
+ }
+
+ if(!envname_.empty()) {
+ // this needs to be the original since envname_ shouldn't match on case insensitivity
+ return (name == envname_);
+ }
+ return false;
+ }
+
+ /// Requires "-" to be removed from string
+ bool check_sname(std::string name) const {
+ return (detail::find_member(std::move(name), snames_, ignore_case_) >= 0);
+ }
+
+ /// Requires "--" to be removed from string
+ bool check_lname(std::string name) const {
+ return (detail::find_member(std::move(name), lnames_, ignore_case_, ignore_underscore_) >= 0);
+ }
+
+ /// Requires "--" to be removed from string
+ bool check_fname(std::string name) const {
+ if(fnames_.empty()) {
+ return false;
+ }
+ return (detail::find_member(std::move(name), fnames_, ignore_case_, ignore_underscore_) >= 0);
+ }
+
+ /// Get the value that goes for a flag, nominally gets the default value but allows for overrides if not
+ /// disabled
+ std::string get_flag_value(const std::string &name, std::string input_value) const {
+ static const std::string trueString{"true"};
+ static const std::string falseString{"false"};
+ static const std::string emptyString{"{}"};
+ // check for disable flag override_
+ if(disable_flag_override_) {
+ if(!((input_value.empty()) || (input_value == emptyString))) {
+ auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
+ if(default_ind >= 0) {
+ // We can static cast this to std::size_t because it is more than 0 in this block
+ if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) {
+ throw(ArgumentMismatch::FlagOverride(name));
+ }
+ } else {
+ if(input_value != trueString) {
+ throw(ArgumentMismatch::FlagOverride(name));
+ }
+ }
+ }
+ }
+ auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
+ if((input_value.empty()) || (input_value == emptyString)) {
+ if(flag_like_) {
+ return (ind < 0) ? trueString : default_flag_values_[static_cast<std::size_t>(ind)].second;
+ } else {
+ return (ind < 0) ? default_str_ : default_flag_values_[static_cast<std::size_t>(ind)].second;
+ }
+ }
+ if(ind < 0) {
+ return input_value;
+ }
+ if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
+ try {
+ auto val = detail::to_flag_value(input_value);
+ return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
+ } catch(const std::invalid_argument &) {
+ return input_value;
+ }
+ } else {
+ return input_value;
+ }
+ }
+
+ /// Puts a result at the end
+ Option *add_result(std::string s) {
+ _add_result(std::move(s), results_);
+ current_option_state_ = option_state::parsing;
+ return this;
+ }
+
+ /// Puts a result at the end and get a count of the number of arguments actually added
+ Option *add_result(std::string s, int &results_added) {
+ results_added = _add_result(std::move(s), results_);
+ current_option_state_ = option_state::parsing;
+ return this;
+ }
+
+ /// Puts a result at the end
+ Option *add_result(std::vector<std::string> s) {
+ current_option_state_ = option_state::parsing;
+ for(auto &str : s) {
+ _add_result(std::move(str), results_);
+ }
+ return this;
+ }
+
+ /// Get the current complete results set
+ const results_t &results() const { return results_; }
+
+ /// Get a copy of the results
+ results_t reduced_results() const {
+ results_t res = proc_results_.empty() ? results_ : proc_results_;
+ if(current_option_state_ < option_state::reduced) {
+ if(current_option_state_ == option_state::parsing) {
+ res = results_;
+ _validate_results(res);
+ }
+ if(!res.empty()) {
+ results_t extra;
+ _reduce_results(extra, res);
+ if(!extra.empty()) {
+ res = std::move(extra);
+ }
+ }
+ }
+ return res;
+ }
+
+ /// Get the results as a specified type
+ template <typename T> void results(T &output) const {
+ bool retval;
+ if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) {
+ const results_t &res = (proc_results_.empty()) ? results_ : proc_results_;
+ retval = detail::lexical_conversion<T, T>(res, output);
+ } else {
+ results_t res;
+ if(results_.empty()) {
+ if(!default_str_.empty()) {
+ // _add_results takes an rvalue only
+ _add_result(std::string(default_str_), res);
+ _validate_results(res);
+ results_t extra;
+ _reduce_results(extra, res);
+ if(!extra.empty()) {
+ res = std::move(extra);
+ }
+ } else {
+ res.emplace_back();
+ }
+ } else {
+ res = reduced_results();
+ }
+ retval = detail::lexical_conversion<T, T>(res, output);
+ }
+ if(!retval) {
+ throw ConversionError(get_name(), results_);
+ }
+ }
+
+ /// Return the results as the specified type
+ template <typename T> T as() const {
+ T output;
+ results(output);
+ return output;
+ }
+
+ /// See if the callback has been run already
+ bool get_callback_run() const { return (current_option_state_ == option_state::callback_run); }
+
+ ///@}
+ /// @name Custom options
+ ///@{
+
+ /// Set the type function to run when displayed on this option
+ Option *type_name_fn(std::function<std::string()> typefun) {
+ type_name_ = std::move(typefun);
+ return this;
+ }
+
+ /// Set a custom option typestring
+ Option *type_name(std::string typeval) {
+ type_name_fn([typeval]() { return typeval; });
+ return this;
+ }
+
+ /// Set a custom option size
+ Option *type_size(int option_type_size) {
+ if(option_type_size < 0) {
+ // this section is included for backwards compatibility
+ type_size_max_ = -option_type_size;
+ type_size_min_ = -option_type_size;
+ expected_max_ = detail::expected_max_vector_size;
+ } else {
+ type_size_max_ = option_type_size;
+ if(type_size_max_ < detail::expected_max_vector_size) {
+ type_size_min_ = option_type_size;
+ } else {
+ inject_separator_ = true;
+ }
+ if(type_size_max_ == 0)
+ required_ = false;
+ }
+ return this;
+ }
+ /// Set a custom option type size range
+ Option *type_size(int option_type_size_min, int option_type_size_max) {
+ if(option_type_size_min < 0 || option_type_size_max < 0) {
+ // this section is included for backwards compatibility
+ expected_max_ = detail::expected_max_vector_size;
+ option_type_size_min = (std::abs)(option_type_size_min);
+ option_type_size_max = (std::abs)(option_type_size_max);
+ }
+
+ if(option_type_size_min > option_type_size_max) {
+ type_size_max_ = option_type_size_min;
+ type_size_min_ = option_type_size_max;
+ } else {
+ type_size_min_ = option_type_size_min;
+ type_size_max_ = option_type_size_max;
+ }
+ if(type_size_max_ == 0) {
+ required_ = false;
+ }
+ if(type_size_max_ >= detail::expected_max_vector_size) {
+ inject_separator_ = true;
+ }
+ return this;
+ }
+
+ /// Set the value of the separator injection flag
+ void inject_separator(bool value = true) { inject_separator_ = value; }
+
+ /// Set a capture function for the default. Mostly used by App.
+ Option *default_function(const std::function<std::string()> &func) {
+ default_function_ = func;
+ return this;
+ }
+
+ /// Capture the default value from the original value (if it can be captured)
+ Option *capture_default_str() {
+ if(default_function_) {
+ default_str_ = default_function_();
+ }
+ return this;
+ }
+
+ /// Set the default value string representation (does not change the contained value)
+ Option *default_str(std::string val) {
+ default_str_ = std::move(val);
+ return this;
+ }
+
+ /// Set the default value and validate the results and run the callback if appropriate to set the value into the
+ /// bound value only available for types that can be converted to a string
+ template <typename X> Option *default_val(const X &val) {
+ std::string val_str = detail::to_string(val);
+ auto old_option_state = current_option_state_;
+ results_t old_results{std::move(results_)};
+ results_.clear();
+ try {
+ add_result(val_str);
+ // if trigger_on_result_ is set the callback already ran
+ if(run_callback_for_default_ && !trigger_on_result_) {
+ run_callback(); // run callback sets the state, we need to reset it again
+ current_option_state_ = option_state::parsing;
+ } else {
+ _validate_results(results_);
+ current_option_state_ = old_option_state;
+ }
+ } catch(const CLI::Error &) {
+ // this should be done
+ results_ = std::move(old_results);
+ current_option_state_ = old_option_state;
+ throw;
+ }
+ results_ = std::move(old_results);
+ default_str_ = std::move(val_str);
+ return this;
+ }
+
+ /// Get the full typename for this option
+ std::string get_type_name() const {
+ std::string full_type_name = type_name_();
+ if(!validators_.empty()) {
+ for(auto &Validator : validators_) {
+ std::string vtype = Validator.get_description();
+ if(!vtype.empty()) {
+ full_type_name += ":" + vtype;
+ }
+ }
+ }
+ return full_type_name;
+ }
+
+ private:
+ /// Run the results through the Validators
+ void _validate_results(results_t &res) const {
+ // Run the Validators (can change the string)
+ if(!validators_.empty()) {
+ if(type_size_max_ > 1) { // in this context index refers to the index in the type
+ int index = 0;
+ if(get_items_expected_max() < static_cast<int>(res.size()) &&
+ multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
+ // create a negative index for the earliest ones
+ index = get_items_expected_max() - static_cast<int>(res.size());
+ }
+
+ for(std::string &result : res) {
+ if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) {
+ index = 0; // reset index for variable size chunks
+ continue;
+ }
+ auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index);
+ if(!err_msg.empty())
+ throw ValidationError(get_name(), err_msg);
+ ++index;
+ }
+ } else {
+ int index = 0;
+ if(expected_max_ < static_cast<int>(res.size()) &&
+ multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
+ // create a negative index for the earliest ones
+ index = expected_max_ - static_cast<int>(res.size());
+ }
+ for(std::string &result : res) {
+ auto err_msg = _validate(result, index);
+ ++index;
+ if(!err_msg.empty())
+ throw ValidationError(get_name(), err_msg);
+ }
+ }
+ }
+ }
+
+ /** reduce the results in accordance with the MultiOptionPolicy
+ @param[out] res results are assigned to res if there if they are different
+ */
+ void _reduce_results(results_t &res, const results_t &original) const {
+
+ // max num items expected or length of vector, always at least 1
+ // Only valid for a trimming policy
+
+ res.clear();
+ // Operation depends on the policy setting
+ switch(multi_option_policy_) {
+ case MultiOptionPolicy::TakeAll:
+ break;
+ case MultiOptionPolicy::TakeLast: {
+ // Allow multi-option sizes (including 0)
+ std::size_t trim_size = std::min<std::size_t>(
+ static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
+ if(original.size() != trim_size) {
+ res.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end());
+ }
+ } break;
+ case MultiOptionPolicy::TakeFirst: {
+ std::size_t trim_size = std::min<std::size_t>(
+ static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
+ if(original.size() != trim_size) {
+ res.assign(original.begin(), original.begin() + static_cast<results_t::difference_type>(trim_size));
+ }
+ } break;
+ case MultiOptionPolicy::Join:
+ if(results_.size() > 1) {
+ res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
+ }
+ break;
+ case MultiOptionPolicy::Sum:
+ res.push_back(detail::sum_string_vector(original));
+ break;
+ case MultiOptionPolicy::Throw:
+ default: {
+ auto num_min = static_cast<std::size_t>(get_items_expected_min());
+ auto num_max = static_cast<std::size_t>(get_items_expected_max());
+ if(num_min == 0) {
+ num_min = 1;
+ }
+ if(num_max == 0) {
+ num_max = 1;
+ }
+ if(original.size() < num_min) {
+ throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size());
+ }
+ if(original.size() > num_max) {
+ throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
+ }
+ break;
+ }
+ }
+ // this check is to allow an empty vector in certain circumstances but not if expected is not zero.
+ // {} is the indicator for a an empty container
+ if(res.empty()) {
+ if(original.size() == 1 && original[0] == "{}" && get_items_expected_min() > 0) {
+ res.push_back("{}");
+ res.push_back("%%");
+ }
+ } else if(res.size() == 1 && res[0] == "{}" && get_items_expected_min() > 0) {
+ res.push_back("%%");
+ }
+ }
+
+ // Run a result through the Validators
+ std::string _validate(std::string &result, int index) const {
+ std::string err_msg;
+ if(result.empty() && expected_min_ == 0) {
+ // an empty with nothing expected is allowed
+ return err_msg;
+ }
+ for(const auto &vali : validators_) {
+ auto v = vali.get_application_index();
+ if(v == -1 || v == index) {
+ try {
+ err_msg = vali(result);
+ } catch(const ValidationError &err) {
+ err_msg = err.what();
+ }
+ if(!err_msg.empty())
+ break;
+ }
+ }
+
+ return err_msg;
+ }
+
+ /// Add a single result to the result set, taking into account delimiters
+ int _add_result(std::string &&result, std::vector<std::string> &res) const {
+ int result_count = 0;
+ if(allow_extra_args_ && !result.empty() && result.front() == '[' &&
+ result.back() == ']') { // this is now a vector string likely from the default or user entry
+ result.pop_back();
+
+ for(auto &var : CLI::detail::split(result.substr(1), ',')) {
+ if(!var.empty()) {
+ result_count += _add_result(std::move(var), res);
+ }
+ }
+ return result_count;
+ }
+ if(delimiter_ == '\0') {
+ res.push_back(std::move(result));
+ ++result_count;
+ } else {
+ if((result.find_first_of(delimiter_) != std::string::npos)) {
+ for(const auto &var : CLI::detail::split(result, delimiter_)) {
+ if(!var.empty()) {
+ res.push_back(var);
+ ++result_count;
+ }
+ }
+ } else {
+ res.push_back(std::move(result));
+ ++result_count;
+ }
+ }
+ return result_count;
+ }
+};
+
+// [CLI11:option_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/Split.hpp b/src/third-party/CLI/Split.hpp
new file mode 100644
index 0000000..0ccfb57
--- /dev/null
+++ b/src/third-party/CLI/Split.hpp
@@ -0,0 +1,143 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "Error.hpp"
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:split_hpp:verbatim]
+
+namespace detail {
+
+// Returns false if not a short option. Otherwise, sets opt name and rest and returns true
+inline bool split_short(const std::string &current, std::string &name, std::string &rest) {
+ if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) {
+ name = current.substr(1, 1);
+ rest = current.substr(2);
+ return true;
+ }
+ return false;
+}
+
+// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
+inline bool split_long(const std::string &current, std::string &name, std::string &value) {
+ if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) {
+ auto loc = current.find_first_of('=');
+ if(loc != std::string::npos) {
+ name = current.substr(2, loc - 2);
+ value = current.substr(loc + 1);
+ } else {
+ name = current.substr(2);
+ value = "";
+ }
+ return true;
+ }
+ return false;
+}
+
+// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
+inline bool split_windows_style(const std::string &current, std::string &name, std::string &value) {
+ if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
+ auto loc = current.find_first_of(':');
+ if(loc != std::string::npos) {
+ name = current.substr(1, loc - 1);
+ value = current.substr(loc + 1);
+ } else {
+ name = current.substr(1);
+ value = "";
+ }
+ return true;
+ }
+ return false;
+}
+
+// Splits a string into multiple long and short names
+inline std::vector<std::string> split_names(std::string current) {
+ std::vector<std::string> output;
+ std::size_t val;
+ while((val = current.find(",")) != std::string::npos) {
+ output.push_back(trim_copy(current.substr(0, val)));
+ current = current.substr(val + 1);
+ }
+ output.push_back(trim_copy(current));
+ return output;
+}
+
+/// extract default flag values either {def} or starting with a !
+inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
+ std::vector<std::string> flags = split_names(str);
+ flags.erase(std::remove_if(flags.begin(),
+ flags.end(),
+ [](const std::string &name) {
+ return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&
+ (name.back() == '}')) ||
+ (name[0] == '!'))));
+ }),
+ flags.end());
+ std::vector<std::pair<std::string, std::string>> output;
+ output.reserve(flags.size());
+ for(auto &flag : flags) {
+ auto def_start = flag.find_first_of('{');
+ std::string defval = "false";
+ if((def_start != std::string::npos) && (flag.back() == '}')) {
+ defval = flag.substr(def_start + 1);
+ defval.pop_back();
+ flag.erase(def_start, std::string::npos);
+ }
+ flag.erase(0, flag.find_first_not_of("-!"));
+ output.emplace_back(flag, defval);
+ }
+ return output;
+}
+
+/// Get a vector of short names, one of long names, and a single name
+inline std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
+get_names(const std::vector<std::string> &input) {
+
+ std::vector<std::string> short_names;
+ std::vector<std::string> long_names;
+ std::string pos_name;
+
+ for(std::string name : input) {
+ if(name.length() == 0) {
+ continue;
+ }
+ if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
+ if(name.length() == 2 && valid_first_char(name[1]))
+ short_names.emplace_back(1, name[1]);
+ else
+ throw BadNameString::OneCharName(name);
+ } else if(name.length() > 2 && name.substr(0, 2) == "--") {
+ name = name.substr(2);
+ if(valid_name_string(name))
+ long_names.push_back(name);
+ else
+ throw BadNameString::BadLongName(name);
+ } else if(name == "-" || name == "--") {
+ throw BadNameString::DashesOnly(name);
+ } else {
+ if(pos_name.length() > 0)
+ throw BadNameString::MultiPositionalNames(name);
+ pos_name = name;
+ }
+ }
+
+ return std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>(
+ short_names, long_names, pos_name);
+}
+
+} // namespace detail
+// [CLI11:split_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/StringTools.hpp b/src/third-party/CLI/StringTools.hpp
new file mode 100644
index 0000000..5126645
--- /dev/null
+++ b/src/third-party/CLI/StringTools.hpp
@@ -0,0 +1,430 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <iomanip>
+#include <locale>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <vector>
+// [CLI11:public_includes:end]
+
+namespace CLI {
+
+// [CLI11:string_tools_hpp:verbatim]
+
+/// Include the items in this namespace to get free conversion of enums to/from streams.
+/// (This is available inside CLI as well, so CLI11 will use this without a using statement).
+namespace enums {
+
+/// output streaming for enumerations
+template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
+std::ostream &operator<<(std::ostream &in, const T &item) {
+ // make sure this is out of the detail namespace otherwise it won't be found when needed
+ return in << static_cast<typename std::underlying_type<T>::type>(item);
+}
+
+} // namespace enums
+
+/// Export to CLI namespace
+using enums::operator<<;
+
+namespace detail {
+/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not
+/// produce overflow for some expected uses
+constexpr int expected_max_vector_size{1 << 29};
+// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
+/// Split a string by a delim
+inline std::vector<std::string> split(const std::string &s, char delim) {
+ std::vector<std::string> elems;
+ // Check to see if empty string, give consistent result
+ if(s.empty()) {
+ elems.emplace_back();
+ } else {
+ std::stringstream ss;
+ ss.str(s);
+ std::string item;
+ while(std::getline(ss, item, delim)) {
+ elems.push_back(item);
+ }
+ }
+ return elems;
+}
+
+/// Simple function to join a string
+template <typename T> std::string join(const T &v, std::string delim = ",") {
+ std::ostringstream s;
+ auto beg = std::begin(v);
+ auto end = std::end(v);
+ if(beg != end)
+ s << *beg++;
+ while(beg != end) {
+ s << delim << *beg++;
+ }
+ return s.str();
+}
+
+/// Simple function to join a string from processed elements
+template <typename T,
+ typename Callable,
+ typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
+std::string join(const T &v, Callable func, std::string delim = ",") {
+ std::ostringstream s;
+ auto beg = std::begin(v);
+ auto end = std::end(v);
+ auto loc = s.tellp();
+ while(beg != end) {
+ auto nloc = s.tellp();
+ if(nloc > loc) {
+ s << delim;
+ loc = nloc;
+ }
+ s << func(*beg++);
+ }
+ return s.str();
+}
+
+/// Join a string in reverse order
+template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
+ std::ostringstream s;
+ for(std::size_t start = 0; start < v.size(); start++) {
+ if(start > 0)
+ s << delim;
+ s << v[v.size() - start - 1];
+ }
+ return s.str();
+}
+
+// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string
+
+/// Trim whitespace from left of string
+inline std::string &ltrim(std::string &str) {
+ auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
+ str.erase(str.begin(), it);
+ return str;
+}
+
+/// Trim anything from left of string
+inline std::string &ltrim(std::string &str, const std::string &filter) {
+ auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
+ str.erase(str.begin(), it);
+ return str;
+}
+
+/// Trim whitespace from right of string
+inline std::string &rtrim(std::string &str) {
+ auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
+ str.erase(it.base(), str.end());
+ return str;
+}
+
+/// Trim anything from right of string
+inline std::string &rtrim(std::string &str, const std::string &filter) {
+ auto it =
+ std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
+ str.erase(it.base(), str.end());
+ return str;
+}
+
+/// Trim whitespace from string
+inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); }
+
+/// Trim anything from string
+inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); }
+
+/// Make a copy of the string and then trim it
+inline std::string trim_copy(const std::string &str) {
+ std::string s = str;
+ return trim(s);
+}
+
+/// remove quotes at the front and back of a string either '"' or '\''
+inline std::string &remove_quotes(std::string &str) {
+ if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
+ if(str.front() == str.back()) {
+ str.pop_back();
+ str.erase(str.begin(), str.begin() + 1);
+ }
+ }
+ return str;
+}
+
+/// Add a leader to the beginning of all new lines (nothing is added
+/// at the start of the first line). `"; "` would be for ini files
+///
+/// Can't use Regex, or this would be a subs.
+inline std::string fix_newlines(const std::string &leader, std::string input) {
+ std::string::size_type n = 0;
+ while(n != std::string::npos && n < input.size()) {
+ n = input.find('\n', n);
+ if(n != std::string::npos) {
+ input = input.substr(0, n + 1) + leader + input.substr(n + 1);
+ n += leader.size();
+ }
+ }
+ return input;
+}
+
+/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered)
+inline std::string trim_copy(const std::string &str, const std::string &filter) {
+ std::string s = str;
+ return trim(s, filter);
+}
+/// Print a two part "help" string
+inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) {
+ name = " " + name;
+ out << std::setw(static_cast<int>(wid)) << std::left << name;
+ if(!description.empty()) {
+ if(name.length() >= wid)
+ out << "\n" << std::setw(static_cast<int>(wid)) << "";
+ for(const char c : description) {
+ out.put(c);
+ if(c == '\n') {
+ out << std::setw(static_cast<int>(wid)) << "";
+ }
+ }
+ }
+ out << "\n";
+ return out;
+}
+
+/// Print subcommand aliases
+inline std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
+ if(!aliases.empty()) {
+ out << std::setw(static_cast<int>(wid)) << " aliases: ";
+ bool front = true;
+ for(const auto &alias : aliases) {
+ if(!front) {
+ out << ", ";
+ } else {
+ front = false;
+ }
+ out << detail::fix_newlines(" ", alias);
+ }
+ out << "\n";
+ }
+ return out;
+}
+
+/// Verify the first character of an option
+/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with
+template <typename T> bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); }
+
+/// Verify following characters of an option
+template <typename T> bool valid_later_char(T c) {
+ // = and : are value separators, { has special meaning for option defaults,
+ // and \n would just be annoying to deal with in many places allowing space here has too much potential for
+ // inadvertent entry errors and bugs
+ return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n');
+}
+
+/// Verify an option/subcommand name
+inline bool valid_name_string(const std::string &str) {
+ if(str.empty() || !valid_first_char(str[0])) {
+ return false;
+ }
+ auto e = str.end();
+ for(auto c = str.begin() + 1; c != e; ++c)
+ if(!valid_later_char(*c))
+ return false;
+ return true;
+}
+
+/// Verify an app name
+inline bool valid_alias_name_string(const std::string &str) {
+ static const std::string badChars(std::string("\n") + '\0');
+ return (str.find_first_of(badChars) == std::string::npos);
+}
+
+/// check if a string is a container segment separator (empty or "%%")
+inline bool is_separator(const std::string &str) {
+ static const std::string sep("%%");
+ return (str.empty() || str == sep);
+}
+
+/// Verify that str consists of letters only
+inline bool isalpha(const std::string &str) {
+ return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
+}
+
+/// Return a lower case version of a string
+inline std::string to_lower(std::string str) {
+ std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) {
+ return std::tolower(x, std::locale());
+ });
+ return str;
+}
+
+/// remove underscores from a string
+inline std::string remove_underscore(std::string str) {
+ str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
+ return str;
+}
+
+/// Find and replace a substring with another substring
+inline std::string find_and_replace(std::string str, std::string from, std::string to) {
+
+ std::size_t start_pos = 0;
+
+ while((start_pos = str.find(from, start_pos)) != std::string::npos) {
+ str.replace(start_pos, from.length(), to);
+ start_pos += to.length();
+ }
+
+ return str;
+}
+
+/// check if the flag definitions has possible false flags
+inline bool has_default_flag_values(const std::string &flags) {
+ return (flags.find_first_of("{!") != std::string::npos);
+}
+
+inline void remove_default_flag_values(std::string &flags) {
+ auto loc = flags.find_first_of('{', 2);
+ while(loc != std::string::npos) {
+ auto finish = flags.find_first_of("},", loc + 1);
+ if((finish != std::string::npos) && (flags[finish] == '}')) {
+ flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
+ flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
+ }
+ loc = flags.find_first_of('{', loc + 1);
+ }
+ flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
+}
+
+/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
+inline std::ptrdiff_t find_member(std::string name,
+ const std::vector<std::string> names,
+ bool ignore_case = false,
+ bool ignore_underscore = false) {
+ auto it = std::end(names);
+ if(ignore_case) {
+ if(ignore_underscore) {
+ name = detail::to_lower(detail::remove_underscore(name));
+ it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
+ return detail::to_lower(detail::remove_underscore(local_name)) == name;
+ });
+ } else {
+ name = detail::to_lower(name);
+ it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
+ return detail::to_lower(local_name) == name;
+ });
+ }
+
+ } else if(ignore_underscore) {
+ name = detail::remove_underscore(name);
+ it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
+ return detail::remove_underscore(local_name) == name;
+ });
+ } else {
+ it = std::find(std::begin(names), std::end(names), name);
+ }
+
+ return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
+}
+
+/// Find a trigger string and call a modify callable function that takes the current string and starting position of the
+/// trigger and returns the position in the string to search for the next trigger string
+template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) {
+ std::size_t start_pos = 0;
+ while((start_pos = str.find(trigger, start_pos)) != std::string::npos) {
+ start_pos = modify(str, start_pos);
+ }
+ return str;
+}
+
+/// Split a string '"one two" "three"' into 'one two', 'three'
+/// Quote characters can be ` ' or "
+inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') {
+
+ const std::string delims("\'\"`");
+ auto find_ws = [delimiter](char ch) {
+ return (delimiter == '\0') ? (std::isspace<char>(ch, std::locale()) != 0) : (ch == delimiter);
+ };
+ trim(str);
+
+ std::vector<std::string> output;
+ bool embeddedQuote = false;
+ char keyChar = ' ';
+ while(!str.empty()) {
+ if(delims.find_first_of(str[0]) != std::string::npos) {
+ keyChar = str[0];
+ auto end = str.find_first_of(keyChar, 1);
+ while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
+ end = str.find_first_of(keyChar, end + 1);
+ embeddedQuote = true;
+ }
+ if(end != std::string::npos) {
+ output.push_back(str.substr(1, end - 1));
+ if(end + 2 < str.size()) {
+ str = str.substr(end + 2);
+ } else {
+ str.clear();
+ }
+
+ } else {
+ output.push_back(str.substr(1));
+ str = "";
+ }
+ } else {
+ auto it = std::find_if(std::begin(str), std::end(str), find_ws);
+ if(it != std::end(str)) {
+ std::string value = std::string(str.begin(), it);
+ output.push_back(value);
+ str = std::string(it + 1, str.end());
+ } else {
+ output.push_back(str);
+ str = "";
+ }
+ }
+ // transform any embedded quotes into the regular character
+ if(embeddedQuote) {
+ output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
+ embeddedQuote = false;
+ }
+ trim(str);
+ }
+ return output;
+}
+
+/// This function detects an equal or colon followed by an escaped quote after an argument
+/// then modifies the string to replace the equality with a space. This is needed
+/// to allow the split up function to work properly and is intended to be used with the find_and_modify function
+/// the return value is the offset+1 which is required by the find_and_modify function.
+inline std::size_t escape_detect(std::string &str, std::size_t offset) {
+ auto next = str[offset + 1];
+ if((next == '\"') || (next == '\'') || (next == '`')) {
+ auto astart = str.find_last_of("-/ \"\'`", offset - 1);
+ if(astart != std::string::npos) {
+ if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
+ str[offset] = ' '; // interpret this as a space so the split_up works properly
+ }
+ }
+ return offset + 1;
+}
+
+/// Add quotes if the string contains spaces
+inline std::string &add_quotes_if_needed(std::string &str) {
+ if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
+ char quote = str.find('"') < str.find('\'') ? '\'' : '"';
+ if(str.find(' ') != std::string::npos) {
+ str.insert(0, 1, quote);
+ str.append(1, quote);
+ }
+ }
+ return str;
+}
+
+} // namespace detail
+
+// [CLI11:string_tools_hpp:end]
+
+} // namespace CLI
diff --git a/src/third-party/CLI/Timer.hpp b/src/third-party/CLI/Timer.hpp
new file mode 100644
index 0000000..e97b17c
--- /dev/null
+++ b/src/third-party/CLI/Timer.hpp
@@ -0,0 +1,134 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// On GCC < 4.8, the following define is often missing. Due to the
+// fact that this library only uses sleep_for, this should be safe
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && __GNUC_MINOR__ < 8
+#define _GLIBCXX_USE_NANOSLEEP
+#endif
+
+#include <array>
+#include <chrono>
+#include <functional>
+#include <iostream>
+#include <string>
+#include <utility>
+
+namespace CLI {
+
+/// This is a simple timer with pretty printing. Creating the timer starts counting.
+class Timer {
+ protected:
+ /// This is a typedef to make clocks easier to use
+ using clock = std::chrono::steady_clock;
+
+ /// This typedef is for points in time
+ using time_point = std::chrono::time_point<clock>;
+
+ /// This is the type of a printing function, you can make your own
+ using time_print_t = std::function<std::string(std::string, std::string)>;
+
+ /// This is the title of the timer
+ std::string title_;
+
+ /// This is the function that is used to format most of the timing message
+ time_print_t time_print_;
+
+ /// This is the starting point (when the timer was created)
+ time_point start_;
+
+ /// This is the number of times cycles (print divides by this number)
+ std::size_t cycles{1};
+
+ public:
+ /// Standard print function, this one is set by default
+ static std::string Simple(std::string title, std::string time) { return title + ": " + time; }
+
+ /// This is a fancy print function with --- headers
+ static std::string Big(std::string title, std::string time) {
+ return std::string("-----------------------------------------\n") + "| " + title + " | Time = " + time + "\n" +
+ "-----------------------------------------";
+ }
+
+ public:
+ /// Standard constructor, can set title and print function
+ explicit Timer(std::string title = "Timer", time_print_t time_print = Simple)
+ : title_(std::move(title)), time_print_(std::move(time_print)), start_(clock::now()) {}
+
+ /// Time a function by running it multiple times. Target time is the len to target.
+ std::string time_it(std::function<void()> f, double target_time = 1) {
+ time_point start = start_;
+ double total_time;
+
+ start_ = clock::now();
+ std::size_t n = 0;
+ do {
+ f();
+ std::chrono::duration<double> elapsed = clock::now() - start_;
+ total_time = elapsed.count();
+ } while(n++ < 100u && total_time < target_time);
+
+ std::string out = make_time_str(total_time / static_cast<double>(n)) + " for " + std::to_string(n) + " tries";
+ start_ = start;
+ return out;
+ }
+
+ /// This formats the numerical value for the time string
+ std::string make_time_str() const {
+ time_point stop = clock::now();
+ std::chrono::duration<double> elapsed = stop - start_;
+ double time = elapsed.count() / static_cast<double>(cycles);
+ return make_time_str(time);
+ }
+
+ // LCOV_EXCL_START
+ /// This prints out a time string from a time
+ std::string make_time_str(double time) const {
+ auto print_it = [](double x, std::string unit) {
+ const unsigned int buffer_length = 50;
+ std::array<char, buffer_length> buffer;
+ std::snprintf(buffer.data(), buffer_length, "%.5g", x);
+ return buffer.data() + std::string(" ") + unit;
+ };
+
+ if(time < .000001)
+ return print_it(time * 1000000000, "ns");
+ else if(time < .001)
+ return print_it(time * 1000000, "us");
+ else if(time < 1)
+ return print_it(time * 1000, "ms");
+ else
+ return print_it(time, "s");
+ }
+ // LCOV_EXCL_STOP
+
+ /// This is the main function, it creates a string
+ std::string to_string() const { return time_print_(title_, make_time_str()); }
+
+ /// Division sets the number of cycles to divide by (no graphical change)
+ Timer &operator/(std::size_t val) {
+ cycles = val;
+ return *this;
+ }
+};
+
+/// This class prints out the time upon destruction
+class AutoTimer : public Timer {
+ public:
+ /// Reimplementing the constructor is required in GCC 4.7
+ explicit AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {}
+ // GCC 4.7 does not support using inheriting constructors.
+
+ /// This destructor prints the string
+ ~AutoTimer() { std::cout << to_string() << std::endl; }
+};
+
+} // namespace CLI
+
+/// This prints out the time if shifted into a std::cout like stream.
+inline std::ostream &operator<<(std::ostream &in, const CLI::Timer &timer) { return in << timer.to_string(); }
diff --git a/src/third-party/CLI/TypeTools.hpp b/src/third-party/CLI/TypeTools.hpp
new file mode 100644
index 0000000..c21d5e3
--- /dev/null
+++ b/src/third-party/CLI/TypeTools.hpp
@@ -0,0 +1,1558 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <cstdint>
+#include <exception>
+#include <limits>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:type_tools_hpp:verbatim]
+
+// Type tools
+
+// Utilities for type enabling
+namespace detail {
+// Based generally on https://rmf.io/cxx11/almost-static-if
+/// Simple empty scoped class
+enum class enabler {};
+
+/// An instance to use in EnableIf
+constexpr enabler dummy = {};
+} // namespace detail
+
+/// A copy of enable_if_t from C++14, compatible with C++11.
+///
+/// We could check to see if C++14 is being used, but it does not hurt to redefine this
+/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h)
+/// It is not in the std namespace anyway, so no harm done.
+template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type;
+
+/// A copy of std::void_t from C++17 (helper for C++11 and C++14)
+template <typename... Ts> struct make_void { using type = void; };
+
+/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine
+template <typename... Ts> using void_t = typename make_void<Ts...>::type;
+
+/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine
+template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type;
+
+/// Check to see if something is bool (fail check by default)
+template <typename T> struct is_bool : std::false_type {};
+
+/// Check to see if something is bool (true if actually a bool)
+template <> struct is_bool<bool> : std::true_type {};
+
+/// Check to see if something is a shared pointer
+template <typename T> struct is_shared_ptr : std::false_type {};
+
+/// Check to see if something is a shared pointer (True if really a shared pointer)
+template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
+
+/// Check to see if something is a shared pointer (True if really a shared pointer)
+template <typename T> struct is_shared_ptr<const std::shared_ptr<T>> : std::true_type {};
+
+/// Check to see if something is copyable pointer
+template <typename T> struct is_copyable_ptr {
+ static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value;
+};
+
+/// This can be specialized to override the type deduction for IsMember.
+template <typename T> struct IsMemberType { using type = T; };
+
+/// The main custom type needed here is const char * should be a string.
+template <> struct IsMemberType<const char *> { using type = std::string; };
+
+namespace detail {
+
+// These are utilities for IsMember and other transforming objects
+
+/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that
+/// pointer_traits<T> be valid.
+
+/// not a pointer
+template <typename T, typename Enable = void> struct element_type { using type = T; };
+
+template <typename T> struct element_type<T, typename std::enable_if<is_copyable_ptr<T>::value>::type> {
+ using type = typename std::pointer_traits<T>::element_type;
+};
+
+/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of
+/// the container
+template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; };
+
+/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing.
+template <typename T, typename _ = void> struct pair_adaptor : std::false_type {
+ using value_type = typename T::value_type;
+ using first_type = typename std::remove_const<value_type>::type;
+ using second_type = typename std::remove_const<value_type>::type;
+
+ /// Get the first value (really just the underlying value)
+ template <typename Q> static auto first(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
+ return std::forward<Q>(pair_value);
+ }
+ /// Get the second value (really just the underlying value)
+ template <typename Q> static auto second(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
+ return std::forward<Q>(pair_value);
+ }
+};
+
+/// Adaptor for map-like structure (true version, must have key_type and mapped_type).
+/// This wraps a mapped container in a few utilities access it in a general way.
+template <typename T>
+struct pair_adaptor<
+ T,
+ conditional_t<false, void_t<typename T::value_type::first_type, typename T::value_type::second_type>, void>>
+ : std::true_type {
+ using value_type = typename T::value_type;
+ using first_type = typename std::remove_const<typename value_type::first_type>::type;
+ using second_type = typename std::remove_const<typename value_type::second_type>::type;
+
+ /// Get the first value (really just the underlying value)
+ template <typename Q> static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward<Q>(pair_value))) {
+ return std::get<0>(std::forward<Q>(pair_value));
+ }
+ /// Get the second value (really just the underlying value)
+ template <typename Q> static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward<Q>(pair_value))) {
+ return std::get<1>(std::forward<Q>(pair_value));
+ }
+};
+
+// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning
+// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in
+// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a
+// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out.
+// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be
+// suppressed
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnarrowing"
+#endif
+// check for constructibility from a specific type and copy assignable used in the parse detection
+template <typename T, typename C> class is_direct_constructible {
+ template <typename TT, typename CC>
+ static auto test(int, std::true_type) -> decltype(
+// NVCC warns about narrowing conversions here
+#ifdef __CUDACC__
+#pragma diag_suppress 2361
+#endif
+ TT { std::declval<CC>() }
+#ifdef __CUDACC__
+#pragma diag_default 2361
+#endif
+ ,
+ std::is_move_assignable<TT>());
+
+ template <typename TT, typename CC> static auto test(int, std::false_type) -> std::false_type;
+
+ template <typename, typename> static auto test(...) -> std::false_type;
+
+ public:
+ static constexpr bool value = decltype(test<T, C>(0, typename std::is_constructible<T, C>::type()))::value;
+};
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+// Check for output streamability
+// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream
+
+template <typename T, typename S = std::ostringstream> class is_ostreamable {
+ template <typename TT, typename SS>
+ static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
+
+ template <typename, typename> static auto test(...) -> std::false_type;
+
+ public:
+ static constexpr bool value = decltype(test<T, S>(0))::value;
+};
+
+/// Check for input streamability
+template <typename T, typename S = std::istringstream> class is_istreamable {
+ template <typename TT, typename SS>
+ static auto test(int) -> decltype(std::declval<SS &>() >> std::declval<TT &>(), std::true_type());
+
+ template <typename, typename> static auto test(...) -> std::false_type;
+
+ public:
+ static constexpr bool value = decltype(test<T, S>(0))::value;
+};
+
+/// Check for complex
+template <typename T> class is_complex {
+ template <typename TT>
+ static auto test(int) -> decltype(std::declval<TT>().real(), std::declval<TT>().imag(), std::true_type());
+
+ template <typename> static auto test(...) -> std::false_type;
+
+ public:
+ static constexpr bool value = decltype(test<T>(0))::value;
+};
+
+/// Templated operation to get a value from a stream
+template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy>
+bool from_stream(const std::string &istring, T &obj) {
+ std::istringstream is;
+ is.str(istring);
+ is >> obj;
+ return !is.fail() && !is.rdbuf()->in_avail();
+}
+
+template <typename T, enable_if_t<!is_istreamable<T>::value, detail::enabler> = detail::dummy>
+bool from_stream(const std::string & /*istring*/, T & /*obj*/) {
+ return false;
+}
+
+// check to see if an object is a mutable container (fail by default)
+template <typename T, typename _ = void> struct is_mutable_container : std::false_type {};
+
+/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and
+/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed
+/// from a std::string
+template <typename T>
+struct is_mutable_container<
+ T,
+ conditional_t<false,
+ void_t<typename T::value_type,
+ decltype(std::declval<T>().end()),
+ decltype(std::declval<T>().clear()),
+ decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(),
+ std::declval<const typename T::value_type &>()))>,
+ void>>
+ : public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {};
+
+// check to see if an object is a mutable container (fail by default)
+template <typename T, typename _ = void> struct is_readable_container : std::false_type {};
+
+/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end
+/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from
+/// a std::string
+template <typename T>
+struct is_readable_container<
+ T,
+ conditional_t<false, void_t<decltype(std::declval<T>().end()), decltype(std::declval<T>().begin())>, void>>
+ : public std::true_type {};
+
+// check to see if an object is a wrapper (fail by default)
+template <typename T, typename _ = void> struct is_wrapper : std::false_type {};
+
+// check if an object is a wrapper (it has a value_type defined)
+template <typename T>
+struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {};
+
+// Check for tuple like types, as in classes with a tuple_size type trait
+template <typename S> class is_tuple_like {
+ template <typename SS>
+ // static auto test(int)
+ // -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type());
+ static auto test(int) -> decltype(std::tuple_size<typename std::decay<SS>::type>::value, std::true_type{});
+ template <typename> static auto test(...) -> std::false_type;
+
+ public:
+ static constexpr bool value = decltype(test<S>(0))::value;
+};
+
+/// Convert an object to a string (directly forward if this can become a string)
+template <typename T, enable_if_t<std::is_convertible<T, std::string>::value, detail::enabler> = detail::dummy>
+auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
+ return std::forward<T>(value);
+}
+
+/// Construct a string from the object
+template <typename T,
+ enable_if_t<std::is_constructible<std::string, T>::value && !std::is_convertible<T, std::string>::value,
+ detail::enabler> = detail::dummy>
+std::string to_string(const T &value) {
+ return std::string(value);
+}
+
+/// Convert an object to a string (streaming must be supported for that type)
+template <typename T,
+ enable_if_t<!std::is_convertible<std::string, T>::value && !std::is_constructible<std::string, T>::value &&
+ is_ostreamable<T>::value,
+ detail::enabler> = detail::dummy>
+std::string to_string(T &&value) {
+ std::stringstream stream;
+ stream << value;
+ return stream.str();
+}
+
+/// If conversion is not supported, return an empty string (streaming is not supported for that type)
+template <typename T,
+ enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
+ !is_readable_container<typename std::remove_const<T>::type>::value,
+ detail::enabler> = detail::dummy>
+std::string to_string(T &&) {
+ return std::string{};
+}
+
+/// convert a readable container to a string
+template <typename T,
+ enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
+ is_readable_container<T>::value,
+ detail::enabler> = detail::dummy>
+std::string to_string(T &&variable) {
+ auto cval = variable.begin();
+ auto end = variable.end();
+ if(cval == end) {
+ return std::string("{}");
+ }
+ std::vector<std::string> defaults;
+ while(cval != end) {
+ defaults.emplace_back(CLI::detail::to_string(*cval));
+ ++cval;
+ }
+ return std::string("[" + detail::join(defaults) + "]");
+}
+
+/// special template overload
+template <typename T1,
+ typename T2,
+ typename T,
+ enable_if_t<std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
+auto checked_to_string(T &&value) -> decltype(to_string(std::forward<T>(value))) {
+ return to_string(std::forward<T>(value));
+}
+
+/// special template overload
+template <typename T1,
+ typename T2,
+ typename T,
+ enable_if_t<!std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
+std::string checked_to_string(T &&) {
+ return std::string{};
+}
+/// get a string as a convertible value for arithmetic types
+template <typename T, enable_if_t<std::is_arithmetic<T>::value, detail::enabler> = detail::dummy>
+std::string value_string(const T &value) {
+ return std::to_string(value);
+}
+/// get a string as a convertible value for enumerations
+template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
+std::string value_string(const T &value) {
+ return std::to_string(static_cast<typename std::underlying_type<T>::type>(value));
+}
+/// for other types just use the regular to_string function
+template <typename T,
+ enable_if_t<!std::is_enum<T>::value && !std::is_arithmetic<T>::value, detail::enabler> = detail::dummy>
+auto value_string(const T &value) -> decltype(to_string(value)) {
+ return to_string(value);
+}
+
+/// template to get the underlying value type if it exists or use a default
+template <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; };
+
+/// Type size for regular object types that do not look like a tuple
+template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> {
+ using type = typename T::value_type;
+};
+
+/// This will only trigger for actual void type
+template <typename T, typename Enable = void> struct type_count_base { static const int value{0}; };
+
+/// Type size for regular object types that do not look like a tuple
+template <typename T>
+struct type_count_base<T,
+ typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value &&
+ !std::is_void<T>::value>::type> {
+ static constexpr int value{1};
+};
+
+/// the base tuple size
+template <typename T>
+struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> {
+ static constexpr int value{std::tuple_size<T>::value};
+};
+
+/// Type count base for containers is the type_count_base of the individual element
+template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
+ static constexpr int value{type_count_base<typename T::value_type>::value};
+};
+
+/// Set of overloads to get the type size of an object
+
+/// forward declare the subtype_count structure
+template <typename T> struct subtype_count;
+
+/// forward declare the subtype_count_min structure
+template <typename T> struct subtype_count_min;
+
+/// This will only trigger for actual void type
+template <typename T, typename Enable = void> struct type_count { static const int value{0}; };
+
+/// Type size for regular object types that do not look like a tuple
+template <typename T>
+struct type_count<T,
+ typename std::enable_if<!is_wrapper<T>::value && !is_tuple_like<T>::value && !is_complex<T>::value &&
+ !std::is_void<T>::value>::type> {
+ static constexpr int value{1};
+};
+
+/// Type size for complex since it sometimes looks like a wrapper
+template <typename T> struct type_count<T, typename std::enable_if<is_complex<T>::value>::type> {
+ static constexpr int value{2};
+};
+
+/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
+template <typename T> struct type_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
+ static constexpr int value{subtype_count<typename T::value_type>::value};
+};
+
+/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes)
+template <typename T>
+struct type_count<T,
+ typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value &&
+ !is_mutable_container<T>::value>::type> {
+ static constexpr int value{type_count<typename T::value_type>::value};
+};
+
+/// 0 if the index > tuple size
+template <typename T, std::size_t I>
+constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size() {
+ return 0;
+}
+
+/// Recursively generate the tuple type name
+template <typename T, std::size_t I>
+ constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size() {
+ return subtype_count<typename std::tuple_element<I, T>::type>::value + tuple_type_size<T, I + 1>();
+}
+
+/// Get the type size of the sum of type sizes for all the individual tuple types
+template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
+ static constexpr int value{tuple_type_size<T, 0>()};
+};
+
+/// definition of subtype count
+template <typename T> struct subtype_count {
+ static constexpr int value{is_mutable_container<T>::value ? expected_max_vector_size : type_count<T>::value};
+};
+
+/// This will only trigger for actual void type
+template <typename T, typename Enable = void> struct type_count_min { static const int value{0}; };
+
+/// Type size for regular object types that do not look like a tuple
+template <typename T>
+struct type_count_min<
+ T,
+ typename std::enable_if<!is_mutable_container<T>::value && !is_tuple_like<T>::value && !is_wrapper<T>::value &&
+ !is_complex<T>::value && !std::is_void<T>::value>::type> {
+ static constexpr int value{type_count<T>::value};
+};
+
+/// Type size for complex since it sometimes looks like a wrapper
+template <typename T> struct type_count_min<T, typename std::enable_if<is_complex<T>::value>::type> {
+ static constexpr int value{1};
+};
+
+/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
+template <typename T>
+struct type_count_min<
+ T,
+ typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value>::type> {
+ static constexpr int value{subtype_count_min<typename T::value_type>::value};
+};
+
+/// 0 if the index > tuple size
+template <typename T, std::size_t I>
+constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size_min() {
+ return 0;
+}
+
+/// Recursively generate the tuple type name
+template <typename T, std::size_t I>
+ constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size_min() {
+ return subtype_count_min<typename std::tuple_element<I, T>::type>::value + tuple_type_size_min<T, I + 1>();
+}
+
+/// Get the type size of the sum of type sizes for all the individual tuple types
+template <typename T> struct type_count_min<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
+ static constexpr int value{tuple_type_size_min<T, 0>()};
+};
+
+/// definition of subtype count
+template <typename T> struct subtype_count_min {
+ static constexpr int value{is_mutable_container<T>::value
+ ? ((type_count<T>::value < expected_max_vector_size) ? type_count<T>::value : 0)
+ : type_count_min<T>::value};
+};
+
+/// This will only trigger for actual void type
+template <typename T, typename Enable = void> struct expected_count { static const int value{0}; };
+
+/// For most types the number of expected items is 1
+template <typename T>
+struct expected_count<T,
+ typename std::enable_if<!is_mutable_container<T>::value && !is_wrapper<T>::value &&
+ !std::is_void<T>::value>::type> {
+ static constexpr int value{1};
+};
+/// number of expected items in a vector
+template <typename T> struct expected_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
+ static constexpr int value{expected_max_vector_size};
+};
+
+/// number of expected items in a vector
+template <typename T>
+struct expected_count<T, typename std::enable_if<!is_mutable_container<T>::value && is_wrapper<T>::value>::type> {
+ static constexpr int value{expected_count<typename T::value_type>::value};
+};
+
+// Enumeration of the different supported categorizations of objects
+enum class object_category : int {
+ char_value = 1,
+ integral_value = 2,
+ unsigned_integral = 4,
+ enumeration = 6,
+ boolean_value = 8,
+ floating_point = 10,
+ number_constructible = 12,
+ double_constructible = 14,
+ integer_constructible = 16,
+ // string like types
+ string_assignable = 23,
+ string_constructible = 24,
+ other = 45,
+ // special wrapper or container types
+ wrapper_value = 50,
+ complex_number = 60,
+ tuple_value = 70,
+ container_value = 80,
+
+};
+
+/// Set of overloads to classify an object according to type
+
+/// some type that is not otherwise recognized
+template <typename T, typename Enable = void> struct classify_object {
+ static constexpr object_category value{object_category::other};
+};
+
+/// Signed integers
+template <typename T>
+struct classify_object<
+ T,
+ typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && std::is_signed<T>::value &&
+ !is_bool<T>::value && !std::is_enum<T>::value>::type> {
+ static constexpr object_category value{object_category::integral_value};
+};
+
+/// Unsigned integers
+template <typename T>
+struct classify_object<T,
+ typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value &&
+ !std::is_same<T, char>::value && !is_bool<T>::value>::type> {
+ static constexpr object_category value{object_category::unsigned_integral};
+};
+
+/// single character values
+template <typename T>
+struct classify_object<T, typename std::enable_if<std::is_same<T, char>::value && !std::is_enum<T>::value>::type> {
+ static constexpr object_category value{object_category::char_value};
+};
+
+/// Boolean values
+template <typename T> struct classify_object<T, typename std::enable_if<is_bool<T>::value>::type> {
+ static constexpr object_category value{object_category::boolean_value};
+};
+
+/// Floats
+template <typename T> struct classify_object<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
+ static constexpr object_category value{object_category::floating_point};
+};
+
+/// String and similar direct assignment
+template <typename T>
+struct classify_object<T,
+ typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
+ std::is_assignable<T &, std::string>::value>::type> {
+ static constexpr object_category value{object_category::string_assignable};
+};
+
+/// String and similar constructible and copy assignment
+template <typename T>
+struct classify_object<
+ T,
+ typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
+ !std::is_assignable<T &, std::string>::value && (type_count<T>::value == 1) &&
+ std::is_constructible<T, std::string>::value>::type> {
+ static constexpr object_category value{object_category::string_constructible};
+};
+
+/// Enumerations
+template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> {
+ static constexpr object_category value{object_category::enumeration};
+};
+
+template <typename T> struct classify_object<T, typename std::enable_if<is_complex<T>::value>::type> {
+ static constexpr object_category value{object_category::complex_number};
+};
+
+/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point,
+/// vectors, and enumerations
+template <typename T> struct uncommon_type {
+ using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
+ !std::is_assignable<T &, std::string>::value &&
+ !std::is_constructible<T, std::string>::value && !is_complex<T>::value &&
+ !is_mutable_container<T>::value && !std::is_enum<T>::value,
+ std::true_type,
+ std::false_type>::type;
+ static constexpr bool value = type::value;
+};
+
+/// wrapper type
+template <typename T>
+struct classify_object<T,
+ typename std::enable_if<(!is_mutable_container<T>::value && is_wrapper<T>::value &&
+ !is_tuple_like<T>::value && uncommon_type<T>::value)>::type> {
+ static constexpr object_category value{object_category::wrapper_value};
+};
+
+/// Assignable from double or int
+template <typename T>
+struct classify_object<T,
+ typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
+ !is_wrapper<T>::value && is_direct_constructible<T, double>::value &&
+ is_direct_constructible<T, int>::value>::type> {
+ static constexpr object_category value{object_category::number_constructible};
+};
+
+/// Assignable from int
+template <typename T>
+struct classify_object<T,
+ typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
+ !is_wrapper<T>::value && !is_direct_constructible<T, double>::value &&
+ is_direct_constructible<T, int>::value>::type> {
+ static constexpr object_category value{object_category::integer_constructible};
+};
+
+/// Assignable from double
+template <typename T>
+struct classify_object<T,
+ typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
+ !is_wrapper<T>::value && is_direct_constructible<T, double>::value &&
+ !is_direct_constructible<T, int>::value>::type> {
+ static constexpr object_category value{object_category::double_constructible};
+};
+
+/// Tuple type
+template <typename T>
+struct classify_object<
+ T,
+ typename std::enable_if<is_tuple_like<T>::value &&
+ ((type_count<T>::value >= 2 && !is_wrapper<T>::value) ||
+ (uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
+ !is_direct_constructible<T, int>::value))>::type> {
+ static constexpr object_category value{object_category::tuple_value};
+ // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be
+ // constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which
+ // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2
+ // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out
+ // those cases that are caught by other object classifications
+};
+
+/// container type
+template <typename T> struct classify_object<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
+ static constexpr object_category value{object_category::container_value};
+};
+
+// Type name print
+
+/// Was going to be based on
+/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template
+/// But this is cleaner and works better in this case
+
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::char_value, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+ return "CHAR";
+}
+
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::integral_value ||
+ classify_object<T>::value == object_category::integer_constructible,
+ detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+ return "INT";
+}
+
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::unsigned_integral, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+ return "UINT";
+}
+
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::floating_point ||
+ classify_object<T>::value == object_category::number_constructible ||
+ classify_object<T>::value == object_category::double_constructible,
+ detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+ return "FLOAT";
+}
+
+/// Print name for enumeration types
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+ return "ENUM";
+}
+
+/// Print name for enumeration types
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+ return "BOOLEAN";
+}
+
+/// Print name for enumeration types
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+ return "COMPLEX";
+}
+
+/// Print for all other types
+template <typename T,
+ enable_if_t<classify_object<T>::value >= object_category::string_assignable &&
+ classify_object<T>::value <= object_category::other,
+ detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+ return "TEXT";
+}
+/// typename for tuple value
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2,
+ detail::enabler> = detail::dummy>
+std::string type_name(); // forward declaration
+
+/// Generate type name for a wrapper or container value
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::container_value ||
+ classify_object<T>::value == object_category::wrapper_value,
+ detail::enabler> = detail::dummy>
+std::string type_name(); // forward declaration
+
+/// Print name for single element tuple types
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value == 1,
+ detail::enabler> = detail::dummy>
+inline std::string type_name() {
+ return type_name<typename std::decay<typename std::tuple_element<0, T>::type>::type>();
+}
+
+/// Empty string if the index > tuple size
+template <typename T, std::size_t I>
+inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_name() {
+ return std::string{};
+}
+
+/// Recursively generate the tuple type name
+template <typename T, std::size_t I>
+inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_name() {
+ std::string str = std::string(type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()) +
+ ',' + tuple_name<T, I + 1>();
+ if(str.back() == ',')
+ str.pop_back();
+ return str;
+}
+
+/// Print type name for tuples with 2 or more elements
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2,
+ detail::enabler>>
+inline std::string type_name() {
+ auto tname = std::string(1, '[') + tuple_name<T, 0>();
+ tname.push_back(']');
+ return tname;
+}
+
+/// get the type name for a type that has a value_type member
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::container_value ||
+ classify_object<T>::value == object_category::wrapper_value,
+ detail::enabler>>
+inline std::string type_name() {
+ return type_name<typename T::value_type>();
+}
+
+// Lexical cast
+
+/// Convert to an unsigned integral
+template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
+bool integral_conversion(const std::string &input, T &output) noexcept {
+ if(input.empty()) {
+ return false;
+ }
+ char *val = nullptr;
+ std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
+ output = static_cast<T>(output_ll);
+ if(val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll) {
+ return true;
+ }
+ val = nullptr;
+ std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0);
+ if(val == (input.c_str() + input.size())) {
+ output = (output_sll < 0) ? static_cast<T>(0) : static_cast<T>(output_sll);
+ return (static_cast<std::int64_t>(output) == output_sll);
+ }
+ return false;
+}
+
+/// Convert to a signed integral
+template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy>
+bool integral_conversion(const std::string &input, T &output) noexcept {
+ if(input.empty()) {
+ return false;
+ }
+ char *val = nullptr;
+ std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0);
+ output = static_cast<T>(output_ll);
+ if(val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll) {
+ return true;
+ }
+ if(input == "true") {
+ // this is to deal with a few oddities with flags and wrapper int types
+ output = static_cast<T>(1);
+ return true;
+ }
+ return false;
+}
+
+/// Convert a flag into an integer value typically binary flags
+inline std::int64_t to_flag_value(std::string val) {
+ static const std::string trueString("true");
+ static const std::string falseString("false");
+ if(val == trueString) {
+ return 1;
+ }
+ if(val == falseString) {
+ return -1;
+ }
+ val = detail::to_lower(val);
+ std::int64_t ret;
+ if(val.size() == 1) {
+ if(val[0] >= '1' && val[0] <= '9') {
+ return (static_cast<std::int64_t>(val[0]) - '0');
+ }
+ switch(val[0]) {
+ case '0':
+ case 'f':
+ case 'n':
+ case '-':
+ ret = -1;
+ break;
+ case 't':
+ case 'y':
+ case '+':
+ ret = 1;
+ break;
+ default:
+ throw std::invalid_argument("unrecognized character");
+ }
+ return ret;
+ }
+ if(val == trueString || val == "on" || val == "yes" || val == "enable") {
+ ret = 1;
+ } else if(val == falseString || val == "off" || val == "no" || val == "disable") {
+ ret = -1;
+ } else {
+ ret = std::stoll(val);
+ }
+ return ret;
+}
+
+/// Integer conversion
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::integral_value ||
+ classify_object<T>::value == object_category::unsigned_integral,
+ detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ return integral_conversion(input, output);
+}
+
+/// char values
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::char_value, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ if(input.size() == 1) {
+ output = static_cast<T>(input[0]);
+ return true;
+ }
+ return integral_conversion(input, output);
+}
+
+/// Boolean values
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ try {
+ auto out = to_flag_value(input);
+ output = (out > 0);
+ return true;
+ } catch(const std::invalid_argument &) {
+ return false;
+ } catch(const std::out_of_range &) {
+ // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still
+ // valid all we care about the sign
+ output = (input[0] != '-');
+ return true;
+ }
+}
+
+/// Floats
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::floating_point, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ if(input.empty()) {
+ return false;
+ }
+ char *val = nullptr;
+ auto output_ld = std::strtold(input.c_str(), &val);
+ output = static_cast<T>(output_ld);
+ return val == (input.c_str() + input.size());
+}
+
+/// complex
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ using XC = typename wrapped_type<T, double>::type;
+ XC x{0.0}, y{0.0};
+ auto str1 = input;
+ bool worked = false;
+ auto nloc = str1.find_last_of("+-");
+ if(nloc != std::string::npos && nloc > 0) {
+ worked = detail::lexical_cast(str1.substr(0, nloc), x);
+ str1 = str1.substr(nloc);
+ if(str1.back() == 'i' || str1.back() == 'j')
+ str1.pop_back();
+ worked = worked && detail::lexical_cast(str1, y);
+ } else {
+ if(str1.back() == 'i' || str1.back() == 'j') {
+ str1.pop_back();
+ worked = detail::lexical_cast(str1, y);
+ x = XC{0};
+ } else {
+ worked = detail::lexical_cast(str1, x);
+ y = XC{0};
+ }
+ }
+ if(worked) {
+ output = T{x, y};
+ return worked;
+ }
+ return from_stream(input, output);
+}
+
+/// String and similar direct assignment
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::string_assignable, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ output = input;
+ return true;
+}
+
+/// String and similar constructible and copy assignment
+template <
+ typename T,
+ enable_if_t<classify_object<T>::value == object_category::string_constructible, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ output = T(input);
+ return true;
+}
+
+/// Enumerations
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ typename std::underlying_type<T>::type val;
+ if(!integral_conversion(input, val)) {
+ return false;
+ }
+ output = static_cast<T>(val);
+ return true;
+}
+
+/// wrapper types
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::wrapper_value &&
+ std::is_assignable<T &, typename T::value_type>::value,
+ detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ typename T::value_type val;
+ if(lexical_cast(input, val)) {
+ output = val;
+ return true;
+ }
+ return from_stream(input, output);
+}
+
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::wrapper_value &&
+ !std::is_assignable<T &, typename T::value_type>::value && std::is_assignable<T &, T>::value,
+ detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ typename T::value_type val;
+ if(lexical_cast(input, val)) {
+ output = T{val};
+ return true;
+ }
+ return from_stream(input, output);
+}
+
+/// Assignable from double or int
+template <
+ typename T,
+ enable_if_t<classify_object<T>::value == object_category::number_constructible, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ int val;
+ if(integral_conversion(input, val)) {
+ output = T(val);
+ return true;
+ } else {
+ double dval;
+ if(lexical_cast(input, dval)) {
+ output = T{dval};
+ return true;
+ }
+ }
+ return from_stream(input, output);
+}
+
+/// Assignable from int
+template <
+ typename T,
+ enable_if_t<classify_object<T>::value == object_category::integer_constructible, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ int val;
+ if(integral_conversion(input, val)) {
+ output = T(val);
+ return true;
+ }
+ return from_stream(input, output);
+}
+
+/// Assignable from double
+template <
+ typename T,
+ enable_if_t<classify_object<T>::value == object_category::double_constructible, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ double val;
+ if(lexical_cast(input, val)) {
+ output = T{val};
+ return true;
+ }
+ return from_stream(input, output);
+}
+
+/// Non-string convertible from an int
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::other && std::is_assignable<T &, int>::value,
+ detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ int val;
+ if(integral_conversion(input, val)) {
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4800)
+#endif
+ // with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style
+ // so will most likely still work
+ output = val;
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+ return true;
+ }
+ // LCOV_EXCL_START
+ // This version of cast is only used for odd cases in an older compilers the fail over
+ // from_stream is tested elsewhere an not relevant for coverage here
+ return from_stream(input, output);
+ // LCOV_EXCL_STOP
+}
+
+/// Non-string parsable by a stream
+template <typename T,
+ enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value,
+ detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ static_assert(is_istreamable<T>::value,
+ "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it "
+ "is convertible from another type use the add_option<T, XC>(...) with XC being the known type");
+ return from_stream(input, output);
+}
+
+/// Assign a value through lexical cast operations
+/// Strings can be empty so we need to do a little different
+template <typename AssignTo,
+ typename ConvertTo,
+ enable_if_t<std::is_same<AssignTo, ConvertTo>::value &&
+ (classify_object<AssignTo>::value == object_category::string_assignable ||
+ classify_object<AssignTo>::value == object_category::string_constructible),
+ detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+ return lexical_cast(input, output);
+}
+
+/// Assign a value through lexical cast operations
+template <typename AssignTo,
+ typename ConvertTo,
+ enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value &&
+ classify_object<AssignTo>::value != object_category::string_assignable &&
+ classify_object<AssignTo>::value != object_category::string_constructible,
+ detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+ if(input.empty()) {
+ output = AssignTo{};
+ return true;
+ }
+
+ return lexical_cast(input, output);
+}
+
+/// Assign a value through lexical cast operations
+template <typename AssignTo,
+ typename ConvertTo,
+ enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value &&
+ classify_object<AssignTo>::value == object_category::wrapper_value,
+ detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+ if(input.empty()) {
+ typename AssignTo::value_type emptyVal{};
+ output = emptyVal;
+ return true;
+ }
+ return lexical_cast(input, output);
+}
+
+/// Assign a value through lexical cast operations for int compatible values
+/// mainly for atomic operations on some compilers
+template <typename AssignTo,
+ typename ConvertTo,
+ enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value &&
+ classify_object<AssignTo>::value != object_category::wrapper_value &&
+ std::is_assignable<AssignTo &, int>::value,
+ detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+ if(input.empty()) {
+ output = 0;
+ return true;
+ }
+ int val;
+ if(lexical_cast(input, val)) {
+ output = val;
+ return true;
+ }
+ return false;
+}
+
+/// Assign a value converted from a string in lexical cast to the output value directly
+template <typename AssignTo,
+ typename ConvertTo,
+ enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, ConvertTo &>::value,
+ detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+ ConvertTo val{};
+ bool parse_result = (!input.empty()) ? lexical_cast<ConvertTo>(input, val) : true;
+ if(parse_result) {
+ output = val;
+ }
+ return parse_result;
+}
+
+/// Assign a value from a lexical cast through constructing a value and move assigning it
+template <
+ typename AssignTo,
+ typename ConvertTo,
+ enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, ConvertTo &>::value &&
+ std::is_move_assignable<AssignTo>::value,
+ detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+ ConvertTo val{};
+ bool parse_result = input.empty() ? true : lexical_cast<ConvertTo>(input, val);
+ if(parse_result) {
+ output = AssignTo(val); // use () form of constructor to allow some implicit conversions
+ }
+ return parse_result;
+}
+
+/// primary lexical conversion operation, 1 string to 1 type of some kind
+template <typename AssignTo,
+ typename ConvertTo,
+ enable_if_t<classify_object<ConvertTo>::value <= object_category::other &&
+ classify_object<AssignTo>::value <= object_category::wrapper_value,
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+ return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
+}
+
+/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element
+/// constructor
+template <typename AssignTo,
+ typename ConvertTo,
+ enable_if_t<(type_count<AssignTo>::value <= 2) && expected_count<AssignTo>::value == 1 &&
+ is_tuple_like<ConvertTo>::value && type_count_base<ConvertTo>::value == 2,
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+ // the remove const is to handle pair types coming from a container
+ typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type v1;
+ typename std::tuple_element<1, ConvertTo>::type v2;
+ bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1);
+ if(strings.size() > 1) {
+ retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2);
+ }
+ if(retval) {
+ output = AssignTo{v1, v2};
+ }
+ return retval;
+}
+
+/// Lexical conversion of a container types of single elements
+template <class AssignTo,
+ class ConvertTo,
+ enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+ type_count<ConvertTo>::value == 1,
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+ output.erase(output.begin(), output.end());
+ if(strings.size() == 1 && strings[0] == "{}") {
+ return true;
+ }
+ bool skip_remaining = false;
+ if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) {
+ skip_remaining = true;
+ }
+ for(const auto &elem : strings) {
+ typename AssignTo::value_type out;
+ bool retval = lexical_assign<typename AssignTo::value_type, typename ConvertTo::value_type>(elem, out);
+ if(!retval) {
+ return false;
+ }
+ output.insert(output.end(), std::move(out));
+ if(skip_remaining) {
+ break;
+ }
+ }
+ return (!output.empty());
+}
+
+/// Lexical conversion for complex types
+template <class AssignTo, class ConvertTo, enable_if_t<is_complex<ConvertTo>::value, detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
+
+ if(strings.size() >= 2 && !strings[1].empty()) {
+ using XC2 = typename wrapped_type<ConvertTo, double>::type;
+ XC2 x{0.0}, y{0.0};
+ auto str1 = strings[1];
+ if(str1.back() == 'i' || str1.back() == 'j') {
+ str1.pop_back();
+ }
+ auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y);
+ if(worked) {
+ output = ConvertTo{x, y};
+ }
+ return worked;
+ } else {
+ return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
+ }
+}
+
+/// Conversion to a vector type using a particular single type as the conversion type
+template <class AssignTo,
+ class ConvertTo,
+ enable_if_t<is_mutable_container<AssignTo>::value && (expected_count<ConvertTo>::value == 1) &&
+ (type_count<ConvertTo>::value == 1),
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+ bool retval = true;
+ output.clear();
+ output.reserve(strings.size());
+ for(const auto &elem : strings) {
+
+ output.emplace_back();
+ retval = retval && lexical_assign<typename AssignTo::value_type, ConvertTo>(elem, output.back());
+ }
+ return (!output.empty()) && retval;
+}
+
+// forward declaration
+
+/// Lexical conversion of a container types with conversion type of two elements
+template <class AssignTo,
+ class ConvertTo,
+ enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+ type_count_base<ConvertTo>::value == 2,
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(std::vector<std::string> strings, AssignTo &output);
+
+/// Lexical conversion of a vector types with type_size >2 forward declaration
+template <class AssignTo,
+ class ConvertTo,
+ enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+ type_count_base<ConvertTo>::value != 2 &&
+ ((type_count<ConvertTo>::value > 2) ||
+ (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)),
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output);
+
+/// Conversion for tuples
+template <class AssignTo,
+ class ConvertTo,
+ enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value &&
+ (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value ||
+ type_count<ConvertTo>::value > 2),
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output); // forward declaration
+
+/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large
+/// tuple
+template <typename AssignTo,
+ typename ConvertTo,
+ enable_if_t<!is_tuple_like<AssignTo>::value && !is_mutable_container<AssignTo>::value &&
+ classify_object<ConvertTo>::value != object_category::wrapper_value &&
+ (is_mutable_container<ConvertTo>::value || type_count<ConvertTo>::value > 2),
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+
+ if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) {
+ ConvertTo val;
+ auto retval = lexical_conversion<ConvertTo, ConvertTo>(strings, val);
+ output = AssignTo{val};
+ return retval;
+ }
+ output = AssignTo{};
+ return true;
+}
+
+/// function template for converting tuples if the static Index is greater than the tuple size
+template <class AssignTo, class ConvertTo, std::size_t I>
+inline typename std::enable_if<(I >= type_count_base<AssignTo>::value), bool>::type
+tuple_conversion(const std::vector<std::string> &, AssignTo &) {
+ return true;
+}
+
+/// Conversion of a tuple element where the type size ==1 and not a mutable container
+template <class AssignTo, class ConvertTo>
+inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && type_count<ConvertTo>::value == 1, bool>::type
+tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
+ auto retval = lexical_assign<AssignTo, ConvertTo>(strings[0], output);
+ strings.erase(strings.begin());
+ return retval;
+}
+
+/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container
+template <class AssignTo, class ConvertTo>
+inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && (type_count<ConvertTo>::value > 1) &&
+ type_count<ConvertTo>::value == type_count_min<ConvertTo>::value,
+ bool>::type
+tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
+ auto retval = lexical_conversion<AssignTo, ConvertTo>(strings, output);
+ strings.erase(strings.begin(), strings.begin() + type_count<ConvertTo>::value);
+ return retval;
+}
+
+/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes
+template <class AssignTo, class ConvertTo>
+inline typename std::enable_if<is_mutable_container<ConvertTo>::value ||
+ type_count<ConvertTo>::value != type_count_min<ConvertTo>::value,
+ bool>::type
+tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
+
+ std::size_t index{subtype_count_min<ConvertTo>::value};
+ const std::size_t mx_count{subtype_count<ConvertTo>::value};
+ const std::size_t mx{(std::max)(mx_count, strings.size())};
+
+ while(index < mx) {
+ if(is_separator(strings[index])) {
+ break;
+ }
+ ++index;
+ }
+ bool retval = lexical_conversion<AssignTo, ConvertTo>(
+ std::vector<std::string>(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index)), output);
+ strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1);
+ return retval;
+}
+
+/// Tuple conversion operation
+template <class AssignTo, class ConvertTo, std::size_t I>
+inline typename std::enable_if<(I < type_count_base<AssignTo>::value), bool>::type
+tuple_conversion(std::vector<std::string> strings, AssignTo &output) {
+ bool retval = true;
+ using ConvertToElement = typename std::
+ conditional<is_tuple_like<ConvertTo>::value, typename std::tuple_element<I, ConvertTo>::type, ConvertTo>::type;
+ if(!strings.empty()) {
+ retval = retval && tuple_type_conversion<typename std::tuple_element<I, AssignTo>::type, ConvertToElement>(
+ strings, std::get<I>(output));
+ }
+ retval = retval && tuple_conversion<AssignTo, ConvertTo, I + 1>(std::move(strings), output);
+ return retval;
+}
+
+/// Lexical conversion of a container types with tuple elements of size 2
+template <class AssignTo,
+ class ConvertTo,
+ enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+ type_count_base<ConvertTo>::value == 2,
+ detail::enabler>>
+bool lexical_conversion(std::vector<std::string> strings, AssignTo &output) {
+ output.clear();
+ while(!strings.empty()) {
+
+ typename std::remove_const<typename std::tuple_element<0, typename ConvertTo::value_type>::type>::type v1;
+ typename std::tuple_element<1, typename ConvertTo::value_type>::type v2;
+ bool retval = tuple_type_conversion<decltype(v1), decltype(v1)>(strings, v1);
+ if(!strings.empty()) {
+ retval = retval && tuple_type_conversion<decltype(v2), decltype(v2)>(strings, v2);
+ }
+ if(retval) {
+ output.insert(output.end(), typename AssignTo::value_type{v1, v2});
+ } else {
+ return false;
+ }
+ }
+ return (!output.empty());
+}
+
+/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2
+template <class AssignTo,
+ class ConvertTo,
+ enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value &&
+ (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value ||
+ type_count<ConvertTo>::value > 2),
+ detail::enabler>>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+ static_assert(
+ !is_tuple_like<ConvertTo>::value || type_count_base<AssignTo>::value == type_count_base<ConvertTo>::value,
+ "if the conversion type is defined as a tuple it must be the same size as the type you are converting to");
+ return tuple_conversion<AssignTo, ConvertTo, 0>(strings, output);
+}
+
+/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1
+template <class AssignTo,
+ class ConvertTo,
+ enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+ type_count_base<ConvertTo>::value != 2 &&
+ ((type_count<ConvertTo>::value > 2) ||
+ (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)),
+ detail::enabler>>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+ bool retval = true;
+ output.clear();
+ std::vector<std::string> temp;
+ std::size_t ii{0};
+ std::size_t icount{0};
+ std::size_t xcm{type_count<ConvertTo>::value};
+ auto ii_max = strings.size();
+ while(ii < ii_max) {
+ temp.push_back(strings[ii]);
+ ++ii;
+ ++icount;
+ if(icount == xcm || is_separator(temp.back()) || ii == ii_max) {
+ if(static_cast<int>(xcm) > type_count_min<ConvertTo>::value && is_separator(temp.back())) {
+ temp.pop_back();
+ }
+ typename AssignTo::value_type temp_out;
+ retval = retval &&
+ lexical_conversion<typename AssignTo::value_type, typename ConvertTo::value_type>(temp, temp_out);
+ temp.clear();
+ if(!retval) {
+ return false;
+ }
+ output.insert(output.end(), std::move(temp_out));
+ icount = 0;
+ }
+ }
+ return retval;
+}
+
+/// conversion for wrapper types
+template <typename AssignTo,
+ class ConvertTo,
+ enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value &&
+ std::is_assignable<ConvertTo &, ConvertTo>::value,
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
+ if(strings.empty() || strings.front().empty()) {
+ output = ConvertTo{};
+ return true;
+ }
+ typename ConvertTo::value_type val;
+ if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) {
+ output = ConvertTo{val};
+ return true;
+ }
+ return false;
+}
+
+/// conversion for wrapper types
+template <typename AssignTo,
+ class ConvertTo,
+ enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value &&
+ !std::is_assignable<AssignTo &, ConvertTo>::value,
+ detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
+ using ConvertType = typename ConvertTo::value_type;
+ if(strings.empty() || strings.front().empty()) {
+ output = ConvertType{};
+ return true;
+ }
+ ConvertType val;
+ if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) {
+ output = val;
+ return true;
+ }
+ return false;
+}
+
+/// Sum a vector of strings
+inline std::string sum_string_vector(const std::vector<std::string> &values) {
+ double val{0.0};
+ bool fail{false};
+ std::string output;
+ for(const auto &arg : values) {
+ double tv{0.0};
+ auto comp = detail::lexical_cast<double>(arg, tv);
+ if(!comp) {
+ try {
+ tv = static_cast<double>(detail::to_flag_value(arg));
+ } catch(const std::exception &) {
+ fail = true;
+ break;
+ }
+ }
+ val += tv;
+ }
+ if(fail) {
+ for(const auto &arg : values) {
+ output.append(arg);
+ }
+ } else {
+ if(val <= static_cast<double>(std::numeric_limits<std::int64_t>::min()) ||
+ val >= static_cast<double>(std::numeric_limits<std::int64_t>::max()) ||
+ val == static_cast<std::int64_t>(val)) {
+ output = detail::value_string(static_cast<int64_t>(val));
+ } else {
+ output = detail::value_string(val);
+ }
+ }
+ return output;
+}
+
+} // namespace detail
+// [CLI11:type_tools_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/Validators.hpp b/src/third-party/CLI/Validators.hpp
new file mode 100644
index 0000000..1281997
--- /dev/null
+++ b/src/third-party/CLI/Validators.hpp
@@ -0,0 +1,1175 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+#include "Macros.hpp"
+#include "StringTools.hpp"
+#include "TypeTools.hpp"
+
+// [CLI11:public_includes:set]
+#include <cmath>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+// [CLI11:validators_hpp_filesystem:verbatim]
+
+// C standard library
+// Only needed for existence checking
+#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM
+#if __has_include(<filesystem>)
+// Filesystem cannot be used if targeting macOS < 10.15
+#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
+#define CLI11_HAS_FILESYSTEM 0
+#elif defined(__wasi__)
+// As of wasi-sdk-14, filesystem is not implemented
+#define CLI11_HAS_FILESYSTEM 0
+#else
+#include <filesystem>
+#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703
+#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9
+#define CLI11_HAS_FILESYSTEM 1
+#elif defined(__GLIBCXX__)
+// if we are using gcc and Version <9 default to no filesystem
+#define CLI11_HAS_FILESYSTEM 0
+#else
+#define CLI11_HAS_FILESYSTEM 1
+#endif
+#else
+#define CLI11_HAS_FILESYSTEM 0
+#endif
+#endif
+#endif
+#endif
+
+#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
+#include <filesystem> // NOLINT(build/include)
+#else
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
+// [CLI11:validators_hpp_filesystem:end]
+
+namespace CLI {
+// [CLI11:validators_hpp:verbatim]
+
+class Option;
+
+/// @defgroup validator_group Validators
+
+/// @brief Some validators that are provided
+///
+/// These are simple `std::string(const std::string&)` validators that are useful. They return
+/// a string if the validation fails. A custom struct is provided, as well, with the same user
+/// semantics, but with the ability to provide a new type name.
+/// @{
+
+///
+class Validator {
+ protected:
+ /// This is the description function, if empty the description_ will be used
+ std::function<std::string()> desc_function_{[]() { return std::string{}; }};
+
+ /// This is the base function that is to be called.
+ /// Returns a string error message if validation fails.
+ std::function<std::string(std::string &)> func_{[](std::string &) { return std::string{}; }};
+ /// The name for search purposes of the Validator
+ std::string name_{};
+ /// A Validator will only apply to an indexed value (-1 is all elements)
+ int application_index_ = -1;
+ /// Enable for Validator to allow it to be disabled if need be
+ bool active_{true};
+ /// specify that a validator should not modify the input
+ bool non_modifying_{false};
+
+ public:
+ Validator() = default;
+ /// Construct a Validator with just the description string
+ explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {}
+ /// Construct Validator from basic information
+ Validator(std::function<std::string(std::string &)> op, std::string validator_desc, std::string validator_name = "")
+ : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)),
+ name_(std::move(validator_name)) {}
+ /// Set the Validator operation function
+ Validator &operation(std::function<std::string(std::string &)> op) {
+ func_ = std::move(op);
+ return *this;
+ }
+ /// This is the required operator for a Validator - provided to help
+ /// users (CLI11 uses the member `func` directly)
+ std::string operator()(std::string &str) const {
+ std::string retstring;
+ if(active_) {
+ if(non_modifying_) {
+ std::string value = str;
+ retstring = func_(value);
+ } else {
+ retstring = func_(str);
+ }
+ }
+ return retstring;
+ }
+
+ /// This is the required operator for a Validator - provided to help
+ /// users (CLI11 uses the member `func` directly)
+ std::string operator()(const std::string &str) const {
+ std::string value = str;
+ return (active_) ? func_(value) : std::string{};
+ }
+
+ /// Specify the type string
+ Validator &description(std::string validator_desc) {
+ desc_function_ = [validator_desc]() { return validator_desc; };
+ return *this;
+ }
+ /// Specify the type string
+ Validator description(std::string validator_desc) const {
+ Validator newval(*this);
+ newval.desc_function_ = [validator_desc]() { return validator_desc; };
+ return newval;
+ }
+ /// Generate type description information for the Validator
+ std::string get_description() const {
+ if(active_) {
+ return desc_function_();
+ }
+ return std::string{};
+ }
+ /// Specify the type string
+ Validator &name(std::string validator_name) {
+ name_ = std::move(validator_name);
+ return *this;
+ }
+ /// Specify the type string
+ Validator name(std::string validator_name) const {
+ Validator newval(*this);
+ newval.name_ = std::move(validator_name);
+ return newval;
+ }
+ /// Get the name of the Validator
+ const std::string &get_name() const { return name_; }
+ /// Specify whether the Validator is active or not
+ Validator &active(bool active_val = true) {
+ active_ = active_val;
+ return *this;
+ }
+ /// Specify whether the Validator is active or not
+ Validator active(bool active_val = true) const {
+ Validator newval(*this);
+ newval.active_ = active_val;
+ return newval;
+ }
+
+ /// Specify whether the Validator can be modifying or not
+ Validator &non_modifying(bool no_modify = true) {
+ non_modifying_ = no_modify;
+ return *this;
+ }
+ /// Specify the application index of a validator
+ Validator &application_index(int app_index) {
+ application_index_ = app_index;
+ return *this;
+ }
+ /// Specify the application index of a validator
+ Validator application_index(int app_index) const {
+ Validator newval(*this);
+ newval.application_index_ = app_index;
+ return newval;
+ }
+ /// Get the current value of the application index
+ int get_application_index() const { return application_index_; }
+ /// Get a boolean if the validator is active
+ bool get_active() const { return active_; }
+
+ /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input
+ bool get_modifying() const { return !non_modifying_; }
+
+ /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
+ /// same.
+ Validator operator&(const Validator &other) const {
+ Validator newval;
+
+ newval._merge_description(*this, other, " AND ");
+
+ // Give references (will make a copy in lambda function)
+ const std::function<std::string(std::string & filename)> &f1 = func_;
+ const std::function<std::string(std::string & filename)> &f2 = other.func_;
+
+ newval.func_ = [f1, f2](std::string &input) {
+ std::string s1 = f1(input);
+ std::string s2 = f2(input);
+ if(!s1.empty() && !s2.empty())
+ return std::string("(") + s1 + ") AND (" + s2 + ")";
+ else
+ return s1 + s2;
+ };
+
+ newval.active_ = (active_ & other.active_);
+ newval.application_index_ = application_index_;
+ return newval;
+ }
+
+ /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
+ /// same.
+ Validator operator|(const Validator &other) const {
+ Validator newval;
+
+ newval._merge_description(*this, other, " OR ");
+
+ // Give references (will make a copy in lambda function)
+ const std::function<std::string(std::string &)> &f1 = func_;
+ const std::function<std::string(std::string &)> &f2 = other.func_;
+
+ newval.func_ = [f1, f2](std::string &input) {
+ std::string s1 = f1(input);
+ std::string s2 = f2(input);
+ if(s1.empty() || s2.empty())
+ return std::string();
+
+ return std::string("(") + s1 + ") OR (" + s2 + ")";
+ };
+ newval.active_ = (active_ & other.active_);
+ newval.application_index_ = application_index_;
+ return newval;
+ }
+
+ /// Create a validator that fails when a given validator succeeds
+ Validator operator!() const {
+ Validator newval;
+ const std::function<std::string()> &dfunc1 = desc_function_;
+ newval.desc_function_ = [dfunc1]() {
+ auto str = dfunc1();
+ return (!str.empty()) ? std::string("NOT ") + str : std::string{};
+ };
+ // Give references (will make a copy in lambda function)
+ const std::function<std::string(std::string & res)> &f1 = func_;
+
+ newval.func_ = [f1, dfunc1](std::string &test) -> std::string {
+ std::string s1 = f1(test);
+ if(s1.empty()) {
+ return std::string("check ") + dfunc1() + " succeeded improperly";
+ }
+ return std::string{};
+ };
+ newval.active_ = active_;
+ newval.application_index_ = application_index_;
+ return newval;
+ }
+
+ private:
+ void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) {
+
+ const std::function<std::string()> &dfunc1 = val1.desc_function_;
+ const std::function<std::string()> &dfunc2 = val2.desc_function_;
+
+ desc_function_ = [=]() {
+ std::string f1 = dfunc1();
+ std::string f2 = dfunc2();
+ if((f1.empty()) || (f2.empty())) {
+ return f1 + f2;
+ }
+ return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')';
+ };
+ }
+}; // namespace CLI
+
+/// Class wrapping some of the accessors of Validator
+class CustomValidator : public Validator {
+ public:
+};
+// The implementation of the built in validators is using the Validator class;
+// the user is only expected to use the const (static) versions (since there's no setup).
+// Therefore, this is in detail.
+namespace detail {
+
+/// CLI enumeration of different file types
+enum class path_type { nonexistent, file, directory };
+
+#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
+/// get the type of the path from a file name
+inline path_type check_path(const char *file) noexcept {
+ std::error_code ec;
+ auto stat = std::filesystem::status(file, ec);
+ if(ec) {
+ return path_type::nonexistent;
+ }
+ switch(stat.type()) {
+ case std::filesystem::file_type::none:
+ case std::filesystem::file_type::not_found:
+ return path_type::nonexistent;
+ case std::filesystem::file_type::directory:
+ return path_type::directory;
+ case std::filesystem::file_type::symlink:
+ case std::filesystem::file_type::block:
+ case std::filesystem::file_type::character:
+ case std::filesystem::file_type::fifo:
+ case std::filesystem::file_type::socket:
+ case std::filesystem::file_type::regular:
+ case std::filesystem::file_type::unknown:
+ default:
+ return path_type::file;
+ }
+}
+#else
+/// get the type of the path from a file name
+inline path_type check_path(const char *file) noexcept {
+#if defined(_MSC_VER)
+ struct __stat64 buffer;
+ if(_stat64(file, &buffer) == 0) {
+ return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
+ }
+#else
+ struct stat buffer;
+ if(stat(file, &buffer) == 0) {
+ return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
+ }
+#endif
+ return path_type::nonexistent;
+}
+#endif
+/// Check for an existing file (returns error message if check fails)
+class ExistingFileValidator : public Validator {
+ public:
+ ExistingFileValidator() : Validator("FILE") {
+ func_ = [](std::string &filename) {
+ auto path_result = check_path(filename.c_str());
+ if(path_result == path_type::nonexistent) {
+ return "File does not exist: " + filename;
+ }
+ if(path_result == path_type::directory) {
+ return "File is actually a directory: " + filename;
+ }
+ return std::string();
+ };
+ }
+};
+
+/// Check for an existing directory (returns error message if check fails)
+class ExistingDirectoryValidator : public Validator {
+ public:
+ ExistingDirectoryValidator() : Validator("DIR") {
+ func_ = [](std::string &filename) {
+ auto path_result = check_path(filename.c_str());
+ if(path_result == path_type::nonexistent) {
+ return "Directory does not exist: " + filename;
+ }
+ if(path_result == path_type::file) {
+ return "Directory is actually a file: " + filename;
+ }
+ return std::string();
+ };
+ }
+};
+
+/// Check for an existing path
+class ExistingPathValidator : public Validator {
+ public:
+ ExistingPathValidator() : Validator("PATH(existing)") {
+ func_ = [](std::string &filename) {
+ auto path_result = check_path(filename.c_str());
+ if(path_result == path_type::nonexistent) {
+ return "Path does not exist: " + filename;
+ }
+ return std::string();
+ };
+ }
+};
+
+/// Check for an non-existing path
+class NonexistentPathValidator : public Validator {
+ public:
+ NonexistentPathValidator() : Validator("PATH(non-existing)") {
+ func_ = [](std::string &filename) {
+ auto path_result = check_path(filename.c_str());
+ if(path_result != path_type::nonexistent) {
+ return "Path already exists: " + filename;
+ }
+ return std::string();
+ };
+ }
+};
+
+/// Validate the given string is a legal ipv4 address
+class IPV4Validator : public Validator {
+ public:
+ IPV4Validator() : Validator("IPV4") {
+ func_ = [](std::string &ip_addr) {
+ auto result = CLI::detail::split(ip_addr, '.');
+ if(result.size() != 4) {
+ return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')';
+ }
+ int num;
+ for(const auto &var : result) {
+ bool retval = detail::lexical_cast(var, num);
+ if(!retval) {
+ return std::string("Failed parsing number (") + var + ')';
+ }
+ if(num < 0 || num > 255) {
+ return std::string("Each IP number must be between 0 and 255 ") + var;
+ }
+ }
+ return std::string();
+ };
+ }
+};
+
+} // namespace detail
+
+// Static is not needed here, because global const implies static.
+
+/// Check for existing file (returns error message if check fails)
+const detail::ExistingFileValidator ExistingFile;
+
+/// Check for an existing directory (returns error message if check fails)
+const detail::ExistingDirectoryValidator ExistingDirectory;
+
+/// Check for an existing path
+const detail::ExistingPathValidator ExistingPath;
+
+/// Check for an non-existing path
+const detail::NonexistentPathValidator NonexistentPath;
+
+/// Check for an IP4 address
+const detail::IPV4Validator ValidIPV4;
+
+/// Validate the input as a particular type
+template <typename DesiredType> class TypeValidator : public Validator {
+ public:
+ explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) {
+ func_ = [](std::string &input_string) {
+ auto val = DesiredType();
+ if(!detail::lexical_cast(input_string, val)) {
+ return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
+ }
+ return std::string();
+ };
+ }
+ TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {}
+};
+
+/// Check for a number
+const TypeValidator<double> Number("NUMBER");
+
+/// Modify a path if the file is a particular default location, can be used as Check or transform
+/// with the error return optionally disabled
+class FileOnDefaultPath : public Validator {
+ public:
+ explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true) : Validator("FILE") {
+ func_ = [default_path, enableErrorReturn](std::string &filename) {
+ auto path_result = detail::check_path(filename.c_str());
+ if(path_result == detail::path_type::nonexistent) {
+ std::string test_file_path = default_path;
+ if(default_path.back() != '/' && default_path.back() != '\\') {
+ // Add folder separator
+ test_file_path += '/';
+ }
+ test_file_path.append(filename);
+ path_result = detail::check_path(test_file_path.c_str());
+ if(path_result == detail::path_type::file) {
+ filename = test_file_path;
+ } else {
+ if(enableErrorReturn) {
+ return "File does not exist: " + filename;
+ }
+ }
+ }
+ return std::string{};
+ };
+ }
+};
+
+/// Produce a range (factory). Min and max are inclusive.
+class Range : public Validator {
+ public:
+ /// This produces a range with min and max inclusive.
+ ///
+ /// Note that the constructor is templated, but the struct is not, so C++17 is not
+ /// needed to provide nice syntax for Range(a,b).
+ template <typename T>
+ Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) {
+ if(validator_name.empty()) {
+ std::stringstream out;
+ out << detail::type_name<T>() << " in [" << min_val << " - " << max_val << "]";
+ description(out.str());
+ }
+
+ func_ = [min_val, max_val](std::string &input) {
+ T val;
+ bool converted = detail::lexical_cast(input, val);
+ if((!converted) || (val < min_val || val > max_val)) {
+ std::stringstream out;
+ out << "Value " << input << " not in range [";
+ out << min_val << " - " << max_val << "]";
+ return out.str();
+ }
+ return std::string{};
+ };
+ }
+
+ /// Range of one value is 0 to value
+ template <typename T>
+ explicit Range(T max_val, const std::string &validator_name = std::string{})
+ : Range(static_cast<T>(0), max_val, validator_name) {}
+};
+
+/// Check for a non negative number
+const Range NonNegativeNumber((std::numeric_limits<double>::max)(), "NONNEGATIVE");
+
+/// Check for a positive valued number (val>0.0), min() her is the smallest positive number
+const Range PositiveNumber((std::numeric_limits<double>::min)(), (std::numeric_limits<double>::max)(), "POSITIVE");
+
+/// Produce a bounded range (factory). Min and max are inclusive.
+class Bound : public Validator {
+ public:
+ /// This bounds a value with min and max inclusive.
+ ///
+ /// Note that the constructor is templated, but the struct is not, so C++17 is not
+ /// needed to provide nice syntax for Range(a,b).
+ template <typename T> Bound(T min_val, T max_val) {
+ std::stringstream out;
+ out << detail::type_name<T>() << " bounded to [" << min_val << " - " << max_val << "]";
+ description(out.str());
+
+ func_ = [min_val, max_val](std::string &input) {
+ T val;
+ bool converted = detail::lexical_cast(input, val);
+ if(!converted) {
+ return std::string("Value ") + input + " could not be converted";
+ }
+ if(val < min_val)
+ input = detail::to_string(min_val);
+ else if(val > max_val)
+ input = detail::to_string(max_val);
+
+ return std::string{};
+ };
+ }
+
+ /// Range of one value is 0 to value
+ template <typename T> explicit Bound(T max_val) : Bound(static_cast<T>(0), max_val) {}
+};
+
+namespace detail {
+template <typename T,
+ enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
+auto smart_deref(T value) -> decltype(*value) {
+ return *value;
+}
+
+template <
+ typename T,
+ enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
+typename std::remove_reference<T>::type &smart_deref(T &value) {
+ return value;
+}
+/// Generate a string representation of a set
+template <typename T> std::string generate_set(const T &set) {
+ using element_t = typename detail::element_type<T>::type;
+ using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
+ std::string out(1, '{');
+ out.append(detail::join(
+ detail::smart_deref(set),
+ [](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); },
+ ","));
+ out.push_back('}');
+ return out;
+}
+
+/// Generate a string representation of a map
+template <typename T> std::string generate_map(const T &map, bool key_only = false) {
+ using element_t = typename detail::element_type<T>::type;
+ using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
+ std::string out(1, '{');
+ out.append(detail::join(
+ detail::smart_deref(map),
+ [key_only](const iteration_type_t &v) {
+ std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))};
+
+ if(!key_only) {
+ res.append("->");
+ res += detail::to_string(detail::pair_adaptor<element_t>::second(v));
+ }
+ return res;
+ },
+ ","));
+ out.push_back('}');
+ return out;
+}
+
+template <typename C, typename V> struct has_find {
+ template <typename CC, typename VV>
+ static auto test(int) -> decltype(std::declval<CC>().find(std::declval<VV>()), std::true_type());
+ template <typename, typename> static auto test(...) -> decltype(std::false_type());
+
+ static const auto value = decltype(test<C, V>(0))::value;
+ using type = std::integral_constant<bool, value>;
+};
+
+/// A search function
+template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy>
+auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
+ using element_t = typename detail::element_type<T>::type;
+ auto &setref = detail::smart_deref(set);
+ auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) {
+ return (detail::pair_adaptor<element_t>::first(v) == val);
+ });
+ return {(it != std::end(setref)), it};
+}
+
+/// A search function that uses the built in find function
+template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>
+auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
+ auto &setref = detail::smart_deref(set);
+ auto it = setref.find(val);
+ return {(it != std::end(setref)), it};
+}
+
+/// A search function with a filter function
+template <typename T, typename V>
+auto search(const T &set, const V &val, const std::function<V(V)> &filter_function)
+ -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
+ using element_t = typename detail::element_type<T>::type;
+ // do the potentially faster first search
+ auto res = search(set, val);
+ if((res.first) || (!(filter_function))) {
+ return res;
+ }
+ // if we haven't found it do the longer linear search with all the element translations
+ auto &setref = detail::smart_deref(set);
+ auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) {
+ V a{detail::pair_adaptor<element_t>::first(v)};
+ a = filter_function(a);
+ return (a == val);
+ });
+ return {(it != std::end(setref)), it};
+}
+
+// the following suggestion was made by Nikita Ofitserov(@himikof)
+// done in templates to prevent compiler warnings on negation of unsigned numbers
+
+/// Do a check for overflow on signed numbers
+template <typename T>
+inline typename std::enable_if<std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
+ if((a > 0) == (b > 0)) {
+ return ((std::numeric_limits<T>::max)() / (std::abs)(a) < (std::abs)(b));
+ } else {
+ return ((std::numeric_limits<T>::min)() / (std::abs)(a) > -(std::abs)(b));
+ }
+}
+/// Do a check for overflow on unsigned numbers
+template <typename T>
+inline typename std::enable_if<!std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
+ return ((std::numeric_limits<T>::max)() / a < b);
+}
+
+/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise.
+template <typename T> typename std::enable_if<std::is_integral<T>::value, bool>::type checked_multiply(T &a, T b) {
+ if(a == 0 || b == 0 || a == 1 || b == 1) {
+ a *= b;
+ return true;
+ }
+ if(a == (std::numeric_limits<T>::min)() || b == (std::numeric_limits<T>::min)()) {
+ return false;
+ }
+ if(overflowCheck(a, b)) {
+ return false;
+ }
+ a *= b;
+ return true;
+}
+
+/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise.
+template <typename T>
+typename std::enable_if<std::is_floating_point<T>::value, bool>::type checked_multiply(T &a, T b) {
+ T c = a * b;
+ if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) {
+ return false;
+ }
+ a = c;
+ return true;
+}
+
+} // namespace detail
+/// Verify items are in a set
+class IsMember : public Validator {
+ public:
+ using filter_fn_t = std::function<std::string(std::string)>;
+
+ /// This allows in-place construction using an initializer list
+ template <typename T, typename... Args>
+ IsMember(std::initializer_list<T> values, Args &&...args)
+ : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
+
+ /// This checks to see if an item is in a set (empty function)
+ template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {}
+
+ /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
+ /// both sides of the comparison before computing the comparison.
+ template <typename T, typename F> explicit IsMember(T set, F filter_function) {
+
+ // Get the type of the contained item - requires a container have ::value_type
+ // if the type does not have first_type and second_type, these are both value_type
+ using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
+ using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
+
+ using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
+ // (const char * to std::string)
+
+ // Make a local copy of the filter function, using a std::function if not one already
+ std::function<local_item_t(local_item_t)> filter_fn = filter_function;
+
+ // This is the type name for help, it will take the current version of the set contents
+ desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); };
+
+ // This is the function that validates
+ // It stores a copy of the set pointer-like, so shared_ptr will stay alive
+ func_ = [set, filter_fn](std::string &input) {
+ local_item_t b;
+ if(!detail::lexical_cast(input, b)) {
+ throw ValidationError(input); // name is added later
+ }
+ if(filter_fn) {
+ b = filter_fn(b);
+ }
+ auto res = detail::search(set, b, filter_fn);
+ if(res.first) {
+ // Make sure the version in the input string is identical to the one in the set
+ if(filter_fn) {
+ input = detail::value_string(detail::pair_adaptor<element_t>::first(*(res.second)));
+ }
+
+ // Return empty error string (success)
+ return std::string{};
+ }
+
+ // If you reach this point, the result was not found
+ return input + " not in " + detail::generate_set(detail::smart_deref(set));
+ };
+ }
+
+ /// You can pass in as many filter functions as you like, they nest (string only currently)
+ template <typename T, typename... Args>
+ IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
+ : IsMember(
+ std::forward<T>(set),
+ [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
+ other...) {}
+};
+
+/// definition of the default transformation object
+template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
+
+/// Translate named items to other or a value set
+class Transformer : public Validator {
+ public:
+ using filter_fn_t = std::function<std::string(std::string)>;
+
+ /// This allows in-place construction
+ template <typename... Args>
+ Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
+ : Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
+
+ /// direct map of std::string to std::string
+ template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
+
+ /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
+ /// both sides of the comparison before computing the comparison.
+ template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
+
+ static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
+ "mapping must produce value pairs");
+ // Get the type of the contained item - requires a container have ::value_type
+ // if the type does not have first_type and second_type, these are both value_type
+ using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
+ using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
+ using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
+ // (const char * to std::string)
+
+ // Make a local copy of the filter function, using a std::function if not one already
+ std::function<local_item_t(local_item_t)> filter_fn = filter_function;
+
+ // This is the type name for help, it will take the current version of the set contents
+ desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
+
+ func_ = [mapping, filter_fn](std::string &input) {
+ local_item_t b;
+ if(!detail::lexical_cast(input, b)) {
+ return std::string();
+ // there is no possible way we can match anything in the mapping if we can't convert so just return
+ }
+ if(filter_fn) {
+ b = filter_fn(b);
+ }
+ auto res = detail::search(mapping, b, filter_fn);
+ if(res.first) {
+ input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
+ }
+ return std::string{};
+ };
+ }
+
+ /// You can pass in as many filter functions as you like, they nest
+ template <typename T, typename... Args>
+ Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
+ : Transformer(
+ std::forward<T>(mapping),
+ [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
+ other...) {}
+};
+
+/// translate named items to other or a value set
+class CheckedTransformer : public Validator {
+ public:
+ using filter_fn_t = std::function<std::string(std::string)>;
+
+ /// This allows in-place construction
+ template <typename... Args>
+ CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
+ : CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
+
+ /// direct map of std::string to std::string
+ template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
+
+ /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
+ /// both sides of the comparison before computing the comparison.
+ template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
+
+ static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
+ "mapping must produce value pairs");
+ // Get the type of the contained item - requires a container have ::value_type
+ // if the type does not have first_type and second_type, these are both value_type
+ using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
+ using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
+ using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
+ // (const char * to std::string)
+ using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
+
+ // Make a local copy of the filter function, using a std::function if not one already
+ std::function<local_item_t(local_item_t)> filter_fn = filter_function;
+
+ auto tfunc = [mapping]() {
+ std::string out("value in ");
+ out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
+ out += detail::join(
+ detail::smart_deref(mapping),
+ [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor<element_t>::second(v)); },
+ ",");
+ out.push_back('}');
+ return out;
+ };
+
+ desc_function_ = tfunc;
+
+ func_ = [mapping, tfunc, filter_fn](std::string &input) {
+ local_item_t b;
+ bool converted = detail::lexical_cast(input, b);
+ if(converted) {
+ if(filter_fn) {
+ b = filter_fn(b);
+ }
+ auto res = detail::search(mapping, b, filter_fn);
+ if(res.first) {
+ input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
+ return std::string{};
+ }
+ }
+ for(const auto &v : detail::smart_deref(mapping)) {
+ auto output_string = detail::value_string(detail::pair_adaptor<element_t>::second(v));
+ if(output_string == input) {
+ return std::string();
+ }
+ }
+
+ return "Check " + input + " " + tfunc() + " FAILED";
+ };
+ }
+
+ /// You can pass in as many filter functions as you like, they nest
+ template <typename T, typename... Args>
+ CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
+ : CheckedTransformer(
+ std::forward<T>(mapping),
+ [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
+ other...) {}
+};
+
+/// Helper function to allow ignore_case to be passed to IsMember or Transform
+inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
+
+/// Helper function to allow ignore_underscore to be passed to IsMember or Transform
+inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
+
+/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform
+inline std::string ignore_space(std::string item) {
+ item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
+ item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
+ return item;
+}
+
+/// Multiply a number by a factor using given mapping.
+/// Can be used to write transforms for SIZE or DURATION inputs.
+///
+/// Example:
+/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}`
+/// one can recognize inputs like "100", "12kb", "100 MB",
+/// that will be automatically transformed to 100, 14448, 104857600.
+///
+/// Output number type matches the type in the provided mapping.
+/// Therefore, if it is required to interpret real inputs like "0.42 s",
+/// the mapping should be of a type <string, float> or <string, double>.
+class AsNumberWithUnit : public Validator {
+ public:
+ /// Adjust AsNumberWithUnit behavior.
+ /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched.
+ /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError
+ /// if UNIT_REQUIRED is set and unit literal is not found.
+ enum Options {
+ CASE_SENSITIVE = 0,
+ CASE_INSENSITIVE = 1,
+ UNIT_OPTIONAL = 0,
+ UNIT_REQUIRED = 2,
+ DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL
+ };
+
+ template <typename Number>
+ explicit AsNumberWithUnit(std::map<std::string, Number> mapping,
+ Options opts = DEFAULT,
+ const std::string &unit_name = "UNIT") {
+ description(generate_description<Number>(unit_name, opts));
+ validate_mapping(mapping, opts);
+
+ // transform function
+ func_ = [mapping, opts](std::string &input) -> std::string {
+ Number num;
+
+ detail::rtrim(input);
+ if(input.empty()) {
+ throw ValidationError("Input is empty");
+ }
+
+ // Find split position between number and prefix
+ auto unit_begin = input.end();
+ while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) {
+ --unit_begin;
+ }
+
+ std::string unit{unit_begin, input.end()};
+ input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin)));
+ detail::trim(input);
+
+ if(opts & UNIT_REQUIRED && unit.empty()) {
+ throw ValidationError("Missing mandatory unit");
+ }
+ if(opts & CASE_INSENSITIVE) {
+ unit = detail::to_lower(unit);
+ }
+ if(unit.empty()) {
+ if(!detail::lexical_cast(input, num)) {
+ throw ValidationError(std::string("Value ") + input + " could not be converted to " +
+ detail::type_name<Number>());
+ }
+ // No need to modify input if no unit passed
+ return {};
+ }
+
+ // find corresponding factor
+ auto it = mapping.find(unit);
+ if(it == mapping.end()) {
+ throw ValidationError(unit +
+ " unit not recognized. "
+ "Allowed values: " +
+ detail::generate_map(mapping, true));
+ }
+
+ if(!input.empty()) {
+ bool converted = detail::lexical_cast(input, num);
+ if(!converted) {
+ throw ValidationError(std::string("Value ") + input + " could not be converted to " +
+ detail::type_name<Number>());
+ }
+ // perform safe multiplication
+ bool ok = detail::checked_multiply(num, it->second);
+ if(!ok) {
+ throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
+ " factor would cause number overflow. Use smaller value.");
+ }
+ } else {
+ num = static_cast<Number>(it->second);
+ }
+
+ input = detail::to_string(num);
+
+ return {};
+ };
+ }
+
+ private:
+ /// Check that mapping contains valid units.
+ /// Update mapping for CASE_INSENSITIVE mode.
+ template <typename Number> static void validate_mapping(std::map<std::string, Number> &mapping, Options opts) {
+ for(auto &kv : mapping) {
+ if(kv.first.empty()) {
+ throw ValidationError("Unit must not be empty.");
+ }
+ if(!detail::isalpha(kv.first)) {
+ throw ValidationError("Unit must contain only letters.");
+ }
+ }
+
+ // make all units lowercase if CASE_INSENSITIVE
+ if(opts & CASE_INSENSITIVE) {
+ std::map<std::string, Number> lower_mapping;
+ for(auto &kv : mapping) {
+ auto s = detail::to_lower(kv.first);
+ if(lower_mapping.count(s)) {
+ throw ValidationError(std::string("Several matching lowercase unit representations are found: ") +
+ s);
+ }
+ lower_mapping[detail::to_lower(kv.first)] = kv.second;
+ }
+ mapping = std::move(lower_mapping);
+ }
+ }
+
+ /// Generate description like this: NUMBER [UNIT]
+ template <typename Number> static std::string generate_description(const std::string &name, Options opts) {
+ std::stringstream out;
+ out << detail::type_name<Number>() << ' ';
+ if(opts & UNIT_REQUIRED) {
+ out << name;
+ } else {
+ out << '[' << name << ']';
+ }
+ return out.str();
+ }
+};
+
+/// Converts a human-readable size string (with unit literal) to uin64_t size.
+/// Example:
+/// "100" => 100
+/// "1 b" => 100
+/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024)
+/// "10 KB" => 10240
+/// "10 kb" => 10240
+/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024)
+/// "10kb" => 10240
+/// "2 MB" => 2097152
+/// "2 EiB" => 2^61 // Units up to exibyte are supported
+class AsSizeValue : public AsNumberWithUnit {
+ public:
+ using result_t = std::uint64_t;
+
+ /// If kb_is_1000 is true,
+ /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024
+ /// (same applies to higher order units as well).
+ /// Otherwise, interpret all literals as factors of 1024.
+ /// The first option is formally correct, but
+ /// the second interpretation is more wide-spread
+ /// (see https://en.wikipedia.org/wiki/Binary_prefix).
+ explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) {
+ if(kb_is_1000) {
+ description("SIZE [b, kb(=1000b), kib(=1024b), ...]");
+ } else {
+ description("SIZE [b, kb(=1024b), ...]");
+ }
+ }
+
+ private:
+ /// Get <size unit, factor> mapping
+ static std::map<std::string, result_t> init_mapping(bool kb_is_1000) {
+ std::map<std::string, result_t> m;
+ result_t k_factor = kb_is_1000 ? 1000 : 1024;
+ result_t ki_factor = 1024;
+ result_t k = 1;
+ result_t ki = 1;
+ m["b"] = 1;
+ for(std::string p : {"k", "m", "g", "t", "p", "e"}) {
+ k *= k_factor;
+ ki *= ki_factor;
+ m[p] = k;
+ m[p + "b"] = k;
+ m[p + "i"] = ki;
+ m[p + "ib"] = ki;
+ }
+ return m;
+ }
+
+ /// Cache calculated mapping
+ static std::map<std::string, result_t> get_mapping(bool kb_is_1000) {
+ if(kb_is_1000) {
+ static auto m = init_mapping(true);
+ return m;
+ } else {
+ static auto m = init_mapping(false);
+ return m;
+ }
+ }
+};
+
+namespace detail {
+/// Split a string into a program name and command line arguments
+/// the string is assumed to contain a file name followed by other arguments
+/// the return value contains is a pair with the first argument containing the program name and the second
+/// everything else.
+inline std::pair<std::string, std::string> split_program_name(std::string commandline) {
+ // try to determine the programName
+ std::pair<std::string, std::string> vals;
+ trim(commandline);
+ auto esp = commandline.find_first_of(' ', 1);
+ while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) {
+ esp = commandline.find_first_of(' ', esp + 1);
+ if(esp == std::string::npos) {
+ // if we have reached the end and haven't found a valid file just assume the first argument is the
+ // program name
+ if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') {
+ bool embeddedQuote = false;
+ auto keyChar = commandline[0];
+ auto end = commandline.find_first_of(keyChar, 1);
+ while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes
+ end = commandline.find_first_of(keyChar, end + 1);
+ embeddedQuote = true;
+ }
+ if(end != std::string::npos) {
+ vals.first = commandline.substr(1, end - 1);
+ esp = end + 1;
+ if(embeddedQuote) {
+ vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar));
+ }
+ } else {
+ esp = commandline.find_first_of(' ', 1);
+ }
+ } else {
+ esp = commandline.find_first_of(' ', 1);
+ }
+
+ break;
+ }
+ }
+ if(vals.first.empty()) {
+ vals.first = commandline.substr(0, esp);
+ rtrim(vals.first);
+ }
+
+ // strip the program name
+ vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{};
+ ltrim(vals.second);
+ return vals;
+}
+
+} // namespace detail
+/// @}
+
+// [CLI11:validators_hpp:end]
+} // namespace CLI
diff --git a/src/third-party/CLI/Version.hpp b/src/third-party/CLI/Version.hpp
new file mode 100644
index 0000000..b03141b
--- /dev/null
+++ b/src/third-party/CLI/Version.hpp
@@ -0,0 +1,16 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:version_hpp:verbatim]
+
+#define CLI11_VERSION_MAJOR 2
+#define CLI11_VERSION_MINOR 2
+#define CLI11_VERSION_PATCH 0
+#define CLI11_VERSION "2.2.0"
+
+// [CLI11:version_hpp:end]
diff --git a/src/third-party/backward-cpp/backward.hpp b/src/third-party/backward-cpp/backward.hpp
new file mode 100644
index 0000000..e9b4909
--- /dev/null
+++ b/src/third-party/backward-cpp/backward.hpp
@@ -0,0 +1,4460 @@
+/*
+ * backward.hpp
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * 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 H_6B9572DA_A64B_49E6_B234_051480991C89
+#define H_6B9572DA_A64B_49E6_B234_051480991C89
+
+#ifndef __cplusplus
+#error "It's not going to compile without a C++ compiler..."
+#endif
+
+#if defined(BACKWARD_CXX11)
+#elif defined(BACKWARD_CXX98)
+#else
+#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)
+#define BACKWARD_CXX11
+#define BACKWARD_ATLEAST_CXX11
+#define BACKWARD_ATLEAST_CXX98
+#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#define BACKWARD_ATLEAST_CXX17
+#endif
+#else
+#define BACKWARD_CXX98
+#define BACKWARD_ATLEAST_CXX98
+#endif
+#endif
+
+// You can define one of the following (or leave it to the auto-detection):
+//
+// #define BACKWARD_SYSTEM_LINUX
+// - specialization for linux
+//
+// #define BACKWARD_SYSTEM_DARWIN
+// - specialization for Mac OS X 10.5 and later.
+//
+// #define BACKWARD_SYSTEM_WINDOWS
+// - specialization for Windows (Clang 9 and MSVC2017)
+//
+// #define BACKWARD_SYSTEM_UNKNOWN
+// - placebo implementation, does nothing.
+//
+#if defined(BACKWARD_SYSTEM_LINUX)
+#elif defined(BACKWARD_SYSTEM_DARWIN)
+#elif defined(BACKWARD_SYSTEM_UNKNOWN)
+#elif defined(BACKWARD_SYSTEM_WINDOWS)
+#else
+#if defined(__linux) || defined(__linux__)
+#define BACKWARD_SYSTEM_LINUX
+#elif defined(__APPLE__)
+#define BACKWARD_SYSTEM_DARWIN
+#elif defined(_WIN32)
+#define BACKWARD_SYSTEM_WINDOWS
+#else
+#define BACKWARD_SYSTEM_UNKNOWN
+#endif
+#endif
+
+#define NOINLINE __attribute__((noinline))
+
+#include <algorithm>
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <limits>
+#include <new>
+#include <sstream>
+#include <streambuf>
+#include <string>
+#include <vector>
+#include <exception>
+#include <iterator>
+
+#if defined(BACKWARD_SYSTEM_LINUX)
+
+// On linux, backtrace can back-trace or "walk" the stack using the following
+// libraries:
+//
+// #define BACKWARD_HAS_UNWIND 1
+// - unwind comes from libgcc, but I saw an equivalent inside clang itself.
+// - with unwind, the stacktrace is as accurate as it can possibly be, since
+// this is used by the C++ runtine in gcc/clang for stack unwinding on
+// exception.
+// - normally libgcc is already linked to your program by default.
+//
+// #define BACKWARD_HAS_LIBUNWIND 1
+// - libunwind provides, in some cases, a more accurate stacktrace as it knows
+// to decode signal handler frames and lets us edit the context registers when
+// unwinding, allowing stack traces over bad function references.
+//
+// #define BACKWARD_HAS_BACKTRACE == 1
+// - backtrace seems to be a little bit more portable than libunwind, but on
+// linux, it uses unwind anyway, but abstract away a tiny information that is
+// sadly really important in order to get perfectly accurate stack traces.
+// - backtrace is part of the (e)glib library.
+//
+// The default is:
+// #define BACKWARD_HAS_UNWIND == 1
+//
+// Note that only one of the define should be set to 1 at a time.
+//
+#if BACKWARD_HAS_UNWIND == 1
+#elif BACKWARD_HAS_LIBUNWIND == 1
+#elif BACKWARD_HAS_BACKTRACE == 1
+#else
+#undef BACKWARD_HAS_UNWIND
+#define BACKWARD_HAS_UNWIND 1
+#undef BACKWARD_HAS_LIBUNWIND
+#define BACKWARD_HAS_LIBUNWIND 0
+#undef BACKWARD_HAS_BACKTRACE
+#define BACKWARD_HAS_BACKTRACE 0
+#endif
+
+// On linux, backward can extract detailed information about a stack trace
+// using one of the following libraries:
+//
+// #define BACKWARD_HAS_DW 1
+// - libdw gives you the most juicy details out of your stack traces:
+// - object filename
+// - function name
+// - source filename
+// - line and column numbers
+// - source code snippet (assuming the file is accessible)
+// - variables name and values (if not optimized out)
+// - You need to link with the lib "dw":
+// - apt-get install libdw-dev
+// - g++/clang++ -ldw ...
+//
+// #define BACKWARD_HAS_BFD 1
+// - With libbfd, you get a fair amount of details:
+// - object filename
+// - function name
+// - source filename
+// - line numbers
+// - source code snippet (assuming the file is accessible)
+// - You need to link with the lib "bfd":
+// - apt-get install binutils-dev
+// - g++/clang++ -lbfd ...
+//
+// #define BACKWARD_HAS_DWARF 1
+// - libdwarf gives you the most juicy details out of your stack traces:
+// - object filename
+// - function name
+// - source filename
+// - line and column numbers
+// - source code snippet (assuming the file is accessible)
+// - variables name and values (if not optimized out)
+// - You need to link with the lib "dwarf":
+// - apt-get install libdwarf-dev
+// - g++/clang++ -ldwarf ...
+//
+// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1
+// - backtrace provides minimal details for a stack trace:
+// - object filename
+// - function name
+// - backtrace is part of the (e)glib library.
+//
+// The default is:
+// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1
+//
+// Note that only one of the define should be set to 1 at a time.
+//
+#if BACKWARD_HAS_DW == 1
+#elif BACKWARD_HAS_BFD == 1
+#elif BACKWARD_HAS_DWARF == 1
+#elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1
+#else
+#undef BACKWARD_HAS_DW
+#define BACKWARD_HAS_DW 0
+#undef BACKWARD_HAS_BFD
+#define BACKWARD_HAS_BFD 0
+#undef BACKWARD_HAS_DWARF
+#define BACKWARD_HAS_DWARF 0
+#undef BACKWARD_HAS_BACKTRACE_SYMBOL
+#define BACKWARD_HAS_BACKTRACE_SYMBOL 1
+#endif
+
+#include <cxxabi.h>
+#include <fcntl.h>
+#ifdef __ANDROID__
+// Old Android API levels define _Unwind_Ptr in both link.h and
+// unwind.h Rename the one in link.h as we are not going to be using
+// it
+#define _Unwind_Ptr _Unwind_Ptr_Custom
+#include <link.h>
+#undef _Unwind_Ptr
+#else
+#include <link.h>
+#endif
+#include <signal.h>
+#include <sys/stat.h>
+#include <syscall.h>
+#include <unistd.h>
+
+#if BACKWARD_HAS_BFD == 1
+// NOTE: defining PACKAGE{,_VERSION} is required before including
+// bfd.h on some platforms, see also:
+// https://sourceware.org/bugzilla/show_bug.cgi?id=14243
+#ifndef PACKAGE
+#define PACKAGE
+#endif
+#ifndef PACKAGE_VERSION
+#define PACKAGE_VERSION
+#endif
+#include <bfd.h>
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#include <dlfcn.h>
+#undef _GNU_SOURCE
+#else
+#include <dlfcn.h>
+#endif
+#endif
+
+#if BACKWARD_HAS_DW == 1
+#include <dwarf.h>
+#include <elfutils/libdw.h>
+#include <elfutils/libdwfl.h>
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#include <dlfcn.h>
+#undef _GNU_SOURCE
+#else
+#include <dlfcn.h>
+#endif
+#endif
+
+#if BACKWARD_HAS_DWARF == 1
+#include <algorithm>
+#include <dwarf.h>
+#include <libdwarf.h>
+#include <libelf.h>
+#include <map>
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#include <dlfcn.h>
+#undef _GNU_SOURCE
+#else
+#include <dlfcn.h>
+#endif
+#endif
+
+#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1)
+// then we shall rely on backtrace
+#include <execinfo.h>
+#endif
+
+#endif // defined(BACKWARD_SYSTEM_LINUX)
+
+#if defined(BACKWARD_SYSTEM_DARWIN)
+// On Darwin, backtrace can back-trace or "walk" the stack using the following
+// libraries:
+//
+// #define BACKWARD_HAS_UNWIND 1
+// - unwind comes from libgcc, but I saw an equivalent inside clang itself.
+// - with unwind, the stacktrace is as accurate as it can possibly be, since
+// this is used by the C++ runtine in gcc/clang for stack unwinding on
+// exception.
+// - normally libgcc is already linked to your program by default.
+//
+// #define BACKWARD_HAS_LIBUNWIND 1
+// - libunwind comes from clang, which implements an API compatible version.
+// - libunwind provides, in some cases, a more accurate stacktrace as it knows
+// to decode signal handler frames and lets us edit the context registers when
+// unwinding, allowing stack traces over bad function references.
+//
+// #define BACKWARD_HAS_BACKTRACE == 1
+// - backtrace is available by default, though it does not produce as much
+// information as another library might.
+//
+// The default is:
+// #define BACKWARD_HAS_UNWIND == 1
+//
+// Note that only one of the define should be set to 1 at a time.
+//
+#if BACKWARD_HAS_UNWIND == 1
+#elif BACKWARD_HAS_BACKTRACE == 1
+#elif BACKWARD_HAS_LIBUNWIND == 1
+#else
+#undef BACKWARD_HAS_UNWIND
+#define BACKWARD_HAS_UNWIND 1
+#undef BACKWARD_HAS_BACKTRACE
+#define BACKWARD_HAS_BACKTRACE 0
+#undef BACKWARD_HAS_LIBUNWIND
+#define BACKWARD_HAS_LIBUNWIND 0
+#endif
+
+// On Darwin, backward can extract detailed information about a stack trace
+// using one of the following libraries:
+//
+// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1
+// - backtrace provides minimal details for a stack trace:
+// - object filename
+// - function name
+//
+// The default is:
+// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1
+//
+#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1
+#else
+#undef BACKWARD_HAS_BACKTRACE_SYMBOL
+#define BACKWARD_HAS_BACKTRACE_SYMBOL 1
+#endif
+
+#include <cxxabi.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1)
+#include <execinfo.h>
+#endif
+#endif // defined(BACKWARD_SYSTEM_DARWIN)
+
+#if defined(BACKWARD_SYSTEM_WINDOWS)
+
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+#include <basetsd.h>
+typedef SSIZE_T ssize_t;
+
+#define NOMINMAX
+#include <windows.h>
+#include <winnt.h>
+
+#include <psapi.h>
+#include <signal.h>
+
+#ifndef __clang__
+#undef NOINLINE
+#define NOINLINE __declspec(noinline)
+#endif
+
+#pragma comment(lib, "psapi.lib")
+#pragma comment(lib, "dbghelp.lib")
+
+// Comment / packing is from stackoverflow:
+// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227
+// Some versions of imagehlp.dll lack the proper packing directives themselves
+// so we need to do it.
+#pragma pack(push, before_imagehlp, 8)
+#include <imagehlp.h>
+#pragma pack(pop, before_imagehlp)
+
+// TODO maybe these should be undefined somewhere else?
+#undef BACKWARD_HAS_UNWIND
+#undef BACKWARD_HAS_BACKTRACE
+#if BACKWARD_HAS_PDB_SYMBOL == 1
+#else
+#undef BACKWARD_HAS_PDB_SYMBOL
+#define BACKWARD_HAS_PDB_SYMBOL 1
+#endif
+
+#endif
+
+#if BACKWARD_HAS_UNWIND == 1
+
+#include <unwind.h>
+// while gcc's unwind.h defines something like that:
+// extern _Unwind_Ptr _Unwind_GetIP (struct _Unwind_Context *);
+// extern _Unwind_Ptr _Unwind_GetIPInfo (struct _Unwind_Context *, int *);
+//
+// clang's unwind.h defines something like this:
+// uintptr_t _Unwind_GetIP(struct _Unwind_Context* __context);
+//
+// Even if the _Unwind_GetIPInfo can be linked to, it is not declared, worse we
+// cannot just redeclare it because clang's unwind.h doesn't define _Unwind_Ptr
+// anyway.
+//
+// Luckily we can play on the fact that the guard macros have a different name:
+#ifdef __CLANG_UNWIND_H
+// In fact, this function still comes from libgcc (on my different linux boxes,
+// clang links against libgcc).
+#include <inttypes.h>
+extern "C" uintptr_t _Unwind_GetIPInfo(_Unwind_Context *, int *);
+#endif
+
+#endif // BACKWARD_HAS_UNWIND == 1
+
+#if BACKWARD_HAS_LIBUNWIND == 1
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+#endif // BACKWARD_HAS_LIBUNWIND == 1
+
+#ifdef BACKWARD_ATLEAST_CXX11
+#include <unordered_map>
+#include <utility> // for std::swap
+namespace backward {
+namespace details {
+template <typename K, typename V> struct hashtable {
+ typedef std::unordered_map<K, V> type;
+};
+using std::move;
+} // namespace details
+} // namespace backward
+#else // NOT BACKWARD_ATLEAST_CXX11
+#define nullptr NULL
+#define override
+#include <map>
+namespace backward {
+namespace details {
+template <typename K, typename V> struct hashtable {
+ typedef std::map<K, V> type;
+};
+template <typename T> const T &move(const T &v) { return v; }
+template <typename T> T &move(T &v) { return v; }
+} // namespace details
+} // namespace backward
+#endif // BACKWARD_ATLEAST_CXX11
+
+namespace backward {
+namespace details {
+#if defined(BACKWARD_SYSTEM_WINDOWS)
+const char kBackwardPathDelimiter[] = ";";
+#else
+const char kBackwardPathDelimiter[] = ":";
+#endif
+} // namespace details
+} // namespace backward
+
+namespace backward {
+
+namespace system_tag {
+struct linux_tag; // seems that I cannot call that "linux" because the name
+// is already defined... so I am adding _tag everywhere.
+struct darwin_tag;
+struct windows_tag;
+struct unknown_tag;
+
+#if defined(BACKWARD_SYSTEM_LINUX)
+typedef linux_tag current_tag;
+#elif defined(BACKWARD_SYSTEM_DARWIN)
+typedef darwin_tag current_tag;
+#elif defined(BACKWARD_SYSTEM_WINDOWS)
+typedef windows_tag current_tag;
+#elif defined(BACKWARD_SYSTEM_UNKNOWN)
+typedef unknown_tag current_tag;
+#else
+#error "May I please get my system defines?"
+#endif
+} // namespace system_tag
+
+namespace trace_resolver_tag {
+#if defined(BACKWARD_SYSTEM_LINUX)
+struct libdw;
+struct libbfd;
+struct libdwarf;
+struct backtrace_symbol;
+
+#if BACKWARD_HAS_DW == 1
+typedef libdw current;
+#elif BACKWARD_HAS_BFD == 1
+typedef libbfd current;
+#elif BACKWARD_HAS_DWARF == 1
+typedef libdwarf current;
+#elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1
+typedef backtrace_symbol current;
+#else
+#error "You shall not pass, until you know what you want."
+#endif
+#elif defined(BACKWARD_SYSTEM_DARWIN)
+struct backtrace_symbol;
+
+#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1
+typedef backtrace_symbol current;
+#else
+#error "You shall not pass, until you know what you want."
+#endif
+#elif defined(BACKWARD_SYSTEM_WINDOWS)
+struct pdb_symbol;
+#if BACKWARD_HAS_PDB_SYMBOL == 1
+typedef pdb_symbol current;
+#else
+#error "You shall not pass, until you know what you want."
+#endif
+#endif
+} // namespace trace_resolver_tag
+
+namespace details {
+
+template <typename T> struct rm_ptr { typedef T type; };
+
+template <typename T> struct rm_ptr<T *> { typedef T type; };
+
+template <typename T> struct rm_ptr<const T *> { typedef const T type; };
+
+template <typename R, typename T, R (*F)(T)> struct deleter {
+ template <typename U> void operator()(U &ptr) const { (*F)(ptr); }
+};
+
+template <typename T> struct default_delete {
+ void operator()(T &ptr) const { delete ptr; }
+};
+
+template <typename T, typename Deleter = deleter<void, void *, &::free>>
+class handle {
+ struct dummy;
+ T _val;
+ bool _empty;
+
+#ifdef BACKWARD_ATLEAST_CXX11
+ handle(const handle &) = delete;
+ handle &operator=(const handle &) = delete;
+#endif
+
+public:
+ ~handle() {
+ if (!_empty) {
+ Deleter()(_val);
+ }
+ }
+
+ explicit handle() : _val(), _empty(true) {}
+ explicit handle(T val) : _val(val), _empty(false) {
+ if (!_val)
+ _empty = true;
+ }
+
+#ifdef BACKWARD_ATLEAST_CXX11
+ handle(handle &&from) : _empty(true) { swap(from); }
+ handle &operator=(handle &&from) {
+ swap(from);
+ return *this;
+ }
+#else
+ explicit handle(const handle &from) : _empty(true) {
+ // some sort of poor man's move semantic.
+ swap(const_cast<handle &>(from));
+ }
+ handle &operator=(const handle &from) {
+ // some sort of poor man's move semantic.
+ swap(const_cast<handle &>(from));
+ return *this;
+ }
+#endif
+
+ void reset(T new_val) {
+ handle tmp(new_val);
+ swap(tmp);
+ }
+
+ void update(T new_val) {
+ _val = new_val;
+ _empty = !static_cast<bool>(new_val);
+ }
+
+ operator const dummy *() const {
+ if (_empty) {
+ return nullptr;
+ }
+ return reinterpret_cast<const dummy *>(_val);
+ }
+ T get() { return _val; }
+ T release() {
+ _empty = true;
+ return _val;
+ }
+ void swap(handle &b) {
+ using std::swap;
+ swap(b._val, _val); // can throw, we are safe here.
+ swap(b._empty, _empty); // should not throw: if you cannot swap two
+ // bools without throwing... It's a lost cause anyway!
+ }
+
+ T &operator->() { return _val; }
+ const T &operator->() const { return _val; }
+
+ typedef typename rm_ptr<T>::type &ref_t;
+ typedef const typename rm_ptr<T>::type &const_ref_t;
+ ref_t operator*() { return *_val; }
+ const_ref_t operator*() const { return *_val; }
+ ref_t operator[](size_t idx) { return _val[idx]; }
+
+ // Watch out, we've got a badass over here
+ T *operator&() {
+ _empty = false;
+ return &_val;
+ }
+};
+
+// Default demangler implementation (do nothing).
+template <typename TAG> struct demangler_impl {
+ static std::string demangle(const char *funcname) { return funcname; }
+};
+
+#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN)
+
+template <> struct demangler_impl<system_tag::current_tag> {
+ demangler_impl() : _demangle_buffer_length(0) {}
+
+ std::string demangle(const char *funcname) {
+ using namespace details;
+ char *result = abi::__cxa_demangle(funcname, _demangle_buffer.get(),
+ &_demangle_buffer_length, nullptr);
+ if (result) {
+ _demangle_buffer.update(result);
+ return result;
+ }
+ return funcname;
+ }
+
+private:
+ details::handle<char *> _demangle_buffer;
+ size_t _demangle_buffer_length;
+};
+
+#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN
+
+struct demangler : public demangler_impl<system_tag::current_tag> {};
+
+// Split a string on the platform's PATH delimiter. Example: if delimiter
+// is ":" then:
+// "" --> []
+// ":" --> ["",""]
+// "::" --> ["","",""]
+// "/a/b/c" --> ["/a/b/c"]
+// "/a/b/c:/d/e/f" --> ["/a/b/c","/d/e/f"]
+// etc.
+inline std::vector<std::string> split_source_prefixes(const std::string &s) {
+ std::vector<std::string> out;
+ size_t last = 0;
+ size_t next = 0;
+ size_t delimiter_size = sizeof(kBackwardPathDelimiter) - 1;
+ while ((next = s.find(kBackwardPathDelimiter, last)) != std::string::npos) {
+ out.push_back(s.substr(last, next - last));
+ last = next + delimiter_size;
+ }
+ if (last <= s.length()) {
+ out.push_back(s.substr(last));
+ }
+ return out;
+}
+
+} // namespace details
+
+/*************** A TRACE ***************/
+
+struct Trace {
+ void *addr;
+ size_t idx;
+
+ Trace() : addr(nullptr), idx(0) {}
+
+ explicit Trace(void *_addr, size_t _idx) : addr(_addr), idx(_idx) {}
+};
+
+struct ResolvedTrace : public Trace {
+
+ struct SourceLoc {
+ std::string function;
+ std::string filename;
+ unsigned line;
+ unsigned col;
+
+ SourceLoc() : line(0), col(0) {}
+
+ bool operator==(const SourceLoc &b) const {
+ return function == b.function && filename == b.filename &&
+ line == b.line && col == b.col;
+ }
+
+ bool operator!=(const SourceLoc &b) const { return !(*this == b); }
+ };
+
+ // In which binary object this trace is located.
+ std::string object_filename;
+
+ // The function in the object that contain the trace. This is not the same
+ // as source.function which can be an function inlined in object_function.
+ std::string object_function;
+
+ // The source location of this trace. It is possible for filename to be
+ // empty and for line/col to be invalid (value 0) if this information
+ // couldn't be deduced, for example if there is no debug information in the
+ // binary object.
+ SourceLoc source;
+
+ // An optionals list of "inliners". All the successive sources location
+ // from where the source location of the trace (the attribute right above)
+ // is inlined. It is especially useful when you compiled with optimization.
+ typedef std::vector<SourceLoc> source_locs_t;
+ source_locs_t inliners;
+
+ ResolvedTrace() : Trace() {}
+ ResolvedTrace(const Trace &mini_trace) : Trace(mini_trace) {}
+};
+
+/*************** STACK TRACE ***************/
+
+// default implemention.
+template <typename TAG> class StackTraceImpl {
+public:
+ size_t size() const { return 0; }
+ Trace operator[](size_t) const { return Trace(); }
+ size_t load_here(size_t = 0) { return 0; }
+ size_t load_from(void *, size_t = 0, void * = nullptr, void * = nullptr) {
+ return 0;
+ }
+ size_t thread_id() const { return 0; }
+ void skip_n_firsts(size_t) {}
+};
+
+class StackTraceImplBase {
+public:
+ StackTraceImplBase()
+ : _thread_id(0), _skip(0), _context(nullptr), _error_addr(nullptr) {}
+
+ size_t thread_id() const { return _thread_id; }
+
+ void skip_n_firsts(size_t n) { _skip = n; }
+
+protected:
+ void load_thread_info() {
+#ifdef BACKWARD_SYSTEM_LINUX
+#ifndef __ANDROID__
+ _thread_id = static_cast<size_t>(syscall(SYS_gettid));
+#else
+ _thread_id = static_cast<size_t>(gettid());
+#endif
+ if (_thread_id == static_cast<size_t>(getpid())) {
+ // If the thread is the main one, let's hide that.
+ // I like to keep little secret sometimes.
+ _thread_id = 0;
+ }
+#elif defined(BACKWARD_SYSTEM_DARWIN)
+ _thread_id = reinterpret_cast<size_t>(pthread_self());
+ if (pthread_main_np() == 1) {
+ // If the thread is the main one, let's hide that.
+ _thread_id = 0;
+ }
+#endif
+ }
+
+ void set_context(void *context) { _context = context; }
+ void *context() const { return _context; }
+
+ void set_error_addr(void *error_addr) { _error_addr = error_addr; }
+ void *error_addr() const { return _error_addr; }
+
+ size_t skip_n_firsts() const { return _skip; }
+
+private:
+ size_t _thread_id;
+ size_t _skip;
+ void *_context;
+ void *_error_addr;
+};
+
+class StackTraceImplHolder : public StackTraceImplBase {
+public:
+ size_t size() const {
+ return (_stacktrace.size() >= skip_n_firsts())
+ ? _stacktrace.size() - skip_n_firsts()
+ : 0;
+ }
+ Trace operator[](size_t idx) const {
+ if (idx >= size()) {
+ return Trace();
+ }
+ return Trace(_stacktrace[idx + skip_n_firsts()], idx);
+ }
+ void *const *begin() const {
+ if (size()) {
+ return &_stacktrace[skip_n_firsts()];
+ }
+ return nullptr;
+ }
+
+protected:
+ std::vector<void *> _stacktrace;
+};
+
+#if BACKWARD_HAS_UNWIND == 1
+
+namespace details {
+
+template <typename F> class Unwinder {
+public:
+ size_t operator()(F &f, size_t depth) {
+ _f = &f;
+ _index = -1;
+ _depth = depth;
+ _Unwind_Backtrace(&this->backtrace_trampoline, this);
+ return static_cast<size_t>(_index);
+ }
+
+private:
+ F *_f;
+ ssize_t _index;
+ size_t _depth;
+
+ static _Unwind_Reason_Code backtrace_trampoline(_Unwind_Context *ctx,
+ void *self) {
+ return (static_cast<Unwinder *>(self))->backtrace(ctx);
+ }
+
+ _Unwind_Reason_Code backtrace(_Unwind_Context *ctx) {
+ if (_index >= 0 && static_cast<size_t>(_index) >= _depth)
+ return _URC_END_OF_STACK;
+
+ int ip_before_instruction = 0;
+ uintptr_t ip = _Unwind_GetIPInfo(ctx, &ip_before_instruction);
+
+ if (!ip_before_instruction) {
+ // calculating 0-1 for unsigned, looks like a possible bug to sanitiziers,
+ // so let's do it explicitly:
+ if (ip == 0) {
+ ip = std::numeric_limits<uintptr_t>::max(); // set it to 0xffff... (as
+ // from casting 0-1)
+ } else {
+ ip -= 1; // else just normally decrement it (no overflow/underflow will
+ // happen)
+ }
+ }
+
+ if (_index >= 0) { // ignore first frame.
+ (*_f)(static_cast<size_t>(_index), reinterpret_cast<void *>(ip));
+ }
+ _index += 1;
+ return _URC_NO_REASON;
+ }
+};
+
+template <typename F> size_t unwind(F f, size_t depth) {
+ Unwinder<F> unwinder;
+ return unwinder(f, depth);
+}
+
+} // namespace details
+
+template <>
+class StackTraceImpl<system_tag::current_tag> : public StackTraceImplHolder {
+public:
+ NOINLINE
+ size_t load_here(size_t depth = 32, void *context = nullptr,
+ void *error_addr = nullptr) {
+ load_thread_info();
+ set_context(context);
+ set_error_addr(error_addr);
+ if (depth == 0) {
+ return 0;
+ }
+ _stacktrace.resize(depth);
+ size_t trace_cnt = details::unwind(callback(*this), depth);
+ _stacktrace.resize(trace_cnt);
+ skip_n_firsts(0);
+ return size();
+ }
+ size_t load_from(void *addr, size_t depth = 32, void *context = nullptr,
+ void *error_addr = nullptr) {
+ load_here(depth + 8, context, error_addr);
+
+ for (size_t i = 0; i < _stacktrace.size(); ++i) {
+ if (_stacktrace[i] == addr) {
+ skip_n_firsts(i);
+ break;
+ }
+ }
+
+ _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth));
+ return size();
+ }
+
+private:
+ struct callback {
+ StackTraceImpl &self;
+ callback(StackTraceImpl &_self) : self(_self) {}
+
+ void operator()(size_t idx, void *addr) { self._stacktrace[idx] = addr; }
+ };
+};
+
+#elif BACKWARD_HAS_LIBUNWIND == 1
+
+template <>
+class StackTraceImpl<system_tag::current_tag> : public StackTraceImplHolder {
+public:
+ __attribute__((noinline)) size_t load_here(size_t depth = 32,
+ void *_context = nullptr,
+ void *_error_addr = nullptr) {
+ set_context(_context);
+ set_error_addr(_error_addr);
+ load_thread_info();
+ if (depth == 0) {
+ return 0;
+ }
+ _stacktrace.resize(depth + 1);
+
+ int result = 0;
+
+ unw_context_t ctx;
+ size_t index = 0;
+
+ // Add the tail call. If the Instruction Pointer is the crash address it
+ // means we got a bad function pointer dereference, so we "unwind" the
+ // bad pointer manually by using the return address pointed to by the
+ // Stack Pointer as the Instruction Pointer and letting libunwind do
+ // the rest
+
+ if (context()) {
+ ucontext_t *uctx = reinterpret_cast<ucontext_t *>(context());
+#ifdef REG_RIP // x86_64
+ if (uctx->uc_mcontext.gregs[REG_RIP] ==
+ reinterpret_cast<greg_t>(error_addr())) {
+ uctx->uc_mcontext.gregs[REG_RIP] =
+ *reinterpret_cast<size_t *>(uctx->uc_mcontext.gregs[REG_RSP]);
+ }
+ _stacktrace[index] =
+ reinterpret_cast<void *>(uctx->uc_mcontext.gregs[REG_RIP]);
+ ++index;
+ ctx = *reinterpret_cast<unw_context_t *>(uctx);
+#elif defined(REG_EIP) // x86_32
+ if (uctx->uc_mcontext.gregs[REG_EIP] ==
+ reinterpret_cast<greg_t>(error_addr())) {
+ uctx->uc_mcontext.gregs[REG_EIP] =
+ *reinterpret_cast<size_t *>(uctx->uc_mcontext.gregs[REG_ESP]);
+ }
+ _stacktrace[index] =
+ reinterpret_cast<void *>(uctx->uc_mcontext.gregs[REG_EIP]);
+ ++index;
+ ctx = *reinterpret_cast<unw_context_t *>(uctx);
+#elif defined(__arm__)
+ // libunwind uses its own context type for ARM unwinding.
+ // Copy the registers from the signal handler's context so we can
+ // unwind
+ unw_getcontext(&ctx);
+ ctx.regs[UNW_ARM_R0] = uctx->uc_mcontext.arm_r0;
+ ctx.regs[UNW_ARM_R1] = uctx->uc_mcontext.arm_r1;
+ ctx.regs[UNW_ARM_R2] = uctx->uc_mcontext.arm_r2;
+ ctx.regs[UNW_ARM_R3] = uctx->uc_mcontext.arm_r3;
+ ctx.regs[UNW_ARM_R4] = uctx->uc_mcontext.arm_r4;
+ ctx.regs[UNW_ARM_R5] = uctx->uc_mcontext.arm_r5;
+ ctx.regs[UNW_ARM_R6] = uctx->uc_mcontext.arm_r6;
+ ctx.regs[UNW_ARM_R7] = uctx->uc_mcontext.arm_r7;
+ ctx.regs[UNW_ARM_R8] = uctx->uc_mcontext.arm_r8;
+ ctx.regs[UNW_ARM_R9] = uctx->uc_mcontext.arm_r9;
+ ctx.regs[UNW_ARM_R10] = uctx->uc_mcontext.arm_r10;
+ ctx.regs[UNW_ARM_R11] = uctx->uc_mcontext.arm_fp;
+ ctx.regs[UNW_ARM_R12] = uctx->uc_mcontext.arm_ip;
+ ctx.regs[UNW_ARM_R13] = uctx->uc_mcontext.arm_sp;
+ ctx.regs[UNW_ARM_R14] = uctx->uc_mcontext.arm_lr;
+ ctx.regs[UNW_ARM_R15] = uctx->uc_mcontext.arm_pc;
+
+ // If we have crashed in the PC use the LR instead, as this was
+ // a bad function dereference
+ if (reinterpret_cast<unsigned long>(error_addr()) ==
+ uctx->uc_mcontext.arm_pc) {
+ ctx.regs[UNW_ARM_R15] =
+ uctx->uc_mcontext.arm_lr - sizeof(unsigned long);
+ }
+ _stacktrace[index] = reinterpret_cast<void *>(ctx.regs[UNW_ARM_R15]);
+ ++index;
+#elif defined(__APPLE__) && defined(__x86_64__)
+ unw_getcontext(&ctx);
+ // OS X's implementation of libunwind uses its own context object
+ // so we need to convert the passed context to libunwind's format
+ // (information about the data layout taken from unw_getcontext.s
+ // in Apple's libunwind source
+ ctx.data[0] = uctx->uc_mcontext->__ss.__rax;
+ ctx.data[1] = uctx->uc_mcontext->__ss.__rbx;
+ ctx.data[2] = uctx->uc_mcontext->__ss.__rcx;
+ ctx.data[3] = uctx->uc_mcontext->__ss.__rdx;
+ ctx.data[4] = uctx->uc_mcontext->__ss.__rdi;
+ ctx.data[5] = uctx->uc_mcontext->__ss.__rsi;
+ ctx.data[6] = uctx->uc_mcontext->__ss.__rbp;
+ ctx.data[7] = uctx->uc_mcontext->__ss.__rsp;
+ ctx.data[8] = uctx->uc_mcontext->__ss.__r8;
+ ctx.data[9] = uctx->uc_mcontext->__ss.__r9;
+ ctx.data[10] = uctx->uc_mcontext->__ss.__r10;
+ ctx.data[11] = uctx->uc_mcontext->__ss.__r11;
+ ctx.data[12] = uctx->uc_mcontext->__ss.__r12;
+ ctx.data[13] = uctx->uc_mcontext->__ss.__r13;
+ ctx.data[14] = uctx->uc_mcontext->__ss.__r14;
+ ctx.data[15] = uctx->uc_mcontext->__ss.__r15;
+ ctx.data[16] = uctx->uc_mcontext->__ss.__rip;
+
+ // If the IP is the same as the crash address we have a bad function
+ // dereference The caller's address is pointed to by %rsp, so we
+ // dereference that value and set it to be the next frame's IP.
+ if (uctx->uc_mcontext->__ss.__rip ==
+ reinterpret_cast<__uint64_t>(error_addr())) {
+ ctx.data[16] =
+ *reinterpret_cast<__uint64_t *>(uctx->uc_mcontext->__ss.__rsp);
+ }
+ _stacktrace[index] = reinterpret_cast<void *>(ctx.data[16]);
+ ++index;
+#elif defined(__APPLE__)
+ unw_getcontext(&ctx)
+ // TODO: Convert the ucontext_t to libunwind's unw_context_t like
+ // we do in 64 bits
+ if (ctx.uc_mcontext->__ss.__eip ==
+ reinterpret_cast<greg_t>(error_addr())) {
+ ctx.uc_mcontext->__ss.__eip = ctx.uc_mcontext->__ss.__esp;
+ }
+ _stacktrace[index] =
+ reinterpret_cast<void *>(ctx.uc_mcontext->__ss.__eip);
+ ++index;
+#endif
+ }
+
+ unw_cursor_t cursor;
+ if (context()) {
+ result = unw_init_local2(&cursor, &ctx, UNW_INIT_SIGNAL_FRAME);
+ } else {
+ unw_getcontext(&ctx);
+ ;
+ result = unw_init_local(&cursor, &ctx);
+ }
+
+ if (result != 0)
+ return 1;
+
+ unw_word_t ip = 0;
+
+ while (index <= depth && unw_step(&cursor) > 0) {
+ result = unw_get_reg(&cursor, UNW_REG_IP, &ip);
+ if (result == 0) {
+ _stacktrace[index] = reinterpret_cast<void *>(--ip);
+ ++index;
+ }
+ }
+ --index;
+
+ _stacktrace.resize(index + 1);
+ skip_n_firsts(0);
+ return size();
+ }
+
+ size_t load_from(void *addr, size_t depth = 32, void *context = nullptr,
+ void *error_addr = nullptr) {
+ load_here(depth + 8, context, error_addr);
+
+ for (size_t i = 0; i < _stacktrace.size(); ++i) {
+ if (_stacktrace[i] == addr) {
+ skip_n_firsts(i);
+ _stacktrace[i] = (void *)((uintptr_t)_stacktrace[i]);
+ break;
+ }
+ }
+
+ _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth));
+ return size();
+ }
+};
+
+#elif defined(BACKWARD_HAS_BACKTRACE)
+
+template <>
+class StackTraceImpl<system_tag::current_tag> : public StackTraceImplHolder {
+public:
+ NOINLINE
+ size_t load_here(size_t depth = 32, void *context = nullptr,
+ void *error_addr = nullptr) {
+ set_context(context);
+ set_error_addr(error_addr);
+ load_thread_info();
+ if (depth == 0) {
+ return 0;
+ }
+ _stacktrace.resize(depth + 1);
+ size_t trace_cnt = backtrace(&_stacktrace[0], _stacktrace.size());
+ _stacktrace.resize(trace_cnt);
+ skip_n_firsts(1);
+ return size();
+ }
+
+ size_t load_from(void *addr, size_t depth = 32, void *context = nullptr,
+ void *error_addr = nullptr) {
+ load_here(depth + 8, contxt, error_addr);
+
+ for (size_t i = 0; i < _stacktrace.size(); ++i) {
+ if (_stacktrace[i] == addr) {
+ skip_n_firsts(i);
+ _stacktrace[i] = (void *)((uintptr_t)_stacktrace[i] + 1);
+ break;
+ }
+ }
+
+ _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth));
+ return size();
+ }
+};
+
+#elif defined(BACKWARD_SYSTEM_WINDOWS)
+
+template <>
+class StackTraceImpl<system_tag::current_tag> : public StackTraceImplHolder {
+public:
+ // We have to load the machine type from the image info
+ // So we first initialize the resolver, and it tells us this info
+ void set_machine_type(DWORD machine_type) { machine_type_ = machine_type; }
+ void set_context(CONTEXT *ctx) { ctx_ = ctx; }
+ void set_thread_handle(HANDLE handle) { thd_ = handle; }
+
+ NOINLINE
+ size_t load_here(size_t depth = 32, void *context = nullptr,
+ void *error_addr = nullptr) {
+ set_context(static_cast<CONTEXT*>(context));
+ set_error_addr(error_addr);
+ CONTEXT localCtx; // used when no context is provided
+
+ if (depth == 0) {
+ return 0;
+ }
+
+ if (!ctx_) {
+ ctx_ = &localCtx;
+ RtlCaptureContext(ctx_);
+ }
+
+ if (!thd_) {
+ thd_ = GetCurrentThread();
+ }
+
+ HANDLE process = GetCurrentProcess();
+
+ STACKFRAME64 s;
+ memset(&s, 0, sizeof(STACKFRAME64));
+
+ // TODO: 32 bit context capture
+ s.AddrStack.Mode = AddrModeFlat;
+ s.AddrFrame.Mode = AddrModeFlat;
+ s.AddrPC.Mode = AddrModeFlat;
+#ifdef _M_X64
+ s.AddrPC.Offset = ctx_->Rip;
+ s.AddrStack.Offset = ctx_->Rsp;
+ s.AddrFrame.Offset = ctx_->Rbp;
+#else
+ s.AddrPC.Offset = ctx_->Eip;
+ s.AddrStack.Offset = ctx_->Esp;
+ s.AddrFrame.Offset = ctx_->Ebp;
+#endif
+
+ if (!machine_type_) {
+#ifdef _M_X64
+ machine_type_ = IMAGE_FILE_MACHINE_AMD64;
+#else
+ machine_type_ = IMAGE_FILE_MACHINE_I386;
+#endif
+ }
+
+ for (;;) {
+ // NOTE: this only works if PDBs are already loaded!
+ SetLastError(0);
+ if (!StackWalk64(machine_type_, process, thd_, &s, ctx_, NULL,
+ SymFunctionTableAccess64, SymGetModuleBase64, NULL))
+ break;
+
+ if (s.AddrReturn.Offset == 0)
+ break;
+
+ _stacktrace.push_back(reinterpret_cast<void *>(s.AddrPC.Offset));
+
+ if (size() >= depth)
+ break;
+ }
+
+ return size();
+ }
+
+ size_t load_from(void *addr, size_t depth = 32, void *context = nullptr,
+ void *error_addr = nullptr) {
+ load_here(depth + 8, context, error_addr);
+
+ for (size_t i = 0; i < _stacktrace.size(); ++i) {
+ if (_stacktrace[i] == addr) {
+ skip_n_firsts(i);
+ break;
+ }
+ }
+
+ _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth));
+ return size();
+ }
+
+private:
+ DWORD machine_type_ = 0;
+ HANDLE thd_ = 0;
+ CONTEXT *ctx_ = nullptr;
+};
+
+#endif
+
+class StackTrace : public StackTraceImpl<system_tag::current_tag> {};
+
+/*************** TRACE RESOLVER ***************/
+
+class TraceResolverImplBase {
+public:
+ virtual ~TraceResolverImplBase() {}
+
+ virtual void load_addresses(void *const*addresses, int address_count) {
+ (void)addresses;
+ (void)address_count;
+ }
+
+ template <class ST> void load_stacktrace(ST &st) {
+ load_addresses(st.begin(), (int)st.size());
+ }
+
+ virtual ResolvedTrace resolve(ResolvedTrace t) { return t; }
+
+protected:
+ std::string demangle(const char *funcname) {
+ return _demangler.demangle(funcname);
+ }
+
+private:
+ details::demangler _demangler;
+};
+
+template <typename TAG> class TraceResolverImpl;
+
+#ifdef BACKWARD_SYSTEM_UNKNOWN
+
+template <> class TraceResolverImpl<system_tag::unknown_tag>
+ : public TraceResolverImplBase {};
+
+#endif
+
+#ifdef BACKWARD_SYSTEM_LINUX
+
+class TraceResolverLinuxBase : public TraceResolverImplBase {
+public:
+ TraceResolverLinuxBase()
+ : argv0_(get_argv0()), exec_path_(read_symlink("/proc/self/exe")) {}
+ std::string resolve_exec_path(Dl_info &symbol_info) const {
+ // mutates symbol_info.dli_fname to be filename to open and returns filename
+ // to display
+ if (symbol_info.dli_fname == argv0_) {
+ // dladdr returns argv[0] in dli_fname for symbols contained in
+ // the main executable, which is not a valid path if the
+ // executable was found by a search of the PATH environment
+ // variable; In that case, we actually open /proc/self/exe, which
+ // is always the actual executable (even if it was deleted/replaced!)
+ // but display the path that /proc/self/exe links to.
+ // However, this right away reduces probability of successful symbol
+ // resolution, because libbfd may try to find *.debug files in the
+ // same dir, in case symbols are stripped. As a result, it may try
+ // to find a file /proc/self/<exe_name>.debug, which obviously does
+ // not exist. /proc/self/exe is a last resort. First load attempt
+ // should go for the original executable file path.
+ symbol_info.dli_fname = "/proc/self/exe";
+ return exec_path_;
+ } else {
+ return symbol_info.dli_fname;
+ }
+ }
+
+private:
+ std::string argv0_;
+ std::string exec_path_;
+
+ static std::string get_argv0() {
+ std::string argv0;
+ std::ifstream ifs("/proc/self/cmdline");
+ std::getline(ifs, argv0, '\0');
+ return argv0;
+ }
+
+ static std::string read_symlink(std::string const &symlink_path) {
+ std::string path;
+ path.resize(100);
+
+ while (true) {
+ ssize_t len =
+ ::readlink(symlink_path.c_str(), &*path.begin(), path.size());
+ if (len < 0) {
+ return "";
+ }
+ if (static_cast<size_t>(len) == path.size()) {
+ path.resize(path.size() * 2);
+ } else {
+ path.resize(static_cast<std::string::size_type>(len));
+ break;
+ }
+ }
+
+ return path;
+ }
+};
+
+template <typename STACKTRACE_TAG> class TraceResolverLinuxImpl;
+
+#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1
+
+template <>
+class TraceResolverLinuxImpl<trace_resolver_tag::backtrace_symbol>
+ : public TraceResolverLinuxBase {
+public:
+ void load_addresses(void *const*addresses, int address_count) override {
+ if (address_count == 0) {
+ return;
+ }
+ _symbols.reset(backtrace_symbols(addresses, address_count));
+ }
+
+ ResolvedTrace resolve(ResolvedTrace trace) override {
+ char *filename = _symbols[trace.idx];
+ char *funcname = filename;
+ while (*funcname && *funcname != '(') {
+ funcname += 1;
+ }
+ trace.object_filename.assign(filename,
+ funcname); // ok even if funcname is the ending
+ // \0 (then we assign entire string)
+
+ if (*funcname) { // if it's not end of string (e.g. from last frame ip==0)
+ funcname += 1;
+ char *funcname_end = funcname;
+ while (*funcname_end && *funcname_end != ')' && *funcname_end != '+') {
+ funcname_end += 1;
+ }
+ *funcname_end = '\0';
+ trace.object_function = this->demangle(funcname);
+ trace.source.function = trace.object_function; // we cannot do better.
+ }
+ return trace;
+ }
+
+private:
+ details::handle<char **> _symbols;
+};
+
+#endif // BACKWARD_HAS_BACKTRACE_SYMBOL == 1
+
+#if BACKWARD_HAS_BFD == 1
+
+template <>
+class TraceResolverLinuxImpl<trace_resolver_tag::libbfd>
+ : public TraceResolverLinuxBase {
+public:
+ TraceResolverLinuxImpl() : _bfd_loaded(false) {}
+
+ ResolvedTrace resolve(ResolvedTrace trace) override {
+ Dl_info symbol_info;
+
+ // trace.addr is a virtual address in memory pointing to some code.
+ // Let's try to find from which loaded object it comes from.
+ // The loaded object can be yourself btw.
+ if (!dladdr(trace.addr, &symbol_info)) {
+ return trace; // dat broken trace...
+ }
+
+ // Now we get in symbol_info:
+ // .dli_fname:
+ // pathname of the shared object that contains the address.
+ // .dli_fbase:
+ // where the object is loaded in memory.
+ // .dli_sname:
+ // the name of the nearest symbol to trace.addr, we expect a
+ // function name.
+ // .dli_saddr:
+ // the exact address corresponding to .dli_sname.
+
+ if (symbol_info.dli_sname) {
+ trace.object_function = demangle(symbol_info.dli_sname);
+ }
+
+ if (!symbol_info.dli_fname) {
+ return trace;
+ }
+
+ trace.object_filename = resolve_exec_path(symbol_info);
+ bfd_fileobject *fobj;
+ // Before rushing to resolution need to ensure the executable
+ // file still can be used. For that compare inode numbers of
+ // what is stored by the executable's file path, and in the
+ // dli_fname, which not necessarily equals to the executable.
+ // It can be a shared library, or /proc/self/exe, and in the
+ // latter case has drawbacks. See the exec path resolution for
+ // details. In short - the dli object should be used only as
+ // the last resort.
+ // If inode numbers are equal, it is known dli_fname and the
+ // executable file are the same. This is guaranteed by Linux,
+ // because if the executable file is changed/deleted, it will
+ // be done in a new inode. The old file will be preserved in
+ // /proc/self/exe, and may even have inode 0. The latter can
+ // happen if the inode was actually reused, and the file was
+ // kept only in the main memory.
+ //
+ struct stat obj_stat;
+ struct stat dli_stat;
+ if (stat(trace.object_filename.c_str(), &obj_stat) == 0 &&
+ stat(symbol_info.dli_fname, &dli_stat) == 0 &&
+ obj_stat.st_ino == dli_stat.st_ino) {
+ // The executable file, and the shared object containing the
+ // address are the same file. Safe to use the original path.
+ // this is preferable. Libbfd will search for stripped debug
+ // symbols in the same directory.
+ fobj = load_object_with_bfd(trace.object_filename);
+ } else{
+ // The original object file was *deleted*! The only hope is
+ // that the debug symbols are either inside the shared
+ // object file, or are in the same directory, and this is
+ // not /proc/self/exe.
+ fobj = nullptr;
+ }
+ if (fobj == nullptr || !fobj->handle) {
+ fobj = load_object_with_bfd(symbol_info.dli_fname);
+ if (!fobj->handle) {
+ return trace;
+ }
+ }
+
+ find_sym_result *details_selected; // to be filled.
+
+ // trace.addr is the next instruction to be executed after returning
+ // from the nested stack frame. In C++ this usually relate to the next
+ // statement right after the function call that leaded to a new stack
+ // frame. This is not usually what you want to see when printing out a
+ // stacktrace...
+ find_sym_result details_call_site =
+ find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase);
+ details_selected = &details_call_site;
+
+#if BACKWARD_HAS_UNWIND == 0
+ // ...this is why we also try to resolve the symbol that is right
+ // before the return address. If we are lucky enough, we will get the
+ // line of the function that was called. But if the code is optimized,
+ // we might get something absolutely not related since the compiler
+ // can reschedule the return address with inline functions and
+ // tail-call optimisation (among other things that I don't even know
+ // or cannot even dream about with my tiny limited brain).
+ find_sym_result details_adjusted_call_site = find_symbol_details(
+ fobj, (void *)(uintptr_t(trace.addr) - 1), symbol_info.dli_fbase);
+
+ // In debug mode, we should always get the right thing(TM).
+ if (details_call_site.found && details_adjusted_call_site.found) {
+ // Ok, we assume that details_adjusted_call_site is a better estimation.
+ details_selected = &details_adjusted_call_site;
+ trace.addr = (void *)(uintptr_t(trace.addr) - 1);
+ }
+
+ if (details_selected == &details_call_site && details_call_site.found) {
+ // we have to re-resolve the symbol in order to reset some
+ // internal state in BFD... so we can call backtrace_inliners
+ // thereafter...
+ details_call_site =
+ find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase);
+ }
+#endif // BACKWARD_HAS_UNWIND
+
+ if (details_selected->found) {
+ if (details_selected->filename) {
+ trace.source.filename = details_selected->filename;
+ }
+ trace.source.line = details_selected->line;
+
+ if (details_selected->funcname) {
+ // this time we get the name of the function where the code is
+ // located, instead of the function were the address is
+ // located. In short, if the code was inlined, we get the
+ // function correspoding to the code. Else we already got in
+ // trace.function.
+ trace.source.function = demangle(details_selected->funcname);
+
+ if (!symbol_info.dli_sname) {
+ // for the case dladdr failed to find the symbol name of
+ // the function, we might as well try to put something
+ // here.
+ trace.object_function = trace.source.function;
+ }
+ }
+
+ // Maybe the source of the trace got inlined inside the function
+ // (trace.source.function). Let's see if we can get all the inlined
+ // calls along the way up to the initial call site.
+ trace.inliners = backtrace_inliners(fobj, *details_selected);
+
+#if 0
+ if (trace.inliners.size() == 0) {
+ // Maybe the trace was not inlined... or maybe it was and we
+ // are lacking the debug information. Let's try to make the
+ // world better and see if we can get the line number of the
+ // function (trace.source.function) now.
+ //
+ // We will get the location of where the function start (to be
+ // exact: the first instruction that really start the
+ // function), not where the name of the function is defined.
+ // This can be quite far away from the name of the function
+ // btw.
+ //
+ // If the source of the function is the same as the source of
+ // the trace, we cannot say if the trace was really inlined or
+ // not. However, if the filename of the source is different
+ // between the function and the trace... we can declare it as
+ // an inliner. This is not 100% accurate, but better than
+ // nothing.
+
+ if (symbol_info.dli_saddr) {
+ find_sym_result details = find_symbol_details(fobj,
+ symbol_info.dli_saddr,
+ symbol_info.dli_fbase);
+
+ if (details.found) {
+ ResolvedTrace::SourceLoc diy_inliner;
+ diy_inliner.line = details.line;
+ if (details.filename) {
+ diy_inliner.filename = details.filename;
+ }
+ if (details.funcname) {
+ diy_inliner.function = demangle(details.funcname);
+ } else {
+ diy_inliner.function = trace.source.function;
+ }
+ if (diy_inliner != trace.source) {
+ trace.inliners.push_back(diy_inliner);
+ }
+ }
+ }
+ }
+#endif
+ }
+
+ return trace;
+ }
+
+private:
+ bool _bfd_loaded;
+
+ typedef details::handle<bfd *,
+ details::deleter<bfd_boolean, bfd *, &bfd_close>>
+ bfd_handle_t;
+
+ typedef details::handle<asymbol **> bfd_symtab_t;
+
+ struct bfd_fileobject {
+ bfd_handle_t handle;
+ bfd_vma base_addr;
+ bfd_symtab_t symtab;
+ bfd_symtab_t dynamic_symtab;
+ };
+
+ typedef details::hashtable<std::string, bfd_fileobject>::type fobj_bfd_map_t;
+ fobj_bfd_map_t _fobj_bfd_map;
+
+ bfd_fileobject *load_object_with_bfd(const std::string &filename_object) {
+ using namespace details;
+
+ if (!_bfd_loaded) {
+ using namespace details;
+ bfd_init();
+ _bfd_loaded = true;
+ }
+
+ fobj_bfd_map_t::iterator it = _fobj_bfd_map.find(filename_object);
+ if (it != _fobj_bfd_map.end()) {
+ return &it->second;
+ }
+
+ // this new object is empty for now.
+ bfd_fileobject *r = &_fobj_bfd_map[filename_object];
+
+ // we do the work temporary in this one;
+ bfd_handle_t bfd_handle;
+
+ int fd = open(filename_object.c_str(), O_RDONLY);
+ bfd_handle.reset(bfd_fdopenr(filename_object.c_str(), "default", fd));
+ if (!bfd_handle) {
+ close(fd);
+ return r;
+ }
+
+ if (!bfd_check_format(bfd_handle.get(), bfd_object)) {
+ return r; // not an object? You lose.
+ }
+
+ if ((bfd_get_file_flags(bfd_handle.get()) & HAS_SYMS) == 0) {
+ return r; // that's what happen when you forget to compile in debug.
+ }
+
+ ssize_t symtab_storage_size = bfd_get_symtab_upper_bound(bfd_handle.get());
+
+ ssize_t dyn_symtab_storage_size =
+ bfd_get_dynamic_symtab_upper_bound(bfd_handle.get());
+
+ if (symtab_storage_size <= 0 && dyn_symtab_storage_size <= 0) {
+ return r; // weird, is the file is corrupted?
+ }
+
+ bfd_symtab_t symtab, dynamic_symtab;
+ ssize_t symcount = 0, dyn_symcount = 0;
+
+ if (symtab_storage_size > 0) {
+ symtab.reset(static_cast<bfd_symbol **>(
+ malloc(static_cast<size_t>(symtab_storage_size))));
+ symcount = bfd_canonicalize_symtab(bfd_handle.get(), symtab.get());
+ }
+
+ if (dyn_symtab_storage_size > 0) {
+ dynamic_symtab.reset(static_cast<bfd_symbol **>(
+ malloc(static_cast<size_t>(dyn_symtab_storage_size))));
+ dyn_symcount = bfd_canonicalize_dynamic_symtab(bfd_handle.get(),
+ dynamic_symtab.get());
+ }
+
+ if (symcount <= 0 && dyn_symcount <= 0) {
+ return r; // damned, that's a stripped file that you got there!
+ }
+
+ r->handle = move(bfd_handle);
+ r->symtab = move(symtab);
+ r->dynamic_symtab = move(dynamic_symtab);
+ return r;
+ }
+
+ struct find_sym_result {
+ bool found;
+ const char *filename;
+ const char *funcname;
+ unsigned int line;
+ };
+
+ struct find_sym_context {
+ TraceResolverLinuxImpl *self;
+ bfd_fileobject *fobj;
+ void *addr;
+ void *base_addr;
+ find_sym_result result;
+ };
+
+ find_sym_result find_symbol_details(bfd_fileobject *fobj, void *addr,
+ void *base_addr) {
+ find_sym_context context;
+ context.self = this;
+ context.fobj = fobj;
+ context.addr = addr;
+ context.base_addr = base_addr;
+ context.result.found = false;
+ bfd_map_over_sections(fobj->handle.get(), &find_in_section_trampoline,
+ static_cast<void *>(&context));
+ return context.result;
+ }
+
+ static void find_in_section_trampoline(bfd *, asection *section, void *data) {
+ find_sym_context *context = static_cast<find_sym_context *>(data);
+ context->self->find_in_section(
+ reinterpret_cast<bfd_vma>(context->addr),
+ reinterpret_cast<bfd_vma>(context->base_addr), context->fobj, section,
+ context->result);
+ }
+
+ void find_in_section(bfd_vma addr, bfd_vma base_addr, bfd_fileobject *fobj,
+ asection *section, find_sym_result &result) {
+ if (result.found)
+ return;
+
+#ifdef bfd_get_section_flags
+ if ((bfd_get_section_flags(fobj->handle.get(), section) & SEC_ALLOC) == 0)
+#else
+ if ((bfd_section_flags(section) & SEC_ALLOC) == 0)
+#endif
+ return; // a debug section is never loaded automatically.
+
+#ifdef bfd_get_section_vma
+ bfd_vma sec_addr = bfd_get_section_vma(fobj->handle.get(), section);
+#else
+ bfd_vma sec_addr = bfd_section_vma(section);
+#endif
+#ifdef bfd_get_section_size
+ bfd_size_type size = bfd_get_section_size(section);
+#else
+ bfd_size_type size = bfd_section_size(section);
+#endif
+
+ // are we in the boundaries of the section?
+ if (addr < sec_addr || addr >= sec_addr + size) {
+ addr -= base_addr; // oups, a relocated object, lets try again...
+ if (addr < sec_addr || addr >= sec_addr + size) {
+ return;
+ }
+ }
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
+#endif
+ if (!result.found && fobj->symtab) {
+ result.found = bfd_find_nearest_line(
+ fobj->handle.get(), section, fobj->symtab.get(), addr - sec_addr,
+ &result.filename, &result.funcname, &result.line);
+ }
+
+ if (!result.found && fobj->dynamic_symtab) {
+ result.found = bfd_find_nearest_line(
+ fobj->handle.get(), section, fobj->dynamic_symtab.get(),
+ addr - sec_addr, &result.filename, &result.funcname, &result.line);
+ }
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+ }
+
+ ResolvedTrace::source_locs_t
+ backtrace_inliners(bfd_fileobject *fobj, find_sym_result previous_result) {
+ // This function can be called ONLY after a SUCCESSFUL call to
+ // find_symbol_details. The state is global to the bfd_handle.
+ ResolvedTrace::source_locs_t results;
+ while (previous_result.found) {
+ find_sym_result result;
+ result.found = bfd_find_inliner_info(fobj->handle.get(), &result.filename,
+ &result.funcname, &result.line);
+
+ if (result
+ .found) /* and not (
+ cstrings_eq(previous_result.filename,
+ result.filename) and
+ cstrings_eq(previous_result.funcname, result.funcname)
+ and result.line == previous_result.line
+ )) */
+ {
+ ResolvedTrace::SourceLoc src_loc;
+ src_loc.line = result.line;
+ if (result.filename) {
+ src_loc.filename = result.filename;
+ }
+ if (result.funcname) {
+ src_loc.function = demangle(result.funcname);
+ }
+ results.push_back(src_loc);
+ }
+ previous_result = result;
+ }
+ return results;
+ }
+
+ bool cstrings_eq(const char *a, const char *b) {
+ if (!a || !b) {
+ return false;
+ }
+ return strcmp(a, b) == 0;
+ }
+};
+#endif // BACKWARD_HAS_BFD == 1
+
+#if BACKWARD_HAS_DW == 1
+
+template <>
+class TraceResolverLinuxImpl<trace_resolver_tag::libdw>
+ : public TraceResolverLinuxBase {
+public:
+ TraceResolverLinuxImpl() : _dwfl_handle_initialized(false) {}
+
+ ResolvedTrace resolve(ResolvedTrace trace) override {
+ using namespace details;
+
+ Dwarf_Addr trace_addr = (Dwarf_Addr)trace.addr;
+
+ if (!_dwfl_handle_initialized) {
+ // initialize dwfl...
+ _dwfl_cb.reset(new Dwfl_Callbacks);
+ _dwfl_cb->find_elf = &dwfl_linux_proc_find_elf;
+ _dwfl_cb->find_debuginfo = &dwfl_standard_find_debuginfo;
+ _dwfl_cb->debuginfo_path = 0;
+
+ _dwfl_handle.reset(dwfl_begin(_dwfl_cb.get()));
+ _dwfl_handle_initialized = true;
+
+ if (!_dwfl_handle) {
+ return trace;
+ }
+
+ // ...from the current process.
+ dwfl_report_begin(_dwfl_handle.get());
+ int r = dwfl_linux_proc_report(_dwfl_handle.get(), getpid());
+ dwfl_report_end(_dwfl_handle.get(), NULL, NULL);
+ if (r < 0) {
+ return trace;
+ }
+ }
+
+ if (!_dwfl_handle) {
+ return trace;
+ }
+
+ // find the module (binary object) that contains the trace's address.
+ // This is not using any debug information, but the addresses ranges of
+ // all the currently loaded binary object.
+ Dwfl_Module *mod = dwfl_addrmodule(_dwfl_handle.get(), trace_addr);
+ if (mod) {
+ // now that we found it, lets get the name of it, this will be the
+ // full path to the running binary or one of the loaded library.
+ const char *module_name = dwfl_module_info(mod, 0, 0, 0, 0, 0, 0, 0);
+ if (module_name) {
+ trace.object_filename = module_name;
+ }
+ // We also look after the name of the symbol, equal or before this
+ // address. This is found by walking the symtab. We should get the
+ // symbol corresponding to the function (mangled) containing the
+ // address. If the code corresponding to the address was inlined,
+ // this is the name of the out-most inliner function.
+ const char *sym_name = dwfl_module_addrname(mod, trace_addr);
+ if (sym_name) {
+ trace.object_function = demangle(sym_name);
+ }
+ }
+
+ // now let's get serious, and find out the source location (file and
+ // line number) of the address.
+
+ // This function will look in .debug_aranges for the address and map it
+ // to the location of the compilation unit DIE in .debug_info and
+ // return it.
+ Dwarf_Addr mod_bias = 0;
+ Dwarf_Die *cudie = dwfl_module_addrdie(mod, trace_addr, &mod_bias);
+
+#if 1
+ if (!cudie) {
+ // Sadly clang does not generate the section .debug_aranges, thus
+ // dwfl_module_addrdie will fail early. Clang doesn't either set
+ // the lowpc/highpc/range info for every compilation unit.
+ //
+ // So in order to save the world:
+ // for every compilation unit, we will iterate over every single
+ // DIEs. Normally functions should have a lowpc/highpc/range, which
+ // we will use to infer the compilation unit.
+
+ // note that this is probably badly inefficient.
+ while ((cudie = dwfl_module_nextcu(mod, cudie, &mod_bias))) {
+ Dwarf_Die die_mem;
+ Dwarf_Die *fundie =
+ find_fundie_by_pc(cudie, trace_addr - mod_bias, &die_mem);
+ if (fundie) {
+ break;
+ }
+ }
+ }
+#endif
+
+//#define BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE
+#ifdef BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE
+ if (!cudie) {
+ // If it's still not enough, lets dive deeper in the shit, and try
+ // to save the world again: for every compilation unit, we will
+ // load the corresponding .debug_line section, and see if we can
+ // find our address in it.
+
+ Dwarf_Addr cfi_bias;
+ Dwarf_CFI *cfi_cache = dwfl_module_eh_cfi(mod, &cfi_bias);
+
+ Dwarf_Addr bias;
+ while ((cudie = dwfl_module_nextcu(mod, cudie, &bias))) {
+ if (dwarf_getsrc_die(cudie, trace_addr - bias)) {
+
+ // ...but if we get a match, it might be a false positive
+ // because our (address - bias) might as well be valid in a
+ // different compilation unit. So we throw our last card on
+ // the table and lookup for the address into the .eh_frame
+ // section.
+
+ handle<Dwarf_Frame *> frame;
+ dwarf_cfi_addrframe(cfi_cache, trace_addr - cfi_bias, &frame);
+ if (frame) {
+ break;
+ }
+ }
+ }
+ }
+#endif
+
+ if (!cudie) {
+ return trace; // this time we lost the game :/
+ }
+
+ // Now that we have a compilation unit DIE, this function will be able
+ // to load the corresponding section in .debug_line (if not already
+ // loaded) and hopefully find the source location mapped to our
+ // address.
+ Dwarf_Line *srcloc = dwarf_getsrc_die(cudie, trace_addr - mod_bias);
+
+ if (srcloc) {
+ const char *srcfile = dwarf_linesrc(srcloc, 0, 0);
+ if (srcfile) {
+ trace.source.filename = srcfile;
+ }
+ int line = 0, col = 0;
+ dwarf_lineno(srcloc, &line);
+ dwarf_linecol(srcloc, &col);
+ trace.source.line = line;
+ trace.source.col = col;
+ }
+
+ deep_first_search_by_pc(cudie, trace_addr - mod_bias,
+ inliners_search_cb(trace));
+ if (trace.source.function.size() == 0) {
+ // fallback.
+ trace.source.function = trace.object_function;
+ }
+
+ return trace;
+ }
+
+private:
+ typedef details::handle<Dwfl *, details::deleter<void, Dwfl *, &dwfl_end>>
+ dwfl_handle_t;
+ details::handle<Dwfl_Callbacks *, details::default_delete<Dwfl_Callbacks *>>
+ _dwfl_cb;
+ dwfl_handle_t _dwfl_handle;
+ bool _dwfl_handle_initialized;
+
+ // defined here because in C++98, template function cannot take locally
+ // defined types... grrr.
+ struct inliners_search_cb {
+ void operator()(Dwarf_Die *die) {
+ switch (dwarf_tag(die)) {
+ const char *name;
+ case DW_TAG_subprogram:
+ if ((name = dwarf_diename(die))) {
+ trace.source.function = name;
+ }
+ break;
+
+ case DW_TAG_inlined_subroutine:
+ ResolvedTrace::SourceLoc sloc;
+ Dwarf_Attribute attr_mem;
+
+ if ((name = dwarf_diename(die))) {
+ sloc.function = name;
+ }
+ if ((name = die_call_file(die))) {
+ sloc.filename = name;
+ }
+
+ Dwarf_Word line = 0, col = 0;
+ dwarf_formudata(dwarf_attr(die, DW_AT_call_line, &attr_mem), &line);
+ dwarf_formudata(dwarf_attr(die, DW_AT_call_column, &attr_mem), &col);
+ sloc.line = (unsigned)line;
+ sloc.col = (unsigned)col;
+
+ trace.inliners.push_back(sloc);
+ break;
+ };
+ }
+ ResolvedTrace &trace;
+ inliners_search_cb(ResolvedTrace &t) : trace(t) {}
+ };
+
+ static bool die_has_pc(Dwarf_Die *die, Dwarf_Addr pc) {
+ Dwarf_Addr low, high;
+
+ // continuous range
+ if (dwarf_hasattr(die, DW_AT_low_pc) && dwarf_hasattr(die, DW_AT_high_pc)) {
+ if (dwarf_lowpc(die, &low) != 0) {
+ return false;
+ }
+ if (dwarf_highpc(die, &high) != 0) {
+ Dwarf_Attribute attr_mem;
+ Dwarf_Attribute *attr = dwarf_attr(die, DW_AT_high_pc, &attr_mem);
+ Dwarf_Word value;
+ if (dwarf_formudata(attr, &value) != 0) {
+ return false;
+ }
+ high = low + value;
+ }
+ return pc >= low && pc < high;
+ }
+
+ // non-continuous range.
+ Dwarf_Addr base;
+ ptrdiff_t offset = 0;
+ while ((offset = dwarf_ranges(die, offset, &base, &low, &high)) > 0) {
+ if (pc >= low && pc < high) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static Dwarf_Die *find_fundie_by_pc(Dwarf_Die *parent_die, Dwarf_Addr pc,
+ Dwarf_Die *result) {
+ if (dwarf_child(parent_die, result) != 0) {
+ return 0;
+ }
+
+ Dwarf_Die *die = result;
+ do {
+ switch (dwarf_tag(die)) {
+ case DW_TAG_subprogram:
+ case DW_TAG_inlined_subroutine:
+ if (die_has_pc(die, pc)) {
+ return result;
+ }
+ };
+ bool declaration = false;
+ Dwarf_Attribute attr_mem;
+ dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem),
+ &declaration);
+ if (!declaration) {
+ // let's be curious and look deeper in the tree,
+ // function are not necessarily at the first level, but
+ // might be nested inside a namespace, structure etc.
+ Dwarf_Die die_mem;
+ Dwarf_Die *indie = find_fundie_by_pc(die, pc, &die_mem);
+ if (indie) {
+ *result = die_mem;
+ return result;
+ }
+ }
+ } while (dwarf_siblingof(die, result) == 0);
+ return 0;
+ }
+
+ template <typename CB>
+ static bool deep_first_search_by_pc(Dwarf_Die *parent_die, Dwarf_Addr pc,
+ CB cb) {
+ Dwarf_Die die_mem;
+ if (dwarf_child(parent_die, &die_mem) != 0) {
+ return false;
+ }
+
+ bool branch_has_pc = false;
+ Dwarf_Die *die = &die_mem;
+ do {
+ bool declaration = false;
+ Dwarf_Attribute attr_mem;
+ dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem),
+ &declaration);
+ if (!declaration) {
+ // let's be curious and look deeper in the tree, function are
+ // not necessarily at the first level, but might be nested
+ // inside a namespace, structure, a function, an inlined
+ // function etc.
+ branch_has_pc = deep_first_search_by_pc(die, pc, cb);
+ }
+ if (!branch_has_pc) {
+ branch_has_pc = die_has_pc(die, pc);
+ }
+ if (branch_has_pc) {
+ cb(die);
+ }
+ } while (dwarf_siblingof(die, &die_mem) == 0);
+ return branch_has_pc;
+ }
+
+ static const char *die_call_file(Dwarf_Die *die) {
+ Dwarf_Attribute attr_mem;
+ Dwarf_Word file_idx = 0;
+
+ dwarf_formudata(dwarf_attr(die, DW_AT_call_file, &attr_mem), &file_idx);
+
+ if (file_idx == 0) {
+ return 0;
+ }
+
+ Dwarf_Die die_mem;
+ Dwarf_Die *cudie = dwarf_diecu(die, &die_mem, 0, 0);
+ if (!cudie) {
+ return 0;
+ }
+
+ Dwarf_Files *files = 0;
+ size_t nfiles;
+ dwarf_getsrcfiles(cudie, &files, &nfiles);
+ if (!files) {
+ return 0;
+ }
+
+ return dwarf_filesrc(files, file_idx, 0, 0);
+ }
+};
+#endif // BACKWARD_HAS_DW == 1
+
+#if BACKWARD_HAS_DWARF == 1
+
+template <>
+class TraceResolverLinuxImpl<trace_resolver_tag::libdwarf>
+ : public TraceResolverLinuxBase {
+public:
+ TraceResolverLinuxImpl() : _dwarf_loaded(false) {}
+
+ ResolvedTrace resolve(ResolvedTrace trace) override {
+ // trace.addr is a virtual address in memory pointing to some code.
+ // Let's try to find from which loaded object it comes from.
+ // The loaded object can be yourself btw.
+
+ Dl_info symbol_info;
+ int dladdr_result = 0;
+#if defined(__GLIBC__)
+ link_map *link_map;
+ // We request the link map so we can get information about offsets
+ dladdr_result =
+ dladdr1(trace.addr, &symbol_info, reinterpret_cast<void **>(&link_map),
+ RTLD_DL_LINKMAP);
+#else
+ // Android doesn't have dladdr1. Don't use the linker map.
+ dladdr_result = dladdr(trace.addr, &symbol_info);
+#endif
+ if (!dladdr_result) {
+ return trace; // dat broken trace...
+ }
+
+ // Now we get in symbol_info:
+ // .dli_fname:
+ // pathname of the shared object that contains the address.
+ // .dli_fbase:
+ // where the object is loaded in memory.
+ // .dli_sname:
+ // the name of the nearest symbol to trace.addr, we expect a
+ // function name.
+ // .dli_saddr:
+ // the exact address corresponding to .dli_sname.
+ //
+ // And in link_map:
+ // .l_addr:
+ // difference between the address in the ELF file and the address
+ // in memory
+ // l_name:
+ // absolute pathname where the object was found
+
+ if (symbol_info.dli_sname) {
+ trace.object_function = demangle(symbol_info.dli_sname);
+ }
+
+ if (!symbol_info.dli_fname) {
+ return trace;
+ }
+
+ trace.object_filename = resolve_exec_path(symbol_info);
+ dwarf_fileobject &fobj = load_object_with_dwarf(symbol_info.dli_fname);
+ if (!fobj.dwarf_handle) {
+ return trace; // sad, we couldn't load the object :(
+ }
+
+#if defined(__GLIBC__)
+ // Convert the address to a module relative one by looking at
+ // the module's loading address in the link map
+ Dwarf_Addr address = reinterpret_cast<uintptr_t>(trace.addr) -
+ reinterpret_cast<uintptr_t>(link_map->l_addr);
+#else
+ Dwarf_Addr address = reinterpret_cast<uintptr_t>(trace.addr);
+#endif
+
+ if (trace.object_function.empty()) {
+ symbol_cache_t::iterator it = fobj.symbol_cache.lower_bound(address);
+
+ if (it != fobj.symbol_cache.end()) {
+ if (it->first != address) {
+ if (it != fobj.symbol_cache.begin()) {
+ --it;
+ }
+ }
+ trace.object_function = demangle(it->second.c_str());
+ }
+ }
+
+ // Get the Compilation Unit DIE for the address
+ Dwarf_Die die = find_die(fobj, address);
+
+ if (!die) {
+ return trace; // this time we lost the game :/
+ }
+
+ // libdwarf doesn't give us direct access to its objects, it always
+ // allocates a copy for the caller. We keep that copy alive in a cache
+ // and we deallocate it later when it's no longer required.
+ die_cache_entry &die_object = get_die_cache(fobj, die);
+ if (die_object.isEmpty())
+ return trace; // We have no line section for this DIE
+
+ die_linemap_t::iterator it = die_object.line_section.lower_bound(address);
+
+ if (it != die_object.line_section.end()) {
+ if (it->first != address) {
+ if (it == die_object.line_section.begin()) {
+ // If we are on the first item of the line section
+ // but the address does not match it means that
+ // the address is below the range of the DIE. Give up.
+ return trace;
+ } else {
+ --it;
+ }
+ }
+ } else {
+ return trace; // We didn't find the address.
+ }
+
+ // Get the Dwarf_Line that the address points to and call libdwarf
+ // to get source file, line and column info.
+ Dwarf_Line line = die_object.line_buffer[it->second];
+ Dwarf_Error error = DW_DLE_NE;
+
+ char *filename;
+ if (dwarf_linesrc(line, &filename, &error) == DW_DLV_OK) {
+ trace.source.filename = std::string(filename);
+ dwarf_dealloc(fobj.dwarf_handle.get(), filename, DW_DLA_STRING);
+ }
+
+ Dwarf_Unsigned number = 0;
+ if (dwarf_lineno(line, &number, &error) == DW_DLV_OK) {
+ trace.source.line = number;
+ } else {
+ trace.source.line = 0;
+ }
+
+ if (dwarf_lineoff_b(line, &number, &error) == DW_DLV_OK) {
+ trace.source.col = number;
+ } else {
+ trace.source.col = 0;
+ }
+
+ std::vector<std::string> namespace_stack;
+ deep_first_search_by_pc(fobj, die, address, namespace_stack,
+ inliners_search_cb(trace, fobj, die));
+
+ dwarf_dealloc(fobj.dwarf_handle.get(), die, DW_DLA_DIE);
+
+ return trace;
+ }
+
+public:
+ static int close_dwarf(Dwarf_Debug dwarf) {
+ return dwarf_finish(dwarf, NULL);
+ }
+
+private:
+ bool _dwarf_loaded;
+
+ typedef details::handle<int, details::deleter<int, int, &::close>>
+ dwarf_file_t;
+
+ typedef details::handle<Elf *, details::deleter<int, Elf *, &elf_end>>
+ dwarf_elf_t;
+
+ typedef details::handle<Dwarf_Debug,
+ details::deleter<int, Dwarf_Debug, &close_dwarf>>
+ dwarf_handle_t;
+
+ typedef std::map<Dwarf_Addr, int> die_linemap_t;
+
+ typedef std::map<Dwarf_Off, Dwarf_Off> die_specmap_t;
+
+ struct die_cache_entry {
+ die_specmap_t spec_section;
+ die_linemap_t line_section;
+ Dwarf_Line *line_buffer;
+ Dwarf_Signed line_count;
+ Dwarf_Line_Context line_context;
+
+ inline bool isEmpty() {
+ return line_buffer == NULL || line_count == 0 || line_context == NULL ||
+ line_section.empty();
+ }
+
+ die_cache_entry() : line_buffer(0), line_count(0), line_context(0) {}
+
+ ~die_cache_entry() {
+ if (line_context) {
+ dwarf_srclines_dealloc_b(line_context);
+ }
+ }
+ };
+
+ typedef std::map<Dwarf_Off, die_cache_entry> die_cache_t;
+
+ typedef std::map<uintptr_t, std::string> symbol_cache_t;
+
+ struct dwarf_fileobject {
+ dwarf_file_t file_handle;
+ dwarf_elf_t elf_handle;
+ dwarf_handle_t dwarf_handle;
+ symbol_cache_t symbol_cache;
+
+ // Die cache
+ die_cache_t die_cache;
+ die_cache_entry *current_cu;
+ };
+
+ typedef details::hashtable<std::string, dwarf_fileobject>::type
+ fobj_dwarf_map_t;
+ fobj_dwarf_map_t _fobj_dwarf_map;
+
+ static bool cstrings_eq(const char *a, const char *b) {
+ if (!a || !b) {
+ return false;
+ }
+ return strcmp(a, b) == 0;
+ }
+
+ dwarf_fileobject &load_object_with_dwarf(const std::string &filename_object) {
+
+ if (!_dwarf_loaded) {
+ // Set the ELF library operating version
+ // If that fails there's nothing we can do
+ _dwarf_loaded = elf_version(EV_CURRENT) != EV_NONE;
+ }
+
+ fobj_dwarf_map_t::iterator it = _fobj_dwarf_map.find(filename_object);
+ if (it != _fobj_dwarf_map.end()) {
+ return it->second;
+ }
+
+ // this new object is empty for now
+ dwarf_fileobject &r = _fobj_dwarf_map[filename_object];
+
+ dwarf_file_t file_handle;
+ file_handle.reset(open(filename_object.c_str(), O_RDONLY));
+ if (file_handle.get() < 0) {
+ return r;
+ }
+
+ // Try to get an ELF handle. We need to read the ELF sections
+ // because we want to see if there is a .gnu_debuglink section
+ // that points to a split debug file
+ dwarf_elf_t elf_handle;
+ elf_handle.reset(elf_begin(file_handle.get(), ELF_C_READ, NULL));
+ if (!elf_handle) {
+ return r;
+ }
+
+ const char *e_ident = elf_getident(elf_handle.get(), 0);
+ if (!e_ident) {
+ return r;
+ }
+
+ // Get the number of sections
+ // We use the new APIs as elf_getshnum is deprecated
+ size_t shdrnum = 0;
+ if (elf_getshdrnum(elf_handle.get(), &shdrnum) == -1) {
+ return r;
+ }
+
+ // Get the index to the string section
+ size_t shdrstrndx = 0;
+ if (elf_getshdrstrndx(elf_handle.get(), &shdrstrndx) == -1) {
+ return r;
+ }
+
+ std::string debuglink;
+ // Iterate through the ELF sections to try to get a gnu_debuglink
+ // note and also to cache the symbol table.
+ // We go the preprocessor way to avoid having to create templated
+ // classes or using gelf (which might throw a compiler error if 64 bit
+ // is not supported
+#define ELF_GET_DATA(ARCH) \
+ Elf_Scn *elf_section = 0; \
+ Elf_Data *elf_data = 0; \
+ Elf##ARCH##_Shdr *section_header = 0; \
+ Elf_Scn *symbol_section = 0; \
+ size_t symbol_count = 0; \
+ size_t symbol_strings = 0; \
+ Elf##ARCH##_Sym *symbol = 0; \
+ const char *section_name = 0; \
+ \
+ while ((elf_section = elf_nextscn(elf_handle.get(), elf_section)) != NULL) { \
+ section_header = elf##ARCH##_getshdr(elf_section); \
+ if (section_header == NULL) { \
+ return r; \
+ } \
+ \
+ if ((section_name = elf_strptr(elf_handle.get(), shdrstrndx, \
+ section_header->sh_name)) == NULL) { \
+ return r; \
+ } \
+ \
+ if (cstrings_eq(section_name, ".gnu_debuglink")) { \
+ elf_data = elf_getdata(elf_section, NULL); \
+ if (elf_data && elf_data->d_size > 0) { \
+ debuglink = \
+ std::string(reinterpret_cast<const char *>(elf_data->d_buf)); \
+ } \
+ } \
+ \
+ switch (section_header->sh_type) { \
+ case SHT_SYMTAB: \
+ symbol_section = elf_section; \
+ symbol_count = section_header->sh_size / section_header->sh_entsize; \
+ symbol_strings = section_header->sh_link; \
+ break; \
+ \
+ /* We use .dynsyms as a last resort, we prefer .symtab */ \
+ case SHT_DYNSYM: \
+ if (!symbol_section) { \
+ symbol_section = elf_section; \
+ symbol_count = section_header->sh_size / section_header->sh_entsize; \
+ symbol_strings = section_header->sh_link; \
+ } \
+ break; \
+ } \
+ } \
+ \
+ if (symbol_section && symbol_count && symbol_strings) { \
+ elf_data = elf_getdata(symbol_section, NULL); \
+ symbol = reinterpret_cast<Elf##ARCH##_Sym *>(elf_data->d_buf); \
+ for (size_t i = 0; i < symbol_count; ++i) { \
+ int type = ELF##ARCH##_ST_TYPE(symbol->st_info); \
+ if (type == STT_FUNC && symbol->st_value > 0) { \
+ r.symbol_cache[symbol->st_value] = std::string( \
+ elf_strptr(elf_handle.get(), symbol_strings, symbol->st_name)); \
+ } \
+ ++symbol; \
+ } \
+ }
+
+ if (e_ident[EI_CLASS] == ELFCLASS32) {
+ ELF_GET_DATA(32)
+ } else if (e_ident[EI_CLASS] == ELFCLASS64) {
+ // libelf might have been built without 64 bit support
+#if __LIBELF64
+ ELF_GET_DATA(64)
+#endif
+ }
+
+ if (!debuglink.empty()) {
+ // We have a debuglink section! Open an elf instance on that
+ // file instead. If we can't open the file, then return
+ // the elf handle we had already opened.
+ dwarf_file_t debuglink_file;
+ debuglink_file.reset(open(debuglink.c_str(), O_RDONLY));
+ if (debuglink_file.get() > 0) {
+ dwarf_elf_t debuglink_elf;
+ debuglink_elf.reset(elf_begin(debuglink_file.get(), ELF_C_READ, NULL));
+
+ // If we have a valid elf handle, return the new elf handle
+ // and file handle and discard the original ones
+ if (debuglink_elf) {
+ elf_handle = move(debuglink_elf);
+ file_handle = move(debuglink_file);
+ }
+ }
+ }
+
+ // Ok, we have a valid ELF handle, let's try to get debug symbols
+ Dwarf_Debug dwarf_debug;
+ Dwarf_Error error = DW_DLE_NE;
+ dwarf_handle_t dwarf_handle;
+
+ int dwarf_result = dwarf_elf_init(elf_handle.get(), DW_DLC_READ, NULL, NULL,
+ &dwarf_debug, &error);
+
+ // We don't do any special handling for DW_DLV_NO_ENTRY specially.
+ // If we get an error, or the file doesn't have debug information
+ // we just return.
+ if (dwarf_result != DW_DLV_OK) {
+ return r;
+ }
+
+ dwarf_handle.reset(dwarf_debug);
+
+ r.file_handle = move(file_handle);
+ r.elf_handle = move(elf_handle);
+ r.dwarf_handle = move(dwarf_handle);
+
+ return r;
+ }
+
+ die_cache_entry &get_die_cache(dwarf_fileobject &fobj, Dwarf_Die die) {
+ Dwarf_Error error = DW_DLE_NE;
+
+ // Get the die offset, we use it as the cache key
+ Dwarf_Off die_offset;
+ if (dwarf_dieoffset(die, &die_offset, &error) != DW_DLV_OK) {
+ die_offset = 0;
+ }
+
+ die_cache_t::iterator it = fobj.die_cache.find(die_offset);
+
+ if (it != fobj.die_cache.end()) {
+ fobj.current_cu = &it->second;
+ return it->second;
+ }
+
+ die_cache_entry &de = fobj.die_cache[die_offset];
+ fobj.current_cu = &de;
+
+ Dwarf_Addr line_addr;
+ Dwarf_Small table_count;
+
+ // The addresses in the line section are not fully sorted (they might
+ // be sorted by block of code belonging to the same file), which makes
+ // it necessary to do so before searching is possible.
+ //
+ // As libdwarf allocates a copy of everything, let's get the contents
+ // of the line section and keep it around. We also create a map of
+ // program counter to line table indices so we can search by address
+ // and get the line buffer index.
+ //
+ // To make things more difficult, the same address can span more than
+ // one line, so we need to keep the index pointing to the first line
+ // by using insert instead of the map's [ operator.
+
+ // Get the line context for the DIE
+ if (dwarf_srclines_b(die, 0, &table_count, &de.line_context, &error) ==
+ DW_DLV_OK) {
+ // Get the source lines for this line context, to be deallocated
+ // later
+ if (dwarf_srclines_from_linecontext(de.line_context, &de.line_buffer,
+ &de.line_count,
+ &error) == DW_DLV_OK) {
+
+ // Add all the addresses to our map
+ for (int i = 0; i < de.line_count; i++) {
+ if (dwarf_lineaddr(de.line_buffer[i], &line_addr, &error) !=
+ DW_DLV_OK) {
+ line_addr = 0;
+ }
+ de.line_section.insert(std::pair<Dwarf_Addr, int>(line_addr, i));
+ }
+ }
+ }
+
+ // For each CU, cache the function DIEs that contain the
+ // DW_AT_specification attribute. When building with -g3 the function
+ // DIEs are separated in declaration and specification, with the
+ // declaration containing only the name and parameters and the
+ // specification the low/high pc and other compiler attributes.
+ //
+ // We cache those specifications so we don't skip over the declarations,
+ // because they have no pc, and we can do namespace resolution for
+ // DWARF function names.
+ Dwarf_Debug dwarf = fobj.dwarf_handle.get();
+ Dwarf_Die current_die = 0;
+ if (dwarf_child(die, &current_die, &error) == DW_DLV_OK) {
+ for (;;) {
+ Dwarf_Die sibling_die = 0;
+
+ Dwarf_Half tag_value;
+ dwarf_tag(current_die, &tag_value, &error);
+
+ if (tag_value == DW_TAG_subprogram ||
+ tag_value == DW_TAG_inlined_subroutine) {
+
+ Dwarf_Bool has_attr = 0;
+ if (dwarf_hasattr(current_die, DW_AT_specification, &has_attr,
+ &error) == DW_DLV_OK) {
+ if (has_attr) {
+ Dwarf_Attribute attr_mem;
+ if (dwarf_attr(current_die, DW_AT_specification, &attr_mem,
+ &error) == DW_DLV_OK) {
+ Dwarf_Off spec_offset = 0;
+ if (dwarf_formref(attr_mem, &spec_offset, &error) ==
+ DW_DLV_OK) {
+ Dwarf_Off spec_die_offset;
+ if (dwarf_dieoffset(current_die, &spec_die_offset, &error) ==
+ DW_DLV_OK) {
+ de.spec_section[spec_offset] = spec_die_offset;
+ }
+ }
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+ }
+ }
+ }
+
+ int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error);
+ if (result == DW_DLV_ERROR) {
+ break;
+ } else if (result == DW_DLV_NO_ENTRY) {
+ break;
+ }
+
+ if (current_die != die) {
+ dwarf_dealloc(dwarf, current_die, DW_DLA_DIE);
+ current_die = 0;
+ }
+
+ current_die = sibling_die;
+ }
+ }
+ return de;
+ }
+
+ static Dwarf_Die get_referenced_die(Dwarf_Debug dwarf, Dwarf_Die die,
+ Dwarf_Half attr, bool global) {
+ Dwarf_Error error = DW_DLE_NE;
+ Dwarf_Attribute attr_mem;
+
+ Dwarf_Die found_die = NULL;
+ if (dwarf_attr(die, attr, &attr_mem, &error) == DW_DLV_OK) {
+ Dwarf_Off offset;
+ int result = 0;
+ if (global) {
+ result = dwarf_global_formref(attr_mem, &offset, &error);
+ } else {
+ result = dwarf_formref(attr_mem, &offset, &error);
+ }
+
+ if (result == DW_DLV_OK) {
+ if (dwarf_offdie(dwarf, offset, &found_die, &error) != DW_DLV_OK) {
+ found_die = NULL;
+ }
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+ }
+ return found_die;
+ }
+
+ static std::string get_referenced_die_name(Dwarf_Debug dwarf, Dwarf_Die die,
+ Dwarf_Half attr, bool global) {
+ Dwarf_Error error = DW_DLE_NE;
+ std::string value;
+
+ Dwarf_Die found_die = get_referenced_die(dwarf, die, attr, global);
+
+ if (found_die) {
+ char *name;
+ if (dwarf_diename(found_die, &name, &error) == DW_DLV_OK) {
+ if (name) {
+ value = std::string(name);
+ }
+ dwarf_dealloc(dwarf, name, DW_DLA_STRING);
+ }
+ dwarf_dealloc(dwarf, found_die, DW_DLA_DIE);
+ }
+
+ return value;
+ }
+
+ // Returns a spec DIE linked to the passed one. The caller should
+ // deallocate the DIE
+ static Dwarf_Die get_spec_die(dwarf_fileobject &fobj, Dwarf_Die die) {
+ Dwarf_Debug dwarf = fobj.dwarf_handle.get();
+ Dwarf_Error error = DW_DLE_NE;
+ Dwarf_Off die_offset;
+ if (fobj.current_cu &&
+ dwarf_die_CU_offset(die, &die_offset, &error) == DW_DLV_OK) {
+ die_specmap_t::iterator it =
+ fobj.current_cu->spec_section.find(die_offset);
+
+ // If we have a DIE that completes the current one, check if
+ // that one has the pc we are looking for
+ if (it != fobj.current_cu->spec_section.end()) {
+ Dwarf_Die spec_die = 0;
+ if (dwarf_offdie(dwarf, it->second, &spec_die, &error) == DW_DLV_OK) {
+ return spec_die;
+ }
+ }
+ }
+
+ // Maybe we have an abstract origin DIE with the function information?
+ return get_referenced_die(fobj.dwarf_handle.get(), die,
+ DW_AT_abstract_origin, true);
+ }
+
+ static bool die_has_pc(dwarf_fileobject &fobj, Dwarf_Die die, Dwarf_Addr pc) {
+ Dwarf_Addr low_pc = 0, high_pc = 0;
+ Dwarf_Half high_pc_form = 0;
+ Dwarf_Form_Class return_class;
+ Dwarf_Error error = DW_DLE_NE;
+ Dwarf_Debug dwarf = fobj.dwarf_handle.get();
+ bool has_lowpc = false;
+ bool has_highpc = false;
+ bool has_ranges = false;
+
+ if (dwarf_lowpc(die, &low_pc, &error) == DW_DLV_OK) {
+ // If we have a low_pc check if there is a high pc.
+ // If we don't have a high pc this might mean we have a base
+ // address for the ranges list or just an address.
+ has_lowpc = true;
+
+ if (dwarf_highpc_b(die, &high_pc, &high_pc_form, &return_class, &error) ==
+ DW_DLV_OK) {
+ // We do have a high pc. In DWARF 4+ this is an offset from the
+ // low pc, but in earlier versions it's an absolute address.
+
+ has_highpc = true;
+ // In DWARF 2/3 this would be a DW_FORM_CLASS_ADDRESS
+ if (return_class == DW_FORM_CLASS_CONSTANT) {
+ high_pc = low_pc + high_pc;
+ }
+
+ // We have low and high pc, check if our address
+ // is in that range
+ return pc >= low_pc && pc < high_pc;
+ }
+ } else {
+ // Reset the low_pc, in case dwarf_lowpc failing set it to some
+ // undefined value.
+ low_pc = 0;
+ }
+
+ // Check if DW_AT_ranges is present and search for the PC in the
+ // returned ranges list. We always add the low_pc, as it not set it will
+ // be 0, in case we had a DW_AT_low_pc and DW_AT_ranges pair
+ bool result = false;
+
+ Dwarf_Attribute attr;
+ if (dwarf_attr(die, DW_AT_ranges, &attr, &error) == DW_DLV_OK) {
+
+ Dwarf_Off offset;
+ if (dwarf_global_formref(attr, &offset, &error) == DW_DLV_OK) {
+ Dwarf_Ranges *ranges;
+ Dwarf_Signed ranges_count = 0;
+ Dwarf_Unsigned byte_count = 0;
+
+ if (dwarf_get_ranges_a(dwarf, offset, die, &ranges, &ranges_count,
+ &byte_count, &error) == DW_DLV_OK) {
+ has_ranges = ranges_count != 0;
+ for (int i = 0; i < ranges_count; i++) {
+ if (ranges[i].dwr_addr1 != 0 &&
+ pc >= ranges[i].dwr_addr1 + low_pc &&
+ pc < ranges[i].dwr_addr2 + low_pc) {
+ result = true;
+ break;
+ }
+ }
+ dwarf_ranges_dealloc(dwarf, ranges, ranges_count);
+ }
+ }
+ }
+
+ // Last attempt. We might have a single address set as low_pc.
+ if (!result && low_pc != 0 && pc == low_pc) {
+ result = true;
+ }
+
+ // If we don't have lowpc, highpc and ranges maybe this DIE is a
+ // declaration that relies on a DW_AT_specification DIE that happens
+ // later. Use the specification cache we filled when we loaded this CU.
+ if (!result && (!has_lowpc && !has_highpc && !has_ranges)) {
+ Dwarf_Die spec_die = get_spec_die(fobj, die);
+ if (spec_die) {
+ result = die_has_pc(fobj, spec_die, pc);
+ dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE);
+ }
+ }
+
+ return result;
+ }
+
+ static void get_type(Dwarf_Debug dwarf, Dwarf_Die die, std::string &type) {
+ Dwarf_Error error = DW_DLE_NE;
+
+ Dwarf_Die child = 0;
+ if (dwarf_child(die, &child, &error) == DW_DLV_OK) {
+ get_type(dwarf, child, type);
+ }
+
+ if (child) {
+ type.insert(0, "::");
+ dwarf_dealloc(dwarf, child, DW_DLA_DIE);
+ }
+
+ char *name;
+ if (dwarf_diename(die, &name, &error) == DW_DLV_OK) {
+ type.insert(0, std::string(name));
+ dwarf_dealloc(dwarf, name, DW_DLA_STRING);
+ } else {
+ type.insert(0, "<unknown>");
+ }
+ }
+
+ static std::string get_type_by_signature(Dwarf_Debug dwarf, Dwarf_Die die) {
+ Dwarf_Error error = DW_DLE_NE;
+
+ Dwarf_Sig8 signature;
+ Dwarf_Bool has_attr = 0;
+ if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) == DW_DLV_OK) {
+ if (has_attr) {
+ Dwarf_Attribute attr_mem;
+ if (dwarf_attr(die, DW_AT_signature, &attr_mem, &error) == DW_DLV_OK) {
+ if (dwarf_formsig8(attr_mem, &signature, &error) != DW_DLV_OK) {
+ return std::string("<no type signature>");
+ }
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+ }
+ }
+
+ Dwarf_Unsigned next_cu_header;
+ Dwarf_Sig8 tu_signature;
+ std::string result;
+ bool found = false;
+
+ while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, &tu_signature, 0,
+ &next_cu_header, 0, &error) == DW_DLV_OK) {
+
+ if (strncmp(signature.signature, tu_signature.signature, 8) == 0) {
+ Dwarf_Die type_cu_die = 0;
+ if (dwarf_siblingof_b(dwarf, 0, 0, &type_cu_die, &error) == DW_DLV_OK) {
+ Dwarf_Die child_die = 0;
+ if (dwarf_child(type_cu_die, &child_die, &error) == DW_DLV_OK) {
+ get_type(dwarf, child_die, result);
+ found = !result.empty();
+ dwarf_dealloc(dwarf, child_die, DW_DLA_DIE);
+ }
+ dwarf_dealloc(dwarf, type_cu_die, DW_DLA_DIE);
+ }
+ }
+ }
+
+ if (found) {
+ while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ &next_cu_header, 0, &error) == DW_DLV_OK) {
+ // Reset the cu header state. Unfortunately, libdwarf's
+ // next_cu_header API keeps its own iterator per Dwarf_Debug
+ // that can't be reset. We need to keep fetching elements until
+ // the end.
+ }
+ } else {
+ // If we couldn't resolve the type just print out the signature
+ std::ostringstream string_stream;
+ string_stream << "<0x" << std::hex << std::setfill('0');
+ for (int i = 0; i < 8; ++i) {
+ string_stream << std::setw(2) << std::hex
+ << (int)(unsigned char)(signature.signature[i]);
+ }
+ string_stream << ">";
+ result = string_stream.str();
+ }
+ return result;
+ }
+
+ struct type_context_t {
+ bool is_const;
+ bool is_typedef;
+ bool has_type;
+ bool has_name;
+ std::string text;
+
+ type_context_t()
+ : is_const(false), is_typedef(false), has_type(false), has_name(false) {
+ }
+ };
+
+ // Types are resolved from right to left: we get the variable name first
+ // and then all specifiers (like const or pointer) in a chain of DW_AT_type
+ // DIEs. Call this function recursively until we get a complete type
+ // string.
+ static void set_parameter_string(dwarf_fileobject &fobj, Dwarf_Die die,
+ type_context_t &context) {
+ char *name;
+ Dwarf_Error error = DW_DLE_NE;
+
+ // typedefs contain also the base type, so we skip it and only
+ // print the typedef name
+ if (!context.is_typedef) {
+ if (dwarf_diename(die, &name, &error) == DW_DLV_OK) {
+ if (!context.text.empty()) {
+ context.text.insert(0, " ");
+ }
+ context.text.insert(0, std::string(name));
+ dwarf_dealloc(fobj.dwarf_handle.get(), name, DW_DLA_STRING);
+ }
+ } else {
+ context.is_typedef = false;
+ context.has_type = true;
+ if (context.is_const) {
+ context.text.insert(0, "const ");
+ context.is_const = false;
+ }
+ }
+
+ bool next_type_is_const = false;
+ bool is_keyword = true;
+
+ Dwarf_Half tag = 0;
+ Dwarf_Bool has_attr = 0;
+ if (dwarf_tag(die, &tag, &error) == DW_DLV_OK) {
+ switch (tag) {
+ case DW_TAG_structure_type:
+ case DW_TAG_union_type:
+ case DW_TAG_class_type:
+ case DW_TAG_enumeration_type:
+ context.has_type = true;
+ if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) ==
+ DW_DLV_OK) {
+ // If we have a signature it means the type is defined
+ // in .debug_types, so we need to load the DIE pointed
+ // at by the signature and resolve it
+ if (has_attr) {
+ std::string type =
+ get_type_by_signature(fobj.dwarf_handle.get(), die);
+ if (context.is_const)
+ type.insert(0, "const ");
+
+ if (!context.text.empty())
+ context.text.insert(0, " ");
+ context.text.insert(0, type);
+ }
+
+ // Treat enums like typedefs, and skip printing its
+ // base type
+ context.is_typedef = (tag == DW_TAG_enumeration_type);
+ }
+ break;
+ case DW_TAG_const_type:
+ next_type_is_const = true;
+ break;
+ case DW_TAG_pointer_type:
+ context.text.insert(0, "*");
+ break;
+ case DW_TAG_reference_type:
+ context.text.insert(0, "&");
+ break;
+ case DW_TAG_restrict_type:
+ context.text.insert(0, "restrict ");
+ break;
+ case DW_TAG_rvalue_reference_type:
+ context.text.insert(0, "&&");
+ break;
+ case DW_TAG_volatile_type:
+ context.text.insert(0, "volatile ");
+ break;
+ case DW_TAG_typedef:
+ // Propagate the const-ness to the next type
+ // as typedefs are linked to its base type
+ next_type_is_const = context.is_const;
+ context.is_typedef = true;
+ context.has_type = true;
+ break;
+ case DW_TAG_base_type:
+ context.has_type = true;
+ break;
+ case DW_TAG_formal_parameter:
+ context.has_name = true;
+ break;
+ default:
+ is_keyword = false;
+ break;
+ }
+ }
+
+ if (!is_keyword && context.is_const) {
+ context.text.insert(0, "const ");
+ }
+
+ context.is_const = next_type_is_const;
+
+ Dwarf_Die ref =
+ get_referenced_die(fobj.dwarf_handle.get(), die, DW_AT_type, true);
+ if (ref) {
+ set_parameter_string(fobj, ref, context);
+ dwarf_dealloc(fobj.dwarf_handle.get(), ref, DW_DLA_DIE);
+ }
+
+ if (!context.has_type && context.has_name) {
+ context.text.insert(0, "void ");
+ context.has_type = true;
+ }
+ }
+
+ // Resolve the function return type and parameters
+ static void set_function_parameters(std::string &function_name,
+ std::vector<std::string> &ns,
+ dwarf_fileobject &fobj, Dwarf_Die die) {
+ Dwarf_Debug dwarf = fobj.dwarf_handle.get();
+ Dwarf_Error error = DW_DLE_NE;
+ Dwarf_Die current_die = 0;
+ std::string parameters;
+ bool has_spec = true;
+ // Check if we have a spec DIE. If we do we use it as it contains
+ // more information, like parameter names.
+ Dwarf_Die spec_die = get_spec_die(fobj, die);
+ if (!spec_die) {
+ has_spec = false;
+ spec_die = die;
+ }
+
+ std::vector<std::string>::const_iterator it = ns.begin();
+ std::string ns_name;
+ for (it = ns.begin(); it < ns.end(); ++it) {
+ ns_name.append(*it).append("::");
+ }
+
+ if (!ns_name.empty()) {
+ function_name.insert(0, ns_name);
+ }
+
+ // See if we have a function return type. It can be either on the
+ // current die or in its spec one (usually true for inlined functions)
+ std::string return_type =
+ get_referenced_die_name(dwarf, die, DW_AT_type, true);
+ if (return_type.empty()) {
+ return_type = get_referenced_die_name(dwarf, spec_die, DW_AT_type, true);
+ }
+ if (!return_type.empty()) {
+ return_type.append(" ");
+ function_name.insert(0, return_type);
+ }
+
+ if (dwarf_child(spec_die, &current_die, &error) == DW_DLV_OK) {
+ for (;;) {
+ Dwarf_Die sibling_die = 0;
+
+ Dwarf_Half tag_value;
+ dwarf_tag(current_die, &tag_value, &error);
+
+ if (tag_value == DW_TAG_formal_parameter) {
+ // Ignore artificial (ie, compiler generated) parameters
+ bool is_artificial = false;
+ Dwarf_Attribute attr_mem;
+ if (dwarf_attr(current_die, DW_AT_artificial, &attr_mem, &error) ==
+ DW_DLV_OK) {
+ Dwarf_Bool flag = 0;
+ if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) {
+ is_artificial = flag != 0;
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+ }
+
+ if (!is_artificial) {
+ type_context_t context;
+ set_parameter_string(fobj, current_die, context);
+
+ if (parameters.empty()) {
+ parameters.append("(");
+ } else {
+ parameters.append(", ");
+ }
+ parameters.append(context.text);
+ }
+ }
+
+ int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error);
+ if (result == DW_DLV_ERROR) {
+ break;
+ } else if (result == DW_DLV_NO_ENTRY) {
+ break;
+ }
+
+ if (current_die != die) {
+ dwarf_dealloc(dwarf, current_die, DW_DLA_DIE);
+ current_die = 0;
+ }
+
+ current_die = sibling_die;
+ }
+ }
+ if (parameters.empty())
+ parameters = "(";
+ parameters.append(")");
+
+ // If we got a spec DIE we need to deallocate it
+ if (has_spec)
+ dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE);
+
+ function_name.append(parameters);
+ }
+
+ // defined here because in C++98, template function cannot take locally
+ // defined types... grrr.
+ struct inliners_search_cb {
+ void operator()(Dwarf_Die die, std::vector<std::string> &ns) {
+ Dwarf_Error error = DW_DLE_NE;
+ Dwarf_Half tag_value;
+ Dwarf_Attribute attr_mem;
+ Dwarf_Debug dwarf = fobj.dwarf_handle.get();
+
+ dwarf_tag(die, &tag_value, &error);
+
+ switch (tag_value) {
+ char *name;
+ case DW_TAG_subprogram:
+ if (!trace.source.function.empty())
+ break;
+ if (dwarf_diename(die, &name, &error) == DW_DLV_OK) {
+ trace.source.function = std::string(name);
+ dwarf_dealloc(dwarf, name, DW_DLA_STRING);
+ } else {
+ // We don't have a function name in this DIE.
+ // Check if there is a referenced non-defining
+ // declaration.
+ trace.source.function =
+ get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true);
+ if (trace.source.function.empty()) {
+ trace.source.function =
+ get_referenced_die_name(dwarf, die, DW_AT_specification, true);
+ }
+ }
+
+ // Append the function parameters, if available
+ set_function_parameters(trace.source.function, ns, fobj, die);
+
+ // If the object function name is empty, it's possible that
+ // there is no dynamic symbol table (maybe the executable
+ // was stripped or not built with -rdynamic). See if we have
+ // a DWARF linkage name to use instead. We try both
+ // linkage_name and MIPS_linkage_name because the MIPS tag
+ // was the unofficial one until it was adopted in DWARF4.
+ // Old gcc versions generate MIPS_linkage_name
+ if (trace.object_function.empty()) {
+ details::demangler demangler;
+
+ if (dwarf_attr(die, DW_AT_linkage_name, &attr_mem, &error) !=
+ DW_DLV_OK) {
+ if (dwarf_attr(die, DW_AT_MIPS_linkage_name, &attr_mem, &error) !=
+ DW_DLV_OK) {
+ break;
+ }
+ }
+
+ char *linkage;
+ if (dwarf_formstring(attr_mem, &linkage, &error) == DW_DLV_OK) {
+ trace.object_function = demangler.demangle(linkage);
+ dwarf_dealloc(dwarf, linkage, DW_DLA_STRING);
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+ }
+ break;
+
+ case DW_TAG_inlined_subroutine:
+ ResolvedTrace::SourceLoc sloc;
+
+ if (dwarf_diename(die, &name, &error) == DW_DLV_OK) {
+ sloc.function = std::string(name);
+ dwarf_dealloc(dwarf, name, DW_DLA_STRING);
+ } else {
+ // We don't have a name for this inlined DIE, it could
+ // be that there is an abstract origin instead.
+ // Get the DW_AT_abstract_origin value, which is a
+ // reference to the source DIE and try to get its name
+ sloc.function =
+ get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true);
+ }
+
+ set_function_parameters(sloc.function, ns, fobj, die);
+
+ std::string file = die_call_file(dwarf, die, cu_die);
+ if (!file.empty())
+ sloc.filename = file;
+
+ Dwarf_Unsigned number = 0;
+ if (dwarf_attr(die, DW_AT_call_line, &attr_mem, &error) == DW_DLV_OK) {
+ if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) {
+ sloc.line = number;
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+ }
+
+ if (dwarf_attr(die, DW_AT_call_column, &attr_mem, &error) ==
+ DW_DLV_OK) {
+ if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) {
+ sloc.col = number;
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+ }
+
+ trace.inliners.push_back(sloc);
+ break;
+ };
+ }
+ ResolvedTrace &trace;
+ dwarf_fileobject &fobj;
+ Dwarf_Die cu_die;
+ inliners_search_cb(ResolvedTrace &t, dwarf_fileobject &f, Dwarf_Die c)
+ : trace(t), fobj(f), cu_die(c) {}
+ };
+
+ static Dwarf_Die find_fundie_by_pc(dwarf_fileobject &fobj,
+ Dwarf_Die parent_die, Dwarf_Addr pc,
+ Dwarf_Die result) {
+ Dwarf_Die current_die = 0;
+ Dwarf_Error error = DW_DLE_NE;
+ Dwarf_Debug dwarf = fobj.dwarf_handle.get();
+
+ if (dwarf_child(parent_die, &current_die, &error) != DW_DLV_OK) {
+ return NULL;
+ }
+
+ for (;;) {
+ Dwarf_Die sibling_die = 0;
+ Dwarf_Half tag_value;
+ dwarf_tag(current_die, &tag_value, &error);
+
+ switch (tag_value) {
+ case DW_TAG_subprogram:
+ case DW_TAG_inlined_subroutine:
+ if (die_has_pc(fobj, current_die, pc)) {
+ return current_die;
+ }
+ };
+ bool declaration = false;
+ Dwarf_Attribute attr_mem;
+ if (dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) ==
+ DW_DLV_OK) {
+ Dwarf_Bool flag = 0;
+ if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) {
+ declaration = flag != 0;
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+ }
+
+ if (!declaration) {
+ // let's be curious and look deeper in the tree, functions are
+ // not necessarily at the first level, but might be nested
+ // inside a namespace, structure, a function, an inlined
+ // function etc.
+ Dwarf_Die die_mem = 0;
+ Dwarf_Die indie = find_fundie_by_pc(fobj, current_die, pc, die_mem);
+ if (indie) {
+ result = die_mem;
+ return result;
+ }
+ }
+
+ int res = dwarf_siblingof(dwarf, current_die, &sibling_die, &error);
+ if (res == DW_DLV_ERROR) {
+ return NULL;
+ } else if (res == DW_DLV_NO_ENTRY) {
+ break;
+ }
+
+ if (current_die != parent_die) {
+ dwarf_dealloc(dwarf, current_die, DW_DLA_DIE);
+ current_die = 0;
+ }
+
+ current_die = sibling_die;
+ }
+ return NULL;
+ }
+
+ template <typename CB>
+ static bool deep_first_search_by_pc(dwarf_fileobject &fobj,
+ Dwarf_Die parent_die, Dwarf_Addr pc,
+ std::vector<std::string> &ns, CB cb) {
+ Dwarf_Die current_die = 0;
+ Dwarf_Debug dwarf = fobj.dwarf_handle.get();
+ Dwarf_Error error = DW_DLE_NE;
+
+ if (dwarf_child(parent_die, &current_die, &error) != DW_DLV_OK) {
+ return false;
+ }
+
+ bool branch_has_pc = false;
+ bool has_namespace = false;
+ for (;;) {
+ Dwarf_Die sibling_die = 0;
+
+ Dwarf_Half tag;
+ if (dwarf_tag(current_die, &tag, &error) == DW_DLV_OK) {
+ if (tag == DW_TAG_namespace || tag == DW_TAG_class_type) {
+ char *ns_name = NULL;
+ if (dwarf_diename(current_die, &ns_name, &error) == DW_DLV_OK) {
+ if (ns_name) {
+ ns.push_back(std::string(ns_name));
+ } else {
+ ns.push_back("<unknown>");
+ }
+ dwarf_dealloc(dwarf, ns_name, DW_DLA_STRING);
+ } else {
+ ns.push_back("<unknown>");
+ }
+ has_namespace = true;
+ }
+ }
+
+ bool declaration = false;
+ Dwarf_Attribute attr_mem;
+ if (tag != DW_TAG_class_type &&
+ dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) ==
+ DW_DLV_OK) {
+ Dwarf_Bool flag = 0;
+ if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) {
+ declaration = flag != 0;
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+ }
+
+ if (!declaration) {
+ // let's be curious and look deeper in the tree, function are
+ // not necessarily at the first level, but might be nested
+ // inside a namespace, structure, a function, an inlined
+ // function etc.
+ branch_has_pc = deep_first_search_by_pc(fobj, current_die, pc, ns, cb);
+ }
+
+ if (!branch_has_pc) {
+ branch_has_pc = die_has_pc(fobj, current_die, pc);
+ }
+
+ if (branch_has_pc) {
+ cb(current_die, ns);
+ }
+
+ int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error);
+ if (result == DW_DLV_ERROR) {
+ return false;
+ } else if (result == DW_DLV_NO_ENTRY) {
+ break;
+ }
+
+ if (current_die != parent_die) {
+ dwarf_dealloc(dwarf, current_die, DW_DLA_DIE);
+ current_die = 0;
+ }
+
+ if (has_namespace) {
+ has_namespace = false;
+ ns.pop_back();
+ }
+ current_die = sibling_die;
+ }
+
+ if (has_namespace) {
+ ns.pop_back();
+ }
+ return branch_has_pc;
+ }
+
+ static std::string die_call_file(Dwarf_Debug dwarf, Dwarf_Die die,
+ Dwarf_Die cu_die) {
+ Dwarf_Attribute attr_mem;
+ Dwarf_Error error = DW_DLE_NE;
+ Dwarf_Unsigned file_index;
+
+ std::string file;
+
+ if (dwarf_attr(die, DW_AT_call_file, &attr_mem, &error) == DW_DLV_OK) {
+ if (dwarf_formudata(attr_mem, &file_index, &error) != DW_DLV_OK) {
+ file_index = 0;
+ }
+ dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR);
+
+ if (file_index == 0) {
+ return file;
+ }
+
+ char **srcfiles = 0;
+ Dwarf_Signed file_count = 0;
+ if (dwarf_srcfiles(cu_die, &srcfiles, &file_count, &error) == DW_DLV_OK) {
+ if (file_count > 0 && file_index <= static_cast<Dwarf_Unsigned>(file_count)) {
+ file = std::string(srcfiles[file_index - 1]);
+ }
+
+ // Deallocate all strings!
+ for (int i = 0; i < file_count; ++i) {
+ dwarf_dealloc(dwarf, srcfiles[i], DW_DLA_STRING);
+ }
+ dwarf_dealloc(dwarf, srcfiles, DW_DLA_LIST);
+ }
+ }
+ return file;
+ }
+
+ Dwarf_Die find_die(dwarf_fileobject &fobj, Dwarf_Addr addr) {
+ // Let's get to work! First see if we have a debug_aranges section so
+ // we can speed up the search
+
+ Dwarf_Debug dwarf = fobj.dwarf_handle.get();
+ Dwarf_Error error = DW_DLE_NE;
+ Dwarf_Arange *aranges;
+ Dwarf_Signed arange_count;
+
+ Dwarf_Die returnDie;
+ bool found = false;
+ if (dwarf_get_aranges(dwarf, &aranges, &arange_count, &error) !=
+ DW_DLV_OK) {
+ aranges = NULL;
+ }
+
+ if (aranges) {
+ // We have aranges. Get the one where our address is.
+ Dwarf_Arange arange;
+ if (dwarf_get_arange(aranges, arange_count, addr, &arange, &error) ==
+ DW_DLV_OK) {
+
+ // We found our address. Get the compilation-unit DIE offset
+ // represented by the given address range.
+ Dwarf_Off cu_die_offset;
+ if (dwarf_get_cu_die_offset(arange, &cu_die_offset, &error) ==
+ DW_DLV_OK) {
+ // Get the DIE at the offset returned by the aranges search.
+ // We set is_info to 1 to specify that the offset is from
+ // the .debug_info section (and not .debug_types)
+ int dwarf_result =
+ dwarf_offdie_b(dwarf, cu_die_offset, 1, &returnDie, &error);
+
+ found = dwarf_result == DW_DLV_OK;
+ }
+ dwarf_dealloc(dwarf, arange, DW_DLA_ARANGE);
+ }
+ }
+
+ if (found)
+ return returnDie; // The caller is responsible for freeing the die
+
+ // The search for aranges failed. Try to find our address by scanning
+ // all compilation units.
+ Dwarf_Unsigned next_cu_header;
+ Dwarf_Half tag = 0;
+ returnDie = 0;
+
+ while (!found &&
+ dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ &next_cu_header, 0, &error) == DW_DLV_OK) {
+
+ if (returnDie)
+ dwarf_dealloc(dwarf, returnDie, DW_DLA_DIE);
+
+ if (dwarf_siblingof(dwarf, 0, &returnDie, &error) == DW_DLV_OK) {
+ if ((dwarf_tag(returnDie, &tag, &error) == DW_DLV_OK) &&
+ tag == DW_TAG_compile_unit) {
+ if (die_has_pc(fobj, returnDie, addr)) {
+ found = true;
+ }
+ }
+ }
+ }
+
+ if (found) {
+ while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ &next_cu_header, 0, &error) == DW_DLV_OK) {
+ // Reset the cu header state. Libdwarf's next_cu_header API
+ // keeps its own iterator per Dwarf_Debug that can't be reset.
+ // We need to keep fetching elements until the end.
+ }
+ }
+
+ if (found)
+ return returnDie;
+
+ // We couldn't find any compilation units with ranges or a high/low pc.
+ // Try again by looking at all DIEs in all compilation units.
+ Dwarf_Die cudie;
+ while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ &next_cu_header, 0, &error) == DW_DLV_OK) {
+ if (dwarf_siblingof(dwarf, 0, &cudie, &error) == DW_DLV_OK) {
+ Dwarf_Die die_mem = 0;
+ Dwarf_Die resultDie = find_fundie_by_pc(fobj, cudie, addr, die_mem);
+
+ if (resultDie) {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (found) {
+ while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ &next_cu_header, 0, &error) == DW_DLV_OK) {
+ // Reset the cu header state. Libdwarf's next_cu_header API
+ // keeps its own iterator per Dwarf_Debug that can't be reset.
+ // We need to keep fetching elements until the end.
+ }
+ }
+
+ if (found)
+ return cudie;
+
+ // We failed.
+ return NULL;
+ }
+};
+#endif // BACKWARD_HAS_DWARF == 1
+
+template <>
+class TraceResolverImpl<system_tag::linux_tag>
+ : public TraceResolverLinuxImpl<trace_resolver_tag::current> {};
+
+#endif // BACKWARD_SYSTEM_LINUX
+
+#ifdef BACKWARD_SYSTEM_DARWIN
+
+template <typename STACKTRACE_TAG> class TraceResolverDarwinImpl;
+
+template <>
+class TraceResolverDarwinImpl<trace_resolver_tag::backtrace_symbol>
+ : public TraceResolverImplBase {
+public:
+ void load_addresses(void *const*addresses, int address_count) override {
+ if (address_count == 0) {
+ return;
+ }
+ _symbols.reset(backtrace_symbols(addresses, address_count));
+ }
+
+ ResolvedTrace resolve(ResolvedTrace trace) override {
+ // parse:
+ // <n> <file> <addr> <mangled-name> + <offset>
+ char *filename = _symbols[trace.idx];
+
+ // skip "<n> "
+ while (*filename && *filename != ' ')
+ filename++;
+ while (*filename == ' ')
+ filename++;
+
+ // find start of <mangled-name> from end (<file> may contain a space)
+ char *p = filename + strlen(filename) - 1;
+ // skip to start of " + <offset>"
+ while (p > filename && *p != ' ')
+ p--;
+ while (p > filename && *p == ' ')
+ p--;
+ while (p > filename && *p != ' ')
+ p--;
+ while (p > filename && *p == ' ')
+ p--;
+ char *funcname_end = p + 1;
+
+ // skip to start of "<manged-name>"
+ while (p > filename && *p != ' ')
+ p--;
+ char *funcname = p + 1;
+
+ // skip to start of " <addr> "
+ while (p > filename && *p == ' ')
+ p--;
+ while (p > filename && *p != ' ')
+ p--;
+ while (p > filename && *p == ' ')
+ p--;
+
+ // skip "<file>", handling the case where it contains a
+ char *filename_end = p + 1;
+ if (p == filename) {
+ // something went wrong, give up
+ filename_end = filename + strlen(filename);
+ funcname = filename_end;
+ }
+ trace.object_filename.assign(
+ filename, filename_end); // ok even if filename_end is the ending \0
+ // (then we assign entire string)
+
+ if (*funcname) { // if it's not end of string
+ *funcname_end = '\0';
+
+ trace.object_function = this->demangle(funcname);
+ trace.object_function += " ";
+ trace.object_function += (funcname_end + 1);
+ trace.source.function = trace.object_function; // we cannot do better.
+ }
+ return trace;
+ }
+
+private:
+ details::handle<char **> _symbols;
+};
+
+template <>
+class TraceResolverImpl<system_tag::darwin_tag>
+ : public TraceResolverDarwinImpl<trace_resolver_tag::current> {};
+
+#endif // BACKWARD_SYSTEM_DARWIN
+
+#ifdef BACKWARD_SYSTEM_WINDOWS
+
+// Load all symbol info
+// Based on:
+// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227
+
+struct module_data {
+ std::string image_name;
+ std::string module_name;
+ void *base_address;
+ DWORD load_size;
+};
+
+class get_mod_info {
+ HANDLE process;
+ static const int buffer_length = 4096;
+
+public:
+ get_mod_info(HANDLE h) : process(h) {}
+
+ module_data operator()(HMODULE module) {
+ module_data ret;
+ char temp[buffer_length];
+ MODULEINFO mi;
+
+ GetModuleInformation(process, module, &mi, sizeof(mi));
+ ret.base_address = mi.lpBaseOfDll;
+ ret.load_size = mi.SizeOfImage;
+
+ GetModuleFileNameExA(process, module, temp, sizeof(temp));
+ ret.image_name = temp;
+ GetModuleBaseNameA(process, module, temp, sizeof(temp));
+ ret.module_name = temp;
+ std::vector<char> img(ret.image_name.begin(), ret.image_name.end());
+ std::vector<char> mod(ret.module_name.begin(), ret.module_name.end());
+ SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address,
+ ret.load_size);
+ return ret;
+ }
+};
+
+template <> class TraceResolverImpl<system_tag::windows_tag>
+ : public TraceResolverImplBase {
+public:
+ TraceResolverImpl() {
+
+ HANDLE process = GetCurrentProcess();
+
+ std::vector<module_data> modules;
+ DWORD cbNeeded;
+ std::vector<HMODULE> module_handles(1);
+ SymInitialize(process, NULL, false);
+ DWORD symOptions = SymGetOptions();
+ symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME;
+ SymSetOptions(symOptions);
+ EnumProcessModules(process, &module_handles[0],
+ module_handles.size() * sizeof(HMODULE), &cbNeeded);
+ module_handles.resize(cbNeeded / sizeof(HMODULE));
+ EnumProcessModules(process, &module_handles[0],
+ module_handles.size() * sizeof(HMODULE), &cbNeeded);
+ std::transform(module_handles.begin(), module_handles.end(),
+ std::back_inserter(modules), get_mod_info(process));
+ void *base = modules[0].base_address;
+ IMAGE_NT_HEADERS *h = ImageNtHeader(base);
+ image_type = h->FileHeader.Machine;
+ }
+
+ static const int max_sym_len = 255;
+ struct symbol_t {
+ SYMBOL_INFO sym;
+ char buffer[max_sym_len];
+ } sym;
+
+ DWORD64 displacement;
+
+ ResolvedTrace resolve(ResolvedTrace t) override {
+ HANDLE process = GetCurrentProcess();
+
+ char name[256];
+
+ memset(&sym, 0, sizeof(sym));
+ sym.sym.SizeOfStruct = sizeof(SYMBOL_INFO);
+ sym.sym.MaxNameLen = max_sym_len;
+
+ if (!SymFromAddr(process, (ULONG64)t.addr, &displacement, &sym.sym)) {
+ // TODO: error handling everywhere
+ char* lpMsgBuf;
+ DWORD dw = GetLastError();
+
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (char*)&lpMsgBuf, 0, NULL);
+
+ printf(lpMsgBuf);
+
+ // abort();
+ }
+ UnDecorateSymbolName(sym.sym.Name, (PSTR)name, 256, UNDNAME_COMPLETE);
+
+ DWORD offset = 0;
+ IMAGEHLP_LINE line;
+ if (SymGetLineFromAddr(process, (ULONG64)t.addr, &offset, &line)) {
+ t.object_filename = line.FileName;
+ t.source.filename = line.FileName;
+ t.source.line = line.LineNumber;
+ t.source.col = offset;
+ }
+
+ t.source.function = name;
+ t.object_filename = "";
+ t.object_function = name;
+
+ return t;
+ }
+
+ DWORD machine_type() const { return image_type; }
+
+private:
+ DWORD image_type;
+};
+
+#endif
+
+class TraceResolver : public TraceResolverImpl<system_tag::current_tag> {};
+
+/*************** CODE SNIPPET ***************/
+
+class SourceFile {
+public:
+ typedef std::vector<std::pair<unsigned, std::string>> lines_t;
+
+ SourceFile() {}
+ SourceFile(const std::string &path) {
+ // 1. If BACKWARD_CXX_SOURCE_PREFIXES is set then assume it contains
+ // a colon-separated list of path prefixes. Try prepending each
+ // to the given path until a valid file is found.
+ const std::vector<std::string> &prefixes = get_paths_from_env_variable();
+ for (size_t i = 0; i < prefixes.size(); ++i) {
+ // Double slashes (//) should not be a problem.
+ std::string new_path = prefixes[i] + '/' + path;
+ _file.reset(new std::ifstream(new_path.c_str()));
+ if (is_open())
+ break;
+ }
+ // 2. If no valid file found then fallback to opening the path as-is.
+ if (!_file || !is_open()) {
+ _file.reset(new std::ifstream(path.c_str()));
+ }
+ }
+ bool is_open() const { return _file->is_open(); }
+
+ lines_t &get_lines(unsigned line_start, unsigned line_count, lines_t &lines) {
+ using namespace std;
+ // This function make uses of the dumbest algo ever:
+ // 1) seek(0)
+ // 2) read lines one by one and discard until line_start
+ // 3) read line one by one until line_start + line_count
+ //
+ // If you are getting snippets many time from the same file, it is
+ // somewhat a waste of CPU, feel free to benchmark and propose a
+ // better solution ;)
+
+ _file->clear();
+ _file->seekg(0);
+ string line;
+ unsigned line_idx;
+
+ for (line_idx = 1; line_idx < line_start; ++line_idx) {
+ std::getline(*_file, line);
+ if (!*_file) {
+ return lines;
+ }
+ }
+
+ // think of it like a lambda in C++98 ;)
+ // but look, I will reuse it two times!
+ // What a good boy am I.
+ struct isspace {
+ bool operator()(char c) { return std::isspace(c); }
+ };
+
+ bool started = false;
+ for (; line_idx < line_start + line_count; ++line_idx) {
+ getline(*_file, line);
+ if (!*_file) {
+ return lines;
+ }
+ if (!started) {
+ if (std::find_if(line.begin(), line.end(), not_isspace()) == line.end())
+ continue;
+ started = true;
+ }
+ lines.push_back(make_pair(line_idx, line));
+ }
+
+ lines.erase(
+ std::find_if(lines.rbegin(), lines.rend(), not_isempty()).base(),
+ lines.end());
+ return lines;
+ }
+
+ lines_t get_lines(unsigned line_start, unsigned line_count) {
+ lines_t lines;
+ return get_lines(line_start, line_count, lines);
+ }
+
+ // there is no find_if_not in C++98, lets do something crappy to
+ // workaround.
+ struct not_isspace {
+ bool operator()(char c) { return !std::isspace(c); }
+ };
+ // and define this one here because C++98 is not happy with local defined
+ // struct passed to template functions, fuuuu.
+ struct not_isempty {
+ bool operator()(const lines_t::value_type &p) {
+ return !(std::find_if(p.second.begin(), p.second.end(), not_isspace()) ==
+ p.second.end());
+ }
+ };
+
+ void swap(SourceFile &b) { _file.swap(b._file); }
+
+#ifdef BACKWARD_ATLEAST_CXX11
+ SourceFile(SourceFile &&from) : _file(nullptr) { swap(from); }
+ SourceFile &operator=(SourceFile &&from) {
+ swap(from);
+ return *this;
+ }
+#else
+ explicit SourceFile(const SourceFile &from) {
+ // some sort of poor man's move semantic.
+ swap(const_cast<SourceFile &>(from));
+ }
+ SourceFile &operator=(const SourceFile &from) {
+ // some sort of poor man's move semantic.
+ swap(const_cast<SourceFile &>(from));
+ return *this;
+ }
+#endif
+
+private:
+ details::handle<std::ifstream *, details::default_delete<std::ifstream *>>
+ _file;
+
+ std::vector<std::string> get_paths_from_env_variable_impl() {
+ std::vector<std::string> paths;
+ const char *prefixes_str = std::getenv("BACKWARD_CXX_SOURCE_PREFIXES");
+ if (prefixes_str && prefixes_str[0]) {
+ paths = details::split_source_prefixes(prefixes_str);
+ }
+ return paths;
+ }
+
+ const std::vector<std::string> &get_paths_from_env_variable() {
+ static std::vector<std::string> paths = get_paths_from_env_variable_impl();
+ return paths;
+ }
+
+#ifdef BACKWARD_ATLEAST_CXX11
+ SourceFile(const SourceFile &) = delete;
+ SourceFile &operator=(const SourceFile &) = delete;
+#endif
+};
+
+class SnippetFactory {
+public:
+ typedef SourceFile::lines_t lines_t;
+
+ lines_t get_snippet(const std::string &filename, unsigned line_start,
+ unsigned context_size) {
+
+ SourceFile &src_file = get_src_file(filename);
+ unsigned start = line_start - context_size / 2;
+ return src_file.get_lines(start, context_size);
+ }
+
+ lines_t get_combined_snippet(const std::string &filename_a, unsigned line_a,
+ const std::string &filename_b, unsigned line_b,
+ unsigned context_size) {
+ SourceFile &src_file_a = get_src_file(filename_a);
+ SourceFile &src_file_b = get_src_file(filename_b);
+
+ lines_t lines =
+ src_file_a.get_lines(line_a - context_size / 4, context_size / 2);
+ src_file_b.get_lines(line_b - context_size / 4, context_size / 2, lines);
+ return lines;
+ }
+
+ lines_t get_coalesced_snippet(const std::string &filename, unsigned line_a,
+ unsigned line_b, unsigned context_size) {
+ SourceFile &src_file = get_src_file(filename);
+
+ using std::max;
+ using std::min;
+ unsigned a = min(line_a, line_b);
+ unsigned b = max(line_a, line_b);
+
+ if ((b - a) < (context_size / 3)) {
+ return src_file.get_lines((a + b - context_size + 1) / 2, context_size);
+ }
+
+ lines_t lines = src_file.get_lines(a - context_size / 4, context_size / 2);
+ src_file.get_lines(b - context_size / 4, context_size / 2, lines);
+ return lines;
+ }
+
+private:
+ typedef details::hashtable<std::string, SourceFile>::type src_files_t;
+ src_files_t _src_files;
+
+ SourceFile &get_src_file(const std::string &filename) {
+ src_files_t::iterator it = _src_files.find(filename);
+ if (it != _src_files.end()) {
+ return it->second;
+ }
+ SourceFile &new_src_file = _src_files[filename];
+ new_src_file = SourceFile(filename);
+ return new_src_file;
+ }
+};
+
+/*************** PRINTER ***************/
+
+namespace ColorMode {
+enum type { automatic, never, always };
+}
+
+class cfile_streambuf : public std::streambuf {
+public:
+ cfile_streambuf(FILE *_sink) : sink(_sink) {}
+ int_type underflow() override { return traits_type::eof(); }
+ int_type overflow(int_type ch) override {
+ if (traits_type::not_eof(ch) && fputc(ch, sink) != EOF) {
+ return ch;
+ }
+ return traits_type::eof();
+ }
+
+ std::streamsize xsputn(const char_type *s, std::streamsize count) override {
+ return static_cast<std::streamsize>(
+ fwrite(s, sizeof *s, static_cast<size_t>(count), sink));
+ }
+
+#ifdef BACKWARD_ATLEAST_CXX11
+public:
+ cfile_streambuf(const cfile_streambuf &) = delete;
+ cfile_streambuf &operator=(const cfile_streambuf &) = delete;
+#else
+private:
+ cfile_streambuf(const cfile_streambuf &);
+ cfile_streambuf &operator=(const cfile_streambuf &);
+#endif
+
+private:
+ FILE *sink;
+ std::vector<char> buffer;
+};
+
+#ifdef BACKWARD_SYSTEM_LINUX
+
+namespace Color {
+enum type { yellow = 33, purple = 35, reset = 39 };
+} // namespace Color
+
+class Colorize {
+public:
+ Colorize(std::ostream &os) : _os(os), _reset(false), _enabled(false) {}
+
+ void activate(ColorMode::type mode) { _enabled = mode == ColorMode::always; }
+
+ void activate(ColorMode::type mode, FILE *fp) { activate(mode, fileno(fp)); }
+
+ void set_color(Color::type ccode) {
+ if (!_enabled)
+ return;
+
+ // I assume that the terminal can handle basic colors. Seriously I
+ // don't want to deal with all the termcap shit.
+ _os << "\033[" << static_cast<int>(ccode) << "m";
+ _reset = (ccode != Color::reset);
+ }
+
+ ~Colorize() {
+ if (_reset) {
+ set_color(Color::reset);
+ }
+ }
+
+private:
+ void activate(ColorMode::type mode, int fd) {
+ activate(mode == ColorMode::automatic && isatty(fd) ? ColorMode::always
+ : mode);
+ }
+
+ std::ostream &_os;
+ bool _reset;
+ bool _enabled;
+};
+
+#else // ndef BACKWARD_SYSTEM_LINUX
+
+namespace Color {
+enum type { yellow = 0, purple = 0, reset = 0 };
+} // namespace Color
+
+class Colorize {
+public:
+ Colorize(std::ostream &) {}
+ void activate(ColorMode::type) {}
+ void activate(ColorMode::type, FILE *) {}
+ void set_color(Color::type) {}
+};
+
+#endif // BACKWARD_SYSTEM_LINUX
+
+class Printer {
+public:
+ bool snippet;
+ ColorMode::type color_mode;
+ bool address;
+ bool object;
+ int inliner_context_size;
+ int trace_context_size;
+
+ Printer()
+ : snippet(true), color_mode(ColorMode::automatic), address(false),
+ object(false), inliner_context_size(5), trace_context_size(7) {}
+
+ template <typename ST> FILE *print(ST &st, FILE *fp = stderr) {
+ cfile_streambuf obuf(fp);
+ std::ostream os(&obuf);
+ Colorize colorize(os);
+ colorize.activate(color_mode, fp);
+ print_stacktrace(st, os, colorize);
+ return fp;
+ }
+
+ template <typename ST> std::ostream &print(ST &st, std::ostream &os) {
+ Colorize colorize(os);
+ colorize.activate(color_mode);
+ print_stacktrace(st, os, colorize);
+ return os;
+ }
+
+ template <typename IT>
+ FILE *print(IT begin, IT end, FILE *fp = stderr, size_t thread_id = 0) {
+ cfile_streambuf obuf(fp);
+ std::ostream os(&obuf);
+ Colorize colorize(os);
+ colorize.activate(color_mode, fp);
+ print_stacktrace(begin, end, os, thread_id, colorize);
+ return fp;
+ }
+
+ template <typename IT>
+ std::ostream &print(IT begin, IT end, std::ostream &os,
+ size_t thread_id = 0) {
+ Colorize colorize(os);
+ colorize.activate(color_mode);
+ print_stacktrace(begin, end, os, thread_id, colorize);
+ return os;
+ }
+
+ TraceResolver const &resolver() const { return _resolver; }
+
+private:
+ TraceResolver _resolver;
+ SnippetFactory _snippets;
+
+ template <typename ST>
+ void print_stacktrace(ST &st, std::ostream &os, Colorize &colorize) {
+ print_header(os, st.thread_id());
+ _resolver.load_stacktrace(st);
+ for (size_t trace_idx = st.size(); trace_idx > 0; --trace_idx) {
+ print_trace(os, _resolver.resolve(st[trace_idx - 1]), colorize);
+ }
+ }
+
+ template <typename IT>
+ void print_stacktrace(IT begin, IT end, std::ostream &os, size_t thread_id,
+ Colorize &colorize) {
+ print_header(os, thread_id);
+ for (; begin != end; ++begin) {
+ print_trace(os, *begin, colorize);
+ }
+ }
+
+ void print_header(std::ostream &os, size_t thread_id) {
+ os << "Stack trace (most recent call last)";
+ if (thread_id) {
+ os << " in thread " << thread_id;
+ }
+ os << ":\n";
+ }
+
+ void print_trace(std::ostream &os, const ResolvedTrace &trace,
+ Colorize &colorize) {
+ os << "#" << std::left << std::setw(2) << trace.idx << std::right;
+ bool already_indented = true;
+
+ if (!trace.source.filename.size() || object) {
+ os << " Object \"" << trace.object_filename << "\", at " << trace.addr
+ << ", in " << trace.object_function << "\n";
+ already_indented = false;
+ }
+
+ for (size_t inliner_idx = trace.inliners.size(); inliner_idx > 0;
+ --inliner_idx) {
+ if (!already_indented) {
+ os << " ";
+ }
+ const ResolvedTrace::SourceLoc &inliner_loc =
+ trace.inliners[inliner_idx - 1];
+ print_source_loc(os, " | ", inliner_loc);
+ if (snippet) {
+ print_snippet(os, " | ", inliner_loc, colorize, Color::purple,
+ inliner_context_size);
+ }
+ already_indented = false;
+ }
+
+ if (trace.source.filename.size()) {
+ if (!already_indented) {
+ os << " ";
+ }
+ print_source_loc(os, " ", trace.source, trace.addr);
+ if (snippet) {
+ print_snippet(os, " ", trace.source, colorize, Color::yellow,
+ trace_context_size);
+ }
+ }
+ }
+
+ void print_snippet(std::ostream &os, const char *indent,
+ const ResolvedTrace::SourceLoc &source_loc,
+ Colorize &colorize, Color::type color_code,
+ int context_size) {
+ using namespace std;
+ typedef SnippetFactory::lines_t lines_t;
+
+ lines_t lines = _snippets.get_snippet(source_loc.filename, source_loc.line,
+ static_cast<unsigned>(context_size));
+
+ for (lines_t::const_iterator it = lines.begin(); it != lines.end(); ++it) {
+ if (it->first == source_loc.line) {
+ colorize.set_color(color_code);
+ os << indent << ">";
+ } else {
+ os << indent << " ";
+ }
+ os << std::setw(4) << it->first << ": " << it->second << "\n";
+ if (it->first == source_loc.line) {
+ colorize.set_color(Color::reset);
+ }
+ }
+ }
+
+ void print_source_loc(std::ostream &os, const char *indent,
+ const ResolvedTrace::SourceLoc &source_loc,
+ void *addr = nullptr) {
+ os << indent << "Source \"" << source_loc.filename << "\", line "
+ << source_loc.line << ", in " << source_loc.function;
+
+ if (address && addr != nullptr) {
+ os << " [" << addr << "]";
+ }
+ os << "\n";
+ }
+};
+
+/*************** SIGNALS HANDLING ***************/
+
+#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN)
+
+class SignalHandling {
+public:
+ static std::vector<int> make_default_signals() {
+ const int posix_signals[] = {
+ // Signals for which the default action is "Core".
+ SIGABRT, // Abort signal from abort(3)
+ SIGBUS, // Bus error (bad memory access)
+ SIGFPE, // Floating point exception
+ SIGILL, // Illegal Instruction
+ SIGIOT, // IOT trap. A synonym for SIGABRT
+ SIGQUIT, // Quit from keyboard
+ SIGSEGV, // Invalid memory reference
+ SIGSYS, // Bad argument to routine (SVr4)
+ SIGTRAP, // Trace/breakpoint trap
+ SIGXCPU, // CPU time limit exceeded (4.2BSD)
+ SIGXFSZ, // File size limit exceeded (4.2BSD)
+#if defined(BACKWARD_SYSTEM_DARWIN)
+ SIGEMT, // emulation instruction executed
+#endif
+ };
+ return std::vector<int>(posix_signals,
+ posix_signals +
+ sizeof posix_signals / sizeof posix_signals[0]);
+ }
+
+ SignalHandling(const std::vector<int> &posix_signals = make_default_signals())
+ : _loaded(false) {
+ bool success = true;
+
+ const size_t stack_size = 1024 * 1024 * 8;
+ _stack_content.reset(static_cast<char *>(malloc(stack_size)));
+ if (_stack_content) {
+ stack_t ss;
+ ss.ss_sp = _stack_content.get();
+ ss.ss_size = stack_size;
+ ss.ss_flags = 0;
+ if (sigaltstack(&ss, nullptr) < 0) {
+ success = false;
+ }
+ } else {
+ success = false;
+ }
+
+ for (size_t i = 0; i < posix_signals.size(); ++i) {
+ struct sigaction action;
+ memset(&action, 0, sizeof action);
+ action.sa_flags =
+ static_cast<int>(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND);
+ sigfillset(&action.sa_mask);
+ sigdelset(&action.sa_mask, posix_signals[i]);
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
+#endif
+ action.sa_sigaction = &sig_handler;
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+ int r = sigaction(posix_signals[i], &action, nullptr);
+ if (r < 0)
+ success = false;
+ }
+
+ _loaded = success;
+ }
+
+ bool loaded() const { return _loaded; }
+
+ static void handleSignal(int, siginfo_t *info, void *_ctx) {
+ ucontext_t *uctx = static_cast<ucontext_t *>(_ctx);
+
+ StackTrace st;
+ void *error_addr = nullptr;
+#ifdef REG_RIP // x86_64
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext.gregs[REG_RIP]);
+#elif defined(REG_EIP) // x86_32
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext.gregs[REG_EIP]);
+#elif defined(__arm__)
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext.arm_pc);
+#elif defined(__aarch64__)
+ #if defined(__APPLE__)
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext->__ss.__pc);
+ #else
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext.pc);
+ #endif
+#elif defined(__mips__)
+ error_addr = reinterpret_cast<void *>(
+ reinterpret_cast<struct sigcontext *>(&uctx->uc_mcontext)->sc_pc);
+#elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \
+ defined(__POWERPC__)
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext.regs->nip);
+#elif defined(__riscv)
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext.__gregs[REG_PC]);
+#elif defined(__s390x__)
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext.psw.addr);
+#elif defined(__APPLE__) && defined(__x86_64__)
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext->__ss.__rip);
+#elif defined(__APPLE__)
+ error_addr = reinterpret_cast<void *>(uctx->uc_mcontext->__ss.__eip);
+#else
+#warning ":/ sorry, ain't know no nothing none not of your architecture!"
+#endif
+ if (error_addr) {
+ st.load_from(error_addr, 32, reinterpret_cast<void *>(uctx),
+ info->si_addr);
+ } else {
+ st.load_here(32, reinterpret_cast<void *>(uctx), info->si_addr);
+ }
+
+ Printer printer;
+ printer.address = true;
+ printer.print(st, stderr);
+
+#if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L
+ psiginfo(info, nullptr);
+#else
+ (void)info;
+#endif
+ }
+
+private:
+ details::handle<char *> _stack_content;
+ bool _loaded;
+
+#ifdef __GNUC__
+ __attribute__((noreturn))
+#endif
+ static void
+ sig_handler(int signo, siginfo_t *info, void *_ctx) {
+ handleSignal(signo, info, _ctx);
+
+ // try to forward the signal.
+ raise(info->si_signo);
+
+ // terminate the process immediately.
+ puts("watf? exit");
+ _exit(EXIT_FAILURE);
+ }
+};
+
+#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN
+
+#ifdef BACKWARD_SYSTEM_WINDOWS
+
+class SignalHandling {
+public:
+ SignalHandling(const std::vector<int> & = std::vector<int>())
+ : reporter_thread_([]() {
+ /* We handle crashes in a utility thread:
+ backward structures and some Windows functions called here
+ need stack space, which we do not have when we encounter a
+ stack overflow.
+ To support reporting stack traces during a stack overflow,
+ we create a utility thread at startup, which waits until a
+ crash happens or the program exits normally. */
+
+ {
+ std::unique_lock<std::mutex> lk(mtx());
+ cv().wait(lk, [] { return crashed() != crash_status::running; });
+ }
+ if (crashed() == crash_status::crashed) {
+ handle_stacktrace(skip_recs());
+ }
+ {
+ std::unique_lock<std::mutex> lk(mtx());
+ crashed() = crash_status::ending;
+ }
+ cv().notify_one();
+ }) {
+ SetUnhandledExceptionFilter(crash_handler);
+
+ signal(SIGABRT, signal_handler);
+ _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
+
+ std::set_terminate(&terminator);
+#ifndef BACKWARD_ATLEAST_CXX17
+ std::set_unexpected(&terminator);
+#endif
+ _set_purecall_handler(&terminator);
+ _set_invalid_parameter_handler(&invalid_parameter_handler);
+ }
+ bool loaded() const { return true; }
+
+ ~SignalHandling() {
+ {
+ std::unique_lock<std::mutex> lk(mtx());
+ crashed() = crash_status::normal_exit;
+ }
+
+ cv().notify_one();
+
+ reporter_thread_.join();
+ }
+
+private:
+ static CONTEXT *ctx() {
+ static CONTEXT data;
+ return &data;
+ }
+
+ enum class crash_status { running, crashed, normal_exit, ending };
+
+ static crash_status &crashed() {
+ static crash_status data;
+ return data;
+ }
+
+ static std::mutex &mtx() {
+ static std::mutex data;
+ return data;
+ }
+
+ static std::condition_variable &cv() {
+ static std::condition_variable data;
+ return data;
+ }
+
+ static HANDLE &thread_handle() {
+ static HANDLE handle;
+ return handle;
+ }
+
+ std::thread reporter_thread_;
+
+ // TODO: how not to hardcode these?
+ static const constexpr int signal_skip_recs =
+#ifdef __clang__
+ // With clang, RtlCaptureContext also captures the stack frame of the
+ // current function Below that, there ar 3 internal Windows functions
+ 4
+#else
+ // With MSVC cl, RtlCaptureContext misses the stack frame of the current
+ // function The first entries during StackWalk are the 3 internal Windows
+ // functions
+ 3
+#endif
+ ;
+
+ static int &skip_recs() {
+ static int data;
+ return data;
+ }
+
+ static inline void terminator() {
+ crash_handler(signal_skip_recs);
+ abort();
+ }
+
+ static inline void signal_handler(int) {
+ crash_handler(signal_skip_recs);
+ abort();
+ }
+
+ static inline void __cdecl invalid_parameter_handler(const wchar_t *,
+ const wchar_t *,
+ const wchar_t *,
+ unsigned int,
+ uintptr_t) {
+ crash_handler(signal_skip_recs);
+ abort();
+ }
+
+ NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS *info) {
+ // The exception info supplies a trace from exactly where the issue was,
+ // no need to skip records
+ crash_handler(0, info->ContextRecord);
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ NOINLINE static void crash_handler(int skip, CONTEXT *ct = nullptr) {
+
+ if (ct == nullptr) {
+ RtlCaptureContext(ctx());
+ } else {
+ memcpy(ctx(), ct, sizeof(CONTEXT));
+ }
+ DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
+ GetCurrentProcess(), &thread_handle(), 0, FALSE,
+ DUPLICATE_SAME_ACCESS);
+
+ skip_recs() = skip;
+
+ {
+ std::unique_lock<std::mutex> lk(mtx());
+ crashed() = crash_status::crashed;
+ }
+
+ cv().notify_one();
+
+ {
+ std::unique_lock<std::mutex> lk(mtx());
+ cv().wait(lk, [] { return crashed() != crash_status::crashed; });
+ }
+ }
+
+ static void handle_stacktrace(int skip_frames = 0) {
+ // printer creates the TraceResolver, which can supply us a machine type
+ // for stack walking. Without this, StackTrace can only guess using some
+ // macros.
+ // StackTrace also requires that the PDBs are already loaded, which is done
+ // in the constructor of TraceResolver
+ Printer printer;
+
+ StackTrace st;
+ st.set_machine_type(printer.resolver().machine_type());
+ st.set_thread_handle(thread_handle());
+ st.load_here(32 + skip_frames, ctx());
+ st.skip_n_firsts(skip_frames);
+
+ printer.address = true;
+ printer.print(st, std::cerr);
+ }
+};
+
+#endif // BACKWARD_SYSTEM_WINDOWS
+
+#ifdef BACKWARD_SYSTEM_UNKNOWN
+
+class SignalHandling {
+public:
+ SignalHandling(const std::vector<int> & = std::vector<int>()) {}
+ bool init() { return false; }
+ bool loaded() { return false; }
+};
+
+#endif // BACKWARD_SYSTEM_UNKNOWN
+
+} // namespace backward
+
+#endif /* H_GUARD */
diff --git a/src/third-party/base64/LICENSE b/src/third-party/base64/LICENSE
new file mode 100644
index 0000000..9446393
--- /dev/null
+++ b/src/third-party/base64/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2005-2007, Nick Galbreath
+Copyright (c) 2013-2019, Alfred Klomp
+Copyright (c) 2015-2017, Wojciech Mula
+Copyright (c) 2016-2017, Matthieu Darbois
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+- Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+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.
diff --git a/src/third-party/base64/include/libbase64.h b/src/third-party/base64/include/libbase64.h
new file mode 100644
index 0000000..f3e5abb
--- /dev/null
+++ b/src/third-party/base64/include/libbase64.h
@@ -0,0 +1,133 @@
+#ifndef LIBBASE64_H
+#define LIBBASE64_H
+
+#include <stddef.h> /* size_t */
+
+
+#define BASE64_SYMBOL_IMPORT
+#define BASE64_SYMBOL_EXPORT
+#define BASE64_SYMBOL_PRIVATE
+
+#if defined(BASE64_STATIC_DEFINE)
+#define BASE64_EXPORT
+#define BASE64_NO_EXPORT
+
+#else
+#if defined(BASE64_EXPORTS) // defined if we are building the shared library
+#define BASE64_EXPORT BASE64_SYMBOL_EXPORT
+
+#else
+#define BASE64_EXPORT BASE64_SYMBOL_IMPORT
+#endif
+
+#define BASE64_NO_EXPORT BASE64_SYMBOL_PRIVATE
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* These are the flags that can be passed in the `flags` argument. The values
+ * below force the use of a given codec, even if that codec is a no-op in the
+ * current build. Used in testing. Set to 0 for the default behavior, which is
+ * runtime feature detection on x86, a compile-time fixed codec on ARM, and
+ * the plain codec on other platforms: */
+#define BASE64_FORCE_AVX2 (1 << 0)
+#define BASE64_FORCE_NEON32 (1 << 1)
+#define BASE64_FORCE_NEON64 (1 << 2)
+#define BASE64_FORCE_PLAIN (1 << 3)
+#define BASE64_FORCE_SSSE3 (1 << 4)
+#define BASE64_FORCE_SSE41 (1 << 5)
+#define BASE64_FORCE_SSE42 (1 << 6)
+#define BASE64_FORCE_AVX (1 << 7)
+
+struct base64_state {
+ int eof;
+ int bytes;
+ int flags;
+ unsigned char carry;
+};
+
+/* Wrapper function to encode a plain string of given length. Output is written
+ * to *out without trailing zero. Output length in bytes is written to *outlen.
+ * The buffer in `out` has been allocated by the caller and is at least 4/3 the
+ * size of the input. See above for `flags`; set to 0 for default operation: */
+void BASE64_EXPORT base64_encode
+ ( const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ , int flags
+ ) ;
+
+/* Call this before calling base64_stream_encode() to init the state. See above
+ * for `flags`; set to 0 for default operation: */
+void BASE64_EXPORT base64_stream_encode_init
+ ( struct base64_state *state
+ , int flags
+ ) ;
+
+/* Encodes the block of data of given length at `src`, into the buffer at
+ * `out`. Caller is responsible for allocating a large enough out-buffer; it
+ * must be at least 4/3 the size of the in-buffer, but take some margin. Places
+ * the number of new bytes written into `outlen` (which is set to zero when the
+ * function starts). Does not zero-terminate or finalize the output. */
+void BASE64_EXPORT base64_stream_encode
+ ( struct base64_state *state
+ , const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ ) ;
+
+/* Finalizes the output begun by previous calls to `base64_stream_encode()`.
+ * Adds the required end-of-stream markers if appropriate. `outlen` is modified
+ * and will contain the number of new bytes written at `out` (which will quite
+ * often be zero). */
+void BASE64_EXPORT base64_stream_encode_final
+ ( struct base64_state *state
+ , char *out
+ , size_t *outlen
+ ) ;
+
+/* Wrapper function to decode a plain string of given length. Output is written
+ * to *out without trailing zero. Output length in bytes is written to *outlen.
+ * The buffer in `out` has been allocated by the caller and is at least 3/4 the
+ * size of the input. See above for `flags`, set to 0 for default operation: */
+int BASE64_EXPORT base64_decode
+ ( const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ , int flags
+ ) ;
+
+/* Call this before calling base64_stream_decode() to init the state. See above
+ * for `flags`; set to 0 for default operation: */
+void BASE64_EXPORT base64_stream_decode_init
+ ( struct base64_state *state
+ , int flags
+ ) ;
+
+/* Decodes the block of data of given length at `src`, into the buffer at
+ * `out`. Caller is responsible for allocating a large enough out-buffer; it
+ * must be at least 3/4 the size of the in-buffer, but take some margin. Places
+ * the number of new bytes written into `outlen` (which is set to zero when the
+ * function starts). Does not zero-terminate the output. Returns 1 if all is
+ * well, and 0 if a decoding error was found, such as an invalid character.
+ * Returns -1 if the chosen codec is not included in the current build. Used by
+ * the test harness to check whether a codec is available for testing. */
+int BASE64_EXPORT base64_stream_decode
+ ( struct base64_state *state
+ , const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ ) ;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBBASE64_H */
diff --git a/src/third-party/base64/lib/Makefile.am b/src/third-party/base64/lib/Makefile.am
new file mode 100644
index 0000000..7058b4b
--- /dev/null
+++ b/src/third-party/base64/lib/Makefile.am
@@ -0,0 +1,23 @@
+
+noinst_HEADERS = \
+ ../include/libbase64.h \
+ arch/generic/32/enc_loop.c \
+ arch/generic/32/dec_loop.c \
+ arch/generic/enc_tail.c \
+ arch/generic/dec_tail.c \
+ arch/generic/64/enc_loop.c \
+ arch/generic/enc_head.c \
+ arch/generic/dec_head.c \
+ tables/tables.h \
+ tables/table_dec_32bit.h \
+ tables/table_enc_12bit.h \
+ codecs.h \
+ config.h \
+ env.h
+
+noinst_LIBRARIES = libbase64.a
+
+libbase64_a_SOURCES = \
+ lib.c \
+ arch/generic/codec.c \
+ tables/tables.c
diff --git a/src/third-party/base64/lib/arch/avx/codec.c b/src/third-party/base64/lib/arch/avx/codec.c
new file mode 100644
index 0000000..a7a963d
--- /dev/null
+++ b/src/third-party/base64/lib/arch/avx/codec.c
@@ -0,0 +1,42 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "../../../include/libbase64.h"
+#include "../../tables/tables.h"
+#include "../../codecs.h"
+#include "config.h"
+#include "../../env.h"
+
+#if HAVE_AVX
+#include <immintrin.h>
+
+#include "../ssse3/dec_reshuffle.c"
+#include "../ssse3/dec_loop.c"
+#include "../ssse3/enc_translate.c"
+#include "../ssse3/enc_reshuffle.c"
+#include "../ssse3/enc_loop.c"
+
+#endif // HAVE_AVX
+
+BASE64_ENC_FUNCTION(avx)
+{
+#if HAVE_AVX
+ #include "../generic/enc_head.c"
+ enc_loop_ssse3(&s, &slen, &o, &olen);
+ #include "../generic/enc_tail.c"
+#else
+ BASE64_ENC_STUB
+#endif
+}
+
+BASE64_DEC_FUNCTION(avx)
+{
+#if HAVE_AVX
+ #include "../generic/dec_head.c"
+ dec_loop_ssse3(&s, &slen, &o, &olen);
+ #include "../generic/dec_tail.c"
+#else
+ BASE64_DEC_STUB
+#endif
+}
diff --git a/src/third-party/base64/lib/arch/avx2/codec.c b/src/third-party/base64/lib/arch/avx2/codec.c
new file mode 100644
index 0000000..0498548
--- /dev/null
+++ b/src/third-party/base64/lib/arch/avx2/codec.c
@@ -0,0 +1,42 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "../../../include/libbase64.h"
+#include "../../tables/tables.h"
+#include "../../codecs.h"
+#include "config.h"
+#include "../../env.h"
+
+#if HAVE_AVX2
+#include <immintrin.h>
+
+#include "dec_reshuffle.c"
+#include "dec_loop.c"
+#include "enc_translate.c"
+#include "enc_reshuffle.c"
+#include "enc_loop.c"
+
+#endif // HAVE_AVX2
+
+BASE64_ENC_FUNCTION(avx2)
+{
+#if HAVE_AVX2
+ #include "../generic/enc_head.c"
+ enc_loop_avx2(&s, &slen, &o, &olen);
+ #include "../generic/enc_tail.c"
+#else
+ BASE64_ENC_STUB
+#endif
+}
+
+BASE64_DEC_FUNCTION(avx2)
+{
+#if HAVE_AVX2
+ #include "../generic/dec_head.c"
+ dec_loop_avx2(&s, &slen, &o, &olen);
+ #include "../generic/dec_tail.c"
+#else
+ BASE64_DEC_STUB
+#endif
+}
diff --git a/src/third-party/base64/lib/arch/avx2/dec_loop.c b/src/third-party/base64/lib/arch/avx2/dec_loop.c
new file mode 100644
index 0000000..f959fc4
--- /dev/null
+++ b/src/third-party/base64/lib/arch/avx2/dec_loop.c
@@ -0,0 +1,110 @@
+static inline int
+dec_loop_avx2_inner (const uint8_t **s, uint8_t **o, size_t *rounds)
+{
+ const __m256i lut_lo = _mm256_setr_epi8(
+ 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A,
+ 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A);
+
+ const __m256i lut_hi = _mm256_setr_epi8(
+ 0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10);
+
+ const __m256i lut_roll = _mm256_setr_epi8(
+ 0, 16, 19, 4, -65, -65, -71, -71,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 16, 19, 4, -65, -65, -71, -71,
+ 0, 0, 0, 0, 0, 0, 0, 0);
+
+ const __m256i mask_2F = _mm256_set1_epi8(0x2F);
+
+ // Load input:
+ __m256i str = _mm256_loadu_si256((__m256i *) *s);
+
+ // See the SSSE3 decoder for an explanation of the algorithm.
+ const __m256i hi_nibbles = _mm256_and_si256(_mm256_srli_epi32(str, 4), mask_2F);
+ const __m256i lo_nibbles = _mm256_and_si256(str, mask_2F);
+ const __m256i hi = _mm256_shuffle_epi8(lut_hi, hi_nibbles);
+ const __m256i lo = _mm256_shuffle_epi8(lut_lo, lo_nibbles);
+
+ if (!_mm256_testz_si256(lo, hi)) {
+ return 0;
+ }
+
+ const __m256i eq_2F = _mm256_cmpeq_epi8(str, mask_2F);
+ const __m256i roll = _mm256_shuffle_epi8(lut_roll, _mm256_add_epi8(eq_2F, hi_nibbles));
+
+ // Now simply add the delta values to the input:
+ str = _mm256_add_epi8(str, roll);
+
+ // Reshuffle the input to packed 12-byte output format:
+ str = dec_reshuffle(str);
+
+ // Store the output:
+ _mm256_storeu_si256((__m256i *) *o, str);
+
+ *s += 32;
+ *o += 24;
+ *rounds -= 1;
+
+ return 1;
+}
+
+static inline void
+dec_loop_avx2 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ if (*slen < 45) {
+ return;
+ }
+
+ // Process blocks of 32 bytes per round. Because 8 extra zero bytes are
+ // written after the output, ensure that there will be at least 13
+ // bytes of input data left to cover the gap. (11 data bytes and up to
+ // two end-of-string markers.)
+ size_t rounds = (*slen - 13) / 32;
+
+ *slen -= rounds * 32; // 32 bytes consumed per round
+ *olen += rounds * 24; // 24 bytes produced per round
+
+ do {
+ if (rounds >= 8) {
+ if (dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds)) {
+ continue;
+ }
+ break;
+ }
+ if (rounds >= 4) {
+ if (dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds)) {
+ continue;
+ }
+ break;
+ }
+ if (rounds >= 2) {
+ if (dec_loop_avx2_inner(s, o, &rounds) &&
+ dec_loop_avx2_inner(s, o, &rounds)) {
+ continue;
+ }
+ break;
+ }
+ dec_loop_avx2_inner(s, o, &rounds);
+ break;
+
+ } while (rounds > 0);
+
+ // Adjust for any rounds that were skipped:
+ *slen += rounds * 32;
+ *olen -= rounds * 24;
+}
diff --git a/src/third-party/base64/lib/arch/avx2/dec_reshuffle.c b/src/third-party/base64/lib/arch/avx2/dec_reshuffle.c
new file mode 100644
index 0000000..f351809
--- /dev/null
+++ b/src/third-party/base64/lib/arch/avx2/dec_reshuffle.c
@@ -0,0 +1,34 @@
+static inline __m256i
+dec_reshuffle (const __m256i in)
+{
+ // in, lower lane, bits, upper case are most significant bits, lower
+ // case are least significant bits:
+ // 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ
+ // 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG
+ // 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD
+ // 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA
+
+ const __m256i merge_ab_and_bc = _mm256_maddubs_epi16(in, _mm256_set1_epi32(0x01400140));
+ // 0000kkkk LLllllll 0000JJJJ JJjjKKKK
+ // 0000hhhh IIiiiiii 0000GGGG GGggHHHH
+ // 0000eeee FFffffff 0000DDDD DDddEEEE
+ // 0000bbbb CCcccccc 0000AAAA AAaaBBBB
+
+ __m256i out = _mm256_madd_epi16(merge_ab_and_bc, _mm256_set1_epi32(0x00011000));
+ // 00000000 JJJJJJjj KKKKkkkk LLllllll
+ // 00000000 GGGGGGgg HHHHhhhh IIiiiiii
+ // 00000000 DDDDDDdd EEEEeeee FFffffff
+ // 00000000 AAAAAAaa BBBBbbbb CCcccccc
+
+ // Pack bytes together in each lane:
+ out = _mm256_shuffle_epi8(out, _mm256_setr_epi8(
+ 2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, -1, -1, -1, -1,
+ 2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, -1, -1, -1, -1));
+ // 00000000 00000000 00000000 00000000
+ // LLllllll KKKKkkkk JJJJJJjj IIiiiiii
+ // HHHHhhhh GGGGGGgg FFffffff EEEEeeee
+ // DDDDDDdd CCcccccc BBBBbbbb AAAAAAaa
+
+ // Pack lanes:
+ return _mm256_permutevar8x32_epi32(out, _mm256_setr_epi32(0, 1, 2, 4, 5, 6, -1, -1));
+}
diff --git a/src/third-party/base64/lib/arch/avx2/enc_loop.c b/src/third-party/base64/lib/arch/avx2/enc_loop.c
new file mode 100644
index 0000000..b9e2736
--- /dev/null
+++ b/src/third-party/base64/lib/arch/avx2/enc_loop.c
@@ -0,0 +1,89 @@
+static inline void
+enc_loop_avx2_inner_first (const uint8_t **s, uint8_t **o)
+{
+ // First load is done at s - 0 to not get a segfault:
+ __m256i src = _mm256_loadu_si256((__m256i *) *s);
+
+ // Shift by 4 bytes, as required by enc_reshuffle:
+ src = _mm256_permutevar8x32_epi32(src, _mm256_setr_epi32(0, 0, 1, 2, 3, 4, 5, 6));
+
+ // Reshuffle, translate, store:
+ src = enc_reshuffle(src);
+ src = enc_translate(src);
+ _mm256_storeu_si256((__m256i *) *o, src);
+
+ // Subsequent loads will be done at s - 4, set pointer for next round:
+ *s += 20;
+ *o += 32;
+}
+
+static inline void
+enc_loop_avx2_inner (const uint8_t **s, uint8_t **o)
+{
+ // Load input:
+ __m256i src = _mm256_loadu_si256((__m256i *) *s);
+
+ // Reshuffle, translate, store:
+ src = enc_reshuffle(src);
+ src = enc_translate(src);
+ _mm256_storeu_si256((__m256i *) *o, src);
+
+ *s += 24;
+ *o += 32;
+}
+
+static inline void
+enc_loop_avx2 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ if (*slen < 32) {
+ return;
+ }
+
+ // Process blocks of 24 bytes at a time. Because blocks are loaded 32
+ // bytes at a time an offset of -4, ensure that there will be at least
+ // 4 remaining bytes after the last round, so that the final read will
+ // not pass beyond the bounds of the input buffer:
+ size_t rounds = (*slen - 4) / 24;
+
+ *slen -= rounds * 24; // 24 bytes consumed per round
+ *olen += rounds * 32; // 32 bytes produced per round
+
+ // The first loop iteration requires special handling to ensure that
+ // the read, which is done at an offset, does not underflow the buffer:
+ enc_loop_avx2_inner_first(s, o);
+ rounds--;
+
+ while (rounds > 0) {
+ if (rounds >= 8) {
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ rounds -= 8;
+ continue;
+ }
+ if (rounds >= 4) {
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ rounds -= 4;
+ continue;
+ }
+ if (rounds >= 2) {
+ enc_loop_avx2_inner(s, o);
+ enc_loop_avx2_inner(s, o);
+ rounds -= 2;
+ continue;
+ }
+ enc_loop_avx2_inner(s, o);
+ break;
+ }
+
+ // Add the offset back:
+ *s += 4;
+}
diff --git a/src/third-party/base64/lib/arch/avx2/enc_reshuffle.c b/src/third-party/base64/lib/arch/avx2/enc_reshuffle.c
new file mode 100644
index 0000000..ba16690
--- /dev/null
+++ b/src/third-party/base64/lib/arch/avx2/enc_reshuffle.c
@@ -0,0 +1,83 @@
+static inline __m256i
+enc_reshuffle (const __m256i input)
+{
+ // Translation of the SSSE3 reshuffling algorithm to AVX2. This one
+ // works with shifted (4 bytes) input in order to be able to work
+ // efficiently in the two 128-bit lanes.
+
+ // Input, bytes MSB to LSB:
+ // 0 0 0 0 x w v u t s r q p o n m
+ // l k j i h g f e d c b a 0 0 0 0
+
+ const __m256i in = _mm256_shuffle_epi8(input, _mm256_set_epi8(
+ 10, 11, 9, 10,
+ 7, 8, 6, 7,
+ 4, 5, 3, 4,
+ 1, 2, 0, 1,
+
+ 14, 15, 13, 14,
+ 11, 12, 10, 11,
+ 8, 9, 7, 8,
+ 5, 6, 4, 5));
+ // in, bytes MSB to LSB:
+ // w x v w
+ // t u s t
+ // q r p q
+ // n o m n
+ // k l j k
+ // h i g h
+ // e f d e
+ // b c a b
+
+ const __m256i t0 = _mm256_and_si256(in, _mm256_set1_epi32(0x0FC0FC00));
+ // bits, upper case are most significant bits, lower case are least
+ // significant bits.
+ // 0000wwww XX000000 VVVVVV00 00000000
+ // 0000tttt UU000000 SSSSSS00 00000000
+ // 0000qqqq RR000000 PPPPPP00 00000000
+ // 0000nnnn OO000000 MMMMMM00 00000000
+ // 0000kkkk LL000000 JJJJJJ00 00000000
+ // 0000hhhh II000000 GGGGGG00 00000000
+ // 0000eeee FF000000 DDDDDD00 00000000
+ // 0000bbbb CC000000 AAAAAA00 00000000
+
+ const __m256i t1 = _mm256_mulhi_epu16(t0, _mm256_set1_epi32(0x04000040));
+ // 00000000 00wwwwXX 00000000 00VVVVVV
+ // 00000000 00ttttUU 00000000 00SSSSSS
+ // 00000000 00qqqqRR 00000000 00PPPPPP
+ // 00000000 00nnnnOO 00000000 00MMMMMM
+ // 00000000 00kkkkLL 00000000 00JJJJJJ
+ // 00000000 00hhhhII 00000000 00GGGGGG
+ // 00000000 00eeeeFF 00000000 00DDDDDD
+ // 00000000 00bbbbCC 00000000 00AAAAAA
+
+ const __m256i t2 = _mm256_and_si256(in, _mm256_set1_epi32(0x003F03F0));
+ // 00000000 00xxxxxx 000000vv WWWW0000
+ // 00000000 00uuuuuu 000000ss TTTT0000
+ // 00000000 00rrrrrr 000000pp QQQQ0000
+ // 00000000 00oooooo 000000mm NNNN0000
+ // 00000000 00llllll 000000jj KKKK0000
+ // 00000000 00iiiiii 000000gg HHHH0000
+ // 00000000 00ffffff 000000dd EEEE0000
+ // 00000000 00cccccc 000000aa BBBB0000
+
+ const __m256i t3 = _mm256_mullo_epi16(t2, _mm256_set1_epi32(0x01000010));
+ // 00xxxxxx 00000000 00vvWWWW 00000000
+ // 00uuuuuu 00000000 00ssTTTT 00000000
+ // 00rrrrrr 00000000 00ppQQQQ 00000000
+ // 00oooooo 00000000 00mmNNNN 00000000
+ // 00llllll 00000000 00jjKKKK 00000000
+ // 00iiiiii 00000000 00ggHHHH 00000000
+ // 00ffffff 00000000 00ddEEEE 00000000
+ // 00cccccc 00000000 00aaBBBB 00000000
+
+ return _mm256_or_si256(t1, t3);
+ // 00xxxxxx 00wwwwXX 00vvWWWW 00VVVVVV
+ // 00uuuuuu 00ttttUU 00ssTTTT 00SSSSSS
+ // 00rrrrrr 00qqqqRR 00ppQQQQ 00PPPPPP
+ // 00oooooo 00nnnnOO 00mmNNNN 00MMMMMM
+ // 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ
+ // 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG
+ // 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD
+ // 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA
+}
diff --git a/src/third-party/base64/lib/arch/avx2/enc_translate.c b/src/third-party/base64/lib/arch/avx2/enc_translate.c
new file mode 100644
index 0000000..46173cd
--- /dev/null
+++ b/src/third-party/base64/lib/arch/avx2/enc_translate.c
@@ -0,0 +1,30 @@
+static inline __m256i
+enc_translate (const __m256i in)
+{
+ // A lookup table containing the absolute offsets for all ranges:
+ const __m256i lut = _mm256_setr_epi8(
+ 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0,
+ 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0);
+
+ // Translate values 0..63 to the Base64 alphabet. There are five sets:
+ // # From To Abs Index Characters
+ // 0 [0..25] [65..90] +65 0 ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ // 1 [26..51] [97..122] +71 1 abcdefghijklmnopqrstuvwxyz
+ // 2 [52..61] [48..57] -4 [2..11] 0123456789
+ // 3 [62] [43] -19 12 +
+ // 4 [63] [47] -16 13 /
+
+ // Create LUT indices from the input. The index for range #0 is right,
+ // others are 1 less than expected:
+ __m256i indices = _mm256_subs_epu8(in, _mm256_set1_epi8(51));
+
+ // mask is 0xFF (-1) for range #[1..4] and 0x00 for range #0:
+ const __m256i mask = _mm256_cmpgt_epi8(in, _mm256_set1_epi8(25));
+
+ // Subtract -1, so add 1 to indices for range #[1..4]. All indices are
+ // now correct:
+ indices = _mm256_sub_epi8(indices, mask);
+
+ // Add offsets to input values:
+ return _mm256_add_epi8(in, _mm256_shuffle_epi8(lut, indices));
+}
diff --git a/src/third-party/base64/lib/arch/generic/32/dec_loop.c b/src/third-party/base64/lib/arch/generic/32/dec_loop.c
new file mode 100644
index 0000000..8a8260f
--- /dev/null
+++ b/src/third-party/base64/lib/arch/generic/32/dec_loop.c
@@ -0,0 +1,86 @@
+static inline int
+dec_loop_generic_32_inner (const uint8_t **s, uint8_t **o, size_t *rounds)
+{
+ const uint32_t str
+ = base64_table_dec_32bit_d0[(*s)[0]]
+ | base64_table_dec_32bit_d1[(*s)[1]]
+ | base64_table_dec_32bit_d2[(*s)[2]]
+ | base64_table_dec_32bit_d3[(*s)[3]];
+
+#if BASE64_LITTLE_ENDIAN
+
+ // LUTs for little-endian set MSB in case of invalid character:
+ if (str & UINT32_C(0x80000000)) {
+ return 0;
+ }
+#else
+ // LUTs for big-endian set LSB in case of invalid character:
+ if (str & UINT32_C(1)) {
+ return 0;
+ }
+#endif
+ // Store the output:
+ memcpy(*o, &str, sizeof (str));
+
+ *s += 4;
+ *o += 3;
+ *rounds -= 1;
+
+ return 1;
+}
+
+static inline void
+dec_loop_generic_32 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ if (*slen < 8) {
+ return;
+ }
+
+ // Process blocks of 4 bytes per round. Because one extra zero byte is
+ // written after the output, ensure that there will be at least 4 bytes
+ // of input data left to cover the gap. (Two data bytes and up to two
+ // end-of-string markers.)
+ size_t rounds = (*slen - 4) / 4;
+
+ *slen -= rounds * 4; // 4 bytes consumed per round
+ *olen += rounds * 3; // 3 bytes produced per round
+
+ do {
+ if (rounds >= 8) {
+ if (dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds)) {
+ continue;
+ }
+ break;
+ }
+ if (rounds >= 4) {
+ if (dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds)) {
+ continue;
+ }
+ break;
+ }
+ if (rounds >= 2) {
+ if (dec_loop_generic_32_inner(s, o, &rounds) &&
+ dec_loop_generic_32_inner(s, o, &rounds)) {
+ continue;
+ }
+ break;
+ }
+ dec_loop_generic_32_inner(s, o, &rounds);
+ break;
+
+ } while (rounds > 0);
+
+ // Adjust for any rounds that were skipped:
+ *slen += rounds * 4;
+ *olen -= rounds * 3;
+}
diff --git a/src/third-party/base64/lib/arch/generic/32/enc_loop.c b/src/third-party/base64/lib/arch/generic/32/enc_loop.c
new file mode 100644
index 0000000..f4870a7
--- /dev/null
+++ b/src/third-party/base64/lib/arch/generic/32/enc_loop.c
@@ -0,0 +1,73 @@
+static inline void
+enc_loop_generic_32_inner (const uint8_t **s, uint8_t **o)
+{
+ uint32_t src;
+
+ // Load input:
+ memcpy(&src, *s, sizeof (src));
+
+ // Reorder to 32-bit big-endian, if not already in that format. The
+ // workset must be in big-endian, otherwise the shifted bits do not
+ // carry over properly among adjacent bytes:
+ src = BASE64_HTOBE32(src);
+
+ // Two indices for the 12-bit lookup table:
+ const size_t index0 = (src >> 20) & 0xFFFU;
+ const size_t index1 = (src >> 8) & 0xFFFU;
+
+ // Table lookup and store:
+ memcpy(*o + 0, base64_table_enc_12bit + index0, 2);
+ memcpy(*o + 2, base64_table_enc_12bit + index1, 2);
+
+ *s += 3;
+ *o += 4;
+}
+
+static inline void
+enc_loop_generic_32 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ if (*slen < 4) {
+ return;
+ }
+
+ // Process blocks of 3 bytes at a time. Because blocks are loaded 4
+ // bytes at a time, ensure that there will be at least one remaining
+ // byte after the last round, so that the final read will not pass
+ // beyond the bounds of the input buffer:
+ size_t rounds = (*slen - 1) / 3;
+
+ *slen -= rounds * 3; // 3 bytes consumed per round
+ *olen += rounds * 4; // 4 bytes produced per round
+
+ do {
+ if (rounds >= 8) {
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ rounds -= 8;
+ continue;
+ }
+ if (rounds >= 4) {
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ rounds -= 4;
+ continue;
+ }
+ if (rounds >= 2) {
+ enc_loop_generic_32_inner(s, o);
+ enc_loop_generic_32_inner(s, o);
+ rounds -= 2;
+ continue;
+ }
+ enc_loop_generic_32_inner(s, o);
+ break;
+
+ } while (rounds > 0);
+}
diff --git a/src/third-party/base64/lib/arch/generic/64/enc_loop.c b/src/third-party/base64/lib/arch/generic/64/enc_loop.c
new file mode 100644
index 0000000..0840bc7
--- /dev/null
+++ b/src/third-party/base64/lib/arch/generic/64/enc_loop.c
@@ -0,0 +1,77 @@
+static inline void
+enc_loop_generic_64_inner (const uint8_t **s, uint8_t **o)
+{
+ uint64_t src;
+
+ // Load input:
+ memcpy(&src, *s, sizeof (src));
+
+ // Reorder to 64-bit big-endian, if not already in that format. The
+ // workset must be in big-endian, otherwise the shifted bits do not
+ // carry over properly among adjacent bytes:
+ src = BASE64_HTOBE64(src);
+
+ // Four indices for the 12-bit lookup table:
+ const size_t index0 = (src >> 52) & 0xFFFU;
+ const size_t index1 = (src >> 40) & 0xFFFU;
+ const size_t index2 = (src >> 28) & 0xFFFU;
+ const size_t index3 = (src >> 16) & 0xFFFU;
+
+ // Table lookup and store:
+ memcpy(*o + 0, base64_table_enc_12bit + index0, 2);
+ memcpy(*o + 2, base64_table_enc_12bit + index1, 2);
+ memcpy(*o + 4, base64_table_enc_12bit + index2, 2);
+ memcpy(*o + 6, base64_table_enc_12bit + index3, 2);
+
+ *s += 6;
+ *o += 8;
+}
+
+static inline void
+enc_loop_generic_64 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ if (*slen < 8) {
+ return;
+ }
+
+ // Process blocks of 6 bytes at a time. Because blocks are loaded 8
+ // bytes at a time, ensure that there will be at least 2 remaining
+ // bytes after the last round, so that the final read will not pass
+ // beyond the bounds of the input buffer:
+ size_t rounds = (*slen - 2) / 6;
+
+ *slen -= rounds * 6; // 6 bytes consumed per round
+ *olen += rounds * 8; // 8 bytes produced per round
+
+ do {
+ if (rounds >= 8) {
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ rounds -= 8;
+ continue;
+ }
+ if (rounds >= 4) {
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ rounds -= 4;
+ continue;
+ }
+ if (rounds >= 2) {
+ enc_loop_generic_64_inner(s, o);
+ enc_loop_generic_64_inner(s, o);
+ rounds -= 2;
+ continue;
+ }
+ enc_loop_generic_64_inner(s, o);
+ break;
+
+ } while (rounds > 0);
+}
diff --git a/src/third-party/base64/lib/arch/generic/codec.c b/src/third-party/base64/lib/arch/generic/codec.c
new file mode 100644
index 0000000..8dd5af2
--- /dev/null
+++ b/src/third-party/base64/lib/arch/generic/codec.c
@@ -0,0 +1,39 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "../../../include/libbase64.h"
+#include "../../tables/tables.h"
+#include "../../codecs.h"
+#include "config.h"
+#include "../../env.h"
+
+#if BASE64_WORDSIZE == 32
+# include "32/enc_loop.c"
+#elif BASE64_WORDSIZE == 64
+# include "64/enc_loop.c"
+#endif
+
+#if BASE64_WORDSIZE >= 32
+# include "32/dec_loop.c"
+#endif
+
+BASE64_ENC_FUNCTION(plain)
+{
+ #include "enc_head.c"
+#if BASE64_WORDSIZE == 32
+ enc_loop_generic_32(&s, &slen, &o, &olen);
+#elif BASE64_WORDSIZE == 64
+ enc_loop_generic_64(&s, &slen, &o, &olen);
+#endif
+ #include "enc_tail.c"
+}
+
+BASE64_DEC_FUNCTION(plain)
+{
+ #include "dec_head.c"
+#if BASE64_WORDSIZE >= 32
+ dec_loop_generic_32(&s, &slen, &o, &olen);
+#endif
+ #include "dec_tail.c"
+}
diff --git a/src/third-party/base64/lib/arch/generic/dec_head.c b/src/third-party/base64/lib/arch/generic/dec_head.c
new file mode 100644
index 0000000..179a31b
--- /dev/null
+++ b/src/third-party/base64/lib/arch/generic/dec_head.c
@@ -0,0 +1,37 @@
+int ret = 0;
+const uint8_t *s = (const uint8_t *) src;
+uint8_t *o = (uint8_t *) out;
+uint8_t q;
+
+// Use local temporaries to avoid cache thrashing:
+size_t olen = 0;
+size_t slen = srclen;
+struct base64_state st;
+st.eof = state->eof;
+st.bytes = state->bytes;
+st.carry = state->carry;
+
+// If we previously saw an EOF or an invalid character, bail out:
+if (st.eof) {
+ *outlen = 0;
+ ret = 0;
+ // If there was a trailing '=' to check, check it:
+ if (slen && (st.eof == BASE64_AEOF)) {
+ state->bytes = 0;
+ state->eof = BASE64_EOF;
+ ret = ((base64_table_dec_8bit[*s++] == 254) && (slen == 1)) ? 1 : 0;
+ }
+ return ret;
+}
+
+// Turn four 6-bit numbers into three bytes:
+// out[0] = 11111122
+// out[1] = 22223333
+// out[2] = 33444444
+
+// Duff's device again:
+switch (st.bytes)
+{
+ for (;;)
+ {
+ case 0:
diff --git a/src/third-party/base64/lib/arch/generic/dec_tail.c b/src/third-party/base64/lib/arch/generic/dec_tail.c
new file mode 100644
index 0000000..e64f724
--- /dev/null
+++ b/src/third-party/base64/lib/arch/generic/dec_tail.c
@@ -0,0 +1,91 @@
+ if (slen-- == 0) {
+ ret = 1;
+ break;
+ }
+ if ((q = base64_table_dec_8bit[*s++]) >= 254) {
+ st.eof = BASE64_EOF;
+ // Treat character '=' as invalid for byte 0:
+ break;
+ }
+ st.carry = q << 2;
+ st.bytes++;
+
+ // Deliberate fallthrough:
+ BASE64_FALLTHROUGH
+
+ case 1: if (slen-- == 0) {
+ ret = 1;
+ break;
+ }
+ if ((q = base64_table_dec_8bit[*s++]) >= 254) {
+ st.eof = BASE64_EOF;
+ // Treat character '=' as invalid for byte 1:
+ break;
+ }
+ *o++ = st.carry | (q >> 4);
+ st.carry = q << 4;
+ st.bytes++;
+ olen++;
+
+ // Deliberate fallthrough:
+ BASE64_FALLTHROUGH
+
+ case 2: if (slen-- == 0) {
+ ret = 1;
+ break;
+ }
+ if ((q = base64_table_dec_8bit[*s++]) >= 254) {
+ st.bytes++;
+ // When q == 254, the input char is '='.
+ // Check if next byte is also '=':
+ if (q == 254) {
+ if (slen-- != 0) {
+ st.bytes = 0;
+ // EOF:
+ st.eof = BASE64_EOF;
+ q = base64_table_dec_8bit[*s++];
+ ret = ((q == 254) && (slen == 0)) ? 1 : 0;
+ break;
+ }
+ else {
+ // Almost EOF
+ st.eof = BASE64_AEOF;
+ ret = 1;
+ break;
+ }
+ }
+ // If we get here, there was an error:
+ break;
+ }
+ *o++ = st.carry | (q >> 2);
+ st.carry = q << 6;
+ st.bytes++;
+ olen++;
+
+ // Deliberate fallthrough:
+ BASE64_FALLTHROUGH
+
+ case 3: if (slen-- == 0) {
+ ret = 1;
+ break;
+ }
+ if ((q = base64_table_dec_8bit[*s++]) >= 254) {
+ st.bytes = 0;
+ st.eof = BASE64_EOF;
+ // When q == 254, the input char is '='. Return 1 and EOF.
+ // When q == 255, the input char is invalid. Return 0 and EOF.
+ ret = ((q == 254) && (slen == 0)) ? 1 : 0;
+ break;
+ }
+ *o++ = st.carry | q;
+ st.carry = 0;
+ st.bytes = 0;
+ olen++;
+ }
+}
+
+state->eof = st.eof;
+state->bytes = st.bytes;
+state->carry = st.carry;
+*outlen = olen;
+return ret;
diff --git a/src/third-party/base64/lib/arch/generic/enc_head.c b/src/third-party/base64/lib/arch/generic/enc_head.c
new file mode 100644
index 0000000..38d60b2
--- /dev/null
+++ b/src/third-party/base64/lib/arch/generic/enc_head.c
@@ -0,0 +1,24 @@
+// Assume that *out is large enough to contain the output.
+// Theoretically it should be 4/3 the length of src.
+const uint8_t *s = (const uint8_t *) src;
+uint8_t *o = (uint8_t *) out;
+
+// Use local temporaries to avoid cache thrashing:
+size_t olen = 0;
+size_t slen = srclen;
+struct base64_state st;
+st.bytes = state->bytes;
+st.carry = state->carry;
+
+// Turn three bytes into four 6-bit numbers:
+// in[0] = 00111111
+// in[1] = 00112222
+// in[2] = 00222233
+// in[3] = 00333333
+
+// Duff's device, a for() loop inside a switch() statement. Legal!
+switch (st.bytes)
+{
+ for (;;)
+ {
+ case 0:
diff --git a/src/third-party/base64/lib/arch/generic/enc_tail.c b/src/third-party/base64/lib/arch/generic/enc_tail.c
new file mode 100644
index 0000000..cbd5733
--- /dev/null
+++ b/src/third-party/base64/lib/arch/generic/enc_tail.c
@@ -0,0 +1,34 @@
+ if (slen-- == 0) {
+ break;
+ }
+ *o++ = base64_table_enc_6bit[*s >> 2];
+ st.carry = (*s++ << 4) & 0x30;
+ st.bytes++;
+ olen += 1;
+
+ // Deliberate fallthrough:
+ BASE64_FALLTHROUGH
+
+ case 1: if (slen-- == 0) {
+ break;
+ }
+ *o++ = base64_table_enc_6bit[st.carry | (*s >> 4)];
+ st.carry = (*s++ << 2) & 0x3C;
+ st.bytes++;
+ olen += 1;
+
+ // Deliberate fallthrough:
+ BASE64_FALLTHROUGH
+
+ case 2: if (slen-- == 0) {
+ break;
+ }
+ *o++ = base64_table_enc_6bit[st.carry | (*s >> 6)];
+ *o++ = base64_table_enc_6bit[*s++ & 0x3F];
+ st.bytes = 0;
+ olen += 2;
+ }
+}
+state->bytes = st.bytes;
+state->carry = st.carry;
+*outlen = olen;
diff --git a/src/third-party/base64/lib/arch/neon32/codec.c b/src/third-party/base64/lib/arch/neon32/codec.c
new file mode 100644
index 0000000..a0b27f9
--- /dev/null
+++ b/src/third-party/base64/lib/arch/neon32/codec.c
@@ -0,0 +1,77 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "../../../include/libbase64.h"
+#include "../../tables/tables.h"
+#include "../../codecs.h"
+#include "config.h"
+#include "../../env.h"
+
+#ifdef __arm__
+# if (defined(__ARM_NEON__) || defined(__ARM_NEON)) && HAVE_NEON32
+# define BASE64_USE_NEON32
+# endif
+#endif
+
+#ifdef BASE64_USE_NEON32
+#include <arm_neon.h>
+
+// Only enable inline assembly on supported compilers.
+#if defined(__GNUC__) || defined(__clang__)
+#define BASE64_NEON32_USE_ASM
+#endif
+
+static inline uint8x16_t
+vqtbl1q_u8 (const uint8x16_t lut, const uint8x16_t indices)
+{
+ // NEON32 only supports 64-bit wide lookups in 128-bit tables. Emulate
+ // the NEON64 `vqtbl1q_u8` intrinsic to do 128-bit wide lookups.
+ uint8x8x2_t lut2;
+ uint8x8x2_t result;
+
+ lut2.val[0] = vget_low_u8(lut);
+ lut2.val[1] = vget_high_u8(lut);
+
+ result.val[0] = vtbl2_u8(lut2, vget_low_u8(indices));
+ result.val[1] = vtbl2_u8(lut2, vget_high_u8(indices));
+
+ return vcombine_u8(result.val[0], result.val[1]);
+}
+
+#include "../generic/32/dec_loop.c"
+#include "../generic/32/enc_loop.c"
+#include "dec_loop.c"
+#include "enc_reshuffle.c"
+#include "enc_translate.c"
+#include "enc_loop.c"
+
+#endif // BASE64_USE_NEON32
+
+// Stride size is so large on these NEON 32-bit functions
+// (48 bytes encode, 32 bytes decode) that we inline the
+// uint32 codec to stay performant on smaller inputs.
+
+BASE64_ENC_FUNCTION(neon32)
+{
+#ifdef BASE64_USE_NEON32
+ #include "../generic/enc_head.c"
+ enc_loop_neon32(&s, &slen, &o, &olen);
+ enc_loop_generic_32(&s, &slen, &o, &olen);
+ #include "../generic/enc_tail.c"
+#else
+ BASE64_ENC_STUB
+#endif
+}
+
+BASE64_DEC_FUNCTION(neon32)
+{
+#ifdef BASE64_USE_NEON32
+ #include "../generic/dec_head.c"
+ dec_loop_neon32(&s, &slen, &o, &olen);
+ dec_loop_generic_32(&s, &slen, &o, &olen);
+ #include "../generic/dec_tail.c"
+#else
+ BASE64_DEC_STUB
+#endif
+}
diff --git a/src/third-party/base64/lib/arch/neon32/dec_loop.c b/src/third-party/base64/lib/arch/neon32/dec_loop.c
new file mode 100644
index 0000000..2216b39
--- /dev/null
+++ b/src/third-party/base64/lib/arch/neon32/dec_loop.c
@@ -0,0 +1,106 @@
+static inline int
+is_nonzero (const uint8x16_t v)
+{
+ uint64_t u64;
+ const uint64x2_t v64 = vreinterpretq_u64_u8(v);
+ const uint32x2_t v32 = vqmovn_u64(v64);
+
+ vst1_u64(&u64, vreinterpret_u64_u32(v32));
+ return u64 != 0;
+}
+
+static inline uint8x16_t
+delta_lookup (const uint8x16_t v)
+{
+ const uint8x8_t lut = {
+ 0, 16, 19, 4, (uint8_t) -65, (uint8_t) -65, (uint8_t) -71, (uint8_t) -71,
+ };
+
+ return vcombine_u8(
+ vtbl1_u8(lut, vget_low_u8(v)),
+ vtbl1_u8(lut, vget_high_u8(v)));
+}
+
+static inline uint8x16_t
+dec_loop_neon32_lane (uint8x16_t *lane)
+{
+ // See the SSSE3 decoder for an explanation of the algorithm.
+ const uint8x16_t lut_lo = {
+ 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A
+ };
+
+ const uint8x16_t lut_hi = {
+ 0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10
+ };
+
+ const uint8x16_t mask_0F = vdupq_n_u8(0x0F);
+ const uint8x16_t mask_2F = vdupq_n_u8(0x2F);
+
+ const uint8x16_t hi_nibbles = vshrq_n_u8(*lane, 4);
+ const uint8x16_t lo_nibbles = vandq_u8(*lane, mask_0F);
+ const uint8x16_t eq_2F = vceqq_u8(*lane, mask_2F);
+
+ const uint8x16_t hi = vqtbl1q_u8(lut_hi, hi_nibbles);
+ const uint8x16_t lo = vqtbl1q_u8(lut_lo, lo_nibbles);
+
+ // Now simply add the delta values to the input:
+ *lane = vaddq_u8(*lane, delta_lookup(vaddq_u8(eq_2F, hi_nibbles)));
+
+ // Return the validity mask:
+ return vandq_u8(lo, hi);
+}
+
+static inline void
+dec_loop_neon32 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ if (*slen < 64) {
+ return;
+ }
+
+ // Process blocks of 64 bytes per round. Unlike the SSE codecs, no
+ // extra trailing zero bytes are written, so it is not necessary to
+ // reserve extra input bytes:
+ size_t rounds = *slen / 64;
+
+ *slen -= rounds * 64; // 64 bytes consumed per round
+ *olen += rounds * 48; // 48 bytes produced per round
+
+ do {
+ uint8x16x3_t dec;
+
+ // Load 64 bytes and deinterleave:
+ uint8x16x4_t str = vld4q_u8(*s);
+
+ // Decode each lane, collect a mask of invalid inputs:
+ const uint8x16_t classified
+ = dec_loop_neon32_lane(&str.val[0])
+ | dec_loop_neon32_lane(&str.val[1])
+ | dec_loop_neon32_lane(&str.val[2])
+ | dec_loop_neon32_lane(&str.val[3]);
+
+ // Check for invalid input: if any of the delta values are
+ // zero, fall back on bytewise code to do error checking and
+ // reporting:
+ if (is_nonzero(classified)) {
+ break;
+ }
+
+ // Compress four bytes into three:
+ dec.val[0] = vorrq_u8(vshlq_n_u8(str.val[0], 2), vshrq_n_u8(str.val[1], 4));
+ dec.val[1] = vorrq_u8(vshlq_n_u8(str.val[1], 4), vshrq_n_u8(str.val[2], 2));
+ dec.val[2] = vorrq_u8(vshlq_n_u8(str.val[2], 6), str.val[3]);
+
+ // Interleave and store decoded result:
+ vst3q_u8(*o, dec);
+
+ *s += 64;
+ *o += 48;
+
+ } while (--rounds > 0);
+
+ // Adjust for any rounds that were skipped:
+ *slen += rounds * 64;
+ *olen -= rounds * 48;
+}
diff --git a/src/third-party/base64/lib/arch/neon32/enc_loop.c b/src/third-party/base64/lib/arch/neon32/enc_loop.c
new file mode 100644
index 0000000..e9e8e28
--- /dev/null
+++ b/src/third-party/base64/lib/arch/neon32/enc_loop.c
@@ -0,0 +1,169 @@
+#ifdef BASE64_NEON32_USE_ASM
+static inline void
+enc_loop_neon32_inner_asm (const uint8_t **s, uint8_t **o)
+{
+ // This function duplicates the functionality of enc_loop_neon32_inner,
+ // but entirely with inline assembly. This gives a significant speedup
+ // over using NEON intrinsics, which do not always generate very good
+ // code. The logic of the assembly is directly lifted from the
+ // intrinsics version, so it can be used as a guide to this code.
+
+ // Temporary registers, used as scratch space.
+ uint8x16_t tmp0, tmp1, tmp2, tmp3;
+ uint8x16_t mask0, mask1, mask2, mask3;
+
+ // A lookup table containing the absolute offsets for all ranges.
+ const uint8x16_t lut = {
+ 65U, 71U, 252U, 252U,
+ 252U, 252U, 252U, 252U,
+ 252U, 252U, 252U, 252U,
+ 237U, 240U, 0U, 0U
+ };
+
+ // Numeric constants.
+ const uint8x16_t n51 = vdupq_n_u8(51);
+ const uint8x16_t n25 = vdupq_n_u8(25);
+ const uint8x16_t n63 = vdupq_n_u8(63);
+
+ __asm__ (
+
+ // Load 48 bytes and deinterleave. The bytes are loaded to
+ // hard-coded registers q12, q13 and q14, to ensure that they
+ // are contiguous. Increment the source pointer.
+ "vld3.8 {d24, d26, d28}, [%[src]]! \n\t"
+ "vld3.8 {d25, d27, d29}, [%[src]]! \n\t"
+
+ // Reshuffle the bytes using temporaries.
+ "vshr.u8 %q[t0], q12, #2 \n\t"
+ "vshr.u8 %q[t1], q13, #4 \n\t"
+ "vshr.u8 %q[t2], q14, #6 \n\t"
+ "vsli.8 %q[t1], q12, #4 \n\t"
+ "vsli.8 %q[t2], q13, #2 \n\t"
+ "vand.u8 %q[t1], %q[t1], %q[n63] \n\t"
+ "vand.u8 %q[t2], %q[t2], %q[n63] \n\t"
+ "vand.u8 %q[t3], q14, %q[n63] \n\t"
+
+ // t0..t3 are the reshuffled inputs. Create LUT indices.
+ "vqsub.u8 q12, %q[t0], %q[n51] \n\t"
+ "vqsub.u8 q13, %q[t1], %q[n51] \n\t"
+ "vqsub.u8 q14, %q[t2], %q[n51] \n\t"
+ "vqsub.u8 q15, %q[t3], %q[n51] \n\t"
+
+ // Create the mask for range #0.
+ "vcgt.u8 %q[m0], %q[t0], %q[n25] \n\t"
+ "vcgt.u8 %q[m1], %q[t1], %q[n25] \n\t"
+ "vcgt.u8 %q[m2], %q[t2], %q[n25] \n\t"
+ "vcgt.u8 %q[m3], %q[t3], %q[n25] \n\t"
+
+ // Subtract -1 to correct the LUT indices.
+ "vsub.u8 q12, %q[m0] \n\t"
+ "vsub.u8 q13, %q[m1] \n\t"
+ "vsub.u8 q14, %q[m2] \n\t"
+ "vsub.u8 q15, %q[m3] \n\t"
+
+ // Lookup the delta values.
+ "vtbl.u8 d24, {%q[lut]}, d24 \n\t"
+ "vtbl.u8 d25, {%q[lut]}, d25 \n\t"
+ "vtbl.u8 d26, {%q[lut]}, d26 \n\t"
+ "vtbl.u8 d27, {%q[lut]}, d27 \n\t"
+ "vtbl.u8 d28, {%q[lut]}, d28 \n\t"
+ "vtbl.u8 d29, {%q[lut]}, d29 \n\t"
+ "vtbl.u8 d30, {%q[lut]}, d30 \n\t"
+ "vtbl.u8 d31, {%q[lut]}, d31 \n\t"
+
+ // Add the delta values.
+ "vadd.u8 q12, %q[t0] \n\t"
+ "vadd.u8 q13, %q[t1] \n\t"
+ "vadd.u8 q14, %q[t2] \n\t"
+ "vadd.u8 q15, %q[t3] \n\t"
+
+ // Store 64 bytes and interleave. Increment the dest pointer.
+ "vst4.8 {d24, d26, d28, d30}, [%[dst]]! \n\t"
+ "vst4.8 {d25, d27, d29, d31}, [%[dst]]! \n\t"
+
+ // Outputs (modified).
+ : [src] "+r" (*s),
+ [dst] "+r" (*o),
+ [t0] "=&w" (tmp0),
+ [t1] "=&w" (tmp1),
+ [t2] "=&w" (tmp2),
+ [t3] "=&w" (tmp3),
+ [m0] "=&w" (mask0),
+ [m1] "=&w" (mask1),
+ [m2] "=&w" (mask2),
+ [m3] "=&w" (mask3)
+
+ // Inputs (not modified).
+ : [lut] "w" (lut),
+ [n25] "w" (n25),
+ [n51] "w" (n51),
+ [n63] "w" (n63)
+
+ // Clobbers.
+ : "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31"
+ );
+}
+#endif
+
+static inline void
+enc_loop_neon32_inner (const uint8_t **s, uint8_t **o)
+{
+#ifdef BASE64_NEON32_USE_ASM
+ enc_loop_neon32_inner_asm(s, o);
+#else
+ // Load 48 bytes and deinterleave:
+ uint8x16x3_t src = vld3q_u8(*s);
+
+ // Reshuffle:
+ uint8x16x4_t out = enc_reshuffle(src);
+
+ // Translate reshuffled bytes to the Base64 alphabet:
+ out = enc_translate(out);
+
+ // Interleave and store output:
+ vst4q_u8(*o, out);
+
+ *s += 48;
+ *o += 64;
+#endif
+}
+
+static inline void
+enc_loop_neon32 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ size_t rounds = *slen / 48;
+
+ *slen -= rounds * 48; // 48 bytes consumed per round
+ *olen += rounds * 64; // 64 bytes produced per round
+
+ while (rounds > 0) {
+ if (rounds >= 8) {
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ rounds -= 8;
+ continue;
+ }
+ if (rounds >= 4) {
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ rounds -= 4;
+ continue;
+ }
+ if (rounds >= 2) {
+ enc_loop_neon32_inner(s, o);
+ enc_loop_neon32_inner(s, o);
+ rounds -= 2;
+ continue;
+ }
+ enc_loop_neon32_inner(s, o);
+ break;
+ }
+}
diff --git a/src/third-party/base64/lib/arch/neon32/enc_reshuffle.c b/src/third-party/base64/lib/arch/neon32/enc_reshuffle.c
new file mode 100644
index 0000000..d6e97cb
--- /dev/null
+++ b/src/third-party/base64/lib/arch/neon32/enc_reshuffle.c
@@ -0,0 +1,31 @@
+static inline uint8x16x4_t
+enc_reshuffle (uint8x16x3_t in)
+{
+ uint8x16x4_t out;
+
+ // Input:
+ // in[0] = a7 a6 a5 a4 a3 a2 a1 a0
+ // in[1] = b7 b6 b5 b4 b3 b2 b1 b0
+ // in[2] = c7 c6 c5 c4 c3 c2 c1 c0
+
+ // Output:
+ // out[0] = 00 00 a7 a6 a5 a4 a3 a2
+ // out[1] = 00 00 a1 a0 b7 b6 b5 b4
+ // out[2] = 00 00 b3 b2 b1 b0 c7 c6
+ // out[3] = 00 00 c5 c4 c3 c2 c1 c0
+
+ // Move the input bits to where they need to be in the outputs. Except
+ // for the first output, the high two bits are not cleared.
+ out.val[0] = vshrq_n_u8(in.val[0], 2);
+ out.val[1] = vshrq_n_u8(in.val[1], 4);
+ out.val[2] = vshrq_n_u8(in.val[2], 6);
+ out.val[1] = vsliq_n_u8(out.val[1], in.val[0], 4);
+ out.val[2] = vsliq_n_u8(out.val[2], in.val[1], 2);
+
+ // Clear the high two bits in the second, third and fourth output.
+ out.val[1] = vandq_u8(out.val[1], vdupq_n_u8(0x3F));
+ out.val[2] = vandq_u8(out.val[2], vdupq_n_u8(0x3F));
+ out.val[3] = vandq_u8(in.val[2], vdupq_n_u8(0x3F));
+
+ return out;
+}
diff --git a/src/third-party/base64/lib/arch/neon32/enc_translate.c b/src/third-party/base64/lib/arch/neon32/enc_translate.c
new file mode 100644
index 0000000..e616d54
--- /dev/null
+++ b/src/third-party/base64/lib/arch/neon32/enc_translate.c
@@ -0,0 +1,57 @@
+static inline uint8x16x4_t
+enc_translate (const uint8x16x4_t in)
+{
+ // A lookup table containing the absolute offsets for all ranges:
+ const uint8x16_t lut = {
+ 65U, 71U, 252U, 252U,
+ 252U, 252U, 252U, 252U,
+ 252U, 252U, 252U, 252U,
+ 237U, 240U, 0U, 0U
+ };
+
+ const uint8x16_t offset = vdupq_n_u8(51);
+
+ uint8x16x4_t indices, mask, delta, out;
+
+ // Translate values 0..63 to the Base64 alphabet. There are five sets:
+ // # From To Abs Index Characters
+ // 0 [0..25] [65..90] +65 0 ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ // 1 [26..51] [97..122] +71 1 abcdefghijklmnopqrstuvwxyz
+ // 2 [52..61] [48..57] -4 [2..11] 0123456789
+ // 3 [62] [43] -19 12 +
+ // 4 [63] [47] -16 13 /
+
+ // Create LUT indices from input:
+ // the index for range #0 is right, others are 1 less than expected:
+ indices.val[0] = vqsubq_u8(in.val[0], offset);
+ indices.val[1] = vqsubq_u8(in.val[1], offset);
+ indices.val[2] = vqsubq_u8(in.val[2], offset);
+ indices.val[3] = vqsubq_u8(in.val[3], offset);
+
+ // mask is 0xFF (-1) for range #[1..4] and 0x00 for range #0:
+ mask.val[0] = vcgtq_u8(in.val[0], vdupq_n_u8(25));
+ mask.val[1] = vcgtq_u8(in.val[1], vdupq_n_u8(25));
+ mask.val[2] = vcgtq_u8(in.val[2], vdupq_n_u8(25));
+ mask.val[3] = vcgtq_u8(in.val[3], vdupq_n_u8(25));
+
+ // Subtract -1, so add 1 to indices for range #[1..4], All indices are
+ // now correct:
+ indices.val[0] = vsubq_u8(indices.val[0], mask.val[0]);
+ indices.val[1] = vsubq_u8(indices.val[1], mask.val[1]);
+ indices.val[2] = vsubq_u8(indices.val[2], mask.val[2]);
+ indices.val[3] = vsubq_u8(indices.val[3], mask.val[3]);
+
+ // Lookup delta values:
+ delta.val[0] = vqtbl1q_u8(lut, indices.val[0]);
+ delta.val[1] = vqtbl1q_u8(lut, indices.val[1]);
+ delta.val[2] = vqtbl1q_u8(lut, indices.val[2]);
+ delta.val[3] = vqtbl1q_u8(lut, indices.val[3]);
+
+ // Add delta values:
+ out.val[0] = vaddq_u8(in.val[0], delta.val[0]);
+ out.val[1] = vaddq_u8(in.val[1], delta.val[1]);
+ out.val[2] = vaddq_u8(in.val[2], delta.val[2]);
+ out.val[3] = vaddq_u8(in.val[3], delta.val[3]);
+
+ return out;
+}
diff --git a/src/third-party/base64/lib/arch/neon64/codec.c b/src/third-party/base64/lib/arch/neon64/codec.c
new file mode 100644
index 0000000..fc953b2
--- /dev/null
+++ b/src/third-party/base64/lib/arch/neon64/codec.c
@@ -0,0 +1,92 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "../../../include/libbase64.h"
+#include "../../tables/tables.h"
+#include "../../codecs.h"
+#include "config.h"
+#include "../../env.h"
+
+#ifdef __aarch64__
+# if (defined(__ARM_NEON__) || defined(__ARM_NEON)) && HAVE_NEON64
+# define BASE64_USE_NEON64
+# endif
+#endif
+
+#ifdef BASE64_USE_NEON64
+#include <arm_neon.h>
+
+// Only enable inline assembly on supported compilers.
+#if defined(__GNUC__) || defined(__clang__)
+#define BASE64_NEON64_USE_ASM
+#endif
+
+static inline uint8x16x4_t
+load_64byte_table (const uint8_t *p)
+{
+#ifdef BASE64_NEON64_USE_ASM
+
+ // Force the table to be loaded into contiguous registers. GCC will not
+ // normally allocate contiguous registers for a `uint8x16x4_t'. These
+ // registers are chosen to not conflict with the ones in the enc loop.
+ register uint8x16_t t0 __asm__ ("v8");
+ register uint8x16_t t1 __asm__ ("v9");
+ register uint8x16_t t2 __asm__ ("v10");
+ register uint8x16_t t3 __asm__ ("v11");
+
+ __asm__ (
+ "ld1 {%[t0].16b, %[t1].16b, %[t2].16b, %[t3].16b}, [%[src]], #64 \n\t"
+ : [src] "+r" (p),
+ [t0] "=w" (t0),
+ [t1] "=w" (t1),
+ [t2] "=w" (t2),
+ [t3] "=w" (t3)
+ );
+
+ return (uint8x16x4_t) {
+ .val[0] = t0,
+ .val[1] = t1,
+ .val[2] = t2,
+ .val[3] = t3,
+ };
+#else
+ return vld1q_u8_x4(p);
+#endif
+}
+
+#include "../generic/32/dec_loop.c"
+#include "../generic/64/enc_loop.c"
+#include "dec_loop.c"
+#include "enc_reshuffle.c"
+#include "enc_loop.c"
+
+#endif // BASE64_USE_NEON64
+
+// Stride size is so large on these NEON 64-bit functions
+// (48 bytes encode, 64 bytes decode) that we inline the
+// uint64 codec to stay performant on smaller inputs.
+
+BASE64_ENC_FUNCTION(neon64)
+{
+#ifdef BASE64_USE_NEON64
+ #include "../generic/enc_head.c"
+ enc_loop_neon64(&s, &slen, &o, &olen);
+ enc_loop_generic_64(&s, &slen, &o, &olen);
+ #include "../generic/enc_tail.c"
+#else
+ BASE64_ENC_STUB
+#endif
+}
+
+BASE64_DEC_FUNCTION(neon64)
+{
+#ifdef BASE64_USE_NEON64
+ #include "../generic/dec_head.c"
+ dec_loop_neon64(&s, &slen, &o, &olen);
+ dec_loop_generic_32(&s, &slen, &o, &olen);
+ #include "../generic/dec_tail.c"
+#else
+ BASE64_DEC_STUB
+#endif
+}
diff --git a/src/third-party/base64/lib/arch/neon64/dec_loop.c b/src/third-party/base64/lib/arch/neon64/dec_loop.c
new file mode 100644
index 0000000..48232f2
--- /dev/null
+++ b/src/third-party/base64/lib/arch/neon64/dec_loop.c
@@ -0,0 +1,129 @@
+// The input consists of five valid character sets in the Base64 alphabet,
+// which we need to map back to the 6-bit values they represent.
+// There are three ranges, two singles, and then there's the rest.
+//
+// # From To LUT Characters
+// 1 [0..42] [255] #1 invalid input
+// 2 [43] [62] #1 +
+// 3 [44..46] [255] #1 invalid input
+// 4 [47] [63] #1 /
+// 5 [48..57] [52..61] #1 0..9
+// 6 [58..63] [255] #1 invalid input
+// 7 [64] [255] #2 invalid input
+// 8 [65..90] [0..25] #2 A..Z
+// 9 [91..96] [255] #2 invalid input
+// 10 [97..122] [26..51] #2 a..z
+// 11 [123..126] [255] #2 invalid input
+// (12) Everything else => invalid input
+
+// The first LUT will use the VTBL instruction (out of range indices are set to
+// 0 in destination).
+static const uint8_t dec_lut1[] = {
+ 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U,
+ 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U,
+ 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 62U, 255U, 255U, 255U, 63U,
+ 52U, 53U, 54U, 55U, 56U, 57U, 58U, 59U, 60U, 61U, 255U, 255U, 255U, 255U, 255U, 255U,
+};
+
+// The second LUT will use the VTBX instruction (out of range indices will be
+// unchanged in destination). Input [64..126] will be mapped to index [1..63]
+// in this LUT. Index 0 means that value comes from LUT #1.
+static const uint8_t dec_lut2[] = {
+ 0U, 255U, 0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 11U, 12U, 13U,
+ 14U, 15U, 16U, 17U, 18U, 19U, 20U, 21U, 22U, 23U, 24U, 25U, 255U, 255U, 255U, 255U,
+ 255U, 255U, 26U, 27U, 28U, 29U, 30U, 31U, 32U, 33U, 34U, 35U, 36U, 37U, 38U, 39U,
+ 40U, 41U, 42U, 43U, 44U, 45U, 46U, 47U, 48U, 49U, 50U, 51U, 255U, 255U, 255U, 255U,
+};
+
+// All input values in range for the first look-up will be 0U in the second
+// look-up result. All input values out of range for the first look-up will be
+// 0U in the first look-up result. Thus, the two results can be ORed without
+// conflicts.
+//
+// Invalid characters that are in the valid range for either look-up will be
+// set to 255U in the combined result. Other invalid characters will just be
+// passed through with the second look-up result (using the VTBX instruction).
+// Since the second LUT is 64 bytes, those passed-through values are guaranteed
+// to have a value greater than 63U. Therefore, valid characters will be mapped
+// to the valid [0..63] range and all invalid characters will be mapped to
+// values greater than 63.
+
+static inline void
+dec_loop_neon64 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ if (*slen < 64) {
+ return;
+ }
+
+ // Process blocks of 64 bytes per round. Unlike the SSE codecs, no
+ // extra trailing zero bytes are written, so it is not necessary to
+ // reserve extra input bytes:
+ size_t rounds = *slen / 64;
+
+ *slen -= rounds * 64; // 64 bytes consumed per round
+ *olen += rounds * 48; // 48 bytes produced per round
+
+ const uint8x16x4_t tbl_dec1 = load_64byte_table(dec_lut1);
+ const uint8x16x4_t tbl_dec2 = load_64byte_table(dec_lut2);
+
+ do {
+ const uint8x16_t offset = vdupq_n_u8(63U);
+ uint8x16x4_t dec1, dec2;
+ uint8x16x3_t dec;
+
+ // Load 64 bytes and deinterleave:
+ uint8x16x4_t str = vld4q_u8((uint8_t *) *s);
+
+ // Get indices for second LUT:
+ dec2.val[0] = vqsubq_u8(str.val[0], offset);
+ dec2.val[1] = vqsubq_u8(str.val[1], offset);
+ dec2.val[2] = vqsubq_u8(str.val[2], offset);
+ dec2.val[3] = vqsubq_u8(str.val[3], offset);
+
+ // Get values from first LUT:
+ dec1.val[0] = vqtbl4q_u8(tbl_dec1, str.val[0]);
+ dec1.val[1] = vqtbl4q_u8(tbl_dec1, str.val[1]);
+ dec1.val[2] = vqtbl4q_u8(tbl_dec1, str.val[2]);
+ dec1.val[3] = vqtbl4q_u8(tbl_dec1, str.val[3]);
+
+ // Get values from second LUT:
+ dec2.val[0] = vqtbx4q_u8(dec2.val[0], tbl_dec2, dec2.val[0]);
+ dec2.val[1] = vqtbx4q_u8(dec2.val[1], tbl_dec2, dec2.val[1]);
+ dec2.val[2] = vqtbx4q_u8(dec2.val[2], tbl_dec2, dec2.val[2]);
+ dec2.val[3] = vqtbx4q_u8(dec2.val[3], tbl_dec2, dec2.val[3]);
+
+ // Get final values:
+ str.val[0] = vorrq_u8(dec1.val[0], dec2.val[0]);
+ str.val[1] = vorrq_u8(dec1.val[1], dec2.val[1]);
+ str.val[2] = vorrq_u8(dec1.val[2], dec2.val[2]);
+ str.val[3] = vorrq_u8(dec1.val[3], dec2.val[3]);
+
+ // Check for invalid input, any value larger than 63:
+ const uint8x16_t classified
+ = vcgtq_u8(str.val[0], vdupq_n_u8(63))
+ | vcgtq_u8(str.val[1], vdupq_n_u8(63))
+ | vcgtq_u8(str.val[2], vdupq_n_u8(63))
+ | vcgtq_u8(str.val[3], vdupq_n_u8(63));
+
+ // Check that all bits are zero:
+ if (vmaxvq_u8(classified) != 0U) {
+ break;
+ }
+
+ // Compress four bytes into three:
+ dec.val[0] = vshlq_n_u8(str.val[0], 2) | vshrq_n_u8(str.val[1], 4);
+ dec.val[1] = vshlq_n_u8(str.val[1], 4) | vshrq_n_u8(str.val[2], 2);
+ dec.val[2] = vshlq_n_u8(str.val[2], 6) | str.val[3];
+
+ // Interleave and store decoded result:
+ vst3q_u8((uint8_t *) *o, dec);
+
+ *s += 64;
+ *o += 48;
+
+ } while (--rounds > 0);
+
+ // Adjust for any rounds that were skipped:
+ *slen += rounds * 64;
+ *olen -= rounds * 48;
+}
diff --git a/src/third-party/base64/lib/arch/neon64/enc_loop.c b/src/third-party/base64/lib/arch/neon64/enc_loop.c
new file mode 100644
index 0000000..d1862f7
--- /dev/null
+++ b/src/third-party/base64/lib/arch/neon64/enc_loop.c
@@ -0,0 +1,133 @@
+#ifdef BASE64_NEON64_USE_ASM
+static inline void
+enc_loop_neon64_inner_asm (const uint8_t **s, uint8_t **o, const uint8x16x4_t tbl_enc)
+{
+ // This function duplicates the functionality of enc_loop_neon64_inner,
+ // but entirely with inline assembly. This gives a significant speedup
+ // over using NEON intrinsics, which do not always generate very good
+ // code. The logic of the assembly is directly lifted from the
+ // intrinsics version, so it can be used as a guide to this code.
+
+ // Temporary registers, used as scratch space.
+ uint8x16_t tmp0, tmp1, tmp2, tmp3;
+
+ // Numeric constant.
+ const uint8x16_t n63 = vdupq_n_u8(63);
+
+ __asm__ (
+
+ // Load 48 bytes and deinterleave. The bytes are loaded to
+ // hard-coded registers v12, v13 and v14, to ensure that they
+ // are contiguous. Increment the source pointer.
+ "ld3 {v12.16b, v13.16b, v14.16b}, [%[src]], #48 \n\t"
+
+ // Reshuffle the bytes using temporaries.
+ "ushr %[t0].16b, v12.16b, #2 \n\t"
+ "ushr %[t1].16b, v13.16b, #4 \n\t"
+ "ushr %[t2].16b, v14.16b, #6 \n\t"
+ "sli %[t1].16b, v12.16b, #4 \n\t"
+ "sli %[t2].16b, v13.16b, #2 \n\t"
+ "and %[t1].16b, %[t1].16b, %[n63].16b \n\t"
+ "and %[t2].16b, %[t2].16b, %[n63].16b \n\t"
+ "and %[t3].16b, v14.16b, %[n63].16b \n\t"
+
+ // Translate the values to the Base64 alphabet.
+ "tbl v12.16b, {%[l0].16b, %[l1].16b, %[l2].16b, %[l3].16b}, %[t0].16b \n\t"
+ "tbl v13.16b, {%[l0].16b, %[l1].16b, %[l2].16b, %[l3].16b}, %[t1].16b \n\t"
+ "tbl v14.16b, {%[l0].16b, %[l1].16b, %[l2].16b, %[l3].16b}, %[t2].16b \n\t"
+ "tbl v15.16b, {%[l0].16b, %[l1].16b, %[l2].16b, %[l3].16b}, %[t3].16b \n\t"
+
+ // Store 64 bytes and interleave. Increment the dest pointer.
+ "st4 {v12.16b, v13.16b, v14.16b, v15.16b}, [%[dst]], #64 \n\t"
+
+ // Outputs (modified).
+ : [src] "+r" (*s),
+ [dst] "+r" (*o),
+ [t0] "=&w" (tmp0),
+ [t1] "=&w" (tmp1),
+ [t2] "=&w" (tmp2),
+ [t3] "=&w" (tmp3)
+
+ // Inputs (not modified).
+ : [n63] "w" (n63),
+ [l0] "w" (tbl_enc.val[0]),
+ [l1] "w" (tbl_enc.val[1]),
+ [l2] "w" (tbl_enc.val[2]),
+ [l3] "w" (tbl_enc.val[3])
+
+ // Clobbers.
+ : "v12", "v13", "v14", "v15"
+ );
+}
+#endif
+
+static inline void
+enc_loop_neon64_inner (const uint8_t **s, uint8_t **o, const uint8x16x4_t tbl_enc)
+{
+#ifdef BASE64_NEON64_USE_ASM
+ enc_loop_neon64_inner_asm(s, o, tbl_enc);
+#else
+ // Load 48 bytes and deinterleave:
+ uint8x16x3_t src = vld3q_u8(*s);
+
+ // Divide bits of three input bytes over four output bytes:
+ uint8x16x4_t out = enc_reshuffle(src);
+
+ // The bits have now been shifted to the right locations;
+ // translate their values 0..63 to the Base64 alphabet.
+ // Use a 64-byte table lookup:
+ out.val[0] = vqtbl4q_u8(tbl_enc, out.val[0]);
+ out.val[1] = vqtbl4q_u8(tbl_enc, out.val[1]);
+ out.val[2] = vqtbl4q_u8(tbl_enc, out.val[2]);
+ out.val[3] = vqtbl4q_u8(tbl_enc, out.val[3]);
+
+ // Interleave and store output:
+ vst4q_u8(*o, out);
+
+ *s += 48;
+ *o += 64;
+#endif
+}
+
+static inline void
+enc_loop_neon64 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ size_t rounds = *slen / 48;
+
+ *slen -= rounds * 48; // 48 bytes consumed per round
+ *olen += rounds * 64; // 64 bytes produced per round
+
+ // Load the encoding table:
+ const uint8x16x4_t tbl_enc = load_64byte_table(base64_table_enc_6bit);
+
+ while (rounds > 0) {
+ if (rounds >= 8) {
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ rounds -= 8;
+ continue;
+ }
+ if (rounds >= 4) {
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ rounds -= 4;
+ continue;
+ }
+ if (rounds >= 2) {
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ rounds -= 2;
+ continue;
+ }
+ enc_loop_neon64_inner(s, o, tbl_enc);
+ break;
+ }
+}
diff --git a/src/third-party/base64/lib/arch/neon64/enc_reshuffle.c b/src/third-party/base64/lib/arch/neon64/enc_reshuffle.c
new file mode 100644
index 0000000..ea543e0
--- /dev/null
+++ b/src/third-party/base64/lib/arch/neon64/enc_reshuffle.c
@@ -0,0 +1,31 @@
+static inline uint8x16x4_t
+enc_reshuffle (const uint8x16x3_t in)
+{
+ uint8x16x4_t out;
+
+ // Input:
+ // in[0] = a7 a6 a5 a4 a3 a2 a1 a0
+ // in[1] = b7 b6 b5 b4 b3 b2 b1 b0
+ // in[2] = c7 c6 c5 c4 c3 c2 c1 c0
+
+ // Output:
+ // out[0] = 00 00 a7 a6 a5 a4 a3 a2
+ // out[1] = 00 00 a1 a0 b7 b6 b5 b4
+ // out[2] = 00 00 b3 b2 b1 b0 c7 c6
+ // out[3] = 00 00 c5 c4 c3 c2 c1 c0
+
+ // Move the input bits to where they need to be in the outputs. Except
+ // for the first output, the high two bits are not cleared.
+ out.val[0] = vshrq_n_u8(in.val[0], 2);
+ out.val[1] = vshrq_n_u8(in.val[1], 4);
+ out.val[2] = vshrq_n_u8(in.val[2], 6);
+ out.val[1] = vsliq_n_u8(out.val[1], in.val[0], 4);
+ out.val[2] = vsliq_n_u8(out.val[2], in.val[1], 2);
+
+ // Clear the high two bits in the second, third and fourth output.
+ out.val[1] = vandq_u8(out.val[1], vdupq_n_u8(0x3F));
+ out.val[2] = vandq_u8(out.val[2], vdupq_n_u8(0x3F));
+ out.val[3] = vandq_u8(in.val[2], vdupq_n_u8(0x3F));
+
+ return out;
+}
diff --git a/src/third-party/base64/lib/arch/sse41/codec.c b/src/third-party/base64/lib/arch/sse41/codec.c
new file mode 100644
index 0000000..00645fe
--- /dev/null
+++ b/src/third-party/base64/lib/arch/sse41/codec.c
@@ -0,0 +1,42 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "../../../include/libbase64.h"
+#include "../../tables/tables.h"
+#include "../../codecs.h"
+#include "config.h"
+#include "../../env.h"
+
+#if HAVE_SSE41
+#include <smmintrin.h>
+
+#include "../ssse3/dec_reshuffle.c"
+#include "../ssse3/dec_loop.c"
+#include "../ssse3/enc_translate.c"
+#include "../ssse3/enc_reshuffle.c"
+#include "../ssse3/enc_loop.c"
+
+#endif // HAVE_SSE41
+
+BASE64_ENC_FUNCTION(sse41)
+{
+#if HAVE_SSE41
+ #include "../generic/enc_head.c"
+ enc_loop_ssse3(&s, &slen, &o, &olen);
+ #include "../generic/enc_tail.c"
+#else
+ BASE64_ENC_STUB
+#endif
+}
+
+BASE64_DEC_FUNCTION(sse41)
+{
+#if HAVE_SSE41
+ #include "../generic/dec_head.c"
+ dec_loop_ssse3(&s, &slen, &o, &olen);
+ #include "../generic/dec_tail.c"
+#else
+ BASE64_DEC_STUB
+#endif
+}
diff --git a/src/third-party/base64/lib/arch/sse42/codec.c b/src/third-party/base64/lib/arch/sse42/codec.c
new file mode 100644
index 0000000..cf5d97c
--- /dev/null
+++ b/src/third-party/base64/lib/arch/sse42/codec.c
@@ -0,0 +1,42 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "../../../include/libbase64.h"
+#include "../../tables/tables.h"
+#include "../../codecs.h"
+#include "config.h"
+#include "../../env.h"
+
+#if HAVE_SSE42
+#include <nmmintrin.h>
+
+#include "../ssse3/dec_reshuffle.c"
+#include "../ssse3/dec_loop.c"
+#include "../ssse3/enc_translate.c"
+#include "../ssse3/enc_reshuffle.c"
+#include "../ssse3/enc_loop.c"
+
+#endif // HAVE_SSE42
+
+BASE64_ENC_FUNCTION(sse42)
+{
+#if HAVE_SSE42
+ #include "../generic/enc_head.c"
+ enc_loop_ssse3(&s, &slen, &o, &olen);
+ #include "../generic/enc_tail.c"
+#else
+ BASE64_ENC_STUB
+#endif
+}
+
+BASE64_DEC_FUNCTION(sse42)
+{
+#if HAVE_SSE42
+ #include "../generic/dec_head.c"
+ dec_loop_ssse3(&s, &slen, &o, &olen);
+ #include "../generic/dec_tail.c"
+#else
+ BASE64_DEC_STUB
+#endif
+}
diff --git a/src/third-party/base64/lib/arch/ssse3/codec.c b/src/third-party/base64/lib/arch/ssse3/codec.c
new file mode 100644
index 0000000..ad14a45
--- /dev/null
+++ b/src/third-party/base64/lib/arch/ssse3/codec.c
@@ -0,0 +1,42 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "../../../include/libbase64.h"
+#include "../../tables/tables.h"
+#include "../../codecs.h"
+#include "config.h"
+#include "../../env.h"
+
+#if HAVE_SSSE3
+#include <tmmintrin.h>
+
+#include "dec_reshuffle.c"
+#include "dec_loop.c"
+#include "enc_reshuffle.c"
+#include "enc_translate.c"
+#include "enc_loop.c"
+
+#endif // HAVE_SSSE3
+
+BASE64_ENC_FUNCTION(ssse3)
+{
+#if HAVE_SSSE3
+ #include "../generic/enc_head.c"
+ enc_loop_ssse3(&s, &slen, &o, &olen);
+ #include "../generic/enc_tail.c"
+#else
+ BASE64_ENC_STUB
+#endif
+}
+
+BASE64_DEC_FUNCTION(ssse3)
+{
+#if HAVE_SSSE3
+ #include "../generic/dec_head.c"
+ dec_loop_ssse3(&s, &slen, &o, &olen);
+ #include "../generic/dec_tail.c"
+#else
+ BASE64_DEC_STUB
+#endif
+}
diff --git a/src/third-party/base64/lib/arch/ssse3/dec_loop.c b/src/third-party/base64/lib/arch/ssse3/dec_loop.c
new file mode 100644
index 0000000..9da71ab
--- /dev/null
+++ b/src/third-party/base64/lib/arch/ssse3/dec_loop.c
@@ -0,0 +1,173 @@
+// The input consists of six character sets in the Base64 alphabet, which we
+// need to map back to the 6-bit values they represent. There are three ranges,
+// two singles, and then there's the rest.
+//
+// # From To Add Characters
+// 1 [43] [62] +19 +
+// 2 [47] [63] +16 /
+// 3 [48..57] [52..61] +4 0..9
+// 4 [65..90] [0..25] -65 A..Z
+// 5 [97..122] [26..51] -71 a..z
+// (6) Everything else => invalid input
+//
+// We will use lookup tables for character validation and offset computation.
+// Remember that 0x2X and 0x0X are the same index for _mm_shuffle_epi8, this
+// allows to mask with 0x2F instead of 0x0F and thus save one constant
+// declaration (register and/or memory access).
+//
+// For offsets:
+// Perfect hash for lut = ((src >> 4) & 0x2F) + ((src == 0x2F) ? 0xFF : 0x00)
+// 0000 = garbage
+// 0001 = /
+// 0010 = +
+// 0011 = 0-9
+// 0100 = A-Z
+// 0101 = A-Z
+// 0110 = a-z
+// 0111 = a-z
+// 1000 >= garbage
+//
+// For validation, here's the table.
+// A character is valid if and only if the AND of the 2 lookups equals 0:
+//
+// hi \ lo 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
+// LUT 0x15 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x13 0x1A 0x1B 0x1B 0x1B 0x1A
+//
+// 0000 0x10 char NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
+// andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+//
+// 0001 0x10 char DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
+// andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+//
+// 0010 0x01 char ! " # $ % & ' ( ) * + , - . /
+// andlut 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x00 0x01 0x01 0x01 0x00
+//
+// 0011 0x02 char 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
+// andlut 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x02 0x02 0x02 0x02 0x02
+//
+// 0100 0x04 char @ A B C D E F G H I J K L M N O
+// andlut 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+//
+// 0101 0x08 char P Q R S T U V W X Y Z [ \ ] ^ _
+// andlut 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x08 0x08 0x08 0x08 0x08
+//
+// 0110 0x04 char ` a b c d e f g h i j k l m n o
+// andlut 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+// 0111 0x08 char p q r s t u v w x y z { | } ~
+// andlut 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x08 0x08 0x08 0x08 0x08
+//
+// 1000 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+// 1001 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+// 1010 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+// 1011 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+// 1100 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+// 1101 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+// 1110 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+// 1111 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10
+
+static inline int
+dec_loop_ssse3_inner (const uint8_t **s, uint8_t **o, size_t *rounds)
+{
+ const __m128i lut_lo = _mm_setr_epi8(
+ 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A);
+
+ const __m128i lut_hi = _mm_setr_epi8(
+ 0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10);
+
+ const __m128i lut_roll = _mm_setr_epi8(
+ 0, 16, 19, 4, -65, -65, -71, -71,
+ 0, 0, 0, 0, 0, 0, 0, 0);
+
+ const __m128i mask_2F = _mm_set1_epi8(0x2F);
+
+ // Load input:
+ __m128i str = _mm_loadu_si128((__m128i *) *s);
+
+ // Table lookups:
+ const __m128i hi_nibbles = _mm_and_si128(_mm_srli_epi32(str, 4), mask_2F);
+ const __m128i lo_nibbles = _mm_and_si128(str, mask_2F);
+ const __m128i hi = _mm_shuffle_epi8(lut_hi, hi_nibbles);
+ const __m128i lo = _mm_shuffle_epi8(lut_lo, lo_nibbles);
+
+ // Check for invalid input: if any "and" values from lo and hi are not
+ // zero, fall back on bytewise code to do error checking and reporting:
+ if (_mm_movemask_epi8(_mm_cmpgt_epi8(_mm_and_si128(lo, hi), _mm_setzero_si128())) != 0) {
+ return 0;
+ }
+
+ const __m128i eq_2F = _mm_cmpeq_epi8(str, mask_2F);
+ const __m128i roll = _mm_shuffle_epi8(lut_roll, _mm_add_epi8(eq_2F, hi_nibbles));
+
+ // Now simply add the delta values to the input:
+ str = _mm_add_epi8(str, roll);
+
+ // Reshuffle the input to packed 12-byte output format:
+ str = dec_reshuffle(str);
+
+ // Store the output:
+ _mm_storeu_si128((__m128i *) *o, str);
+
+ *s += 16;
+ *o += 12;
+ *rounds -= 1;
+
+ return 1;
+}
+
+static inline void
+dec_loop_ssse3 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ if (*slen < 24) {
+ return;
+ }
+
+ // Process blocks of 16 bytes per round. Because 4 extra zero bytes are
+ // written after the output, ensure that there will be at least 8 bytes
+ // of input data left to cover the gap. (6 data bytes and up to two
+ // end-of-string markers.)
+ size_t rounds = (*slen - 8) / 16;
+
+ *slen -= rounds * 16; // 16 bytes consumed per round
+ *olen += rounds * 12; // 12 bytes produced per round
+
+ do {
+ if (rounds >= 8) {
+ if (dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds)) {
+ continue;
+ }
+ break;
+ }
+ if (rounds >= 4) {
+ if (dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds)) {
+ continue;
+ }
+ break;
+ }
+ if (rounds >= 2) {
+ if (dec_loop_ssse3_inner(s, o, &rounds) &&
+ dec_loop_ssse3_inner(s, o, &rounds)) {
+ continue;
+ }
+ break;
+ }
+ dec_loop_ssse3_inner(s, o, &rounds);
+ break;
+
+ } while (rounds > 0);
+
+ // Adjust for any rounds that were skipped:
+ *slen += rounds * 16;
+ *olen -= rounds * 12;
+}
diff --git a/src/third-party/base64/lib/arch/ssse3/dec_reshuffle.c b/src/third-party/base64/lib/arch/ssse3/dec_reshuffle.c
new file mode 100644
index 0000000..fdf587f
--- /dev/null
+++ b/src/third-party/base64/lib/arch/ssse3/dec_reshuffle.c
@@ -0,0 +1,33 @@
+static inline __m128i
+dec_reshuffle (const __m128i in)
+{
+ // in, bits, upper case are most significant bits, lower case are least significant bits
+ // 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ
+ // 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG
+ // 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD
+ // 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA
+
+ const __m128i merge_ab_and_bc = _mm_maddubs_epi16(in, _mm_set1_epi32(0x01400140));
+ // 0000kkkk LLllllll 0000JJJJ JJjjKKKK
+ // 0000hhhh IIiiiiii 0000GGGG GGggHHHH
+ // 0000eeee FFffffff 0000DDDD DDddEEEE
+ // 0000bbbb CCcccccc 0000AAAA AAaaBBBB
+
+ const __m128i out = _mm_madd_epi16(merge_ab_and_bc, _mm_set1_epi32(0x00011000));
+ // 00000000 JJJJJJjj KKKKkkkk LLllllll
+ // 00000000 GGGGGGgg HHHHhhhh IIiiiiii
+ // 00000000 DDDDDDdd EEEEeeee FFffffff
+ // 00000000 AAAAAAaa BBBBbbbb CCcccccc
+
+ // Pack bytes together:
+ return _mm_shuffle_epi8(out, _mm_setr_epi8(
+ 2, 1, 0,
+ 6, 5, 4,
+ 10, 9, 8,
+ 14, 13, 12,
+ -1, -1, -1, -1));
+ // 00000000 00000000 00000000 00000000
+ // LLllllll KKKKkkkk JJJJJJjj IIiiiiii
+ // HHHHhhhh GGGGGGgg FFffffff EEEEeeee
+ // DDDDDDdd CCcccccc BBBBbbbb AAAAAAaa
+}
diff --git a/src/third-party/base64/lib/arch/ssse3/enc_loop.c b/src/third-party/base64/lib/arch/ssse3/enc_loop.c
new file mode 100644
index 0000000..6de652e
--- /dev/null
+++ b/src/third-party/base64/lib/arch/ssse3/enc_loop.c
@@ -0,0 +1,67 @@
+static inline void
+enc_loop_ssse3_inner (const uint8_t **s, uint8_t **o)
+{
+ // Load input:
+ __m128i str = _mm_loadu_si128((__m128i *) *s);
+
+ // Reshuffle:
+ str = enc_reshuffle(str);
+
+ // Translate reshuffled bytes to the Base64 alphabet:
+ str = enc_translate(str);
+
+ // Store:
+ _mm_storeu_si128((__m128i *) *o, str);
+
+ *s += 12;
+ *o += 16;
+}
+
+static inline void
+enc_loop_ssse3 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen)
+{
+ if (*slen < 16) {
+ return;
+ }
+
+ // Process blocks of 12 bytes at a time. Because blocks are loaded 16
+ // bytes at a time, ensure that there will be at least 4 remaining
+ // bytes after the last round, so that the final read will not pass
+ // beyond the bounds of the input buffer:
+ size_t rounds = (*slen - 4) / 12;
+
+ *slen -= rounds * 12; // 12 bytes consumed per round
+ *olen += rounds * 16; // 16 bytes produced per round
+
+ do {
+ if (rounds >= 8) {
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ rounds -= 8;
+ continue;
+ }
+ if (rounds >= 4) {
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ rounds -= 4;
+ continue;
+ }
+ if (rounds >= 2) {
+ enc_loop_ssse3_inner(s, o);
+ enc_loop_ssse3_inner(s, o);
+ rounds -= 2;
+ continue;
+ }
+ enc_loop_ssse3_inner(s, o);
+ break;
+
+ } while (rounds > 0);
+}
diff --git a/src/third-party/base64/lib/arch/ssse3/enc_reshuffle.c b/src/third-party/base64/lib/arch/ssse3/enc_reshuffle.c
new file mode 100644
index 0000000..b738591
--- /dev/null
+++ b/src/third-party/base64/lib/arch/ssse3/enc_reshuffle.c
@@ -0,0 +1,48 @@
+static inline __m128i
+enc_reshuffle (__m128i in)
+{
+ // Input, bytes MSB to LSB:
+ // 0 0 0 0 l k j i h g f e d c b a
+
+ in = _mm_shuffle_epi8(in, _mm_set_epi8(
+ 10, 11, 9, 10,
+ 7, 8, 6, 7,
+ 4, 5, 3, 4,
+ 1, 2, 0, 1));
+ // in, bytes MSB to LSB:
+ // k l j k
+ // h i g h
+ // e f d e
+ // b c a b
+
+ const __m128i t0 = _mm_and_si128(in, _mm_set1_epi32(0x0FC0FC00));
+ // bits, upper case are most significant bits, lower case are least significant bits
+ // 0000kkkk LL000000 JJJJJJ00 00000000
+ // 0000hhhh II000000 GGGGGG00 00000000
+ // 0000eeee FF000000 DDDDDD00 00000000
+ // 0000bbbb CC000000 AAAAAA00 00000000
+
+ const __m128i t1 = _mm_mulhi_epu16(t0, _mm_set1_epi32(0x04000040));
+ // 00000000 00kkkkLL 00000000 00JJJJJJ
+ // 00000000 00hhhhII 00000000 00GGGGGG
+ // 00000000 00eeeeFF 00000000 00DDDDDD
+ // 00000000 00bbbbCC 00000000 00AAAAAA
+
+ const __m128i t2 = _mm_and_si128(in, _mm_set1_epi32(0x003F03F0));
+ // 00000000 00llllll 000000jj KKKK0000
+ // 00000000 00iiiiii 000000gg HHHH0000
+ // 00000000 00ffffff 000000dd EEEE0000
+ // 00000000 00cccccc 000000aa BBBB0000
+
+ const __m128i t3 = _mm_mullo_epi16(t2, _mm_set1_epi32(0x01000010));
+ // 00llllll 00000000 00jjKKKK 00000000
+ // 00iiiiii 00000000 00ggHHHH 00000000
+ // 00ffffff 00000000 00ddEEEE 00000000
+ // 00cccccc 00000000 00aaBBBB 00000000
+
+ return _mm_or_si128(t1, t3);
+ // 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ
+ // 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG
+ // 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD
+ // 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA
+}
diff --git a/src/third-party/base64/lib/arch/ssse3/enc_translate.c b/src/third-party/base64/lib/arch/ssse3/enc_translate.c
new file mode 100644
index 0000000..04f288f
--- /dev/null
+++ b/src/third-party/base64/lib/arch/ssse3/enc_translate.c
@@ -0,0 +1,33 @@
+static inline __m128i
+enc_translate (const __m128i in)
+{
+ // A lookup table containing the absolute offsets for all ranges:
+ const __m128i lut = _mm_setr_epi8(
+ 65, 71, -4, -4,
+ -4, -4, -4, -4,
+ -4, -4, -4, -4,
+ -19, -16, 0, 0
+ );
+
+ // Translate values 0..63 to the Base64 alphabet. There are five sets:
+ // # From To Abs Index Characters
+ // 0 [0..25] [65..90] +65 0 ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ // 1 [26..51] [97..122] +71 1 abcdefghijklmnopqrstuvwxyz
+ // 2 [52..61] [48..57] -4 [2..11] 0123456789
+ // 3 [62] [43] -19 12 +
+ // 4 [63] [47] -16 13 /
+
+ // Create LUT indices from the input. The index for range #0 is right,
+ // others are 1 less than expected:
+ __m128i indices = _mm_subs_epu8(in, _mm_set1_epi8(51));
+
+ // mask is 0xFF (-1) for range #[1..4] and 0x00 for range #0:
+ __m128i mask = _mm_cmpgt_epi8(in, _mm_set1_epi8(25));
+
+ // Subtract -1, so add 1 to indices for range #[1..4]. All indices are
+ // now correct:
+ indices = _mm_sub_epi8(indices, mask);
+
+ // Add offsets to input values:
+ return _mm_add_epi8(in, _mm_shuffle_epi8(lut, indices));
+}
diff --git a/src/third-party/base64/lib/codec_choose.c b/src/third-party/base64/lib/codec_choose.c
new file mode 100644
index 0000000..6a07d6a
--- /dev/null
+++ b/src/third-party/base64/lib/codec_choose.c
@@ -0,0 +1,281 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "../include/libbase64.h"
+#include "codecs.h"
+#include "config.h"
+#include "env.h"
+
+#if (__x86_64__ || __i386__ || _M_X86 || _M_X64)
+ #define BASE64_X86
+ #if (HAVE_SSSE3 || HAVE_SSE41 || HAVE_SSE42 || HAVE_AVX || HAVE_AVX2)
+ #define BASE64_X86_SIMD
+ #endif
+#endif
+
+#ifdef BASE64_X86
+#ifdef _MSC_VER
+ #include <intrin.h>
+ #define __cpuid_count(__level, __count, __eax, __ebx, __ecx, __edx) \
+ { \
+ int info[4]; \
+ __cpuidex(info, __level, __count); \
+ __eax = info[0]; \
+ __ebx = info[1]; \
+ __ecx = info[2]; \
+ __edx = info[3]; \
+ }
+ #define __cpuid(__level, __eax, __ebx, __ecx, __edx) \
+ __cpuid_count(__level, 0, __eax, __ebx, __ecx, __edx)
+#else
+ #include <cpuid.h>
+ #if HAVE_AVX2 || HAVE_AVX
+ #if ((__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 2) || (__clang_major__ >= 3))
+ static inline uint64_t _xgetbv (uint32_t index)
+ {
+ uint32_t eax, edx;
+ __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index));
+ return ((uint64_t)edx << 32) | eax;
+ }
+ #else
+ #error "Platform not supported"
+ #endif
+ #endif
+#endif
+
+#ifndef bit_AVX2
+#define bit_AVX2 (1 << 5)
+#endif
+#ifndef bit_SSSE3
+#define bit_SSSE3 (1 << 9)
+#endif
+#ifndef bit_SSE41
+#define bit_SSE41 (1 << 19)
+#endif
+#ifndef bit_SSE42
+#define bit_SSE42 (1 << 20)
+#endif
+#ifndef bit_AVX
+#define bit_AVX (1 << 28)
+#endif
+
+#define bit_XSAVE_XRSTORE (1 << 27)
+
+#ifndef _XCR_XFEATURE_ENABLED_MASK
+#define _XCR_XFEATURE_ENABLED_MASK 0
+#endif
+
+#define _XCR_XMM_AND_YMM_STATE_ENABLED_BY_OS 0x6
+#endif
+
+// Function declarations:
+#define BASE64_CODEC_FUNCS(arch) \
+ BASE64_ENC_FUNCTION(arch); \
+ BASE64_DEC_FUNCTION(arch); \
+
+BASE64_CODEC_FUNCS(avx2)
+BASE64_CODEC_FUNCS(neon32)
+BASE64_CODEC_FUNCS(neon64)
+BASE64_CODEC_FUNCS(plain)
+BASE64_CODEC_FUNCS(ssse3)
+BASE64_CODEC_FUNCS(sse41)
+BASE64_CODEC_FUNCS(sse42)
+BASE64_CODEC_FUNCS(avx)
+
+static bool
+codec_choose_forced (struct codec *codec, int flags)
+{
+ // If the user wants to use a certain codec,
+ // always allow it, even if the codec is a no-op.
+ // For testing purposes.
+
+ if (!(flags & 0xFF)) {
+ return false;
+ }
+ if (flags & BASE64_FORCE_AVX2) {
+ codec->enc = base64_stream_encode_avx2;
+ codec->dec = base64_stream_decode_avx2;
+ return true;
+ }
+ if (flags & BASE64_FORCE_NEON32) {
+ codec->enc = base64_stream_encode_neon32;
+ codec->dec = base64_stream_decode_neon32;
+ return true;
+ }
+ if (flags & BASE64_FORCE_NEON64) {
+ codec->enc = base64_stream_encode_neon64;
+ codec->dec = base64_stream_decode_neon64;
+ return true;
+ }
+ if (flags & BASE64_FORCE_PLAIN) {
+ codec->enc = base64_stream_encode_plain;
+ codec->dec = base64_stream_decode_plain;
+ return true;
+ }
+ if (flags & BASE64_FORCE_SSSE3) {
+ codec->enc = base64_stream_encode_ssse3;
+ codec->dec = base64_stream_decode_ssse3;
+ return true;
+ }
+ if (flags & BASE64_FORCE_SSE41) {
+ codec->enc = base64_stream_encode_sse41;
+ codec->dec = base64_stream_decode_sse41;
+ return true;
+ }
+ if (flags & BASE64_FORCE_SSE42) {
+ codec->enc = base64_stream_encode_sse42;
+ codec->dec = base64_stream_decode_sse42;
+ return true;
+ }
+ if (flags & BASE64_FORCE_AVX) {
+ codec->enc = base64_stream_encode_avx;
+ codec->dec = base64_stream_decode_avx;
+ return true;
+ }
+ return false;
+}
+
+static bool
+codec_choose_arm (struct codec *codec)
+{
+#if (defined(__ARM_NEON__) || defined(__ARM_NEON)) && ((defined(__aarch64__) && HAVE_NEON64) || HAVE_NEON32)
+
+ // Unfortunately there is no portable way to check for NEON
+ // support at runtime from userland in the same way that x86
+ // has cpuid, so just stick to the compile-time configuration:
+
+ #if defined(__aarch64__) && HAVE_NEON64
+ codec->enc = base64_stream_encode_neon64;
+ codec->dec = base64_stream_decode_neon64;
+ #else
+ codec->enc = base64_stream_encode_neon32;
+ codec->dec = base64_stream_decode_neon32;
+ #endif
+
+ return true;
+
+#else
+ (void)codec;
+ return false;
+#endif
+}
+
+static bool
+codec_choose_x86 (struct codec *codec)
+{
+#ifdef BASE64_X86_SIMD
+
+ unsigned int eax, ebx = 0, ecx = 0, edx;
+ unsigned int max_level;
+
+ #ifdef _MSC_VER
+ int info[4];
+ __cpuidex(info, 0, 0);
+ max_level = info[0];
+ #else
+ max_level = __get_cpuid_max(0, NULL);
+ #endif
+
+ #if HAVE_AVX2 || HAVE_AVX
+ // Check for AVX/AVX2 support:
+ // Checking for AVX requires 3 things:
+ // 1) CPUID indicates that the OS uses XSAVE and XRSTORE instructions
+ // (allowing saving YMM registers on context switch)
+ // 2) CPUID indicates support for AVX
+ // 3) XGETBV indicates the AVX registers will be saved and restored on
+ // context switch
+ //
+ // Note that XGETBV is only available on 686 or later CPUs, so the
+ // instruction needs to be conditionally run.
+ if (max_level >= 1) {
+ __cpuid_count(1, 0, eax, ebx, ecx, edx);
+ if (ecx & bit_XSAVE_XRSTORE) {
+ uint64_t xcr_mask;
+ xcr_mask = _xgetbv(_XCR_XFEATURE_ENABLED_MASK);
+ if (xcr_mask & _XCR_XMM_AND_YMM_STATE_ENABLED_BY_OS) {
+ #if HAVE_AVX2
+ if (max_level >= 7) {
+ __cpuid_count(7, 0, eax, ebx, ecx, edx);
+ if (ebx & bit_AVX2) {
+ codec->enc = base64_stream_encode_avx2;
+ codec->dec = base64_stream_decode_avx2;
+ return true;
+ }
+ }
+ #endif
+ #if HAVE_AVX
+ __cpuid_count(1, 0, eax, ebx, ecx, edx);
+ if (ecx & bit_AVX) {
+ codec->enc = base64_stream_encode_avx;
+ codec->dec = base64_stream_decode_avx;
+ return true;
+ }
+ #endif
+ }
+ }
+ }
+ #endif
+
+ #if HAVE_SSE42
+ // Check for SSE42 support:
+ if (max_level >= 1) {
+ __cpuid(1, eax, ebx, ecx, edx);
+ if (ecx & bit_SSE42) {
+ codec->enc = base64_stream_encode_sse42;
+ codec->dec = base64_stream_decode_sse42;
+ return true;
+ }
+ }
+ #endif
+
+ #if HAVE_SSE41
+ // Check for SSE41 support:
+ if (max_level >= 1) {
+ __cpuid(1, eax, ebx, ecx, edx);
+ if (ecx & bit_SSE41) {
+ codec->enc = base64_stream_encode_sse41;
+ codec->dec = base64_stream_decode_sse41;
+ return true;
+ }
+ }
+ #endif
+
+ #if HAVE_SSSE3
+ // Check for SSSE3 support:
+ if (max_level >= 1) {
+ __cpuid(1, eax, ebx, ecx, edx);
+ if (ecx & bit_SSSE3) {
+ codec->enc = base64_stream_encode_ssse3;
+ codec->dec = base64_stream_decode_ssse3;
+ return true;
+ }
+ }
+ #endif
+
+#else
+ (void)codec;
+#endif
+
+ return false;
+}
+
+void
+codec_choose (struct codec *codec, int flags)
+{
+ // User forced a codec:
+ if (codec_choose_forced(codec, flags)) {
+ return;
+ }
+
+ // Runtime feature detection:
+ if (codec_choose_arm(codec)) {
+ return;
+ }
+ if (codec_choose_x86(codec)) {
+ return;
+ }
+ codec->enc = base64_stream_encode_plain;
+ codec->dec = base64_stream_decode_plain;
+}
diff --git a/src/third-party/base64/lib/codecs.h b/src/third-party/base64/lib/codecs.h
new file mode 100644
index 0000000..441fd60
--- /dev/null
+++ b/src/third-party/base64/lib/codecs.h
@@ -0,0 +1,65 @@
+#include <stdint.h>
+#include <stddef.h>
+
+#include "../include/libbase64.h"
+#include "config.h"
+
+// Function parameters for encoding functions:
+#define BASE64_ENC_PARAMS \
+ ( struct base64_state *state \
+ , const char *src \
+ , size_t srclen \
+ , char *out \
+ , size_t *outlen \
+ )
+
+// Function parameters for decoding functions:
+#define BASE64_DEC_PARAMS \
+ ( struct base64_state *state \
+ , const char *src \
+ , size_t srclen \
+ , char *out \
+ , size_t *outlen \
+ )
+
+// Function signature for encoding functions:
+#define BASE64_ENC_FUNCTION(arch) \
+ void \
+ base64_stream_encode_ ## arch \
+ BASE64_ENC_PARAMS
+
+// Function signature for decoding functions:
+#define BASE64_DEC_FUNCTION(arch) \
+ int \
+ base64_stream_decode_ ## arch \
+ BASE64_DEC_PARAMS
+
+// Cast away unused variable, silence compiler:
+#define UNUSED(x) ((void)(x))
+
+// Stub function when encoder arch unsupported:
+#define BASE64_ENC_STUB \
+ UNUSED(state); \
+ UNUSED(src); \
+ UNUSED(srclen); \
+ UNUSED(out); \
+ \
+ *outlen = 0;
+
+// Stub function when decoder arch unsupported:
+#define BASE64_DEC_STUB \
+ UNUSED(state); \
+ UNUSED(src); \
+ UNUSED(srclen); \
+ UNUSED(out); \
+ UNUSED(outlen); \
+ \
+ return -1;
+
+struct codec
+{
+ void (* enc) BASE64_ENC_PARAMS;
+ int (* dec) BASE64_DEC_PARAMS;
+};
+
+extern void codec_choose (struct codec *, int flags);
diff --git a/src/third-party/base64/lib/config.h b/src/third-party/base64/lib/config.h
new file mode 100644
index 0000000..0de5358
--- /dev/null
+++ b/src/third-party/base64/lib/config.h
@@ -0,0 +1,7 @@
+#define HAVE_AVX2 0
+#define HAVE_NEON32 0
+#define HAVE_NEON64 0
+#define HAVE_SSSE3 0
+#define HAVE_SSE41 0
+#define HAVE_SSE42 0
+#define HAVE_AVX 0
diff --git a/src/third-party/base64/lib/env.h b/src/third-party/base64/lib/env.h
new file mode 100644
index 0000000..d5c2fdb
--- /dev/null
+++ b/src/third-party/base64/lib/env.h
@@ -0,0 +1,74 @@
+#ifndef BASE64_ENV_H
+#define BASE64_ENV_H
+
+// This header file contains macro definitions that describe certain aspects of
+// the compile-time environment. Compatibility and portability macros go here.
+
+// Define machine endianness. This is for GCC:
+#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+# define BASE64_LITTLE_ENDIAN 1
+#else
+# define BASE64_LITTLE_ENDIAN 0
+#endif
+
+// This is for Clang:
+#ifdef __LITTLE_ENDIAN__
+# define BASE64_LITTLE_ENDIAN 1
+#endif
+
+#ifdef __BIG_ENDIAN__
+# define BASE64_LITTLE_ENDIAN 0
+#endif
+
+// MSVC++ needs intrin.h for _byteswap_uint64 (issue #68):
+#if BASE64_LITTLE_ENDIAN && defined(_MSC_VER)
+# include <intrin.h>
+#endif
+
+// Endian conversion functions:
+#if BASE64_LITTLE_ENDIAN
+# ifdef _MSC_VER
+// Microsoft Visual C++:
+# define BASE64_HTOBE32(x) _byteswap_ulong(x)
+# define BASE64_HTOBE64(x) _byteswap_uint64(x)
+# else
+// GCC and Clang:
+# define BASE64_HTOBE32(x) __builtin_bswap32(x)
+# define BASE64_HTOBE64(x) __builtin_bswap64(x)
+# endif
+#else
+// No conversion needed:
+# define BASE64_HTOBE32(x) (x)
+# define BASE64_HTOBE64(x) (x)
+#endif
+
+// Detect word size:
+#if defined (__x86_64__)
+// This also works for the x32 ABI, which has a 64-bit word size.
+# define BASE64_WORDSIZE 64
+#elif defined (_INTEGRAL_MAX_BITS)
+# define BASE64_WORDSIZE _INTEGRAL_MAX_BITS
+#elif defined (__WORDSIZE)
+# define BASE64_WORDSIZE __WORDSIZE
+#elif defined (__SIZE_WIDTH__)
+# define BASE64_WORDSIZE __SIZE_WIDTH__
+#else
+# error BASE64_WORDSIZE_NOT_DEFINED
+#endif
+
+// End-of-file definitions.
+// Almost end-of-file when waiting for the last '=' character:
+#define BASE64_AEOF 1
+// End-of-file when stream end has been reached or invalid input provided:
+#define BASE64_EOF 2
+
+// GCC 7 defaults to issuing a warning for fallthrough in switch statements,
+// unless the fallthrough cases are marked with an attribute. As we use
+// fallthrough deliberately, define an alias for the attribute:
+#if __GNUC__ >= 7
+# define BASE64_FALLTHROUGH __attribute__((fallthrough));
+#else
+# define BASE64_FALLTHROUGH
+#endif
+
+#endif // BASE64_ENV_H
diff --git a/src/third-party/base64/lib/lib.c b/src/third-party/base64/lib/lib.c
new file mode 100644
index 0000000..659b986
--- /dev/null
+++ b/src/third-party/base64/lib/lib.c
@@ -0,0 +1,175 @@
+#include <stdint.h>
+#include <stddef.h>
+#ifdef _OPENMP
+#include <omp.h>
+#endif
+
+#include "../include/libbase64.h"
+#include "tables/tables.h"
+#include "codecs.h"
+#include "env.h"
+
+// These static function pointers are initialized once when the library is
+// first used, and remain in use for the remaining lifetime of the program.
+// The idea being that CPU features don't change at runtime.
+static struct codec codec = { NULL, NULL };
+
+// Function declarations:
+#define BASE64_CODEC_FUNCS(arch) \
+ BASE64_ENC_FUNCTION(arch); \
+ BASE64_DEC_FUNCTION(arch); \
+
+BASE64_CODEC_FUNCS(plain)
+
+void
+base64_stream_encode_init (struct base64_state *state, int flags)
+{
+ // If any of the codec flags are set, redo choice:
+ if (codec.enc == NULL || flags & 0xFF) {
+ // codec_choose(&codec, flags);
+ codec.enc = base64_stream_encode_plain;
+ codec.dec = base64_stream_decode_plain;
+ }
+ state->eof = 0;
+ state->bytes = 0;
+ state->carry = 0;
+ state->flags = flags;
+}
+
+void
+base64_stream_encode
+ ( struct base64_state *state
+ , const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ )
+{
+ codec.enc(state, src, srclen, out, outlen);
+}
+
+void
+base64_stream_encode_final
+ ( struct base64_state *state
+ , char *out
+ , size_t *outlen
+ )
+{
+ uint8_t *o = (uint8_t *)out;
+
+ if (state->bytes == 1) {
+ *o++ = base64_table_enc_6bit[state->carry];
+ *o++ = '=';
+ *o++ = '=';
+ *outlen = 3;
+ return;
+ }
+ if (state->bytes == 2) {
+ *o++ = base64_table_enc_6bit[state->carry];
+ *o++ = '=';
+ *outlen = 2;
+ return;
+ }
+ *outlen = 0;
+}
+
+void
+base64_stream_decode_init (struct base64_state *state, int flags)
+{
+ // If any of the codec flags are set, redo choice:
+ if (codec.dec == NULL || flags & 0xFF) {
+ // codec_choose(&codec, flags);
+ codec.enc = base64_stream_encode_plain;
+ codec.dec = base64_stream_decode_plain;
+ }
+ state->eof = 0;
+ state->bytes = 0;
+ state->carry = 0;
+ state->flags = flags;
+}
+
+int
+base64_stream_decode
+ ( struct base64_state *state
+ , const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ )
+{
+ return codec.dec(state, src, srclen, out, outlen);
+}
+
+#ifdef _OPENMP
+
+ // Due to the overhead of initializing OpenMP and creating a team of
+ // threads, we require the data length to be larger than a threshold:
+ #define OMP_THRESHOLD 20000
+
+ // Conditionally include OpenMP-accelerated codec implementations:
+ #include "lib_openmp.c"
+#endif
+
+void
+base64_encode
+ ( const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ , int flags
+ )
+{
+ size_t s;
+ size_t t;
+ struct base64_state state;
+
+ #ifdef _OPENMP
+ if (srclen >= OMP_THRESHOLD) {
+ base64_encode_openmp(src, srclen, out, outlen, flags);
+ return;
+ }
+ #endif
+
+ // Init the stream reader:
+ base64_stream_encode_init(&state, flags);
+
+ // Feed the whole string to the stream reader:
+ base64_stream_encode(&state, src, srclen, out, &s);
+
+ // Finalize the stream by writing trailer if any:
+ base64_stream_encode_final(&state, out + s, &t);
+
+ // Final output length is stream length plus tail:
+ *outlen = s + t;
+}
+
+int
+base64_decode
+ ( const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ , int flags
+ )
+{
+ int ret;
+ struct base64_state state;
+
+ #ifdef _OPENMP
+ if (srclen >= OMP_THRESHOLD) {
+ return base64_decode_openmp(src, srclen, out, outlen, flags);
+ }
+ #endif
+
+ // Init the stream reader:
+ base64_stream_decode_init(&state, flags);
+
+ // Feed the whole string to the stream reader:
+ ret = base64_stream_decode(&state, src, srclen, out, outlen);
+
+ // If when decoding a whole block, we're still waiting for input then fail:
+ if (ret && (state.bytes == 0)) {
+ return ret;
+ }
+ return 0;
+}
diff --git a/src/third-party/base64/lib/lib_openmp.c b/src/third-party/base64/lib/lib_openmp.c
new file mode 100644
index 0000000..6b87c52
--- /dev/null
+++ b/src/third-party/base64/lib/lib_openmp.c
@@ -0,0 +1,149 @@
+// This code makes some assumptions on the implementation of
+// base64_stream_encode_init(), base64_stream_encode() and base64_stream_decode().
+// Basically these assumptions boil down to that when breaking the src into
+// parts, out parts can be written without side effects.
+// This is met when:
+// 1) base64_stream_encode() and base64_stream_decode() don't use globals;
+// 2) the shared variables src and out are not read or written outside of the
+// bounds of their parts, i.e. when base64_stream_encode() reads a multiple
+// of 3 bytes, it must write no more then a multiple of 4 bytes, not even
+// temporarily;
+// 3) the state flag can be discarded after base64_stream_encode() and
+// base64_stream_decode() on the parts.
+
+static inline void
+base64_encode_openmp
+ ( const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ , int flags
+ )
+{
+ size_t s;
+ size_t t;
+ size_t sum = 0, len, last_len;
+ struct base64_state state, initial_state;
+ int num_threads, i;
+
+ // Request a number of threads but not necessarily get them:
+ #pragma omp parallel
+ {
+ // Get the number of threads used from one thread only,
+ // as num_threads is a shared var:
+ #pragma omp single
+ {
+ num_threads = omp_get_num_threads();
+
+ // Split the input string into num_threads parts, each
+ // part a multiple of 3 bytes. The remaining bytes will
+ // be done later:
+ len = srclen / (num_threads * 3);
+ len *= 3;
+ last_len = srclen - num_threads * len;
+
+ // Init the stream reader:
+ base64_stream_encode_init(&state, flags);
+ initial_state = state;
+ }
+
+ // Single has an implicit barrier for all threads to wait here
+ // for the above to complete:
+ #pragma omp for firstprivate(state) private(s) reduction(+:sum) schedule(static,1)
+ for (i = 0; i < num_threads; i++)
+ {
+ // Feed each part of the string to the stream reader:
+ base64_stream_encode(&state, src + i * len, len, out + i * len * 4 / 3, &s);
+ sum += s;
+ }
+ }
+
+ // As encoding should never fail and we encode an exact multiple
+ // of 3 bytes, we can discard state:
+ state = initial_state;
+
+ // Encode the remaining bytes:
+ base64_stream_encode(&state, src + num_threads * len, last_len, out + num_threads * len * 4 / 3, &s);
+
+ // Finalize the stream by writing trailer if any:
+ base64_stream_encode_final(&state, out + num_threads * len * 4 / 3 + s, &t);
+
+ // Final output length is stream length plus tail:
+ sum += s + t;
+ *outlen = sum;
+}
+
+static inline int
+base64_decode_openmp
+ ( const char *src
+ , size_t srclen
+ , char *out
+ , size_t *outlen
+ , int flags
+ )
+{
+ int num_threads, result = 0, i;
+ size_t sum = 0, len, last_len, s;
+ struct base64_state state, initial_state;
+
+ // Request a number of threads but not necessarily get them:
+ #pragma omp parallel
+ {
+ // Get the number of threads used from one thread only,
+ // as num_threads is a shared var:
+ #pragma omp single
+ {
+ num_threads = omp_get_num_threads();
+
+ // Split the input string into num_threads parts, each
+ // part a multiple of 4 bytes. The remaining bytes will
+ // be done later:
+ len = srclen / (num_threads * 4);
+ len *= 4;
+ last_len = srclen - num_threads * len;
+
+ // Init the stream reader:
+ base64_stream_decode_init(&state, flags);
+
+ initial_state = state;
+ }
+
+ // Single has an implicit barrier to wait here for the above to
+ // complete:
+ #pragma omp for firstprivate(state) private(s) reduction(+:sum, result) schedule(static,1)
+ for (i = 0; i < num_threads; i++)
+ {
+ int this_result;
+
+ // Feed each part of the string to the stream reader:
+ this_result = base64_stream_decode(&state, src + i * len, len, out + i * len * 3 / 4, &s);
+ sum += s;
+ result += this_result;
+ }
+ }
+
+ // If `result' equals `-num_threads', then all threads returned -1,
+ // indicating that the requested codec is not available:
+ if (result == -num_threads) {
+ return -1;
+ }
+
+ // If `result' does not equal `num_threads', then at least one of the
+ // threads hit a decode error:
+ if (result != num_threads) {
+ return 0;
+ }
+
+ // So far so good, now decode whatever remains in the buffer. Reuse the
+ // initial state, since we are at a 4-byte boundary:
+ state = initial_state;
+ result = base64_stream_decode(&state, src + num_threads * len, last_len, out + num_threads * len * 3 / 4, &s);
+ sum += s;
+ *outlen = sum;
+
+ // If when decoding a whole block, we're still waiting for input then fail:
+ if (result && (state.bytes == 0)) {
+ return result;
+ }
+ return 0;
+}
diff --git a/src/third-party/base64/lib/tables/table_dec_32bit.h b/src/third-party/base64/lib/tables/table_dec_32bit.h
new file mode 100644
index 0000000..f5d951f
--- /dev/null
+++ b/src/third-party/base64/lib/tables/table_dec_32bit.h
@@ -0,0 +1,393 @@
+#include <stdint.h>
+#define CHAR62 '+'
+#define CHAR63 '/'
+#define CHARPAD '='
+
+
+#if BASE64_LITTLE_ENDIAN
+
+
+/* SPECIAL DECODE TABLES FOR LITTLE ENDIAN (INTEL) CPUS */
+
+const uint32_t base64_table_dec_32bit_d0[256] = {
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x000000f8, 0xffffffff, 0xffffffff, 0xffffffff, 0x000000fc,
+0x000000d0, 0x000000d4, 0x000000d8, 0x000000dc, 0x000000e0, 0x000000e4,
+0x000000e8, 0x000000ec, 0x000000f0, 0x000000f4, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
+0x00000004, 0x00000008, 0x0000000c, 0x00000010, 0x00000014, 0x00000018,
+0x0000001c, 0x00000020, 0x00000024, 0x00000028, 0x0000002c, 0x00000030,
+0x00000034, 0x00000038, 0x0000003c, 0x00000040, 0x00000044, 0x00000048,
+0x0000004c, 0x00000050, 0x00000054, 0x00000058, 0x0000005c, 0x00000060,
+0x00000064, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x00000068, 0x0000006c, 0x00000070, 0x00000074, 0x00000078,
+0x0000007c, 0x00000080, 0x00000084, 0x00000088, 0x0000008c, 0x00000090,
+0x00000094, 0x00000098, 0x0000009c, 0x000000a0, 0x000000a4, 0x000000a8,
+0x000000ac, 0x000000b0, 0x000000b4, 0x000000b8, 0x000000bc, 0x000000c0,
+0x000000c4, 0x000000c8, 0x000000cc, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
+};
+
+
+const uint32_t base64_table_dec_32bit_d1[256] = {
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x0000e003, 0xffffffff, 0xffffffff, 0xffffffff, 0x0000f003,
+0x00004003, 0x00005003, 0x00006003, 0x00007003, 0x00008003, 0x00009003,
+0x0000a003, 0x0000b003, 0x0000c003, 0x0000d003, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
+0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000,
+0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000,
+0x0000d000, 0x0000e000, 0x0000f000, 0x00000001, 0x00001001, 0x00002001,
+0x00003001, 0x00004001, 0x00005001, 0x00006001, 0x00007001, 0x00008001,
+0x00009001, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x0000a001, 0x0000b001, 0x0000c001, 0x0000d001, 0x0000e001,
+0x0000f001, 0x00000002, 0x00001002, 0x00002002, 0x00003002, 0x00004002,
+0x00005002, 0x00006002, 0x00007002, 0x00008002, 0x00009002, 0x0000a002,
+0x0000b002, 0x0000c002, 0x0000d002, 0x0000e002, 0x0000f002, 0x00000003,
+0x00001003, 0x00002003, 0x00003003, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
+};
+
+
+const uint32_t base64_table_dec_32bit_d2[256] = {
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x00800f00, 0xffffffff, 0xffffffff, 0xffffffff, 0x00c00f00,
+0x00000d00, 0x00400d00, 0x00800d00, 0x00c00d00, 0x00000e00, 0x00400e00,
+0x00800e00, 0x00c00e00, 0x00000f00, 0x00400f00, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
+0x00400000, 0x00800000, 0x00c00000, 0x00000100, 0x00400100, 0x00800100,
+0x00c00100, 0x00000200, 0x00400200, 0x00800200, 0x00c00200, 0x00000300,
+0x00400300, 0x00800300, 0x00c00300, 0x00000400, 0x00400400, 0x00800400,
+0x00c00400, 0x00000500, 0x00400500, 0x00800500, 0x00c00500, 0x00000600,
+0x00400600, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x00800600, 0x00c00600, 0x00000700, 0x00400700, 0x00800700,
+0x00c00700, 0x00000800, 0x00400800, 0x00800800, 0x00c00800, 0x00000900,
+0x00400900, 0x00800900, 0x00c00900, 0x00000a00, 0x00400a00, 0x00800a00,
+0x00c00a00, 0x00000b00, 0x00400b00, 0x00800b00, 0x00c00b00, 0x00000c00,
+0x00400c00, 0x00800c00, 0x00c00c00, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
+};
+
+
+const uint32_t base64_table_dec_32bit_d3[256] = {
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x003e0000, 0xffffffff, 0xffffffff, 0xffffffff, 0x003f0000,
+0x00340000, 0x00350000, 0x00360000, 0x00370000, 0x00380000, 0x00390000,
+0x003a0000, 0x003b0000, 0x003c0000, 0x003d0000, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
+0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050000, 0x00060000,
+0x00070000, 0x00080000, 0x00090000, 0x000a0000, 0x000b0000, 0x000c0000,
+0x000d0000, 0x000e0000, 0x000f0000, 0x00100000, 0x00110000, 0x00120000,
+0x00130000, 0x00140000, 0x00150000, 0x00160000, 0x00170000, 0x00180000,
+0x00190000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x001a0000, 0x001b0000, 0x001c0000, 0x001d0000, 0x001e0000,
+0x001f0000, 0x00200000, 0x00210000, 0x00220000, 0x00230000, 0x00240000,
+0x00250000, 0x00260000, 0x00270000, 0x00280000, 0x00290000, 0x002a0000,
+0x002b0000, 0x002c0000, 0x002d0000, 0x002e0000, 0x002f0000, 0x00300000,
+0x00310000, 0x00320000, 0x00330000, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
+};
+
+
+#else
+
+
+/* SPECIAL DECODE TABLES FOR BIG ENDIAN (IBM/MOTOROLA/SUN) CPUS */
+
+const uint32_t base64_table_dec_32bit_d0[256] = {
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xf8000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xfc000000,
+0xd0000000, 0xd4000000, 0xd8000000, 0xdc000000, 0xe0000000, 0xe4000000,
+0xe8000000, 0xec000000, 0xf0000000, 0xf4000000, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
+0x04000000, 0x08000000, 0x0c000000, 0x10000000, 0x14000000, 0x18000000,
+0x1c000000, 0x20000000, 0x24000000, 0x28000000, 0x2c000000, 0x30000000,
+0x34000000, 0x38000000, 0x3c000000, 0x40000000, 0x44000000, 0x48000000,
+0x4c000000, 0x50000000, 0x54000000, 0x58000000, 0x5c000000, 0x60000000,
+0x64000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x68000000, 0x6c000000, 0x70000000, 0x74000000, 0x78000000,
+0x7c000000, 0x80000000, 0x84000000, 0x88000000, 0x8c000000, 0x90000000,
+0x94000000, 0x98000000, 0x9c000000, 0xa0000000, 0xa4000000, 0xa8000000,
+0xac000000, 0xb0000000, 0xb4000000, 0xb8000000, 0xbc000000, 0xc0000000,
+0xc4000000, 0xc8000000, 0xcc000000, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
+};
+
+
+const uint32_t base64_table_dec_32bit_d1[256] = {
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x03e00000, 0xffffffff, 0xffffffff, 0xffffffff, 0x03f00000,
+0x03400000, 0x03500000, 0x03600000, 0x03700000, 0x03800000, 0x03900000,
+0x03a00000, 0x03b00000, 0x03c00000, 0x03d00000, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
+0x00100000, 0x00200000, 0x00300000, 0x00400000, 0x00500000, 0x00600000,
+0x00700000, 0x00800000, 0x00900000, 0x00a00000, 0x00b00000, 0x00c00000,
+0x00d00000, 0x00e00000, 0x00f00000, 0x01000000, 0x01100000, 0x01200000,
+0x01300000, 0x01400000, 0x01500000, 0x01600000, 0x01700000, 0x01800000,
+0x01900000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x01a00000, 0x01b00000, 0x01c00000, 0x01d00000, 0x01e00000,
+0x01f00000, 0x02000000, 0x02100000, 0x02200000, 0x02300000, 0x02400000,
+0x02500000, 0x02600000, 0x02700000, 0x02800000, 0x02900000, 0x02a00000,
+0x02b00000, 0x02c00000, 0x02d00000, 0x02e00000, 0x02f00000, 0x03000000,
+0x03100000, 0x03200000, 0x03300000, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
+};
+
+
+const uint32_t base64_table_dec_32bit_d2[256] = {
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x000f8000, 0xffffffff, 0xffffffff, 0xffffffff, 0x000fc000,
+0x000d0000, 0x000d4000, 0x000d8000, 0x000dc000, 0x000e0000, 0x000e4000,
+0x000e8000, 0x000ec000, 0x000f0000, 0x000f4000, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
+0x00004000, 0x00008000, 0x0000c000, 0x00010000, 0x00014000, 0x00018000,
+0x0001c000, 0x00020000, 0x00024000, 0x00028000, 0x0002c000, 0x00030000,
+0x00034000, 0x00038000, 0x0003c000, 0x00040000, 0x00044000, 0x00048000,
+0x0004c000, 0x00050000, 0x00054000, 0x00058000, 0x0005c000, 0x00060000,
+0x00064000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x00068000, 0x0006c000, 0x00070000, 0x00074000, 0x00078000,
+0x0007c000, 0x00080000, 0x00084000, 0x00088000, 0x0008c000, 0x00090000,
+0x00094000, 0x00098000, 0x0009c000, 0x000a0000, 0x000a4000, 0x000a8000,
+0x000ac000, 0x000b0000, 0x000b4000, 0x000b8000, 0x000bc000, 0x000c0000,
+0x000c4000, 0x000c8000, 0x000cc000, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
+};
+
+
+const uint32_t base64_table_dec_32bit_d3[256] = {
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x00003e00, 0xffffffff, 0xffffffff, 0xffffffff, 0x00003f00,
+0x00003400, 0x00003500, 0x00003600, 0x00003700, 0x00003800, 0x00003900,
+0x00003a00, 0x00003b00, 0x00003c00, 0x00003d00, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
+0x00000100, 0x00000200, 0x00000300, 0x00000400, 0x00000500, 0x00000600,
+0x00000700, 0x00000800, 0x00000900, 0x00000a00, 0x00000b00, 0x00000c00,
+0x00000d00, 0x00000e00, 0x00000f00, 0x00001000, 0x00001100, 0x00001200,
+0x00001300, 0x00001400, 0x00001500, 0x00001600, 0x00001700, 0x00001800,
+0x00001900, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0x00001a00, 0x00001b00, 0x00001c00, 0x00001d00, 0x00001e00,
+0x00001f00, 0x00002000, 0x00002100, 0x00002200, 0x00002300, 0x00002400,
+0x00002500, 0x00002600, 0x00002700, 0x00002800, 0x00002900, 0x00002a00,
+0x00002b00, 0x00002c00, 0x00002d00, 0x00002e00, 0x00002f00, 0x00003000,
+0x00003100, 0x00003200, 0x00003300, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
+};
+
+
+#endif
diff --git a/src/third-party/base64/lib/tables/table_enc_12bit.h b/src/third-party/base64/lib/tables/table_enc_12bit.h
new file mode 100644
index 0000000..2bc0d23
--- /dev/null
+++ b/src/third-party/base64/lib/tables/table_enc_12bit.h
@@ -0,0 +1,1031 @@
+#include <stdint.h>
+
+const uint16_t base64_table_enc_12bit[] = {
+#if BASE64_LITTLE_ENDIAN
+ 0x4141U, 0x4241U, 0x4341U, 0x4441U, 0x4541U, 0x4641U, 0x4741U, 0x4841U,
+ 0x4941U, 0x4A41U, 0x4B41U, 0x4C41U, 0x4D41U, 0x4E41U, 0x4F41U, 0x5041U,
+ 0x5141U, 0x5241U, 0x5341U, 0x5441U, 0x5541U, 0x5641U, 0x5741U, 0x5841U,
+ 0x5941U, 0x5A41U, 0x6141U, 0x6241U, 0x6341U, 0x6441U, 0x6541U, 0x6641U,
+ 0x6741U, 0x6841U, 0x6941U, 0x6A41U, 0x6B41U, 0x6C41U, 0x6D41U, 0x6E41U,
+ 0x6F41U, 0x7041U, 0x7141U, 0x7241U, 0x7341U, 0x7441U, 0x7541U, 0x7641U,
+ 0x7741U, 0x7841U, 0x7941U, 0x7A41U, 0x3041U, 0x3141U, 0x3241U, 0x3341U,
+ 0x3441U, 0x3541U, 0x3641U, 0x3741U, 0x3841U, 0x3941U, 0x2B41U, 0x2F41U,
+ 0x4142U, 0x4242U, 0x4342U, 0x4442U, 0x4542U, 0x4642U, 0x4742U, 0x4842U,
+ 0x4942U, 0x4A42U, 0x4B42U, 0x4C42U, 0x4D42U, 0x4E42U, 0x4F42U, 0x5042U,
+ 0x5142U, 0x5242U, 0x5342U, 0x5442U, 0x5542U, 0x5642U, 0x5742U, 0x5842U,
+ 0x5942U, 0x5A42U, 0x6142U, 0x6242U, 0x6342U, 0x6442U, 0x6542U, 0x6642U,
+ 0x6742U, 0x6842U, 0x6942U, 0x6A42U, 0x6B42U, 0x6C42U, 0x6D42U, 0x6E42U,
+ 0x6F42U, 0x7042U, 0x7142U, 0x7242U, 0x7342U, 0x7442U, 0x7542U, 0x7642U,
+ 0x7742U, 0x7842U, 0x7942U, 0x7A42U, 0x3042U, 0x3142U, 0x3242U, 0x3342U,
+ 0x3442U, 0x3542U, 0x3642U, 0x3742U, 0x3842U, 0x3942U, 0x2B42U, 0x2F42U,
+ 0x4143U, 0x4243U, 0x4343U, 0x4443U, 0x4543U, 0x4643U, 0x4743U, 0x4843U,
+ 0x4943U, 0x4A43U, 0x4B43U, 0x4C43U, 0x4D43U, 0x4E43U, 0x4F43U, 0x5043U,
+ 0x5143U, 0x5243U, 0x5343U, 0x5443U, 0x5543U, 0x5643U, 0x5743U, 0x5843U,
+ 0x5943U, 0x5A43U, 0x6143U, 0x6243U, 0x6343U, 0x6443U, 0x6543U, 0x6643U,
+ 0x6743U, 0x6843U, 0x6943U, 0x6A43U, 0x6B43U, 0x6C43U, 0x6D43U, 0x6E43U,
+ 0x6F43U, 0x7043U, 0x7143U, 0x7243U, 0x7343U, 0x7443U, 0x7543U, 0x7643U,
+ 0x7743U, 0x7843U, 0x7943U, 0x7A43U, 0x3043U, 0x3143U, 0x3243U, 0x3343U,
+ 0x3443U, 0x3543U, 0x3643U, 0x3743U, 0x3843U, 0x3943U, 0x2B43U, 0x2F43U,
+ 0x4144U, 0x4244U, 0x4344U, 0x4444U, 0x4544U, 0x4644U, 0x4744U, 0x4844U,
+ 0x4944U, 0x4A44U, 0x4B44U, 0x4C44U, 0x4D44U, 0x4E44U, 0x4F44U, 0x5044U,
+ 0x5144U, 0x5244U, 0x5344U, 0x5444U, 0x5544U, 0x5644U, 0x5744U, 0x5844U,
+ 0x5944U, 0x5A44U, 0x6144U, 0x6244U, 0x6344U, 0x6444U, 0x6544U, 0x6644U,
+ 0x6744U, 0x6844U, 0x6944U, 0x6A44U, 0x6B44U, 0x6C44U, 0x6D44U, 0x6E44U,
+ 0x6F44U, 0x7044U, 0x7144U, 0x7244U, 0x7344U, 0x7444U, 0x7544U, 0x7644U,
+ 0x7744U, 0x7844U, 0x7944U, 0x7A44U, 0x3044U, 0x3144U, 0x3244U, 0x3344U,
+ 0x3444U, 0x3544U, 0x3644U, 0x3744U, 0x3844U, 0x3944U, 0x2B44U, 0x2F44U,
+ 0x4145U, 0x4245U, 0x4345U, 0x4445U, 0x4545U, 0x4645U, 0x4745U, 0x4845U,
+ 0x4945U, 0x4A45U, 0x4B45U, 0x4C45U, 0x4D45U, 0x4E45U, 0x4F45U, 0x5045U,
+ 0x5145U, 0x5245U, 0x5345U, 0x5445U, 0x5545U, 0x5645U, 0x5745U, 0x5845U,
+ 0x5945U, 0x5A45U, 0x6145U, 0x6245U, 0x6345U, 0x6445U, 0x6545U, 0x6645U,
+ 0x6745U, 0x6845U, 0x6945U, 0x6A45U, 0x6B45U, 0x6C45U, 0x6D45U, 0x6E45U,
+ 0x6F45U, 0x7045U, 0x7145U, 0x7245U, 0x7345U, 0x7445U, 0x7545U, 0x7645U,
+ 0x7745U, 0x7845U, 0x7945U, 0x7A45U, 0x3045U, 0x3145U, 0x3245U, 0x3345U,
+ 0x3445U, 0x3545U, 0x3645U, 0x3745U, 0x3845U, 0x3945U, 0x2B45U, 0x2F45U,
+ 0x4146U, 0x4246U, 0x4346U, 0x4446U, 0x4546U, 0x4646U, 0x4746U, 0x4846U,
+ 0x4946U, 0x4A46U, 0x4B46U, 0x4C46U, 0x4D46U, 0x4E46U, 0x4F46U, 0x5046U,
+ 0x5146U, 0x5246U, 0x5346U, 0x5446U, 0x5546U, 0x5646U, 0x5746U, 0x5846U,
+ 0x5946U, 0x5A46U, 0x6146U, 0x6246U, 0x6346U, 0x6446U, 0x6546U, 0x6646U,
+ 0x6746U, 0x6846U, 0x6946U, 0x6A46U, 0x6B46U, 0x6C46U, 0x6D46U, 0x6E46U,
+ 0x6F46U, 0x7046U, 0x7146U, 0x7246U, 0x7346U, 0x7446U, 0x7546U, 0x7646U,
+ 0x7746U, 0x7846U, 0x7946U, 0x7A46U, 0x3046U, 0x3146U, 0x3246U, 0x3346U,
+ 0x3446U, 0x3546U, 0x3646U, 0x3746U, 0x3846U, 0x3946U, 0x2B46U, 0x2F46U,
+ 0x4147U, 0x4247U, 0x4347U, 0x4447U, 0x4547U, 0x4647U, 0x4747U, 0x4847U,
+ 0x4947U, 0x4A47U, 0x4B47U, 0x4C47U, 0x4D47U, 0x4E47U, 0x4F47U, 0x5047U,
+ 0x5147U, 0x5247U, 0x5347U, 0x5447U, 0x5547U, 0x5647U, 0x5747U, 0x5847U,
+ 0x5947U, 0x5A47U, 0x6147U, 0x6247U, 0x6347U, 0x6447U, 0x6547U, 0x6647U,
+ 0x6747U, 0x6847U, 0x6947U, 0x6A47U, 0x6B47U, 0x6C47U, 0x6D47U, 0x6E47U,
+ 0x6F47U, 0x7047U, 0x7147U, 0x7247U, 0x7347U, 0x7447U, 0x7547U, 0x7647U,
+ 0x7747U, 0x7847U, 0x7947U, 0x7A47U, 0x3047U, 0x3147U, 0x3247U, 0x3347U,
+ 0x3447U, 0x3547U, 0x3647U, 0x3747U, 0x3847U, 0x3947U, 0x2B47U, 0x2F47U,
+ 0x4148U, 0x4248U, 0x4348U, 0x4448U, 0x4548U, 0x4648U, 0x4748U, 0x4848U,
+ 0x4948U, 0x4A48U, 0x4B48U, 0x4C48U, 0x4D48U, 0x4E48U, 0x4F48U, 0x5048U,
+ 0x5148U, 0x5248U, 0x5348U, 0x5448U, 0x5548U, 0x5648U, 0x5748U, 0x5848U,
+ 0x5948U, 0x5A48U, 0x6148U, 0x6248U, 0x6348U, 0x6448U, 0x6548U, 0x6648U,
+ 0x6748U, 0x6848U, 0x6948U, 0x6A48U, 0x6B48U, 0x6C48U, 0x6D48U, 0x6E48U,
+ 0x6F48U, 0x7048U, 0x7148U, 0x7248U, 0x7348U, 0x7448U, 0x7548U, 0x7648U,
+ 0x7748U, 0x7848U, 0x7948U, 0x7A48U, 0x3048U, 0x3148U, 0x3248U, 0x3348U,
+ 0x3448U, 0x3548U, 0x3648U, 0x3748U, 0x3848U, 0x3948U, 0x2B48U, 0x2F48U,
+ 0x4149U, 0x4249U, 0x4349U, 0x4449U, 0x4549U, 0x4649U, 0x4749U, 0x4849U,
+ 0x4949U, 0x4A49U, 0x4B49U, 0x4C49U, 0x4D49U, 0x4E49U, 0x4F49U, 0x5049U,
+ 0x5149U, 0x5249U, 0x5349U, 0x5449U, 0x5549U, 0x5649U, 0x5749U, 0x5849U,
+ 0x5949U, 0x5A49U, 0x6149U, 0x6249U, 0x6349U, 0x6449U, 0x6549U, 0x6649U,
+ 0x6749U, 0x6849U, 0x6949U, 0x6A49U, 0x6B49U, 0x6C49U, 0x6D49U, 0x6E49U,
+ 0x6F49U, 0x7049U, 0x7149U, 0x7249U, 0x7349U, 0x7449U, 0x7549U, 0x7649U,
+ 0x7749U, 0x7849U, 0x7949U, 0x7A49U, 0x3049U, 0x3149U, 0x3249U, 0x3349U,
+ 0x3449U, 0x3549U, 0x3649U, 0x3749U, 0x3849U, 0x3949U, 0x2B49U, 0x2F49U,
+ 0x414AU, 0x424AU, 0x434AU, 0x444AU, 0x454AU, 0x464AU, 0x474AU, 0x484AU,
+ 0x494AU, 0x4A4AU, 0x4B4AU, 0x4C4AU, 0x4D4AU, 0x4E4AU, 0x4F4AU, 0x504AU,
+ 0x514AU, 0x524AU, 0x534AU, 0x544AU, 0x554AU, 0x564AU, 0x574AU, 0x584AU,
+ 0x594AU, 0x5A4AU, 0x614AU, 0x624AU, 0x634AU, 0x644AU, 0x654AU, 0x664AU,
+ 0x674AU, 0x684AU, 0x694AU, 0x6A4AU, 0x6B4AU, 0x6C4AU, 0x6D4AU, 0x6E4AU,
+ 0x6F4AU, 0x704AU, 0x714AU, 0x724AU, 0x734AU, 0x744AU, 0x754AU, 0x764AU,
+ 0x774AU, 0x784AU, 0x794AU, 0x7A4AU, 0x304AU, 0x314AU, 0x324AU, 0x334AU,
+ 0x344AU, 0x354AU, 0x364AU, 0x374AU, 0x384AU, 0x394AU, 0x2B4AU, 0x2F4AU,
+ 0x414BU, 0x424BU, 0x434BU, 0x444BU, 0x454BU, 0x464BU, 0x474BU, 0x484BU,
+ 0x494BU, 0x4A4BU, 0x4B4BU, 0x4C4BU, 0x4D4BU, 0x4E4BU, 0x4F4BU, 0x504BU,
+ 0x514BU, 0x524BU, 0x534BU, 0x544BU, 0x554BU, 0x564BU, 0x574BU, 0x584BU,
+ 0x594BU, 0x5A4BU, 0x614BU, 0x624BU, 0x634BU, 0x644BU, 0x654BU, 0x664BU,
+ 0x674BU, 0x684BU, 0x694BU, 0x6A4BU, 0x6B4BU, 0x6C4BU, 0x6D4BU, 0x6E4BU,
+ 0x6F4BU, 0x704BU, 0x714BU, 0x724BU, 0x734BU, 0x744BU, 0x754BU, 0x764BU,
+ 0x774BU, 0x784BU, 0x794BU, 0x7A4BU, 0x304BU, 0x314BU, 0x324BU, 0x334BU,
+ 0x344BU, 0x354BU, 0x364BU, 0x374BU, 0x384BU, 0x394BU, 0x2B4BU, 0x2F4BU,
+ 0x414CU, 0x424CU, 0x434CU, 0x444CU, 0x454CU, 0x464CU, 0x474CU, 0x484CU,
+ 0x494CU, 0x4A4CU, 0x4B4CU, 0x4C4CU, 0x4D4CU, 0x4E4CU, 0x4F4CU, 0x504CU,
+ 0x514CU, 0x524CU, 0x534CU, 0x544CU, 0x554CU, 0x564CU, 0x574CU, 0x584CU,
+ 0x594CU, 0x5A4CU, 0x614CU, 0x624CU, 0x634CU, 0x644CU, 0x654CU, 0x664CU,
+ 0x674CU, 0x684CU, 0x694CU, 0x6A4CU, 0x6B4CU, 0x6C4CU, 0x6D4CU, 0x6E4CU,
+ 0x6F4CU, 0x704CU, 0x714CU, 0x724CU, 0x734CU, 0x744CU, 0x754CU, 0x764CU,
+ 0x774CU, 0x784CU, 0x794CU, 0x7A4CU, 0x304CU, 0x314CU, 0x324CU, 0x334CU,
+ 0x344CU, 0x354CU, 0x364CU, 0x374CU, 0x384CU, 0x394CU, 0x2B4CU, 0x2F4CU,
+ 0x414DU, 0x424DU, 0x434DU, 0x444DU, 0x454DU, 0x464DU, 0x474DU, 0x484DU,
+ 0x494DU, 0x4A4DU, 0x4B4DU, 0x4C4DU, 0x4D4DU, 0x4E4DU, 0x4F4DU, 0x504DU,
+ 0x514DU, 0x524DU, 0x534DU, 0x544DU, 0x554DU, 0x564DU, 0x574DU, 0x584DU,
+ 0x594DU, 0x5A4DU, 0x614DU, 0x624DU, 0x634DU, 0x644DU, 0x654DU, 0x664DU,
+ 0x674DU, 0x684DU, 0x694DU, 0x6A4DU, 0x6B4DU, 0x6C4DU, 0x6D4DU, 0x6E4DU,
+ 0x6F4DU, 0x704DU, 0x714DU, 0x724DU, 0x734DU, 0x744DU, 0x754DU, 0x764DU,
+ 0x774DU, 0x784DU, 0x794DU, 0x7A4DU, 0x304DU, 0x314DU, 0x324DU, 0x334DU,
+ 0x344DU, 0x354DU, 0x364DU, 0x374DU, 0x384DU, 0x394DU, 0x2B4DU, 0x2F4DU,
+ 0x414EU, 0x424EU, 0x434EU, 0x444EU, 0x454EU, 0x464EU, 0x474EU, 0x484EU,
+ 0x494EU, 0x4A4EU, 0x4B4EU, 0x4C4EU, 0x4D4EU, 0x4E4EU, 0x4F4EU, 0x504EU,
+ 0x514EU, 0x524EU, 0x534EU, 0x544EU, 0x554EU, 0x564EU, 0x574EU, 0x584EU,
+ 0x594EU, 0x5A4EU, 0x614EU, 0x624EU, 0x634EU, 0x644EU, 0x654EU, 0x664EU,
+ 0x674EU, 0x684EU, 0x694EU, 0x6A4EU, 0x6B4EU, 0x6C4EU, 0x6D4EU, 0x6E4EU,
+ 0x6F4EU, 0x704EU, 0x714EU, 0x724EU, 0x734EU, 0x744EU, 0x754EU, 0x764EU,
+ 0x774EU, 0x784EU, 0x794EU, 0x7A4EU, 0x304EU, 0x314EU, 0x324EU, 0x334EU,
+ 0x344EU, 0x354EU, 0x364EU, 0x374EU, 0x384EU, 0x394EU, 0x2B4EU, 0x2F4EU,
+ 0x414FU, 0x424FU, 0x434FU, 0x444FU, 0x454FU, 0x464FU, 0x474FU, 0x484FU,
+ 0x494FU, 0x4A4FU, 0x4B4FU, 0x4C4FU, 0x4D4FU, 0x4E4FU, 0x4F4FU, 0x504FU,
+ 0x514FU, 0x524FU, 0x534FU, 0x544FU, 0x554FU, 0x564FU, 0x574FU, 0x584FU,
+ 0x594FU, 0x5A4FU, 0x614FU, 0x624FU, 0x634FU, 0x644FU, 0x654FU, 0x664FU,
+ 0x674FU, 0x684FU, 0x694FU, 0x6A4FU, 0x6B4FU, 0x6C4FU, 0x6D4FU, 0x6E4FU,
+ 0x6F4FU, 0x704FU, 0x714FU, 0x724FU, 0x734FU, 0x744FU, 0x754FU, 0x764FU,
+ 0x774FU, 0x784FU, 0x794FU, 0x7A4FU, 0x304FU, 0x314FU, 0x324FU, 0x334FU,
+ 0x344FU, 0x354FU, 0x364FU, 0x374FU, 0x384FU, 0x394FU, 0x2B4FU, 0x2F4FU,
+ 0x4150U, 0x4250U, 0x4350U, 0x4450U, 0x4550U, 0x4650U, 0x4750U, 0x4850U,
+ 0x4950U, 0x4A50U, 0x4B50U, 0x4C50U, 0x4D50U, 0x4E50U, 0x4F50U, 0x5050U,
+ 0x5150U, 0x5250U, 0x5350U, 0x5450U, 0x5550U, 0x5650U, 0x5750U, 0x5850U,
+ 0x5950U, 0x5A50U, 0x6150U, 0x6250U, 0x6350U, 0x6450U, 0x6550U, 0x6650U,
+ 0x6750U, 0x6850U, 0x6950U, 0x6A50U, 0x6B50U, 0x6C50U, 0x6D50U, 0x6E50U,
+ 0x6F50U, 0x7050U, 0x7150U, 0x7250U, 0x7350U, 0x7450U, 0x7550U, 0x7650U,
+ 0x7750U, 0x7850U, 0x7950U, 0x7A50U, 0x3050U, 0x3150U, 0x3250U, 0x3350U,
+ 0x3450U, 0x3550U, 0x3650U, 0x3750U, 0x3850U, 0x3950U, 0x2B50U, 0x2F50U,
+ 0x4151U, 0x4251U, 0x4351U, 0x4451U, 0x4551U, 0x4651U, 0x4751U, 0x4851U,
+ 0x4951U, 0x4A51U, 0x4B51U, 0x4C51U, 0x4D51U, 0x4E51U, 0x4F51U, 0x5051U,
+ 0x5151U, 0x5251U, 0x5351U, 0x5451U, 0x5551U, 0x5651U, 0x5751U, 0x5851U,
+ 0x5951U, 0x5A51U, 0x6151U, 0x6251U, 0x6351U, 0x6451U, 0x6551U, 0x6651U,
+ 0x6751U, 0x6851U, 0x6951U, 0x6A51U, 0x6B51U, 0x6C51U, 0x6D51U, 0x6E51U,
+ 0x6F51U, 0x7051U, 0x7151U, 0x7251U, 0x7351U, 0x7451U, 0x7551U, 0x7651U,
+ 0x7751U, 0x7851U, 0x7951U, 0x7A51U, 0x3051U, 0x3151U, 0x3251U, 0x3351U,
+ 0x3451U, 0x3551U, 0x3651U, 0x3751U, 0x3851U, 0x3951U, 0x2B51U, 0x2F51U,
+ 0x4152U, 0x4252U, 0x4352U, 0x4452U, 0x4552U, 0x4652U, 0x4752U, 0x4852U,
+ 0x4952U, 0x4A52U, 0x4B52U, 0x4C52U, 0x4D52U, 0x4E52U, 0x4F52U, 0x5052U,
+ 0x5152U, 0x5252U, 0x5352U, 0x5452U, 0x5552U, 0x5652U, 0x5752U, 0x5852U,
+ 0x5952U, 0x5A52U, 0x6152U, 0x6252U, 0x6352U, 0x6452U, 0x6552U, 0x6652U,
+ 0x6752U, 0x6852U, 0x6952U, 0x6A52U, 0x6B52U, 0x6C52U, 0x6D52U, 0x6E52U,
+ 0x6F52U, 0x7052U, 0x7152U, 0x7252U, 0x7352U, 0x7452U, 0x7552U, 0x7652U,
+ 0x7752U, 0x7852U, 0x7952U, 0x7A52U, 0x3052U, 0x3152U, 0x3252U, 0x3352U,
+ 0x3452U, 0x3552U, 0x3652U, 0x3752U, 0x3852U, 0x3952U, 0x2B52U, 0x2F52U,
+ 0x4153U, 0x4253U, 0x4353U, 0x4453U, 0x4553U, 0x4653U, 0x4753U, 0x4853U,
+ 0x4953U, 0x4A53U, 0x4B53U, 0x4C53U, 0x4D53U, 0x4E53U, 0x4F53U, 0x5053U,
+ 0x5153U, 0x5253U, 0x5353U, 0x5453U, 0x5553U, 0x5653U, 0x5753U, 0x5853U,
+ 0x5953U, 0x5A53U, 0x6153U, 0x6253U, 0x6353U, 0x6453U, 0x6553U, 0x6653U,
+ 0x6753U, 0x6853U, 0x6953U, 0x6A53U, 0x6B53U, 0x6C53U, 0x6D53U, 0x6E53U,
+ 0x6F53U, 0x7053U, 0x7153U, 0x7253U, 0x7353U, 0x7453U, 0x7553U, 0x7653U,
+ 0x7753U, 0x7853U, 0x7953U, 0x7A53U, 0x3053U, 0x3153U, 0x3253U, 0x3353U,
+ 0x3453U, 0x3553U, 0x3653U, 0x3753U, 0x3853U, 0x3953U, 0x2B53U, 0x2F53U,
+ 0x4154U, 0x4254U, 0x4354U, 0x4454U, 0x4554U, 0x4654U, 0x4754U, 0x4854U,
+ 0x4954U, 0x4A54U, 0x4B54U, 0x4C54U, 0x4D54U, 0x4E54U, 0x4F54U, 0x5054U,
+ 0x5154U, 0x5254U, 0x5354U, 0x5454U, 0x5554U, 0x5654U, 0x5754U, 0x5854U,
+ 0x5954U, 0x5A54U, 0x6154U, 0x6254U, 0x6354U, 0x6454U, 0x6554U, 0x6654U,
+ 0x6754U, 0x6854U, 0x6954U, 0x6A54U, 0x6B54U, 0x6C54U, 0x6D54U, 0x6E54U,
+ 0x6F54U, 0x7054U, 0x7154U, 0x7254U, 0x7354U, 0x7454U, 0x7554U, 0x7654U,
+ 0x7754U, 0x7854U, 0x7954U, 0x7A54U, 0x3054U, 0x3154U, 0x3254U, 0x3354U,
+ 0x3454U, 0x3554U, 0x3654U, 0x3754U, 0x3854U, 0x3954U, 0x2B54U, 0x2F54U,
+ 0x4155U, 0x4255U, 0x4355U, 0x4455U, 0x4555U, 0x4655U, 0x4755U, 0x4855U,
+ 0x4955U, 0x4A55U, 0x4B55U, 0x4C55U, 0x4D55U, 0x4E55U, 0x4F55U, 0x5055U,
+ 0x5155U, 0x5255U, 0x5355U, 0x5455U, 0x5555U, 0x5655U, 0x5755U, 0x5855U,
+ 0x5955U, 0x5A55U, 0x6155U, 0x6255U, 0x6355U, 0x6455U, 0x6555U, 0x6655U,
+ 0x6755U, 0x6855U, 0x6955U, 0x6A55U, 0x6B55U, 0x6C55U, 0x6D55U, 0x6E55U,
+ 0x6F55U, 0x7055U, 0x7155U, 0x7255U, 0x7355U, 0x7455U, 0x7555U, 0x7655U,
+ 0x7755U, 0x7855U, 0x7955U, 0x7A55U, 0x3055U, 0x3155U, 0x3255U, 0x3355U,
+ 0x3455U, 0x3555U, 0x3655U, 0x3755U, 0x3855U, 0x3955U, 0x2B55U, 0x2F55U,
+ 0x4156U, 0x4256U, 0x4356U, 0x4456U, 0x4556U, 0x4656U, 0x4756U, 0x4856U,
+ 0x4956U, 0x4A56U, 0x4B56U, 0x4C56U, 0x4D56U, 0x4E56U, 0x4F56U, 0x5056U,
+ 0x5156U, 0x5256U, 0x5356U, 0x5456U, 0x5556U, 0x5656U, 0x5756U, 0x5856U,
+ 0x5956U, 0x5A56U, 0x6156U, 0x6256U, 0x6356U, 0x6456U, 0x6556U, 0x6656U,
+ 0x6756U, 0x6856U, 0x6956U, 0x6A56U, 0x6B56U, 0x6C56U, 0x6D56U, 0x6E56U,
+ 0x6F56U, 0x7056U, 0x7156U, 0x7256U, 0x7356U, 0x7456U, 0x7556U, 0x7656U,
+ 0x7756U, 0x7856U, 0x7956U, 0x7A56U, 0x3056U, 0x3156U, 0x3256U, 0x3356U,
+ 0x3456U, 0x3556U, 0x3656U, 0x3756U, 0x3856U, 0x3956U, 0x2B56U, 0x2F56U,
+ 0x4157U, 0x4257U, 0x4357U, 0x4457U, 0x4557U, 0x4657U, 0x4757U, 0x4857U,
+ 0x4957U, 0x4A57U, 0x4B57U, 0x4C57U, 0x4D57U, 0x4E57U, 0x4F57U, 0x5057U,
+ 0x5157U, 0x5257U, 0x5357U, 0x5457U, 0x5557U, 0x5657U, 0x5757U, 0x5857U,
+ 0x5957U, 0x5A57U, 0x6157U, 0x6257U, 0x6357U, 0x6457U, 0x6557U, 0x6657U,
+ 0x6757U, 0x6857U, 0x6957U, 0x6A57U, 0x6B57U, 0x6C57U, 0x6D57U, 0x6E57U,
+ 0x6F57U, 0x7057U, 0x7157U, 0x7257U, 0x7357U, 0x7457U, 0x7557U, 0x7657U,
+ 0x7757U, 0x7857U, 0x7957U, 0x7A57U, 0x3057U, 0x3157U, 0x3257U, 0x3357U,
+ 0x3457U, 0x3557U, 0x3657U, 0x3757U, 0x3857U, 0x3957U, 0x2B57U, 0x2F57U,
+ 0x4158U, 0x4258U, 0x4358U, 0x4458U, 0x4558U, 0x4658U, 0x4758U, 0x4858U,
+ 0x4958U, 0x4A58U, 0x4B58U, 0x4C58U, 0x4D58U, 0x4E58U, 0x4F58U, 0x5058U,
+ 0x5158U, 0x5258U, 0x5358U, 0x5458U, 0x5558U, 0x5658U, 0x5758U, 0x5858U,
+ 0x5958U, 0x5A58U, 0x6158U, 0x6258U, 0x6358U, 0x6458U, 0x6558U, 0x6658U,
+ 0x6758U, 0x6858U, 0x6958U, 0x6A58U, 0x6B58U, 0x6C58U, 0x6D58U, 0x6E58U,
+ 0x6F58U, 0x7058U, 0x7158U, 0x7258U, 0x7358U, 0x7458U, 0x7558U, 0x7658U,
+ 0x7758U, 0x7858U, 0x7958U, 0x7A58U, 0x3058U, 0x3158U, 0x3258U, 0x3358U,
+ 0x3458U, 0x3558U, 0x3658U, 0x3758U, 0x3858U, 0x3958U, 0x2B58U, 0x2F58U,
+ 0x4159U, 0x4259U, 0x4359U, 0x4459U, 0x4559U, 0x4659U, 0x4759U, 0x4859U,
+ 0x4959U, 0x4A59U, 0x4B59U, 0x4C59U, 0x4D59U, 0x4E59U, 0x4F59U, 0x5059U,
+ 0x5159U, 0x5259U, 0x5359U, 0x5459U, 0x5559U, 0x5659U, 0x5759U, 0x5859U,
+ 0x5959U, 0x5A59U, 0x6159U, 0x6259U, 0x6359U, 0x6459U, 0x6559U, 0x6659U,
+ 0x6759U, 0x6859U, 0x6959U, 0x6A59U, 0x6B59U, 0x6C59U, 0x6D59U, 0x6E59U,
+ 0x6F59U, 0x7059U, 0x7159U, 0x7259U, 0x7359U, 0x7459U, 0x7559U, 0x7659U,
+ 0x7759U, 0x7859U, 0x7959U, 0x7A59U, 0x3059U, 0x3159U, 0x3259U, 0x3359U,
+ 0x3459U, 0x3559U, 0x3659U, 0x3759U, 0x3859U, 0x3959U, 0x2B59U, 0x2F59U,
+ 0x415AU, 0x425AU, 0x435AU, 0x445AU, 0x455AU, 0x465AU, 0x475AU, 0x485AU,
+ 0x495AU, 0x4A5AU, 0x4B5AU, 0x4C5AU, 0x4D5AU, 0x4E5AU, 0x4F5AU, 0x505AU,
+ 0x515AU, 0x525AU, 0x535AU, 0x545AU, 0x555AU, 0x565AU, 0x575AU, 0x585AU,
+ 0x595AU, 0x5A5AU, 0x615AU, 0x625AU, 0x635AU, 0x645AU, 0x655AU, 0x665AU,
+ 0x675AU, 0x685AU, 0x695AU, 0x6A5AU, 0x6B5AU, 0x6C5AU, 0x6D5AU, 0x6E5AU,
+ 0x6F5AU, 0x705AU, 0x715AU, 0x725AU, 0x735AU, 0x745AU, 0x755AU, 0x765AU,
+ 0x775AU, 0x785AU, 0x795AU, 0x7A5AU, 0x305AU, 0x315AU, 0x325AU, 0x335AU,
+ 0x345AU, 0x355AU, 0x365AU, 0x375AU, 0x385AU, 0x395AU, 0x2B5AU, 0x2F5AU,
+ 0x4161U, 0x4261U, 0x4361U, 0x4461U, 0x4561U, 0x4661U, 0x4761U, 0x4861U,
+ 0x4961U, 0x4A61U, 0x4B61U, 0x4C61U, 0x4D61U, 0x4E61U, 0x4F61U, 0x5061U,
+ 0x5161U, 0x5261U, 0x5361U, 0x5461U, 0x5561U, 0x5661U, 0x5761U, 0x5861U,
+ 0x5961U, 0x5A61U, 0x6161U, 0x6261U, 0x6361U, 0x6461U, 0x6561U, 0x6661U,
+ 0x6761U, 0x6861U, 0x6961U, 0x6A61U, 0x6B61U, 0x6C61U, 0x6D61U, 0x6E61U,
+ 0x6F61U, 0x7061U, 0x7161U, 0x7261U, 0x7361U, 0x7461U, 0x7561U, 0x7661U,
+ 0x7761U, 0x7861U, 0x7961U, 0x7A61U, 0x3061U, 0x3161U, 0x3261U, 0x3361U,
+ 0x3461U, 0x3561U, 0x3661U, 0x3761U, 0x3861U, 0x3961U, 0x2B61U, 0x2F61U,
+ 0x4162U, 0x4262U, 0x4362U, 0x4462U, 0x4562U, 0x4662U, 0x4762U, 0x4862U,
+ 0x4962U, 0x4A62U, 0x4B62U, 0x4C62U, 0x4D62U, 0x4E62U, 0x4F62U, 0x5062U,
+ 0x5162U, 0x5262U, 0x5362U, 0x5462U, 0x5562U, 0x5662U, 0x5762U, 0x5862U,
+ 0x5962U, 0x5A62U, 0x6162U, 0x6262U, 0x6362U, 0x6462U, 0x6562U, 0x6662U,
+ 0x6762U, 0x6862U, 0x6962U, 0x6A62U, 0x6B62U, 0x6C62U, 0x6D62U, 0x6E62U,
+ 0x6F62U, 0x7062U, 0x7162U, 0x7262U, 0x7362U, 0x7462U, 0x7562U, 0x7662U,
+ 0x7762U, 0x7862U, 0x7962U, 0x7A62U, 0x3062U, 0x3162U, 0x3262U, 0x3362U,
+ 0x3462U, 0x3562U, 0x3662U, 0x3762U, 0x3862U, 0x3962U, 0x2B62U, 0x2F62U,
+ 0x4163U, 0x4263U, 0x4363U, 0x4463U, 0x4563U, 0x4663U, 0x4763U, 0x4863U,
+ 0x4963U, 0x4A63U, 0x4B63U, 0x4C63U, 0x4D63U, 0x4E63U, 0x4F63U, 0x5063U,
+ 0x5163U, 0x5263U, 0x5363U, 0x5463U, 0x5563U, 0x5663U, 0x5763U, 0x5863U,
+ 0x5963U, 0x5A63U, 0x6163U, 0x6263U, 0x6363U, 0x6463U, 0x6563U, 0x6663U,
+ 0x6763U, 0x6863U, 0x6963U, 0x6A63U, 0x6B63U, 0x6C63U, 0x6D63U, 0x6E63U,
+ 0x6F63U, 0x7063U, 0x7163U, 0x7263U, 0x7363U, 0x7463U, 0x7563U, 0x7663U,
+ 0x7763U, 0x7863U, 0x7963U, 0x7A63U, 0x3063U, 0x3163U, 0x3263U, 0x3363U,
+ 0x3463U, 0x3563U, 0x3663U, 0x3763U, 0x3863U, 0x3963U, 0x2B63U, 0x2F63U,
+ 0x4164U, 0x4264U, 0x4364U, 0x4464U, 0x4564U, 0x4664U, 0x4764U, 0x4864U,
+ 0x4964U, 0x4A64U, 0x4B64U, 0x4C64U, 0x4D64U, 0x4E64U, 0x4F64U, 0x5064U,
+ 0x5164U, 0x5264U, 0x5364U, 0x5464U, 0x5564U, 0x5664U, 0x5764U, 0x5864U,
+ 0x5964U, 0x5A64U, 0x6164U, 0x6264U, 0x6364U, 0x6464U, 0x6564U, 0x6664U,
+ 0x6764U, 0x6864U, 0x6964U, 0x6A64U, 0x6B64U, 0x6C64U, 0x6D64U, 0x6E64U,
+ 0x6F64U, 0x7064U, 0x7164U, 0x7264U, 0x7364U, 0x7464U, 0x7564U, 0x7664U,
+ 0x7764U, 0x7864U, 0x7964U, 0x7A64U, 0x3064U, 0x3164U, 0x3264U, 0x3364U,
+ 0x3464U, 0x3564U, 0x3664U, 0x3764U, 0x3864U, 0x3964U, 0x2B64U, 0x2F64U,
+ 0x4165U, 0x4265U, 0x4365U, 0x4465U, 0x4565U, 0x4665U, 0x4765U, 0x4865U,
+ 0x4965U, 0x4A65U, 0x4B65U, 0x4C65U, 0x4D65U, 0x4E65U, 0x4F65U, 0x5065U,
+ 0x5165U, 0x5265U, 0x5365U, 0x5465U, 0x5565U, 0x5665U, 0x5765U, 0x5865U,
+ 0x5965U, 0x5A65U, 0x6165U, 0x6265U, 0x6365U, 0x6465U, 0x6565U, 0x6665U,
+ 0x6765U, 0x6865U, 0x6965U, 0x6A65U, 0x6B65U, 0x6C65U, 0x6D65U, 0x6E65U,
+ 0x6F65U, 0x7065U, 0x7165U, 0x7265U, 0x7365U, 0x7465U, 0x7565U, 0x7665U,
+ 0x7765U, 0x7865U, 0x7965U, 0x7A65U, 0x3065U, 0x3165U, 0x3265U, 0x3365U,
+ 0x3465U, 0x3565U, 0x3665U, 0x3765U, 0x3865U, 0x3965U, 0x2B65U, 0x2F65U,
+ 0x4166U, 0x4266U, 0x4366U, 0x4466U, 0x4566U, 0x4666U, 0x4766U, 0x4866U,
+ 0x4966U, 0x4A66U, 0x4B66U, 0x4C66U, 0x4D66U, 0x4E66U, 0x4F66U, 0x5066U,
+ 0x5166U, 0x5266U, 0x5366U, 0x5466U, 0x5566U, 0x5666U, 0x5766U, 0x5866U,
+ 0x5966U, 0x5A66U, 0x6166U, 0x6266U, 0x6366U, 0x6466U, 0x6566U, 0x6666U,
+ 0x6766U, 0x6866U, 0x6966U, 0x6A66U, 0x6B66U, 0x6C66U, 0x6D66U, 0x6E66U,
+ 0x6F66U, 0x7066U, 0x7166U, 0x7266U, 0x7366U, 0x7466U, 0x7566U, 0x7666U,
+ 0x7766U, 0x7866U, 0x7966U, 0x7A66U, 0x3066U, 0x3166U, 0x3266U, 0x3366U,
+ 0x3466U, 0x3566U, 0x3666U, 0x3766U, 0x3866U, 0x3966U, 0x2B66U, 0x2F66U,
+ 0x4167U, 0x4267U, 0x4367U, 0x4467U, 0x4567U, 0x4667U, 0x4767U, 0x4867U,
+ 0x4967U, 0x4A67U, 0x4B67U, 0x4C67U, 0x4D67U, 0x4E67U, 0x4F67U, 0x5067U,
+ 0x5167U, 0x5267U, 0x5367U, 0x5467U, 0x5567U, 0x5667U, 0x5767U, 0x5867U,
+ 0x5967U, 0x5A67U, 0x6167U, 0x6267U, 0x6367U, 0x6467U, 0x6567U, 0x6667U,
+ 0x6767U, 0x6867U, 0x6967U, 0x6A67U, 0x6B67U, 0x6C67U, 0x6D67U, 0x6E67U,
+ 0x6F67U, 0x7067U, 0x7167U, 0x7267U, 0x7367U, 0x7467U, 0x7567U, 0x7667U,
+ 0x7767U, 0x7867U, 0x7967U, 0x7A67U, 0x3067U, 0x3167U, 0x3267U, 0x3367U,
+ 0x3467U, 0x3567U, 0x3667U, 0x3767U, 0x3867U, 0x3967U, 0x2B67U, 0x2F67U,
+ 0x4168U, 0x4268U, 0x4368U, 0x4468U, 0x4568U, 0x4668U, 0x4768U, 0x4868U,
+ 0x4968U, 0x4A68U, 0x4B68U, 0x4C68U, 0x4D68U, 0x4E68U, 0x4F68U, 0x5068U,
+ 0x5168U, 0x5268U, 0x5368U, 0x5468U, 0x5568U, 0x5668U, 0x5768U, 0x5868U,
+ 0x5968U, 0x5A68U, 0x6168U, 0x6268U, 0x6368U, 0x6468U, 0x6568U, 0x6668U,
+ 0x6768U, 0x6868U, 0x6968U, 0x6A68U, 0x6B68U, 0x6C68U, 0x6D68U, 0x6E68U,
+ 0x6F68U, 0x7068U, 0x7168U, 0x7268U, 0x7368U, 0x7468U, 0x7568U, 0x7668U,
+ 0x7768U, 0x7868U, 0x7968U, 0x7A68U, 0x3068U, 0x3168U, 0x3268U, 0x3368U,
+ 0x3468U, 0x3568U, 0x3668U, 0x3768U, 0x3868U, 0x3968U, 0x2B68U, 0x2F68U,
+ 0x4169U, 0x4269U, 0x4369U, 0x4469U, 0x4569U, 0x4669U, 0x4769U, 0x4869U,
+ 0x4969U, 0x4A69U, 0x4B69U, 0x4C69U, 0x4D69U, 0x4E69U, 0x4F69U, 0x5069U,
+ 0x5169U, 0x5269U, 0x5369U, 0x5469U, 0x5569U, 0x5669U, 0x5769U, 0x5869U,
+ 0x5969U, 0x5A69U, 0x6169U, 0x6269U, 0x6369U, 0x6469U, 0x6569U, 0x6669U,
+ 0x6769U, 0x6869U, 0x6969U, 0x6A69U, 0x6B69U, 0x6C69U, 0x6D69U, 0x6E69U,
+ 0x6F69U, 0x7069U, 0x7169U, 0x7269U, 0x7369U, 0x7469U, 0x7569U, 0x7669U,
+ 0x7769U, 0x7869U, 0x7969U, 0x7A69U, 0x3069U, 0x3169U, 0x3269U, 0x3369U,
+ 0x3469U, 0x3569U, 0x3669U, 0x3769U, 0x3869U, 0x3969U, 0x2B69U, 0x2F69U,
+ 0x416AU, 0x426AU, 0x436AU, 0x446AU, 0x456AU, 0x466AU, 0x476AU, 0x486AU,
+ 0x496AU, 0x4A6AU, 0x4B6AU, 0x4C6AU, 0x4D6AU, 0x4E6AU, 0x4F6AU, 0x506AU,
+ 0x516AU, 0x526AU, 0x536AU, 0x546AU, 0x556AU, 0x566AU, 0x576AU, 0x586AU,
+ 0x596AU, 0x5A6AU, 0x616AU, 0x626AU, 0x636AU, 0x646AU, 0x656AU, 0x666AU,
+ 0x676AU, 0x686AU, 0x696AU, 0x6A6AU, 0x6B6AU, 0x6C6AU, 0x6D6AU, 0x6E6AU,
+ 0x6F6AU, 0x706AU, 0x716AU, 0x726AU, 0x736AU, 0x746AU, 0x756AU, 0x766AU,
+ 0x776AU, 0x786AU, 0x796AU, 0x7A6AU, 0x306AU, 0x316AU, 0x326AU, 0x336AU,
+ 0x346AU, 0x356AU, 0x366AU, 0x376AU, 0x386AU, 0x396AU, 0x2B6AU, 0x2F6AU,
+ 0x416BU, 0x426BU, 0x436BU, 0x446BU, 0x456BU, 0x466BU, 0x476BU, 0x486BU,
+ 0x496BU, 0x4A6BU, 0x4B6BU, 0x4C6BU, 0x4D6BU, 0x4E6BU, 0x4F6BU, 0x506BU,
+ 0x516BU, 0x526BU, 0x536BU, 0x546BU, 0x556BU, 0x566BU, 0x576BU, 0x586BU,
+ 0x596BU, 0x5A6BU, 0x616BU, 0x626BU, 0x636BU, 0x646BU, 0x656BU, 0x666BU,
+ 0x676BU, 0x686BU, 0x696BU, 0x6A6BU, 0x6B6BU, 0x6C6BU, 0x6D6BU, 0x6E6BU,
+ 0x6F6BU, 0x706BU, 0x716BU, 0x726BU, 0x736BU, 0x746BU, 0x756BU, 0x766BU,
+ 0x776BU, 0x786BU, 0x796BU, 0x7A6BU, 0x306BU, 0x316BU, 0x326BU, 0x336BU,
+ 0x346BU, 0x356BU, 0x366BU, 0x376BU, 0x386BU, 0x396BU, 0x2B6BU, 0x2F6BU,
+ 0x416CU, 0x426CU, 0x436CU, 0x446CU, 0x456CU, 0x466CU, 0x476CU, 0x486CU,
+ 0x496CU, 0x4A6CU, 0x4B6CU, 0x4C6CU, 0x4D6CU, 0x4E6CU, 0x4F6CU, 0x506CU,
+ 0x516CU, 0x526CU, 0x536CU, 0x546CU, 0x556CU, 0x566CU, 0x576CU, 0x586CU,
+ 0x596CU, 0x5A6CU, 0x616CU, 0x626CU, 0x636CU, 0x646CU, 0x656CU, 0x666CU,
+ 0x676CU, 0x686CU, 0x696CU, 0x6A6CU, 0x6B6CU, 0x6C6CU, 0x6D6CU, 0x6E6CU,
+ 0x6F6CU, 0x706CU, 0x716CU, 0x726CU, 0x736CU, 0x746CU, 0x756CU, 0x766CU,
+ 0x776CU, 0x786CU, 0x796CU, 0x7A6CU, 0x306CU, 0x316CU, 0x326CU, 0x336CU,
+ 0x346CU, 0x356CU, 0x366CU, 0x376CU, 0x386CU, 0x396CU, 0x2B6CU, 0x2F6CU,
+ 0x416DU, 0x426DU, 0x436DU, 0x446DU, 0x456DU, 0x466DU, 0x476DU, 0x486DU,
+ 0x496DU, 0x4A6DU, 0x4B6DU, 0x4C6DU, 0x4D6DU, 0x4E6DU, 0x4F6DU, 0x506DU,
+ 0x516DU, 0x526DU, 0x536DU, 0x546DU, 0x556DU, 0x566DU, 0x576DU, 0x586DU,
+ 0x596DU, 0x5A6DU, 0x616DU, 0x626DU, 0x636DU, 0x646DU, 0x656DU, 0x666DU,
+ 0x676DU, 0x686DU, 0x696DU, 0x6A6DU, 0x6B6DU, 0x6C6DU, 0x6D6DU, 0x6E6DU,
+ 0x6F6DU, 0x706DU, 0x716DU, 0x726DU, 0x736DU, 0x746DU, 0x756DU, 0x766DU,
+ 0x776DU, 0x786DU, 0x796DU, 0x7A6DU, 0x306DU, 0x316DU, 0x326DU, 0x336DU,
+ 0x346DU, 0x356DU, 0x366DU, 0x376DU, 0x386DU, 0x396DU, 0x2B6DU, 0x2F6DU,
+ 0x416EU, 0x426EU, 0x436EU, 0x446EU, 0x456EU, 0x466EU, 0x476EU, 0x486EU,
+ 0x496EU, 0x4A6EU, 0x4B6EU, 0x4C6EU, 0x4D6EU, 0x4E6EU, 0x4F6EU, 0x506EU,
+ 0x516EU, 0x526EU, 0x536EU, 0x546EU, 0x556EU, 0x566EU, 0x576EU, 0x586EU,
+ 0x596EU, 0x5A6EU, 0x616EU, 0x626EU, 0x636EU, 0x646EU, 0x656EU, 0x666EU,
+ 0x676EU, 0x686EU, 0x696EU, 0x6A6EU, 0x6B6EU, 0x6C6EU, 0x6D6EU, 0x6E6EU,
+ 0x6F6EU, 0x706EU, 0x716EU, 0x726EU, 0x736EU, 0x746EU, 0x756EU, 0x766EU,
+ 0x776EU, 0x786EU, 0x796EU, 0x7A6EU, 0x306EU, 0x316EU, 0x326EU, 0x336EU,
+ 0x346EU, 0x356EU, 0x366EU, 0x376EU, 0x386EU, 0x396EU, 0x2B6EU, 0x2F6EU,
+ 0x416FU, 0x426FU, 0x436FU, 0x446FU, 0x456FU, 0x466FU, 0x476FU, 0x486FU,
+ 0x496FU, 0x4A6FU, 0x4B6FU, 0x4C6FU, 0x4D6FU, 0x4E6FU, 0x4F6FU, 0x506FU,
+ 0x516FU, 0x526FU, 0x536FU, 0x546FU, 0x556FU, 0x566FU, 0x576FU, 0x586FU,
+ 0x596FU, 0x5A6FU, 0x616FU, 0x626FU, 0x636FU, 0x646FU, 0x656FU, 0x666FU,
+ 0x676FU, 0x686FU, 0x696FU, 0x6A6FU, 0x6B6FU, 0x6C6FU, 0x6D6FU, 0x6E6FU,
+ 0x6F6FU, 0x706FU, 0x716FU, 0x726FU, 0x736FU, 0x746FU, 0x756FU, 0x766FU,
+ 0x776FU, 0x786FU, 0x796FU, 0x7A6FU, 0x306FU, 0x316FU, 0x326FU, 0x336FU,
+ 0x346FU, 0x356FU, 0x366FU, 0x376FU, 0x386FU, 0x396FU, 0x2B6FU, 0x2F6FU,
+ 0x4170U, 0x4270U, 0x4370U, 0x4470U, 0x4570U, 0x4670U, 0x4770U, 0x4870U,
+ 0x4970U, 0x4A70U, 0x4B70U, 0x4C70U, 0x4D70U, 0x4E70U, 0x4F70U, 0x5070U,
+ 0x5170U, 0x5270U, 0x5370U, 0x5470U, 0x5570U, 0x5670U, 0x5770U, 0x5870U,
+ 0x5970U, 0x5A70U, 0x6170U, 0x6270U, 0x6370U, 0x6470U, 0x6570U, 0x6670U,
+ 0x6770U, 0x6870U, 0x6970U, 0x6A70U, 0x6B70U, 0x6C70U, 0x6D70U, 0x6E70U,
+ 0x6F70U, 0x7070U, 0x7170U, 0x7270U, 0x7370U, 0x7470U, 0x7570U, 0x7670U,
+ 0x7770U, 0x7870U, 0x7970U, 0x7A70U, 0x3070U, 0x3170U, 0x3270U, 0x3370U,
+ 0x3470U, 0x3570U, 0x3670U, 0x3770U, 0x3870U, 0x3970U, 0x2B70U, 0x2F70U,
+ 0x4171U, 0x4271U, 0x4371U, 0x4471U, 0x4571U, 0x4671U, 0x4771U, 0x4871U,
+ 0x4971U, 0x4A71U, 0x4B71U, 0x4C71U, 0x4D71U, 0x4E71U, 0x4F71U, 0x5071U,
+ 0x5171U, 0x5271U, 0x5371U, 0x5471U, 0x5571U, 0x5671U, 0x5771U, 0x5871U,
+ 0x5971U, 0x5A71U, 0x6171U, 0x6271U, 0x6371U, 0x6471U, 0x6571U, 0x6671U,
+ 0x6771U, 0x6871U, 0x6971U, 0x6A71U, 0x6B71U, 0x6C71U, 0x6D71U, 0x6E71U,
+ 0x6F71U, 0x7071U, 0x7171U, 0x7271U, 0x7371U, 0x7471U, 0x7571U, 0x7671U,
+ 0x7771U, 0x7871U, 0x7971U, 0x7A71U, 0x3071U, 0x3171U, 0x3271U, 0x3371U,
+ 0x3471U, 0x3571U, 0x3671U, 0x3771U, 0x3871U, 0x3971U, 0x2B71U, 0x2F71U,
+ 0x4172U, 0x4272U, 0x4372U, 0x4472U, 0x4572U, 0x4672U, 0x4772U, 0x4872U,
+ 0x4972U, 0x4A72U, 0x4B72U, 0x4C72U, 0x4D72U, 0x4E72U, 0x4F72U, 0x5072U,
+ 0x5172U, 0x5272U, 0x5372U, 0x5472U, 0x5572U, 0x5672U, 0x5772U, 0x5872U,
+ 0x5972U, 0x5A72U, 0x6172U, 0x6272U, 0x6372U, 0x6472U, 0x6572U, 0x6672U,
+ 0x6772U, 0x6872U, 0x6972U, 0x6A72U, 0x6B72U, 0x6C72U, 0x6D72U, 0x6E72U,
+ 0x6F72U, 0x7072U, 0x7172U, 0x7272U, 0x7372U, 0x7472U, 0x7572U, 0x7672U,
+ 0x7772U, 0x7872U, 0x7972U, 0x7A72U, 0x3072U, 0x3172U, 0x3272U, 0x3372U,
+ 0x3472U, 0x3572U, 0x3672U, 0x3772U, 0x3872U, 0x3972U, 0x2B72U, 0x2F72U,
+ 0x4173U, 0x4273U, 0x4373U, 0x4473U, 0x4573U, 0x4673U, 0x4773U, 0x4873U,
+ 0x4973U, 0x4A73U, 0x4B73U, 0x4C73U, 0x4D73U, 0x4E73U, 0x4F73U, 0x5073U,
+ 0x5173U, 0x5273U, 0x5373U, 0x5473U, 0x5573U, 0x5673U, 0x5773U, 0x5873U,
+ 0x5973U, 0x5A73U, 0x6173U, 0x6273U, 0x6373U, 0x6473U, 0x6573U, 0x6673U,
+ 0x6773U, 0x6873U, 0x6973U, 0x6A73U, 0x6B73U, 0x6C73U, 0x6D73U, 0x6E73U,
+ 0x6F73U, 0x7073U, 0x7173U, 0x7273U, 0x7373U, 0x7473U, 0x7573U, 0x7673U,
+ 0x7773U, 0x7873U, 0x7973U, 0x7A73U, 0x3073U, 0x3173U, 0x3273U, 0x3373U,
+ 0x3473U, 0x3573U, 0x3673U, 0x3773U, 0x3873U, 0x3973U, 0x2B73U, 0x2F73U,
+ 0x4174U, 0x4274U, 0x4374U, 0x4474U, 0x4574U, 0x4674U, 0x4774U, 0x4874U,
+ 0x4974U, 0x4A74U, 0x4B74U, 0x4C74U, 0x4D74U, 0x4E74U, 0x4F74U, 0x5074U,
+ 0x5174U, 0x5274U, 0x5374U, 0x5474U, 0x5574U, 0x5674U, 0x5774U, 0x5874U,
+ 0x5974U, 0x5A74U, 0x6174U, 0x6274U, 0x6374U, 0x6474U, 0x6574U, 0x6674U,
+ 0x6774U, 0x6874U, 0x6974U, 0x6A74U, 0x6B74U, 0x6C74U, 0x6D74U, 0x6E74U,
+ 0x6F74U, 0x7074U, 0x7174U, 0x7274U, 0x7374U, 0x7474U, 0x7574U, 0x7674U,
+ 0x7774U, 0x7874U, 0x7974U, 0x7A74U, 0x3074U, 0x3174U, 0x3274U, 0x3374U,
+ 0x3474U, 0x3574U, 0x3674U, 0x3774U, 0x3874U, 0x3974U, 0x2B74U, 0x2F74U,
+ 0x4175U, 0x4275U, 0x4375U, 0x4475U, 0x4575U, 0x4675U, 0x4775U, 0x4875U,
+ 0x4975U, 0x4A75U, 0x4B75U, 0x4C75U, 0x4D75U, 0x4E75U, 0x4F75U, 0x5075U,
+ 0x5175U, 0x5275U, 0x5375U, 0x5475U, 0x5575U, 0x5675U, 0x5775U, 0x5875U,
+ 0x5975U, 0x5A75U, 0x6175U, 0x6275U, 0x6375U, 0x6475U, 0x6575U, 0x6675U,
+ 0x6775U, 0x6875U, 0x6975U, 0x6A75U, 0x6B75U, 0x6C75U, 0x6D75U, 0x6E75U,
+ 0x6F75U, 0x7075U, 0x7175U, 0x7275U, 0x7375U, 0x7475U, 0x7575U, 0x7675U,
+ 0x7775U, 0x7875U, 0x7975U, 0x7A75U, 0x3075U, 0x3175U, 0x3275U, 0x3375U,
+ 0x3475U, 0x3575U, 0x3675U, 0x3775U, 0x3875U, 0x3975U, 0x2B75U, 0x2F75U,
+ 0x4176U, 0x4276U, 0x4376U, 0x4476U, 0x4576U, 0x4676U, 0x4776U, 0x4876U,
+ 0x4976U, 0x4A76U, 0x4B76U, 0x4C76U, 0x4D76U, 0x4E76U, 0x4F76U, 0x5076U,
+ 0x5176U, 0x5276U, 0x5376U, 0x5476U, 0x5576U, 0x5676U, 0x5776U, 0x5876U,
+ 0x5976U, 0x5A76U, 0x6176U, 0x6276U, 0x6376U, 0x6476U, 0x6576U, 0x6676U,
+ 0x6776U, 0x6876U, 0x6976U, 0x6A76U, 0x6B76U, 0x6C76U, 0x6D76U, 0x6E76U,
+ 0x6F76U, 0x7076U, 0x7176U, 0x7276U, 0x7376U, 0x7476U, 0x7576U, 0x7676U,
+ 0x7776U, 0x7876U, 0x7976U, 0x7A76U, 0x3076U, 0x3176U, 0x3276U, 0x3376U,
+ 0x3476U, 0x3576U, 0x3676U, 0x3776U, 0x3876U, 0x3976U, 0x2B76U, 0x2F76U,
+ 0x4177U, 0x4277U, 0x4377U, 0x4477U, 0x4577U, 0x4677U, 0x4777U, 0x4877U,
+ 0x4977U, 0x4A77U, 0x4B77U, 0x4C77U, 0x4D77U, 0x4E77U, 0x4F77U, 0x5077U,
+ 0x5177U, 0x5277U, 0x5377U, 0x5477U, 0x5577U, 0x5677U, 0x5777U, 0x5877U,
+ 0x5977U, 0x5A77U, 0x6177U, 0x6277U, 0x6377U, 0x6477U, 0x6577U, 0x6677U,
+ 0x6777U, 0x6877U, 0x6977U, 0x6A77U, 0x6B77U, 0x6C77U, 0x6D77U, 0x6E77U,
+ 0x6F77U, 0x7077U, 0x7177U, 0x7277U, 0x7377U, 0x7477U, 0x7577U, 0x7677U,
+ 0x7777U, 0x7877U, 0x7977U, 0x7A77U, 0x3077U, 0x3177U, 0x3277U, 0x3377U,
+ 0x3477U, 0x3577U, 0x3677U, 0x3777U, 0x3877U, 0x3977U, 0x2B77U, 0x2F77U,
+ 0x4178U, 0x4278U, 0x4378U, 0x4478U, 0x4578U, 0x4678U, 0x4778U, 0x4878U,
+ 0x4978U, 0x4A78U, 0x4B78U, 0x4C78U, 0x4D78U, 0x4E78U, 0x4F78U, 0x5078U,
+ 0x5178U, 0x5278U, 0x5378U, 0x5478U, 0x5578U, 0x5678U, 0x5778U, 0x5878U,
+ 0x5978U, 0x5A78U, 0x6178U, 0x6278U, 0x6378U, 0x6478U, 0x6578U, 0x6678U,
+ 0x6778U, 0x6878U, 0x6978U, 0x6A78U, 0x6B78U, 0x6C78U, 0x6D78U, 0x6E78U,
+ 0x6F78U, 0x7078U, 0x7178U, 0x7278U, 0x7378U, 0x7478U, 0x7578U, 0x7678U,
+ 0x7778U, 0x7878U, 0x7978U, 0x7A78U, 0x3078U, 0x3178U, 0x3278U, 0x3378U,
+ 0x3478U, 0x3578U, 0x3678U, 0x3778U, 0x3878U, 0x3978U, 0x2B78U, 0x2F78U,
+ 0x4179U, 0x4279U, 0x4379U, 0x4479U, 0x4579U, 0x4679U, 0x4779U, 0x4879U,
+ 0x4979U, 0x4A79U, 0x4B79U, 0x4C79U, 0x4D79U, 0x4E79U, 0x4F79U, 0x5079U,
+ 0x5179U, 0x5279U, 0x5379U, 0x5479U, 0x5579U, 0x5679U, 0x5779U, 0x5879U,
+ 0x5979U, 0x5A79U, 0x6179U, 0x6279U, 0x6379U, 0x6479U, 0x6579U, 0x6679U,
+ 0x6779U, 0x6879U, 0x6979U, 0x6A79U, 0x6B79U, 0x6C79U, 0x6D79U, 0x6E79U,
+ 0x6F79U, 0x7079U, 0x7179U, 0x7279U, 0x7379U, 0x7479U, 0x7579U, 0x7679U,
+ 0x7779U, 0x7879U, 0x7979U, 0x7A79U, 0x3079U, 0x3179U, 0x3279U, 0x3379U,
+ 0x3479U, 0x3579U, 0x3679U, 0x3779U, 0x3879U, 0x3979U, 0x2B79U, 0x2F79U,
+ 0x417AU, 0x427AU, 0x437AU, 0x447AU, 0x457AU, 0x467AU, 0x477AU, 0x487AU,
+ 0x497AU, 0x4A7AU, 0x4B7AU, 0x4C7AU, 0x4D7AU, 0x4E7AU, 0x4F7AU, 0x507AU,
+ 0x517AU, 0x527AU, 0x537AU, 0x547AU, 0x557AU, 0x567AU, 0x577AU, 0x587AU,
+ 0x597AU, 0x5A7AU, 0x617AU, 0x627AU, 0x637AU, 0x647AU, 0x657AU, 0x667AU,
+ 0x677AU, 0x687AU, 0x697AU, 0x6A7AU, 0x6B7AU, 0x6C7AU, 0x6D7AU, 0x6E7AU,
+ 0x6F7AU, 0x707AU, 0x717AU, 0x727AU, 0x737AU, 0x747AU, 0x757AU, 0x767AU,
+ 0x777AU, 0x787AU, 0x797AU, 0x7A7AU, 0x307AU, 0x317AU, 0x327AU, 0x337AU,
+ 0x347AU, 0x357AU, 0x367AU, 0x377AU, 0x387AU, 0x397AU, 0x2B7AU, 0x2F7AU,
+ 0x4130U, 0x4230U, 0x4330U, 0x4430U, 0x4530U, 0x4630U, 0x4730U, 0x4830U,
+ 0x4930U, 0x4A30U, 0x4B30U, 0x4C30U, 0x4D30U, 0x4E30U, 0x4F30U, 0x5030U,
+ 0x5130U, 0x5230U, 0x5330U, 0x5430U, 0x5530U, 0x5630U, 0x5730U, 0x5830U,
+ 0x5930U, 0x5A30U, 0x6130U, 0x6230U, 0x6330U, 0x6430U, 0x6530U, 0x6630U,
+ 0x6730U, 0x6830U, 0x6930U, 0x6A30U, 0x6B30U, 0x6C30U, 0x6D30U, 0x6E30U,
+ 0x6F30U, 0x7030U, 0x7130U, 0x7230U, 0x7330U, 0x7430U, 0x7530U, 0x7630U,
+ 0x7730U, 0x7830U, 0x7930U, 0x7A30U, 0x3030U, 0x3130U, 0x3230U, 0x3330U,
+ 0x3430U, 0x3530U, 0x3630U, 0x3730U, 0x3830U, 0x3930U, 0x2B30U, 0x2F30U,
+ 0x4131U, 0x4231U, 0x4331U, 0x4431U, 0x4531U, 0x4631U, 0x4731U, 0x4831U,
+ 0x4931U, 0x4A31U, 0x4B31U, 0x4C31U, 0x4D31U, 0x4E31U, 0x4F31U, 0x5031U,
+ 0x5131U, 0x5231U, 0x5331U, 0x5431U, 0x5531U, 0x5631U, 0x5731U, 0x5831U,
+ 0x5931U, 0x5A31U, 0x6131U, 0x6231U, 0x6331U, 0x6431U, 0x6531U, 0x6631U,
+ 0x6731U, 0x6831U, 0x6931U, 0x6A31U, 0x6B31U, 0x6C31U, 0x6D31U, 0x6E31U,
+ 0x6F31U, 0x7031U, 0x7131U, 0x7231U, 0x7331U, 0x7431U, 0x7531U, 0x7631U,
+ 0x7731U, 0x7831U, 0x7931U, 0x7A31U, 0x3031U, 0x3131U, 0x3231U, 0x3331U,
+ 0x3431U, 0x3531U, 0x3631U, 0x3731U, 0x3831U, 0x3931U, 0x2B31U, 0x2F31U,
+ 0x4132U, 0x4232U, 0x4332U, 0x4432U, 0x4532U, 0x4632U, 0x4732U, 0x4832U,
+ 0x4932U, 0x4A32U, 0x4B32U, 0x4C32U, 0x4D32U, 0x4E32U, 0x4F32U, 0x5032U,
+ 0x5132U, 0x5232U, 0x5332U, 0x5432U, 0x5532U, 0x5632U, 0x5732U, 0x5832U,
+ 0x5932U, 0x5A32U, 0x6132U, 0x6232U, 0x6332U, 0x6432U, 0x6532U, 0x6632U,
+ 0x6732U, 0x6832U, 0x6932U, 0x6A32U, 0x6B32U, 0x6C32U, 0x6D32U, 0x6E32U,
+ 0x6F32U, 0x7032U, 0x7132U, 0x7232U, 0x7332U, 0x7432U, 0x7532U, 0x7632U,
+ 0x7732U, 0x7832U, 0x7932U, 0x7A32U, 0x3032U, 0x3132U, 0x3232U, 0x3332U,
+ 0x3432U, 0x3532U, 0x3632U, 0x3732U, 0x3832U, 0x3932U, 0x2B32U, 0x2F32U,
+ 0x4133U, 0x4233U, 0x4333U, 0x4433U, 0x4533U, 0x4633U, 0x4733U, 0x4833U,
+ 0x4933U, 0x4A33U, 0x4B33U, 0x4C33U, 0x4D33U, 0x4E33U, 0x4F33U, 0x5033U,
+ 0x5133U, 0x5233U, 0x5333U, 0x5433U, 0x5533U, 0x5633U, 0x5733U, 0x5833U,
+ 0x5933U, 0x5A33U, 0x6133U, 0x6233U, 0x6333U, 0x6433U, 0x6533U, 0x6633U,
+ 0x6733U, 0x6833U, 0x6933U, 0x6A33U, 0x6B33U, 0x6C33U, 0x6D33U, 0x6E33U,
+ 0x6F33U, 0x7033U, 0x7133U, 0x7233U, 0x7333U, 0x7433U, 0x7533U, 0x7633U,
+ 0x7733U, 0x7833U, 0x7933U, 0x7A33U, 0x3033U, 0x3133U, 0x3233U, 0x3333U,
+ 0x3433U, 0x3533U, 0x3633U, 0x3733U, 0x3833U, 0x3933U, 0x2B33U, 0x2F33U,
+ 0x4134U, 0x4234U, 0x4334U, 0x4434U, 0x4534U, 0x4634U, 0x4734U, 0x4834U,
+ 0x4934U, 0x4A34U, 0x4B34U, 0x4C34U, 0x4D34U, 0x4E34U, 0x4F34U, 0x5034U,
+ 0x5134U, 0x5234U, 0x5334U, 0x5434U, 0x5534U, 0x5634U, 0x5734U, 0x5834U,
+ 0x5934U, 0x5A34U, 0x6134U, 0x6234U, 0x6334U, 0x6434U, 0x6534U, 0x6634U,
+ 0x6734U, 0x6834U, 0x6934U, 0x6A34U, 0x6B34U, 0x6C34U, 0x6D34U, 0x6E34U,
+ 0x6F34U, 0x7034U, 0x7134U, 0x7234U, 0x7334U, 0x7434U, 0x7534U, 0x7634U,
+ 0x7734U, 0x7834U, 0x7934U, 0x7A34U, 0x3034U, 0x3134U, 0x3234U, 0x3334U,
+ 0x3434U, 0x3534U, 0x3634U, 0x3734U, 0x3834U, 0x3934U, 0x2B34U, 0x2F34U,
+ 0x4135U, 0x4235U, 0x4335U, 0x4435U, 0x4535U, 0x4635U, 0x4735U, 0x4835U,
+ 0x4935U, 0x4A35U, 0x4B35U, 0x4C35U, 0x4D35U, 0x4E35U, 0x4F35U, 0x5035U,
+ 0x5135U, 0x5235U, 0x5335U, 0x5435U, 0x5535U, 0x5635U, 0x5735U, 0x5835U,
+ 0x5935U, 0x5A35U, 0x6135U, 0x6235U, 0x6335U, 0x6435U, 0x6535U, 0x6635U,
+ 0x6735U, 0x6835U, 0x6935U, 0x6A35U, 0x6B35U, 0x6C35U, 0x6D35U, 0x6E35U,
+ 0x6F35U, 0x7035U, 0x7135U, 0x7235U, 0x7335U, 0x7435U, 0x7535U, 0x7635U,
+ 0x7735U, 0x7835U, 0x7935U, 0x7A35U, 0x3035U, 0x3135U, 0x3235U, 0x3335U,
+ 0x3435U, 0x3535U, 0x3635U, 0x3735U, 0x3835U, 0x3935U, 0x2B35U, 0x2F35U,
+ 0x4136U, 0x4236U, 0x4336U, 0x4436U, 0x4536U, 0x4636U, 0x4736U, 0x4836U,
+ 0x4936U, 0x4A36U, 0x4B36U, 0x4C36U, 0x4D36U, 0x4E36U, 0x4F36U, 0x5036U,
+ 0x5136U, 0x5236U, 0x5336U, 0x5436U, 0x5536U, 0x5636U, 0x5736U, 0x5836U,
+ 0x5936U, 0x5A36U, 0x6136U, 0x6236U, 0x6336U, 0x6436U, 0x6536U, 0x6636U,
+ 0x6736U, 0x6836U, 0x6936U, 0x6A36U, 0x6B36U, 0x6C36U, 0x6D36U, 0x6E36U,
+ 0x6F36U, 0x7036U, 0x7136U, 0x7236U, 0x7336U, 0x7436U, 0x7536U, 0x7636U,
+ 0x7736U, 0x7836U, 0x7936U, 0x7A36U, 0x3036U, 0x3136U, 0x3236U, 0x3336U,
+ 0x3436U, 0x3536U, 0x3636U, 0x3736U, 0x3836U, 0x3936U, 0x2B36U, 0x2F36U,
+ 0x4137U, 0x4237U, 0x4337U, 0x4437U, 0x4537U, 0x4637U, 0x4737U, 0x4837U,
+ 0x4937U, 0x4A37U, 0x4B37U, 0x4C37U, 0x4D37U, 0x4E37U, 0x4F37U, 0x5037U,
+ 0x5137U, 0x5237U, 0x5337U, 0x5437U, 0x5537U, 0x5637U, 0x5737U, 0x5837U,
+ 0x5937U, 0x5A37U, 0x6137U, 0x6237U, 0x6337U, 0x6437U, 0x6537U, 0x6637U,
+ 0x6737U, 0x6837U, 0x6937U, 0x6A37U, 0x6B37U, 0x6C37U, 0x6D37U, 0x6E37U,
+ 0x6F37U, 0x7037U, 0x7137U, 0x7237U, 0x7337U, 0x7437U, 0x7537U, 0x7637U,
+ 0x7737U, 0x7837U, 0x7937U, 0x7A37U, 0x3037U, 0x3137U, 0x3237U, 0x3337U,
+ 0x3437U, 0x3537U, 0x3637U, 0x3737U, 0x3837U, 0x3937U, 0x2B37U, 0x2F37U,
+ 0x4138U, 0x4238U, 0x4338U, 0x4438U, 0x4538U, 0x4638U, 0x4738U, 0x4838U,
+ 0x4938U, 0x4A38U, 0x4B38U, 0x4C38U, 0x4D38U, 0x4E38U, 0x4F38U, 0x5038U,
+ 0x5138U, 0x5238U, 0x5338U, 0x5438U, 0x5538U, 0x5638U, 0x5738U, 0x5838U,
+ 0x5938U, 0x5A38U, 0x6138U, 0x6238U, 0x6338U, 0x6438U, 0x6538U, 0x6638U,
+ 0x6738U, 0x6838U, 0x6938U, 0x6A38U, 0x6B38U, 0x6C38U, 0x6D38U, 0x6E38U,
+ 0x6F38U, 0x7038U, 0x7138U, 0x7238U, 0x7338U, 0x7438U, 0x7538U, 0x7638U,
+ 0x7738U, 0x7838U, 0x7938U, 0x7A38U, 0x3038U, 0x3138U, 0x3238U, 0x3338U,
+ 0x3438U, 0x3538U, 0x3638U, 0x3738U, 0x3838U, 0x3938U, 0x2B38U, 0x2F38U,
+ 0x4139U, 0x4239U, 0x4339U, 0x4439U, 0x4539U, 0x4639U, 0x4739U, 0x4839U,
+ 0x4939U, 0x4A39U, 0x4B39U, 0x4C39U, 0x4D39U, 0x4E39U, 0x4F39U, 0x5039U,
+ 0x5139U, 0x5239U, 0x5339U, 0x5439U, 0x5539U, 0x5639U, 0x5739U, 0x5839U,
+ 0x5939U, 0x5A39U, 0x6139U, 0x6239U, 0x6339U, 0x6439U, 0x6539U, 0x6639U,
+ 0x6739U, 0x6839U, 0x6939U, 0x6A39U, 0x6B39U, 0x6C39U, 0x6D39U, 0x6E39U,
+ 0x6F39U, 0x7039U, 0x7139U, 0x7239U, 0x7339U, 0x7439U, 0x7539U, 0x7639U,
+ 0x7739U, 0x7839U, 0x7939U, 0x7A39U, 0x3039U, 0x3139U, 0x3239U, 0x3339U,
+ 0x3439U, 0x3539U, 0x3639U, 0x3739U, 0x3839U, 0x3939U, 0x2B39U, 0x2F39U,
+ 0x412BU, 0x422BU, 0x432BU, 0x442BU, 0x452BU, 0x462BU, 0x472BU, 0x482BU,
+ 0x492BU, 0x4A2BU, 0x4B2BU, 0x4C2BU, 0x4D2BU, 0x4E2BU, 0x4F2BU, 0x502BU,
+ 0x512BU, 0x522BU, 0x532BU, 0x542BU, 0x552BU, 0x562BU, 0x572BU, 0x582BU,
+ 0x592BU, 0x5A2BU, 0x612BU, 0x622BU, 0x632BU, 0x642BU, 0x652BU, 0x662BU,
+ 0x672BU, 0x682BU, 0x692BU, 0x6A2BU, 0x6B2BU, 0x6C2BU, 0x6D2BU, 0x6E2BU,
+ 0x6F2BU, 0x702BU, 0x712BU, 0x722BU, 0x732BU, 0x742BU, 0x752BU, 0x762BU,
+ 0x772BU, 0x782BU, 0x792BU, 0x7A2BU, 0x302BU, 0x312BU, 0x322BU, 0x332BU,
+ 0x342BU, 0x352BU, 0x362BU, 0x372BU, 0x382BU, 0x392BU, 0x2B2BU, 0x2F2BU,
+ 0x412FU, 0x422FU, 0x432FU, 0x442FU, 0x452FU, 0x462FU, 0x472FU, 0x482FU,
+ 0x492FU, 0x4A2FU, 0x4B2FU, 0x4C2FU, 0x4D2FU, 0x4E2FU, 0x4F2FU, 0x502FU,
+ 0x512FU, 0x522FU, 0x532FU, 0x542FU, 0x552FU, 0x562FU, 0x572FU, 0x582FU,
+ 0x592FU, 0x5A2FU, 0x612FU, 0x622FU, 0x632FU, 0x642FU, 0x652FU, 0x662FU,
+ 0x672FU, 0x682FU, 0x692FU, 0x6A2FU, 0x6B2FU, 0x6C2FU, 0x6D2FU, 0x6E2FU,
+ 0x6F2FU, 0x702FU, 0x712FU, 0x722FU, 0x732FU, 0x742FU, 0x752FU, 0x762FU,
+ 0x772FU, 0x782FU, 0x792FU, 0x7A2FU, 0x302FU, 0x312FU, 0x322FU, 0x332FU,
+ 0x342FU, 0x352FU, 0x362FU, 0x372FU, 0x382FU, 0x392FU, 0x2B2FU, 0x2F2FU,
+#else
+ 0x4141U, 0x4142U, 0x4143U, 0x4144U, 0x4145U, 0x4146U, 0x4147U, 0x4148U,
+ 0x4149U, 0x414AU, 0x414BU, 0x414CU, 0x414DU, 0x414EU, 0x414FU, 0x4150U,
+ 0x4151U, 0x4152U, 0x4153U, 0x4154U, 0x4155U, 0x4156U, 0x4157U, 0x4158U,
+ 0x4159U, 0x415AU, 0x4161U, 0x4162U, 0x4163U, 0x4164U, 0x4165U, 0x4166U,
+ 0x4167U, 0x4168U, 0x4169U, 0x416AU, 0x416BU, 0x416CU, 0x416DU, 0x416EU,
+ 0x416FU, 0x4170U, 0x4171U, 0x4172U, 0x4173U, 0x4174U, 0x4175U, 0x4176U,
+ 0x4177U, 0x4178U, 0x4179U, 0x417AU, 0x4130U, 0x4131U, 0x4132U, 0x4133U,
+ 0x4134U, 0x4135U, 0x4136U, 0x4137U, 0x4138U, 0x4139U, 0x412BU, 0x412FU,
+ 0x4241U, 0x4242U, 0x4243U, 0x4244U, 0x4245U, 0x4246U, 0x4247U, 0x4248U,
+ 0x4249U, 0x424AU, 0x424BU, 0x424CU, 0x424DU, 0x424EU, 0x424FU, 0x4250U,
+ 0x4251U, 0x4252U, 0x4253U, 0x4254U, 0x4255U, 0x4256U, 0x4257U, 0x4258U,
+ 0x4259U, 0x425AU, 0x4261U, 0x4262U, 0x4263U, 0x4264U, 0x4265U, 0x4266U,
+ 0x4267U, 0x4268U, 0x4269U, 0x426AU, 0x426BU, 0x426CU, 0x426DU, 0x426EU,
+ 0x426FU, 0x4270U, 0x4271U, 0x4272U, 0x4273U, 0x4274U, 0x4275U, 0x4276U,
+ 0x4277U, 0x4278U, 0x4279U, 0x427AU, 0x4230U, 0x4231U, 0x4232U, 0x4233U,
+ 0x4234U, 0x4235U, 0x4236U, 0x4237U, 0x4238U, 0x4239U, 0x422BU, 0x422FU,
+ 0x4341U, 0x4342U, 0x4343U, 0x4344U, 0x4345U, 0x4346U, 0x4347U, 0x4348U,
+ 0x4349U, 0x434AU, 0x434BU, 0x434CU, 0x434DU, 0x434EU, 0x434FU, 0x4350U,
+ 0x4351U, 0x4352U, 0x4353U, 0x4354U, 0x4355U, 0x4356U, 0x4357U, 0x4358U,
+ 0x4359U, 0x435AU, 0x4361U, 0x4362U, 0x4363U, 0x4364U, 0x4365U, 0x4366U,
+ 0x4367U, 0x4368U, 0x4369U, 0x436AU, 0x436BU, 0x436CU, 0x436DU, 0x436EU,
+ 0x436FU, 0x4370U, 0x4371U, 0x4372U, 0x4373U, 0x4374U, 0x4375U, 0x4376U,
+ 0x4377U, 0x4378U, 0x4379U, 0x437AU, 0x4330U, 0x4331U, 0x4332U, 0x4333U,
+ 0x4334U, 0x4335U, 0x4336U, 0x4337U, 0x4338U, 0x4339U, 0x432BU, 0x432FU,
+ 0x4441U, 0x4442U, 0x4443U, 0x4444U, 0x4445U, 0x4446U, 0x4447U, 0x4448U,
+ 0x4449U, 0x444AU, 0x444BU, 0x444CU, 0x444DU, 0x444EU, 0x444FU, 0x4450U,
+ 0x4451U, 0x4452U, 0x4453U, 0x4454U, 0x4455U, 0x4456U, 0x4457U, 0x4458U,
+ 0x4459U, 0x445AU, 0x4461U, 0x4462U, 0x4463U, 0x4464U, 0x4465U, 0x4466U,
+ 0x4467U, 0x4468U, 0x4469U, 0x446AU, 0x446BU, 0x446CU, 0x446DU, 0x446EU,
+ 0x446FU, 0x4470U, 0x4471U, 0x4472U, 0x4473U, 0x4474U, 0x4475U, 0x4476U,
+ 0x4477U, 0x4478U, 0x4479U, 0x447AU, 0x4430U, 0x4431U, 0x4432U, 0x4433U,
+ 0x4434U, 0x4435U, 0x4436U, 0x4437U, 0x4438U, 0x4439U, 0x442BU, 0x442FU,
+ 0x4541U, 0x4542U, 0x4543U, 0x4544U, 0x4545U, 0x4546U, 0x4547U, 0x4548U,
+ 0x4549U, 0x454AU, 0x454BU, 0x454CU, 0x454DU, 0x454EU, 0x454FU, 0x4550U,
+ 0x4551U, 0x4552U, 0x4553U, 0x4554U, 0x4555U, 0x4556U, 0x4557U, 0x4558U,
+ 0x4559U, 0x455AU, 0x4561U, 0x4562U, 0x4563U, 0x4564U, 0x4565U, 0x4566U,
+ 0x4567U, 0x4568U, 0x4569U, 0x456AU, 0x456BU, 0x456CU, 0x456DU, 0x456EU,
+ 0x456FU, 0x4570U, 0x4571U, 0x4572U, 0x4573U, 0x4574U, 0x4575U, 0x4576U,
+ 0x4577U, 0x4578U, 0x4579U, 0x457AU, 0x4530U, 0x4531U, 0x4532U, 0x4533U,
+ 0x4534U, 0x4535U, 0x4536U, 0x4537U, 0x4538U, 0x4539U, 0x452BU, 0x452FU,
+ 0x4641U, 0x4642U, 0x4643U, 0x4644U, 0x4645U, 0x4646U, 0x4647U, 0x4648U,
+ 0x4649U, 0x464AU, 0x464BU, 0x464CU, 0x464DU, 0x464EU, 0x464FU, 0x4650U,
+ 0x4651U, 0x4652U, 0x4653U, 0x4654U, 0x4655U, 0x4656U, 0x4657U, 0x4658U,
+ 0x4659U, 0x465AU, 0x4661U, 0x4662U, 0x4663U, 0x4664U, 0x4665U, 0x4666U,
+ 0x4667U, 0x4668U, 0x4669U, 0x466AU, 0x466BU, 0x466CU, 0x466DU, 0x466EU,
+ 0x466FU, 0x4670U, 0x4671U, 0x4672U, 0x4673U, 0x4674U, 0x4675U, 0x4676U,
+ 0x4677U, 0x4678U, 0x4679U, 0x467AU, 0x4630U, 0x4631U, 0x4632U, 0x4633U,
+ 0x4634U, 0x4635U, 0x4636U, 0x4637U, 0x4638U, 0x4639U, 0x462BU, 0x462FU,
+ 0x4741U, 0x4742U, 0x4743U, 0x4744U, 0x4745U, 0x4746U, 0x4747U, 0x4748U,
+ 0x4749U, 0x474AU, 0x474BU, 0x474CU, 0x474DU, 0x474EU, 0x474FU, 0x4750U,
+ 0x4751U, 0x4752U, 0x4753U, 0x4754U, 0x4755U, 0x4756U, 0x4757U, 0x4758U,
+ 0x4759U, 0x475AU, 0x4761U, 0x4762U, 0x4763U, 0x4764U, 0x4765U, 0x4766U,
+ 0x4767U, 0x4768U, 0x4769U, 0x476AU, 0x476BU, 0x476CU, 0x476DU, 0x476EU,
+ 0x476FU, 0x4770U, 0x4771U, 0x4772U, 0x4773U, 0x4774U, 0x4775U, 0x4776U,
+ 0x4777U, 0x4778U, 0x4779U, 0x477AU, 0x4730U, 0x4731U, 0x4732U, 0x4733U,
+ 0x4734U, 0x4735U, 0x4736U, 0x4737U, 0x4738U, 0x4739U, 0x472BU, 0x472FU,
+ 0x4841U, 0x4842U, 0x4843U, 0x4844U, 0x4845U, 0x4846U, 0x4847U, 0x4848U,
+ 0x4849U, 0x484AU, 0x484BU, 0x484CU, 0x484DU, 0x484EU, 0x484FU, 0x4850U,
+ 0x4851U, 0x4852U, 0x4853U, 0x4854U, 0x4855U, 0x4856U, 0x4857U, 0x4858U,
+ 0x4859U, 0x485AU, 0x4861U, 0x4862U, 0x4863U, 0x4864U, 0x4865U, 0x4866U,
+ 0x4867U, 0x4868U, 0x4869U, 0x486AU, 0x486BU, 0x486CU, 0x486DU, 0x486EU,
+ 0x486FU, 0x4870U, 0x4871U, 0x4872U, 0x4873U, 0x4874U, 0x4875U, 0x4876U,
+ 0x4877U, 0x4878U, 0x4879U, 0x487AU, 0x4830U, 0x4831U, 0x4832U, 0x4833U,
+ 0x4834U, 0x4835U, 0x4836U, 0x4837U, 0x4838U, 0x4839U, 0x482BU, 0x482FU,
+ 0x4941U, 0x4942U, 0x4943U, 0x4944U, 0x4945U, 0x4946U, 0x4947U, 0x4948U,
+ 0x4949U, 0x494AU, 0x494BU, 0x494CU, 0x494DU, 0x494EU, 0x494FU, 0x4950U,
+ 0x4951U, 0x4952U, 0x4953U, 0x4954U, 0x4955U, 0x4956U, 0x4957U, 0x4958U,
+ 0x4959U, 0x495AU, 0x4961U, 0x4962U, 0x4963U, 0x4964U, 0x4965U, 0x4966U,
+ 0x4967U, 0x4968U, 0x4969U, 0x496AU, 0x496BU, 0x496CU, 0x496DU, 0x496EU,
+ 0x496FU, 0x4970U, 0x4971U, 0x4972U, 0x4973U, 0x4974U, 0x4975U, 0x4976U,
+ 0x4977U, 0x4978U, 0x4979U, 0x497AU, 0x4930U, 0x4931U, 0x4932U, 0x4933U,
+ 0x4934U, 0x4935U, 0x4936U, 0x4937U, 0x4938U, 0x4939U, 0x492BU, 0x492FU,
+ 0x4A41U, 0x4A42U, 0x4A43U, 0x4A44U, 0x4A45U, 0x4A46U, 0x4A47U, 0x4A48U,
+ 0x4A49U, 0x4A4AU, 0x4A4BU, 0x4A4CU, 0x4A4DU, 0x4A4EU, 0x4A4FU, 0x4A50U,
+ 0x4A51U, 0x4A52U, 0x4A53U, 0x4A54U, 0x4A55U, 0x4A56U, 0x4A57U, 0x4A58U,
+ 0x4A59U, 0x4A5AU, 0x4A61U, 0x4A62U, 0x4A63U, 0x4A64U, 0x4A65U, 0x4A66U,
+ 0x4A67U, 0x4A68U, 0x4A69U, 0x4A6AU, 0x4A6BU, 0x4A6CU, 0x4A6DU, 0x4A6EU,
+ 0x4A6FU, 0x4A70U, 0x4A71U, 0x4A72U, 0x4A73U, 0x4A74U, 0x4A75U, 0x4A76U,
+ 0x4A77U, 0x4A78U, 0x4A79U, 0x4A7AU, 0x4A30U, 0x4A31U, 0x4A32U, 0x4A33U,
+ 0x4A34U, 0x4A35U, 0x4A36U, 0x4A37U, 0x4A38U, 0x4A39U, 0x4A2BU, 0x4A2FU,
+ 0x4B41U, 0x4B42U, 0x4B43U, 0x4B44U, 0x4B45U, 0x4B46U, 0x4B47U, 0x4B48U,
+ 0x4B49U, 0x4B4AU, 0x4B4BU, 0x4B4CU, 0x4B4DU, 0x4B4EU, 0x4B4FU, 0x4B50U,
+ 0x4B51U, 0x4B52U, 0x4B53U, 0x4B54U, 0x4B55U, 0x4B56U, 0x4B57U, 0x4B58U,
+ 0x4B59U, 0x4B5AU, 0x4B61U, 0x4B62U, 0x4B63U, 0x4B64U, 0x4B65U, 0x4B66U,
+ 0x4B67U, 0x4B68U, 0x4B69U, 0x4B6AU, 0x4B6BU, 0x4B6CU, 0x4B6DU, 0x4B6EU,
+ 0x4B6FU, 0x4B70U, 0x4B71U, 0x4B72U, 0x4B73U, 0x4B74U, 0x4B75U, 0x4B76U,
+ 0x4B77U, 0x4B78U, 0x4B79U, 0x4B7AU, 0x4B30U, 0x4B31U, 0x4B32U, 0x4B33U,
+ 0x4B34U, 0x4B35U, 0x4B36U, 0x4B37U, 0x4B38U, 0x4B39U, 0x4B2BU, 0x4B2FU,
+ 0x4C41U, 0x4C42U, 0x4C43U, 0x4C44U, 0x4C45U, 0x4C46U, 0x4C47U, 0x4C48U,
+ 0x4C49U, 0x4C4AU, 0x4C4BU, 0x4C4CU, 0x4C4DU, 0x4C4EU, 0x4C4FU, 0x4C50U,
+ 0x4C51U, 0x4C52U, 0x4C53U, 0x4C54U, 0x4C55U, 0x4C56U, 0x4C57U, 0x4C58U,
+ 0x4C59U, 0x4C5AU, 0x4C61U, 0x4C62U, 0x4C63U, 0x4C64U, 0x4C65U, 0x4C66U,
+ 0x4C67U, 0x4C68U, 0x4C69U, 0x4C6AU, 0x4C6BU, 0x4C6CU, 0x4C6DU, 0x4C6EU,
+ 0x4C6FU, 0x4C70U, 0x4C71U, 0x4C72U, 0x4C73U, 0x4C74U, 0x4C75U, 0x4C76U,
+ 0x4C77U, 0x4C78U, 0x4C79U, 0x4C7AU, 0x4C30U, 0x4C31U, 0x4C32U, 0x4C33U,
+ 0x4C34U, 0x4C35U, 0x4C36U, 0x4C37U, 0x4C38U, 0x4C39U, 0x4C2BU, 0x4C2FU,
+ 0x4D41U, 0x4D42U, 0x4D43U, 0x4D44U, 0x4D45U, 0x4D46U, 0x4D47U, 0x4D48U,
+ 0x4D49U, 0x4D4AU, 0x4D4BU, 0x4D4CU, 0x4D4DU, 0x4D4EU, 0x4D4FU, 0x4D50U,
+ 0x4D51U, 0x4D52U, 0x4D53U, 0x4D54U, 0x4D55U, 0x4D56U, 0x4D57U, 0x4D58U,
+ 0x4D59U, 0x4D5AU, 0x4D61U, 0x4D62U, 0x4D63U, 0x4D64U, 0x4D65U, 0x4D66U,
+ 0x4D67U, 0x4D68U, 0x4D69U, 0x4D6AU, 0x4D6BU, 0x4D6CU, 0x4D6DU, 0x4D6EU,
+ 0x4D6FU, 0x4D70U, 0x4D71U, 0x4D72U, 0x4D73U, 0x4D74U, 0x4D75U, 0x4D76U,
+ 0x4D77U, 0x4D78U, 0x4D79U, 0x4D7AU, 0x4D30U, 0x4D31U, 0x4D32U, 0x4D33U,
+ 0x4D34U, 0x4D35U, 0x4D36U, 0x4D37U, 0x4D38U, 0x4D39U, 0x4D2BU, 0x4D2FU,
+ 0x4E41U, 0x4E42U, 0x4E43U, 0x4E44U, 0x4E45U, 0x4E46U, 0x4E47U, 0x4E48U,
+ 0x4E49U, 0x4E4AU, 0x4E4BU, 0x4E4CU, 0x4E4DU, 0x4E4EU, 0x4E4FU, 0x4E50U,
+ 0x4E51U, 0x4E52U, 0x4E53U, 0x4E54U, 0x4E55U, 0x4E56U, 0x4E57U, 0x4E58U,
+ 0x4E59U, 0x4E5AU, 0x4E61U, 0x4E62U, 0x4E63U, 0x4E64U, 0x4E65U, 0x4E66U,
+ 0x4E67U, 0x4E68U, 0x4E69U, 0x4E6AU, 0x4E6BU, 0x4E6CU, 0x4E6DU, 0x4E6EU,
+ 0x4E6FU, 0x4E70U, 0x4E71U, 0x4E72U, 0x4E73U, 0x4E74U, 0x4E75U, 0x4E76U,
+ 0x4E77U, 0x4E78U, 0x4E79U, 0x4E7AU, 0x4E30U, 0x4E31U, 0x4E32U, 0x4E33U,
+ 0x4E34U, 0x4E35U, 0x4E36U, 0x4E37U, 0x4E38U, 0x4E39U, 0x4E2BU, 0x4E2FU,
+ 0x4F41U, 0x4F42U, 0x4F43U, 0x4F44U, 0x4F45U, 0x4F46U, 0x4F47U, 0x4F48U,
+ 0x4F49U, 0x4F4AU, 0x4F4BU, 0x4F4CU, 0x4F4DU, 0x4F4EU, 0x4F4FU, 0x4F50U,
+ 0x4F51U, 0x4F52U, 0x4F53U, 0x4F54U, 0x4F55U, 0x4F56U, 0x4F57U, 0x4F58U,
+ 0x4F59U, 0x4F5AU, 0x4F61U, 0x4F62U, 0x4F63U, 0x4F64U, 0x4F65U, 0x4F66U,
+ 0x4F67U, 0x4F68U, 0x4F69U, 0x4F6AU, 0x4F6BU, 0x4F6CU, 0x4F6DU, 0x4F6EU,
+ 0x4F6FU, 0x4F70U, 0x4F71U, 0x4F72U, 0x4F73U, 0x4F74U, 0x4F75U, 0x4F76U,
+ 0x4F77U, 0x4F78U, 0x4F79U, 0x4F7AU, 0x4F30U, 0x4F31U, 0x4F32U, 0x4F33U,
+ 0x4F34U, 0x4F35U, 0x4F36U, 0x4F37U, 0x4F38U, 0x4F39U, 0x4F2BU, 0x4F2FU,
+ 0x5041U, 0x5042U, 0x5043U, 0x5044U, 0x5045U, 0x5046U, 0x5047U, 0x5048U,
+ 0x5049U, 0x504AU, 0x504BU, 0x504CU, 0x504DU, 0x504EU, 0x504FU, 0x5050U,
+ 0x5051U, 0x5052U, 0x5053U, 0x5054U, 0x5055U, 0x5056U, 0x5057U, 0x5058U,
+ 0x5059U, 0x505AU, 0x5061U, 0x5062U, 0x5063U, 0x5064U, 0x5065U, 0x5066U,
+ 0x5067U, 0x5068U, 0x5069U, 0x506AU, 0x506BU, 0x506CU, 0x506DU, 0x506EU,
+ 0x506FU, 0x5070U, 0x5071U, 0x5072U, 0x5073U, 0x5074U, 0x5075U, 0x5076U,
+ 0x5077U, 0x5078U, 0x5079U, 0x507AU, 0x5030U, 0x5031U, 0x5032U, 0x5033U,
+ 0x5034U, 0x5035U, 0x5036U, 0x5037U, 0x5038U, 0x5039U, 0x502BU, 0x502FU,
+ 0x5141U, 0x5142U, 0x5143U, 0x5144U, 0x5145U, 0x5146U, 0x5147U, 0x5148U,
+ 0x5149U, 0x514AU, 0x514BU, 0x514CU, 0x514DU, 0x514EU, 0x514FU, 0x5150U,
+ 0x5151U, 0x5152U, 0x5153U, 0x5154U, 0x5155U, 0x5156U, 0x5157U, 0x5158U,
+ 0x5159U, 0x515AU, 0x5161U, 0x5162U, 0x5163U, 0x5164U, 0x5165U, 0x5166U,
+ 0x5167U, 0x5168U, 0x5169U, 0x516AU, 0x516BU, 0x516CU, 0x516DU, 0x516EU,
+ 0x516FU, 0x5170U, 0x5171U, 0x5172U, 0x5173U, 0x5174U, 0x5175U, 0x5176U,
+ 0x5177U, 0x5178U, 0x5179U, 0x517AU, 0x5130U, 0x5131U, 0x5132U, 0x5133U,
+ 0x5134U, 0x5135U, 0x5136U, 0x5137U, 0x5138U, 0x5139U, 0x512BU, 0x512FU,
+ 0x5241U, 0x5242U, 0x5243U, 0x5244U, 0x5245U, 0x5246U, 0x5247U, 0x5248U,
+ 0x5249U, 0x524AU, 0x524BU, 0x524CU, 0x524DU, 0x524EU, 0x524FU, 0x5250U,
+ 0x5251U, 0x5252U, 0x5253U, 0x5254U, 0x5255U, 0x5256U, 0x5257U, 0x5258U,
+ 0x5259U, 0x525AU, 0x5261U, 0x5262U, 0x5263U, 0x5264U, 0x5265U, 0x5266U,
+ 0x5267U, 0x5268U, 0x5269U, 0x526AU, 0x526BU, 0x526CU, 0x526DU, 0x526EU,
+ 0x526FU, 0x5270U, 0x5271U, 0x5272U, 0x5273U, 0x5274U, 0x5275U, 0x5276U,
+ 0x5277U, 0x5278U, 0x5279U, 0x527AU, 0x5230U, 0x5231U, 0x5232U, 0x5233U,
+ 0x5234U, 0x5235U, 0x5236U, 0x5237U, 0x5238U, 0x5239U, 0x522BU, 0x522FU,
+ 0x5341U, 0x5342U, 0x5343U, 0x5344U, 0x5345U, 0x5346U, 0x5347U, 0x5348U,
+ 0x5349U, 0x534AU, 0x534BU, 0x534CU, 0x534DU, 0x534EU, 0x534FU, 0x5350U,
+ 0x5351U, 0x5352U, 0x5353U, 0x5354U, 0x5355U, 0x5356U, 0x5357U, 0x5358U,
+ 0x5359U, 0x535AU, 0x5361U, 0x5362U, 0x5363U, 0x5364U, 0x5365U, 0x5366U,
+ 0x5367U, 0x5368U, 0x5369U, 0x536AU, 0x536BU, 0x536CU, 0x536DU, 0x536EU,
+ 0x536FU, 0x5370U, 0x5371U, 0x5372U, 0x5373U, 0x5374U, 0x5375U, 0x5376U,
+ 0x5377U, 0x5378U, 0x5379U, 0x537AU, 0x5330U, 0x5331U, 0x5332U, 0x5333U,
+ 0x5334U, 0x5335U, 0x5336U, 0x5337U, 0x5338U, 0x5339U, 0x532BU, 0x532FU,
+ 0x5441U, 0x5442U, 0x5443U, 0x5444U, 0x5445U, 0x5446U, 0x5447U, 0x5448U,
+ 0x5449U, 0x544AU, 0x544BU, 0x544CU, 0x544DU, 0x544EU, 0x544FU, 0x5450U,
+ 0x5451U, 0x5452U, 0x5453U, 0x5454U, 0x5455U, 0x5456U, 0x5457U, 0x5458U,
+ 0x5459U, 0x545AU, 0x5461U, 0x5462U, 0x5463U, 0x5464U, 0x5465U, 0x5466U,
+ 0x5467U, 0x5468U, 0x5469U, 0x546AU, 0x546BU, 0x546CU, 0x546DU, 0x546EU,
+ 0x546FU, 0x5470U, 0x5471U, 0x5472U, 0x5473U, 0x5474U, 0x5475U, 0x5476U,
+ 0x5477U, 0x5478U, 0x5479U, 0x547AU, 0x5430U, 0x5431U, 0x5432U, 0x5433U,
+ 0x5434U, 0x5435U, 0x5436U, 0x5437U, 0x5438U, 0x5439U, 0x542BU, 0x542FU,
+ 0x5541U, 0x5542U, 0x5543U, 0x5544U, 0x5545U, 0x5546U, 0x5547U, 0x5548U,
+ 0x5549U, 0x554AU, 0x554BU, 0x554CU, 0x554DU, 0x554EU, 0x554FU, 0x5550U,
+ 0x5551U, 0x5552U, 0x5553U, 0x5554U, 0x5555U, 0x5556U, 0x5557U, 0x5558U,
+ 0x5559U, 0x555AU, 0x5561U, 0x5562U, 0x5563U, 0x5564U, 0x5565U, 0x5566U,
+ 0x5567U, 0x5568U, 0x5569U, 0x556AU, 0x556BU, 0x556CU, 0x556DU, 0x556EU,
+ 0x556FU, 0x5570U, 0x5571U, 0x5572U, 0x5573U, 0x5574U, 0x5575U, 0x5576U,
+ 0x5577U, 0x5578U, 0x5579U, 0x557AU, 0x5530U, 0x5531U, 0x5532U, 0x5533U,
+ 0x5534U, 0x5535U, 0x5536U, 0x5537U, 0x5538U, 0x5539U, 0x552BU, 0x552FU,
+ 0x5641U, 0x5642U, 0x5643U, 0x5644U, 0x5645U, 0x5646U, 0x5647U, 0x5648U,
+ 0x5649U, 0x564AU, 0x564BU, 0x564CU, 0x564DU, 0x564EU, 0x564FU, 0x5650U,
+ 0x5651U, 0x5652U, 0x5653U, 0x5654U, 0x5655U, 0x5656U, 0x5657U, 0x5658U,
+ 0x5659U, 0x565AU, 0x5661U, 0x5662U, 0x5663U, 0x5664U, 0x5665U, 0x5666U,
+ 0x5667U, 0x5668U, 0x5669U, 0x566AU, 0x566BU, 0x566CU, 0x566DU, 0x566EU,
+ 0x566FU, 0x5670U, 0x5671U, 0x5672U, 0x5673U, 0x5674U, 0x5675U, 0x5676U,
+ 0x5677U, 0x5678U, 0x5679U, 0x567AU, 0x5630U, 0x5631U, 0x5632U, 0x5633U,
+ 0x5634U, 0x5635U, 0x5636U, 0x5637U, 0x5638U, 0x5639U, 0x562BU, 0x562FU,
+ 0x5741U, 0x5742U, 0x5743U, 0x5744U, 0x5745U, 0x5746U, 0x5747U, 0x5748U,
+ 0x5749U, 0x574AU, 0x574BU, 0x574CU, 0x574DU, 0x574EU, 0x574FU, 0x5750U,
+ 0x5751U, 0x5752U, 0x5753U, 0x5754U, 0x5755U, 0x5756U, 0x5757U, 0x5758U,
+ 0x5759U, 0x575AU, 0x5761U, 0x5762U, 0x5763U, 0x5764U, 0x5765U, 0x5766U,
+ 0x5767U, 0x5768U, 0x5769U, 0x576AU, 0x576BU, 0x576CU, 0x576DU, 0x576EU,
+ 0x576FU, 0x5770U, 0x5771U, 0x5772U, 0x5773U, 0x5774U, 0x5775U, 0x5776U,
+ 0x5777U, 0x5778U, 0x5779U, 0x577AU, 0x5730U, 0x5731U, 0x5732U, 0x5733U,
+ 0x5734U, 0x5735U, 0x5736U, 0x5737U, 0x5738U, 0x5739U, 0x572BU, 0x572FU,
+ 0x5841U, 0x5842U, 0x5843U, 0x5844U, 0x5845U, 0x5846U, 0x5847U, 0x5848U,
+ 0x5849U, 0x584AU, 0x584BU, 0x584CU, 0x584DU, 0x584EU, 0x584FU, 0x5850U,
+ 0x5851U, 0x5852U, 0x5853U, 0x5854U, 0x5855U, 0x5856U, 0x5857U, 0x5858U,
+ 0x5859U, 0x585AU, 0x5861U, 0x5862U, 0x5863U, 0x5864U, 0x5865U, 0x5866U,
+ 0x5867U, 0x5868U, 0x5869U, 0x586AU, 0x586BU, 0x586CU, 0x586DU, 0x586EU,
+ 0x586FU, 0x5870U, 0x5871U, 0x5872U, 0x5873U, 0x5874U, 0x5875U, 0x5876U,
+ 0x5877U, 0x5878U, 0x5879U, 0x587AU, 0x5830U, 0x5831U, 0x5832U, 0x5833U,
+ 0x5834U, 0x5835U, 0x5836U, 0x5837U, 0x5838U, 0x5839U, 0x582BU, 0x582FU,
+ 0x5941U, 0x5942U, 0x5943U, 0x5944U, 0x5945U, 0x5946U, 0x5947U, 0x5948U,
+ 0x5949U, 0x594AU, 0x594BU, 0x594CU, 0x594DU, 0x594EU, 0x594FU, 0x5950U,
+ 0x5951U, 0x5952U, 0x5953U, 0x5954U, 0x5955U, 0x5956U, 0x5957U, 0x5958U,
+ 0x5959U, 0x595AU, 0x5961U, 0x5962U, 0x5963U, 0x5964U, 0x5965U, 0x5966U,
+ 0x5967U, 0x5968U, 0x5969U, 0x596AU, 0x596BU, 0x596CU, 0x596DU, 0x596EU,
+ 0x596FU, 0x5970U, 0x5971U, 0x5972U, 0x5973U, 0x5974U, 0x5975U, 0x5976U,
+ 0x5977U, 0x5978U, 0x5979U, 0x597AU, 0x5930U, 0x5931U, 0x5932U, 0x5933U,
+ 0x5934U, 0x5935U, 0x5936U, 0x5937U, 0x5938U, 0x5939U, 0x592BU, 0x592FU,
+ 0x5A41U, 0x5A42U, 0x5A43U, 0x5A44U, 0x5A45U, 0x5A46U, 0x5A47U, 0x5A48U,
+ 0x5A49U, 0x5A4AU, 0x5A4BU, 0x5A4CU, 0x5A4DU, 0x5A4EU, 0x5A4FU, 0x5A50U,
+ 0x5A51U, 0x5A52U, 0x5A53U, 0x5A54U, 0x5A55U, 0x5A56U, 0x5A57U, 0x5A58U,
+ 0x5A59U, 0x5A5AU, 0x5A61U, 0x5A62U, 0x5A63U, 0x5A64U, 0x5A65U, 0x5A66U,
+ 0x5A67U, 0x5A68U, 0x5A69U, 0x5A6AU, 0x5A6BU, 0x5A6CU, 0x5A6DU, 0x5A6EU,
+ 0x5A6FU, 0x5A70U, 0x5A71U, 0x5A72U, 0x5A73U, 0x5A74U, 0x5A75U, 0x5A76U,
+ 0x5A77U, 0x5A78U, 0x5A79U, 0x5A7AU, 0x5A30U, 0x5A31U, 0x5A32U, 0x5A33U,
+ 0x5A34U, 0x5A35U, 0x5A36U, 0x5A37U, 0x5A38U, 0x5A39U, 0x5A2BU, 0x5A2FU,
+ 0x6141U, 0x6142U, 0x6143U, 0x6144U, 0x6145U, 0x6146U, 0x6147U, 0x6148U,
+ 0x6149U, 0x614AU, 0x614BU, 0x614CU, 0x614DU, 0x614EU, 0x614FU, 0x6150U,
+ 0x6151U, 0x6152U, 0x6153U, 0x6154U, 0x6155U, 0x6156U, 0x6157U, 0x6158U,
+ 0x6159U, 0x615AU, 0x6161U, 0x6162U, 0x6163U, 0x6164U, 0x6165U, 0x6166U,
+ 0x6167U, 0x6168U, 0x6169U, 0x616AU, 0x616BU, 0x616CU, 0x616DU, 0x616EU,
+ 0x616FU, 0x6170U, 0x6171U, 0x6172U, 0x6173U, 0x6174U, 0x6175U, 0x6176U,
+ 0x6177U, 0x6178U, 0x6179U, 0x617AU, 0x6130U, 0x6131U, 0x6132U, 0x6133U,
+ 0x6134U, 0x6135U, 0x6136U, 0x6137U, 0x6138U, 0x6139U, 0x612BU, 0x612FU,
+ 0x6241U, 0x6242U, 0x6243U, 0x6244U, 0x6245U, 0x6246U, 0x6247U, 0x6248U,
+ 0x6249U, 0x624AU, 0x624BU, 0x624CU, 0x624DU, 0x624EU, 0x624FU, 0x6250U,
+ 0x6251U, 0x6252U, 0x6253U, 0x6254U, 0x6255U, 0x6256U, 0x6257U, 0x6258U,
+ 0x6259U, 0x625AU, 0x6261U, 0x6262U, 0x6263U, 0x6264U, 0x6265U, 0x6266U,
+ 0x6267U, 0x6268U, 0x6269U, 0x626AU, 0x626BU, 0x626CU, 0x626DU, 0x626EU,
+ 0x626FU, 0x6270U, 0x6271U, 0x6272U, 0x6273U, 0x6274U, 0x6275U, 0x6276U,
+ 0x6277U, 0x6278U, 0x6279U, 0x627AU, 0x6230U, 0x6231U, 0x6232U, 0x6233U,
+ 0x6234U, 0x6235U, 0x6236U, 0x6237U, 0x6238U, 0x6239U, 0x622BU, 0x622FU,
+ 0x6341U, 0x6342U, 0x6343U, 0x6344U, 0x6345U, 0x6346U, 0x6347U, 0x6348U,
+ 0x6349U, 0x634AU, 0x634BU, 0x634CU, 0x634DU, 0x634EU, 0x634FU, 0x6350U,
+ 0x6351U, 0x6352U, 0x6353U, 0x6354U, 0x6355U, 0x6356U, 0x6357U, 0x6358U,
+ 0x6359U, 0x635AU, 0x6361U, 0x6362U, 0x6363U, 0x6364U, 0x6365U, 0x6366U,
+ 0x6367U, 0x6368U, 0x6369U, 0x636AU, 0x636BU, 0x636CU, 0x636DU, 0x636EU,
+ 0x636FU, 0x6370U, 0x6371U, 0x6372U, 0x6373U, 0x6374U, 0x6375U, 0x6376U,
+ 0x6377U, 0x6378U, 0x6379U, 0x637AU, 0x6330U, 0x6331U, 0x6332U, 0x6333U,
+ 0x6334U, 0x6335U, 0x6336U, 0x6337U, 0x6338U, 0x6339U, 0x632BU, 0x632FU,
+ 0x6441U, 0x6442U, 0x6443U, 0x6444U, 0x6445U, 0x6446U, 0x6447U, 0x6448U,
+ 0x6449U, 0x644AU, 0x644BU, 0x644CU, 0x644DU, 0x644EU, 0x644FU, 0x6450U,
+ 0x6451U, 0x6452U, 0x6453U, 0x6454U, 0x6455U, 0x6456U, 0x6457U, 0x6458U,
+ 0x6459U, 0x645AU, 0x6461U, 0x6462U, 0x6463U, 0x6464U, 0x6465U, 0x6466U,
+ 0x6467U, 0x6468U, 0x6469U, 0x646AU, 0x646BU, 0x646CU, 0x646DU, 0x646EU,
+ 0x646FU, 0x6470U, 0x6471U, 0x6472U, 0x6473U, 0x6474U, 0x6475U, 0x6476U,
+ 0x6477U, 0x6478U, 0x6479U, 0x647AU, 0x6430U, 0x6431U, 0x6432U, 0x6433U,
+ 0x6434U, 0x6435U, 0x6436U, 0x6437U, 0x6438U, 0x6439U, 0x642BU, 0x642FU,
+ 0x6541U, 0x6542U, 0x6543U, 0x6544U, 0x6545U, 0x6546U, 0x6547U, 0x6548U,
+ 0x6549U, 0x654AU, 0x654BU, 0x654CU, 0x654DU, 0x654EU, 0x654FU, 0x6550U,
+ 0x6551U, 0x6552U, 0x6553U, 0x6554U, 0x6555U, 0x6556U, 0x6557U, 0x6558U,
+ 0x6559U, 0x655AU, 0x6561U, 0x6562U, 0x6563U, 0x6564U, 0x6565U, 0x6566U,
+ 0x6567U, 0x6568U, 0x6569U, 0x656AU, 0x656BU, 0x656CU, 0x656DU, 0x656EU,
+ 0x656FU, 0x6570U, 0x6571U, 0x6572U, 0x6573U, 0x6574U, 0x6575U, 0x6576U,
+ 0x6577U, 0x6578U, 0x6579U, 0x657AU, 0x6530U, 0x6531U, 0x6532U, 0x6533U,
+ 0x6534U, 0x6535U, 0x6536U, 0x6537U, 0x6538U, 0x6539U, 0x652BU, 0x652FU,
+ 0x6641U, 0x6642U, 0x6643U, 0x6644U, 0x6645U, 0x6646U, 0x6647U, 0x6648U,
+ 0x6649U, 0x664AU, 0x664BU, 0x664CU, 0x664DU, 0x664EU, 0x664FU, 0x6650U,
+ 0x6651U, 0x6652U, 0x6653U, 0x6654U, 0x6655U, 0x6656U, 0x6657U, 0x6658U,
+ 0x6659U, 0x665AU, 0x6661U, 0x6662U, 0x6663U, 0x6664U, 0x6665U, 0x6666U,
+ 0x6667U, 0x6668U, 0x6669U, 0x666AU, 0x666BU, 0x666CU, 0x666DU, 0x666EU,
+ 0x666FU, 0x6670U, 0x6671U, 0x6672U, 0x6673U, 0x6674U, 0x6675U, 0x6676U,
+ 0x6677U, 0x6678U, 0x6679U, 0x667AU, 0x6630U, 0x6631U, 0x6632U, 0x6633U,
+ 0x6634U, 0x6635U, 0x6636U, 0x6637U, 0x6638U, 0x6639U, 0x662BU, 0x662FU,
+ 0x6741U, 0x6742U, 0x6743U, 0x6744U, 0x6745U, 0x6746U, 0x6747U, 0x6748U,
+ 0x6749U, 0x674AU, 0x674BU, 0x674CU, 0x674DU, 0x674EU, 0x674FU, 0x6750U,
+ 0x6751U, 0x6752U, 0x6753U, 0x6754U, 0x6755U, 0x6756U, 0x6757U, 0x6758U,
+ 0x6759U, 0x675AU, 0x6761U, 0x6762U, 0x6763U, 0x6764U, 0x6765U, 0x6766U,
+ 0x6767U, 0x6768U, 0x6769U, 0x676AU, 0x676BU, 0x676CU, 0x676DU, 0x676EU,
+ 0x676FU, 0x6770U, 0x6771U, 0x6772U, 0x6773U, 0x6774U, 0x6775U, 0x6776U,
+ 0x6777U, 0x6778U, 0x6779U, 0x677AU, 0x6730U, 0x6731U, 0x6732U, 0x6733U,
+ 0x6734U, 0x6735U, 0x6736U, 0x6737U, 0x6738U, 0x6739U, 0x672BU, 0x672FU,
+ 0x6841U, 0x6842U, 0x6843U, 0x6844U, 0x6845U, 0x6846U, 0x6847U, 0x6848U,
+ 0x6849U, 0x684AU, 0x684BU, 0x684CU, 0x684DU, 0x684EU, 0x684FU, 0x6850U,
+ 0x6851U, 0x6852U, 0x6853U, 0x6854U, 0x6855U, 0x6856U, 0x6857U, 0x6858U,
+ 0x6859U, 0x685AU, 0x6861U, 0x6862U, 0x6863U, 0x6864U, 0x6865U, 0x6866U,
+ 0x6867U, 0x6868U, 0x6869U, 0x686AU, 0x686BU, 0x686CU, 0x686DU, 0x686EU,
+ 0x686FU, 0x6870U, 0x6871U, 0x6872U, 0x6873U, 0x6874U, 0x6875U, 0x6876U,
+ 0x6877U, 0x6878U, 0x6879U, 0x687AU, 0x6830U, 0x6831U, 0x6832U, 0x6833U,
+ 0x6834U, 0x6835U, 0x6836U, 0x6837U, 0x6838U, 0x6839U, 0x682BU, 0x682FU,
+ 0x6941U, 0x6942U, 0x6943U, 0x6944U, 0x6945U, 0x6946U, 0x6947U, 0x6948U,
+ 0x6949U, 0x694AU, 0x694BU, 0x694CU, 0x694DU, 0x694EU, 0x694FU, 0x6950U,
+ 0x6951U, 0x6952U, 0x6953U, 0x6954U, 0x6955U, 0x6956U, 0x6957U, 0x6958U,
+ 0x6959U, 0x695AU, 0x6961U, 0x6962U, 0x6963U, 0x6964U, 0x6965U, 0x6966U,
+ 0x6967U, 0x6968U, 0x6969U, 0x696AU, 0x696BU, 0x696CU, 0x696DU, 0x696EU,
+ 0x696FU, 0x6970U, 0x6971U, 0x6972U, 0x6973U, 0x6974U, 0x6975U, 0x6976U,
+ 0x6977U, 0x6978U, 0x6979U, 0x697AU, 0x6930U, 0x6931U, 0x6932U, 0x6933U,
+ 0x6934U, 0x6935U, 0x6936U, 0x6937U, 0x6938U, 0x6939U, 0x692BU, 0x692FU,
+ 0x6A41U, 0x6A42U, 0x6A43U, 0x6A44U, 0x6A45U, 0x6A46U, 0x6A47U, 0x6A48U,
+ 0x6A49U, 0x6A4AU, 0x6A4BU, 0x6A4CU, 0x6A4DU, 0x6A4EU, 0x6A4FU, 0x6A50U,
+ 0x6A51U, 0x6A52U, 0x6A53U, 0x6A54U, 0x6A55U, 0x6A56U, 0x6A57U, 0x6A58U,
+ 0x6A59U, 0x6A5AU, 0x6A61U, 0x6A62U, 0x6A63U, 0x6A64U, 0x6A65U, 0x6A66U,
+ 0x6A67U, 0x6A68U, 0x6A69U, 0x6A6AU, 0x6A6BU, 0x6A6CU, 0x6A6DU, 0x6A6EU,
+ 0x6A6FU, 0x6A70U, 0x6A71U, 0x6A72U, 0x6A73U, 0x6A74U, 0x6A75U, 0x6A76U,
+ 0x6A77U, 0x6A78U, 0x6A79U, 0x6A7AU, 0x6A30U, 0x6A31U, 0x6A32U, 0x6A33U,
+ 0x6A34U, 0x6A35U, 0x6A36U, 0x6A37U, 0x6A38U, 0x6A39U, 0x6A2BU, 0x6A2FU,
+ 0x6B41U, 0x6B42U, 0x6B43U, 0x6B44U, 0x6B45U, 0x6B46U, 0x6B47U, 0x6B48U,
+ 0x6B49U, 0x6B4AU, 0x6B4BU, 0x6B4CU, 0x6B4DU, 0x6B4EU, 0x6B4FU, 0x6B50U,
+ 0x6B51U, 0x6B52U, 0x6B53U, 0x6B54U, 0x6B55U, 0x6B56U, 0x6B57U, 0x6B58U,
+ 0x6B59U, 0x6B5AU, 0x6B61U, 0x6B62U, 0x6B63U, 0x6B64U, 0x6B65U, 0x6B66U,
+ 0x6B67U, 0x6B68U, 0x6B69U, 0x6B6AU, 0x6B6BU, 0x6B6CU, 0x6B6DU, 0x6B6EU,
+ 0x6B6FU, 0x6B70U, 0x6B71U, 0x6B72U, 0x6B73U, 0x6B74U, 0x6B75U, 0x6B76U,
+ 0x6B77U, 0x6B78U, 0x6B79U, 0x6B7AU, 0x6B30U, 0x6B31U, 0x6B32U, 0x6B33U,
+ 0x6B34U, 0x6B35U, 0x6B36U, 0x6B37U, 0x6B38U, 0x6B39U, 0x6B2BU, 0x6B2FU,
+ 0x6C41U, 0x6C42U, 0x6C43U, 0x6C44U, 0x6C45U, 0x6C46U, 0x6C47U, 0x6C48U,
+ 0x6C49U, 0x6C4AU, 0x6C4BU, 0x6C4CU, 0x6C4DU, 0x6C4EU, 0x6C4FU, 0x6C50U,
+ 0x6C51U, 0x6C52U, 0x6C53U, 0x6C54U, 0x6C55U, 0x6C56U, 0x6C57U, 0x6C58U,
+ 0x6C59U, 0x6C5AU, 0x6C61U, 0x6C62U, 0x6C63U, 0x6C64U, 0x6C65U, 0x6C66U,
+ 0x6C67U, 0x6C68U, 0x6C69U, 0x6C6AU, 0x6C6BU, 0x6C6CU, 0x6C6DU, 0x6C6EU,
+ 0x6C6FU, 0x6C70U, 0x6C71U, 0x6C72U, 0x6C73U, 0x6C74U, 0x6C75U, 0x6C76U,
+ 0x6C77U, 0x6C78U, 0x6C79U, 0x6C7AU, 0x6C30U, 0x6C31U, 0x6C32U, 0x6C33U,
+ 0x6C34U, 0x6C35U, 0x6C36U, 0x6C37U, 0x6C38U, 0x6C39U, 0x6C2BU, 0x6C2FU,
+ 0x6D41U, 0x6D42U, 0x6D43U, 0x6D44U, 0x6D45U, 0x6D46U, 0x6D47U, 0x6D48U,
+ 0x6D49U, 0x6D4AU, 0x6D4BU, 0x6D4CU, 0x6D4DU, 0x6D4EU, 0x6D4FU, 0x6D50U,
+ 0x6D51U, 0x6D52U, 0x6D53U, 0x6D54U, 0x6D55U, 0x6D56U, 0x6D57U, 0x6D58U,
+ 0x6D59U, 0x6D5AU, 0x6D61U, 0x6D62U, 0x6D63U, 0x6D64U, 0x6D65U, 0x6D66U,
+ 0x6D67U, 0x6D68U, 0x6D69U, 0x6D6AU, 0x6D6BU, 0x6D6CU, 0x6D6DU, 0x6D6EU,
+ 0x6D6FU, 0x6D70U, 0x6D71U, 0x6D72U, 0x6D73U, 0x6D74U, 0x6D75U, 0x6D76U,
+ 0x6D77U, 0x6D78U, 0x6D79U, 0x6D7AU, 0x6D30U, 0x6D31U, 0x6D32U, 0x6D33U,
+ 0x6D34U, 0x6D35U, 0x6D36U, 0x6D37U, 0x6D38U, 0x6D39U, 0x6D2BU, 0x6D2FU,
+ 0x6E41U, 0x6E42U, 0x6E43U, 0x6E44U, 0x6E45U, 0x6E46U, 0x6E47U, 0x6E48U,
+ 0x6E49U, 0x6E4AU, 0x6E4BU, 0x6E4CU, 0x6E4DU, 0x6E4EU, 0x6E4FU, 0x6E50U,
+ 0x6E51U, 0x6E52U, 0x6E53U, 0x6E54U, 0x6E55U, 0x6E56U, 0x6E57U, 0x6E58U,
+ 0x6E59U, 0x6E5AU, 0x6E61U, 0x6E62U, 0x6E63U, 0x6E64U, 0x6E65U, 0x6E66U,
+ 0x6E67U, 0x6E68U, 0x6E69U, 0x6E6AU, 0x6E6BU, 0x6E6CU, 0x6E6DU, 0x6E6EU,
+ 0x6E6FU, 0x6E70U, 0x6E71U, 0x6E72U, 0x6E73U, 0x6E74U, 0x6E75U, 0x6E76U,
+ 0x6E77U, 0x6E78U, 0x6E79U, 0x6E7AU, 0x6E30U, 0x6E31U, 0x6E32U, 0x6E33U,
+ 0x6E34U, 0x6E35U, 0x6E36U, 0x6E37U, 0x6E38U, 0x6E39U, 0x6E2BU, 0x6E2FU,
+ 0x6F41U, 0x6F42U, 0x6F43U, 0x6F44U, 0x6F45U, 0x6F46U, 0x6F47U, 0x6F48U,
+ 0x6F49U, 0x6F4AU, 0x6F4BU, 0x6F4CU, 0x6F4DU, 0x6F4EU, 0x6F4FU, 0x6F50U,
+ 0x6F51U, 0x6F52U, 0x6F53U, 0x6F54U, 0x6F55U, 0x6F56U, 0x6F57U, 0x6F58U,
+ 0x6F59U, 0x6F5AU, 0x6F61U, 0x6F62U, 0x6F63U, 0x6F64U, 0x6F65U, 0x6F66U,
+ 0x6F67U, 0x6F68U, 0x6F69U, 0x6F6AU, 0x6F6BU, 0x6F6CU, 0x6F6DU, 0x6F6EU,
+ 0x6F6FU, 0x6F70U, 0x6F71U, 0x6F72U, 0x6F73U, 0x6F74U, 0x6F75U, 0x6F76U,
+ 0x6F77U, 0x6F78U, 0x6F79U, 0x6F7AU, 0x6F30U, 0x6F31U, 0x6F32U, 0x6F33U,
+ 0x6F34U, 0x6F35U, 0x6F36U, 0x6F37U, 0x6F38U, 0x6F39U, 0x6F2BU, 0x6F2FU,
+ 0x7041U, 0x7042U, 0x7043U, 0x7044U, 0x7045U, 0x7046U, 0x7047U, 0x7048U,
+ 0x7049U, 0x704AU, 0x704BU, 0x704CU, 0x704DU, 0x704EU, 0x704FU, 0x7050U,
+ 0x7051U, 0x7052U, 0x7053U, 0x7054U, 0x7055U, 0x7056U, 0x7057U, 0x7058U,
+ 0x7059U, 0x705AU, 0x7061U, 0x7062U, 0x7063U, 0x7064U, 0x7065U, 0x7066U,
+ 0x7067U, 0x7068U, 0x7069U, 0x706AU, 0x706BU, 0x706CU, 0x706DU, 0x706EU,
+ 0x706FU, 0x7070U, 0x7071U, 0x7072U, 0x7073U, 0x7074U, 0x7075U, 0x7076U,
+ 0x7077U, 0x7078U, 0x7079U, 0x707AU, 0x7030U, 0x7031U, 0x7032U, 0x7033U,
+ 0x7034U, 0x7035U, 0x7036U, 0x7037U, 0x7038U, 0x7039U, 0x702BU, 0x702FU,
+ 0x7141U, 0x7142U, 0x7143U, 0x7144U, 0x7145U, 0x7146U, 0x7147U, 0x7148U,
+ 0x7149U, 0x714AU, 0x714BU, 0x714CU, 0x714DU, 0x714EU, 0x714FU, 0x7150U,
+ 0x7151U, 0x7152U, 0x7153U, 0x7154U, 0x7155U, 0x7156U, 0x7157U, 0x7158U,
+ 0x7159U, 0x715AU, 0x7161U, 0x7162U, 0x7163U, 0x7164U, 0x7165U, 0x7166U,
+ 0x7167U, 0x7168U, 0x7169U, 0x716AU, 0x716BU, 0x716CU, 0x716DU, 0x716EU,
+ 0x716FU, 0x7170U, 0x7171U, 0x7172U, 0x7173U, 0x7174U, 0x7175U, 0x7176U,
+ 0x7177U, 0x7178U, 0x7179U, 0x717AU, 0x7130U, 0x7131U, 0x7132U, 0x7133U,
+ 0x7134U, 0x7135U, 0x7136U, 0x7137U, 0x7138U, 0x7139U, 0x712BU, 0x712FU,
+ 0x7241U, 0x7242U, 0x7243U, 0x7244U, 0x7245U, 0x7246U, 0x7247U, 0x7248U,
+ 0x7249U, 0x724AU, 0x724BU, 0x724CU, 0x724DU, 0x724EU, 0x724FU, 0x7250U,
+ 0x7251U, 0x7252U, 0x7253U, 0x7254U, 0x7255U, 0x7256U, 0x7257U, 0x7258U,
+ 0x7259U, 0x725AU, 0x7261U, 0x7262U, 0x7263U, 0x7264U, 0x7265U, 0x7266U,
+ 0x7267U, 0x7268U, 0x7269U, 0x726AU, 0x726BU, 0x726CU, 0x726DU, 0x726EU,
+ 0x726FU, 0x7270U, 0x7271U, 0x7272U, 0x7273U, 0x7274U, 0x7275U, 0x7276U,
+ 0x7277U, 0x7278U, 0x7279U, 0x727AU, 0x7230U, 0x7231U, 0x7232U, 0x7233U,
+ 0x7234U, 0x7235U, 0x7236U, 0x7237U, 0x7238U, 0x7239U, 0x722BU, 0x722FU,
+ 0x7341U, 0x7342U, 0x7343U, 0x7344U, 0x7345U, 0x7346U, 0x7347U, 0x7348U,
+ 0x7349U, 0x734AU, 0x734BU, 0x734CU, 0x734DU, 0x734EU, 0x734FU, 0x7350U,
+ 0x7351U, 0x7352U, 0x7353U, 0x7354U, 0x7355U, 0x7356U, 0x7357U, 0x7358U,
+ 0x7359U, 0x735AU, 0x7361U, 0x7362U, 0x7363U, 0x7364U, 0x7365U, 0x7366U,
+ 0x7367U, 0x7368U, 0x7369U, 0x736AU, 0x736BU, 0x736CU, 0x736DU, 0x736EU,
+ 0x736FU, 0x7370U, 0x7371U, 0x7372U, 0x7373U, 0x7374U, 0x7375U, 0x7376U,
+ 0x7377U, 0x7378U, 0x7379U, 0x737AU, 0x7330U, 0x7331U, 0x7332U, 0x7333U,
+ 0x7334U, 0x7335U, 0x7336U, 0x7337U, 0x7338U, 0x7339U, 0x732BU, 0x732FU,
+ 0x7441U, 0x7442U, 0x7443U, 0x7444U, 0x7445U, 0x7446U, 0x7447U, 0x7448U,
+ 0x7449U, 0x744AU, 0x744BU, 0x744CU, 0x744DU, 0x744EU, 0x744FU, 0x7450U,
+ 0x7451U, 0x7452U, 0x7453U, 0x7454U, 0x7455U, 0x7456U, 0x7457U, 0x7458U,
+ 0x7459U, 0x745AU, 0x7461U, 0x7462U, 0x7463U, 0x7464U, 0x7465U, 0x7466U,
+ 0x7467U, 0x7468U, 0x7469U, 0x746AU, 0x746BU, 0x746CU, 0x746DU, 0x746EU,
+ 0x746FU, 0x7470U, 0x7471U, 0x7472U, 0x7473U, 0x7474U, 0x7475U, 0x7476U,
+ 0x7477U, 0x7478U, 0x7479U, 0x747AU, 0x7430U, 0x7431U, 0x7432U, 0x7433U,
+ 0x7434U, 0x7435U, 0x7436U, 0x7437U, 0x7438U, 0x7439U, 0x742BU, 0x742FU,
+ 0x7541U, 0x7542U, 0x7543U, 0x7544U, 0x7545U, 0x7546U, 0x7547U, 0x7548U,
+ 0x7549U, 0x754AU, 0x754BU, 0x754CU, 0x754DU, 0x754EU, 0x754FU, 0x7550U,
+ 0x7551U, 0x7552U, 0x7553U, 0x7554U, 0x7555U, 0x7556U, 0x7557U, 0x7558U,
+ 0x7559U, 0x755AU, 0x7561U, 0x7562U, 0x7563U, 0x7564U, 0x7565U, 0x7566U,
+ 0x7567U, 0x7568U, 0x7569U, 0x756AU, 0x756BU, 0x756CU, 0x756DU, 0x756EU,
+ 0x756FU, 0x7570U, 0x7571U, 0x7572U, 0x7573U, 0x7574U, 0x7575U, 0x7576U,
+ 0x7577U, 0x7578U, 0x7579U, 0x757AU, 0x7530U, 0x7531U, 0x7532U, 0x7533U,
+ 0x7534U, 0x7535U, 0x7536U, 0x7537U, 0x7538U, 0x7539U, 0x752BU, 0x752FU,
+ 0x7641U, 0x7642U, 0x7643U, 0x7644U, 0x7645U, 0x7646U, 0x7647U, 0x7648U,
+ 0x7649U, 0x764AU, 0x764BU, 0x764CU, 0x764DU, 0x764EU, 0x764FU, 0x7650U,
+ 0x7651U, 0x7652U, 0x7653U, 0x7654U, 0x7655U, 0x7656U, 0x7657U, 0x7658U,
+ 0x7659U, 0x765AU, 0x7661U, 0x7662U, 0x7663U, 0x7664U, 0x7665U, 0x7666U,
+ 0x7667U, 0x7668U, 0x7669U, 0x766AU, 0x766BU, 0x766CU, 0x766DU, 0x766EU,
+ 0x766FU, 0x7670U, 0x7671U, 0x7672U, 0x7673U, 0x7674U, 0x7675U, 0x7676U,
+ 0x7677U, 0x7678U, 0x7679U, 0x767AU, 0x7630U, 0x7631U, 0x7632U, 0x7633U,
+ 0x7634U, 0x7635U, 0x7636U, 0x7637U, 0x7638U, 0x7639U, 0x762BU, 0x762FU,
+ 0x7741U, 0x7742U, 0x7743U, 0x7744U, 0x7745U, 0x7746U, 0x7747U, 0x7748U,
+ 0x7749U, 0x774AU, 0x774BU, 0x774CU, 0x774DU, 0x774EU, 0x774FU, 0x7750U,
+ 0x7751U, 0x7752U, 0x7753U, 0x7754U, 0x7755U, 0x7756U, 0x7757U, 0x7758U,
+ 0x7759U, 0x775AU, 0x7761U, 0x7762U, 0x7763U, 0x7764U, 0x7765U, 0x7766U,
+ 0x7767U, 0x7768U, 0x7769U, 0x776AU, 0x776BU, 0x776CU, 0x776DU, 0x776EU,
+ 0x776FU, 0x7770U, 0x7771U, 0x7772U, 0x7773U, 0x7774U, 0x7775U, 0x7776U,
+ 0x7777U, 0x7778U, 0x7779U, 0x777AU, 0x7730U, 0x7731U, 0x7732U, 0x7733U,
+ 0x7734U, 0x7735U, 0x7736U, 0x7737U, 0x7738U, 0x7739U, 0x772BU, 0x772FU,
+ 0x7841U, 0x7842U, 0x7843U, 0x7844U, 0x7845U, 0x7846U, 0x7847U, 0x7848U,
+ 0x7849U, 0x784AU, 0x784BU, 0x784CU, 0x784DU, 0x784EU, 0x784FU, 0x7850U,
+ 0x7851U, 0x7852U, 0x7853U, 0x7854U, 0x7855U, 0x7856U, 0x7857U, 0x7858U,
+ 0x7859U, 0x785AU, 0x7861U, 0x7862U, 0x7863U, 0x7864U, 0x7865U, 0x7866U,
+ 0x7867U, 0x7868U, 0x7869U, 0x786AU, 0x786BU, 0x786CU, 0x786DU, 0x786EU,
+ 0x786FU, 0x7870U, 0x7871U, 0x7872U, 0x7873U, 0x7874U, 0x7875U, 0x7876U,
+ 0x7877U, 0x7878U, 0x7879U, 0x787AU, 0x7830U, 0x7831U, 0x7832U, 0x7833U,
+ 0x7834U, 0x7835U, 0x7836U, 0x7837U, 0x7838U, 0x7839U, 0x782BU, 0x782FU,
+ 0x7941U, 0x7942U, 0x7943U, 0x7944U, 0x7945U, 0x7946U, 0x7947U, 0x7948U,
+ 0x7949U, 0x794AU, 0x794BU, 0x794CU, 0x794DU, 0x794EU, 0x794FU, 0x7950U,
+ 0x7951U, 0x7952U, 0x7953U, 0x7954U, 0x7955U, 0x7956U, 0x7957U, 0x7958U,
+ 0x7959U, 0x795AU, 0x7961U, 0x7962U, 0x7963U, 0x7964U, 0x7965U, 0x7966U,
+ 0x7967U, 0x7968U, 0x7969U, 0x796AU, 0x796BU, 0x796CU, 0x796DU, 0x796EU,
+ 0x796FU, 0x7970U, 0x7971U, 0x7972U, 0x7973U, 0x7974U, 0x7975U, 0x7976U,
+ 0x7977U, 0x7978U, 0x7979U, 0x797AU, 0x7930U, 0x7931U, 0x7932U, 0x7933U,
+ 0x7934U, 0x7935U, 0x7936U, 0x7937U, 0x7938U, 0x7939U, 0x792BU, 0x792FU,
+ 0x7A41U, 0x7A42U, 0x7A43U, 0x7A44U, 0x7A45U, 0x7A46U, 0x7A47U, 0x7A48U,
+ 0x7A49U, 0x7A4AU, 0x7A4BU, 0x7A4CU, 0x7A4DU, 0x7A4EU, 0x7A4FU, 0x7A50U,
+ 0x7A51U, 0x7A52U, 0x7A53U, 0x7A54U, 0x7A55U, 0x7A56U, 0x7A57U, 0x7A58U,
+ 0x7A59U, 0x7A5AU, 0x7A61U, 0x7A62U, 0x7A63U, 0x7A64U, 0x7A65U, 0x7A66U,
+ 0x7A67U, 0x7A68U, 0x7A69U, 0x7A6AU, 0x7A6BU, 0x7A6CU, 0x7A6DU, 0x7A6EU,
+ 0x7A6FU, 0x7A70U, 0x7A71U, 0x7A72U, 0x7A73U, 0x7A74U, 0x7A75U, 0x7A76U,
+ 0x7A77U, 0x7A78U, 0x7A79U, 0x7A7AU, 0x7A30U, 0x7A31U, 0x7A32U, 0x7A33U,
+ 0x7A34U, 0x7A35U, 0x7A36U, 0x7A37U, 0x7A38U, 0x7A39U, 0x7A2BU, 0x7A2FU,
+ 0x3041U, 0x3042U, 0x3043U, 0x3044U, 0x3045U, 0x3046U, 0x3047U, 0x3048U,
+ 0x3049U, 0x304AU, 0x304BU, 0x304CU, 0x304DU, 0x304EU, 0x304FU, 0x3050U,
+ 0x3051U, 0x3052U, 0x3053U, 0x3054U, 0x3055U, 0x3056U, 0x3057U, 0x3058U,
+ 0x3059U, 0x305AU, 0x3061U, 0x3062U, 0x3063U, 0x3064U, 0x3065U, 0x3066U,
+ 0x3067U, 0x3068U, 0x3069U, 0x306AU, 0x306BU, 0x306CU, 0x306DU, 0x306EU,
+ 0x306FU, 0x3070U, 0x3071U, 0x3072U, 0x3073U, 0x3074U, 0x3075U, 0x3076U,
+ 0x3077U, 0x3078U, 0x3079U, 0x307AU, 0x3030U, 0x3031U, 0x3032U, 0x3033U,
+ 0x3034U, 0x3035U, 0x3036U, 0x3037U, 0x3038U, 0x3039U, 0x302BU, 0x302FU,
+ 0x3141U, 0x3142U, 0x3143U, 0x3144U, 0x3145U, 0x3146U, 0x3147U, 0x3148U,
+ 0x3149U, 0x314AU, 0x314BU, 0x314CU, 0x314DU, 0x314EU, 0x314FU, 0x3150U,
+ 0x3151U, 0x3152U, 0x3153U, 0x3154U, 0x3155U, 0x3156U, 0x3157U, 0x3158U,
+ 0x3159U, 0x315AU, 0x3161U, 0x3162U, 0x3163U, 0x3164U, 0x3165U, 0x3166U,
+ 0x3167U, 0x3168U, 0x3169U, 0x316AU, 0x316BU, 0x316CU, 0x316DU, 0x316EU,
+ 0x316FU, 0x3170U, 0x3171U, 0x3172U, 0x3173U, 0x3174U, 0x3175U, 0x3176U,
+ 0x3177U, 0x3178U, 0x3179U, 0x317AU, 0x3130U, 0x3131U, 0x3132U, 0x3133U,
+ 0x3134U, 0x3135U, 0x3136U, 0x3137U, 0x3138U, 0x3139U, 0x312BU, 0x312FU,
+ 0x3241U, 0x3242U, 0x3243U, 0x3244U, 0x3245U, 0x3246U, 0x3247U, 0x3248U,
+ 0x3249U, 0x324AU, 0x324BU, 0x324CU, 0x324DU, 0x324EU, 0x324FU, 0x3250U,
+ 0x3251U, 0x3252U, 0x3253U, 0x3254U, 0x3255U, 0x3256U, 0x3257U, 0x3258U,
+ 0x3259U, 0x325AU, 0x3261U, 0x3262U, 0x3263U, 0x3264U, 0x3265U, 0x3266U,
+ 0x3267U, 0x3268U, 0x3269U, 0x326AU, 0x326BU, 0x326CU, 0x326DU, 0x326EU,
+ 0x326FU, 0x3270U, 0x3271U, 0x3272U, 0x3273U, 0x3274U, 0x3275U, 0x3276U,
+ 0x3277U, 0x3278U, 0x3279U, 0x327AU, 0x3230U, 0x3231U, 0x3232U, 0x3233U,
+ 0x3234U, 0x3235U, 0x3236U, 0x3237U, 0x3238U, 0x3239U, 0x322BU, 0x322FU,
+ 0x3341U, 0x3342U, 0x3343U, 0x3344U, 0x3345U, 0x3346U, 0x3347U, 0x3348U,
+ 0x3349U, 0x334AU, 0x334BU, 0x334CU, 0x334DU, 0x334EU, 0x334FU, 0x3350U,
+ 0x3351U, 0x3352U, 0x3353U, 0x3354U, 0x3355U, 0x3356U, 0x3357U, 0x3358U,
+ 0x3359U, 0x335AU, 0x3361U, 0x3362U, 0x3363U, 0x3364U, 0x3365U, 0x3366U,
+ 0x3367U, 0x3368U, 0x3369U, 0x336AU, 0x336BU, 0x336CU, 0x336DU, 0x336EU,
+ 0x336FU, 0x3370U, 0x3371U, 0x3372U, 0x3373U, 0x3374U, 0x3375U, 0x3376U,
+ 0x3377U, 0x3378U, 0x3379U, 0x337AU, 0x3330U, 0x3331U, 0x3332U, 0x3333U,
+ 0x3334U, 0x3335U, 0x3336U, 0x3337U, 0x3338U, 0x3339U, 0x332BU, 0x332FU,
+ 0x3441U, 0x3442U, 0x3443U, 0x3444U, 0x3445U, 0x3446U, 0x3447U, 0x3448U,
+ 0x3449U, 0x344AU, 0x344BU, 0x344CU, 0x344DU, 0x344EU, 0x344FU, 0x3450U,
+ 0x3451U, 0x3452U, 0x3453U, 0x3454U, 0x3455U, 0x3456U, 0x3457U, 0x3458U,
+ 0x3459U, 0x345AU, 0x3461U, 0x3462U, 0x3463U, 0x3464U, 0x3465U, 0x3466U,
+ 0x3467U, 0x3468U, 0x3469U, 0x346AU, 0x346BU, 0x346CU, 0x346DU, 0x346EU,
+ 0x346FU, 0x3470U, 0x3471U, 0x3472U, 0x3473U, 0x3474U, 0x3475U, 0x3476U,
+ 0x3477U, 0x3478U, 0x3479U, 0x347AU, 0x3430U, 0x3431U, 0x3432U, 0x3433U,
+ 0x3434U, 0x3435U, 0x3436U, 0x3437U, 0x3438U, 0x3439U, 0x342BU, 0x342FU,
+ 0x3541U, 0x3542U, 0x3543U, 0x3544U, 0x3545U, 0x3546U, 0x3547U, 0x3548U,
+ 0x3549U, 0x354AU, 0x354BU, 0x354CU, 0x354DU, 0x354EU, 0x354FU, 0x3550U,
+ 0x3551U, 0x3552U, 0x3553U, 0x3554U, 0x3555U, 0x3556U, 0x3557U, 0x3558U,
+ 0x3559U, 0x355AU, 0x3561U, 0x3562U, 0x3563U, 0x3564U, 0x3565U, 0x3566U,
+ 0x3567U, 0x3568U, 0x3569U, 0x356AU, 0x356BU, 0x356CU, 0x356DU, 0x356EU,
+ 0x356FU, 0x3570U, 0x3571U, 0x3572U, 0x3573U, 0x3574U, 0x3575U, 0x3576U,
+ 0x3577U, 0x3578U, 0x3579U, 0x357AU, 0x3530U, 0x3531U, 0x3532U, 0x3533U,
+ 0x3534U, 0x3535U, 0x3536U, 0x3537U, 0x3538U, 0x3539U, 0x352BU, 0x352FU,
+ 0x3641U, 0x3642U, 0x3643U, 0x3644U, 0x3645U, 0x3646U, 0x3647U, 0x3648U,
+ 0x3649U, 0x364AU, 0x364BU, 0x364CU, 0x364DU, 0x364EU, 0x364FU, 0x3650U,
+ 0x3651U, 0x3652U, 0x3653U, 0x3654U, 0x3655U, 0x3656U, 0x3657U, 0x3658U,
+ 0x3659U, 0x365AU, 0x3661U, 0x3662U, 0x3663U, 0x3664U, 0x3665U, 0x3666U,
+ 0x3667U, 0x3668U, 0x3669U, 0x366AU, 0x366BU, 0x366CU, 0x366DU, 0x366EU,
+ 0x366FU, 0x3670U, 0x3671U, 0x3672U, 0x3673U, 0x3674U, 0x3675U, 0x3676U,
+ 0x3677U, 0x3678U, 0x3679U, 0x367AU, 0x3630U, 0x3631U, 0x3632U, 0x3633U,
+ 0x3634U, 0x3635U, 0x3636U, 0x3637U, 0x3638U, 0x3639U, 0x362BU, 0x362FU,
+ 0x3741U, 0x3742U, 0x3743U, 0x3744U, 0x3745U, 0x3746U, 0x3747U, 0x3748U,
+ 0x3749U, 0x374AU, 0x374BU, 0x374CU, 0x374DU, 0x374EU, 0x374FU, 0x3750U,
+ 0x3751U, 0x3752U, 0x3753U, 0x3754U, 0x3755U, 0x3756U, 0x3757U, 0x3758U,
+ 0x3759U, 0x375AU, 0x3761U, 0x3762U, 0x3763U, 0x3764U, 0x3765U, 0x3766U,
+ 0x3767U, 0x3768U, 0x3769U, 0x376AU, 0x376BU, 0x376CU, 0x376DU, 0x376EU,
+ 0x376FU, 0x3770U, 0x3771U, 0x3772U, 0x3773U, 0x3774U, 0x3775U, 0x3776U,
+ 0x3777U, 0x3778U, 0x3779U, 0x377AU, 0x3730U, 0x3731U, 0x3732U, 0x3733U,
+ 0x3734U, 0x3735U, 0x3736U, 0x3737U, 0x3738U, 0x3739U, 0x372BU, 0x372FU,
+ 0x3841U, 0x3842U, 0x3843U, 0x3844U, 0x3845U, 0x3846U, 0x3847U, 0x3848U,
+ 0x3849U, 0x384AU, 0x384BU, 0x384CU, 0x384DU, 0x384EU, 0x384FU, 0x3850U,
+ 0x3851U, 0x3852U, 0x3853U, 0x3854U, 0x3855U, 0x3856U, 0x3857U, 0x3858U,
+ 0x3859U, 0x385AU, 0x3861U, 0x3862U, 0x3863U, 0x3864U, 0x3865U, 0x3866U,
+ 0x3867U, 0x3868U, 0x3869U, 0x386AU, 0x386BU, 0x386CU, 0x386DU, 0x386EU,
+ 0x386FU, 0x3870U, 0x3871U, 0x3872U, 0x3873U, 0x3874U, 0x3875U, 0x3876U,
+ 0x3877U, 0x3878U, 0x3879U, 0x387AU, 0x3830U, 0x3831U, 0x3832U, 0x3833U,
+ 0x3834U, 0x3835U, 0x3836U, 0x3837U, 0x3838U, 0x3839U, 0x382BU, 0x382FU,
+ 0x3941U, 0x3942U, 0x3943U, 0x3944U, 0x3945U, 0x3946U, 0x3947U, 0x3948U,
+ 0x3949U, 0x394AU, 0x394BU, 0x394CU, 0x394DU, 0x394EU, 0x394FU, 0x3950U,
+ 0x3951U, 0x3952U, 0x3953U, 0x3954U, 0x3955U, 0x3956U, 0x3957U, 0x3958U,
+ 0x3959U, 0x395AU, 0x3961U, 0x3962U, 0x3963U, 0x3964U, 0x3965U, 0x3966U,
+ 0x3967U, 0x3968U, 0x3969U, 0x396AU, 0x396BU, 0x396CU, 0x396DU, 0x396EU,
+ 0x396FU, 0x3970U, 0x3971U, 0x3972U, 0x3973U, 0x3974U, 0x3975U, 0x3976U,
+ 0x3977U, 0x3978U, 0x3979U, 0x397AU, 0x3930U, 0x3931U, 0x3932U, 0x3933U,
+ 0x3934U, 0x3935U, 0x3936U, 0x3937U, 0x3938U, 0x3939U, 0x392BU, 0x392FU,
+ 0x2B41U, 0x2B42U, 0x2B43U, 0x2B44U, 0x2B45U, 0x2B46U, 0x2B47U, 0x2B48U,
+ 0x2B49U, 0x2B4AU, 0x2B4BU, 0x2B4CU, 0x2B4DU, 0x2B4EU, 0x2B4FU, 0x2B50U,
+ 0x2B51U, 0x2B52U, 0x2B53U, 0x2B54U, 0x2B55U, 0x2B56U, 0x2B57U, 0x2B58U,
+ 0x2B59U, 0x2B5AU, 0x2B61U, 0x2B62U, 0x2B63U, 0x2B64U, 0x2B65U, 0x2B66U,
+ 0x2B67U, 0x2B68U, 0x2B69U, 0x2B6AU, 0x2B6BU, 0x2B6CU, 0x2B6DU, 0x2B6EU,
+ 0x2B6FU, 0x2B70U, 0x2B71U, 0x2B72U, 0x2B73U, 0x2B74U, 0x2B75U, 0x2B76U,
+ 0x2B77U, 0x2B78U, 0x2B79U, 0x2B7AU, 0x2B30U, 0x2B31U, 0x2B32U, 0x2B33U,
+ 0x2B34U, 0x2B35U, 0x2B36U, 0x2B37U, 0x2B38U, 0x2B39U, 0x2B2BU, 0x2B2FU,
+ 0x2F41U, 0x2F42U, 0x2F43U, 0x2F44U, 0x2F45U, 0x2F46U, 0x2F47U, 0x2F48U,
+ 0x2F49U, 0x2F4AU, 0x2F4BU, 0x2F4CU, 0x2F4DU, 0x2F4EU, 0x2F4FU, 0x2F50U,
+ 0x2F51U, 0x2F52U, 0x2F53U, 0x2F54U, 0x2F55U, 0x2F56U, 0x2F57U, 0x2F58U,
+ 0x2F59U, 0x2F5AU, 0x2F61U, 0x2F62U, 0x2F63U, 0x2F64U, 0x2F65U, 0x2F66U,
+ 0x2F67U, 0x2F68U, 0x2F69U, 0x2F6AU, 0x2F6BU, 0x2F6CU, 0x2F6DU, 0x2F6EU,
+ 0x2F6FU, 0x2F70U, 0x2F71U, 0x2F72U, 0x2F73U, 0x2F74U, 0x2F75U, 0x2F76U,
+ 0x2F77U, 0x2F78U, 0x2F79U, 0x2F7AU, 0x2F30U, 0x2F31U, 0x2F32U, 0x2F33U,
+ 0x2F34U, 0x2F35U, 0x2F36U, 0x2F37U, 0x2F38U, 0x2F39U, 0x2F2BU, 0x2F2FU,
+#endif
+};
diff --git a/src/third-party/base64/lib/tables/tables.c b/src/third-party/base64/lib/tables/tables.c
new file mode 100644
index 0000000..45778b6
--- /dev/null
+++ b/src/third-party/base64/lib/tables/tables.c
@@ -0,0 +1,40 @@
+#include "tables.h"
+
+const uint8_t
+base64_table_enc_6bit[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "+/";
+
+// In the lookup table below, note that the value for '=' (character 61) is
+// 254, not 255. This character is used for in-band signaling of the end of
+// the datastream, and we will use that later. The characters A-Z, a-z, 0-9
+// and + / are mapped to their "decoded" values. The other bytes all map to
+// the value 255, which flags them as "invalid input".
+
+const uint8_t
+base64_table_dec_8bit[] =
+{
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 0..15
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 16..31
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, // 32..47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 254, 255, 255, // 48..63
+ 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64..79
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, // 80..95
+ 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96..111
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255, // 112..127
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 128..143
+ 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,
+};
+
+#if BASE64_WORDSIZE >= 32
+# include "table_dec_32bit.h"
+# include "table_enc_12bit.h"
+#endif
diff --git a/src/third-party/base64/lib/tables/tables.h b/src/third-party/base64/lib/tables/tables.h
new file mode 100644
index 0000000..cb74268
--- /dev/null
+++ b/src/third-party/base64/lib/tables/tables.h
@@ -0,0 +1,23 @@
+#ifndef BASE64_TABLES_H
+#define BASE64_TABLES_H
+
+#include <stdint.h>
+
+#include "../env.h"
+
+// These tables are used by all codecs for fallback plain encoding/decoding:
+extern const uint8_t base64_table_enc_6bit[];
+extern const uint8_t base64_table_dec_8bit[];
+
+// These tables are used for the 32-bit and 64-bit generic decoders:
+#if BASE64_WORDSIZE >= 32
+extern const uint32_t base64_table_dec_32bit_d0[];
+extern const uint32_t base64_table_dec_32bit_d1[];
+extern const uint32_t base64_table_dec_32bit_d2[];
+extern const uint32_t base64_table_dec_32bit_d3[];
+
+// This table is used by the 32 and 64-bit generic encoders:
+extern const uint16_t base64_table_enc_12bit[];
+#endif
+
+#endif // BASE64_TABLES_H
diff --git a/src/third-party/doctest-root/doctest/doctest.h b/src/third-party/doctest-root/doctest/doctest.h
new file mode 100644
index 0000000..aa2724c
--- /dev/null
+++ b/src/third-party/doctest-root/doctest/doctest.h
@@ -0,0 +1,7019 @@
+// ====================================================================== lgtm [cpp/missing-header-guard]
+// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! ==
+// ======================================================================
+//
+// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD
+//
+// Copyright (c) 2016-2021 Viktor Kirilov
+//
+// Distributed under the MIT Software License
+// See accompanying file LICENSE.txt or copy at
+// https://opensource.org/licenses/MIT
+//
+// The documentation can be found at the library's page:
+// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md
+//
+// =================================================================================================
+// =================================================================================================
+// =================================================================================================
+//
+// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2
+// which uses the Boost Software License - Version 1.0
+// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt
+//
+// The concept of subcases (sections in Catch) and expression decomposition are from there.
+// Some parts of the code are taken directly:
+// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<>
+// - the Approx() helper class for floating point comparison
+// - colors in the console
+// - breaking into a debugger
+// - signal / SEH handling
+// - timer
+// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste)
+//
+// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest
+// which uses the Boost Software License - Version 1.0
+// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt
+//
+// =================================================================================================
+// =================================================================================================
+// =================================================================================================
+
+#ifndef DOCTEST_LIBRARY_INCLUDED
+#define DOCTEST_LIBRARY_INCLUDED
+
+// =================================================================================================
+// == VERSION ======================================================================================
+// =================================================================================================
+
+#define DOCTEST_VERSION_MAJOR 2
+#define DOCTEST_VERSION_MINOR 4
+#define DOCTEST_VERSION_PATCH 9
+
+// util we need here
+#define DOCTEST_TOSTR_IMPL(x) #x
+#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x)
+
+#define DOCTEST_VERSION_STR \
+ DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \
+ DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \
+ DOCTEST_TOSTR(DOCTEST_VERSION_PATCH)
+
+#define DOCTEST_VERSION \
+ (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH)
+
+// =================================================================================================
+// == COMPILER VERSION =============================================================================
+// =================================================================================================
+
+// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect
+
+#ifdef _MSC_VER
+#define DOCTEST_CPLUSPLUS _MSVC_LANG
+#else
+#define DOCTEST_CPLUSPLUS __cplusplus
+#endif
+
+#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH))
+
+// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl...
+#if defined(_MSC_VER) && defined(_MSC_FULL_VER)
+#if _MSC_VER == _MSC_FULL_VER / 10000
+#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000)
+#else // MSVC
+#define DOCTEST_MSVC \
+ DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000)
+#endif // MSVC
+#endif // MSVC
+#if defined(__clang__) && defined(__clang_minor__)
+#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__)
+#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \
+ !defined(__INTEL_COMPILER)
+#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
+#endif // GCC
+
+#ifndef DOCTEST_MSVC
+#define DOCTEST_MSVC 0
+#endif // DOCTEST_MSVC
+#ifndef DOCTEST_CLANG
+#define DOCTEST_CLANG 0
+#endif // DOCTEST_CLANG
+#ifndef DOCTEST_GCC
+#define DOCTEST_GCC 0
+#endif // DOCTEST_GCC
+
+// =================================================================================================
+// == COMPILER WARNINGS HELPERS ====================================================================
+// =================================================================================================
+
+#if DOCTEST_CLANG
+#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x)
+#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push")
+#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w)
+#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop")
+#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w)
+#else // DOCTEST_CLANG
+#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+#define DOCTEST_CLANG_SUPPRESS_WARNING(w)
+#define DOCTEST_CLANG_SUPPRESS_WARNING_POP
+#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w)
+#endif // DOCTEST_CLANG
+
+#if DOCTEST_GCC
+#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x)
+#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push")
+#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w)
+#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop")
+#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \
+ DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w)
+#else // DOCTEST_GCC
+#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+#define DOCTEST_GCC_SUPPRESS_WARNING(w)
+#define DOCTEST_GCC_SUPPRESS_WARNING_POP
+#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w)
+#endif // DOCTEST_GCC
+
+#if DOCTEST_MSVC
+#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push))
+#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w))
+#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop))
+#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \
+ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w)
+#else // DOCTEST_MSVC
+#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+#define DOCTEST_MSVC_SUPPRESS_WARNING(w)
+#define DOCTEST_MSVC_SUPPRESS_WARNING_POP
+#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w)
+#endif // DOCTEST_MSVC
+
+// =================================================================================================
+// == COMPILER WARNINGS ============================================================================
+// =================================================================================================
+
+// both the header and the implementation suppress all of these,
+// so it only makes sense to aggregrate them like so
+#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \
+ DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \
+ \
+ DOCTEST_GCC_SUPPRESS_WARNING_PUSH \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \
+ \
+ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \
+ /* these 4 also disabled globally via cmake: */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \
+ /* */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \
+ /* static analysis */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */
+
+#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP \
+ DOCTEST_GCC_SUPPRESS_WARNING_POP \
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH
+
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated")
+
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo")
+
+DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted
+
+#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \
+ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */
+
+#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+// =================================================================================================
+// == FEATURE DETECTION ============================================================================
+// =================================================================================================
+
+// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support
+// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx
+// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html
+// MSVC version table:
+// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering
+// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022)
+// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019)
+// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017)
+// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
+// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
+// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
+// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
+// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008)
+// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005)
+
+// Universal Windows Platform support
+#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+#define DOCTEST_CONFIG_NO_WINDOWS_SEH
+#endif // WINAPI_FAMILY
+#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
+#define DOCTEST_CONFIG_WINDOWS_SEH
+#endif // MSVC
+#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH)
+#undef DOCTEST_CONFIG_WINDOWS_SEH
+#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH
+
+#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \
+ !defined(__EMSCRIPTEN__) && !defined(__wasi__)
+#define DOCTEST_CONFIG_POSIX_SIGNALS
+#endif // _WIN32
+#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS)
+#undef DOCTEST_CONFIG_POSIX_SIGNALS
+#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) \
+ || defined(__wasi__)
+#define DOCTEST_CONFIG_NO_EXCEPTIONS
+#endif // no exceptions
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+#define DOCTEST_CONFIG_NO_EXCEPTIONS
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS)
+#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+
+#ifdef __wasi__
+#define DOCTEST_CONFIG_NO_MULTITHREADING
+#endif
+
+#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT)
+#define DOCTEST_CONFIG_IMPLEMENT
+#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+#if DOCTEST_MSVC
+#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport)
+#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport)
+#else // MSVC
+#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport))
+#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport))
+#endif // MSVC
+#else // _WIN32
+#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default")))
+#define DOCTEST_SYMBOL_IMPORT
+#endif // _WIN32
+
+#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+#ifdef DOCTEST_CONFIG_IMPLEMENT
+#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT
+#else // DOCTEST_CONFIG_IMPLEMENT
+#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT
+#endif // DOCTEST_CONFIG_IMPLEMENT
+#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+#define DOCTEST_INTERFACE
+#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+
+// needed for extern template instantiations
+// see https://github.com/fmtlib/fmt/issues/2228
+#if DOCTEST_MSVC
+#define DOCTEST_INTERFACE_DECL
+#define DOCTEST_INTERFACE_DEF DOCTEST_INTERFACE
+#else // DOCTEST_MSVC
+#define DOCTEST_INTERFACE_DECL DOCTEST_INTERFACE
+#define DOCTEST_INTERFACE_DEF
+#endif // DOCTEST_MSVC
+
+#define DOCTEST_EMPTY
+
+#if DOCTEST_MSVC
+#define DOCTEST_NOINLINE __declspec(noinline)
+#define DOCTEST_UNUSED
+#define DOCTEST_ALIGNMENT(x)
+#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0)
+#define DOCTEST_NOINLINE
+#define DOCTEST_UNUSED
+#define DOCTEST_ALIGNMENT(x)
+#else
+#define DOCTEST_NOINLINE __attribute__((noinline))
+#define DOCTEST_UNUSED __attribute__((unused))
+#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x)))
+#endif
+
+#ifndef DOCTEST_NORETURN
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_NORETURN
+#else // DOCTEST_MSVC
+#define DOCTEST_NORETURN [[noreturn]]
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_NORETURN
+
+#ifndef DOCTEST_NOEXCEPT
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_NOEXCEPT
+#else // DOCTEST_MSVC
+#define DOCTEST_NOEXCEPT noexcept
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_NOEXCEPT
+
+#ifndef DOCTEST_CONSTEXPR
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_CONSTEXPR const
+#define DOCTEST_CONSTEXPR_FUNC inline
+#else // DOCTEST_MSVC
+#define DOCTEST_CONSTEXPR constexpr
+#define DOCTEST_CONSTEXPR_FUNC constexpr
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_CONSTEXPR
+
+// =================================================================================================
+// == FEATURE DETECTION END ========================================================================
+// =================================================================================================
+
+#define DOCTEST_DECLARE_INTERFACE(name) \
+ virtual ~name(); \
+ name() = default; \
+ name(const name&) = delete; \
+ name(name&&) = delete; \
+ name& operator=(const name&) = delete; \
+ name& operator=(name&&) = delete;
+
+#define DOCTEST_DEFINE_INTERFACE(name) \
+ name::~name() = default;
+
+// internal macros for string concatenation and anonymous variable name generation
+#define DOCTEST_CAT_IMPL(s1, s2) s1##s2
+#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2)
+#ifdef __COUNTER__ // not standard and may be missing for some compilers
+#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__)
+#else // __COUNTER__
+#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__)
+#endif // __COUNTER__
+
+#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
+#define DOCTEST_REF_WRAP(x) x&
+#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
+#define DOCTEST_REF_WRAP(x) x
+#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
+
+// not using __APPLE__ because... this is how Catch does it
+#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
+#define DOCTEST_PLATFORM_MAC
+#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+#define DOCTEST_PLATFORM_IPHONE
+#elif defined(_WIN32)
+#define DOCTEST_PLATFORM_WINDOWS
+#elif defined(__wasi__)
+#define DOCTEST_PLATFORM_WASI
+#else // DOCTEST_PLATFORM
+#define DOCTEST_PLATFORM_LINUX
+#endif // DOCTEST_PLATFORM
+
+namespace doctest { namespace detail {
+ static DOCTEST_CONSTEXPR int consume(const int*, int) noexcept { return 0; }
+}}
+
+#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \
+ static const int var = doctest::detail::consume(&var, __VA_ARGS__); \
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+#ifndef DOCTEST_BREAK_INTO_DEBUGGER
+// should probably take a look at https://github.com/scottt/debugbreak
+#ifdef DOCTEST_PLATFORM_LINUX
+#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
+// Break at the location of the failing check if possible
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler)
+#else
+#include <signal.h>
+#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP)
+#endif
+#elif defined(DOCTEST_PLATFORM_MAC)
+#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386)
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler)
+#elif defined(__ppc__) || defined(__ppc64__)
+// https://www.cocoawithlove.com/2008/03/break-into-debugger.html
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n": : : "memory","r0","r3","r4") // NOLINT(hicpp-no-assembler)
+#else
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT(hicpp-no-assembler)
+#endif
+#elif DOCTEST_MSVC
+#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()
+#elif defined(__MINGW32__)
+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls")
+extern "C" __declspec(dllimport) void __stdcall DebugBreak();
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak()
+#else // linux
+#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast<void>(0))
+#endif // linux
+#endif // DOCTEST_BREAK_INTO_DEBUGGER
+
+// this is kept here for backwards compatibility since the config option was changed
+#ifdef DOCTEST_CONFIG_USE_IOSFWD
+#ifndef DOCTEST_CONFIG_USE_STD_HEADERS
+#define DOCTEST_CONFIG_USE_STD_HEADERS
+#endif
+#endif // DOCTEST_CONFIG_USE_IOSFWD
+
+// for clang - always include ciso646 (which drags some std stuff) because
+// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in
+// which case we don't want to forward declare stuff from std - for reference:
+// https://github.com/doctest/doctest/issues/126
+// https://github.com/doctest/doctest/issues/356
+#if DOCTEST_CLANG
+#include <ciso646>
+#ifdef _LIBCPP_VERSION
+#ifndef DOCTEST_CONFIG_USE_STD_HEADERS
+#define DOCTEST_CONFIG_USE_STD_HEADERS
+#endif
+#endif // _LIBCPP_VERSION
+#endif // clang
+
+#ifdef DOCTEST_CONFIG_USE_STD_HEADERS
+#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
+#include <cstddef>
+#include <ostream>
+#include <istream>
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
+#else // DOCTEST_CONFIG_USE_STD_HEADERS
+
+// Forward declaring 'X' in namespace std is not permitted by the C++ Standard.
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643)
+
+namespace std { // NOLINT(cert-dcl58-cpp)
+typedef decltype(nullptr) nullptr_t; // NOLINT(modernize-use-using)
+typedef decltype(sizeof(void*)) size_t; // NOLINT(modernize-use-using)
+template <class charT>
+struct char_traits;
+template <>
+struct char_traits<char>;
+template <class charT, class traits>
+class basic_ostream; // NOLINT(fuchsia-virtual-inheritance)
+typedef basic_ostream<char, char_traits<char>> ostream; // NOLINT(modernize-use-using)
+template<class traits>
+// NOLINTNEXTLINE
+basic_ostream<char, traits>& operator<<(basic_ostream<char, traits>&, const char*);
+template <class charT, class traits>
+class basic_istream;
+typedef basic_istream<char, char_traits<char>> istream; // NOLINT(modernize-use-using)
+template <class... Types>
+class tuple;
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183
+template <class Ty>
+class allocator;
+template <class Elem, class Traits, class Alloc>
+class basic_string;
+using string = basic_string<char, char_traits<char>, allocator<char>>;
+#endif // VS 2019
+} // namespace std
+
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_CONFIG_USE_STD_HEADERS
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#include <type_traits>
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+namespace doctest {
+
+using std::size_t;
+
+DOCTEST_INTERFACE extern bool is_running_in_test;
+
+#ifndef DOCTEST_CONFIG_STRING_SIZE_TYPE
+#define DOCTEST_CONFIG_STRING_SIZE_TYPE unsigned
+#endif
+
+// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length
+// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for:
+// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128)
+// - if small - capacity left before going on the heap - using the lowest 5 bits
+// - if small - 2 bits are left unused - the second and third highest ones
+// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator)
+// and the "is small" bit remains "0" ("as well as the capacity left") so its OK
+// Idea taken from this lecture about the string implementation of facebook/folly - fbstring
+// https://www.youtube.com/watch?v=kPR8h4-qZdk
+// TODO:
+// - optimizations - like not deleting memory unnecessarily in operator= and etc.
+// - resize/reserve/clear
+// - replace
+// - back/front
+// - iterator stuff
+// - find & friends
+// - push_back/pop_back
+// - assign/insert/erase
+// - relational operators as free functions - taking const char* as one of the params
+class DOCTEST_INTERFACE String
+{
+public:
+ using size_type = DOCTEST_CONFIG_STRING_SIZE_TYPE;
+
+private:
+ static DOCTEST_CONSTEXPR size_type len = 24; //!OCLINT avoid private static members
+ static DOCTEST_CONSTEXPR size_type last = len - 1; //!OCLINT avoid private static members
+
+ struct view // len should be more than sizeof(view) - because of the final byte for flags
+ {
+ char* ptr;
+ size_type size;
+ size_type capacity;
+ };
+
+ union
+ {
+ char buf[len]; // NOLINT(*-avoid-c-arrays)
+ view data;
+ };
+
+ char* allocate(size_type sz);
+
+ bool isOnStack() const noexcept { return (buf[last] & 128) == 0; }
+ void setOnHeap() noexcept;
+ void setLast(size_type in = last) noexcept;
+ void setSize(size_type sz) noexcept;
+
+ void copy(const String& other);
+
+public:
+ static DOCTEST_CONSTEXPR size_type npos = static_cast<size_type>(-1);
+
+ String() noexcept;
+ ~String();
+
+ // cppcheck-suppress noExplicitConstructor
+ String(const char* in);
+ String(const char* in, size_type in_size);
+
+ String(std::istream& in, size_type in_size);
+
+ String(const String& other);
+ String& operator=(const String& other);
+
+ String& operator+=(const String& other);
+
+ String(String&& other) noexcept;
+ String& operator=(String&& other) noexcept;
+
+ char operator[](size_type i) const;
+ char& operator[](size_type i);
+
+ // the only functions I'm willing to leave in the interface - available for inlining
+ const char* c_str() const { return const_cast<String*>(this)->c_str(); } // NOLINT
+ char* c_str() {
+ if (isOnStack()) {
+ return reinterpret_cast<char*>(buf);
+ }
+ return data.ptr;
+ }
+
+ size_type size() const;
+ size_type capacity() const;
+
+ String substr(size_type pos, size_type cnt = npos) &&;
+ String substr(size_type pos, size_type cnt = npos) const &;
+
+ size_type find(char ch, size_type pos = 0) const;
+ size_type rfind(char ch, size_type pos = npos) const;
+
+ int compare(const char* other, bool no_case = false) const;
+ int compare(const String& other, bool no_case = false) const;
+
+friend DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in);
+};
+
+DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs);
+
+DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs);
+
+class DOCTEST_INTERFACE Contains {
+public:
+ explicit Contains(const String& string);
+
+ bool checkWith(const String& other) const;
+
+ String string;
+};
+
+DOCTEST_INTERFACE String toString(const Contains& in);
+
+DOCTEST_INTERFACE bool operator==(const String& lhs, const Contains& rhs);
+DOCTEST_INTERFACE bool operator==(const Contains& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator!=(const String& lhs, const Contains& rhs);
+DOCTEST_INTERFACE bool operator!=(const Contains& lhs, const String& rhs);
+
+namespace Color {
+ enum Enum
+ {
+ None = 0,
+ White,
+ Red,
+ Green,
+ Blue,
+ Cyan,
+ Yellow,
+ Grey,
+
+ Bright = 0x10,
+
+ BrightRed = Bright | Red,
+ BrightGreen = Bright | Green,
+ LightGrey = Bright | Grey,
+ BrightWhite = Bright | White
+ };
+
+ DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code);
+} // namespace Color
+
+namespace assertType {
+ enum Enum
+ {
+ // macro traits
+
+ is_warn = 1,
+ is_check = 2 * is_warn,
+ is_require = 2 * is_check,
+
+ is_normal = 2 * is_require,
+ is_throws = 2 * is_normal,
+ is_throws_as = 2 * is_throws,
+ is_throws_with = 2 * is_throws_as,
+ is_nothrow = 2 * is_throws_with,
+
+ is_false = 2 * is_nothrow,
+ is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types
+
+ is_eq = 2 * is_unary,
+ is_ne = 2 * is_eq,
+
+ is_lt = 2 * is_ne,
+ is_gt = 2 * is_lt,
+
+ is_ge = 2 * is_gt,
+ is_le = 2 * is_ge,
+
+ // macro types
+
+ DT_WARN = is_normal | is_warn,
+ DT_CHECK = is_normal | is_check,
+ DT_REQUIRE = is_normal | is_require,
+
+ DT_WARN_FALSE = is_normal | is_false | is_warn,
+ DT_CHECK_FALSE = is_normal | is_false | is_check,
+ DT_REQUIRE_FALSE = is_normal | is_false | is_require,
+
+ DT_WARN_THROWS = is_throws | is_warn,
+ DT_CHECK_THROWS = is_throws | is_check,
+ DT_REQUIRE_THROWS = is_throws | is_require,
+
+ DT_WARN_THROWS_AS = is_throws_as | is_warn,
+ DT_CHECK_THROWS_AS = is_throws_as | is_check,
+ DT_REQUIRE_THROWS_AS = is_throws_as | is_require,
+
+ DT_WARN_THROWS_WITH = is_throws_with | is_warn,
+ DT_CHECK_THROWS_WITH = is_throws_with | is_check,
+ DT_REQUIRE_THROWS_WITH = is_throws_with | is_require,
+
+ DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn,
+ DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check,
+ DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require,
+
+ DT_WARN_NOTHROW = is_nothrow | is_warn,
+ DT_CHECK_NOTHROW = is_nothrow | is_check,
+ DT_REQUIRE_NOTHROW = is_nothrow | is_require,
+
+ DT_WARN_EQ = is_normal | is_eq | is_warn,
+ DT_CHECK_EQ = is_normal | is_eq | is_check,
+ DT_REQUIRE_EQ = is_normal | is_eq | is_require,
+
+ DT_WARN_NE = is_normal | is_ne | is_warn,
+ DT_CHECK_NE = is_normal | is_ne | is_check,
+ DT_REQUIRE_NE = is_normal | is_ne | is_require,
+
+ DT_WARN_GT = is_normal | is_gt | is_warn,
+ DT_CHECK_GT = is_normal | is_gt | is_check,
+ DT_REQUIRE_GT = is_normal | is_gt | is_require,
+
+ DT_WARN_LT = is_normal | is_lt | is_warn,
+ DT_CHECK_LT = is_normal | is_lt | is_check,
+ DT_REQUIRE_LT = is_normal | is_lt | is_require,
+
+ DT_WARN_GE = is_normal | is_ge | is_warn,
+ DT_CHECK_GE = is_normal | is_ge | is_check,
+ DT_REQUIRE_GE = is_normal | is_ge | is_require,
+
+ DT_WARN_LE = is_normal | is_le | is_warn,
+ DT_CHECK_LE = is_normal | is_le | is_check,
+ DT_REQUIRE_LE = is_normal | is_le | is_require,
+
+ DT_WARN_UNARY = is_normal | is_unary | is_warn,
+ DT_CHECK_UNARY = is_normal | is_unary | is_check,
+ DT_REQUIRE_UNARY = is_normal | is_unary | is_require,
+
+ DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn,
+ DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check,
+ DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require,
+ };
+} // namespace assertType
+
+DOCTEST_INTERFACE const char* assertString(assertType::Enum at);
+DOCTEST_INTERFACE const char* failureString(assertType::Enum at);
+DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file);
+
+struct DOCTEST_INTERFACE TestCaseData
+{
+ String m_file; // the file in which the test was registered (using String - see #350)
+ unsigned m_line; // the line where the test was registered
+ const char* m_name; // name of the test case
+ const char* m_test_suite; // the test suite in which the test was added
+ const char* m_description;
+ bool m_skip;
+ bool m_no_breaks;
+ bool m_no_output;
+ bool m_may_fail;
+ bool m_should_fail;
+ int m_expected_failures;
+ double m_timeout;
+};
+
+struct DOCTEST_INTERFACE AssertData
+{
+ // common - for all asserts
+ const TestCaseData* m_test_case;
+ assertType::Enum m_at;
+ const char* m_file;
+ int m_line;
+ const char* m_expr;
+ bool m_failed;
+
+ // exception-related - for all asserts
+ bool m_threw;
+ String m_exception;
+
+ // for normal asserts
+ String m_decomp;
+
+ // for specific exception-related asserts
+ bool m_threw_as;
+ const char* m_exception_type;
+
+ class DOCTEST_INTERFACE StringContains {
+ private:
+ Contains content;
+ bool isContains;
+
+ public:
+ StringContains(const String& str) : content(str), isContains(false) { }
+ StringContains(Contains cntn) : content(static_cast<Contains&&>(cntn)), isContains(true) { }
+
+ bool check(const String& str) { return isContains ? (content == str) : (content.string == str); }
+
+ operator const String&() const { return content.string; }
+
+ const char* c_str() const { return content.string.c_str(); }
+ } m_exception_string;
+
+ AssertData(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const StringContains& exception_string);
+};
+
+struct DOCTEST_INTERFACE MessageData
+{
+ String m_string;
+ const char* m_file;
+ int m_line;
+ assertType::Enum m_severity;
+};
+
+struct DOCTEST_INTERFACE SubcaseSignature
+{
+ String m_name;
+ const char* m_file;
+ int m_line;
+
+ bool operator==(const SubcaseSignature& other) const;
+ bool operator<(const SubcaseSignature& other) const;
+};
+
+struct DOCTEST_INTERFACE IContextScope
+{
+ DOCTEST_DECLARE_INTERFACE(IContextScope)
+ virtual void stringify(std::ostream*) const = 0;
+};
+
+namespace detail {
+ struct DOCTEST_INTERFACE TestCase;
+} // namespace detail
+
+struct ContextOptions //!OCLINT too many fields
+{
+ std::ostream* cout = nullptr; // stdout stream
+ String binary_name; // the test binary name
+
+ const detail::TestCase* currentTest = nullptr;
+
+ // == parameters from the command line
+ String out; // output filename
+ String order_by; // how tests should be ordered
+ unsigned rand_seed; // the seed for rand ordering
+
+ unsigned first; // the first (matching) test to be executed
+ unsigned last; // the last (matching) test to be executed
+
+ int abort_after; // stop tests after this many failed assertions
+ int subcase_filter_levels; // apply the subcase filters for the first N levels
+
+ bool success; // include successful assertions in output
+ bool case_sensitive; // if filtering should be case sensitive
+ bool exit; // if the program should be exited after the tests are ran/whatever
+ bool duration; // print the time duration of each test case
+ bool minimal; // minimal console output (only test failures)
+ bool quiet; // no console output
+ bool no_throw; // to skip exceptions-related assertion macros
+ bool no_exitcode; // if the framework should return 0 as the exitcode
+ bool no_run; // to not run the tests at all (can be done with an "*" exclude)
+ bool no_intro; // to not print the intro of the framework
+ bool no_version; // to not print the version of the framework
+ bool no_colors; // if output to the console should be colorized
+ bool force_colors; // forces the use of colors even when a tty cannot be detected
+ bool no_breaks; // to not break into the debugger
+ bool no_skip; // don't skip test cases which are marked to be skipped
+ bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x):
+ bool no_path_in_filenames; // if the path to files should be removed from the output
+ bool no_line_numbers; // if source code line numbers should be omitted from the output
+ bool no_debug_output; // no output in the debug console when a debugger is attached
+ bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!!
+ bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!!
+
+ bool help; // to print the help
+ bool version; // to print the version
+ bool count; // if only the count of matching tests is to be retrieved
+ bool list_test_cases; // to list all tests matching the filters
+ bool list_test_suites; // to list all suites matching the filters
+ bool list_reporters; // lists all registered reporters
+};
+
+namespace detail {
+ namespace types {
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+ using namespace std;
+#else
+ template <bool COND, typename T = void>
+ struct enable_if { };
+
+ template <typename T>
+ struct enable_if<true, T> { using type = T; };
+
+ struct true_type { static DOCTEST_CONSTEXPR bool value = true; };
+ struct false_type { static DOCTEST_CONSTEXPR bool value = false; };
+
+ template <typename T> struct remove_reference { using type = T; };
+ template <typename T> struct remove_reference<T&> { using type = T; };
+ template <typename T> struct remove_reference<T&&> { using type = T; };
+
+ template <typename T> struct is_rvalue_reference : false_type { };
+ template <typename T> struct is_rvalue_reference<T&&> : true_type { };
+
+ template<typename T> struct remove_const { using type = T; };
+ template <typename T> struct remove_const<const T> { using type = T; };
+
+ // Compiler intrinsics
+ template <typename T> struct is_enum { static DOCTEST_CONSTEXPR bool value = __is_enum(T); };
+ template <typename T> struct underlying_type { using type = __underlying_type(T); };
+
+ template <typename T> struct is_pointer : false_type { };
+ template <typename T> struct is_pointer<T*> : true_type { };
+
+ template <typename T> struct is_array : false_type { };
+ // NOLINTNEXTLINE(*-avoid-c-arrays)
+ template <typename T, size_t SIZE> struct is_array<T[SIZE]> : true_type { };
+#endif
+ }
+
+ // <utility>
+ template <typename T>
+ T&& declval();
+
+ template <class T>
+ DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type& t) DOCTEST_NOEXCEPT {
+ return static_cast<T&&>(t);
+ }
+
+ template <class T>
+ DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type&& t) DOCTEST_NOEXCEPT {
+ return static_cast<T&&>(t);
+ }
+
+ template <typename T>
+ struct deferred_false : types::false_type { };
+
+// MSVS 2015 :(
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+ template <typename T, typename = void>
+ struct has_global_insertion_operator : types::false_type { };
+
+ template <typename T>
+ struct has_global_insertion_operator<T, decltype(::operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { };
+
+ template <typename T, typename = void>
+ struct has_insertion_operator { static DOCTEST_CONSTEXPR bool value = has_global_insertion_operator<T>::value; };
+
+ template <typename T, bool global>
+ struct insert_hack;
+
+ template <typename T>
+ struct insert_hack<T, true> {
+ static void insert(std::ostream& os, const T& t) { ::operator<<(os, t); }
+ };
+
+ template <typename T>
+ struct insert_hack<T, false> {
+ static void insert(std::ostream& os, const T& t) { operator<<(os, t); }
+ };
+
+ template <typename T>
+ using insert_hack_t = insert_hack<T, has_global_insertion_operator<T>::value>;
+#else
+ template <typename T, typename = void>
+ struct has_insertion_operator : types::false_type { };
+#endif
+
+template <typename T>
+struct has_insertion_operator<T, decltype(operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { };
+
+ DOCTEST_INTERFACE std::ostream* tlssPush();
+ DOCTEST_INTERFACE String tlssPop();
+
+ template <bool C>
+ struct StringMakerBase {
+ template <typename T>
+ static String convert(const DOCTEST_REF_WRAP(T)) {
+#ifdef DOCTEST_CONFIG_REQUIRE_STRINGIFICATION_FOR_ALL_USED_TYPES
+ static_assert(deferred_false<T>::value, "No stringification detected for type T. See string conversion manual");
+#endif
+ return "{?}";
+ }
+ };
+
+ template <typename T>
+ struct filldata;
+
+ template <typename T>
+ void filloss(std::ostream* stream, const T& in) {
+ filldata<T>::fill(stream, in);
+ }
+
+ template <typename T, size_t N>
+ void filloss(std::ostream* stream, const T (&in)[N]) { // NOLINT(*-avoid-c-arrays)
+ // T[N], T(&)[N], T(&&)[N] have same behaviour.
+ // Hence remove reference.
+ filloss<typename types::remove_reference<decltype(in)>::type>(stream, in);
+ }
+
+ template <typename T>
+ String toStream(const T& in) {
+ std::ostream* stream = tlssPush();
+ filloss(stream, in);
+ return tlssPop();
+ }
+
+ template <>
+ struct StringMakerBase<true> {
+ template <typename T>
+ static String convert(const DOCTEST_REF_WRAP(T) in) {
+ return toStream(in);
+ }
+ };
+} // namespace detail
+
+template <typename T>
+struct StringMaker : public detail::StringMakerBase<
+ detail::has_insertion_operator<T>::value || detail::types::is_pointer<T>::value || detail::types::is_array<T>::value>
+{};
+
+#ifndef DOCTEST_STRINGIFY
+#ifdef DOCTEST_CONFIG_DOUBLE_STRINGIFY
+#define DOCTEST_STRINGIFY(...) toString(toString(__VA_ARGS__))
+#else
+#define DOCTEST_STRINGIFY(...) toString(__VA_ARGS__)
+#endif
+#endif
+
+template <typename T>
+String toString() {
+#if DOCTEST_MSVC >= 0 && DOCTEST_CLANG == 0 && DOCTEST_GCC == 0
+ String ret = __FUNCSIG__; // class doctest::String __cdecl doctest::toString<TYPE>(void)
+ String::size_type beginPos = ret.find('<');
+ return ret.substr(beginPos + 1, ret.size() - beginPos - static_cast<String::size_type>(sizeof(">(void)")));
+#else
+ String ret = __PRETTY_FUNCTION__; // doctest::String toString() [with T = TYPE]
+ String::size_type begin = ret.find('=') + 2;
+ return ret.substr(begin, ret.size() - begin - 1);
+#endif
+}
+
+template <typename T, typename detail::types::enable_if<!detail::types::is_enum<T>::value, bool>::type = true>
+String toString(const DOCTEST_REF_WRAP(T) value) {
+ return StringMaker<T>::convert(value);
+}
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+DOCTEST_INTERFACE String toString(const char* in);
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183
+DOCTEST_INTERFACE String toString(const std::string& in);
+#endif // VS 2019
+
+DOCTEST_INTERFACE String toString(String in);
+
+DOCTEST_INTERFACE String toString(std::nullptr_t);
+
+DOCTEST_INTERFACE String toString(bool in);
+
+DOCTEST_INTERFACE String toString(float in);
+DOCTEST_INTERFACE String toString(double in);
+DOCTEST_INTERFACE String toString(double long in);
+
+DOCTEST_INTERFACE String toString(char in);
+DOCTEST_INTERFACE String toString(char signed in);
+DOCTEST_INTERFACE String toString(char unsigned in);
+DOCTEST_INTERFACE String toString(short in);
+DOCTEST_INTERFACE String toString(short unsigned in);
+DOCTEST_INTERFACE String toString(signed in);
+DOCTEST_INTERFACE String toString(unsigned in);
+DOCTEST_INTERFACE String toString(long in);
+DOCTEST_INTERFACE String toString(long unsigned in);
+DOCTEST_INTERFACE String toString(long long in);
+DOCTEST_INTERFACE String toString(long long unsigned in);
+
+template <typename T, typename detail::types::enable_if<detail::types::is_enum<T>::value, bool>::type = true>
+String toString(const DOCTEST_REF_WRAP(T) value) {
+ using UT = typename detail::types::underlying_type<T>::type;
+ return (DOCTEST_STRINGIFY(static_cast<UT>(value)));
+}
+
+namespace detail {
+ template <typename T>
+ struct filldata
+ {
+ static void fill(std::ostream* stream, const T& in) {
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+ insert_hack_t<T>::insert(*stream, in);
+#else
+ operator<<(*stream, in);
+#endif
+ }
+ };
+
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866)
+// NOLINTBEGIN(*-avoid-c-arrays)
+ template <typename T, size_t N>
+ struct filldata<T[N]> {
+ static void fill(std::ostream* stream, const T(&in)[N]) {
+ *stream << "[";
+ for (size_t i = 0; i < N; i++) {
+ if (i != 0) { *stream << ", "; }
+ *stream << (DOCTEST_STRINGIFY(in[i]));
+ }
+ *stream << "]";
+ }
+ };
+// NOLINTEND(*-avoid-c-arrays)
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+ // Specialized since we don't want the terminating null byte!
+// NOLINTBEGIN(*-avoid-c-arrays)
+ template <size_t N>
+ struct filldata<const char[N]> {
+ static void fill(std::ostream* stream, const char (&in)[N]) {
+ *stream << String(in, in[N - 1] ? N : N - 1);
+ } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
+ };
+// NOLINTEND(*-avoid-c-arrays)
+
+ template <>
+ struct filldata<const void*> {
+ static void fill(std::ostream* stream, const void* in);
+ };
+
+ template <typename T>
+ struct filldata<T*> {
+ static void fill(std::ostream* stream, const T* in) {
+ filldata<const void*>::fill(stream, in);
+ }
+ };
+}
+
+struct DOCTEST_INTERFACE Approx
+{
+ Approx(double value);
+
+ Approx operator()(double value) const;
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+ template <typename T>
+ explicit Approx(const T& value,
+ typename detail::types::enable_if<std::is_constructible<double, T>::value>::type* =
+ static_cast<T*>(nullptr)) {
+ *this = static_cast<double>(value);
+ }
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+ Approx& epsilon(double newEpsilon);
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+ template <typename T>
+ typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon(
+ const T& newEpsilon) {
+ m_epsilon = static_cast<double>(newEpsilon);
+ return *this;
+ }
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+ Approx& scale(double newScale);
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+ template <typename T>
+ typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale(
+ const T& newScale) {
+ m_scale = static_cast<double>(newScale);
+ return *this;
+ }
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+ // clang-format off
+ DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs);
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#define DOCTEST_APPROX_PREFIX \
+ template <typename T> friend typename std::enable_if<std::is_constructible<double, T>::value, bool>::type
+
+ DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(static_cast<double>(lhs), rhs); }
+ DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); }
+ DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); }
+ DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); }
+ DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) && lhs != rhs; }
+#undef DOCTEST_APPROX_PREFIX
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+ // clang-format on
+
+ double m_epsilon;
+ double m_scale;
+ double m_value;
+};
+
+DOCTEST_INTERFACE String toString(const Approx& in);
+
+DOCTEST_INTERFACE const ContextOptions* getContextOptions();
+
+template <typename F>
+struct DOCTEST_INTERFACE_DECL IsNaN
+{
+ F value; bool flipped;
+ IsNaN(F f, bool flip = false) : value(f), flipped(flip) { }
+ IsNaN<F> operator!() const { return { value, !flipped }; }
+ operator bool() const;
+};
+#ifndef __MINGW32__
+extern template struct DOCTEST_INTERFACE_DECL IsNaN<float>;
+extern template struct DOCTEST_INTERFACE_DECL IsNaN<double>;
+extern template struct DOCTEST_INTERFACE_DECL IsNaN<long double>;
+#endif
+DOCTEST_INTERFACE String toString(IsNaN<float> in);
+DOCTEST_INTERFACE String toString(IsNaN<double> in);
+DOCTEST_INTERFACE String toString(IsNaN<double long> in);
+
+#ifndef DOCTEST_CONFIG_DISABLE
+
+namespace detail {
+ // clang-format off
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ template<class T> struct decay_array { using type = T; };
+ template<class T, unsigned N> struct decay_array<T[N]> { using type = T*; };
+ template<class T> struct decay_array<T[]> { using type = T*; };
+
+ template<class T> struct not_char_pointer { static DOCTEST_CONSTEXPR value = 1; };
+ template<> struct not_char_pointer<char*> { static DOCTEST_CONSTEXPR value = 0; };
+ template<> struct not_char_pointer<const char*> { static DOCTEST_CONSTEXPR value = 0; };
+
+ template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {};
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ // clang-format on
+
+ struct DOCTEST_INTERFACE TestFailureException
+ {
+ };
+
+ DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at);
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ DOCTEST_NORETURN
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ DOCTEST_INTERFACE void throwException();
+
+ struct DOCTEST_INTERFACE Subcase
+ {
+ SubcaseSignature m_signature;
+ bool m_entered = false;
+
+ Subcase(const String& name, const char* file, int line);
+ Subcase(const Subcase&) = delete;
+ Subcase(Subcase&&) = delete;
+ Subcase& operator=(const Subcase&) = delete;
+ Subcase& operator=(Subcase&&) = delete;
+ ~Subcase();
+
+ operator bool() const;
+
+ private:
+ bool checkFilters();
+ };
+
+ template <typename L, typename R>
+ String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op,
+ const DOCTEST_REF_WRAP(R) rhs) {
+ return (DOCTEST_STRINGIFY(lhs)) + op + (DOCTEST_STRINGIFY(rhs));
+ }
+
+#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0)
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison")
+#endif
+
+// This will check if there is any way it could find a operator like member or friend and uses it.
+// If not it doesn't find the operator or if the operator at global scope is defined after
+// this template, the template won't be instantiated due to SFINAE. Once the template is not
+// instantiated it can look for global operator using normal conversions.
+#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval<L>() op doctest::detail::declval<R>()),ret{})
+
+#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \
+ template <typename R> \
+ DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \
+ bool res = op_macro(doctest::detail::forward<const L>(lhs), doctest::detail::forward<R>(rhs)); \
+ if(m_at & assertType::is_false) \
+ res = !res; \
+ if(!res || doctest::getContextOptions()->success) \
+ return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \
+ return Result(res); \
+ }
+
+ // more checks could be added - like in Catch:
+ // https://github.com/catchorg/Catch2/pull/1480/files
+ // https://github.com/catchorg/Catch2/pull/1481/files
+#define DOCTEST_FORBIT_EXPRESSION(rt, op) \
+ template <typename R> \
+ rt& operator op(const R&) { \
+ static_assert(deferred_false<R>::value, \
+ "Expression Too Complex Please Rewrite As Binary Comparison!"); \
+ return *this; \
+ }
+
+ struct DOCTEST_INTERFACE Result // NOLINT(*-member-init)
+ {
+ bool m_passed;
+ String m_decomp;
+
+ Result() = default; // TODO: Why do we need this? (To remove NOLINT)
+ Result(bool passed, const String& decomposition = String());
+
+ // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence
+ DOCTEST_FORBIT_EXPRESSION(Result, &)
+ DOCTEST_FORBIT_EXPRESSION(Result, ^)
+ DOCTEST_FORBIT_EXPRESSION(Result, |)
+ DOCTEST_FORBIT_EXPRESSION(Result, &&)
+ DOCTEST_FORBIT_EXPRESSION(Result, ||)
+ DOCTEST_FORBIT_EXPRESSION(Result, ==)
+ DOCTEST_FORBIT_EXPRESSION(Result, !=)
+ DOCTEST_FORBIT_EXPRESSION(Result, <)
+ DOCTEST_FORBIT_EXPRESSION(Result, >)
+ DOCTEST_FORBIT_EXPRESSION(Result, <=)
+ DOCTEST_FORBIT_EXPRESSION(Result, >=)
+ DOCTEST_FORBIT_EXPRESSION(Result, =)
+ DOCTEST_FORBIT_EXPRESSION(Result, +=)
+ DOCTEST_FORBIT_EXPRESSION(Result, -=)
+ DOCTEST_FORBIT_EXPRESSION(Result, *=)
+ DOCTEST_FORBIT_EXPRESSION(Result, /=)
+ DOCTEST_FORBIT_EXPRESSION(Result, %=)
+ DOCTEST_FORBIT_EXPRESSION(Result, <<=)
+ DOCTEST_FORBIT_EXPRESSION(Result, >>=)
+ DOCTEST_FORBIT_EXPRESSION(Result, &=)
+ DOCTEST_FORBIT_EXPRESSION(Result, ^=)
+ DOCTEST_FORBIT_EXPRESSION(Result, |=)
+ };
+
+#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion")
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare")
+ //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion")
+ //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion")
+ //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal")
+
+ DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion")
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare")
+ //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion")
+ //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion")
+ //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal")
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+ // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389
+ DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch
+ DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch
+ DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch
+ //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation
+
+#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+ // clang-format off
+#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_COMPARISON_RETURN_TYPE bool
+#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_COMPARISON_RETURN_TYPE typename types::enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type
+ inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); }
+ inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); }
+ inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); }
+ inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); }
+ inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); }
+ inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); }
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ // clang-format on
+
+#define DOCTEST_RELATIONAL_OP(name, op) \
+ template <typename L, typename R> \
+ DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \
+ const DOCTEST_REF_WRAP(R) rhs) { \
+ return lhs op rhs; \
+ }
+
+ DOCTEST_RELATIONAL_OP(eq, ==)
+ DOCTEST_RELATIONAL_OP(ne, !=)
+ DOCTEST_RELATIONAL_OP(lt, <)
+ DOCTEST_RELATIONAL_OP(gt, >)
+ DOCTEST_RELATIONAL_OP(le, <=)
+ DOCTEST_RELATIONAL_OP(ge, >=)
+
+#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_CMP_EQ(l, r) l == r
+#define DOCTEST_CMP_NE(l, r) l != r
+#define DOCTEST_CMP_GT(l, r) l > r
+#define DOCTEST_CMP_LT(l, r) l < r
+#define DOCTEST_CMP_GE(l, r) l >= r
+#define DOCTEST_CMP_LE(l, r) l <= r
+#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_CMP_EQ(l, r) eq(l, r)
+#define DOCTEST_CMP_NE(l, r) ne(l, r)
+#define DOCTEST_CMP_GT(l, r) gt(l, r)
+#define DOCTEST_CMP_LT(l, r) lt(l, r)
+#define DOCTEST_CMP_GE(l, r) ge(l, r)
+#define DOCTEST_CMP_LE(l, r) le(l, r)
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+
+ template <typename L>
+ // cppcheck-suppress copyCtorAndEqOperator
+ struct Expression_lhs
+ {
+ L lhs;
+ assertType::Enum m_at;
+
+ explicit Expression_lhs(L&& in, assertType::Enum at)
+ : lhs(static_cast<L&&>(in))
+ , m_at(at) {}
+
+ DOCTEST_NOINLINE operator Result() {
+// this is needed only for MSVC 2015
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool
+ bool res = static_cast<bool>(lhs);
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+ if(m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional
+ res = !res;
+ }
+
+ if(!res || getContextOptions()->success) {
+ return { res, (DOCTEST_STRINGIFY(lhs)) };
+ }
+ return { res };
+ }
+
+ /* This is required for user-defined conversions from Expression_lhs to L */
+ operator L() const { return lhs; }
+
+ // clang-format off
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional
+ // clang-format on
+
+ // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=)
+ // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the
+ // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression...
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>)
+ };
+
+#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0)
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+#endif
+
+ struct DOCTEST_INTERFACE ExpressionDecomposer
+ {
+ assertType::Enum m_at;
+
+ ExpressionDecomposer(assertType::Enum at);
+
+ // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table)
+ // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now...
+ // https://github.com/catchorg/Catch2/issues/870
+ // https://github.com/catchorg/Catch2/issues/565
+ template <typename L>
+ Expression_lhs<L> operator<<(L&& operand) {
+ return Expression_lhs<L>(static_cast<L&&>(operand), m_at);
+ }
+
+ template <typename L,typename types::enable_if<!doctest::detail::types::is_rvalue_reference<L>::value,void >::type* = nullptr>
+ Expression_lhs<const L&> operator<<(const L &operand) {
+ return Expression_lhs<const L&>(operand, m_at);
+ }
+ };
+
+ struct DOCTEST_INTERFACE TestSuite
+ {
+ const char* m_test_suite = nullptr;
+ const char* m_description = nullptr;
+ bool m_skip = false;
+ bool m_no_breaks = false;
+ bool m_no_output = false;
+ bool m_may_fail = false;
+ bool m_should_fail = false;
+ int m_expected_failures = 0;
+ double m_timeout = 0;
+
+ TestSuite& operator*(const char* in);
+
+ template <typename T>
+ TestSuite& operator*(const T& in) {
+ in.fill(*this);
+ return *this;
+ }
+ };
+
+ using funcType = void (*)();
+
+ struct DOCTEST_INTERFACE TestCase : public TestCaseData
+ {
+ funcType m_test; // a function pointer to the test case
+
+ String m_type; // for templated test cases - gets appended to the real name
+ int m_template_id; // an ID used to distinguish between the different versions of a templated test case
+ String m_full_name; // contains the name (only for templated test cases!) + the template type
+
+ TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
+ const String& type = String(), int template_id = -1);
+
+ TestCase(const TestCase& other);
+ TestCase(TestCase&&) = delete;
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
+ TestCase& operator=(const TestCase& other);
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+ TestCase& operator=(TestCase&&) = delete;
+
+ TestCase& operator*(const char* in);
+
+ template <typename T>
+ TestCase& operator*(const T& in) {
+ in.fill(*this);
+ return *this;
+ }
+
+ bool operator<(const TestCase& other) const;
+
+ ~TestCase() = default;
+ };
+
+ // forward declarations of functions used by the macros
+ DOCTEST_INTERFACE int regTest(const TestCase& tc);
+ DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts);
+ DOCTEST_INTERFACE bool isDebuggerActive();
+
+ template<typename T>
+ int instantiationHelper(const T&) { return 0; }
+
+ namespace binaryAssertComparison {
+ enum Enum
+ {
+ eq = 0,
+ ne,
+ gt,
+ lt,
+ ge,
+ le
+ };
+ } // namespace binaryAssertComparison
+
+ // clang-format off
+ template <int, class L, class R> struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } };
+
+#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \
+ template <class L, class R> struct RelationalComparator<n, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } };
+ // clang-format on
+
+ DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq)
+ DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne)
+ DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt)
+ DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt)
+ DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge)
+ DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le)
+
+ struct DOCTEST_INTERFACE ResultBuilder : public AssertData
+ {
+ ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type = "", const String& exception_string = "");
+
+ ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const Contains& exception_string);
+
+ void setResult(const Result& res);
+
+ template <int comparison, typename L, typename R>
+ DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs,
+ const DOCTEST_REF_WRAP(R) rhs) {
+ m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
+ if (m_failed || getContextOptions()->success) {
+ m_decomp = stringifyBinaryExpr(lhs, ", ", rhs);
+ }
+ return !m_failed;
+ }
+
+ template <typename L>
+ DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) {
+ m_failed = !val;
+
+ if (m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional
+ m_failed = !m_failed;
+ }
+
+ if (m_failed || getContextOptions()->success) {
+ m_decomp = (DOCTEST_STRINGIFY(val));
+ }
+
+ return !m_failed;
+ }
+
+ void translateException();
+
+ bool log();
+ void react() const;
+ };
+
+ namespace assertAction {
+ enum Enum
+ {
+ nothing = 0,
+ dbgbreak = 1,
+ shouldthrow = 2
+ };
+ } // namespace assertAction
+
+ DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad);
+
+ DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line,
+ const char* expr, const Result& result);
+
+#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \
+ do { \
+ if(!is_running_in_test) { \
+ if(failed) { \
+ ResultBuilder rb(at, file, line, expr); \
+ rb.m_failed = failed; \
+ rb.m_decomp = decomp; \
+ failed_out_of_a_testing_context(rb); \
+ if(isDebuggerActive() && !getContextOptions()->no_breaks) \
+ DOCTEST_BREAK_INTO_DEBUGGER(); \
+ if(checkIfShouldThrow(at)) \
+ throwException(); \
+ } \
+ return !failed; \
+ } \
+ } while(false)
+
+#define DOCTEST_ASSERT_IN_TESTS(decomp) \
+ ResultBuilder rb(at, file, line, expr); \
+ rb.m_failed = failed; \
+ if(rb.m_failed || getContextOptions()->success) \
+ rb.m_decomp = decomp; \
+ if(rb.log()) \
+ DOCTEST_BREAK_INTO_DEBUGGER(); \
+ if(rb.m_failed && checkIfShouldThrow(at)) \
+ throwException()
+
+ template <int comparison, typename L, typename R>
+ DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line,
+ const char* expr, const DOCTEST_REF_WRAP(L) lhs,
+ const DOCTEST_REF_WRAP(R) rhs) {
+ bool failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
+
+ // ###################################################################################
+ // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+ // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+ // ###################################################################################
+ DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));
+ DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));
+ return !failed;
+ }
+
+ template <typename L>
+ DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line,
+ const char* expr, const DOCTEST_REF_WRAP(L) val) {
+ bool failed = !val;
+
+ if(at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ failed = !failed;
+
+ // ###################################################################################
+ // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+ // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+ // ###################################################################################
+ DOCTEST_ASSERT_OUT_OF_TESTS((DOCTEST_STRINGIFY(val)));
+ DOCTEST_ASSERT_IN_TESTS((DOCTEST_STRINGIFY(val)));
+ return !failed;
+ }
+
+ struct DOCTEST_INTERFACE IExceptionTranslator
+ {
+ DOCTEST_DECLARE_INTERFACE(IExceptionTranslator)
+ virtual bool translate(String&) const = 0;
+ };
+
+ template <typename T>
+ class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class
+ {
+ public:
+ explicit ExceptionTranslator(String (*translateFunction)(T))
+ : m_translateFunction(translateFunction) {}
+
+ bool translate(String& res) const override {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ try {
+ throw; // lgtm [cpp/rethrow-no-exception]
+ // cppcheck-suppress catchExceptionByValue
+ } catch(const T& ex) {
+ res = m_translateFunction(ex); //!OCLINT parameter reassignment
+ return true;
+ } catch(...) {} //!OCLINT - empty catch statement
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ static_cast<void>(res); // to silence -Wunused-parameter
+ return false;
+ }
+
+ private:
+ String (*m_translateFunction)(T);
+ };
+
+ DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et);
+
+ // ContextScope base class used to allow implementing methods of ContextScope
+ // that don't depend on the template parameter in doctest.cpp.
+ struct DOCTEST_INTERFACE ContextScopeBase : public IContextScope {
+ ContextScopeBase(const ContextScopeBase&) = delete;
+
+ ContextScopeBase& operator=(const ContextScopeBase&) = delete;
+ ContextScopeBase& operator=(ContextScopeBase&&) = delete;
+
+ ~ContextScopeBase() override = default;
+
+ protected:
+ ContextScopeBase();
+ ContextScopeBase(ContextScopeBase&& other) noexcept;
+
+ void destroy();
+ bool need_to_destroy{true};
+ };
+
+ template <typename L> class ContextScope : public ContextScopeBase
+ {
+ L lambda_;
+
+ public:
+ explicit ContextScope(const L &lambda) : lambda_(lambda) {}
+ explicit ContextScope(L&& lambda) : lambda_(static_cast<L&&>(lambda)) { }
+
+ ContextScope(const ContextScope&) = delete;
+ ContextScope(ContextScope&&) noexcept = default;
+
+ ContextScope& operator=(const ContextScope&) = delete;
+ ContextScope& operator=(ContextScope&&) = delete;
+
+ void stringify(std::ostream* s) const override { lambda_(s); }
+
+ ~ContextScope() override {
+ if (need_to_destroy) {
+ destroy();
+ }
+ }
+ };
+
+ struct DOCTEST_INTERFACE MessageBuilder : public MessageData
+ {
+ std::ostream* m_stream;
+ bool logged = false;
+
+ MessageBuilder(const char* file, int line, assertType::Enum severity);
+
+ MessageBuilder(const MessageBuilder&) = delete;
+ MessageBuilder(MessageBuilder&&) = delete;
+
+ MessageBuilder& operator=(const MessageBuilder&) = delete;
+ MessageBuilder& operator=(MessageBuilder&&) = delete;
+
+ ~MessageBuilder();
+
+ // the preferred way of chaining parameters for stringification
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866)
+ template <typename T>
+ MessageBuilder& operator,(const T& in) {
+ *m_stream << (DOCTEST_STRINGIFY(in));
+ return *this;
+ }
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+ // kept here just for backwards-compatibility - the comma operator should be preferred now
+ template <typename T>
+ MessageBuilder& operator<<(const T& in) { return this->operator,(in); }
+
+ // the `,` operator has the lowest operator precedence - if `<<` is used by the user then
+ // the `,` operator will be called last which is not what we want and thus the `*` operator
+ // is used first (has higher operator precedence compared to `<<`) so that we guarantee that
+ // an operator of the MessageBuilder class is called first before the rest of the parameters
+ template <typename T>
+ MessageBuilder& operator*(const T& in) { return this->operator,(in); }
+
+ bool log();
+ void react();
+ };
+
+ template <typename L>
+ ContextScope<L> MakeContextScope(const L &lambda) {
+ return ContextScope<L>(lambda);
+ }
+} // namespace detail
+
+#define DOCTEST_DEFINE_DECORATOR(name, type, def) \
+ struct name \
+ { \
+ type data; \
+ name(type in = def) \
+ : data(in) {} \
+ void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \
+ void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \
+ }
+
+DOCTEST_DEFINE_DECORATOR(test_suite, const char*, "");
+DOCTEST_DEFINE_DECORATOR(description, const char*, "");
+DOCTEST_DEFINE_DECORATOR(skip, bool, true);
+DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true);
+DOCTEST_DEFINE_DECORATOR(no_output, bool, true);
+DOCTEST_DEFINE_DECORATOR(timeout, double, 0);
+DOCTEST_DEFINE_DECORATOR(may_fail, bool, true);
+DOCTEST_DEFINE_DECORATOR(should_fail, bool, true);
+DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0);
+
+template <typename T>
+int registerExceptionTranslator(String (*translateFunction)(T)) {
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors")
+ static detail::ExceptionTranslator<T> exceptionTranslator(translateFunction);
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ detail::registerExceptionTranslatorImpl(&exceptionTranslator);
+ return 0;
+}
+
+} // namespace doctest
+
+// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro
+// introduces an anonymous namespace in which getCurrentTestSuite gets overridden
+namespace doctest_detail_test_suite_ns {
+DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite();
+} // namespace doctest_detail_test_suite_ns
+
+namespace doctest {
+#else // DOCTEST_CONFIG_DISABLE
+template <typename T>
+int registerExceptionTranslator(String (*)(T)) {
+ return 0;
+}
+#endif // DOCTEST_CONFIG_DISABLE
+
+namespace detail {
+ using assert_handler = void (*)(const AssertData&);
+ struct ContextState;
+} // namespace detail
+
+class DOCTEST_INTERFACE Context
+{
+ detail::ContextState* p;
+
+ void parseArgs(int argc, const char* const* argv, bool withDefaults = false);
+
+public:
+ explicit Context(int argc = 0, const char* const* argv = nullptr);
+
+ Context(const Context&) = delete;
+ Context(Context&&) = delete;
+
+ Context& operator=(const Context&) = delete;
+ Context& operator=(Context&&) = delete;
+
+ ~Context(); // NOLINT(performance-trivially-destructible)
+
+ void applyCommandLine(int argc, const char* const* argv);
+
+ void addFilter(const char* filter, const char* value);
+ void clearFilters();
+ void setOption(const char* option, bool value);
+ void setOption(const char* option, int value);
+ void setOption(const char* option, const char* value);
+
+ bool shouldExit();
+
+ void setAsDefaultForAssertsOutOfTestCases();
+
+ void setAssertHandler(detail::assert_handler ah);
+
+ void setCout(std::ostream* out);
+
+ int run();
+};
+
+namespace TestCaseFailureReason {
+ enum Enum
+ {
+ None = 0,
+ AssertFailure = 1, // an assertion has failed in the test case
+ Exception = 2, // test case threw an exception
+ Crash = 4, // a crash...
+ TooManyFailedAsserts = 8, // the abort-after option
+ Timeout = 16, // see the timeout decorator
+ ShouldHaveFailedButDidnt = 32, // see the should_fail decorator
+ ShouldHaveFailedAndDid = 64, // see the should_fail decorator
+ DidntFailExactlyNumTimes = 128, // see the expected_failures decorator
+ FailedExactlyNumTimes = 256, // see the expected_failures decorator
+ CouldHaveFailedAndDid = 512 // see the may_fail decorator
+ };
+} // namespace TestCaseFailureReason
+
+struct DOCTEST_INTERFACE CurrentTestCaseStats
+{
+ int numAssertsCurrentTest;
+ int numAssertsFailedCurrentTest;
+ double seconds;
+ int failure_flags; // use TestCaseFailureReason::Enum
+ bool testCaseSuccess;
+};
+
+struct DOCTEST_INTERFACE TestCaseException
+{
+ String error_string;
+ bool is_crash;
+};
+
+struct DOCTEST_INTERFACE TestRunStats
+{
+ unsigned numTestCases;
+ unsigned numTestCasesPassingFilters;
+ unsigned numTestSuitesPassingFilters;
+ unsigned numTestCasesFailed;
+ int numAsserts;
+ int numAssertsFailed;
+};
+
+struct QueryData
+{
+ const TestRunStats* run_stats = nullptr;
+ const TestCaseData** data = nullptr;
+ unsigned num_data = 0;
+};
+
+struct DOCTEST_INTERFACE IReporter
+{
+ // The constructor has to accept "const ContextOptions&" as a single argument
+ // which has most of the options for the run + a pointer to the stdout stream
+ // Reporter(const ContextOptions& in)
+
+ // called when a query should be reported (listing test cases, printing the version, etc.)
+ virtual void report_query(const QueryData&) = 0;
+
+ // called when the whole test run starts
+ virtual void test_run_start() = 0;
+ // called when the whole test run ends (caching a pointer to the input doesn't make sense here)
+ virtual void test_run_end(const TestRunStats&) = 0;
+
+ // called when a test case is started (safe to cache a pointer to the input)
+ virtual void test_case_start(const TestCaseData&) = 0;
+ // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input)
+ virtual void test_case_reenter(const TestCaseData&) = 0;
+ // called when a test case has ended
+ virtual void test_case_end(const CurrentTestCaseStats&) = 0;
+
+ // called when an exception is thrown from the test case (or it crashes)
+ virtual void test_case_exception(const TestCaseException&) = 0;
+
+ // called whenever a subcase is entered (don't cache pointers to the input)
+ virtual void subcase_start(const SubcaseSignature&) = 0;
+ // called whenever a subcase is exited (don't cache pointers to the input)
+ virtual void subcase_end() = 0;
+
+ // called for each assert (don't cache pointers to the input)
+ virtual void log_assert(const AssertData&) = 0;
+ // called for each message (don't cache pointers to the input)
+ virtual void log_message(const MessageData&) = 0;
+
+ // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator
+ // or isn't in the execution range (between first and last) (safe to cache a pointer to the input)
+ virtual void test_case_skipped(const TestCaseData&) = 0;
+
+ DOCTEST_DECLARE_INTERFACE(IReporter)
+
+ // can obtain all currently active contexts and stringify them if one wishes to do so
+ static int get_num_active_contexts();
+ static const IContextScope* const* get_active_contexts();
+
+ // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown
+ static int get_num_stringified_contexts();
+ static const String* get_stringified_contexts();
+};
+
+namespace detail {
+ using reporterCreatorFunc = IReporter* (*)(const ContextOptions&);
+
+ DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter);
+
+ template <typename Reporter>
+ IReporter* reporterCreator(const ContextOptions& o) {
+ return new Reporter(o);
+ }
+} // namespace detail
+
+template <typename Reporter>
+int registerReporter(const char* name, int priority, bool isReporter) {
+ detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>, isReporter);
+ return 0;
+}
+} // namespace doctest
+
+#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES
+#define DOCTEST_FUNC_EMPTY [] { return false; }()
+#else
+#define DOCTEST_FUNC_EMPTY (void)0
+#endif
+
+// if registering is not disabled
+#ifndef DOCTEST_CONFIG_DISABLE
+
+#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES
+#define DOCTEST_FUNC_SCOPE_BEGIN [&]
+#define DOCTEST_FUNC_SCOPE_END ()
+#define DOCTEST_FUNC_SCOPE_RET(v) return v
+#else
+#define DOCTEST_FUNC_SCOPE_BEGIN do
+#define DOCTEST_FUNC_SCOPE_END while(false)
+#define DOCTEST_FUNC_SCOPE_RET(v) (void)0
+#endif
+
+// common code in asserts - for convenience
+#define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \
+ if(b.log()) DOCTEST_BREAK_INTO_DEBUGGER(); \
+ b.react(); \
+ DOCTEST_FUNC_SCOPE_RET(!b.m_failed)
+
+#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#define DOCTEST_WRAP_IN_TRY(x) x;
+#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#define DOCTEST_WRAP_IN_TRY(x) \
+ try { \
+ x; \
+ } catch(...) { DOCTEST_RB.translateException(); }
+#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+
+#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
+#define DOCTEST_CAST_TO_VOID(...) \
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \
+ static_cast<void>(__VA_ARGS__); \
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
+#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__;
+#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
+
+// registers the test by initializing a dummy var with a function
+#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \
+ global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT */ \
+ doctest::detail::regTest( \
+ doctest::detail::TestCase( \
+ f, __FILE__, __LINE__, \
+ doctest_detail_test_suite_ns::getCurrentTestSuite()) * \
+ decorators))
+
+#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \
+ namespace { /* NOLINT */ \
+ struct der : public base \
+ { \
+ void f(); \
+ }; \
+ static inline DOCTEST_NOINLINE void func() { \
+ der v; \
+ v.f(); \
+ } \
+ DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \
+ } \
+ inline DOCTEST_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers)
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \
+ static void f(); \
+ DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \
+ static void f()
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \
+ static doctest::detail::funcType proxy() { return f; } \
+ DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \
+ static void f()
+
+// for registering tests
+#define DOCTEST_TEST_CASE(decorators) \
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators)
+
+// for registering tests in classes - requires C++17 for inline variables!
+#if DOCTEST_CPLUSPLUS >= 201703L
+#define DOCTEST_TEST_CASE_CLASS(decorators) \
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \
+ DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \
+ decorators)
+#else // DOCTEST_TEST_CASE_CLASS
+#define DOCTEST_TEST_CASE_CLASS(...) \
+ TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER
+#endif // DOCTEST_TEST_CASE_CLASS
+
+// for registering tests with a fixture
+#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \
+ DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \
+ DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators)
+
+// for converting types to strings without the <typeinfo> header and demangling
+#define DOCTEST_TYPE_TO_STRING_AS(str, ...) \
+ namespace doctest { \
+ template <> \
+ inline String toString<__VA_ARGS__>() { \
+ return str; \
+ } \
+ } \
+ static_assert(true, "")
+
+#define DOCTEST_TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING_AS(#__VA_ARGS__, __VA_ARGS__)
+
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \
+ template <typename T> \
+ static void func(); \
+ namespace { /* NOLINT */ \
+ template <typename Tuple> \
+ struct iter; \
+ template <typename Type, typename... Rest> \
+ struct iter<std::tuple<Type, Rest...>> \
+ { \
+ iter(const char* file, unsigned line, int index) { \
+ doctest::detail::regTest(doctest::detail::TestCase(func<Type>, file, line, \
+ doctest_detail_test_suite_ns::getCurrentTestSuite(), \
+ doctest::toString<Type>(), \
+ int(line) * 1000 + index) \
+ * dec); \
+ iter<std::tuple<Rest...>>(file, line, index + 1); \
+ } \
+ }; \
+ template <> \
+ struct iter<std::tuple<>> \
+ { \
+ iter(const char*, unsigned, int) {} \
+ }; \
+ } \
+ template <typename T> \
+ static void func()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \
+ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \
+ DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_))
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), /* NOLINT(cert-err58-cpp, fuchsia-statically-constructed-objects) */ \
+ doctest::detail::instantiationHelper( \
+ DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0)))
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \
+ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \
+ static_assert(true, "")
+
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \
+ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \
+ static_assert(true, "")
+
+#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \
+ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \
+ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \
+ template <typename T> \
+ static void anon()
+
+#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \
+ DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__)
+
+// for subcases
+#define DOCTEST_SUBCASE(name) \
+ if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \
+ doctest::detail::Subcase(name, __FILE__, __LINE__))
+
+// for grouping tests in test suites by using code blocks
+#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \
+ namespace ns_name { namespace doctest_detail_test_suite_ns { \
+ static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() noexcept { \
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \
+ static doctest::detail::TestSuite data{}; \
+ static bool inited = false; \
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP \
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP \
+ DOCTEST_GCC_SUPPRESS_WARNING_POP \
+ if(!inited) { \
+ data* decorators; \
+ inited = true; \
+ } \
+ return data; \
+ } \
+ } \
+ } \
+ namespace ns_name
+
+#define DOCTEST_TEST_SUITE(decorators) \
+ DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_))
+
+// for starting a testsuite block
+#define DOCTEST_TEST_SUITE_BEGIN(decorators) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \
+ static_assert(true, "")
+
+// for ending a testsuite block
+#define DOCTEST_TEST_SUITE_END \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \
+ using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int
+
+// for registering exception translators
+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \
+ inline doctest::String translatorName(signature); \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::registerExceptionTranslator(translatorName)) \
+ doctest::String translatorName(signature)
+
+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \
+ DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \
+ signature)
+
+// for registering reporters
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::registerReporter<reporter>(name, priority, true)) \
+ static_assert(true, "")
+
+// for registering listeners
+#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::registerReporter<reporter>(name, priority, false)) \
+ static_assert(true, "")
+
+// clang-format off
+// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557
+#define DOCTEST_INFO(...) \
+ DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \
+ DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \
+ __VA_ARGS__)
+// clang-format on
+
+#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \
+ auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \
+ [&](std::ostream* s_name) { \
+ doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \
+ mb_name.m_stream = s_name; \
+ mb_name * __VA_ARGS__; \
+ })
+
+#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x)
+
+#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \
+ mb * __VA_ARGS__; \
+ if(mb.log()) \
+ DOCTEST_BREAK_INTO_DEBUGGER(); \
+ mb.react(); \
+ } DOCTEST_FUNC_SCOPE_END
+
+// clang-format off
+#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)
+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)
+#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)
+// clang-format on
+
+#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__)
+#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__)
+#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__)
+
+#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility.
+
+#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \
+ /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \
+ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \
+ << __VA_ARGS__)) /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \
+ } DOCTEST_FUNC_SCOPE_END // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
+
+#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY( \
+ DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \
+ __VA_ARGS__)) \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } DOCTEST_FUNC_SCOPE_END
+
+#define DOCTEST_UNARY_ASSERT(assert_type, ...) \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } DOCTEST_FUNC_SCOPE_END
+
+#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+// necessary for <ASSERT>_MESSAGE
+#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1
+
+#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \
+ doctest::detail::decomp_assert( \
+ doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \
+ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \
+ << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \
+ doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>( \
+ doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__)
+
+#define DOCTEST_UNARY_ASSERT(assert_type, ...) \
+ doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \
+ #__VA_ARGS__, __VA_ARGS__)
+
+#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__)
+#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__)
+#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__)
+#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__)
+#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__)
+#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__)
+
+// clang-format off
+#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } DOCTEST_FUNC_SCOPE_END
+// clang-format on
+
+#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__)
+#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__)
+#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__)
+#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__)
+#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__)
+#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__)
+#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__)
+#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__)
+#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__)
+#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__)
+#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__)
+#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__)
+#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__)
+#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__)
+#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__)
+#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__)
+#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__)
+
+#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__)
+#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__)
+#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__)
+#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__)
+#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__)
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__)
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ if(!doctest::getContextOptions()->no_throw) { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #expr, #__VA_ARGS__, message); \
+ try { \
+ DOCTEST_CAST_TO_VOID(expr) \
+ } catch(const typename doctest::detail::types::remove_const< \
+ typename doctest::detail::types::remove_reference<__VA_ARGS__>::type>::type&) {\
+ DOCTEST_RB.translateException(); \
+ DOCTEST_RB.m_threw_as = true; \
+ } catch(...) { DOCTEST_RB.translateException(); } \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } else { /* NOLINT(*-else-after-return) */ \
+ DOCTEST_FUNC_SCOPE_RET(false); \
+ } \
+ } DOCTEST_FUNC_SCOPE_END
+
+#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ if(!doctest::getContextOptions()->no_throw) { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, expr_str, "", __VA_ARGS__); \
+ try { \
+ DOCTEST_CAST_TO_VOID(expr) \
+ } catch(...) { DOCTEST_RB.translateException(); } \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } else { /* NOLINT(*-else-after-return) */ \
+ DOCTEST_FUNC_SCOPE_RET(false); \
+ } \
+ } DOCTEST_FUNC_SCOPE_END
+
+#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ try { \
+ DOCTEST_CAST_TO_VOID(__VA_ARGS__) \
+ } catch(...) { DOCTEST_RB.translateException(); } \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } DOCTEST_FUNC_SCOPE_END
+
+// clang-format off
+#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "")
+#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "")
+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "")
+
+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__)
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__)
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__)
+
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__)
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__)
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__)
+
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__)
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__)
+
+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__)
+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__)
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END
+// clang-format on
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+// =================================================================================================
+// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! ==
+// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! ==
+// =================================================================================================
+#else // DOCTEST_CONFIG_DISABLE
+
+#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \
+ namespace /* NOLINT */ { \
+ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
+ struct der : public base \
+ { void f(); }; \
+ } \
+ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
+ inline void der<DOCTEST_UNUSED_TEMPLATE_TYPE>::f()
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \
+ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
+ static inline void f()
+
+// for registering tests
+#define DOCTEST_TEST_CASE(name) \
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)
+
+// for registering tests in classes
+#define DOCTEST_TEST_CASE_CLASS(name) \
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)
+
+// for registering tests with a fixture
+#define DOCTEST_TEST_CASE_FIXTURE(x, name) \
+ DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \
+ DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)
+
+// for converting types to strings without the <typeinfo> header and demangling
+#define DOCTEST_TYPE_TO_STRING_AS(str, ...) static_assert(true, "")
+#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "")
+
+// for typed tests
+#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \
+ template <typename type> \
+ inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \
+ template <typename type> \
+ inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "")
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "")
+
+// for subcases
+#define DOCTEST_SUBCASE(name)
+
+// for a testsuite block
+#define DOCTEST_TEST_SUITE(name) namespace // NOLINT
+
+// for starting a testsuite block
+#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "")
+
+// for ending a testsuite block
+#define DOCTEST_TEST_SUITE_END using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int
+
+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \
+ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
+ static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature)
+
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)
+#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)
+
+#define DOCTEST_INFO(...) (static_cast<void>(0))
+#define DOCTEST_CAPTURE(x) (static_cast<void>(0))
+#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast<void>(0))
+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast<void>(0))
+#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast<void>(0))
+#define DOCTEST_MESSAGE(...) (static_cast<void>(0))
+#define DOCTEST_FAIL_CHECK(...) (static_cast<void>(0))
+#define DOCTEST_FAIL(...) (static_cast<void>(0))
+
+#if defined(DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED) \
+ && defined(DOCTEST_CONFIG_ASSERTS_RETURN_VALUES)
+
+#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }()
+
+#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }()
+#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }()
+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }()
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()
+
+namespace doctest {
+namespace detail {
+#define DOCTEST_RELATIONAL_OP(name, op) \
+ template <typename L, typename R> \
+ bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; }
+
+ DOCTEST_RELATIONAL_OP(eq, ==)
+ DOCTEST_RELATIONAL_OP(ne, !=)
+ DOCTEST_RELATIONAL_OP(lt, <)
+ DOCTEST_RELATIONAL_OP(gt, >)
+ DOCTEST_RELATIONAL_OP(le, <=)
+ DOCTEST_RELATIONAL_OP(ge, >=)
+} // namespace detail
+} // namespace doctest
+
+#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()
+#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()
+#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()
+#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()
+#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()
+#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()
+#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()
+#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()
+#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()
+#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()
+#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()
+#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()
+#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#define DOCTEST_WARN_THROWS_WITH(expr, with, ...) [] { static_assert(false, "Exception translation is not available when doctest is disabled."); return false; }()
+#define DOCTEST_CHECK_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+
+#define DOCTEST_WARN_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_CHECK_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_REQUIRE_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_WARN_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_WARN_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+#define DOCTEST_CHECK_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+#define DOCTEST_REQUIRE_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
+
+#define DOCTEST_WARN(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_FALSE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_FALSE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_FUNC_EMPTY
+
+#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+
+#define DOCTEST_WARN_EQ(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_EQ(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_EQ(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_NE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_NE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_NE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_GT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_GT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_GT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_LT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_LT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_LT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_GE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_GE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_GE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_LE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_LE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_LE(...) DOCTEST_FUNC_EMPTY
+
+#define DOCTEST_WARN_UNARY(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_UNARY(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#define DOCTEST_WARN_THROWS(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_FUNC_EMPTY
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
+
+#endif // DOCTEST_CONFIG_DISABLE
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+#define DOCTEST_EXCEPTION_EMPTY_FUNC DOCTEST_FUNC_EMPTY
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+#define DOCTEST_EXCEPTION_EMPTY_FUNC [] { static_assert(false, "Exceptions are disabled! " \
+ "Use DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS if you want to compile with exceptions disabled."); return false; }()
+
+#undef DOCTEST_REQUIRE
+#undef DOCTEST_REQUIRE_FALSE
+#undef DOCTEST_REQUIRE_MESSAGE
+#undef DOCTEST_REQUIRE_FALSE_MESSAGE
+#undef DOCTEST_REQUIRE_EQ
+#undef DOCTEST_REQUIRE_NE
+#undef DOCTEST_REQUIRE_GT
+#undef DOCTEST_REQUIRE_LT
+#undef DOCTEST_REQUIRE_GE
+#undef DOCTEST_REQUIRE_LE
+#undef DOCTEST_REQUIRE_UNARY
+#undef DOCTEST_REQUIRE_UNARY_FALSE
+
+#define DOCTEST_REQUIRE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_FALSE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_EQ DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_NE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_GT DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_LT DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_GE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_LE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_UNARY DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_UNARY_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#define DOCTEST_WARN_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+// clang-format off
+// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS
+#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ
+#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ
+#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ
+#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE
+#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE
+#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE
+#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT
+#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT
+#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT
+#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT
+#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT
+#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT
+#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE
+#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE
+#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE
+#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE
+#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE
+#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE
+
+#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY
+#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY
+#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY
+#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE
+#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE
+#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__)
+// clang-format on
+
+// BDD style macros
+// clang-format off
+#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name)
+#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name)
+#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__)
+#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id)
+
+#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name)
+#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name)
+#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name)
+#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name)
+#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name)
+// clang-format on
+
+// == SHORT VERSIONS OF THE MACROS
+#ifndef DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
+
+#define TEST_CASE(name) DOCTEST_TEST_CASE(name)
+#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name)
+#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name)
+#define TYPE_TO_STRING_AS(str, ...) DOCTEST_TYPE_TO_STRING_AS(str, __VA_ARGS__)
+#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__)
+#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__)
+#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id)
+#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__)
+#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__)
+#define SUBCASE(name) DOCTEST_SUBCASE(name)
+#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators)
+#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name)
+#define TEST_SUITE_END DOCTEST_TEST_SUITE_END
+#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)
+#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter)
+#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter)
+#define INFO(...) DOCTEST_INFO(__VA_ARGS__)
+#define CAPTURE(x) DOCTEST_CAPTURE(x)
+#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__)
+#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__)
+#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__)
+#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__)
+#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__)
+#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__)
+#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__)
+
+#define WARN(...) DOCTEST_WARN(__VA_ARGS__)
+#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__)
+#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__)
+#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__)
+#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__)
+#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__)
+#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__)
+#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__)
+#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__)
+#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__)
+#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__)
+#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__)
+#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__)
+#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__)
+#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__)
+#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__)
+#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__)
+#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__)
+#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__)
+
+#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__)
+#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__)
+#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__)
+#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)
+#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)
+#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)
+#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__)
+#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__)
+#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__)
+#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__)
+#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)
+#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)
+#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)
+#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__)
+#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__)
+#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__)
+#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__)
+#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)
+#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__)
+
+#define SCENARIO(name) DOCTEST_SCENARIO(name)
+#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name)
+#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__)
+#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id)
+#define GIVEN(name) DOCTEST_GIVEN(name)
+#define WHEN(name) DOCTEST_WHEN(name)
+#define AND_WHEN(name) DOCTEST_AND_WHEN(name)
+#define THEN(name) DOCTEST_THEN(name)
+#define AND_THEN(name) DOCTEST_AND_THEN(name)
+
+#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__)
+#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__)
+#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__)
+#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__)
+#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__)
+#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__)
+#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__)
+#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__)
+#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__)
+#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__)
+#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__)
+#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__)
+#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__)
+#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__)
+#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__)
+#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__)
+#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__)
+#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__)
+#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__)
+#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__)
+#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__)
+#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__)
+#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__)
+#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__)
+
+// KEPT FOR BACKWARDS COMPATIBILITY
+#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__)
+#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__)
+#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__)
+#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__)
+#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__)
+#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__)
+#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__)
+#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__)
+#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__)
+#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__)
+#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__)
+#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__)
+#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__)
+#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__)
+#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__)
+#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__)
+#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__)
+#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__)
+
+#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__)
+#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__)
+#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__)
+#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__)
+#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__)
+#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__)
+
+#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__)
+
+#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
+
+#ifndef DOCTEST_CONFIG_DISABLE
+
+// this is here to clear the 'current test suite' for the current translation unit - at the top
+DOCTEST_TEST_SUITE_END();
+
+#endif // DOCTEST_CONFIG_DISABLE
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+DOCTEST_SUPPRESS_COMMON_WARNINGS_POP
+
+#endif // DOCTEST_LIBRARY_INCLUDED
+
+#ifndef DOCTEST_SINGLE_HEADER
+#define DOCTEST_SINGLE_HEADER
+#endif // DOCTEST_SINGLE_HEADER
+
+#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER)
+
+#ifndef DOCTEST_SINGLE_HEADER
+#include "doctest_fwd.h"
+#endif // DOCTEST_SINGLE_HEADER
+
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros")
+
+#ifndef DOCTEST_LIBRARY_IMPLEMENTATION
+#define DOCTEST_LIBRARY_IMPLEMENTATION
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH
+
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path")
+
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute")
+
+DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data
+DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled
+DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified
+DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal
+DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch
+DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C
+DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning)
+DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed
+
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
+
+// required includes - will go only in one translation unit!
+#include <ctime>
+#include <cmath>
+#include <climits>
+// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37
+#ifdef __BORLANDC__
+#include <math.h>
+#endif // __BORLANDC__
+#include <new>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <utility>
+#include <fstream>
+#include <sstream>
+#include <iostream>
+#include <algorithm>
+#include <iomanip>
+#include <vector>
+#ifndef DOCTEST_CONFIG_NO_MULTITHREADING
+#include <atomic>
+#include <mutex>
+#define DOCTEST_DECLARE_MUTEX(name) std::mutex name;
+#define DOCTEST_DECLARE_STATIC_MUTEX(name) static DOCTEST_DECLARE_MUTEX(name)
+#define DOCTEST_LOCK_MUTEX(name) std::lock_guard<std::mutex> DOCTEST_ANONYMOUS(DOCTEST_ANON_LOCK_)(name);
+#else // DOCTEST_CONFIG_NO_MULTITHREADING
+#define DOCTEST_DECLARE_MUTEX(name)
+#define DOCTEST_DECLARE_STATIC_MUTEX(name)
+#define DOCTEST_LOCK_MUTEX(name)
+#endif // DOCTEST_CONFIG_NO_MULTITHREADING
+#include <set>
+#include <map>
+#include <unordered_set>
+#include <exception>
+#include <stdexcept>
+#include <csignal>
+#include <cfloat>
+#include <cctype>
+#include <cstdint>
+#include <string>
+
+#ifdef DOCTEST_PLATFORM_MAC
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/sysctl.h>
+#endif // DOCTEST_PLATFORM_MAC
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+
+// defines for a leaner windows.h
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif // WIN32_LEAN_AND_MEAN
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+
+// not sure what AfxWin.h is for - here I do what Catch does
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#include <windows.h>
+#endif
+#include <io.h>
+
+#else // DOCTEST_PLATFORM_WINDOWS
+
+#include <sys/time.h>
+#include <unistd.h>
+
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+// this is a fix for https://github.com/doctest/doctest/issues/348
+// https://mail.gnome.org/archives/xml/2012-January/msg00000.html
+#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO)
+#define STDOUT_FILENO fileno(stdout)
+#endif // HAVE_UNISTD_H
+
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
+
+// counts the number of elements in a C array
+#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0]))
+
+#ifdef DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled
+#else // DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled
+#endif // DOCTEST_CONFIG_DISABLE
+
+#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX
+#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-"
+#endif
+
+#ifndef DOCTEST_THREAD_LOCAL
+#if defined(DOCTEST_CONFIG_NO_MULTITHREADING) || DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_THREAD_LOCAL
+#else // DOCTEST_MSVC
+#define DOCTEST_THREAD_LOCAL thread_local
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_THREAD_LOCAL
+
+#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES
+#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32
+#endif
+
+#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE
+#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64
+#endif
+
+#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX
+#else
+#define DOCTEST_OPTIONS_PREFIX_DISPLAY ""
+#endif
+
+#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+#endif
+
+#ifndef DOCTEST_CDECL
+#define DOCTEST_CDECL __cdecl
+#endif
+
+namespace doctest {
+
+bool is_running_in_test = false;
+
+namespace {
+ using namespace detail;
+
+ template <typename Ex>
+ DOCTEST_NORETURN void throw_exception(Ex const& e) {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ throw e;
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS
+ std::cerr << "doctest will terminate because it needed to throw an exception.\n"
+ << "The message was: " << e.what() << '\n';
+ std::terminate();
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ }
+
+#ifndef DOCTEST_INTERNAL_ERROR
+#define DOCTEST_INTERNAL_ERROR(msg) \
+ throw_exception(std::logic_error( \
+ __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg))
+#endif // DOCTEST_INTERNAL_ERROR
+
+ // case insensitive strcmp
+ int stricmp(const char* a, const char* b) {
+ for(;; a++, b++) {
+ const int d = tolower(*a) - tolower(*b);
+ if(d != 0 || !*a)
+ return d;
+ }
+ }
+
+ struct Endianness
+ {
+ enum Arch
+ {
+ Big,
+ Little
+ };
+
+ static Arch which() {
+ int x = 1;
+ // casting any data pointer to char* is allowed
+ auto ptr = reinterpret_cast<char*>(&x);
+ if(*ptr)
+ return Little;
+ return Big;
+ }
+ };
+} // namespace
+
+namespace detail {
+ DOCTEST_THREAD_LOCAL class
+ {
+ std::vector<std::streampos> stack;
+ std::stringstream ss;
+
+ public:
+ std::ostream* push() {
+ stack.push_back(ss.tellp());
+ return &ss;
+ }
+
+ String pop() {
+ if (stack.empty())
+ DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!");
+
+ std::streampos pos = stack.back();
+ stack.pop_back();
+ unsigned sz = static_cast<unsigned>(ss.tellp() - pos);
+ ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out);
+ return String(ss, sz);
+ }
+ } g_oss;
+
+ std::ostream* tlssPush() {
+ return g_oss.push();
+ }
+
+ String tlssPop() {
+ return g_oss.pop();
+ }
+
+#ifndef DOCTEST_CONFIG_DISABLE
+
+namespace timer_large_integer
+{
+
+#if defined(DOCTEST_PLATFORM_WINDOWS)
+ using type = ULONGLONG;
+#else // DOCTEST_PLATFORM_WINDOWS
+ using type = std::uint64_t;
+#endif // DOCTEST_PLATFORM_WINDOWS
+}
+
+using ticks_t = timer_large_integer::type;
+
+#ifdef DOCTEST_CONFIG_GETCURRENTTICKS
+ ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); }
+#elif defined(DOCTEST_PLATFORM_WINDOWS)
+ ticks_t getCurrentTicks() {
+ static LARGE_INTEGER hz = { {0} }, hzo = { {0} };
+ if(!hz.QuadPart) {
+ QueryPerformanceFrequency(&hz);
+ QueryPerformanceCounter(&hzo);
+ }
+ LARGE_INTEGER t;
+ QueryPerformanceCounter(&t);
+ return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart;
+ }
+#else // DOCTEST_PLATFORM_WINDOWS
+ ticks_t getCurrentTicks() {
+ timeval t;
+ gettimeofday(&t, nullptr);
+ return static_cast<ticks_t>(t.tv_sec) * 1000000 + static_cast<ticks_t>(t.tv_usec);
+ }
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+ struct Timer
+ {
+ void start() { m_ticks = getCurrentTicks(); }
+ unsigned int getElapsedMicroseconds() const {
+ return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
+ }
+ //unsigned int getElapsedMilliseconds() const {
+ // return static_cast<unsigned int>(getElapsedMicroseconds() / 1000);
+ //}
+ double getElapsedSeconds() const { return static_cast<double>(getCurrentTicks() - m_ticks) / 1000000.0; }
+
+ private:
+ ticks_t m_ticks = 0;
+ };
+
+#ifdef DOCTEST_CONFIG_NO_MULTITHREADING
+ template <typename T>
+ using Atomic = T;
+#else // DOCTEST_CONFIG_NO_MULTITHREADING
+ template <typename T>
+ using Atomic = std::atomic<T>;
+#endif // DOCTEST_CONFIG_NO_MULTITHREADING
+
+#if defined(DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS) || defined(DOCTEST_CONFIG_NO_MULTITHREADING)
+ template <typename T>
+ using MultiLaneAtomic = Atomic<T>;
+#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+ // Provides a multilane implementation of an atomic variable that supports add, sub, load,
+ // store. Instead of using a single atomic variable, this splits up into multiple ones,
+ // each sitting on a separate cache line. The goal is to provide a speedup when most
+ // operations are modifying. It achieves this with two properties:
+ //
+ // * Multiple atomics are used, so chance of congestion from the same atomic is reduced.
+ // * Each atomic sits on a separate cache line, so false sharing is reduced.
+ //
+ // The disadvantage is that there is a small overhead due to the use of TLS, and load/store
+ // is slower because all atomics have to be accessed.
+ template <typename T>
+ class MultiLaneAtomic
+ {
+ struct CacheLineAlignedAtomic
+ {
+ Atomic<T> atomic{};
+ char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(Atomic<T>)];
+ };
+ CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES];
+
+ static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE,
+ "guarantee one atomic takes exactly one cache line");
+
+ public:
+ T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; }
+
+ T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); }
+
+ T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {
+ return myAtomic().fetch_add(arg, order);
+ }
+
+ T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {
+ return myAtomic().fetch_sub(arg, order);
+ }
+
+ operator T() const DOCTEST_NOEXCEPT { return load(); }
+
+ T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT {
+ auto result = T();
+ for(auto const& c : m_atomics) {
+ result += c.atomic.load(order);
+ }
+ return result;
+ }
+
+ T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this]
+ store(desired);
+ return desired;
+ }
+
+ void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {
+ // first value becomes desired", all others become 0.
+ for(auto& c : m_atomics) {
+ c.atomic.store(desired, order);
+ desired = {};
+ }
+ }
+
+ private:
+ // Each thread has a different atomic that it operates on. If more than NumLanes threads
+ // use this, some will use the same atomic. So performance will degrade a bit, but still
+ // everything will work.
+ //
+ // The logic here is a bit tricky. The call should be as fast as possible, so that there
+ // is minimal to no overhead in determining the correct atomic for the current thread.
+ //
+ // 1. A global static counter laneCounter counts continuously up.
+ // 2. Each successive thread will use modulo operation of that counter so it gets an atomic
+ // assigned in a round-robin fashion.
+ // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with
+ // little overhead.
+ Atomic<T>& myAtomic() DOCTEST_NOEXCEPT {
+ static Atomic<size_t> laneCounter;
+ DOCTEST_THREAD_LOCAL size_t tlsLaneIdx =
+ laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES;
+
+ return m_atomics[tlsLaneIdx].atomic;
+ }
+ };
+#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+
+ // this holds both parameters from the command line and runtime data for tests
+ struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats
+ {
+ MultiLaneAtomic<int> numAssertsCurrentTest_atomic;
+ MultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic;
+
+ std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters
+
+ std::vector<IReporter*> reporters_currently_used;
+
+ assert_handler ah = nullptr;
+
+ Timer timer;
+
+ std::vector<String> stringifiedContexts; // logging from INFO() due to an exception
+
+ // stuff for subcases
+ bool reachedLeaf;
+ std::vector<SubcaseSignature> subcaseStack;
+ std::vector<SubcaseSignature> nextSubcaseStack;
+ std::unordered_set<unsigned long long> fullyTraversedSubcases;
+ size_t currentSubcaseDepth;
+ Atomic<bool> shouldLogCurrentException;
+
+ void resetRunData() {
+ numTestCases = 0;
+ numTestCasesPassingFilters = 0;
+ numTestSuitesPassingFilters = 0;
+ numTestCasesFailed = 0;
+ numAsserts = 0;
+ numAssertsFailed = 0;
+ numAssertsCurrentTest = 0;
+ numAssertsFailedCurrentTest = 0;
+ }
+
+ void finalizeTestCaseData() {
+ seconds = timer.getElapsedSeconds();
+
+ // update the non-atomic counters
+ numAsserts += numAssertsCurrentTest_atomic;
+ numAssertsFailed += numAssertsFailedCurrentTest_atomic;
+ numAssertsCurrentTest = numAssertsCurrentTest_atomic;
+ numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic;
+
+ if(numAssertsFailedCurrentTest)
+ failure_flags |= TestCaseFailureReason::AssertFailure;
+
+ if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 &&
+ Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout)
+ failure_flags |= TestCaseFailureReason::Timeout;
+
+ if(currentTest->m_should_fail) {
+ if(failure_flags) {
+ failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid;
+ } else {
+ failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt;
+ }
+ } else if(failure_flags && currentTest->m_may_fail) {
+ failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid;
+ } else if(currentTest->m_expected_failures > 0) {
+ if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) {
+ failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes;
+ } else {
+ failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes;
+ }
+ }
+
+ bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) ||
+ (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) ||
+ (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags);
+
+ // if any subcase has failed - the whole test case has failed
+ testCaseSuccess = !(failure_flags && !ok_to_fail);
+ if(!testCaseSuccess)
+ numTestCasesFailed++;
+ }
+ };
+
+ ContextState* g_cs = nullptr;
+
+ // used to avoid locks for the debug output
+ // TODO: figure out if this is indeed necessary/correct - seems like either there still
+ // could be a race or that there wouldn't be a race even if using the context directly
+ DOCTEST_THREAD_LOCAL bool g_no_colors;
+
+#endif // DOCTEST_CONFIG_DISABLE
+} // namespace detail
+
+char* String::allocate(size_type sz) {
+ if (sz <= last) {
+ buf[sz] = '\0';
+ setLast(last - sz);
+ return buf;
+ } else {
+ setOnHeap();
+ data.size = sz;
+ data.capacity = data.size + 1;
+ data.ptr = new char[data.capacity];
+ data.ptr[sz] = '\0';
+ return data.ptr;
+ }
+}
+
+void String::setOnHeap() noexcept { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; }
+void String::setLast(size_type in) noexcept { buf[last] = char(in); }
+void String::setSize(size_type sz) noexcept {
+ if (isOnStack()) { buf[sz] = '\0'; setLast(last - sz); }
+ else { data.ptr[sz] = '\0'; data.size = sz; }
+}
+
+void String::copy(const String& other) {
+ if(other.isOnStack()) {
+ memcpy(buf, other.buf, len);
+ } else {
+ memcpy(allocate(other.data.size), other.data.ptr, other.data.size);
+ }
+}
+
+String::String() noexcept {
+ buf[0] = '\0';
+ setLast();
+}
+
+String::~String() {
+ if(!isOnStack())
+ delete[] data.ptr;
+} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
+
+String::String(const char* in)
+ : String(in, strlen(in)) {}
+
+String::String(const char* in, size_type in_size) {
+ memcpy(allocate(in_size), in, in_size);
+}
+
+String::String(std::istream& in, size_type in_size) {
+ in.read(allocate(in_size), in_size);
+}
+
+String::String(const String& other) { copy(other); }
+
+String& String::operator=(const String& other) {
+ if(this != &other) {
+ if(!isOnStack())
+ delete[] data.ptr;
+
+ copy(other);
+ }
+
+ return *this;
+}
+
+String& String::operator+=(const String& other) {
+ const size_type my_old_size = size();
+ const size_type other_size = other.size();
+ const size_type total_size = my_old_size + other_size;
+ if(isOnStack()) {
+ if(total_size < len) {
+ // append to the current stack space
+ memcpy(buf + my_old_size, other.c_str(), other_size + 1);
+ // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+ setLast(last - total_size);
+ } else {
+ // alloc new chunk
+ char* temp = new char[total_size + 1];
+ // copy current data to new location before writing in the union
+ memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed
+ // update data in union
+ setOnHeap();
+ data.size = total_size;
+ data.capacity = data.size + 1;
+ data.ptr = temp;
+ // transfer the rest of the data
+ memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);
+ }
+ } else {
+ if(data.capacity > total_size) {
+ // append to the current heap block
+ data.size = total_size;
+ memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);
+ } else {
+ // resize
+ data.capacity *= 2;
+ if(data.capacity <= total_size)
+ data.capacity = total_size + 1;
+ // alloc new chunk
+ char* temp = new char[data.capacity];
+ // copy current data to new location before releasing it
+ memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed
+ // release old chunk
+ delete[] data.ptr;
+ // update the rest of the union members
+ data.size = total_size;
+ data.ptr = temp;
+ // transfer the rest of the data
+ memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);
+ }
+ }
+
+ return *this;
+}
+
+String::String(String&& other) noexcept {
+ memcpy(buf, other.buf, len);
+ other.buf[0] = '\0';
+ other.setLast();
+}
+
+String& String::operator=(String&& other) noexcept {
+ if(this != &other) {
+ if(!isOnStack())
+ delete[] data.ptr;
+ memcpy(buf, other.buf, len);
+ other.buf[0] = '\0';
+ other.setLast();
+ }
+ return *this;
+}
+
+char String::operator[](size_type i) const {
+ return const_cast<String*>(this)->operator[](i);
+}
+
+char& String::operator[](size_type i) {
+ if(isOnStack())
+ return reinterpret_cast<char*>(buf)[i];
+ return data.ptr[i];
+}
+
+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized")
+String::size_type String::size() const {
+ if(isOnStack())
+ return last - (size_type(buf[last]) & 31); // using "last" would work only if "len" is 32
+ return data.size;
+}
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+String::size_type String::capacity() const {
+ if(isOnStack())
+ return len;
+ return data.capacity;
+}
+
+String String::substr(size_type pos, size_type cnt) && {
+ cnt = std::min(cnt, size() - 1 - pos);
+ char* cptr = c_str();
+ memmove(cptr, cptr + pos, cnt);
+ setSize(cnt);
+ return std::move(*this);
+}
+
+String String::substr(size_type pos, size_type cnt) const & {
+ cnt = std::min(cnt, size() - 1 - pos);
+ return String{ c_str() + pos, cnt };
+}
+
+String::size_type String::find(char ch, size_type pos) const {
+ const char* begin = c_str();
+ const char* end = begin + size();
+ const char* it = begin + pos;
+ for (; it < end && *it != ch; it++);
+ if (it < end) { return static_cast<size_type>(it - begin); }
+ else { return npos; }
+}
+
+String::size_type String::rfind(char ch, size_type pos) const {
+ const char* begin = c_str();
+ const char* it = begin + std::min(pos, size() - 1);
+ for (; it >= begin && *it != ch; it--);
+ if (it >= begin) { return static_cast<size_type>(it - begin); }
+ else { return npos; }
+}
+
+int String::compare(const char* other, bool no_case) const {
+ if(no_case)
+ return doctest::stricmp(c_str(), other);
+ return std::strcmp(c_str(), other);
+}
+
+int String::compare(const String& other, bool no_case) const {
+ return compare(other.c_str(), no_case);
+}
+
+String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; }
+
+bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; }
+bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; }
+bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; }
+bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; }
+bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; }
+bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; }
+
+std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); }
+
+Contains::Contains(const String& str) : string(str) { }
+
+bool Contains::checkWith(const String& other) const {
+ return strstr(other.c_str(), string.c_str()) != nullptr;
+}
+
+String toString(const Contains& in) {
+ return "Contains( " + in.string + " )";
+}
+
+bool operator==(const String& lhs, const Contains& rhs) { return rhs.checkWith(lhs); }
+bool operator==(const Contains& lhs, const String& rhs) { return lhs.checkWith(rhs); }
+bool operator!=(const String& lhs, const Contains& rhs) { return !rhs.checkWith(lhs); }
+bool operator!=(const Contains& lhs, const String& rhs) { return !lhs.checkWith(rhs); }
+
+namespace {
+ void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;)
+} // namespace
+
+namespace Color {
+ std::ostream& operator<<(std::ostream& s, Color::Enum code) {
+ color_to_stream(s, code);
+ return s;
+ }
+} // namespace Color
+
+// clang-format off
+const char* assertString(assertType::Enum at) {
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitely handled
+ #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type
+ #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type)
+ switch(at) {
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(FALSE);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_AS);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOTHROW);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(LE);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE);
+
+ default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!");
+ }
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+}
+// clang-format on
+
+const char* failureString(assertType::Enum at) {
+ if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional
+ return "WARNING";
+ if(at & assertType::is_check) //!OCLINT bitwise operator in conditional
+ return "ERROR";
+ if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
+ return "FATAL ERROR";
+ return "";
+}
+
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")
+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")
+// depending on the current options this will remove the path of filenames
+const char* skipPathFromFilename(const char* file) {
+#ifndef DOCTEST_CONFIG_DISABLE
+ if(getContextOptions()->no_path_in_filenames) {
+ auto back = std::strrchr(file, '\\');
+ auto forward = std::strrchr(file, '/');
+ if(back || forward) {
+ if(back > forward)
+ forward = back;
+ return forward + 1;
+ }
+ }
+#endif // DOCTEST_CONFIG_DISABLE
+ return file;
+}
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+bool SubcaseSignature::operator==(const SubcaseSignature& other) const {
+ return m_line == other.m_line
+ && std::strcmp(m_file, other.m_file) == 0
+ && m_name == other.m_name;
+}
+
+bool SubcaseSignature::operator<(const SubcaseSignature& other) const {
+ if(m_line != other.m_line)
+ return m_line < other.m_line;
+ if(std::strcmp(m_file, other.m_file) != 0)
+ return std::strcmp(m_file, other.m_file) < 0;
+ return m_name.compare(other.m_name) < 0;
+}
+
+DOCTEST_DEFINE_INTERFACE(IContextScope)
+
+namespace detail {
+ void filldata<const void*>::fill(std::ostream* stream, const void* in) {
+ if (in) { *stream << in; }
+ else { *stream << "nullptr"; }
+ }
+
+ template <typename T>
+ String toStreamLit(T t) {
+ std::ostream* os = tlssPush();
+ os->operator<<(t);
+ return tlssPop();
+ }
+}
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; }
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183
+String toString(const std::string& in) { return in.c_str(); }
+#endif // VS 2019
+
+String toString(String in) { return in; }
+
+String toString(std::nullptr_t) { return "nullptr"; }
+
+String toString(bool in) { return in ? "true" : "false"; }
+
+String toString(float in) { return toStreamLit(in); }
+String toString(double in) { return toStreamLit(in); }
+String toString(double long in) { return toStreamLit(in); }
+
+String toString(char in) { return toStreamLit(static_cast<signed>(in)); }
+String toString(char signed in) { return toStreamLit(static_cast<signed>(in)); }
+String toString(char unsigned in) { return toStreamLit(static_cast<unsigned>(in)); }
+String toString(short in) { return toStreamLit(in); }
+String toString(short unsigned in) { return toStreamLit(in); }
+String toString(signed in) { return toStreamLit(in); }
+String toString(unsigned in) { return toStreamLit(in); }
+String toString(long in) { return toStreamLit(in); }
+String toString(long unsigned in) { return toStreamLit(in); }
+String toString(long long in) { return toStreamLit(in); }
+String toString(long long unsigned in) { return toStreamLit(in); }
+
+Approx::Approx(double value)
+ : m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100)
+ , m_scale(1.0)
+ , m_value(value) {}
+
+Approx Approx::operator()(double value) const {
+ Approx approx(value);
+ approx.epsilon(m_epsilon);
+ approx.scale(m_scale);
+ return approx;
+}
+
+Approx& Approx::epsilon(double newEpsilon) {
+ m_epsilon = newEpsilon;
+ return *this;
+}
+Approx& Approx::scale(double newScale) {
+ m_scale = newScale;
+ return *this;
+}
+
+bool operator==(double lhs, const Approx& rhs) {
+ // Thanks to Richard Harris for his help refining this formula
+ return std::fabs(lhs - rhs.m_value) <
+ rhs.m_epsilon * (rhs.m_scale + std::max<double>(std::fabs(lhs), std::fabs(rhs.m_value)));
+}
+bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); }
+bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); }
+bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); }
+bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; }
+bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; }
+bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; }
+bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; }
+bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; }
+bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; }
+bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; }
+bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; }
+
+String toString(const Approx& in) {
+ return "Approx( " + doctest::toString(in.m_value) + " )";
+}
+const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); }
+
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738)
+template <typename F>
+IsNaN<F>::operator bool() const {
+ return std::isnan(value) ^ flipped;
+}
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+template struct DOCTEST_INTERFACE_DEF IsNaN<float>;
+template struct DOCTEST_INTERFACE_DEF IsNaN<double>;
+template struct DOCTEST_INTERFACE_DEF IsNaN<long double>;
+template <typename F>
+String toString(IsNaN<F> in) { return String(in.flipped ? "! " : "") + "IsNaN( " + doctest::toString(in.value) + " )"; }
+String toString(IsNaN<float> in) { return toString<float>(in); }
+String toString(IsNaN<double> in) { return toString<double>(in); }
+String toString(IsNaN<double long> in) { return toString<double long>(in); }
+
+} // namespace doctest
+
+#ifdef DOCTEST_CONFIG_DISABLE
+namespace doctest {
+Context::Context(int, const char* const*) {}
+Context::~Context() = default;
+void Context::applyCommandLine(int, const char* const*) {}
+void Context::addFilter(const char*, const char*) {}
+void Context::clearFilters() {}
+void Context::setOption(const char*, bool) {}
+void Context::setOption(const char*, int) {}
+void Context::setOption(const char*, const char*) {}
+bool Context::shouldExit() { return false; }
+void Context::setAsDefaultForAssertsOutOfTestCases() {}
+void Context::setAssertHandler(detail::assert_handler) {}
+void Context::setCout(std::ostream*) {}
+int Context::run() { return 0; }
+
+int IReporter::get_num_active_contexts() { return 0; }
+const IContextScope* const* IReporter::get_active_contexts() { return nullptr; }
+int IReporter::get_num_stringified_contexts() { return 0; }
+const String* IReporter::get_stringified_contexts() { return nullptr; }
+
+int registerReporter(const char*, int, IReporter*) { return 0; }
+
+} // namespace doctest
+#else // DOCTEST_CONFIG_DISABLE
+
+#if !defined(DOCTEST_CONFIG_COLORS_NONE)
+#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI)
+#ifdef DOCTEST_PLATFORM_WINDOWS
+#define DOCTEST_CONFIG_COLORS_WINDOWS
+#else // linux
+#define DOCTEST_CONFIG_COLORS_ANSI
+#endif // platform
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI
+#endif // DOCTEST_CONFIG_COLORS_NONE
+
+namespace doctest_detail_test_suite_ns {
+// holds the current test suite
+doctest::detail::TestSuite& getCurrentTestSuite() {
+ static doctest::detail::TestSuite data{};
+ return data;
+}
+} // namespace doctest_detail_test_suite_ns
+
+namespace doctest {
+namespace {
+ // the int (priority) is part of the key for automatic sorting - sadly one can register a
+ // reporter with a duplicate name and a different priority but hopefully that won't happen often :|
+ using reporterMap = std::map<std::pair<int, String>, reporterCreatorFunc>;
+
+ reporterMap& getReporters() {
+ static reporterMap data;
+ return data;
+ }
+ reporterMap& getListeners() {
+ static reporterMap data;
+ return data;
+ }
+} // namespace
+namespace detail {
+#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \
+ for(auto& curr_rep : g_cs->reporters_currently_used) \
+ curr_rep->function(__VA_ARGS__)
+
+ bool checkIfShouldThrow(assertType::Enum at) {
+ if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
+ return true;
+
+ if((at & assertType::is_check) //!OCLINT bitwise operator in conditional
+ && getContextOptions()->abort_after > 0 &&
+ (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >=
+ getContextOptions()->abort_after)
+ return true;
+
+ return false;
+ }
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ DOCTEST_NORETURN void throwException() {
+ g_cs->shouldLogCurrentException = false;
+ throw TestFailureException(); // NOLINT(hicpp-exception-baseclass)
+ }
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS
+ void throwException() {}
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+} // namespace detail
+
+namespace {
+ using namespace detail;
+ // matching of a string against a wildcard mask (case sensitivity configurable) taken from
+ // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing
+ int wildcmp(const char* str, const char* wild, bool caseSensitive) {
+ const char* cp = str;
+ const char* mp = wild;
+
+ while((*str) && (*wild != '*')) {
+ if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) &&
+ (*wild != '?')) {
+ return 0;
+ }
+ wild++;
+ str++;
+ }
+
+ while(*str) {
+ if(*wild == '*') {
+ if(!*++wild) {
+ return 1;
+ }
+ mp = wild;
+ cp = str + 1;
+ } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) ||
+ (*wild == '?')) {
+ wild++;
+ str++;
+ } else {
+ wild = mp; //!OCLINT parameter reassignment
+ str = cp++; //!OCLINT parameter reassignment
+ }
+ }
+
+ while(*wild == '*') {
+ wild++;
+ }
+ return !*wild;
+ }
+
+ // checks if the name matches any of the filters (and can be configured what to do when empty)
+ bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty,
+ bool caseSensitive) {
+ if (filters.empty() && matchEmpty)
+ return true;
+ for (auto& curr : filters)
+ if (wildcmp(name, curr.c_str(), caseSensitive))
+ return true;
+ return false;
+ }
+
+ unsigned long long hash(unsigned long long a, unsigned long long b) {
+ return (a << 5) + b;
+ }
+
+ // C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html
+ unsigned long long hash(const char* str) {
+ unsigned long long hash = 5381;
+ char c;
+ while ((c = *str++))
+ hash = ((hash << 5) + hash) + c; // hash * 33 + c
+ return hash;
+ }
+
+ unsigned long long hash(const SubcaseSignature& sig) {
+ return hash(hash(hash(sig.m_file), hash(sig.m_name.c_str())), sig.m_line);
+ }
+
+ unsigned long long hash(const std::vector<SubcaseSignature>& sigs, size_t count) {
+ unsigned long long running = 0;
+ auto end = sigs.begin() + count;
+ for (auto it = sigs.begin(); it != end; it++) {
+ running = hash(running, hash(*it));
+ }
+ return running;
+ }
+
+ unsigned long long hash(const std::vector<SubcaseSignature>& sigs) {
+ unsigned long long running = 0;
+ for (const SubcaseSignature& sig : sigs) {
+ running = hash(running, hash(sig));
+ }
+ return running;
+ }
+} // namespace
+namespace detail {
+ bool Subcase::checkFilters() {
+ if (g_cs->subcaseStack.size() < size_t(g_cs->subcase_filter_levels)) {
+ if (!matchesAny(m_signature.m_name.c_str(), g_cs->filters[6], true, g_cs->case_sensitive))
+ return true;
+ if (matchesAny(m_signature.m_name.c_str(), g_cs->filters[7], false, g_cs->case_sensitive))
+ return true;
+ }
+ return false;
+ }
+
+ Subcase::Subcase(const String& name, const char* file, int line)
+ : m_signature({name, file, line}) {
+ if (!g_cs->reachedLeaf) {
+ if (g_cs->nextSubcaseStack.size() <= g_cs->subcaseStack.size()
+ || g_cs->nextSubcaseStack[g_cs->subcaseStack.size()] == m_signature) {
+ // Going down.
+ if (checkFilters()) { return; }
+
+ g_cs->subcaseStack.push_back(m_signature);
+ g_cs->currentSubcaseDepth++;
+ m_entered = true;
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);
+ }
+ } else {
+ if (g_cs->subcaseStack[g_cs->currentSubcaseDepth] == m_signature) {
+ // This subcase is reentered via control flow.
+ g_cs->currentSubcaseDepth++;
+ m_entered = true;
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);
+ } else if (g_cs->nextSubcaseStack.size() <= g_cs->currentSubcaseDepth
+ && g_cs->fullyTraversedSubcases.find(hash(hash(g_cs->subcaseStack, g_cs->currentSubcaseDepth), hash(m_signature)))
+ == g_cs->fullyTraversedSubcases.end()) {
+ if (checkFilters()) { return; }
+ // This subcase is part of the one to be executed next.
+ g_cs->nextSubcaseStack.clear();
+ g_cs->nextSubcaseStack.insert(g_cs->nextSubcaseStack.end(),
+ g_cs->subcaseStack.begin(), g_cs->subcaseStack.begin() + g_cs->currentSubcaseDepth);
+ g_cs->nextSubcaseStack.push_back(m_signature);
+ }
+ }
+ }
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+
+ Subcase::~Subcase() {
+ if (m_entered) {
+ g_cs->currentSubcaseDepth--;
+
+ if (!g_cs->reachedLeaf) {
+ // Leaf.
+ g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack));
+ g_cs->nextSubcaseStack.clear();
+ g_cs->reachedLeaf = true;
+ } else if (g_cs->nextSubcaseStack.empty()) {
+ // All children are finished.
+ g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack));
+ }
+
+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)
+ if(std::uncaught_exceptions() > 0
+#else
+ if(std::uncaught_exception()
+#endif
+ && g_cs->shouldLogCurrentException) {
+ DOCTEST_ITERATE_THROUGH_REPORTERS(
+ test_case_exception, {"exception thrown in subcase - will translate later "
+ "when the whole test case has been exited (cannot "
+ "translate while there is an active exception)",
+ false});
+ g_cs->shouldLogCurrentException = false;
+ }
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
+ }
+ }
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+ Subcase::operator bool() const { return m_entered; }
+
+ Result::Result(bool passed, const String& decomposition)
+ : m_passed(passed)
+ , m_decomp(decomposition) {}
+
+ ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at)
+ : m_at(at) {}
+
+ TestSuite& TestSuite::operator*(const char* in) {
+ m_test_suite = in;
+ return *this;
+ }
+
+ TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
+ const String& type, int template_id) {
+ m_file = file;
+ m_line = line;
+ m_name = nullptr; // will be later overridden in operator*
+ m_test_suite = test_suite.m_test_suite;
+ m_description = test_suite.m_description;
+ m_skip = test_suite.m_skip;
+ m_no_breaks = test_suite.m_no_breaks;
+ m_no_output = test_suite.m_no_output;
+ m_may_fail = test_suite.m_may_fail;
+ m_should_fail = test_suite.m_should_fail;
+ m_expected_failures = test_suite.m_expected_failures;
+ m_timeout = test_suite.m_timeout;
+
+ m_test = test;
+ m_type = type;
+ m_template_id = template_id;
+ }
+
+ TestCase::TestCase(const TestCase& other)
+ : TestCaseData() {
+ *this = other;
+ }
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
+ TestCase& TestCase::operator=(const TestCase& other) {
+ TestCaseData::operator=(other);
+ m_test = other.m_test;
+ m_type = other.m_type;
+ m_template_id = other.m_template_id;
+ m_full_name = other.m_full_name;
+
+ if(m_template_id != -1)
+ m_name = m_full_name.c_str();
+ return *this;
+ }
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+ TestCase& TestCase::operator*(const char* in) {
+ m_name = in;
+ // make a new name with an appended type for templated test case
+ if(m_template_id != -1) {
+ m_full_name = String(m_name) + "<" + m_type + ">";
+ // redirect the name to point to the newly constructed full name
+ m_name = m_full_name.c_str();
+ }
+ return *this;
+ }
+
+ bool TestCase::operator<(const TestCase& other) const {
+ // this will be used only to differentiate between test cases - not relevant for sorting
+ if(m_line != other.m_line)
+ return m_line < other.m_line;
+ const int name_cmp = strcmp(m_name, other.m_name);
+ if(name_cmp != 0)
+ return name_cmp < 0;
+ const int file_cmp = m_file.compare(other.m_file);
+ if(file_cmp != 0)
+ return file_cmp < 0;
+ return m_template_id < other.m_template_id;
+ }
+
+ // all the registered tests
+ std::set<TestCase>& getRegisteredTests() {
+ static std::set<TestCase> data;
+ return data;
+ }
+} // namespace detail
+namespace {
+ using namespace detail;
+ // for sorting tests by file/line
+ bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+ // this is needed because MSVC gives different case for drive letters
+ // for __FILE__ when evaluated in a header and a source file
+ const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC));
+ if(res != 0)
+ return res < 0;
+ if(lhs->m_line != rhs->m_line)
+ return lhs->m_line < rhs->m_line;
+ return lhs->m_template_id < rhs->m_template_id;
+ }
+
+ // for sorting tests by suite/file/line
+ bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+ const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite);
+ if(res != 0)
+ return res < 0;
+ return fileOrderComparator(lhs, rhs);
+ }
+
+ // for sorting tests by name/suite/file/line
+ bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+ const int res = std::strcmp(lhs->m_name, rhs->m_name);
+ if(res != 0)
+ return res < 0;
+ return suiteOrderComparator(lhs, rhs);
+ }
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+ void color_to_stream(std::ostream& s, Color::Enum code) {
+ static_cast<void>(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS
+ static_cast<void>(code); // for DOCTEST_CONFIG_COLORS_NONE
+#ifdef DOCTEST_CONFIG_COLORS_ANSI
+ if(g_no_colors ||
+ (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false))
+ return;
+
+ auto col = "";
+ // clang-format off
+ switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement
+ case Color::Red: col = "[0;31m"; break;
+ case Color::Green: col = "[0;32m"; break;
+ case Color::Blue: col = "[0;34m"; break;
+ case Color::Cyan: col = "[0;36m"; break;
+ case Color::Yellow: col = "[0;33m"; break;
+ case Color::Grey: col = "[1;30m"; break;
+ case Color::LightGrey: col = "[0;37m"; break;
+ case Color::BrightRed: col = "[1;31m"; break;
+ case Color::BrightGreen: col = "[1;32m"; break;
+ case Color::BrightWhite: col = "[1;37m"; break;
+ case Color::Bright: // invalid
+ case Color::None:
+ case Color::White:
+ default: col = "[0m";
+ }
+ // clang-format on
+ s << "\033" << col;
+#endif // DOCTEST_CONFIG_COLORS_ANSI
+
+#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
+ if(g_no_colors ||
+ (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false))
+ return;
+
+ static struct ConsoleHelper {
+ HANDLE stdoutHandle;
+ WORD origFgAttrs;
+ WORD origBgAttrs;
+
+ ConsoleHelper() {
+ stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
+ CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+ GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo);
+ origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |
+ BACKGROUND_BLUE | BACKGROUND_INTENSITY);
+ origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY);
+ }
+ } ch;
+
+#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs)
+
+ // clang-format off
+ switch (code) {
+ case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+ case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break;
+ case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break;
+ case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break;
+ case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break;
+ case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break;
+ case Color::Grey: DOCTEST_SET_ATTR(0); break;
+ case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break;
+ case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break;
+ case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break;
+ case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+ case Color::None:
+ case Color::Bright: // invalid
+ default: DOCTEST_SET_ATTR(ch.origFgAttrs);
+ }
+ // clang-format on
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+ }
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+ std::vector<const IExceptionTranslator*>& getExceptionTranslators() {
+ static std::vector<const IExceptionTranslator*> data;
+ return data;
+ }
+
+ String translateActiveException() {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ String res;
+ auto& translators = getExceptionTranslators();
+ for(auto& curr : translators)
+ if(curr->translate(res))
+ return res;
+ // clang-format off
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value")
+ try {
+ throw;
+ } catch(std::exception& ex) {
+ return ex.what();
+ } catch(std::string& msg) {
+ return msg.c_str();
+ } catch(const char* msg) {
+ return msg;
+ } catch(...) {
+ return "unknown exception";
+ }
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+// clang-format on
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS
+ return "";
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ }
+} // namespace
+
+namespace detail {
+ // used by the macros for registering tests
+ int regTest(const TestCase& tc) {
+ getRegisteredTests().insert(tc);
+ return 0;
+ }
+
+ // sets the current test suite
+ int setTestSuite(const TestSuite& ts) {
+ doctest_detail_test_suite_ns::getCurrentTestSuite() = ts;
+ return 0;
+ }
+
+#ifdef DOCTEST_IS_DEBUGGER_ACTIVE
+ bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); }
+#else // DOCTEST_IS_DEBUGGER_ACTIVE
+#ifdef DOCTEST_PLATFORM_LINUX
+ class ErrnoGuard {
+ public:
+ ErrnoGuard() : m_oldErrno(errno) {}
+ ~ErrnoGuard() { errno = m_oldErrno; }
+ private:
+ int m_oldErrno;
+ };
+ // See the comments in Catch2 for the reasoning behind this implementation:
+ // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102
+ bool isDebuggerActive() {
+ ErrnoGuard guard;
+ std::ifstream in("/proc/self/status");
+ for(std::string line; std::getline(in, line);) {
+ static const int PREFIX_LEN = 11;
+ if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) {
+ return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';
+ }
+ }
+ return false;
+ }
+#elif defined(DOCTEST_PLATFORM_MAC)
+ // The following function is taken directly from the following technical note:
+ // https://developer.apple.com/library/archive/qa/qa1361/_index.html
+ // Returns true if the current process is being debugged (either
+ // running under the debugger or has a debugger attached post facto).
+ bool isDebuggerActive() {
+ int mib[4];
+ 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);
+ if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) {
+ std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n";
+ return false;
+ }
+ // We're being debugged if the P_TRACED flag is set.
+ return ((info.kp_proc.p_flag & P_TRACED) != 0);
+ }
+#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__)
+ bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; }
+#else
+ bool isDebuggerActive() { return false; }
+#endif // Platform
+#endif // DOCTEST_IS_DEBUGGER_ACTIVE
+
+ void registerExceptionTranslatorImpl(const IExceptionTranslator* et) {
+ if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) ==
+ getExceptionTranslators().end())
+ getExceptionTranslators().push_back(et);
+ }
+
+ DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO()
+
+ ContextScopeBase::ContextScopeBase() {
+ g_infoContexts.push_back(this);
+ }
+
+ ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) noexcept {
+ if (other.need_to_destroy) {
+ other.destroy();
+ }
+ other.need_to_destroy = false;
+ g_infoContexts.push_back(this);
+ }
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+
+ // destroy cannot be inlined into the destructor because that would mean calling stringify after
+ // ContextScope has been destroyed (base class destructors run after derived class destructors).
+ // Instead, ContextScope calls this method directly from its destructor.
+ void ContextScopeBase::destroy() {
+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)
+ if(std::uncaught_exceptions() > 0) {
+#else
+ if(std::uncaught_exception()) {
+#endif
+ std::ostringstream s;
+ this->stringify(&s);
+ g_cs->stringifiedContexts.push_back(s.str().c_str());
+ }
+ g_infoContexts.pop_back();
+ }
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+} // namespace detail
+namespace {
+ using namespace detail;
+
+#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
+ struct FatalConditionHandler
+ {
+ static void reset() {}
+ static void allocateAltStackMem() {}
+ static void freeAltStackMem() {}
+ };
+#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
+
+ void reportFatal(const std::string&);
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+
+ struct SignalDefs
+ {
+ DWORD id;
+ const char* name;
+ };
+ // There is no 1-1 mapping between signals and windows exceptions.
+ // Windows can easily distinguish between SO and SigSegV,
+ // but SigInt, SigTerm, etc are handled differently.
+ SignalDefs signalDefs[] = {
+ {static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION),
+ "SIGILL - Illegal instruction signal"},
+ {static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"},
+ {static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION),
+ "SIGSEGV - Segmentation violation signal"},
+ {static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"},
+ };
+
+ struct FatalConditionHandler
+ {
+ static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) {
+ // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the
+ // console just once no matter how many threads have crashed.
+ DOCTEST_DECLARE_STATIC_MUTEX(mutex)
+ static bool execute = true;
+ {
+ DOCTEST_LOCK_MUTEX(mutex)
+ if(execute) {
+ bool reported = false;
+ for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+ if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) {
+ reportFatal(signalDefs[i].name);
+ reported = true;
+ break;
+ }
+ }
+ if(reported == false)
+ reportFatal("Unhandled SEH exception caught");
+ if(isDebuggerActive() && !g_cs->no_breaks)
+ DOCTEST_BREAK_INTO_DEBUGGER();
+ }
+ execute = false;
+ }
+ std::exit(EXIT_FAILURE);
+ }
+
+ static void allocateAltStackMem() {}
+ static void freeAltStackMem() {}
+
+ FatalConditionHandler() {
+ isSet = true;
+ // 32k seems enough for doctest to handle stack overflow,
+ // but the value was found experimentally, so there is no strong guarantee
+ guaranteeSize = 32 * 1024;
+ // Register an unhandled exception filter
+ previousTop = SetUnhandledExceptionFilter(handleException);
+ // Pass in guarantee size to be filled
+ SetThreadStackGuarantee(&guaranteeSize);
+
+ // On Windows uncaught exceptions from another thread, exceptions from
+ // destructors, or calls to std::terminate are not a SEH exception
+
+ // The terminal handler gets called when:
+ // - std::terminate is called FROM THE TEST RUNNER THREAD
+ // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD
+ original_terminate_handler = std::get_terminate();
+ std::set_terminate([]() DOCTEST_NOEXCEPT {
+ reportFatal("Terminate handler called");
+ if(isDebuggerActive() && !g_cs->no_breaks)
+ DOCTEST_BREAK_INTO_DEBUGGER();
+ std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well
+ });
+
+ // SIGABRT is raised when:
+ // - std::terminate is called FROM A DIFFERENT THREAD
+ // - an exception is thrown from a destructor FROM A DIFFERENT THREAD
+ // - an uncaught exception is thrown FROM A DIFFERENT THREAD
+ prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT {
+ if(signal == SIGABRT) {
+ reportFatal("SIGABRT - Abort (abnormal termination) signal");
+ if(isDebuggerActive() && !g_cs->no_breaks)
+ DOCTEST_BREAK_INTO_DEBUGGER();
+ std::exit(EXIT_FAILURE);
+ }
+ });
+
+ // The following settings are taken from google test, and more
+ // specifically from UnitTest::Run() inside of gtest.cc
+
+ // the user does not want to see pop-up dialogs about crashes
+ prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT |
+ SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
+ // This forces the abort message to go to stderr in all circumstances.
+ prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR);
+ // In the debug version, Visual Studio pops up a separate dialog
+ // offering a choice to debug the aborted program - we want to disable that.
+ prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
+ // In debug mode, the Windows CRT can crash with an assertion over invalid
+ // input (e.g. passing an invalid file descriptor). The default handling
+ // for these assertions is to pop up a dialog and wait for user input.
+ // Instead ask the CRT to dump such assertions to stderr non-interactively.
+ prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+ prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+ }
+
+ static void reset() {
+ if(isSet) {
+ // Unregister handler and restore the old guarantee
+ SetUnhandledExceptionFilter(previousTop);
+ SetThreadStackGuarantee(&guaranteeSize);
+ std::set_terminate(original_terminate_handler);
+ std::signal(SIGABRT, prev_sigabrt_handler);
+ SetErrorMode(prev_error_mode_1);
+ _set_error_mode(prev_error_mode_2);
+ _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
+ static_cast<void>(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode));
+ static_cast<void>(_CrtSetReportFile(_CRT_ASSERT, prev_report_file));
+ isSet = false;
+ }
+ }
+
+ ~FatalConditionHandler() { reset(); }
+
+ private:
+ static UINT prev_error_mode_1;
+ static int prev_error_mode_2;
+ static unsigned int prev_abort_behavior;
+ static int prev_report_mode;
+ static _HFILE prev_report_file;
+ static void (DOCTEST_CDECL *prev_sigabrt_handler)(int);
+ static std::terminate_handler original_terminate_handler;
+ static bool isSet;
+ static ULONG guaranteeSize;
+ static LPTOP_LEVEL_EXCEPTION_FILTER previousTop;
+ };
+
+ UINT FatalConditionHandler::prev_error_mode_1;
+ int FatalConditionHandler::prev_error_mode_2;
+ unsigned int FatalConditionHandler::prev_abort_behavior;
+ int FatalConditionHandler::prev_report_mode;
+ _HFILE FatalConditionHandler::prev_report_file;
+ void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int);
+ std::terminate_handler FatalConditionHandler::original_terminate_handler;
+ bool FatalConditionHandler::isSet = false;
+ ULONG FatalConditionHandler::guaranteeSize = 0;
+ LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr;
+
+#else // DOCTEST_PLATFORM_WINDOWS
+
+ struct SignalDefs
+ {
+ int id;
+ const char* name;
+ };
+ SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"},
+ {SIGILL, "SIGILL - Illegal instruction signal"},
+ {SIGFPE, "SIGFPE - Floating point error signal"},
+ {SIGSEGV, "SIGSEGV - Segmentation violation signal"},
+ {SIGTERM, "SIGTERM - Termination request signal"},
+ {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}};
+
+ struct FatalConditionHandler
+ {
+ static bool isSet;
+ static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)];
+ static stack_t oldSigStack;
+ static size_t altStackSize;
+ static char* altStackMem;
+
+ static void handleSignal(int sig) {
+ const char* name = "<unknown signal>";
+ for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+ SignalDefs& def = signalDefs[i];
+ if(sig == def.id) {
+ name = def.name;
+ break;
+ }
+ }
+ reset();
+ reportFatal(name);
+ raise(sig);
+ }
+
+ static void allocateAltStackMem() {
+ altStackMem = new char[altStackSize];
+ }
+
+ static void freeAltStackMem() {
+ delete[] altStackMem;
+ }
+
+ FatalConditionHandler() {
+ isSet = true;
+ stack_t sigStack;
+ sigStack.ss_sp = altStackMem;
+ sigStack.ss_size = altStackSize;
+ sigStack.ss_flags = 0;
+ sigaltstack(&sigStack, &oldSigStack);
+ struct sigaction sa = {};
+ sa.sa_handler = handleSignal;
+ sa.sa_flags = SA_ONSTACK;
+ for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+ sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
+ }
+ }
+
+ ~FatalConditionHandler() { reset(); }
+ static void reset() {
+ if(isSet) {
+ // Set signals back to previous values -- hopefully nobody overwrote them in the meantime
+ for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+ sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
+ }
+ // Return the old stack
+ sigaltstack(&oldSigStack, nullptr);
+ isSet = false;
+ }
+ }
+ };
+
+ bool FatalConditionHandler::isSet = false;
+ struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {};
+ stack_t FatalConditionHandler::oldSigStack = {};
+ size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ;
+ char* FatalConditionHandler::altStackMem = nullptr;
+
+#endif // DOCTEST_PLATFORM_WINDOWS
+#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
+
+} // namespace
+
+namespace {
+ using namespace detail;
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text)
+#else
+ // TODO: integration with XCode and other IDEs
+#define DOCTEST_OUTPUT_DEBUG_STRING(text)
+#endif // Platform
+
+ void addAssert(assertType::Enum at) {
+ if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
+ g_cs->numAssertsCurrentTest_atomic++;
+ }
+
+ void addFailedAssert(assertType::Enum at) {
+ if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
+ g_cs->numAssertsFailedCurrentTest_atomic++;
+ }
+
+#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH)
+ void reportFatal(const std::string& message) {
+ g_cs->failure_flags |= TestCaseFailureReason::Crash;
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true});
+
+ while (g_cs->subcaseStack.size()) {
+ g_cs->subcaseStack.pop_back();
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
+ }
+
+ g_cs->finalizeTestCaseData();
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);
+ }
+#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
+} // namespace
+
+AssertData::AssertData(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const StringContains& exception_string)
+ : m_test_case(g_cs->currentTest), m_at(at), m_file(file), m_line(line), m_expr(expr),
+ m_failed(true), m_threw(false), m_threw_as(false), m_exception_type(exception_type),
+ m_exception_string(exception_string) {
+#if DOCTEST_MSVC
+ if (m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC
+ ++m_expr;
+#endif // MSVC
+}
+
+namespace detail {
+ ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const String& exception_string)
+ : AssertData(at, file, line, expr, exception_type, exception_string) { }
+
+ ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const Contains& exception_string)
+ : AssertData(at, file, line, expr, exception_type, exception_string) { }
+
+ void ResultBuilder::setResult(const Result& res) {
+ m_decomp = res.m_decomp;
+ m_failed = !res.m_passed;
+ }
+
+ void ResultBuilder::translateException() {
+ m_threw = true;
+ m_exception = translateActiveException();
+ }
+
+ bool ResultBuilder::log() {
+ if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
+ m_failed = !m_threw;
+ } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT
+ m_failed = !m_threw_as || !m_exception_string.check(m_exception);
+ } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
+ m_failed = !m_threw_as;
+ } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
+ m_failed = !m_exception_string.check(m_exception);
+ } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
+ m_failed = m_threw;
+ }
+
+ if(m_exception.size())
+ m_exception = "\"" + m_exception + "\"";
+
+ if(is_running_in_test) {
+ addAssert(m_at);
+ DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this);
+
+ if(m_failed)
+ addFailedAssert(m_at);
+ } else if(m_failed) {
+ failed_out_of_a_testing_context(*this);
+ }
+
+ return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks &&
+ (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger
+ }
+
+ void ResultBuilder::react() const {
+ if(m_failed && checkIfShouldThrow(m_at))
+ throwException();
+ }
+
+ void failed_out_of_a_testing_context(const AssertData& ad) {
+ if(g_cs->ah)
+ g_cs->ah(ad);
+ else
+ std::abort();
+ }
+
+ bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr,
+ const Result& result) {
+ bool failed = !result.m_passed;
+
+ // ###################################################################################
+ // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+ // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+ // ###################################################################################
+ DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp);
+ DOCTEST_ASSERT_IN_TESTS(result.m_decomp);
+ return !failed;
+ }
+
+ MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) {
+ m_stream = tlssPush();
+ m_file = file;
+ m_line = line;
+ m_severity = severity;
+ }
+
+ MessageBuilder::~MessageBuilder() {
+ if (!logged)
+ tlssPop();
+ }
+
+ DOCTEST_DEFINE_INTERFACE(IExceptionTranslator)
+
+ bool MessageBuilder::log() {
+ if (!logged) {
+ m_string = tlssPop();
+ logged = true;
+ }
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this);
+
+ const bool isWarn = m_severity & assertType::is_warn;
+
+ // warn is just a message in this context so we don't treat it as an assert
+ if(!isWarn) {
+ addAssert(m_severity);
+ addFailedAssert(m_severity);
+ }
+
+ return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn &&
+ (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger
+ }
+
+ void MessageBuilder::react() {
+ if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional
+ throwException();
+ }
+} // namespace detail
+namespace {
+ using namespace detail;
+
+ // clang-format off
+
+// =================================================================================================
+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp
+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.
+// =================================================================================================
+
+ class XmlEncode {
+ public:
+ enum ForWhat { ForTextNodes, ForAttributes };
+
+ XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes );
+
+ void encodeTo( std::ostream& os ) const;
+
+ friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode );
+
+ private:
+ std::string m_str;
+ ForWhat m_forWhat;
+ };
+
+ class XmlWriter {
+ public:
+
+ class ScopedElement {
+ public:
+ ScopedElement( XmlWriter* writer );
+
+ ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT;
+ ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT;
+
+ ~ScopedElement();
+
+ ScopedElement& writeText( std::string const& text, bool indent = true );
+
+ template<typename T>
+ ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
+ m_writer->writeAttribute( name, attribute );
+ return *this;
+ }
+
+ private:
+ mutable XmlWriter* m_writer = nullptr;
+ };
+
+ XmlWriter( std::ostream& os = std::cout );
+ ~XmlWriter();
+
+ XmlWriter( XmlWriter const& ) = delete;
+ XmlWriter& operator=( XmlWriter const& ) = delete;
+
+ XmlWriter& startElement( std::string const& name );
+
+ ScopedElement scopedElement( std::string const& name );
+
+ XmlWriter& endElement();
+
+ XmlWriter& writeAttribute( std::string const& name, std::string const& attribute );
+
+ XmlWriter& writeAttribute( std::string const& name, const char* attribute );
+
+ XmlWriter& writeAttribute( std::string const& name, bool attribute );
+
+ template<typename T>
+ XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
+ std::stringstream rss;
+ rss << attribute;
+ return writeAttribute( name, rss.str() );
+ }
+
+ XmlWriter& writeText( std::string const& text, bool indent = true );
+
+ //XmlWriter& writeComment( std::string const& text );
+
+ //void writeStylesheetRef( std::string const& url );
+
+ //XmlWriter& writeBlankLine();
+
+ void ensureTagClosed();
+
+ void writeDeclaration();
+
+ private:
+
+ void newlineIfNecessary();
+
+ bool m_tagIsOpen = false;
+ bool m_needsNewline = false;
+ std::vector<std::string> m_tags;
+ std::string m_indent;
+ std::ostream& m_os;
+ };
+
+// =================================================================================================
+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp
+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.
+// =================================================================================================
+
+using uchar = unsigned char;
+
+namespace {
+
+ size_t trailingBytes(unsigned char c) {
+ if ((c & 0xE0) == 0xC0) {
+ return 2;
+ }
+ if ((c & 0xF0) == 0xE0) {
+ return 3;
+ }
+ if ((c & 0xF8) == 0xF0) {
+ return 4;
+ }
+ DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+ }
+
+ uint32_t headerValue(unsigned char c) {
+ if ((c & 0xE0) == 0xC0) {
+ return c & 0x1F;
+ }
+ if ((c & 0xF0) == 0xE0) {
+ return c & 0x0F;
+ }
+ if ((c & 0xF8) == 0xF0) {
+ return c & 0x07;
+ }
+ DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+ }
+
+ void hexEscapeChar(std::ostream& os, unsigned char c) {
+ std::ios_base::fmtflags f(os.flags());
+ os << "\\x"
+ << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
+ << static_cast<int>(c);
+ os.flags(f);
+ }
+
+} // anonymous namespace
+
+ XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
+ : m_str( str ),
+ m_forWhat( forWhat )
+ {}
+
+ void XmlEncode::encodeTo( std::ostream& os ) const {
+ // Apostrophe escaping not necessary if we always use " to write attributes
+ // (see: https://www.w3.org/TR/xml/#syntax)
+
+ for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
+ uchar c = m_str[idx];
+ switch (c) {
+ case '<': os << "&lt;"; break;
+ case '&': os << "&amp;"; break;
+
+ case '>':
+ // See: https://www.w3.org/TR/xml/#syntax
+ if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
+ os << "&gt;";
+ else
+ os << c;
+ break;
+
+ case '\"':
+ if (m_forWhat == ForAttributes)
+ os << "&quot;";
+ else
+ os << c;
+ break;
+
+ default:
+ // Check for control characters and invalid utf-8
+
+ // Escape control characters in standard ascii
+ // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+ if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ // Plain ASCII: Write it to stream
+ if (c < 0x7F) {
+ os << c;
+ break;
+ }
+
+ // UTF-8 territory
+ // Check if the encoding is valid and if it is not, hex escape bytes.
+ // Important: We do not check the exact decoded values for validity, only the encoding format
+ // First check that this bytes is a valid lead byte:
+ // This means that it is not encoded as 1111 1XXX
+ // Or as 10XX XXXX
+ if (c < 0xC0 ||
+ c >= 0xF8) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ auto encBytes = trailingBytes(c);
+ // Are there enough bytes left to avoid accessing out-of-bounds memory?
+ if (idx + encBytes - 1 >= m_str.size()) {
+ hexEscapeChar(os, c);
+ break;
+ }
+ // The header is valid, check data
+ // The next encBytes bytes must together be a valid utf-8
+ // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
+ bool valid = true;
+ uint32_t value = headerValue(c);
+ for (std::size_t n = 1; n < encBytes; ++n) {
+ uchar nc = m_str[idx + n];
+ valid &= ((nc & 0xC0) == 0x80);
+ value = (value << 6) | (nc & 0x3F);
+ }
+
+ if (
+ // Wrong bit pattern of following bytes
+ (!valid) ||
+ // Overlong encodings
+ (value < 0x80) ||
+ ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant
+ (0x800 < value && value < 0x10000 && encBytes > 3) ||
+ // Encoded value out of range
+ (value >= 0x110000)
+ ) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ // If we got here, this is in fact a valid(ish) utf-8 sequence
+ for (std::size_t n = 0; n < encBytes; ++n) {
+ os << m_str[idx + n];
+ }
+ idx += encBytes - 1;
+ break;
+ }
+ }
+ }
+
+ std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
+ xmlEncode.encodeTo( os );
+ return os;
+ }
+
+ XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer )
+ : m_writer( writer )
+ {}
+
+ XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT
+ : m_writer( other.m_writer ){
+ other.m_writer = nullptr;
+ }
+ XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT {
+ if ( m_writer ) {
+ m_writer->endElement();
+ }
+ m_writer = other.m_writer;
+ other.m_writer = nullptr;
+ return *this;
+ }
+
+
+ XmlWriter::ScopedElement::~ScopedElement() {
+ if( m_writer )
+ m_writer->endElement();
+ }
+
+ XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) {
+ m_writer->writeText( text, indent );
+ return *this;
+ }
+
+ XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )
+ {
+ // writeDeclaration(); // called explicitly by the reporters that use the writer class - see issue #627
+ }
+
+ XmlWriter::~XmlWriter() {
+ while( !m_tags.empty() )
+ endElement();
+ }
+
+ XmlWriter& XmlWriter::startElement( std::string const& name ) {
+ ensureTagClosed();
+ newlineIfNecessary();
+ m_os << m_indent << '<' << name;
+ m_tags.push_back( name );
+ m_indent += " ";
+ m_tagIsOpen = true;
+ return *this;
+ }
+
+ XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) {
+ ScopedElement scoped( this );
+ startElement( name );
+ return scoped;
+ }
+
+ XmlWriter& XmlWriter::endElement() {
+ newlineIfNecessary();
+ m_indent = m_indent.substr( 0, m_indent.size()-2 );
+ if( m_tagIsOpen ) {
+ m_os << "/>";
+ m_tagIsOpen = false;
+ }
+ else {
+ m_os << m_indent << "</" << m_tags.back() << ">";
+ }
+ m_os << std::endl;
+ m_tags.pop_back();
+ return *this;
+ }
+
+ XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) {
+ if( !name.empty() && !attribute.empty() )
+ m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+ return *this;
+ }
+
+ XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) {
+ if( !name.empty() && attribute && attribute[0] != '\0' )
+ m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+ return *this;
+ }
+
+ XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {
+ m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
+ return *this;
+ }
+
+ XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) {
+ if( !text.empty() ){
+ bool tagWasOpen = m_tagIsOpen;
+ ensureTagClosed();
+ if( tagWasOpen && indent )
+ m_os << m_indent;
+ m_os << XmlEncode( text );
+ m_needsNewline = true;
+ }
+ return *this;
+ }
+
+ //XmlWriter& XmlWriter::writeComment( std::string const& text ) {
+ // ensureTagClosed();
+ // m_os << m_indent << "<!--" << text << "-->";
+ // m_needsNewline = true;
+ // return *this;
+ //}
+
+ //void XmlWriter::writeStylesheetRef( std::string const& url ) {
+ // m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";
+ //}
+
+ //XmlWriter& XmlWriter::writeBlankLine() {
+ // ensureTagClosed();
+ // m_os << '\n';
+ // return *this;
+ //}
+
+ void XmlWriter::ensureTagClosed() {
+ if( m_tagIsOpen ) {
+ m_os << ">" << std::endl;
+ m_tagIsOpen = false;
+ }
+ }
+
+ void XmlWriter::writeDeclaration() {
+ m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ }
+
+ void XmlWriter::newlineIfNecessary() {
+ if( m_needsNewline ) {
+ m_os << std::endl;
+ m_needsNewline = false;
+ }
+ }
+
+// =================================================================================================
+// End of copy-pasted code from Catch
+// =================================================================================================
+
+ // clang-format on
+
+ struct XmlReporter : public IReporter
+ {
+ XmlWriter xml;
+ DOCTEST_DECLARE_MUTEX(mutex)
+
+ // caching pointers/references to objects of these types - safe to do
+ const ContextOptions& opt;
+ const TestCaseData* tc = nullptr;
+
+ XmlReporter(const ContextOptions& co)
+ : xml(*co.cout)
+ , opt(co) {}
+
+ void log_contexts() {
+ int num_contexts = get_num_active_contexts();
+ if(num_contexts) {
+ auto contexts = get_active_contexts();
+ std::stringstream ss;
+ for(int i = 0; i < num_contexts; ++i) {
+ contexts[i]->stringify(&ss);
+ xml.scopedElement("Info").writeText(ss.str());
+ ss.str("");
+ }
+ }
+ }
+
+ unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }
+
+ void test_case_start_impl(const TestCaseData& in) {
+ bool open_ts_tag = false;
+ if(tc != nullptr) { // we have already opened a test suite
+ if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) {
+ xml.endElement();
+ open_ts_tag = true;
+ }
+ }
+ else {
+ open_ts_tag = true; // first test case ==> first test suite
+ }
+
+ if(open_ts_tag) {
+ xml.startElement("TestSuite");
+ xml.writeAttribute("name", in.m_test_suite);
+ }
+
+ tc = &in;
+ xml.startElement("TestCase")
+ .writeAttribute("name", in.m_name)
+ .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str()))
+ .writeAttribute("line", line(in.m_line))
+ .writeAttribute("description", in.m_description);
+
+ if(Approx(in.m_timeout) != 0)
+ xml.writeAttribute("timeout", in.m_timeout);
+ if(in.m_may_fail)
+ xml.writeAttribute("may_fail", true);
+ if(in.m_should_fail)
+ xml.writeAttribute("should_fail", true);
+ }
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+ // =========================================================================================
+
+ void report_query(const QueryData& in) override {
+ test_run_start();
+ if(opt.list_reporters) {
+ for(auto& curr : getListeners())
+ xml.scopedElement("Listener")
+ .writeAttribute("priority", curr.first.first)
+ .writeAttribute("name", curr.first.second);
+ for(auto& curr : getReporters())
+ xml.scopedElement("Reporter")
+ .writeAttribute("priority", curr.first.first)
+ .writeAttribute("name", curr.first.second);
+ } else if(opt.count || opt.list_test_cases) {
+ for(unsigned i = 0; i < in.num_data; ++i) {
+ xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name)
+ .writeAttribute("testsuite", in.data[i]->m_test_suite)
+ .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str()))
+ .writeAttribute("line", line(in.data[i]->m_line))
+ .writeAttribute("skipped", in.data[i]->m_skip);
+ }
+ xml.scopedElement("OverallResultsTestCases")
+ .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);
+ } else if(opt.list_test_suites) {
+ for(unsigned i = 0; i < in.num_data; ++i)
+ xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite);
+ xml.scopedElement("OverallResultsTestCases")
+ .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);
+ xml.scopedElement("OverallResultsTestSuites")
+ .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters);
+ }
+ xml.endElement();
+ }
+
+ void test_run_start() override {
+ xml.writeDeclaration();
+
+ // remove .exe extension - mainly to have the same output on UNIX and Windows
+ std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ if(binary_name.rfind(".exe") != std::string::npos)
+ binary_name = binary_name.substr(0, binary_name.length() - 4);
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+ xml.startElement("doctest").writeAttribute("binary", binary_name);
+ if(opt.no_version == false)
+ xml.writeAttribute("version", DOCTEST_VERSION_STR);
+
+ // only the consequential ones (TODO: filters)
+ xml.scopedElement("Options")
+ .writeAttribute("order_by", opt.order_by.c_str())
+ .writeAttribute("rand_seed", opt.rand_seed)
+ .writeAttribute("first", opt.first)
+ .writeAttribute("last", opt.last)
+ .writeAttribute("abort_after", opt.abort_after)
+ .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels)
+ .writeAttribute("case_sensitive", opt.case_sensitive)
+ .writeAttribute("no_throw", opt.no_throw)
+ .writeAttribute("no_skip", opt.no_skip);
+ }
+
+ void test_run_end(const TestRunStats& p) override {
+ if(tc) // the TestSuite tag - only if there has been at least 1 test case
+ xml.endElement();
+
+ xml.scopedElement("OverallResultsAsserts")
+ .writeAttribute("successes", p.numAsserts - p.numAssertsFailed)
+ .writeAttribute("failures", p.numAssertsFailed);
+
+ xml.startElement("OverallResultsTestCases")
+ .writeAttribute("successes",
+ p.numTestCasesPassingFilters - p.numTestCasesFailed)
+ .writeAttribute("failures", p.numTestCasesFailed);
+ if(opt.no_skipped_summary == false)
+ xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters);
+ xml.endElement();
+
+ xml.endElement();
+ }
+
+ void test_case_start(const TestCaseData& in) override {
+ test_case_start_impl(in);
+ xml.ensureTagClosed();
+ }
+
+ void test_case_reenter(const TestCaseData&) override {}
+
+ void test_case_end(const CurrentTestCaseStats& st) override {
+ xml.startElement("OverallResultsAsserts")
+ .writeAttribute("successes",
+ st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest)
+ .writeAttribute("failures", st.numAssertsFailedCurrentTest)
+ .writeAttribute("test_case_success", st.testCaseSuccess);
+ if(opt.duration)
+ xml.writeAttribute("duration", st.seconds);
+ if(tc->m_expected_failures)
+ xml.writeAttribute("expected_failures", tc->m_expected_failures);
+ xml.endElement();
+
+ xml.endElement();
+ }
+
+ void test_case_exception(const TestCaseException& e) override {
+ DOCTEST_LOCK_MUTEX(mutex)
+
+ xml.scopedElement("Exception")
+ .writeAttribute("crash", e.is_crash)
+ .writeText(e.error_string.c_str());
+ }
+
+ void subcase_start(const SubcaseSignature& in) override {
+ xml.startElement("SubCase")
+ .writeAttribute("name", in.m_name)
+ .writeAttribute("filename", skipPathFromFilename(in.m_file))
+ .writeAttribute("line", line(in.m_line));
+ xml.ensureTagClosed();
+ }
+
+ void subcase_end() override { xml.endElement(); }
+
+ void log_assert(const AssertData& rb) override {
+ if(!rb.m_failed && !opt.success)
+ return;
+
+ DOCTEST_LOCK_MUTEX(mutex)
+
+ xml.startElement("Expression")
+ .writeAttribute("success", !rb.m_failed)
+ .writeAttribute("type", assertString(rb.m_at))
+ .writeAttribute("filename", skipPathFromFilename(rb.m_file))
+ .writeAttribute("line", line(rb.m_line));
+
+ xml.scopedElement("Original").writeText(rb.m_expr);
+
+ if(rb.m_threw)
+ xml.scopedElement("Exception").writeText(rb.m_exception.c_str());
+
+ if(rb.m_at & assertType::is_throws_as)
+ xml.scopedElement("ExpectedException").writeText(rb.m_exception_type);
+ if(rb.m_at & assertType::is_throws_with)
+ xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string.c_str());
+ if((rb.m_at & assertType::is_normal) && !rb.m_threw)
+ xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str());
+
+ log_contexts();
+
+ xml.endElement();
+ }
+
+ void log_message(const MessageData& mb) override {
+ DOCTEST_LOCK_MUTEX(mutex)
+
+ xml.startElement("Message")
+ .writeAttribute("type", failureString(mb.m_severity))
+ .writeAttribute("filename", skipPathFromFilename(mb.m_file))
+ .writeAttribute("line", line(mb.m_line));
+
+ xml.scopedElement("Text").writeText(mb.m_string.c_str());
+
+ log_contexts();
+
+ xml.endElement();
+ }
+
+ void test_case_skipped(const TestCaseData& in) override {
+ if(opt.no_skipped_summary == false) {
+ test_case_start_impl(in);
+ xml.writeAttribute("skipped", "true");
+ xml.endElement();
+ }
+ }
+ };
+
+ DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter);
+
+ void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) {
+ if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) ==
+ 0) //!OCLINT bitwise operator in conditional
+ s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) "
+ << Color::None;
+
+ if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
+ s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n";
+ } else if((rb.m_at & assertType::is_throws_as) &&
+ (rb.m_at & assertType::is_throws_with)) { //!OCLINT
+ s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
+ << rb.m_exception_string.c_str()
+ << "\", " << rb.m_exception_type << " ) " << Color::None;
+ if(rb.m_threw) {
+ if(!rb.m_failed) {
+ s << "threw as expected!\n";
+ } else {
+ s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n";
+ }
+ } else {
+ s << "did NOT throw at all!\n";
+ }
+ } else if(rb.m_at &
+ assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
+ s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", "
+ << rb.m_exception_type << " ) " << Color::None
+ << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" :
+ "threw a DIFFERENT exception: ") :
+ "did NOT throw at all!")
+ << Color::Cyan << rb.m_exception << "\n";
+ } else if(rb.m_at &
+ assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
+ s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
+ << rb.m_exception_string.c_str()
+ << "\" ) " << Color::None
+ << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" :
+ "threw a DIFFERENT exception: ") :
+ "did NOT throw at all!")
+ << Color::Cyan << rb.m_exception << "\n";
+ } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
+ s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan
+ << rb.m_exception << "\n";
+ } else {
+ s << (rb.m_threw ? "THREW exception: " :
+ (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n"));
+ if(rb.m_threw)
+ s << rb.m_exception << "\n";
+ else
+ s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n";
+ }
+ }
+
+ // TODO:
+ // - log_message()
+ // - respond to queries
+ // - honor remaining options
+ // - more attributes in tags
+ struct JUnitReporter : public IReporter
+ {
+ XmlWriter xml;
+ DOCTEST_DECLARE_MUTEX(mutex)
+ Timer timer;
+ std::vector<String> deepestSubcaseStackNames;
+
+ struct JUnitTestCaseData
+ {
+ static std::string getCurrentTimestamp() {
+ // Beware, this is not reentrant because of backward compatibility issues
+ // Also, UTC only, again because of backward compatibility (%z is C++11)
+ time_t rawtime;
+ std::time(&rawtime);
+ auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
+
+ std::tm timeInfo;
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ gmtime_s(&timeInfo, &rawtime);
+#else // DOCTEST_PLATFORM_WINDOWS
+ gmtime_r(&rawtime, &timeInfo);
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+ char timeStamp[timeStampSize];
+ const char* const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+ std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
+ return std::string(timeStamp);
+ }
+
+ struct JUnitTestMessage
+ {
+ JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details)
+ : message(_message), type(_type), details(_details) {}
+
+ JUnitTestMessage(const std::string& _message, const std::string& _details)
+ : message(_message), type(), details(_details) {}
+
+ std::string message, type, details;
+ };
+
+ struct JUnitTestCase
+ {
+ JUnitTestCase(const std::string& _classname, const std::string& _name)
+ : classname(_classname), name(_name), time(0), failures() {}
+
+ std::string classname, name;
+ double time;
+ std::vector<JUnitTestMessage> failures, errors;
+ };
+
+ void add(const std::string& classname, const std::string& name) {
+ testcases.emplace_back(classname, name);
+ }
+
+ void appendSubcaseNamesToLastTestcase(std::vector<String> nameStack) {
+ for(auto& curr: nameStack)
+ if(curr.size())
+ testcases.back().name += std::string("/") + curr.c_str();
+ }
+
+ void addTime(double time) {
+ if(time < 1e-4)
+ time = 0;
+ testcases.back().time = time;
+ totalSeconds += time;
+ }
+
+ void addFailure(const std::string& message, const std::string& type, const std::string& details) {
+ testcases.back().failures.emplace_back(message, type, details);
+ ++totalFailures;
+ }
+
+ void addError(const std::string& message, const std::string& details) {
+ testcases.back().errors.emplace_back(message, details);
+ ++totalErrors;
+ }
+
+ std::vector<JUnitTestCase> testcases;
+ double totalSeconds = 0;
+ int totalErrors = 0, totalFailures = 0;
+ };
+
+ JUnitTestCaseData testCaseData;
+
+ // caching pointers/references to objects of these types - safe to do
+ const ContextOptions& opt;
+ const TestCaseData* tc = nullptr;
+
+ JUnitReporter(const ContextOptions& co)
+ : xml(*co.cout)
+ , opt(co) {}
+
+ unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+ // =========================================================================================
+
+ void report_query(const QueryData&) override {
+ xml.writeDeclaration();
+ }
+
+ void test_run_start() override {
+ xml.writeDeclaration();
+ }
+
+ void test_run_end(const TestRunStats& p) override {
+ // remove .exe extension - mainly to have the same output on UNIX and Windows
+ std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ if(binary_name.rfind(".exe") != std::string::npos)
+ binary_name = binary_name.substr(0, binary_name.length() - 4);
+#endif // DOCTEST_PLATFORM_WINDOWS
+ xml.startElement("testsuites");
+ xml.startElement("testsuite").writeAttribute("name", binary_name)
+ .writeAttribute("errors", testCaseData.totalErrors)
+ .writeAttribute("failures", testCaseData.totalFailures)
+ .writeAttribute("tests", p.numAsserts);
+ if(opt.no_time_in_output == false) {
+ xml.writeAttribute("time", testCaseData.totalSeconds);
+ xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp());
+ }
+ if(opt.no_version == false)
+ xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR);
+
+ for(const auto& testCase : testCaseData.testcases) {
+ xml.startElement("testcase")
+ .writeAttribute("classname", testCase.classname)
+ .writeAttribute("name", testCase.name);
+ if(opt.no_time_in_output == false)
+ xml.writeAttribute("time", testCase.time);
+ // This is not ideal, but it should be enough to mimic gtest's junit output.
+ xml.writeAttribute("status", "run");
+
+ for(const auto& failure : testCase.failures) {
+ xml.scopedElement("failure")
+ .writeAttribute("message", failure.message)
+ .writeAttribute("type", failure.type)
+ .writeText(failure.details, false);
+ }
+
+ for(const auto& error : testCase.errors) {
+ xml.scopedElement("error")
+ .writeAttribute("message", error.message)
+ .writeText(error.details);
+ }
+
+ xml.endElement();
+ }
+ xml.endElement();
+ xml.endElement();
+ }
+
+ void test_case_start(const TestCaseData& in) override {
+ testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name);
+ timer.start();
+ }
+
+ void test_case_reenter(const TestCaseData& in) override {
+ testCaseData.addTime(timer.getElapsedSeconds());
+ testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames);
+ deepestSubcaseStackNames.clear();
+
+ timer.start();
+ testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name);
+ }
+
+ void test_case_end(const CurrentTestCaseStats&) override {
+ testCaseData.addTime(timer.getElapsedSeconds());
+ testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames);
+ deepestSubcaseStackNames.clear();
+ }
+
+ void test_case_exception(const TestCaseException& e) override {
+ DOCTEST_LOCK_MUTEX(mutex)
+ testCaseData.addError("exception", e.error_string.c_str());
+ }
+
+ void subcase_start(const SubcaseSignature& in) override {
+ deepestSubcaseStackNames.push_back(in.m_name);
+ }
+
+ void subcase_end() override {}
+
+ void log_assert(const AssertData& rb) override {
+ if(!rb.m_failed) // report only failures & ignore the `success` option
+ return;
+
+ DOCTEST_LOCK_MUTEX(mutex)
+
+ std::ostringstream os;
+ os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(")
+ << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl;
+
+ fulltext_log_assert_to_stream(os, rb);
+ log_contexts(os);
+ testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str());
+ }
+
+ void log_message(const MessageData&) override {}
+
+ void test_case_skipped(const TestCaseData&) override {}
+
+ void log_contexts(std::ostringstream& s) {
+ int num_contexts = get_num_active_contexts();
+ if(num_contexts) {
+ auto contexts = get_active_contexts();
+
+ s << " logged: ";
+ for(int i = 0; i < num_contexts; ++i) {
+ s << (i == 0 ? "" : " ");
+ contexts[i]->stringify(&s);
+ s << std::endl;
+ }
+ }
+ }
+ };
+
+ DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter);
+
+ struct Whitespace
+ {
+ int nrSpaces;
+ explicit Whitespace(int nr)
+ : nrSpaces(nr) {}
+ };
+
+ std::ostream& operator<<(std::ostream& out, const Whitespace& ws) {
+ if(ws.nrSpaces != 0)
+ out << std::setw(ws.nrSpaces) << ' ';
+ return out;
+ }
+
+ struct ConsoleReporter : public IReporter
+ {
+ std::ostream& s;
+ bool hasLoggedCurrentTestStart;
+ std::vector<SubcaseSignature> subcasesStack;
+ size_t currentSubcaseLevel;
+ DOCTEST_DECLARE_MUTEX(mutex)
+
+ // caching pointers/references to objects of these types - safe to do
+ const ContextOptions& opt;
+ const TestCaseData* tc;
+
+ ConsoleReporter(const ContextOptions& co)
+ : s(*co.cout)
+ , opt(co) {}
+
+ ConsoleReporter(const ContextOptions& co, std::ostream& ostr)
+ : s(ostr)
+ , opt(co) {}
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE
+ // =========================================================================================
+
+ void separator_to_stream() {
+ s << Color::Yellow
+ << "==============================================================================="
+ "\n";
+ }
+
+ const char* getSuccessOrFailString(bool success, assertType::Enum at,
+ const char* success_str) {
+ if(success)
+ return success_str;
+ return failureString(at);
+ }
+
+ Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) {
+ return success ? Color::BrightGreen :
+ (at & assertType::is_warn) ? Color::Yellow : Color::Red;
+ }
+
+ void successOrFailColoredStringToStream(bool success, assertType::Enum at,
+ const char* success_str = "SUCCESS") {
+ s << getSuccessOrFailColor(success, at)
+ << getSuccessOrFailString(success, at, success_str) << ": ";
+ }
+
+ void log_contexts() {
+ int num_contexts = get_num_active_contexts();
+ if(num_contexts) {
+ auto contexts = get_active_contexts();
+
+ s << Color::None << " logged: ";
+ for(int i = 0; i < num_contexts; ++i) {
+ s << (i == 0 ? "" : " ");
+ contexts[i]->stringify(&s);
+ s << "\n";
+ }
+ }
+
+ s << "\n";
+ }
+
+ // this was requested to be made virtual so users could override it
+ virtual void file_line_to_stream(const char* file, int line,
+ const char* tail = "") {
+ s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(")
+ << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option
+ << (opt.gnu_file_line ? ":" : "):") << tail;
+ }
+
+ void logTestStart() {
+ if(hasLoggedCurrentTestStart)
+ return;
+
+ separator_to_stream();
+ file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n");
+ if(tc->m_description)
+ s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n";
+ if(tc->m_test_suite && tc->m_test_suite[0] != '\0')
+ s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";
+ if(strncmp(tc->m_name, " Scenario:", 11) != 0)
+ s << Color::Yellow << "TEST CASE: ";
+ s << Color::None << tc->m_name << "\n";
+
+ for(size_t i = 0; i < currentSubcaseLevel; ++i) {
+ if(subcasesStack[i].m_name[0] != '\0')
+ s << " " << subcasesStack[i].m_name << "\n";
+ }
+
+ if(currentSubcaseLevel != subcasesStack.size()) {
+ s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None;
+ for(size_t i = 0; i < subcasesStack.size(); ++i) {
+ if(subcasesStack[i].m_name[0] != '\0')
+ s << " " << subcasesStack[i].m_name << "\n";
+ }
+ }
+
+ s << "\n";
+
+ hasLoggedCurrentTestStart = true;
+ }
+
+ void printVersion() {
+ if(opt.no_version == false)
+ s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \""
+ << DOCTEST_VERSION_STR << "\"\n";
+ }
+
+ void printIntro() {
+ if(opt.no_intro == false) {
+ printVersion();
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n";
+ }
+ }
+
+ void printHelp() {
+ int sizePrefixDisplay = static_cast<int>(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY));
+ printVersion();
+ // clang-format off
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n";
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "filter values: \"str1,str2,str3\" (comma separated strings)\n";
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "filters use wildcards for matching strings\n";
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "something passes a filter if any of the strings in a filter matches\n";
+#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n";
+#endif
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "Query flags - the program quits after them. Available:\n\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h "
+ << Whitespace(sizePrefixDisplay*0) << "prints this message\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version "
+ << Whitespace(sizePrefixDisplay*1) << "prints the version\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count "
+ << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases "
+ << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites "
+ << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters "
+ << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n";
+ // ================================================================================== << 79
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "The available <int>/<string> options/filters are:\n\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<string> "
+ << Whitespace(sizePrefixDisplay*1) << "output filename\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string> "
+ << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n";
+ s << Whitespace(sizePrefixDisplay*3) << " <string> - [file/suite/name/rand/none]\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n";
+ s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n";
+ s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "stop after <int> failed assertions\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "apply filters for the first <int> levels\n";
+ s << Color::Cyan << "\n[doctest] " << Color::None;
+ s << "Bool options - can be used like flags and true is assumed. Available:\n\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "no console output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n";
+ // ================================================================================== << 79
+ // clang-format on
+
+ s << Color::Cyan << "\n[doctest] " << Color::None;
+ s << "for more information visit the project documentation\n\n";
+ }
+
+ void printRegisteredReporters() {
+ printVersion();
+ auto printReporters = [this] (const reporterMap& reporters, const char* type) {
+ if(reporters.size()) {
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n";
+ for(auto& curr : reporters)
+ s << "priority: " << std::setw(5) << curr.first.first
+ << " name: " << curr.first.second << "\n";
+ }
+ };
+ printReporters(getListeners(), "listeners");
+ printReporters(getReporters(), "reporters");
+ }
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+ // =========================================================================================
+
+ void report_query(const QueryData& in) override {
+ if(opt.version) {
+ printVersion();
+ } else if(opt.help) {
+ printHelp();
+ } else if(opt.list_reporters) {
+ printRegisteredReporters();
+ } else if(opt.count || opt.list_test_cases) {
+ if(opt.list_test_cases) {
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "listing all test case names\n";
+ separator_to_stream();
+ }
+
+ for(unsigned i = 0; i < in.num_data; ++i)
+ s << Color::None << in.data[i]->m_name << "\n";
+
+ separator_to_stream();
+
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_cs->numTestCasesPassingFilters << "\n";
+
+ } else if(opt.list_test_suites) {
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
+ separator_to_stream();
+
+ for(unsigned i = 0; i < in.num_data; ++i)
+ s << Color::None << in.data[i]->m_test_suite << "\n";
+
+ separator_to_stream();
+
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_cs->numTestCasesPassingFilters << "\n";
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "test suites with unskipped test cases passing the current filters: "
+ << g_cs->numTestSuitesPassingFilters << "\n";
+ }
+ }
+
+ void test_run_start() override {
+ if(!opt.minimal)
+ printIntro();
+ }
+
+ void test_run_end(const TestRunStats& p) override {
+ if(opt.minimal && p.numTestCasesFailed == 0)
+ return;
+
+ separator_to_stream();
+ s << std::dec;
+
+ auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1)));
+ auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1)));
+ auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1)));
+ const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
+ s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth)
+ << p.numTestCasesPassingFilters << " | "
+ << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None :
+ Color::Green)
+ << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed"
+ << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None)
+ << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |";
+ if(opt.no_skipped_summary == false) {
+ const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters;
+ s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped
+ << " skipped" << Color::None;
+ }
+ s << "\n";
+ s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth)
+ << p.numAsserts << " | "
+ << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
+ << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None
+ << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth)
+ << p.numAssertsFailed << " failed" << Color::None << " |\n";
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green)
+ << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;
+ }
+
+ void test_case_start(const TestCaseData& in) override {
+ hasLoggedCurrentTestStart = false;
+ tc = &in;
+ subcasesStack.clear();
+ currentSubcaseLevel = 0;
+ }
+
+ void test_case_reenter(const TestCaseData&) override {
+ subcasesStack.clear();
+ }
+
+ void test_case_end(const CurrentTestCaseStats& st) override {
+ if(tc->m_no_output)
+ return;
+
+ // log the preamble of the test case only if there is something
+ // else to print - something other than that an assert has failed
+ if(opt.duration ||
+ (st.failure_flags && st.failure_flags != static_cast<int>(TestCaseFailureReason::AssertFailure)))
+ logTestStart();
+
+ if(opt.duration)
+ s << Color::None << std::setprecision(6) << std::fixed << st.seconds
+ << " s: " << tc->m_name << "\n";
+
+ if(st.failure_flags & TestCaseFailureReason::Timeout)
+ s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6)
+ << std::fixed << tc->m_timeout << "!\n";
+
+ if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) {
+ s << Color::Red << "Should have failed but didn't! Marking it as failed!\n";
+ } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) {
+ s << Color::Yellow << "Failed as expected so marking it as not failed\n";
+ } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) {
+ s << Color::Yellow << "Allowed to fail so marking it as not failed\n";
+ } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) {
+ s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures
+ << " times so marking it as failed!\n";
+ } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) {
+ s << Color::Yellow << "Failed exactly " << tc->m_expected_failures
+ << " times as expected so marking it as not failed!\n";
+ }
+ if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) {
+ s << Color::Red << "Aborting - too many failed asserts!\n";
+ }
+ s << Color::None; // lgtm [cpp/useless-expression]
+ }
+
+ void test_case_exception(const TestCaseException& e) override {
+ DOCTEST_LOCK_MUTEX(mutex)
+ if(tc->m_no_output)
+ return;
+
+ logTestStart();
+
+ file_line_to_stream(tc->m_file.c_str(), tc->m_line, " ");
+ successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require :
+ assertType::is_check);
+ s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ")
+ << Color::Cyan << e.error_string << "\n";
+
+ int num_stringified_contexts = get_num_stringified_contexts();
+ if(num_stringified_contexts) {
+ auto stringified_contexts = get_stringified_contexts();
+ s << Color::None << " logged: ";
+ for(int i = num_stringified_contexts; i > 0; --i) {
+ s << (i == num_stringified_contexts ? "" : " ")
+ << stringified_contexts[i - 1] << "\n";
+ }
+ }
+ s << "\n" << Color::None;
+ }
+
+ void subcase_start(const SubcaseSignature& subc) override {
+ subcasesStack.push_back(subc);
+ ++currentSubcaseLevel;
+ hasLoggedCurrentTestStart = false;
+ }
+
+ void subcase_end() override {
+ --currentSubcaseLevel;
+ hasLoggedCurrentTestStart = false;
+ }
+
+ void log_assert(const AssertData& rb) override {
+ if((!rb.m_failed && !opt.success) || tc->m_no_output)
+ return;
+
+ DOCTEST_LOCK_MUTEX(mutex)
+
+ logTestStart();
+
+ file_line_to_stream(rb.m_file, rb.m_line, " ");
+ successOrFailColoredStringToStream(!rb.m_failed, rb.m_at);
+
+ fulltext_log_assert_to_stream(s, rb);
+
+ log_contexts();
+ }
+
+ void log_message(const MessageData& mb) override {
+ if(tc->m_no_output)
+ return;
+
+ DOCTEST_LOCK_MUTEX(mutex)
+
+ logTestStart();
+
+ file_line_to_stream(mb.m_file, mb.m_line, " ");
+ s << getSuccessOrFailColor(false, mb.m_severity)
+ << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity,
+ "MESSAGE") << ": ";
+ s << Color::None << mb.m_string << "\n";
+ log_contexts();
+ }
+
+ void test_case_skipped(const TestCaseData&) override {}
+ };
+
+ DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter);
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ struct DebugOutputWindowReporter : public ConsoleReporter
+ {
+ DOCTEST_THREAD_LOCAL static std::ostringstream oss;
+
+ DebugOutputWindowReporter(const ContextOptions& co)
+ : ConsoleReporter(co, oss) {}
+
+#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \
+ void func(type arg) override { \
+ bool with_col = g_no_colors; \
+ g_no_colors = false; \
+ ConsoleReporter::func(arg); \
+ if(oss.tellp() != std::streampos{}) { \
+ DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \
+ oss.str(""); \
+ } \
+ g_no_colors = with_col; \
+ }
+
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in)
+ };
+
+ DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss;
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+ // the implementation of parseOption()
+ bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) {
+ // going from the end to the beginning and stopping on the first occurrence from the end
+ for(int i = argc; i > 0; --i) {
+ auto index = i - 1;
+ auto temp = std::strstr(argv[index], pattern);
+ if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue
+ // eliminate matches in which the chars before the option are not '-'
+ bool noBadCharsFound = true;
+ auto curr = argv[index];
+ while(curr != temp) {
+ if(*curr++ != '-') {
+ noBadCharsFound = false;
+ break;
+ }
+ }
+ if(noBadCharsFound && argv[index][0] == '-') {
+ if(value) {
+ // parsing the value of an option
+ temp += strlen(pattern);
+ const unsigned len = strlen(temp);
+ if(len) {
+ *value = temp;
+ return true;
+ }
+ } else {
+ // just a flag - no value
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ // parses an option and returns the string after the '=' character
+ bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr,
+ const String& defaultVal = String()) {
+ if(value)
+ *value = defaultVal;
+#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ // offset (normally 3 for "dt-") to skip prefix
+ if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value))
+ return true;
+#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ return parseOptionImpl(argc, argv, pattern, value);
+ }
+
+ // locates a flag on the command line
+ bool parseFlag(int argc, const char* const* argv, const char* pattern) {
+ return parseOption(argc, argv, pattern);
+ }
+
+ // parses a comma separated list of words after a pattern in one of the arguments in argv
+ bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern,
+ std::vector<String>& res) {
+ String filtersString;
+ if(parseOption(argc, argv, pattern, &filtersString)) {
+ // tokenize with "," as a separator, unless escaped with backslash
+ std::ostringstream s;
+ auto flush = [&s, &res]() {
+ auto string = s.str();
+ if(string.size() > 0) {
+ res.push_back(string.c_str());
+ }
+ s.str("");
+ };
+
+ bool seenBackslash = false;
+ const char* current = filtersString.c_str();
+ const char* end = current + strlen(current);
+ while(current != end) {
+ char character = *current++;
+ if(seenBackslash) {
+ seenBackslash = false;
+ if(character == ',' || character == '\\') {
+ s.put(character);
+ continue;
+ }
+ s.put('\\');
+ }
+ if(character == '\\') {
+ seenBackslash = true;
+ } else if(character == ',') {
+ flush();
+ } else {
+ s.put(character);
+ }
+ }
+
+ if(seenBackslash) {
+ s.put('\\');
+ }
+ flush();
+ return true;
+ }
+ return false;
+ }
+
+ enum optionType
+ {
+ option_bool,
+ option_int
+ };
+
+ // parses an int/bool option from the command line
+ bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type,
+ int& res) {
+ String parsedValue;
+ if(!parseOption(argc, argv, pattern, &parsedValue))
+ return false;
+
+ if(type) {
+ // integer
+ // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse...
+ int theInt = std::atoi(parsedValue.c_str());
+ if (theInt != 0) {
+ res = theInt; //!OCLINT parameter reassignment
+ return true;
+ }
+ } else {
+ // boolean
+ const char positive[][5] = { "1", "true", "on", "yes" }; // 5 - strlen("true") + 1
+ const char negative[][6] = { "0", "false", "off", "no" }; // 6 - strlen("false") + 1
+
+ // if the value matches any of the positive/negative possibilities
+ for (unsigned i = 0; i < 4; i++) {
+ if (parsedValue.compare(positive[i], true) == 0) {
+ res = 1; //!OCLINT parameter reassignment
+ return true;
+ }
+ if (parsedValue.compare(negative[i], true) == 0) {
+ res = 0; //!OCLINT parameter reassignment
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+} // namespace
+
+Context::Context(int argc, const char* const* argv)
+ : p(new detail::ContextState) {
+ parseArgs(argc, argv, true);
+ if(argc)
+ p->binary_name = argv[0];
+}
+
+Context::~Context() {
+ if(g_cs == p)
+ g_cs = nullptr;
+ delete p;
+}
+
+void Context::applyCommandLine(int argc, const char* const* argv) {
+ parseArgs(argc, argv);
+ if(argc)
+ p->binary_name = argv[0];
+}
+
+// parses args
+void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {
+ using namespace detail;
+
+ // clang-format off
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]);
+ // clang-format on
+
+ int intRes = 0;
+ String strRes;
+
+#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \
+ if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \
+ parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \
+ p->var = static_cast<bool>(intRes); \
+ else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \
+ p->var = true; \
+ else if(withDefaults) \
+ p->var = default
+
+#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \
+ if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \
+ parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \
+ p->var = intRes; \
+ else if(withDefaults) \
+ p->var = default
+
+#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \
+ if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \
+ parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \
+ withDefaults) \
+ p->var = strRes
+
+ // clang-format off
+ DOCTEST_PARSE_STR_OPTION("out", "o", out, "");
+ DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file");
+ DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0);
+
+ DOCTEST_PARSE_INT_OPTION("first", "f", first, 0);
+ DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX);
+
+ DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0);
+ DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX);
+
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC));
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false);
+ // clang-format on
+
+ if(withDefaults) {
+ p->help = false;
+ p->version = false;
+ p->count = false;
+ p->list_test_cases = false;
+ p->list_test_suites = false;
+ p->list_reporters = false;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) {
+ p->help = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) {
+ p->version = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) {
+ p->count = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) {
+ p->list_test_cases = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) {
+ p->list_test_suites = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) {
+ p->list_reporters = true;
+ p->exit = true;
+ }
+}
+
+// allows the user to add procedurally to the filters from the command line
+void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); }
+
+// allows the user to clear all filters from the command line
+void Context::clearFilters() {
+ for(auto& curr : p->filters)
+ curr.clear();
+}
+
+// allows the user to override procedurally the bool options from the command line
+void Context::setOption(const char* option, bool value) {
+ setOption(option, value ? "true" : "false");
+}
+
+// allows the user to override procedurally the int options from the command line
+void Context::setOption(const char* option, int value) {
+ setOption(option, toString(value).c_str());
+}
+
+// allows the user to override procedurally the string options from the command line
+void Context::setOption(const char* option, const char* value) {
+ auto argv = String("-") + option + "=" + value;
+ auto lvalue = argv.c_str();
+ parseArgs(1, &lvalue);
+}
+
+// users should query this in their main() and exit the program if true
+bool Context::shouldExit() { return p->exit; }
+
+void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; }
+
+void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; }
+
+void Context::setCout(std::ostream* out) { p->cout = out; }
+
+static class DiscardOStream : public std::ostream
+{
+private:
+ class : public std::streambuf
+ {
+ private:
+ // allowing some buffering decreases the amount of calls to overflow
+ char buf[1024];
+
+ protected:
+ std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; }
+
+ int_type overflow(int_type ch) override {
+ setp(std::begin(buf), std::end(buf));
+ return traits_type::not_eof(ch);
+ }
+ } discardBuf;
+
+public:
+ DiscardOStream()
+ : std::ostream(&discardBuf) {}
+} discardOut;
+
+// the main function that does all the filtering and test running
+int Context::run() {
+ using namespace detail;
+
+ // save the old context state in case such was setup - for using asserts out of a testing context
+ auto old_cs = g_cs;
+ // this is the current contest
+ g_cs = p;
+ is_running_in_test = true;
+
+ g_no_colors = p->no_colors;
+ p->resetRunData();
+
+ std::fstream fstr;
+ if(p->cout == nullptr) {
+ if(p->quiet) {
+ p->cout = &discardOut;
+ } else if(p->out.size()) {
+ // to a file if specified
+ fstr.open(p->out.c_str(), std::fstream::out);
+ p->cout = &fstr;
+ } else {
+ // stdout by default
+ p->cout = &std::cout;
+ }
+ }
+
+ FatalConditionHandler::allocateAltStackMem();
+
+ auto cleanup_and_return = [&]() {
+ FatalConditionHandler::freeAltStackMem();
+
+ if(fstr.is_open())
+ fstr.close();
+
+ // restore context
+ g_cs = old_cs;
+ is_running_in_test = false;
+
+ // we have to free the reporters which were allocated when the run started
+ for(auto& curr : p->reporters_currently_used)
+ delete curr;
+ p->reporters_currently_used.clear();
+
+ if(p->numTestCasesFailed && !p->no_exitcode)
+ return EXIT_FAILURE;
+ return EXIT_SUCCESS;
+ };
+
+ // setup default reporter if none is given through the command line
+ if(p->filters[8].empty())
+ p->filters[8].push_back("console");
+
+ // check to see if any of the registered reporters has been selected
+ for(auto& curr : getReporters()) {
+ if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive))
+ p->reporters_currently_used.push_back(curr.second(*g_cs));
+ }
+
+ // TODO: check if there is nothing in reporters_currently_used
+
+ // prepend all listeners
+ for(auto& curr : getListeners())
+ p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs));
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ if(isDebuggerActive() && p->no_debug_output == false)
+ p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs));
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+ // handle version, help and no_run
+ if(p->no_run || p->version || p->help || p->list_reporters) {
+ DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData());
+
+ return cleanup_and_return();
+ }
+
+ std::vector<const TestCase*> testArray;
+ for(auto& curr : getRegisteredTests())
+ testArray.push_back(&curr);
+ p->numTestCases = testArray.size();
+
+ // sort the collected records
+ if(!testArray.empty()) {
+ if(p->order_by.compare("file", true) == 0) {
+ std::sort(testArray.begin(), testArray.end(), fileOrderComparator);
+ } else if(p->order_by.compare("suite", true) == 0) {
+ std::sort(testArray.begin(), testArray.end(), suiteOrderComparator);
+ } else if(p->order_by.compare("name", true) == 0) {
+ std::sort(testArray.begin(), testArray.end(), nameOrderComparator);
+ } else if(p->order_by.compare("rand", true) == 0) {
+ std::srand(p->rand_seed);
+
+ // random_shuffle implementation
+ const auto first = &testArray[0];
+ for(size_t i = testArray.size() - 1; i > 0; --i) {
+ int idxToSwap = std::rand() % (i + 1);
+
+ const auto temp = first[i];
+
+ first[i] = first[idxToSwap];
+ first[idxToSwap] = temp;
+ }
+ } else if(p->order_by.compare("none", true) == 0) {
+ // means no sorting - beneficial for death tests which call into the executable
+ // with a specific test case in mind - we don't want to slow down the startup times
+ }
+ }
+
+ std::set<String> testSuitesPassingFilt;
+
+ bool query_mode = p->count || p->list_test_cases || p->list_test_suites;
+ std::vector<const TestCaseData*> queryResults;
+
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY);
+
+ // invoke the registered functions if they match the filter criteria (or just count them)
+ for(auto& curr : testArray) {
+ const auto& tc = *curr;
+
+ bool skip_me = false;
+ if(tc.m_skip && !p->no_skip)
+ skip_me = true;
+
+ if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive))
+ skip_me = true;
+ if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive))
+ skip_me = true;
+ if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive))
+ skip_me = true;
+
+ if(!skip_me)
+ p->numTestCasesPassingFilters++;
+
+ // skip the test if it is not in the execution range
+ if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) ||
+ (p->first > p->numTestCasesPassingFilters))
+ skip_me = true;
+
+ if(skip_me) {
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc);
+ continue;
+ }
+
+ // do not execute the test if we are to only count the number of filter passing tests
+ if(p->count)
+ continue;
+
+ // print the name of the test and don't execute it
+ if(p->list_test_cases) {
+ queryResults.push_back(&tc);
+ continue;
+ }
+
+ // print the name of the test suite if not done already and don't execute it
+ if(p->list_test_suites) {
+ if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') {
+ queryResults.push_back(&tc);
+ testSuitesPassingFilt.insert(tc.m_test_suite);
+ p->numTestSuitesPassingFilters++;
+ }
+ continue;
+ }
+
+ // execute the test if it passes all the filtering
+ {
+ p->currentTest = &tc;
+
+ p->failure_flags = TestCaseFailureReason::None;
+ p->seconds = 0;
+
+ // reset atomic counters
+ p->numAssertsFailedCurrentTest_atomic = 0;
+ p->numAssertsCurrentTest_atomic = 0;
+
+ p->fullyTraversedSubcases.clear();
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);
+
+ p->timer.start();
+
+ bool run_test = true;
+
+ do {
+ // reset some of the fields for subcases (except for the set of fully passed ones)
+ p->reachedLeaf = false;
+ // May not be empty if previous subcase exited via exception.
+ p->subcaseStack.clear();
+ p->currentSubcaseDepth = 0;
+
+ p->shouldLogCurrentException = true;
+
+ // reset stuff for logging with INFO()
+ p->stringifiedContexts.clear();
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ try {
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method)
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable
+ FatalConditionHandler fatalConditionHandler; // Handle signals
+ // execute the test
+ tc.m_test();
+ fatalConditionHandler.reset();
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ } catch(const TestFailureException&) {
+ p->failure_flags |= TestCaseFailureReason::AssertFailure;
+ } catch(...) {
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception,
+ {translateActiveException(), false});
+ p->failure_flags |= TestCaseFailureReason::Exception;
+ }
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+ // exit this loop if enough assertions have failed - even if there are more subcases
+ if(p->abort_after > 0 &&
+ p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) {
+ run_test = false;
+ p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;
+ }
+
+ if(!p->nextSubcaseStack.empty() && run_test)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc);
+ if(p->nextSubcaseStack.empty())
+ run_test = false;
+ } while(run_test);
+
+ p->finalizeTestCaseData();
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);
+
+ p->currentTest = nullptr;
+
+ // stop executing tests if enough assertions have failed
+ if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after)
+ break;
+ }
+ }
+
+ if(!query_mode) {
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);
+ } else {
+ QueryData qdata;
+ qdata.run_stats = g_cs;
+ qdata.data = queryResults.data();
+ qdata.num_data = unsigned(queryResults.size());
+ DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata);
+ }
+
+ return cleanup_and_return();
+}
+
+DOCTEST_DEFINE_INTERFACE(IReporter)
+
+int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); }
+const IContextScope* const* IReporter::get_active_contexts() {
+ return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr;
+}
+
+int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); }
+const String* IReporter::get_stringified_contexts() {
+ return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr;
+}
+
+namespace detail {
+ void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) {
+ if(isReporter)
+ getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
+ else
+ getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
+ }
+} // namespace detail
+
+} // namespace doctest
+
+#endif // DOCTEST_CONFIG_DISABLE
+
+#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182
+int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); }
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+DOCTEST_SUPPRESS_COMMON_WARNINGS_POP
+
+#endif // DOCTEST_LIBRARY_IMPLEMENTATION
+#endif // DOCTEST_CONFIG_IMPLEMENT
diff --git a/src/third-party/intervaltree/IntervalTree.h b/src/third-party/intervaltree/IntervalTree.h
new file mode 100644
index 0000000..d631b5f
--- /dev/null
+++ b/src/third-party/intervaltree/IntervalTree.h
@@ -0,0 +1,346 @@
+/**
+ * Origin: https://github.com/ekg/intervaltree
+ */
+
+#ifndef __INTERVAL_TREE_H
+#define __INTERVAL_TREE_H
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <vector>
+
+namespace interval_tree {
+
+template <class Scalar, typename Value>
+class Interval {
+public:
+ Scalar start;
+ Scalar stop;
+ Value value;
+ Interval(const Scalar& s, const Scalar& e, const Value& v)
+ : start(std::min(s, e))
+ , stop(std::max(s, e))
+ , value(v)
+ {}
+};
+
+template <class Scalar, typename Value>
+Value intervalStart(const Interval<Scalar,Value>& i) {
+ return i.start;
+}
+
+template <class Scalar, typename Value>
+Value intervalStop(const Interval<Scalar, Value>& i) {
+ return i.stop;
+}
+
+template <class Scalar, typename Value>
+std::ostream& operator<<(std::ostream& out, const Interval<Scalar, Value>& i) {
+ out << "Interval(" << i.start << ", " << i.stop << "): " << i.value;
+ return out;
+}
+
+template <class Scalar, class Value>
+class IntervalTree {
+public:
+ typedef Interval<Scalar, Value> interval;
+ typedef std::vector<interval> interval_vector;
+
+
+ struct IntervalStartCmp {
+ bool operator()(const interval& a, const interval& b) {
+ return a.start < b.start;
+ }
+ };
+
+ struct IntervalStopCmp {
+ bool operator()(const interval& a, const interval& b) {
+ return a.stop < b.stop;
+ }
+ };
+
+ IntervalTree()
+ : left(nullptr)
+ , right(nullptr)
+ , center(0)
+ {}
+
+ ~IntervalTree() = default;
+
+ std::unique_ptr<IntervalTree> clone() const {
+ return std::unique_ptr<IntervalTree>(new IntervalTree(*this));
+ }
+
+ IntervalTree(const IntervalTree& other)
+ : intervals(other.intervals),
+ left(other.left ? other.left->clone() : nullptr),
+ right(other.right ? other.right->clone() : nullptr),
+ center(other.center)
+ {}
+
+ IntervalTree& operator=(IntervalTree&&) = default;
+ IntervalTree(IntervalTree&&) = default;
+
+ IntervalTree& operator=(const IntervalTree& other) {
+ center = other.center;
+ intervals = other.intervals;
+ left = other.left ? other.left->clone() : nullptr;
+ right = other.right ? other.right->clone() : nullptr;
+ return *this;
+ }
+
+ IntervalTree(
+ interval_vector&& ivals,
+ std::size_t depth = 16,
+ std::size_t minbucket = 64,
+ std::size_t maxbucket = 512,
+ Scalar leftextent = 0,
+ Scalar rightextent = 0)
+ : left(nullptr)
+ , right(nullptr)
+ {
+ --depth;
+ const auto minmaxStop = std::minmax_element(ivals.begin(), ivals.end(),
+ IntervalStopCmp());
+ const auto minmaxStart = std::minmax_element(ivals.begin(), ivals.end(),
+ IntervalStartCmp());
+ if (!ivals.empty()) {
+ center = (minmaxStart.first->start + minmaxStop.second->stop) / 2;
+ }
+ if (leftextent == 0 && rightextent == 0) {
+ // sort intervals by start
+ std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
+ } else {
+ assert(std::is_sorted(ivals.begin(), ivals.end(), IntervalStartCmp()));
+ }
+ if (depth == 0 || (ivals.size() < minbucket && ivals.size() < maxbucket)) {
+ std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
+ intervals = std::move(ivals);
+ assert(is_valid().first);
+ return;
+ } else {
+ Scalar leftp = 0;
+ Scalar rightp = 0;
+
+ if (leftextent || rightextent) {
+ leftp = leftextent;
+ rightp = rightextent;
+ } else {
+ leftp = ivals.front().start;
+ rightp = std::max_element(ivals.begin(), ivals.end(),
+ IntervalStopCmp())->stop;
+ }
+
+ interval_vector lefts;
+ interval_vector rights;
+
+ for (typename interval_vector::const_iterator i = ivals.begin();
+ i != ivals.end(); ++i) {
+ const interval& interval = *i;
+ if (interval.stop < center) {
+ lefts.push_back(interval);
+ } else if (interval.start > center) {
+ rights.push_back(interval);
+ } else {
+ assert(interval.start <= center);
+ assert(center <= interval.stop);
+ intervals.push_back(interval);
+ }
+ }
+
+ if (!lefts.empty()) {
+ left.reset(new IntervalTree(std::move(lefts),
+ depth, minbucket, maxbucket,
+ leftp, center));
+ }
+ if (!rights.empty()) {
+ right.reset(new IntervalTree(std::move(rights),
+ depth, minbucket, maxbucket,
+ center, rightp));
+ }
+ }
+ assert(is_valid().first);
+ }
+
+ // Call f on all intervals near the range [start, stop]:
+ template <class UnaryFunction>
+ void visit_near(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
+ if (!intervals.empty() && ! (stop < intervals.front().start)) {
+ for (auto & i : intervals) {
+ f(i);
+ }
+ }
+ if (left && start <= center) {
+ left->visit_near(start, stop, f);
+ }
+ if (right && stop >= center) {
+ right->visit_near(start, stop, f);
+ }
+ }
+
+ // Call f on all intervals crossing pos
+ template <class UnaryFunction>
+ void visit_overlapping(const Scalar& pos, UnaryFunction f) const {
+ visit_overlapping(pos, pos, f);
+ }
+
+ // Call f on all intervals overlapping [start, stop]
+ template <class UnaryFunction>
+ void visit_overlapping(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
+ auto filterF = [&](const interval& interval) {
+ if (interval.stop >= start && interval.start <= stop) {
+ // Only apply f if overlapping
+ f(interval);
+ }
+ };
+ visit_near(start, stop, filterF);
+ }
+
+ // Call f on all intervals contained within [start, stop]
+ template <class UnaryFunction>
+ void visit_contained(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
+ auto filterF = [&](const interval& interval) {
+ if (start <= interval.start && interval.stop <= stop) {
+ f(interval);
+ }
+ };
+ visit_near(start, stop, filterF);
+ }
+
+ interval_vector findOverlapping(const Scalar& start, const Scalar& stop) const {
+ interval_vector result;
+ visit_overlapping(start, stop,
+ [&](const interval& interval) {
+ result.emplace_back(interval);
+ });
+ return result;
+ }
+
+ interval_vector findContained(const Scalar& start, const Scalar& stop) const {
+ interval_vector result;
+ visit_contained(start, stop,
+ [&](const interval& interval) {
+ result.push_back(interval);
+ });
+ return result;
+ }
+ bool empty() const {
+ if (left && !left->empty()) {
+ return false;
+ }
+ if (!intervals.empty()) {
+ return false;
+ }
+ if (right && !right->empty()) {
+ return false;
+ }
+ return true;
+ }
+
+ template <class UnaryFunction>
+ void visit_all(UnaryFunction f) const {
+ if (left) {
+ left->visit_all(f);
+ }
+ std::for_each(intervals.begin(), intervals.end(), f);
+ if (right) {
+ right->visit_all(f);
+ }
+ }
+
+ std::pair<Scalar, Scalar> extentBruitForce() const {
+ struct Extent {
+ std::pair<Scalar, Scalar> x = {std::numeric_limits<Scalar>::max(),
+ std::numeric_limits<Scalar>::min() };
+ void operator()(const interval & interval) {
+ x.first = std::min(x.first, interval.start);
+ x.second = std::max(x.second, interval.stop);
+ }
+ };
+ Extent extent;
+
+ visit_all([&](const interval & interval) { extent(interval); });
+ return extent.x;
+ }
+
+ // Check all constraints.
+ // If first is false, second is invalid.
+ std::pair<bool, std::pair<Scalar, Scalar>> is_valid() const {
+ const auto minmaxStop = std::minmax_element(intervals.begin(), intervals.end(),
+ IntervalStopCmp());
+ const auto minmaxStart = std::minmax_element(intervals.begin(), intervals.end(),
+ IntervalStartCmp());
+
+ std::pair<bool, std::pair<Scalar, Scalar>> result = {true, { std::numeric_limits<Scalar>::max(),
+ std::numeric_limits<Scalar>::min() }};
+ if (!intervals.empty()) {
+ result.second.first = std::min(result.second.first, minmaxStart.first->start);
+ result.second.second = std::min(result.second.second, minmaxStop.second->stop);
+ }
+ if (left) {
+ auto valid = left->is_valid();
+ result.first &= valid.first;
+ result.second.first = std::min(result.second.first, valid.second.first);
+ result.second.second = std::min(result.second.second, valid.second.second);
+ if (!result.first) { return result; }
+ if (valid.second.second >= center) {
+ result.first = false;
+ return result;
+ }
+ }
+ if (right) {
+ auto valid = right->is_valid();
+ result.first &= valid.first;
+ result.second.first = std::min(result.second.first, valid.second.first);
+ result.second.second = std::min(result.second.second, valid.second.second);
+ if (!result.first) { return result; }
+ if (valid.second.first <= center) {
+ result.first = false;
+ return result;
+ }
+ }
+ if (!std::is_sorted(intervals.begin(), intervals.end(), IntervalStartCmp())) {
+ result.first = false;
+ }
+ return result;
+ }
+
+ friend std::ostream& operator<<(std::ostream& os, const IntervalTree& itree) {
+ return writeOut(os, itree);
+ }
+
+ friend std::ostream& writeOut(std::ostream& os, const IntervalTree& itree,
+ std::size_t depth = 0) {
+ auto pad = [&]() { for (std::size_t i = 0; i != depth; ++i) { os << ' '; } };
+ pad(); os << "center: " << itree.center << '\n';
+ for (const interval & inter : itree.intervals) {
+ pad(); os << inter << '\n';
+ }
+ if (itree.left) {
+ pad(); os << "left:\n";
+ writeOut(os, *itree.left, depth + 1);
+ } else {
+ pad(); os << "left: nullptr\n";
+ }
+ if (itree.right) {
+ pad(); os << "right:\n";
+ writeOut(os, *itree.right, depth + 1);
+ } else {
+ pad(); os << "right: nullptr\n";
+ }
+ return os;
+ }
+
+private:
+ interval_vector intervals;
+ std::unique_ptr<IntervalTree> left;
+ std::unique_ptr<IntervalTree> right;
+ Scalar center;
+};
+
+}
+
+#endif
diff --git a/src/third-party/md4c/md4c.c b/src/third-party/md4c/md4c.c
new file mode 100644
index 0000000..3677c0e
--- /dev/null
+++ b/src/third-party/md4c/md4c.c
@@ -0,0 +1,6410 @@
+/*
+ * MD4C: Markdown parser for C
+ * (http://github.com/mity/md4c)
+ *
+ * Copyright (c) 2016-2020 Martin Mitas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "md4c.h"
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/*****************************
+ *** Miscellaneous Stuff ***
+ *****************************/
+
+#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199409L
+ /* C89/90 or old compilers in general may not understand "inline". */
+ #if defined __GNUC__
+ #define inline __inline__
+ #elif defined _MSC_VER
+ #define inline __inline
+ #else
+ #define inline
+ #endif
+#endif
+
+/* Make the UTF-8 support the default. */
+#if !defined MD4C_USE_ASCII && !defined MD4C_USE_UTF8 && !defined MD4C_USE_UTF16
+ #define MD4C_USE_UTF8
+#endif
+
+/* Magic for making wide literals with MD4C_USE_UTF16. */
+#ifdef _T
+ #undef _T
+#endif
+#if defined MD4C_USE_UTF16
+ #define _T(x) L##x
+#else
+ #define _T(x) x
+#endif
+
+/* Misc. macros. */
+#define SIZEOF_ARRAY(a) (sizeof(a) / sizeof(a[0]))
+
+#define STRINGIZE_(x) #x
+#define STRINGIZE(x) STRINGIZE_(x)
+
+#ifndef TRUE
+ #define TRUE 1
+ #define FALSE 0
+#endif
+
+#define MD_LOG(msg) \
+ do { \
+ if(ctx->parser.debug_log != NULL) \
+ ctx->parser.debug_log((msg), ctx->userdata); \
+ } while(0)
+
+#ifdef DEBUG
+ #define MD_ASSERT(cond) \
+ do { \
+ if(!(cond)) { \
+ MD_LOG(__FILE__ ":" STRINGIZE(__LINE__) ": " \
+ "Assertion '" STRINGIZE(cond) "' failed."); \
+ exit(1); \
+ } \
+ } while(0)
+
+ #define MD_UNREACHABLE() MD_ASSERT(1 == 0)
+#else
+ #ifdef __GNUC__
+ #define MD_ASSERT(cond) do { if(!(cond)) __builtin_unreachable(); } while(0)
+ #define MD_UNREACHABLE() do { __builtin_unreachable(); } while(0)
+ #elif defined _MSC_VER && _MSC_VER > 120
+ #define MD_ASSERT(cond) do { __assume(cond); } while(0)
+ #define MD_UNREACHABLE() do { __assume(0); } while(0)
+ #else
+ #define MD_ASSERT(cond) do {} while(0)
+ #define MD_UNREACHABLE() do {} while(0)
+ #endif
+#endif
+
+/* For falling through case labels in switch statements. */
+#if defined __clang__ && __clang_major__ >= 12
+ #define MD_FALLTHROUGH() __attribute__((fallthrough))
+#elif defined __GNUC__ && __GNUC__ >= 7
+ #define MD_FALLTHROUGH() __attribute__((fallthrough))
+#else
+ #define MD_FALLTHROUGH() ((void)0)
+#endif
+
+/* Suppress "unused parameter" warnings. */
+#define MD_UNUSED(x) ((void)x)
+
+
+/************************
+ *** Internal Types ***
+ ************************/
+
+/* These are omnipresent so lets save some typing. */
+#define CHAR MD_CHAR
+#define SZ MD_SIZE
+#define OFF MD_OFFSET
+
+typedef struct MD_MARK_tag MD_MARK;
+typedef struct MD_BLOCK_tag MD_BLOCK;
+typedef struct MD_CONTAINER_tag MD_CONTAINER;
+typedef struct MD_REF_DEF_tag MD_REF_DEF;
+
+
+/* During analyzes of inline marks, we need to manage some "mark chains",
+ * of (yet unresolved) openers. This structure holds start/end of the chain.
+ * The chain internals are then realized through MD_MARK::prev and ::next.
+ */
+typedef struct MD_MARKCHAIN_tag MD_MARKCHAIN;
+struct MD_MARKCHAIN_tag {
+ int head; /* Index of first mark in the chain, or -1 if empty. */
+ int tail; /* Index of last mark in the chain, or -1 if empty. */
+};
+
+/* Context propagated through all the parsing. */
+typedef struct MD_CTX_tag MD_CTX;
+struct MD_CTX_tag {
+ /* Immutable stuff (parameters of md_parse()). */
+ const CHAR* text;
+ SZ size;
+ MD_PARSER parser;
+ void* userdata;
+
+ /* When this is true, it allows some optimizations. */
+ int doc_ends_with_newline;
+
+ /* Helper temporary growing buffer. */
+ CHAR* buffer;
+ unsigned alloc_buffer;
+
+ /* Reference definitions. */
+ MD_REF_DEF* ref_defs;
+ int n_ref_defs;
+ int alloc_ref_defs;
+ void** ref_def_hashtable;
+ int ref_def_hashtable_size;
+
+ /* Stack of inline/span markers.
+ * This is only used for parsing a single block contents but by storing it
+ * here we may reuse the stack for subsequent blocks; i.e. we have fewer
+ * (re)allocations. */
+ MD_MARK* marks;
+ int n_marks;
+ int alloc_marks;
+
+#if defined MD4C_USE_UTF16
+ char mark_char_map[128];
+#else
+ char mark_char_map[256];
+#endif
+
+ /* For resolving of inline spans. */
+ MD_MARKCHAIN mark_chains[13];
+#define PTR_CHAIN (ctx->mark_chains[0])
+#define TABLECELLBOUNDARIES (ctx->mark_chains[1])
+#define ASTERISK_OPENERS_extraword_mod3_0 (ctx->mark_chains[2])
+#define ASTERISK_OPENERS_extraword_mod3_1 (ctx->mark_chains[3])
+#define ASTERISK_OPENERS_extraword_mod3_2 (ctx->mark_chains[4])
+#define ASTERISK_OPENERS_intraword_mod3_0 (ctx->mark_chains[5])
+#define ASTERISK_OPENERS_intraword_mod3_1 (ctx->mark_chains[6])
+#define ASTERISK_OPENERS_intraword_mod3_2 (ctx->mark_chains[7])
+#define UNDERSCORE_OPENERS (ctx->mark_chains[8])
+#define TILDE_OPENERS_1 (ctx->mark_chains[9])
+#define TILDE_OPENERS_2 (ctx->mark_chains[10])
+#define BRACKET_OPENERS (ctx->mark_chains[11])
+#define DOLLAR_OPENERS (ctx->mark_chains[12])
+#define OPENERS_CHAIN_FIRST 1
+#define OPENERS_CHAIN_LAST 12
+
+ int n_table_cell_boundaries;
+
+ /* For resolving links. */
+ int unresolved_link_head;
+ int unresolved_link_tail;
+
+ /* For resolving raw HTML. */
+ OFF html_comment_horizon;
+ OFF html_proc_instr_horizon;
+ OFF html_decl_horizon;
+ OFF html_cdata_horizon;
+
+ /* For block analysis.
+ * Notes:
+ * -- It holds MD_BLOCK as well as MD_LINE structures. After each
+ * MD_BLOCK, its (multiple) MD_LINE(s) follow.
+ * -- For MD_BLOCK_HTML and MD_BLOCK_CODE, MD_VERBATIMLINE(s) are used
+ * instead of MD_LINE(s).
+ */
+ void* block_bytes;
+ MD_BLOCK* current_block;
+ int n_block_bytes;
+ int alloc_block_bytes;
+
+ /* For container block analysis. */
+ MD_CONTAINER* containers;
+ int n_containers;
+ int alloc_containers;
+
+ /* Minimal indentation to call the block "indented code block". */
+ unsigned code_indent_offset;
+
+ /* Contextual info for line analysis. */
+ SZ code_fence_length; /* For checking closing fence length. */
+ int html_block_type; /* For checking closing raw HTML condition. */
+ int last_line_has_list_loosening_effect;
+ int last_list_item_starts_with_two_blank_lines;
+};
+
+enum MD_LINETYPE_tag {
+ MD_LINE_BLANK,
+ MD_LINE_HR,
+ MD_LINE_ATXHEADER,
+ MD_LINE_SETEXTHEADER,
+ MD_LINE_SETEXTUNDERLINE,
+ MD_LINE_INDENTEDCODE,
+ MD_LINE_FENCEDCODE,
+ MD_LINE_HTML,
+ MD_LINE_TEXT,
+ MD_LINE_TABLE,
+ MD_LINE_TABLEUNDERLINE
+};
+typedef enum MD_LINETYPE_tag MD_LINETYPE;
+
+typedef struct MD_LINE_ANALYSIS_tag MD_LINE_ANALYSIS;
+struct MD_LINE_ANALYSIS_tag {
+ MD_LINETYPE type : 16;
+ unsigned data : 16;
+ OFF beg;
+ OFF end;
+ unsigned indent; /* Indentation level. */
+};
+
+typedef struct MD_LINE_tag MD_LINE;
+struct MD_LINE_tag {
+ OFF beg;
+ OFF end;
+};
+
+typedef struct MD_VERBATIMLINE_tag MD_VERBATIMLINE;
+struct MD_VERBATIMLINE_tag {
+ OFF beg;
+ OFF end;
+ OFF indent;
+};
+
+
+/*****************
+ *** Helpers ***
+ *****************/
+
+/* Character accessors. */
+#define CH(off) (ctx->text[(off)])
+#define STR(off) (ctx->text + (off))
+
+/* Character classification.
+ * Note we assume ASCII compatibility of code points < 128 here. */
+#define ISIN_(ch, ch_min, ch_max) ((ch_min) <= (unsigned)(ch) && (unsigned)(ch) <= (ch_max))
+#define ISANYOF_(ch, palette) ((ch) != _T('\0') && md_strchr((palette), (ch)) != NULL)
+#define ISANYOF2_(ch, ch1, ch2) ((ch) == (ch1) || (ch) == (ch2))
+#define ISANYOF3_(ch, ch1, ch2, ch3) ((ch) == (ch1) || (ch) == (ch2) || (ch) == (ch3))
+#define ISASCII_(ch) ((unsigned)(ch) <= 127)
+#define ISBLANK_(ch) (ISANYOF2_((ch), _T(' '), _T('\t')))
+#define ISNEWLINE_(ch) (ISANYOF2_((ch), _T('\r'), _T('\n')))
+#define ISWHITESPACE_(ch) (ISBLANK_(ch) || ISANYOF2_((ch), _T('\v'), _T('\f')))
+#define ISCNTRL_(ch) ((unsigned)(ch) <= 31 || (unsigned)(ch) == 127)
+#define ISPUNCT_(ch) (ISIN_(ch, 33, 47) || ISIN_(ch, 58, 64) || ISIN_(ch, 91, 96) || ISIN_(ch, 123, 126))
+#define ISUPPER_(ch) (ISIN_(ch, _T('A'), _T('Z')))
+#define ISLOWER_(ch) (ISIN_(ch, _T('a'), _T('z')))
+#define ISALPHA_(ch) (ISUPPER_(ch) || ISLOWER_(ch))
+#define ISDIGIT_(ch) (ISIN_(ch, _T('0'), _T('9')))
+#define ISXDIGIT_(ch) (ISDIGIT_(ch) || ISIN_(ch, _T('A'), _T('F')) || ISIN_(ch, _T('a'), _T('f')))
+#define ISALNUM_(ch) (ISALPHA_(ch) || ISDIGIT_(ch))
+
+#define ISANYOF(off, palette) ISANYOF_(CH(off), (palette))
+#define ISANYOF2(off, ch1, ch2) ISANYOF2_(CH(off), (ch1), (ch2))
+#define ISANYOF3(off, ch1, ch2, ch3) ISANYOF3_(CH(off), (ch1), (ch2), (ch3))
+#define ISASCII(off) ISASCII_(CH(off))
+#define ISBLANK(off) ISBLANK_(CH(off))
+#define ISNEWLINE(off) ISNEWLINE_(CH(off))
+#define ISWHITESPACE(off) ISWHITESPACE_(CH(off))
+#define ISCNTRL(off) ISCNTRL_(CH(off))
+#define ISPUNCT(off) ISPUNCT_(CH(off))
+#define ISUPPER(off) ISUPPER_(CH(off))
+#define ISLOWER(off) ISLOWER_(CH(off))
+#define ISALPHA(off) ISALPHA_(CH(off))
+#define ISDIGIT(off) ISDIGIT_(CH(off))
+#define ISXDIGIT(off) ISXDIGIT_(CH(off))
+#define ISALNUM(off) ISALNUM_(CH(off))
+
+
+#if defined MD4C_USE_UTF16
+ #define md_strchr wcschr
+#else
+ #define md_strchr strchr
+#endif
+
+
+/* Case insensitive check of string equality. */
+static inline int
+md_ascii_case_eq(const CHAR* s1, const CHAR* s2, SZ n)
+{
+ OFF i;
+ for(i = 0; i < n; i++) {
+ CHAR ch1 = s1[i];
+ CHAR ch2 = s2[i];
+
+ if(ISLOWER_(ch1))
+ ch1 += ('A'-'a');
+ if(ISLOWER_(ch2))
+ ch2 += ('A'-'a');
+ if(ch1 != ch2)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static inline int
+md_ascii_eq(const CHAR* s1, const CHAR* s2, SZ n)
+{
+ return memcmp(s1, s2, n * sizeof(CHAR)) == 0;
+}
+
+static int
+md_text_with_null_replacement(MD_CTX* ctx, MD_TEXTTYPE type, const CHAR* str, SZ size)
+{
+ OFF off = 0;
+ int ret = 0;
+
+ while(1) {
+ while(off < size && str[off] != _T('\0'))
+ off++;
+
+ if(off > 0) {
+ ret = ctx->parser.text(type, str, off, ctx->userdata);
+ if(ret != 0)
+ return ret;
+
+ str += off;
+ size -= off;
+ off = 0;
+ }
+
+ if(off >= size)
+ return 0;
+
+ ret = ctx->parser.text(MD_TEXT_NULLCHAR, _T(""), 1, ctx->userdata);
+ if(ret != 0)
+ return ret;
+ off++;
+ }
+}
+
+
+#define MD_CHECK(func) \
+ do { \
+ ret = (func); \
+ if(ret < 0) \
+ goto abort; \
+ } while(0)
+
+
+#define MD_TEMP_BUFFER(sz) \
+ do { \
+ if(sz > ctx->alloc_buffer) { \
+ CHAR* new_buffer; \
+ SZ new_size = ((sz) + (sz) / 2 + 128) & ~127; \
+ \
+ new_buffer = realloc(ctx->buffer, new_size); \
+ if(new_buffer == NULL) { \
+ MD_LOG("realloc() failed."); \
+ ret = -1; \
+ goto abort; \
+ } \
+ \
+ ctx->buffer = new_buffer; \
+ ctx->alloc_buffer = new_size; \
+ } \
+ } while(0)
+
+
+#define MD_ENTER_BLOCK(type, arg) \
+ do { \
+ ret = ctx->parser.enter_block((type), (arg), ctx->userdata); \
+ if(ret != 0) { \
+ MD_LOG("Aborted from enter_block() callback."); \
+ goto abort; \
+ } \
+ } while(0)
+
+#define MD_LEAVE_BLOCK(type, arg) \
+ do { \
+ ret = ctx->parser.leave_block((type), (arg), ctx->userdata); \
+ if(ret != 0) { \
+ MD_LOG("Aborted from leave_block() callback."); \
+ goto abort; \
+ } \
+ } while(0)
+
+#define MD_ENTER_SPAN(type, arg) \
+ do { \
+ ret = ctx->parser.enter_span((type), (arg), ctx->userdata); \
+ if(ret != 0) { \
+ MD_LOG("Aborted from enter_span() callback."); \
+ goto abort; \
+ } \
+ } while(0)
+
+#define MD_LEAVE_SPAN(type, arg) \
+ do { \
+ ret = ctx->parser.leave_span((type), (arg), ctx->userdata); \
+ if(ret != 0) { \
+ MD_LOG("Aborted from leave_span() callback."); \
+ goto abort; \
+ } \
+ } while(0)
+
+#define MD_TEXT(type, str, size) \
+ do { \
+ if(size > 0) { \
+ ret = ctx->parser.text((type), (str), (size), ctx->userdata); \
+ if(ret != 0) { \
+ MD_LOG("Aborted from text() callback."); \
+ goto abort; \
+ } \
+ } \
+ } while(0)
+
+#define MD_TEXT_INSECURE(type, str, size) \
+ do { \
+ if(size > 0) { \
+ ret = md_text_with_null_replacement(ctx, type, str, size); \
+ if(ret != 0) { \
+ MD_LOG("Aborted from text() callback."); \
+ goto abort; \
+ } \
+ } \
+ } while(0)
+
+
+/* If the offset falls into a gap between line, we return the following
+ * line. */
+static const MD_LINE*
+md_lookup_line(OFF off, const MD_LINE* lines, int n_lines)
+{
+ int lo, hi;
+ int pivot;
+ const MD_LINE* line;
+
+ lo = 0;
+ hi = n_lines - 1;
+ while(lo <= hi) {
+ pivot = (lo + hi) / 2;
+ line = &lines[pivot];
+
+ if(off < line->beg) {
+ hi = pivot - 1;
+ if(hi < 0 || lines[hi].end <= off)
+ return line;
+ } else if(off > line->end) {
+ lo = pivot + 1;
+ } else {
+ return line;
+ }
+ }
+
+ return NULL;
+}
+
+
+/*************************
+ *** Unicode Support ***
+ *************************/
+
+typedef struct MD_UNICODE_FOLD_INFO_tag MD_UNICODE_FOLD_INFO;
+struct MD_UNICODE_FOLD_INFO_tag {
+ unsigned codepoints[3];
+ unsigned n_codepoints;
+};
+
+
+#if defined MD4C_USE_UTF16 || defined MD4C_USE_UTF8
+ /* Binary search over sorted "map" of codepoints. Consecutive sequences
+ * of codepoints may be encoded in the map by just using the
+ * (MIN_CODEPOINT | 0x40000000) and (MAX_CODEPOINT | 0x80000000).
+ *
+ * Returns index of the found record in the map (in the case of ranges,
+ * the minimal value is used); or -1 on failure. */
+ static int
+ md_unicode_bsearch__(unsigned codepoint, const unsigned* map, size_t map_size)
+ {
+ int beg, end;
+ int pivot_beg, pivot_end;
+
+ beg = 0;
+ end = (int) map_size-1;
+ while(beg <= end) {
+ /* Pivot may be a range, not just a single value. */
+ pivot_beg = pivot_end = (beg + end) / 2;
+ if(map[pivot_end] & 0x40000000)
+ pivot_end++;
+ if(map[pivot_beg] & 0x80000000)
+ pivot_beg--;
+
+ if(codepoint < (map[pivot_beg] & 0x00ffffff))
+ end = pivot_beg - 1;
+ else if(codepoint > (map[pivot_end] & 0x00ffffff))
+ beg = pivot_end + 1;
+ else
+ return pivot_beg;
+ }
+
+ return -1;
+ }
+
+ static int
+ md_is_unicode_whitespace__(unsigned codepoint)
+ {
+#define R(cp_min, cp_max) ((cp_min) | 0x40000000), ((cp_max) | 0x80000000)
+#define S(cp) (cp)
+ /* Unicode "Zs" category.
+ * (generated by scripts/build_whitespace_map.py) */
+ static const unsigned WHITESPACE_MAP[] = {
+ S(0x0020), S(0x00a0), S(0x1680), R(0x2000,0x200a), S(0x202f), S(0x205f), S(0x3000)
+ };
+#undef R
+#undef S
+
+ /* The ASCII ones are the most frequently used ones, also CommonMark
+ * specification requests few more in this range. */
+ if(codepoint <= 0x7f)
+ return ISWHITESPACE_(codepoint);
+
+ return (md_unicode_bsearch__(codepoint, WHITESPACE_MAP, SIZEOF_ARRAY(WHITESPACE_MAP)) >= 0);
+ }
+
+ static int
+ md_is_unicode_punct__(unsigned codepoint)
+ {
+#define R(cp_min, cp_max) ((cp_min) | 0x40000000), ((cp_max) | 0x80000000)
+#define S(cp) (cp)
+ /* Unicode "Pc", "Pd", "Pe", "Pf", "Pi", "Po", "Ps" categories.
+ * (generated by scripts/build_punct_map.py) */
+ static const unsigned PUNCT_MAP[] = {
+ R(0x0021,0x0023), R(0x0025,0x002a), R(0x002c,0x002f), R(0x003a,0x003b), R(0x003f,0x0040),
+ R(0x005b,0x005d), S(0x005f), S(0x007b), S(0x007d), S(0x00a1), S(0x00a7), S(0x00ab), R(0x00b6,0x00b7),
+ S(0x00bb), S(0x00bf), S(0x037e), S(0x0387), R(0x055a,0x055f), R(0x0589,0x058a), S(0x05be), S(0x05c0),
+ S(0x05c3), S(0x05c6), R(0x05f3,0x05f4), R(0x0609,0x060a), R(0x060c,0x060d), S(0x061b), R(0x061e,0x061f),
+ R(0x066a,0x066d), S(0x06d4), R(0x0700,0x070d), R(0x07f7,0x07f9), R(0x0830,0x083e), S(0x085e),
+ R(0x0964,0x0965), S(0x0970), S(0x09fd), S(0x0a76), S(0x0af0), S(0x0c77), S(0x0c84), S(0x0df4), S(0x0e4f),
+ R(0x0e5a,0x0e5b), R(0x0f04,0x0f12), S(0x0f14), R(0x0f3a,0x0f3d), S(0x0f85), R(0x0fd0,0x0fd4),
+ R(0x0fd9,0x0fda), R(0x104a,0x104f), S(0x10fb), R(0x1360,0x1368), S(0x1400), S(0x166e), R(0x169b,0x169c),
+ R(0x16eb,0x16ed), R(0x1735,0x1736), R(0x17d4,0x17d6), R(0x17d8,0x17da), R(0x1800,0x180a),
+ R(0x1944,0x1945), R(0x1a1e,0x1a1f), R(0x1aa0,0x1aa6), R(0x1aa8,0x1aad), R(0x1b5a,0x1b60),
+ R(0x1bfc,0x1bff), R(0x1c3b,0x1c3f), R(0x1c7e,0x1c7f), R(0x1cc0,0x1cc7), S(0x1cd3), R(0x2010,0x2027),
+ R(0x2030,0x2043), R(0x2045,0x2051), R(0x2053,0x205e), R(0x207d,0x207e), R(0x208d,0x208e),
+ R(0x2308,0x230b), R(0x2329,0x232a), R(0x2768,0x2775), R(0x27c5,0x27c6), R(0x27e6,0x27ef),
+ R(0x2983,0x2998), R(0x29d8,0x29db), R(0x29fc,0x29fd), R(0x2cf9,0x2cfc), R(0x2cfe,0x2cff), S(0x2d70),
+ R(0x2e00,0x2e2e), R(0x2e30,0x2e4f), S(0x2e52), R(0x3001,0x3003), R(0x3008,0x3011), R(0x3014,0x301f),
+ S(0x3030), S(0x303d), S(0x30a0), S(0x30fb), R(0xa4fe,0xa4ff), R(0xa60d,0xa60f), S(0xa673), S(0xa67e),
+ R(0xa6f2,0xa6f7), R(0xa874,0xa877), R(0xa8ce,0xa8cf), R(0xa8f8,0xa8fa), S(0xa8fc), R(0xa92e,0xa92f),
+ S(0xa95f), R(0xa9c1,0xa9cd), R(0xa9de,0xa9df), R(0xaa5c,0xaa5f), R(0xaade,0xaadf), R(0xaaf0,0xaaf1),
+ S(0xabeb), R(0xfd3e,0xfd3f), R(0xfe10,0xfe19), R(0xfe30,0xfe52), R(0xfe54,0xfe61), S(0xfe63), S(0xfe68),
+ R(0xfe6a,0xfe6b), R(0xff01,0xff03), R(0xff05,0xff0a), R(0xff0c,0xff0f), R(0xff1a,0xff1b),
+ R(0xff1f,0xff20), R(0xff3b,0xff3d), S(0xff3f), S(0xff5b), S(0xff5d), R(0xff5f,0xff65), R(0x10100,0x10102),
+ S(0x1039f), S(0x103d0), S(0x1056f), S(0x10857), S(0x1091f), S(0x1093f), R(0x10a50,0x10a58), S(0x10a7f),
+ R(0x10af0,0x10af6), R(0x10b39,0x10b3f), R(0x10b99,0x10b9c), S(0x10ead), R(0x10f55,0x10f59),
+ R(0x11047,0x1104d), R(0x110bb,0x110bc), R(0x110be,0x110c1), R(0x11140,0x11143), R(0x11174,0x11175),
+ R(0x111c5,0x111c8), S(0x111cd), S(0x111db), R(0x111dd,0x111df), R(0x11238,0x1123d), S(0x112a9),
+ R(0x1144b,0x1144f), R(0x1145a,0x1145b), S(0x1145d), S(0x114c6), R(0x115c1,0x115d7), R(0x11641,0x11643),
+ R(0x11660,0x1166c), R(0x1173c,0x1173e), S(0x1183b), R(0x11944,0x11946), S(0x119e2), R(0x11a3f,0x11a46),
+ R(0x11a9a,0x11a9c), R(0x11a9e,0x11aa2), R(0x11c41,0x11c45), R(0x11c70,0x11c71), R(0x11ef7,0x11ef8),
+ S(0x11fff), R(0x12470,0x12474), R(0x16a6e,0x16a6f), S(0x16af5), R(0x16b37,0x16b3b), S(0x16b44),
+ R(0x16e97,0x16e9a), S(0x16fe2), S(0x1bc9f), R(0x1da87,0x1da8b), R(0x1e95e,0x1e95f)
+ };
+#undef R
+#undef S
+
+ /* The ASCII ones are the most frequently used ones, also CommonMark
+ * specification requests few more in this range. */
+ if(codepoint <= 0x7f)
+ return ISPUNCT_(codepoint);
+
+ return (md_unicode_bsearch__(codepoint, PUNCT_MAP, SIZEOF_ARRAY(PUNCT_MAP)) >= 0);
+ }
+
+ static void
+ md_get_unicode_fold_info(unsigned codepoint, MD_UNICODE_FOLD_INFO* info)
+ {
+#define R(cp_min, cp_max) ((cp_min) | 0x40000000), ((cp_max) | 0x80000000)
+#define S(cp) (cp)
+ /* Unicode "Pc", "Pd", "Pe", "Pf", "Pi", "Po", "Ps" categories.
+ * (generated by scripts/build_folding_map.py) */
+ static const unsigned FOLD_MAP_1[] = {
+ R(0x0041,0x005a), S(0x00b5), R(0x00c0,0x00d6), R(0x00d8,0x00de), R(0x0100,0x012e), R(0x0132,0x0136),
+ R(0x0139,0x0147), R(0x014a,0x0176), S(0x0178), R(0x0179,0x017d), S(0x017f), S(0x0181), S(0x0182),
+ S(0x0184), S(0x0186), S(0x0187), S(0x0189), S(0x018a), S(0x018b), S(0x018e), S(0x018f), S(0x0190),
+ S(0x0191), S(0x0193), S(0x0194), S(0x0196), S(0x0197), S(0x0198), S(0x019c), S(0x019d), S(0x019f),
+ R(0x01a0,0x01a4), S(0x01a6), S(0x01a7), S(0x01a9), S(0x01ac), S(0x01ae), S(0x01af), S(0x01b1), S(0x01b2),
+ S(0x01b3), S(0x01b5), S(0x01b7), S(0x01b8), S(0x01bc), S(0x01c4), S(0x01c5), S(0x01c7), S(0x01c8),
+ S(0x01ca), R(0x01cb,0x01db), R(0x01de,0x01ee), S(0x01f1), S(0x01f2), S(0x01f4), S(0x01f6), S(0x01f7),
+ R(0x01f8,0x021e), S(0x0220), R(0x0222,0x0232), S(0x023a), S(0x023b), S(0x023d), S(0x023e), S(0x0241),
+ S(0x0243), S(0x0244), S(0x0245), R(0x0246,0x024e), S(0x0345), S(0x0370), S(0x0372), S(0x0376), S(0x037f),
+ S(0x0386), R(0x0388,0x038a), S(0x038c), S(0x038e), S(0x038f), R(0x0391,0x03a1), R(0x03a3,0x03ab),
+ S(0x03c2), S(0x03cf), S(0x03d0), S(0x03d1), S(0x03d5), S(0x03d6), R(0x03d8,0x03ee), S(0x03f0), S(0x03f1),
+ S(0x03f4), S(0x03f5), S(0x03f7), S(0x03f9), S(0x03fa), R(0x03fd,0x03ff), R(0x0400,0x040f),
+ R(0x0410,0x042f), R(0x0460,0x0480), R(0x048a,0x04be), S(0x04c0), R(0x04c1,0x04cd), R(0x04d0,0x052e),
+ R(0x0531,0x0556), R(0x10a0,0x10c5), S(0x10c7), S(0x10cd), R(0x13f8,0x13fd), S(0x1c80), S(0x1c81),
+ S(0x1c82), S(0x1c83), S(0x1c84), S(0x1c85), S(0x1c86), S(0x1c87), S(0x1c88), R(0x1c90,0x1cba),
+ R(0x1cbd,0x1cbf), R(0x1e00,0x1e94), S(0x1e9b), R(0x1ea0,0x1efe), R(0x1f08,0x1f0f), R(0x1f18,0x1f1d),
+ R(0x1f28,0x1f2f), R(0x1f38,0x1f3f), R(0x1f48,0x1f4d), S(0x1f59), S(0x1f5b), S(0x1f5d), S(0x1f5f),
+ R(0x1f68,0x1f6f), S(0x1fb8), S(0x1fb9), S(0x1fba), S(0x1fbb), S(0x1fbe), R(0x1fc8,0x1fcb), S(0x1fd8),
+ S(0x1fd9), S(0x1fda), S(0x1fdb), S(0x1fe8), S(0x1fe9), S(0x1fea), S(0x1feb), S(0x1fec), S(0x1ff8),
+ S(0x1ff9), S(0x1ffa), S(0x1ffb), S(0x2126), S(0x212a), S(0x212b), S(0x2132), R(0x2160,0x216f), S(0x2183),
+ R(0x24b6,0x24cf), R(0x2c00,0x2c2e), S(0x2c60), S(0x2c62), S(0x2c63), S(0x2c64), R(0x2c67,0x2c6b),
+ S(0x2c6d), S(0x2c6e), S(0x2c6f), S(0x2c70), S(0x2c72), S(0x2c75), S(0x2c7e), S(0x2c7f), R(0x2c80,0x2ce2),
+ S(0x2ceb), S(0x2ced), S(0x2cf2), R(0xa640,0xa66c), R(0xa680,0xa69a), R(0xa722,0xa72e), R(0xa732,0xa76e),
+ S(0xa779), S(0xa77b), S(0xa77d), R(0xa77e,0xa786), S(0xa78b), S(0xa78d), S(0xa790), S(0xa792),
+ R(0xa796,0xa7a8), S(0xa7aa), S(0xa7ab), S(0xa7ac), S(0xa7ad), S(0xa7ae), S(0xa7b0), S(0xa7b1), S(0xa7b2),
+ S(0xa7b3), R(0xa7b4,0xa7be), S(0xa7c2), S(0xa7c4), S(0xa7c5), S(0xa7c6), S(0xa7c7), S(0xa7c9), S(0xa7f5),
+ R(0xab70,0xabbf), R(0xff21,0xff3a), R(0x10400,0x10427), R(0x104b0,0x104d3), R(0x10c80,0x10cb2),
+ R(0x118a0,0x118bf), R(0x16e40,0x16e5f), R(0x1e900,0x1e921)
+ };
+ static const unsigned FOLD_MAP_1_DATA[] = {
+ 0x0061, 0x007a, 0x03bc, 0x00e0, 0x00f6, 0x00f8, 0x00fe, 0x0101, 0x012f, 0x0133, 0x0137, 0x013a, 0x0148,
+ 0x014b, 0x0177, 0x00ff, 0x017a, 0x017e, 0x0073, 0x0253, 0x0183, 0x0185, 0x0254, 0x0188, 0x0256, 0x0257,
+ 0x018c, 0x01dd, 0x0259, 0x025b, 0x0192, 0x0260, 0x0263, 0x0269, 0x0268, 0x0199, 0x026f, 0x0272, 0x0275,
+ 0x01a1, 0x01a5, 0x0280, 0x01a8, 0x0283, 0x01ad, 0x0288, 0x01b0, 0x028a, 0x028b, 0x01b4, 0x01b6, 0x0292,
+ 0x01b9, 0x01bd, 0x01c6, 0x01c6, 0x01c9, 0x01c9, 0x01cc, 0x01cc, 0x01dc, 0x01df, 0x01ef, 0x01f3, 0x01f3,
+ 0x01f5, 0x0195, 0x01bf, 0x01f9, 0x021f, 0x019e, 0x0223, 0x0233, 0x2c65, 0x023c, 0x019a, 0x2c66, 0x0242,
+ 0x0180, 0x0289, 0x028c, 0x0247, 0x024f, 0x03b9, 0x0371, 0x0373, 0x0377, 0x03f3, 0x03ac, 0x03ad, 0x03af,
+ 0x03cc, 0x03cd, 0x03ce, 0x03b1, 0x03c1, 0x03c3, 0x03cb, 0x03c3, 0x03d7, 0x03b2, 0x03b8, 0x03c6, 0x03c0,
+ 0x03d9, 0x03ef, 0x03ba, 0x03c1, 0x03b8, 0x03b5, 0x03f8, 0x03f2, 0x03fb, 0x037b, 0x037d, 0x0450, 0x045f,
+ 0x0430, 0x044f, 0x0461, 0x0481, 0x048b, 0x04bf, 0x04cf, 0x04c2, 0x04ce, 0x04d1, 0x052f, 0x0561, 0x0586,
+ 0x2d00, 0x2d25, 0x2d27, 0x2d2d, 0x13f0, 0x13f5, 0x0432, 0x0434, 0x043e, 0x0441, 0x0442, 0x0442, 0x044a,
+ 0x0463, 0xa64b, 0x10d0, 0x10fa, 0x10fd, 0x10ff, 0x1e01, 0x1e95, 0x1e61, 0x1ea1, 0x1eff, 0x1f00, 0x1f07,
+ 0x1f10, 0x1f15, 0x1f20, 0x1f27, 0x1f30, 0x1f37, 0x1f40, 0x1f45, 0x1f51, 0x1f53, 0x1f55, 0x1f57, 0x1f60,
+ 0x1f67, 0x1fb0, 0x1fb1, 0x1f70, 0x1f71, 0x03b9, 0x1f72, 0x1f75, 0x1fd0, 0x1fd1, 0x1f76, 0x1f77, 0x1fe0,
+ 0x1fe1, 0x1f7a, 0x1f7b, 0x1fe5, 0x1f78, 0x1f79, 0x1f7c, 0x1f7d, 0x03c9, 0x006b, 0x00e5, 0x214e, 0x2170,
+ 0x217f, 0x2184, 0x24d0, 0x24e9, 0x2c30, 0x2c5e, 0x2c61, 0x026b, 0x1d7d, 0x027d, 0x2c68, 0x2c6c, 0x0251,
+ 0x0271, 0x0250, 0x0252, 0x2c73, 0x2c76, 0x023f, 0x0240, 0x2c81, 0x2ce3, 0x2cec, 0x2cee, 0x2cf3, 0xa641,
+ 0xa66d, 0xa681, 0xa69b, 0xa723, 0xa72f, 0xa733, 0xa76f, 0xa77a, 0xa77c, 0x1d79, 0xa77f, 0xa787, 0xa78c,
+ 0x0265, 0xa791, 0xa793, 0xa797, 0xa7a9, 0x0266, 0x025c, 0x0261, 0x026c, 0x026a, 0x029e, 0x0287, 0x029d,
+ 0xab53, 0xa7b5, 0xa7bf, 0xa7c3, 0xa794, 0x0282, 0x1d8e, 0xa7c8, 0xa7ca, 0xa7f6, 0x13a0, 0x13ef, 0xff41,
+ 0xff5a, 0x10428, 0x1044f, 0x104d8, 0x104fb, 0x10cc0, 0x10cf2, 0x118c0, 0x118df, 0x16e60, 0x16e7f, 0x1e922,
+ 0x1e943
+ };
+ static const unsigned FOLD_MAP_2[] = {
+ S(0x00df), S(0x0130), S(0x0149), S(0x01f0), S(0x0587), S(0x1e96), S(0x1e97), S(0x1e98), S(0x1e99),
+ S(0x1e9a), S(0x1e9e), S(0x1f50), R(0x1f80,0x1f87), R(0x1f88,0x1f8f), R(0x1f90,0x1f97), R(0x1f98,0x1f9f),
+ R(0x1fa0,0x1fa7), R(0x1fa8,0x1faf), S(0x1fb2), S(0x1fb3), S(0x1fb4), S(0x1fb6), S(0x1fbc), S(0x1fc2),
+ S(0x1fc3), S(0x1fc4), S(0x1fc6), S(0x1fcc), S(0x1fd6), S(0x1fe4), S(0x1fe6), S(0x1ff2), S(0x1ff3),
+ S(0x1ff4), S(0x1ff6), S(0x1ffc), S(0xfb00), S(0xfb01), S(0xfb02), S(0xfb05), S(0xfb06), S(0xfb13),
+ S(0xfb14), S(0xfb15), S(0xfb16), S(0xfb17)
+ };
+ static const unsigned FOLD_MAP_2_DATA[] = {
+ 0x0073,0x0073, 0x0069,0x0307, 0x02bc,0x006e, 0x006a,0x030c, 0x0565,0x0582, 0x0068,0x0331, 0x0074,0x0308,
+ 0x0077,0x030a, 0x0079,0x030a, 0x0061,0x02be, 0x0073,0x0073, 0x03c5,0x0313, 0x1f00,0x03b9, 0x1f07,0x03b9,
+ 0x1f00,0x03b9, 0x1f07,0x03b9, 0x1f20,0x03b9, 0x1f27,0x03b9, 0x1f20,0x03b9, 0x1f27,0x03b9, 0x1f60,0x03b9,
+ 0x1f67,0x03b9, 0x1f60,0x03b9, 0x1f67,0x03b9, 0x1f70,0x03b9, 0x03b1,0x03b9, 0x03ac,0x03b9, 0x03b1,0x0342,
+ 0x03b1,0x03b9, 0x1f74,0x03b9, 0x03b7,0x03b9, 0x03ae,0x03b9, 0x03b7,0x0342, 0x03b7,0x03b9, 0x03b9,0x0342,
+ 0x03c1,0x0313, 0x03c5,0x0342, 0x1f7c,0x03b9, 0x03c9,0x03b9, 0x03ce,0x03b9, 0x03c9,0x0342, 0x03c9,0x03b9,
+ 0x0066,0x0066, 0x0066,0x0069, 0x0066,0x006c, 0x0073,0x0074, 0x0073,0x0074, 0x0574,0x0576, 0x0574,0x0565,
+ 0x0574,0x056b, 0x057e,0x0576, 0x0574,0x056d
+ };
+ static const unsigned FOLD_MAP_3[] = {
+ S(0x0390), S(0x03b0), S(0x1f52), S(0x1f54), S(0x1f56), S(0x1fb7), S(0x1fc7), S(0x1fd2), S(0x1fd3),
+ S(0x1fd7), S(0x1fe2), S(0x1fe3), S(0x1fe7), S(0x1ff7), S(0xfb03), S(0xfb04)
+ };
+ static const unsigned FOLD_MAP_3_DATA[] = {
+ 0x03b9,0x0308,0x0301, 0x03c5,0x0308,0x0301, 0x03c5,0x0313,0x0300, 0x03c5,0x0313,0x0301,
+ 0x03c5,0x0313,0x0342, 0x03b1,0x0342,0x03b9, 0x03b7,0x0342,0x03b9, 0x03b9,0x0308,0x0300,
+ 0x03b9,0x0308,0x0301, 0x03b9,0x0308,0x0342, 0x03c5,0x0308,0x0300, 0x03c5,0x0308,0x0301,
+ 0x03c5,0x0308,0x0342, 0x03c9,0x0342,0x03b9, 0x0066,0x0066,0x0069, 0x0066,0x0066,0x006c
+ };
+#undef R
+#undef S
+ static const struct {
+ const unsigned* map;
+ const unsigned* data;
+ size_t map_size;
+ unsigned n_codepoints;
+ } FOLD_MAP_LIST[] = {
+ { FOLD_MAP_1, FOLD_MAP_1_DATA, SIZEOF_ARRAY(FOLD_MAP_1), 1 },
+ { FOLD_MAP_2, FOLD_MAP_2_DATA, SIZEOF_ARRAY(FOLD_MAP_2), 2 },
+ { FOLD_MAP_3, FOLD_MAP_3_DATA, SIZEOF_ARRAY(FOLD_MAP_3), 3 }
+ };
+
+ int i;
+
+ /* Fast path for ASCII characters. */
+ if(codepoint <= 0x7f) {
+ info->codepoints[0] = codepoint;
+ if(ISUPPER_(codepoint))
+ info->codepoints[0] += 'a' - 'A';
+ info->n_codepoints = 1;
+ return;
+ }
+
+ /* Try to locate the codepoint in any of the maps. */
+ for(i = 0; i < (int) SIZEOF_ARRAY(FOLD_MAP_LIST); i++) {
+ int index;
+
+ index = md_unicode_bsearch__(codepoint, FOLD_MAP_LIST[i].map, FOLD_MAP_LIST[i].map_size);
+ if(index >= 0) {
+ /* Found the mapping. */
+ unsigned n_codepoints = FOLD_MAP_LIST[i].n_codepoints;
+ const unsigned* map = FOLD_MAP_LIST[i].map;
+ const unsigned* codepoints = FOLD_MAP_LIST[i].data + (index * n_codepoints);
+
+ memcpy(info->codepoints, codepoints, sizeof(unsigned) * n_codepoints);
+ info->n_codepoints = n_codepoints;
+
+ if(FOLD_MAP_LIST[i].map[index] != codepoint) {
+ /* The found mapping maps whole range of codepoints,
+ * i.e. we have to offset info->codepoints[0] accordingly. */
+ if((map[index] & 0x00ffffff)+1 == codepoints[0]) {
+ /* Alternating type of the range. */
+ info->codepoints[0] = codepoint + ((codepoint & 0x1) == (map[index] & 0x1) ? 1 : 0);
+ } else {
+ /* Range to range kind of mapping. */
+ info->codepoints[0] += (codepoint - (map[index] & 0x00ffffff));
+ }
+ }
+
+ return;
+ }
+ }
+
+ /* No mapping found. Map the codepoint to itself. */
+ info->codepoints[0] = codepoint;
+ info->n_codepoints = 1;
+ }
+#endif
+
+
+#if defined MD4C_USE_UTF16
+ #define IS_UTF16_SURROGATE_HI(word) (((WORD)(word) & 0xfc00) == 0xd800)
+ #define IS_UTF16_SURROGATE_LO(word) (((WORD)(word) & 0xfc00) == 0xdc00)
+ #define UTF16_DECODE_SURROGATE(hi, lo) (0x10000 + ((((unsigned)(hi) & 0x3ff) << 10) | (((unsigned)(lo) & 0x3ff) << 0)))
+
+ static unsigned
+ md_decode_utf16le__(const CHAR* str, SZ str_size, SZ* p_size)
+ {
+ if(IS_UTF16_SURROGATE_HI(str[0])) {
+ if(1 < str_size && IS_UTF16_SURROGATE_LO(str[1])) {
+ if(p_size != NULL)
+ *p_size = 2;
+ return UTF16_DECODE_SURROGATE(str[0], str[1]);
+ }
+ }
+
+ if(p_size != NULL)
+ *p_size = 1;
+ return str[0];
+ }
+
+ static unsigned
+ md_decode_utf16le_before__(MD_CTX* ctx, OFF off)
+ {
+ if(off > 2 && IS_UTF16_SURROGATE_HI(CH(off-2)) && IS_UTF16_SURROGATE_LO(CH(off-1)))
+ return UTF16_DECODE_SURROGATE(CH(off-2), CH(off-1));
+
+ return CH(off);
+ }
+
+ /* No whitespace uses surrogates, so no decoding needed here. */
+ #define ISUNICODEWHITESPACE_(codepoint) md_is_unicode_whitespace__(codepoint)
+ #define ISUNICODEWHITESPACE(off) md_is_unicode_whitespace__(CH(off))
+ #define ISUNICODEWHITESPACEBEFORE(off) md_is_unicode_whitespace__(CH((off)-1))
+
+ #define ISUNICODEPUNCT(off) md_is_unicode_punct__(md_decode_utf16le__(STR(off), ctx->size - (off), NULL))
+ #define ISUNICODEPUNCTBEFORE(off) md_is_unicode_punct__(md_decode_utf16le_before__(ctx, off))
+
+ static inline int
+ md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_char_size)
+ {
+ return md_decode_utf16le__(str+off, str_size-off, p_char_size);
+ }
+#elif defined MD4C_USE_UTF8
+ #define IS_UTF8_LEAD1(byte) ((unsigned char)(byte) <= 0x7f)
+ #define IS_UTF8_LEAD2(byte) (((unsigned char)(byte) & 0xe0) == 0xc0)
+ #define IS_UTF8_LEAD3(byte) (((unsigned char)(byte) & 0xf0) == 0xe0)
+ #define IS_UTF8_LEAD4(byte) (((unsigned char)(byte) & 0xf8) == 0xf0)
+ #define IS_UTF8_TAIL(byte) (((unsigned char)(byte) & 0xc0) == 0x80)
+
+ static unsigned
+ md_decode_utf8__(const CHAR* str, SZ str_size, SZ* p_size)
+ {
+ if(!IS_UTF8_LEAD1(str[0])) {
+ if(IS_UTF8_LEAD2(str[0])) {
+ if(1 < str_size && IS_UTF8_TAIL(str[1])) {
+ if(p_size != NULL)
+ *p_size = 2;
+
+ return (((unsigned int)str[0] & 0x1f) << 6) |
+ (((unsigned int)str[1] & 0x3f) << 0);
+ }
+ } else if(IS_UTF8_LEAD3(str[0])) {
+ if(2 < str_size && IS_UTF8_TAIL(str[1]) && IS_UTF8_TAIL(str[2])) {
+ if(p_size != NULL)
+ *p_size = 3;
+
+ return (((unsigned int)str[0] & 0x0f) << 12) |
+ (((unsigned int)str[1] & 0x3f) << 6) |
+ (((unsigned int)str[2] & 0x3f) << 0);
+ }
+ } else if(IS_UTF8_LEAD4(str[0])) {
+ if(3 < str_size && IS_UTF8_TAIL(str[1]) && IS_UTF8_TAIL(str[2]) && IS_UTF8_TAIL(str[3])) {
+ if(p_size != NULL)
+ *p_size = 4;
+
+ return (((unsigned int)str[0] & 0x07) << 18) |
+ (((unsigned int)str[1] & 0x3f) << 12) |
+ (((unsigned int)str[2] & 0x3f) << 6) |
+ (((unsigned int)str[3] & 0x3f) << 0);
+ }
+ }
+ }
+
+ if(p_size != NULL)
+ *p_size = 1;
+ return (unsigned) str[0];
+ }
+
+ static unsigned
+ md_decode_utf8_before__(MD_CTX* ctx, OFF off)
+ {
+ if(!IS_UTF8_LEAD1(CH(off-1))) {
+ if(off > 1 && IS_UTF8_LEAD2(CH(off-2)) && IS_UTF8_TAIL(CH(off-1)))
+ return (((unsigned int)CH(off-2) & 0x1f) << 6) |
+ (((unsigned int)CH(off-1) & 0x3f) << 0);
+
+ if(off > 2 && IS_UTF8_LEAD3(CH(off-3)) && IS_UTF8_TAIL(CH(off-2)) && IS_UTF8_TAIL(CH(off-1)))
+ return (((unsigned int)CH(off-3) & 0x0f) << 12) |
+ (((unsigned int)CH(off-2) & 0x3f) << 6) |
+ (((unsigned int)CH(off-1) & 0x3f) << 0);
+
+ if(off > 3 && IS_UTF8_LEAD4(CH(off-4)) && IS_UTF8_TAIL(CH(off-3)) && IS_UTF8_TAIL(CH(off-2)) && IS_UTF8_TAIL(CH(off-1)))
+ return (((unsigned int)CH(off-4) & 0x07) << 18) |
+ (((unsigned int)CH(off-3) & 0x3f) << 12) |
+ (((unsigned int)CH(off-2) & 0x3f) << 6) |
+ (((unsigned int)CH(off-1) & 0x3f) << 0);
+ }
+
+ return (unsigned) CH(off-1);
+ }
+
+ #define ISUNICODEWHITESPACE_(codepoint) md_is_unicode_whitespace__(codepoint)
+ #define ISUNICODEWHITESPACE(off) md_is_unicode_whitespace__(md_decode_utf8__(STR(off), ctx->size - (off), NULL))
+ #define ISUNICODEWHITESPACEBEFORE(off) md_is_unicode_whitespace__(md_decode_utf8_before__(ctx, off))
+
+ #define ISUNICODEPUNCT(off) md_is_unicode_punct__(md_decode_utf8__(STR(off), ctx->size - (off), NULL))
+ #define ISUNICODEPUNCTBEFORE(off) md_is_unicode_punct__(md_decode_utf8_before__(ctx, off))
+
+ static inline unsigned
+ md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_char_size)
+ {
+ return md_decode_utf8__(str+off, str_size-off, p_char_size);
+ }
+#else
+ #define ISUNICODEWHITESPACE_(codepoint) ISWHITESPACE_(codepoint)
+ #define ISUNICODEWHITESPACE(off) ISWHITESPACE(off)
+ #define ISUNICODEWHITESPACEBEFORE(off) ISWHITESPACE((off)-1)
+
+ #define ISUNICODEPUNCT(off) ISPUNCT(off)
+ #define ISUNICODEPUNCTBEFORE(off) ISPUNCT((off)-1)
+
+ static inline void
+ md_get_unicode_fold_info(unsigned codepoint, MD_UNICODE_FOLD_INFO* info)
+ {
+ info->codepoints[0] = codepoint;
+ if(ISUPPER_(codepoint))
+ info->codepoints[0] += 'a' - 'A';
+ info->n_codepoints = 1;
+ }
+
+ static inline unsigned
+ md_decode_unicode(const CHAR* str, OFF off, SZ str_size, SZ* p_size)
+ {
+ *p_size = 1;
+ return (unsigned) str[off];
+ }
+#endif
+
+
+/*************************************
+ *** Helper string manipulations ***
+ *************************************/
+
+/* Fill buffer with copy of the string between 'beg' and 'end' but replace any
+ * line breaks with given replacement character.
+ *
+ * NOTE: Caller is responsible to make sure the buffer is large enough.
+ * (Given the output is always shorter then input, (end - beg) is good idea
+ * what the caller should allocate.)
+ */
+static void
+md_merge_lines(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, int n_lines,
+ CHAR line_break_replacement_char, CHAR* buffer, SZ* p_size)
+{
+ CHAR* ptr = buffer;
+ int line_index = 0;
+ OFF off = beg;
+
+ MD_UNUSED(n_lines);
+
+ while(1) {
+ const MD_LINE* line = &lines[line_index];
+ OFF line_end = line->end;
+ if(end < line_end)
+ line_end = end;
+
+ while(off < line_end) {
+ *ptr = CH(off);
+ ptr++;
+ off++;
+ }
+
+ if(off >= end) {
+ *p_size = (MD_SIZE)(ptr - buffer);
+ return;
+ }
+
+ *ptr = line_break_replacement_char;
+ ptr++;
+
+ line_index++;
+ off = lines[line_index].beg;
+ }
+}
+
+/* Wrapper of md_merge_lines() which allocates new buffer for the output string.
+ */
+static int
+md_merge_lines_alloc(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, int n_lines,
+ CHAR line_break_replacement_char, CHAR** p_str, SZ* p_size)
+{
+ CHAR* buffer;
+
+ buffer = (CHAR*) malloc(sizeof(CHAR) * (end - beg));
+ if(buffer == NULL) {
+ MD_LOG("malloc() failed.");
+ return -1;
+ }
+
+ md_merge_lines(ctx, beg, end, lines, n_lines,
+ line_break_replacement_char, buffer, p_size);
+
+ *p_str = buffer;
+ return 0;
+}
+
+static OFF
+md_skip_unicode_whitespace(const CHAR* label, OFF off, SZ size)
+{
+ SZ char_size;
+ unsigned codepoint;
+
+ while(off < size) {
+ codepoint = md_decode_unicode(label, off, size, &char_size);
+ if(!ISUNICODEWHITESPACE_(codepoint) && !ISNEWLINE_(label[off]))
+ break;
+ off += char_size;
+ }
+
+ return off;
+}
+
+
+/******************************
+ *** Recognizing raw HTML ***
+ ******************************/
+
+/* md_is_html_tag() may be called when processing inlines (inline raw HTML)
+ * or when breaking document to blocks (checking for start of HTML block type 7).
+ *
+ * When breaking document to blocks, we do not yet know line boundaries, but
+ * in that case the whole tag has to live on a single line. We distinguish this
+ * by n_lines == 0.
+ */
+static int
+md_is_html_tag(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end)
+{
+ int attr_state;
+ OFF off = beg;
+ OFF line_end = (n_lines > 0) ? lines[0].end : ctx->size;
+ int i = 0;
+
+ MD_ASSERT(CH(beg) == _T('<'));
+
+ if(off + 1 >= line_end)
+ return FALSE;
+ off++;
+
+ /* For parsing attributes, we need a little state automaton below.
+ * State -1: no attributes are allowed.
+ * State 0: attribute could follow after some whitespace.
+ * State 1: after a whitespace (attribute name may follow).
+ * State 2: after attribute name ('=' MAY follow).
+ * State 3: after '=' (value specification MUST follow).
+ * State 41: in middle of unquoted attribute value.
+ * State 42: in middle of single-quoted attribute value.
+ * State 43: in middle of double-quoted attribute value.
+ */
+ attr_state = 0;
+
+ if(CH(off) == _T('/')) {
+ /* Closer tag "</ ... >". No attributes may be present. */
+ attr_state = -1;
+ off++;
+ }
+
+ /* Tag name */
+ if(off >= line_end || !ISALPHA(off))
+ return FALSE;
+ off++;
+ while(off < line_end && (ISALNUM(off) || CH(off) == _T('-')))
+ off++;
+
+ /* (Optional) attributes (if not closer), (optional) '/' (if not closer)
+ * and final '>'. */
+ while(1) {
+ while(off < line_end && !ISNEWLINE(off)) {
+ if(attr_state > 40) {
+ if(attr_state == 41 && (ISBLANK(off) || ISANYOF(off, _T("\"'=<>`")))) {
+ attr_state = 0;
+ off--; /* Put the char back for re-inspection in the new state. */
+ } else if(attr_state == 42 && CH(off) == _T('\'')) {
+ attr_state = 0;
+ } else if(attr_state == 43 && CH(off) == _T('"')) {
+ attr_state = 0;
+ }
+ off++;
+ } else if(ISWHITESPACE(off)) {
+ if(attr_state == 0)
+ attr_state = 1;
+ off++;
+ } else if(attr_state <= 2 && CH(off) == _T('>')) {
+ /* End. */
+ goto done;
+ } else if(attr_state <= 2 && CH(off) == _T('/') && off+1 < line_end && CH(off+1) == _T('>')) {
+ /* End with digraph '/>' */
+ off++;
+ goto done;
+ } else if((attr_state == 1 || attr_state == 2) && (ISALPHA(off) || CH(off) == _T('_') || CH(off) == _T(':'))) {
+ off++;
+ /* Attribute name */
+ while(off < line_end && (ISALNUM(off) || ISANYOF(off, _T("_.:-"))))
+ off++;
+ attr_state = 2;
+ } else if(attr_state == 2 && CH(off) == _T('=')) {
+ /* Attribute assignment sign */
+ off++;
+ attr_state = 3;
+ } else if(attr_state == 3) {
+ /* Expecting start of attribute value. */
+ if(CH(off) == _T('"'))
+ attr_state = 43;
+ else if(CH(off) == _T('\''))
+ attr_state = 42;
+ else if(!ISANYOF(off, _T("\"'=<>`")) && !ISNEWLINE(off))
+ attr_state = 41;
+ else
+ return FALSE;
+ off++;
+ } else {
+ /* Anything unexpected. */
+ return FALSE;
+ }
+ }
+
+ /* We have to be on a single line. See definition of start condition
+ * of HTML block, type 7. */
+ if(n_lines == 0)
+ return FALSE;
+
+ i++;
+ if(i >= n_lines)
+ return FALSE;
+
+ off = lines[i].beg;
+ line_end = lines[i].end;
+
+ if(attr_state == 0 || attr_state == 41)
+ attr_state = 1;
+
+ if(off >= max_end)
+ return FALSE;
+ }
+
+done:
+ if(off >= max_end)
+ return FALSE;
+
+ *p_end = off+1;
+ return TRUE;
+}
+
+static int
+md_scan_for_html_closer(MD_CTX* ctx, const MD_CHAR* str, MD_SIZE len,
+ const MD_LINE* lines, int n_lines,
+ OFF beg, OFF max_end, OFF* p_end,
+ OFF* p_scan_horizon)
+{
+ OFF off = beg;
+ int i = 0;
+
+ if(off < *p_scan_horizon && *p_scan_horizon >= max_end - len) {
+ /* We have already scanned the range up to the max_end so we know
+ * there is nothing to see. */
+ return FALSE;
+ }
+
+ while(TRUE) {
+ while(off + len <= lines[i].end && off + len <= max_end) {
+ if(md_ascii_eq(STR(off), str, len)) {
+ /* Success. */
+ *p_end = off + len;
+ return TRUE;
+ }
+ off++;
+ }
+
+ i++;
+ if(off >= max_end || i >= n_lines) {
+ /* Failure. */
+ *p_scan_horizon = off;
+ return FALSE;
+ }
+
+ off = lines[i].beg;
+ }
+}
+
+static int
+md_is_html_comment(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end)
+{
+ OFF off = beg;
+
+ MD_ASSERT(CH(beg) == _T('<'));
+
+ if(off + 4 >= lines[0].end)
+ return FALSE;
+ if(CH(off+1) != _T('!') || CH(off+2) != _T('-') || CH(off+3) != _T('-'))
+ return FALSE;
+ off += 4;
+
+ /* ">" and "->" must not follow the opening. */
+ if(off < lines[0].end && CH(off) == _T('>'))
+ return FALSE;
+ if(off+1 < lines[0].end && CH(off) == _T('-') && CH(off+1) == _T('>'))
+ return FALSE;
+
+ /* HTML comment must not contain "--", so we scan just for "--" instead
+ * of "-->" and verify manually that '>' follows. */
+ if(md_scan_for_html_closer(ctx, _T("--"), 2,
+ lines, n_lines, off, max_end, p_end, &ctx->html_comment_horizon))
+ {
+ if(*p_end < max_end && CH(*p_end) == _T('>')) {
+ *p_end = *p_end + 1;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static int
+md_is_html_processing_instruction(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end)
+{
+ OFF off = beg;
+
+ if(off + 2 >= lines[0].end)
+ return FALSE;
+ if(CH(off+1) != _T('?'))
+ return FALSE;
+ off += 2;
+
+ return md_scan_for_html_closer(ctx, _T("?>"), 2,
+ lines, n_lines, off, max_end, p_end, &ctx->html_proc_instr_horizon);
+}
+
+static int
+md_is_html_declaration(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end)
+{
+ OFF off = beg;
+
+ if(off + 2 >= lines[0].end)
+ return FALSE;
+ if(CH(off+1) != _T('!'))
+ return FALSE;
+ off += 2;
+
+ /* Declaration name. */
+ if(off >= lines[0].end || !ISALPHA(off))
+ return FALSE;
+ off++;
+ while(off < lines[0].end && ISALPHA(off))
+ off++;
+ if(off < lines[0].end && !ISWHITESPACE(off))
+ return FALSE;
+
+ return md_scan_for_html_closer(ctx, _T(">"), 1,
+ lines, n_lines, off, max_end, p_end, &ctx->html_decl_horizon);
+}
+
+static int
+md_is_html_cdata(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end)
+{
+ static const CHAR open_str[] = _T("<![CDATA[");
+ static const SZ open_size = SIZEOF_ARRAY(open_str) - 1;
+
+ OFF off = beg;
+
+ if(off + open_size >= lines[0].end)
+ return FALSE;
+ if(memcmp(STR(off), open_str, open_size) != 0)
+ return FALSE;
+ off += open_size;
+
+ if(lines[n_lines-1].end < max_end)
+ max_end = lines[n_lines-1].end - 2;
+
+ return md_scan_for_html_closer(ctx, _T("]]>"), 3,
+ lines, n_lines, off, max_end, p_end, &ctx->html_cdata_horizon);
+}
+
+static int
+md_is_html_any(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_end, OFF* p_end)
+{
+ MD_ASSERT(CH(beg) == _T('<'));
+ return (md_is_html_tag(ctx, lines, n_lines, beg, max_end, p_end) ||
+ md_is_html_comment(ctx, lines, n_lines, beg, max_end, p_end) ||
+ md_is_html_processing_instruction(ctx, lines, n_lines, beg, max_end, p_end) ||
+ md_is_html_declaration(ctx, lines, n_lines, beg, max_end, p_end) ||
+ md_is_html_cdata(ctx, lines, n_lines, beg, max_end, p_end));
+}
+
+
+/****************************
+ *** Recognizing Entity ***
+ ****************************/
+
+static int
+md_is_hex_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
+{
+ OFF off = beg;
+ MD_UNUSED(ctx);
+
+ while(off < max_end && ISXDIGIT_(text[off]) && off - beg <= 8)
+ off++;
+
+ if(1 <= off - beg && off - beg <= 6) {
+ *p_end = off;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static int
+md_is_dec_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
+{
+ OFF off = beg;
+ MD_UNUSED(ctx);
+
+ while(off < max_end && ISDIGIT_(text[off]) && off - beg <= 8)
+ off++;
+
+ if(1 <= off - beg && off - beg <= 7) {
+ *p_end = off;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static int
+md_is_named_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
+{
+ OFF off = beg;
+ MD_UNUSED(ctx);
+
+ if(off < max_end && ISALPHA_(text[off]))
+ off++;
+ else
+ return FALSE;
+
+ while(off < max_end && ISALNUM_(text[off]) && off - beg <= 48)
+ off++;
+
+ if(2 <= off - beg && off - beg <= 48) {
+ *p_end = off;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static int
+md_is_entity_str(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
+{
+ int is_contents;
+ OFF off = beg;
+
+ MD_ASSERT(text[off] == _T('&'));
+ off++;
+
+ if(off+2 < max_end && text[off] == _T('#') && (text[off+1] == _T('x') || text[off+1] == _T('X')))
+ is_contents = md_is_hex_entity_contents(ctx, text, off+2, max_end, &off);
+ else if(off+1 < max_end && text[off] == _T('#'))
+ is_contents = md_is_dec_entity_contents(ctx, text, off+1, max_end, &off);
+ else
+ is_contents = md_is_named_entity_contents(ctx, text, off, max_end, &off);
+
+ if(is_contents && off < max_end && text[off] == _T(';')) {
+ *p_end = off+1;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static inline int
+md_is_entity(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end)
+{
+ return md_is_entity_str(ctx, ctx->text, beg, max_end, p_end);
+}
+
+
+/******************************
+ *** Attribute Management ***
+ ******************************/
+
+typedef struct MD_ATTRIBUTE_BUILD_tag MD_ATTRIBUTE_BUILD;
+struct MD_ATTRIBUTE_BUILD_tag {
+ CHAR* text;
+ MD_TEXTTYPE* substr_types;
+ OFF* substr_offsets;
+ int substr_count;
+ int substr_alloc;
+ MD_TEXTTYPE trivial_types[1];
+ OFF trivial_offsets[2];
+};
+
+
+#define MD_BUILD_ATTR_NO_ESCAPES 0x0001
+
+static int
+md_build_attr_append_substr(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build,
+ MD_TEXTTYPE type, OFF off)
+{
+ if(build->substr_count >= build->substr_alloc) {
+ MD_TEXTTYPE* new_substr_types;
+ OFF* new_substr_offsets;
+
+ build->substr_alloc = (build->substr_alloc > 0
+ ? build->substr_alloc + build->substr_alloc / 2
+ : 8);
+ new_substr_types = (MD_TEXTTYPE*) realloc(build->substr_types,
+ build->substr_alloc * sizeof(MD_TEXTTYPE));
+ if(new_substr_types == NULL) {
+ MD_LOG("realloc() failed.");
+ return -1;
+ }
+ /* Note +1 to reserve space for final offset (== raw_size). */
+ new_substr_offsets = (OFF*) realloc(build->substr_offsets,
+ (build->substr_alloc+1) * sizeof(OFF));
+ if(new_substr_offsets == NULL) {
+ MD_LOG("realloc() failed.");
+ free(new_substr_types);
+ return -1;
+ }
+
+ build->substr_types = new_substr_types;
+ build->substr_offsets = new_substr_offsets;
+ }
+
+ build->substr_types[build->substr_count] = type;
+ build->substr_offsets[build->substr_count] = off;
+ build->substr_count++;
+ return 0;
+}
+
+static void
+md_free_attribute(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build)
+{
+ MD_UNUSED(ctx);
+
+ if(build->substr_alloc > 0) {
+ free(build->text);
+ free(build->substr_types);
+ free(build->substr_offsets);
+ }
+}
+
+static int
+md_build_attribute(MD_CTX* ctx, const CHAR* raw_text, SZ raw_size,
+ unsigned flags, MD_ATTRIBUTE* attr, MD_ATTRIBUTE_BUILD* build)
+{
+ OFF raw_off, off;
+ int is_trivial;
+ int ret = 0;
+
+ memset(build, 0, sizeof(MD_ATTRIBUTE_BUILD));
+
+ /* If there is no backslash and no ampersand, build trivial attribute
+ * without any malloc(). */
+ is_trivial = TRUE;
+ for(raw_off = 0; raw_off < raw_size; raw_off++) {
+ if(ISANYOF3_(raw_text[raw_off], _T('\\'), _T('&'), _T('\0'))) {
+ is_trivial = FALSE;
+ break;
+ }
+ }
+
+ if(is_trivial) {
+ build->text = (CHAR*) (raw_size ? raw_text : NULL);
+ build->substr_types = build->trivial_types;
+ build->substr_offsets = build->trivial_offsets;
+ build->substr_count = 1;
+ build->substr_alloc = 0;
+ build->trivial_types[0] = MD_TEXT_NORMAL;
+ build->trivial_offsets[0] = 0;
+ build->trivial_offsets[1] = raw_size;
+ off = raw_size;
+ } else {
+ build->text = (CHAR*) malloc(raw_size * sizeof(CHAR));
+ if(build->text == NULL) {
+ MD_LOG("malloc() failed.");
+ goto abort;
+ }
+
+ raw_off = 0;
+ off = 0;
+
+ while(raw_off < raw_size) {
+ if(raw_text[raw_off] == _T('\0')) {
+ MD_CHECK(md_build_attr_append_substr(ctx, build, MD_TEXT_NULLCHAR, off));
+ memcpy(build->text + off, raw_text + raw_off, 1);
+ off++;
+ raw_off++;
+ continue;
+ }
+
+ if(raw_text[raw_off] == _T('&')) {
+ OFF ent_end;
+
+ if(md_is_entity_str(ctx, raw_text, raw_off, raw_size, &ent_end)) {
+ MD_CHECK(md_build_attr_append_substr(ctx, build, MD_TEXT_ENTITY, off));
+ memcpy(build->text + off, raw_text + raw_off, ent_end - raw_off);
+ off += ent_end - raw_off;
+ raw_off = ent_end;
+ continue;
+ }
+ }
+
+ if(build->substr_count == 0 || build->substr_types[build->substr_count-1] != MD_TEXT_NORMAL)
+ MD_CHECK(md_build_attr_append_substr(ctx, build, MD_TEXT_NORMAL, off));
+
+ if(!(flags & MD_BUILD_ATTR_NO_ESCAPES) &&
+ raw_text[raw_off] == _T('\\') && raw_off+1 < raw_size &&
+ (ISPUNCT_(raw_text[raw_off+1]) || ISNEWLINE_(raw_text[raw_off+1])))
+ raw_off++;
+
+ build->text[off++] = raw_text[raw_off++];
+ }
+ build->substr_offsets[build->substr_count] = off;
+ }
+
+ attr->text = build->text;
+ attr->size = off;
+ attr->substr_offsets = build->substr_offsets;
+ attr->substr_types = build->substr_types;
+ return 0;
+
+abort:
+ md_free_attribute(ctx, build);
+ return -1;
+}
+
+
+/*********************************************
+ *** Dictionary of Reference Definitions ***
+ *********************************************/
+
+#define MD_FNV1A_BASE 2166136261U
+#define MD_FNV1A_PRIME 16777619U
+
+static inline unsigned
+md_fnv1a(unsigned base, const void* data, size_t n)
+{
+ const unsigned char* buf = (const unsigned char*) data;
+ unsigned hash = base;
+ size_t i;
+
+ for(i = 0; i < n; i++) {
+ hash ^= buf[i];
+ hash *= MD_FNV1A_PRIME;
+ }
+
+ return hash;
+}
+
+
+struct MD_REF_DEF_tag {
+ CHAR* label;
+ CHAR* title;
+ unsigned hash;
+ SZ label_size;
+ SZ title_size;
+ OFF dest_beg;
+ OFF dest_end;
+ unsigned char label_needs_free : 1;
+ unsigned char title_needs_free : 1;
+};
+
+/* Label equivalence is quite complicated with regards to whitespace and case
+ * folding. This complicates computing a hash of it as well as direct comparison
+ * of two labels. */
+
+static unsigned
+md_link_label_hash(const CHAR* label, SZ size)
+{
+ unsigned hash = MD_FNV1A_BASE;
+ OFF off;
+ unsigned codepoint;
+ int is_whitespace = FALSE;
+
+ off = md_skip_unicode_whitespace(label, 0, size);
+ while(off < size) {
+ SZ char_size;
+
+ codepoint = md_decode_unicode(label, off, size, &char_size);
+ is_whitespace = ISUNICODEWHITESPACE_(codepoint) || ISNEWLINE_(label[off]);
+
+ if(is_whitespace) {
+ codepoint = ' ';
+ hash = md_fnv1a(hash, &codepoint, sizeof(unsigned));
+ off = md_skip_unicode_whitespace(label, off, size);
+ } else {
+ MD_UNICODE_FOLD_INFO fold_info;
+
+ md_get_unicode_fold_info(codepoint, &fold_info);
+ hash = md_fnv1a(hash, fold_info.codepoints, fold_info.n_codepoints * sizeof(unsigned));
+ off += char_size;
+ }
+ }
+
+ return hash;
+}
+
+static OFF
+md_link_label_cmp_load_fold_info(const CHAR* label, OFF off, SZ size,
+ MD_UNICODE_FOLD_INFO* fold_info)
+{
+ unsigned codepoint;
+ SZ char_size;
+
+ if(off >= size) {
+ /* Treat end of a link label as a whitespace. */
+ goto whitespace;
+ }
+
+ codepoint = md_decode_unicode(label, off, size, &char_size);
+ off += char_size;
+ if(ISUNICODEWHITESPACE_(codepoint)) {
+ /* Treat all whitespace as equivalent */
+ goto whitespace;
+ }
+
+ /* Get real folding info. */
+ md_get_unicode_fold_info(codepoint, fold_info);
+ return off;
+
+whitespace:
+ fold_info->codepoints[0] = _T(' ');
+ fold_info->n_codepoints = 1;
+ return md_skip_unicode_whitespace(label, off, size);
+}
+
+static int
+md_link_label_cmp(const CHAR* a_label, SZ a_size, const CHAR* b_label, SZ b_size)
+{
+ OFF a_off;
+ OFF b_off;
+ MD_UNICODE_FOLD_INFO a_fi = { { 0 }, 0 };
+ MD_UNICODE_FOLD_INFO b_fi = { { 0 }, 0 };
+ OFF a_fi_off = 0;
+ OFF b_fi_off = 0;
+ int cmp;
+
+ a_off = md_skip_unicode_whitespace(a_label, 0, a_size);
+ b_off = md_skip_unicode_whitespace(b_label, 0, b_size);
+ while(a_off < a_size || a_fi_off < a_fi.n_codepoints ||
+ b_off < b_size || b_fi_off < b_fi.n_codepoints)
+ {
+ /* If needed, load fold info for next char. */
+ if(a_fi_off >= a_fi.n_codepoints) {
+ a_fi_off = 0;
+ a_off = md_link_label_cmp_load_fold_info(a_label, a_off, a_size, &a_fi);
+ }
+ if(b_fi_off >= b_fi.n_codepoints) {
+ b_fi_off = 0;
+ b_off = md_link_label_cmp_load_fold_info(b_label, b_off, b_size, &b_fi);
+ }
+
+ cmp = b_fi.codepoints[b_fi_off] - a_fi.codepoints[a_fi_off];
+ if(cmp != 0)
+ return cmp;
+
+ a_fi_off++;
+ b_fi_off++;
+ }
+
+ return 0;
+}
+
+typedef struct MD_REF_DEF_LIST_tag MD_REF_DEF_LIST;
+struct MD_REF_DEF_LIST_tag {
+ int n_ref_defs;
+ int alloc_ref_defs;
+ MD_REF_DEF* ref_defs[]; /* Valid items always point into ctx->ref_defs[] */
+};
+
+static int
+md_ref_def_cmp(const void* a, const void* b)
+{
+ const MD_REF_DEF* a_ref = *(const MD_REF_DEF**)a;
+ const MD_REF_DEF* b_ref = *(const MD_REF_DEF**)b;
+
+ if(a_ref->hash < b_ref->hash)
+ return -1;
+ else if(a_ref->hash > b_ref->hash)
+ return +1;
+ else
+ return md_link_label_cmp(a_ref->label, a_ref->label_size, b_ref->label, b_ref->label_size);
+}
+
+static int
+md_ref_def_cmp_for_sort(const void* a, const void* b)
+{
+ int cmp;
+
+ cmp = md_ref_def_cmp(a, b);
+
+ /* Ensure stability of the sorting. */
+ if(cmp == 0) {
+ const MD_REF_DEF* a_ref = *(const MD_REF_DEF**)a;
+ const MD_REF_DEF* b_ref = *(const MD_REF_DEF**)b;
+
+ if(a_ref < b_ref)
+ cmp = -1;
+ else if(a_ref > b_ref)
+ cmp = +1;
+ else
+ cmp = 0;
+ }
+
+ return cmp;
+}
+
+static int
+md_build_ref_def_hashtable(MD_CTX* ctx)
+{
+ int i, j;
+
+ if(ctx->n_ref_defs == 0)
+ return 0;
+
+ ctx->ref_def_hashtable_size = (ctx->n_ref_defs * 5) / 4;
+ ctx->ref_def_hashtable = malloc(ctx->ref_def_hashtable_size * sizeof(void*));
+ if(ctx->ref_def_hashtable == NULL) {
+ MD_LOG("malloc() failed.");
+ goto abort;
+ }
+ memset(ctx->ref_def_hashtable, 0, ctx->ref_def_hashtable_size * sizeof(void*));
+
+ /* Each member of ctx->ref_def_hashtable[] can be:
+ * -- NULL,
+ * -- pointer to the MD_REF_DEF in ctx->ref_defs[], or
+ * -- pointer to a MD_REF_DEF_LIST, which holds multiple pointers to
+ * such MD_REF_DEFs.
+ */
+ for(i = 0; i < ctx->n_ref_defs; i++) {
+ MD_REF_DEF* def = &ctx->ref_defs[i];
+ void* bucket;
+ MD_REF_DEF_LIST* list;
+
+ def->hash = md_link_label_hash(def->label, def->label_size);
+ bucket = ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size];
+
+ if(bucket == NULL) {
+ /* The bucket is empty. Make it just point to the def. */
+ ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size] = def;
+ continue;
+ }
+
+ if(ctx->ref_defs <= (MD_REF_DEF*) bucket && (MD_REF_DEF*) bucket < ctx->ref_defs + ctx->n_ref_defs) {
+ /* The bucket already contains one ref. def. Lets see whether it
+ * is the same label (ref. def. duplicate) or different one
+ * (hash conflict). */
+ MD_REF_DEF* old_def = (MD_REF_DEF*) bucket;
+
+ if(md_link_label_cmp(def->label, def->label_size, old_def->label, old_def->label_size) == 0) {
+ /* Duplicate label: Ignore this ref. def. */
+ continue;
+ }
+
+ /* Make the bucket complex, i.e. able to hold more ref. defs. */
+ list = (MD_REF_DEF_LIST*) malloc(sizeof(MD_REF_DEF_LIST) + 2 * sizeof(MD_REF_DEF*));
+ if(list == NULL) {
+ MD_LOG("malloc() failed.");
+ goto abort;
+ }
+ list->ref_defs[0] = old_def;
+ list->ref_defs[1] = def;
+ list->n_ref_defs = 2;
+ list->alloc_ref_defs = 2;
+ ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size] = list;
+ continue;
+ }
+
+ /* Append the def to the complex bucket list.
+ *
+ * Note in this case we ignore potential duplicates to avoid expensive
+ * iterating over the complex bucket. Below, we revisit all the complex
+ * buckets and handle it more cheaply after the complex bucket contents
+ * is sorted. */
+ list = (MD_REF_DEF_LIST*) bucket;
+ if(list->n_ref_defs >= list->alloc_ref_defs) {
+ int alloc_ref_defs = list->alloc_ref_defs + list->alloc_ref_defs / 2;
+ MD_REF_DEF_LIST* list_tmp = (MD_REF_DEF_LIST*) realloc(list,
+ sizeof(MD_REF_DEF_LIST) + alloc_ref_defs * sizeof(MD_REF_DEF*));
+ if(list_tmp == NULL) {
+ MD_LOG("realloc() failed.");
+ goto abort;
+ }
+ list = list_tmp;
+ list->alloc_ref_defs = alloc_ref_defs;
+ ctx->ref_def_hashtable[def->hash % ctx->ref_def_hashtable_size] = list;
+ }
+
+ list->ref_defs[list->n_ref_defs] = def;
+ list->n_ref_defs++;
+ }
+
+ /* Sort the complex buckets so we can use bsearch() with them. */
+ for(i = 0; i < ctx->ref_def_hashtable_size; i++) {
+ void* bucket = ctx->ref_def_hashtable[i];
+ MD_REF_DEF_LIST* list;
+
+ if(bucket == NULL)
+ continue;
+ if(ctx->ref_defs <= (MD_REF_DEF*) bucket && (MD_REF_DEF*) bucket < ctx->ref_defs + ctx->n_ref_defs)
+ continue;
+
+ list = (MD_REF_DEF_LIST*) bucket;
+ qsort(list->ref_defs, list->n_ref_defs, sizeof(MD_REF_DEF*), md_ref_def_cmp_for_sort);
+
+ /* Disable all duplicates in the complex bucket by forcing all such
+ * records to point to the 1st such ref. def. I.e. no matter which
+ * record is found during the lookup, it will always point to the right
+ * ref. def. in ctx->ref_defs[]. */
+ for(j = 1; j < list->n_ref_defs; j++) {
+ if(md_ref_def_cmp(&list->ref_defs[j-1], &list->ref_defs[j]) == 0)
+ list->ref_defs[j] = list->ref_defs[j-1];
+ }
+ }
+
+ return 0;
+
+abort:
+ return -1;
+}
+
+static void
+md_free_ref_def_hashtable(MD_CTX* ctx)
+{
+ if(ctx->ref_def_hashtable != NULL) {
+ int i;
+
+ for(i = 0; i < ctx->ref_def_hashtable_size; i++) {
+ void* bucket = ctx->ref_def_hashtable[i];
+ if(bucket == NULL)
+ continue;
+ if(ctx->ref_defs <= (MD_REF_DEF*) bucket && (MD_REF_DEF*) bucket < ctx->ref_defs + ctx->n_ref_defs)
+ continue;
+ free(bucket);
+ }
+
+ free(ctx->ref_def_hashtable);
+ }
+}
+
+static const MD_REF_DEF*
+md_lookup_ref_def(MD_CTX* ctx, const CHAR* label, SZ label_size)
+{
+ unsigned hash;
+ void* bucket;
+
+ if(ctx->ref_def_hashtable_size == 0)
+ return NULL;
+
+ hash = md_link_label_hash(label, label_size);
+ bucket = ctx->ref_def_hashtable[hash % ctx->ref_def_hashtable_size];
+
+ if(bucket == NULL) {
+ return NULL;
+ } else if(ctx->ref_defs <= (MD_REF_DEF*) bucket && (MD_REF_DEF*) bucket < ctx->ref_defs + ctx->n_ref_defs) {
+ const MD_REF_DEF* def = (MD_REF_DEF*) bucket;
+
+ if(md_link_label_cmp(def->label, def->label_size, label, label_size) == 0)
+ return def;
+ else
+ return NULL;
+ } else {
+ MD_REF_DEF_LIST* list = (MD_REF_DEF_LIST*) bucket;
+ MD_REF_DEF key_buf;
+ const MD_REF_DEF* key = &key_buf;
+ const MD_REF_DEF** ret;
+
+ key_buf.label = (CHAR*) label;
+ key_buf.label_size = label_size;
+ key_buf.hash = md_link_label_hash(key_buf.label, key_buf.label_size);
+
+ ret = (const MD_REF_DEF**) bsearch(&key, list->ref_defs,
+ list->n_ref_defs, sizeof(MD_REF_DEF*), md_ref_def_cmp);
+ if(ret != NULL)
+ return *ret;
+ else
+ return NULL;
+ }
+}
+
+
+/***************************
+ *** Recognizing Links ***
+ ***************************/
+
+/* Note this code is partially shared between processing inlines and blocks
+ * as reference definitions and links share some helper parser functions.
+ */
+
+typedef struct MD_LINK_ATTR_tag MD_LINK_ATTR;
+struct MD_LINK_ATTR_tag {
+ OFF dest_beg;
+ OFF dest_end;
+
+ CHAR* title;
+ SZ title_size;
+ int title_needs_free;
+};
+
+
+static int
+md_is_link_label(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg,
+ OFF* p_end, int* p_beg_line_index, int* p_end_line_index,
+ OFF* p_contents_beg, OFF* p_contents_end)
+{
+ OFF off = beg;
+ OFF contents_beg = 0;
+ OFF contents_end = 0;
+ int line_index = 0;
+ int len = 0;
+
+ if(CH(off) != _T('['))
+ return FALSE;
+ off++;
+
+ while(1) {
+ OFF line_end = lines[line_index].end;
+
+ while(off < line_end) {
+ if(CH(off) == _T('\\') && off+1 < ctx->size && (ISPUNCT(off+1) || ISNEWLINE(off+1))) {
+ if(contents_end == 0) {
+ contents_beg = off;
+ *p_beg_line_index = line_index;
+ }
+ contents_end = off + 2;
+ off += 2;
+ } else if(CH(off) == _T('[')) {
+ return FALSE;
+ } else if(CH(off) == _T(']')) {
+ if(contents_beg < contents_end) {
+ /* Success. */
+ *p_contents_beg = contents_beg;
+ *p_contents_end = contents_end;
+ *p_end = off+1;
+ *p_end_line_index = line_index;
+ return TRUE;
+ } else {
+ /* Link label must have some non-whitespace contents. */
+ return FALSE;
+ }
+ } else {
+ unsigned codepoint;
+ SZ char_size;
+
+ codepoint = md_decode_unicode(ctx->text, off, ctx->size, &char_size);
+ if(!ISUNICODEWHITESPACE_(codepoint)) {
+ if(contents_end == 0) {
+ contents_beg = off;
+ *p_beg_line_index = line_index;
+ }
+ contents_end = off + char_size;
+ }
+
+ off += char_size;
+ }
+
+ len++;
+ if(len > 999)
+ return FALSE;
+ }
+
+ line_index++;
+ len++;
+ if(line_index < n_lines)
+ off = lines[line_index].beg;
+ else
+ break;
+ }
+
+ return FALSE;
+}
+
+static int
+md_is_link_destination_A(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end,
+ OFF* p_contents_beg, OFF* p_contents_end)
+{
+ OFF off = beg;
+
+ if(off >= max_end || CH(off) != _T('<'))
+ return FALSE;
+ off++;
+
+ while(off < max_end) {
+ if(CH(off) == _T('\\') && off+1 < max_end && ISPUNCT(off+1)) {
+ off += 2;
+ continue;
+ }
+
+ if(ISNEWLINE(off) || CH(off) == _T('<'))
+ return FALSE;
+
+ if(CH(off) == _T('>')) {
+ /* Success. */
+ *p_contents_beg = beg+1;
+ *p_contents_end = off;
+ *p_end = off+1;
+ return TRUE;
+ }
+
+ off++;
+ }
+
+ return FALSE;
+}
+
+static int
+md_is_link_destination_B(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end,
+ OFF* p_contents_beg, OFF* p_contents_end)
+{
+ OFF off = beg;
+ int parenthesis_level = 0;
+
+ while(off < max_end) {
+ if(CH(off) == _T('\\') && off+1 < max_end && ISPUNCT(off+1)) {
+ off += 2;
+ continue;
+ }
+
+ if(ISWHITESPACE(off) || ISCNTRL(off))
+ break;
+
+ /* Link destination may include balanced pairs of unescaped '(' ')'.
+ * Note we limit the maximal nesting level by 32 to protect us from
+ * https://github.com/jgm/cmark/issues/214 */
+ if(CH(off) == _T('(')) {
+ parenthesis_level++;
+ if(parenthesis_level > 32)
+ return FALSE;
+ } else if(CH(off) == _T(')')) {
+ if(parenthesis_level == 0)
+ break;
+ parenthesis_level--;
+ }
+
+ off++;
+ }
+
+ if(parenthesis_level != 0 || off == beg)
+ return FALSE;
+
+ /* Success. */
+ *p_contents_beg = beg;
+ *p_contents_end = off;
+ *p_end = off;
+ return TRUE;
+}
+
+static inline int
+md_is_link_destination(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end,
+ OFF* p_contents_beg, OFF* p_contents_end)
+{
+ if(CH(beg) == _T('<'))
+ return md_is_link_destination_A(ctx, beg, max_end, p_end, p_contents_beg, p_contents_end);
+ else
+ return md_is_link_destination_B(ctx, beg, max_end, p_end, p_contents_beg, p_contents_end);
+}
+
+static int
+md_is_link_title(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg,
+ OFF* p_end, int* p_beg_line_index, int* p_end_line_index,
+ OFF* p_contents_beg, OFF* p_contents_end)
+{
+ OFF off = beg;
+ CHAR closer_char;
+ int line_index = 0;
+
+ /* White space with up to one line break. */
+ while(off < lines[line_index].end && ISWHITESPACE(off))
+ off++;
+ if(off >= lines[line_index].end) {
+ line_index++;
+ if(line_index >= n_lines)
+ return FALSE;
+ off = lines[line_index].beg;
+ }
+ if(off == beg)
+ return FALSE;
+
+ *p_beg_line_index = line_index;
+
+ /* First char determines how to detect end of it. */
+ switch(CH(off)) {
+ case _T('"'): closer_char = _T('"'); break;
+ case _T('\''): closer_char = _T('\''); break;
+ case _T('('): closer_char = _T(')'); break;
+ default: return FALSE;
+ }
+ off++;
+
+ *p_contents_beg = off;
+
+ while(line_index < n_lines) {
+ OFF line_end = lines[line_index].end;
+
+ while(off < line_end) {
+ if(CH(off) == _T('\\') && off+1 < ctx->size && (ISPUNCT(off+1) || ISNEWLINE(off+1))) {
+ off++;
+ } else if(CH(off) == closer_char) {
+ /* Success. */
+ *p_contents_end = off;
+ *p_end = off+1;
+ *p_end_line_index = line_index;
+ return TRUE;
+ } else if(closer_char == _T(')') && CH(off) == _T('(')) {
+ /* ()-style title cannot contain (unescaped '(')) */
+ return FALSE;
+ }
+
+ off++;
+ }
+
+ line_index++;
+ }
+
+ return FALSE;
+}
+
+/* Returns 0 if it is not a reference definition.
+ *
+ * Returns N > 0 if it is a reference definition. N then corresponds to the
+ * number of lines forming it). In this case the definition is stored for
+ * resolving any links referring to it.
+ *
+ * Returns -1 in case of an error (out of memory).
+ */
+static int
+md_is_link_reference_definition(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
+{
+ OFF label_contents_beg;
+ OFF label_contents_end;
+ int label_contents_line_index = -1;
+ int label_is_multiline = FALSE;
+ OFF dest_contents_beg;
+ OFF dest_contents_end;
+ OFF title_contents_beg;
+ OFF title_contents_end;
+ int title_contents_line_index;
+ int title_is_multiline = FALSE;
+ OFF off;
+ int line_index = 0;
+ int tmp_line_index;
+ MD_REF_DEF* def = NULL;
+ int ret = 0;
+
+ /* Link label. */
+ if(!md_is_link_label(ctx, lines, n_lines, lines[0].beg,
+ &off, &label_contents_line_index, &line_index,
+ &label_contents_beg, &label_contents_end))
+ return FALSE;
+ label_is_multiline = (label_contents_line_index != line_index);
+
+ /* Colon. */
+ if(off >= lines[line_index].end || CH(off) != _T(':'))
+ return FALSE;
+ off++;
+
+ /* Optional white space with up to one line break. */
+ while(off < lines[line_index].end && ISWHITESPACE(off))
+ off++;
+ if(off >= lines[line_index].end) {
+ line_index++;
+ if(line_index >= n_lines)
+ return FALSE;
+ off = lines[line_index].beg;
+ }
+
+ /* Link destination. */
+ if(!md_is_link_destination(ctx, off, lines[line_index].end,
+ &off, &dest_contents_beg, &dest_contents_end))
+ return FALSE;
+
+ /* (Optional) title. Note we interpret it as an title only if nothing
+ * more follows on its last line. */
+ if(md_is_link_title(ctx, lines + line_index, n_lines - line_index, off,
+ &off, &title_contents_line_index, &tmp_line_index,
+ &title_contents_beg, &title_contents_end)
+ && off >= lines[line_index + tmp_line_index].end)
+ {
+ title_is_multiline = (tmp_line_index != title_contents_line_index);
+ title_contents_line_index += line_index;
+ line_index += tmp_line_index;
+ } else {
+ /* Not a title. */
+ title_is_multiline = FALSE;
+ title_contents_beg = off;
+ title_contents_end = off;
+ title_contents_line_index = 0;
+ }
+
+ /* Nothing more can follow on the last line. */
+ if(off < lines[line_index].end)
+ return FALSE;
+
+ /* So, it _is_ a reference definition. Remember it. */
+ if(ctx->n_ref_defs >= ctx->alloc_ref_defs) {
+ MD_REF_DEF* new_defs;
+
+ ctx->alloc_ref_defs = (ctx->alloc_ref_defs > 0
+ ? ctx->alloc_ref_defs + ctx->alloc_ref_defs / 2
+ : 16);
+ new_defs = (MD_REF_DEF*) realloc(ctx->ref_defs, ctx->alloc_ref_defs * sizeof(MD_REF_DEF));
+ if(new_defs == NULL) {
+ MD_LOG("realloc() failed.");
+ goto abort;
+ }
+
+ ctx->ref_defs = new_defs;
+ }
+ def = &ctx->ref_defs[ctx->n_ref_defs];
+ memset(def, 0, sizeof(MD_REF_DEF));
+
+ if(label_is_multiline) {
+ MD_CHECK(md_merge_lines_alloc(ctx, label_contents_beg, label_contents_end,
+ lines + label_contents_line_index, n_lines - label_contents_line_index,
+ _T(' '), &def->label, &def->label_size));
+ def->label_needs_free = TRUE;
+ } else {
+ def->label = (CHAR*) STR(label_contents_beg);
+ def->label_size = label_contents_end - label_contents_beg;
+ }
+
+ if(title_is_multiline) {
+ MD_CHECK(md_merge_lines_alloc(ctx, title_contents_beg, title_contents_end,
+ lines + title_contents_line_index, n_lines - title_contents_line_index,
+ _T('\n'), &def->title, &def->title_size));
+ def->title_needs_free = TRUE;
+ } else {
+ def->title = (CHAR*) STR(title_contents_beg);
+ def->title_size = title_contents_end - title_contents_beg;
+ }
+
+ def->dest_beg = dest_contents_beg;
+ def->dest_end = dest_contents_end;
+
+ /* Success. */
+ ctx->n_ref_defs++;
+ return line_index + 1;
+
+abort:
+ /* Failure. */
+ if(def != NULL && def->label_needs_free)
+ free(def->label);
+ if(def != NULL && def->title_needs_free)
+ free(def->title);
+ return ret;
+}
+
+static int
+md_is_link_reference(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
+ OFF beg, OFF end, MD_LINK_ATTR* attr)
+{
+ const MD_REF_DEF* def;
+ const MD_LINE* beg_line;
+ int is_multiline;
+ CHAR* label;
+ SZ label_size;
+ int ret;
+
+ MD_ASSERT(CH(beg) == _T('[') || CH(beg) == _T('!'));
+ MD_ASSERT(CH(end-1) == _T(']'));
+
+ beg += (CH(beg) == _T('!') ? 2 : 1);
+ end--;
+
+ /* Find lines corresponding to the beg and end positions. */
+ beg_line = md_lookup_line(beg, lines, n_lines);
+ is_multiline = (end > beg_line->end);
+
+ if(is_multiline) {
+ MD_CHECK(md_merge_lines_alloc(ctx, beg, end, beg_line,
+ (int)(n_lines - (beg_line - lines)), _T(' '), &label, &label_size));
+ } else {
+ label = (CHAR*) STR(beg);
+ label_size = end - beg;
+ }
+
+ def = md_lookup_ref_def(ctx, label, label_size);
+ if(def != NULL) {
+ attr->dest_beg = def->dest_beg;
+ attr->dest_end = def->dest_end;
+ attr->title = def->title;
+ attr->title_size = def->title_size;
+ attr->title_needs_free = FALSE;
+ }
+
+ if(is_multiline)
+ free(label);
+
+ ret = (def != NULL);
+
+abort:
+ return ret;
+}
+
+static int
+md_is_inline_link_spec(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
+ OFF beg, OFF* p_end, MD_LINK_ATTR* attr)
+{
+ int line_index = 0;
+ int tmp_line_index;
+ OFF title_contents_beg;
+ OFF title_contents_end;
+ int title_contents_line_index;
+ int title_is_multiline;
+ OFF off = beg;
+ int ret = FALSE;
+
+ while(off >= lines[line_index].end)
+ line_index++;
+
+ MD_ASSERT(CH(off) == _T('('));
+ off++;
+
+ /* Optional white space with up to one line break. */
+ while(off < lines[line_index].end && ISWHITESPACE(off))
+ off++;
+ if(off >= lines[line_index].end && (off >= ctx->size || ISNEWLINE(off))) {
+ line_index++;
+ if(line_index >= n_lines)
+ return FALSE;
+ off = lines[line_index].beg;
+ }
+
+ /* Link destination may be omitted, but only when not also having a title. */
+ if(off < ctx->size && CH(off) == _T(')')) {
+ attr->dest_beg = off;
+ attr->dest_end = off;
+ attr->title = NULL;
+ attr->title_size = 0;
+ attr->title_needs_free = FALSE;
+ off++;
+ *p_end = off;
+ return TRUE;
+ }
+
+ /* Link destination. */
+ if(!md_is_link_destination(ctx, off, lines[line_index].end,
+ &off, &attr->dest_beg, &attr->dest_end))
+ return FALSE;
+
+ /* (Optional) title. */
+ if(md_is_link_title(ctx, lines + line_index, n_lines - line_index, off,
+ &off, &title_contents_line_index, &tmp_line_index,
+ &title_contents_beg, &title_contents_end))
+ {
+ title_is_multiline = (tmp_line_index != title_contents_line_index);
+ title_contents_line_index += line_index;
+ line_index += tmp_line_index;
+ } else {
+ /* Not a title. */
+ title_is_multiline = FALSE;
+ title_contents_beg = off;
+ title_contents_end = off;
+ title_contents_line_index = 0;
+ }
+
+ /* Optional whitespace followed with final ')'. */
+ while(off < lines[line_index].end && ISWHITESPACE(off))
+ off++;
+ if (off >= lines[line_index].end && (off >= ctx->size || ISNEWLINE(off))) {
+ line_index++;
+ if(line_index >= n_lines)
+ return FALSE;
+ off = lines[line_index].beg;
+ }
+ if(CH(off) != _T(')'))
+ goto abort;
+ off++;
+
+ if(title_contents_beg >= title_contents_end) {
+ attr->title = NULL;
+ attr->title_size = 0;
+ attr->title_needs_free = FALSE;
+ } else if(!title_is_multiline) {
+ attr->title = (CHAR*) STR(title_contents_beg);
+ attr->title_size = title_contents_end - title_contents_beg;
+ attr->title_needs_free = FALSE;
+ } else {
+ MD_CHECK(md_merge_lines_alloc(ctx, title_contents_beg, title_contents_end,
+ lines + title_contents_line_index, n_lines - title_contents_line_index,
+ _T('\n'), &attr->title, &attr->title_size));
+ attr->title_needs_free = TRUE;
+ }
+
+ *p_end = off;
+ ret = TRUE;
+
+abort:
+ return ret;
+}
+
+static void
+md_free_ref_defs(MD_CTX* ctx)
+{
+ int i;
+
+ for(i = 0; i < ctx->n_ref_defs; i++) {
+ MD_REF_DEF* def = &ctx->ref_defs[i];
+
+ if(def->label_needs_free)
+ free(def->label);
+ if(def->title_needs_free)
+ free(def->title);
+ }
+
+ free(ctx->ref_defs);
+}
+
+
+/******************************************
+ *** Processing Inlines (a.k.a Spans) ***
+ ******************************************/
+
+/* We process inlines in few phases:
+ *
+ * (1) We go through the block text and collect all significant characters
+ * which may start/end a span or some other significant position into
+ * ctx->marks[]. Core of this is what md_collect_marks() does.
+ *
+ * We also do some very brief preliminary context-less analysis, whether
+ * it might be opener or closer (e.g. of an emphasis span).
+ *
+ * This speeds the other steps as we do not need to re-iterate over all
+ * characters anymore.
+ *
+ * (2) We analyze each potential mark types, in order by their precedence.
+ *
+ * In each md_analyze_XXX() function, we re-iterate list of the marks,
+ * skipping already resolved regions (in preceding precedences) and try to
+ * resolve them.
+ *
+ * (2.1) For trivial marks, which are single (e.g. HTML entity), we just mark
+ * them as resolved.
+ *
+ * (2.2) For range-type marks, we analyze whether the mark could be closer
+ * and, if yes, whether there is some preceding opener it could satisfy.
+ *
+ * If not we check whether it could be really an opener and if yes, we
+ * remember it so subsequent closers may resolve it.
+ *
+ * (3) Finally, when all marks were analyzed, we render the block contents
+ * by calling MD_RENDERER::text() callback, interrupting by ::enter_span()
+ * or ::close_span() whenever we reach a resolved mark.
+ */
+
+
+/* The mark structure.
+ *
+ * '\\': Maybe escape sequence.
+ * '\0': NULL char.
+ * '*': Maybe (strong) emphasis start/end.
+ * '_': Maybe (strong) emphasis start/end.
+ * '~': Maybe strikethrough start/end (needs MD_FLAG_STRIKETHROUGH).
+ * '`': Maybe code span start/end.
+ * '&': Maybe start of entity.
+ * ';': Maybe end of entity.
+ * '<': Maybe start of raw HTML or autolink.
+ * '>': Maybe end of raw HTML or autolink.
+ * '[': Maybe start of link label or link text.
+ * '!': Equivalent of '[' for image.
+ * ']': Maybe end of link label or link text.
+ * '@': Maybe permissive e-mail auto-link (needs MD_FLAG_PERMISSIVEEMAILAUTOLINKS).
+ * ':': Maybe permissive URL auto-link (needs MD_FLAG_PERMISSIVEURLAUTOLINKS).
+ * '.': Maybe permissive WWW auto-link (needs MD_FLAG_PERMISSIVEWWWAUTOLINKS).
+ * 'D': Dummy mark, it reserves a space for splitting a previous mark
+ * (e.g. emphasis) or to make more space for storing some special data
+ * related to the preceding mark (e.g. link).
+ *
+ * Note that not all instances of these chars in the text imply creation of the
+ * structure. Only those which have (or may have, after we see more context)
+ * the special meaning.
+ *
+ * (Keep this struct as small as possible to fit as much of them into CPU
+ * cache line.)
+ */
+struct MD_MARK_tag {
+ OFF beg;
+ OFF end;
+
+ /* For unresolved openers, 'prev' and 'next' form the chain of open openers
+ * of given type 'ch'.
+ *
+ * During resolving, we disconnect from the chain and point to the
+ * corresponding counterpart so opener points to its closer and vice versa.
+ */
+ int prev;
+ int next;
+ CHAR ch;
+ unsigned char flags;
+};
+
+/* Mark flags (these apply to ALL mark types). */
+#define MD_MARK_POTENTIAL_OPENER 0x01 /* Maybe opener. */
+#define MD_MARK_POTENTIAL_CLOSER 0x02 /* Maybe closer. */
+#define MD_MARK_OPENER 0x04 /* Definitely opener. */
+#define MD_MARK_CLOSER 0x08 /* Definitely closer. */
+#define MD_MARK_RESOLVED 0x10 /* Resolved in any definite way. */
+
+/* Mark flags specific for various mark types (so they can share bits). */
+#define MD_MARK_EMPH_INTRAWORD 0x20 /* Helper for the "rule of 3". */
+#define MD_MARK_EMPH_MOD3_0 0x40
+#define MD_MARK_EMPH_MOD3_1 0x80
+#define MD_MARK_EMPH_MOD3_2 (0x40 | 0x80)
+#define MD_MARK_EMPH_MOD3_MASK (0x40 | 0x80)
+#define MD_MARK_AUTOLINK 0x20 /* Distinguisher for '<', '>'. */
+#define MD_MARK_VALIDPERMISSIVEAUTOLINK 0x20 /* For permissive autolinks. */
+#define MD_MARK_HASNESTEDBRACKETS 0x20 /* For '[' to rule out invalid link labels early */
+
+static MD_MARKCHAIN*
+md_asterisk_chain(MD_CTX* ctx, unsigned flags)
+{
+ switch(flags & (MD_MARK_EMPH_INTRAWORD | MD_MARK_EMPH_MOD3_MASK)) {
+ case MD_MARK_EMPH_INTRAWORD | MD_MARK_EMPH_MOD3_0: return &ASTERISK_OPENERS_intraword_mod3_0;
+ case MD_MARK_EMPH_INTRAWORD | MD_MARK_EMPH_MOD3_1: return &ASTERISK_OPENERS_intraword_mod3_1;
+ case MD_MARK_EMPH_INTRAWORD | MD_MARK_EMPH_MOD3_2: return &ASTERISK_OPENERS_intraword_mod3_2;
+ case MD_MARK_EMPH_MOD3_0: return &ASTERISK_OPENERS_extraword_mod3_0;
+ case MD_MARK_EMPH_MOD3_1: return &ASTERISK_OPENERS_extraword_mod3_1;
+ case MD_MARK_EMPH_MOD3_2: return &ASTERISK_OPENERS_extraword_mod3_2;
+ default: MD_UNREACHABLE();
+ }
+ return NULL;
+}
+
+static MD_MARKCHAIN*
+md_mark_chain(MD_CTX* ctx, int mark_index)
+{
+ MD_MARK* mark = &ctx->marks[mark_index];
+
+ switch(mark->ch) {
+ case _T('*'): return md_asterisk_chain(ctx, mark->flags);
+ case _T('_'): return &UNDERSCORE_OPENERS;
+ case _T('~'): return (mark->end - mark->beg == 1) ? &TILDE_OPENERS_1 : &TILDE_OPENERS_2;
+ case _T('!'): MD_FALLTHROUGH();
+ case _T('['): return &BRACKET_OPENERS;
+ case _T('|'): return &TABLECELLBOUNDARIES;
+ default: return NULL;
+ }
+}
+
+static MD_MARK*
+md_push_mark(MD_CTX* ctx)
+{
+ if(ctx->n_marks >= ctx->alloc_marks) {
+ MD_MARK* new_marks;
+
+ ctx->alloc_marks = (ctx->alloc_marks > 0
+ ? ctx->alloc_marks + ctx->alloc_marks / 2
+ : 64);
+ new_marks = realloc(ctx->marks, ctx->alloc_marks * sizeof(MD_MARK));
+ if(new_marks == NULL) {
+ MD_LOG("realloc() failed.");
+ return NULL;
+ }
+
+ ctx->marks = new_marks;
+ }
+
+ return &ctx->marks[ctx->n_marks++];
+}
+
+#define PUSH_MARK_() \
+ do { \
+ mark = md_push_mark(ctx); \
+ if(mark == NULL) { \
+ ret = -1; \
+ goto abort; \
+ } \
+ } while(0)
+
+#define PUSH_MARK(ch_, beg_, end_, flags_) \
+ do { \
+ PUSH_MARK_(); \
+ mark->beg = (beg_); \
+ mark->end = (end_); \
+ mark->prev = -1; \
+ mark->next = -1; \
+ mark->ch = (char)(ch_); \
+ mark->flags = (flags_); \
+ } while(0)
+
+
+static void
+md_mark_chain_append(MD_CTX* ctx, MD_MARKCHAIN* chain, int mark_index)
+{
+ if(chain->tail >= 0)
+ ctx->marks[chain->tail].next = mark_index;
+ else
+ chain->head = mark_index;
+
+ ctx->marks[mark_index].prev = chain->tail;
+ ctx->marks[mark_index].next = -1;
+ chain->tail = mark_index;
+}
+
+/* Sometimes, we need to store a pointer into the mark. It is quite rare
+ * so we do not bother to make MD_MARK use union, and it can only happen
+ * for dummy marks. */
+static inline void
+md_mark_store_ptr(MD_CTX* ctx, int mark_index, void* ptr)
+{
+ MD_MARK* mark = &ctx->marks[mark_index];
+ MD_ASSERT(mark->ch == 'D');
+
+ /* Check only members beg and end are misused for this. */
+ MD_ASSERT(sizeof(void*) <= 2 * sizeof(OFF));
+ memcpy(mark, &ptr, sizeof(void*));
+}
+
+static inline void*
+md_mark_get_ptr(MD_CTX* ctx, int mark_index)
+{
+ void* ptr;
+ MD_MARK* mark = &ctx->marks[mark_index];
+ MD_ASSERT(mark->ch == 'D');
+ memcpy(&ptr, mark, sizeof(void*));
+ return ptr;
+}
+
+static void
+md_resolve_range(MD_CTX* ctx, MD_MARKCHAIN* chain, int opener_index, int closer_index)
+{
+ MD_MARK* opener = &ctx->marks[opener_index];
+ MD_MARK* closer = &ctx->marks[closer_index];
+
+ /* Remove opener from the list of openers. */
+ if(chain != NULL) {
+ if(opener->prev >= 0)
+ ctx->marks[opener->prev].next = opener->next;
+ else
+ chain->head = opener->next;
+
+ if(opener->next >= 0)
+ ctx->marks[opener->next].prev = opener->prev;
+ else
+ chain->tail = opener->prev;
+ }
+
+ /* Interconnect opener and closer and mark both as resolved. */
+ opener->next = closer_index;
+ opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED;
+ closer->prev = opener_index;
+ closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED;
+}
+
+
+#define MD_ROLLBACK_ALL 0
+#define MD_ROLLBACK_CROSSING 1
+
+/* In the range ctx->marks[opener_index] ... [closer_index], undo some or all
+ * resolvings accordingly to these rules:
+ *
+ * (1) All openers BEFORE the range corresponding to any closer inside the
+ * range are un-resolved and they are re-added to their respective chains
+ * of unresolved openers. This ensures we can reuse the opener for closers
+ * AFTER the range.
+ *
+ * (2) If 'how' is MD_ROLLBACK_ALL, then ALL resolved marks inside the range
+ * are discarded.
+ *
+ * (3) If 'how' is MD_ROLLBACK_CROSSING, only closers with openers handled
+ * in (1) are discarded. I.e. pairs of openers and closers which are both
+ * inside the range are retained as well as any unpaired marks.
+ */
+static void
+md_rollback(MD_CTX* ctx, int opener_index, int closer_index, int how)
+{
+ int i;
+ int mark_index;
+
+ /* Cut all unresolved openers at the mark index. */
+ for(i = OPENERS_CHAIN_FIRST; i < OPENERS_CHAIN_LAST+1; i++) {
+ MD_MARKCHAIN* chain = &ctx->mark_chains[i];
+
+ while(chain->tail >= opener_index) {
+ int same = chain->tail == opener_index;
+ chain->tail = ctx->marks[chain->tail].prev;
+ if (same) break;
+ }
+
+ if(chain->tail >= 0)
+ ctx->marks[chain->tail].next = -1;
+ else
+ chain->head = -1;
+ }
+
+ /* Go backwards so that unresolved openers are re-added into their
+ * respective chains, in the right order. */
+ mark_index = closer_index - 1;
+ while(mark_index > opener_index) {
+ MD_MARK* mark = &ctx->marks[mark_index];
+ int mark_flags = mark->flags;
+ int discard_flag = (how == MD_ROLLBACK_ALL);
+
+ if(mark->flags & MD_MARK_CLOSER) {
+ int mark_opener_index = mark->prev;
+
+ /* Undo opener BEFORE the range. */
+ if(mark_opener_index < opener_index) {
+ MD_MARK* mark_opener = &ctx->marks[mark_opener_index];
+ MD_MARKCHAIN* chain;
+
+ mark_opener->flags &= ~(MD_MARK_OPENER | MD_MARK_CLOSER | MD_MARK_RESOLVED);
+ chain = md_mark_chain(ctx, opener_index);
+ if(chain != NULL) {
+ md_mark_chain_append(ctx, chain, mark_opener_index);
+ discard_flag = 1;
+ }
+ }
+ }
+
+ /* And reset our flags. */
+ if(discard_flag) {
+ /* Make zero-length closer a dummy mark as that's how it was born */
+ if((mark->flags & MD_MARK_CLOSER) && mark->beg == mark->end)
+ mark->ch = 'D';
+
+ mark->flags &= ~(MD_MARK_OPENER | MD_MARK_CLOSER | MD_MARK_RESOLVED);
+ }
+
+ /* Jump as far as we can over unresolved or non-interesting marks. */
+ switch(how) {
+ case MD_ROLLBACK_CROSSING:
+ if((mark_flags & MD_MARK_CLOSER) && mark->prev > opener_index) {
+ /* If we are closer with opener INSIDE the range, there may
+ * not be any other crosser inside the subrange. */
+ mark_index = mark->prev;
+ break;
+ }
+ MD_FALLTHROUGH();
+ default:
+ mark_index--;
+ break;
+ }
+ }
+}
+
+static void
+md_build_mark_char_map(MD_CTX* ctx)
+{
+ memset(ctx->mark_char_map, 0, sizeof(ctx->mark_char_map));
+
+ ctx->mark_char_map['\\'] = 1;
+ ctx->mark_char_map['*'] = 1;
+ ctx->mark_char_map['_'] = 1;
+ ctx->mark_char_map['`'] = 1;
+ ctx->mark_char_map['&'] = 1;
+ ctx->mark_char_map[';'] = 1;
+ ctx->mark_char_map['<'] = 1;
+ ctx->mark_char_map['>'] = 1;
+ ctx->mark_char_map['['] = 1;
+ ctx->mark_char_map['!'] = 1;
+ ctx->mark_char_map[']'] = 1;
+ ctx->mark_char_map['\0'] = 1;
+
+ if(ctx->parser.flags & MD_FLAG_STRIKETHROUGH)
+ ctx->mark_char_map['~'] = 1;
+
+ if(ctx->parser.flags & MD_FLAG_LATEXMATHSPANS)
+ ctx->mark_char_map['$'] = 1;
+
+ if(ctx->parser.flags & MD_FLAG_PERMISSIVEEMAILAUTOLINKS)
+ ctx->mark_char_map['@'] = 1;
+
+ if(ctx->parser.flags & MD_FLAG_PERMISSIVEURLAUTOLINKS)
+ ctx->mark_char_map[':'] = 1;
+
+ if(ctx->parser.flags & MD_FLAG_PERMISSIVEWWWAUTOLINKS)
+ ctx->mark_char_map['.'] = 1;
+
+ if((ctx->parser.flags & MD_FLAG_TABLES) || (ctx->parser.flags & MD_FLAG_WIKILINKS))
+ ctx->mark_char_map['|'] = 1;
+
+ if(ctx->parser.flags & MD_FLAG_COLLAPSEWHITESPACE) {
+ int i;
+
+ for(i = 0; i < (int) sizeof(ctx->mark_char_map); i++) {
+ if(ISWHITESPACE_(i))
+ ctx->mark_char_map[i] = 1;
+ }
+ }
+}
+
+/* We limit code span marks to lower than 32 backticks. This solves the
+ * pathologic case of too many openers, each of different length: Their
+ * resolving would be then O(n^2). */
+#define CODESPAN_MARK_MAXLEN 32
+
+static int
+md_is_code_span(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg,
+ OFF* p_opener_beg, OFF* p_opener_end,
+ OFF* p_closer_beg, OFF* p_closer_end,
+ OFF last_potential_closers[CODESPAN_MARK_MAXLEN],
+ int* p_reached_paragraph_end)
+{
+ OFF opener_beg = beg;
+ OFF opener_end;
+ OFF closer_beg;
+ OFF closer_end;
+ SZ mark_len;
+ OFF line_end;
+ int has_space_after_opener = FALSE;
+ int has_eol_after_opener = FALSE;
+ int has_space_before_closer = FALSE;
+ int has_eol_before_closer = FALSE;
+ int has_only_space = TRUE;
+ int line_index = 0;
+
+ line_end = lines[0].end;
+ opener_end = opener_beg;
+ while(opener_end < line_end && CH(opener_end) == _T('`'))
+ opener_end++;
+ has_space_after_opener = (opener_end < line_end && CH(opener_end) == _T(' '));
+ has_eol_after_opener = (opener_end == line_end);
+
+ /* The caller needs to know end of the opening mark even if we fail. */
+ *p_opener_end = opener_end;
+
+ mark_len = opener_end - opener_beg;
+ if(mark_len > CODESPAN_MARK_MAXLEN)
+ return FALSE;
+
+ /* Check whether we already know there is no closer of this length.
+ * If so, re-scan does no sense. This fixes issue #59. */
+ if(last_potential_closers[mark_len-1] >= lines[n_lines-1].end ||
+ (*p_reached_paragraph_end && last_potential_closers[mark_len-1] < opener_end))
+ return FALSE;
+
+ closer_beg = opener_end;
+ closer_end = opener_end;
+
+ /* Find closer mark. */
+ while(TRUE) {
+ while(closer_beg < line_end && CH(closer_beg) != _T('`')) {
+ if(CH(closer_beg) != _T(' '))
+ has_only_space = FALSE;
+ closer_beg++;
+ }
+ closer_end = closer_beg;
+ while(closer_end < line_end && CH(closer_end) == _T('`'))
+ closer_end++;
+
+ if(closer_end - closer_beg == mark_len) {
+ /* Success. */
+ has_space_before_closer = (closer_beg > lines[line_index].beg && CH(closer_beg-1) == _T(' '));
+ has_eol_before_closer = (closer_beg == lines[line_index].beg);
+ break;
+ }
+
+ if(closer_end - closer_beg > 0) {
+ /* We have found a back-tick which is not part of the closer. */
+ has_only_space = FALSE;
+
+ /* But if we eventually fail, remember it as a potential closer
+ * of its own length for future attempts. This mitigates needs for
+ * rescans. */
+ if(closer_end - closer_beg < CODESPAN_MARK_MAXLEN) {
+ if(closer_beg > last_potential_closers[closer_end - closer_beg - 1])
+ last_potential_closers[closer_end - closer_beg - 1] = closer_beg;
+ }
+ }
+
+ if(closer_end >= line_end) {
+ line_index++;
+ if(line_index >= n_lines) {
+ /* Reached end of the paragraph and still nothing. */
+ *p_reached_paragraph_end = TRUE;
+ return FALSE;
+ }
+ /* Try on the next line. */
+ line_end = lines[line_index].end;
+ closer_beg = lines[line_index].beg;
+ } else {
+ closer_beg = closer_end;
+ }
+ }
+
+ /* If there is a space or a new line both after and before the opener
+ * (and if the code span is not made of spaces only), consume one initial
+ * and one trailing space as part of the marks. */
+ if(!has_only_space &&
+ (has_space_after_opener || has_eol_after_opener) &&
+ (has_space_before_closer || has_eol_before_closer))
+ {
+ if(has_space_after_opener)
+ opener_end++;
+ else
+ opener_end = lines[1].beg;
+
+ if(has_space_before_closer)
+ closer_beg--;
+ else {
+ closer_beg = lines[line_index-1].end;
+ /* We need to eat the preceding "\r\n" but not any line trailing
+ * spaces. */
+ while(closer_beg < ctx->size && ISBLANK(closer_beg))
+ closer_beg++;
+ }
+ }
+
+ *p_opener_beg = opener_beg;
+ *p_opener_end = opener_end;
+ *p_closer_beg = closer_beg;
+ *p_closer_end = closer_end;
+ return TRUE;
+}
+
+static int
+md_is_autolink_uri(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end)
+{
+ OFF off = beg+1;
+
+ MD_ASSERT(CH(beg) == _T('<'));
+
+ /* Check for scheme. */
+ if(off >= max_end || !ISASCII(off))
+ return FALSE;
+ off++;
+ while(1) {
+ if(off >= max_end)
+ return FALSE;
+ if(off - beg > 32)
+ return FALSE;
+ if(CH(off) == _T(':') && off - beg >= 3)
+ break;
+ if(!ISALNUM(off) && CH(off) != _T('+') && CH(off) != _T('-') && CH(off) != _T('.'))
+ return FALSE;
+ off++;
+ }
+
+ /* Check the path after the scheme. */
+ while(off < max_end && CH(off) != _T('>')) {
+ if(ISWHITESPACE(off) || ISCNTRL(off) || CH(off) == _T('<'))
+ return FALSE;
+ off++;
+ }
+
+ if(off >= max_end)
+ return FALSE;
+
+ MD_ASSERT(CH(off) == _T('>'));
+ *p_end = off+1;
+ return TRUE;
+}
+
+static int
+md_is_autolink_email(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end)
+{
+ OFF off = beg + 1;
+ int label_len;
+
+ MD_ASSERT(CH(beg) == _T('<'));
+
+ /* The code should correspond to this regexp:
+ /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+
+ @[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?
+ (?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
+ */
+
+ /* Username (before '@'). */
+ while(off < max_end && (ISALNUM(off) || ISANYOF(off, _T(".!#$%&'*+/=?^_`{|}~-"))))
+ off++;
+ if(off <= beg+1)
+ return FALSE;
+
+ /* '@' */
+ if(off >= max_end || CH(off) != _T('@'))
+ return FALSE;
+ off++;
+
+ /* Labels delimited with '.'; each label is sequence of 1 - 63 alnum
+ * characters or '-', but '-' is not allowed as first or last char. */
+ label_len = 0;
+ while(off < max_end) {
+ if(ISALNUM(off))
+ label_len++;
+ else if(CH(off) == _T('-') && label_len > 0)
+ label_len++;
+ else if(CH(off) == _T('.') && label_len > 0 && CH(off-1) != _T('-'))
+ label_len = 0;
+ else
+ break;
+
+ if(label_len > 63)
+ return FALSE;
+
+ off++;
+ }
+
+ if(label_len <= 0 || off >= max_end || CH(off) != _T('>') || CH(off-1) == _T('-'))
+ return FALSE;
+
+ *p_end = off+1;
+ return TRUE;
+}
+
+static int
+md_is_autolink(MD_CTX* ctx, OFF beg, OFF max_end, OFF* p_end, int* p_missing_mailto)
+{
+ if(md_is_autolink_uri(ctx, beg, max_end, p_end)) {
+ *p_missing_mailto = FALSE;
+ return TRUE;
+ }
+
+ if(md_is_autolink_email(ctx, beg, max_end, p_end)) {
+ *p_missing_mailto = TRUE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int
+md_collect_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode)
+{
+ const MD_LINE* line_term = lines + n_lines;
+ const MD_LINE* line;
+ int ret = 0;
+ MD_MARK* mark;
+ OFF codespan_last_potential_closers[CODESPAN_MARK_MAXLEN] = { 0 };
+ int codespan_scanned_till_paragraph_end = FALSE;
+
+ for(line = lines; line < line_term; line++) {
+ OFF off = line->beg;
+ OFF line_end = line->end;
+
+ while(TRUE) {
+ CHAR ch;
+
+#ifdef MD4C_USE_UTF16
+ /* For UTF-16, mark_char_map[] covers only ASCII. */
+ #define IS_MARK_CHAR(off) ((CH(off) < SIZEOF_ARRAY(ctx->mark_char_map)) && \
+ (ctx->mark_char_map[(unsigned char) CH(off)]))
+#else
+ /* For 8-bit encodings, mark_char_map[] covers all 256 elements. */
+ #define IS_MARK_CHAR(off) (ctx->mark_char_map[(unsigned char) CH(off)])
+#endif
+
+ /* Optimization: Use some loop unrolling. */
+ while(off + 3 < line_end && !IS_MARK_CHAR(off+0) && !IS_MARK_CHAR(off+1)
+ && !IS_MARK_CHAR(off+2) && !IS_MARK_CHAR(off+3))
+ off += 4;
+ while(off < line_end && !IS_MARK_CHAR(off+0))
+ off++;
+
+ if(off >= line_end)
+ break;
+
+ ch = CH(off);
+
+ /* A backslash escape.
+ * It can go beyond line->end as it may involve escaped new
+ * line to form a hard break. */
+ if(ch == _T('\\') && off+1 < ctx->size && (ISPUNCT(off+1) || ISNEWLINE(off+1))) {
+ /* Hard-break cannot be on the last line of the block. */
+ if(!ISNEWLINE(off+1) || line+1 < line_term)
+ PUSH_MARK(ch, off, off+2, MD_MARK_RESOLVED);
+ off += 2;
+ continue;
+ }
+
+ /* A potential (string) emphasis start/end. */
+ if(ch == _T('*') || ch == _T('_')) {
+ OFF tmp = off+1;
+ int left_level; /* What precedes: 0 = whitespace; 1 = punctuation; 2 = other char. */
+ int right_level; /* What follows: 0 = whitespace; 1 = punctuation; 2 = other char. */
+
+ while(tmp < line_end && CH(tmp) == ch)
+ tmp++;
+
+ if(off == line->beg || ISUNICODEWHITESPACEBEFORE(off))
+ left_level = 0;
+ else if(ISUNICODEPUNCTBEFORE(off))
+ left_level = 1;
+ else
+ left_level = 2;
+
+ if(tmp == line_end || ISUNICODEWHITESPACE(tmp))
+ right_level = 0;
+ else if(ISUNICODEPUNCT(tmp))
+ right_level = 1;
+ else
+ right_level = 2;
+
+ /* Intra-word underscore doesn't have special meaning. */
+ if(ch == _T('_') && left_level == 2 && right_level == 2) {
+ left_level = 0;
+ right_level = 0;
+ }
+
+ if(left_level != 0 || right_level != 0) {
+ unsigned flags = 0;
+
+ if(left_level > 0 && left_level >= right_level)
+ flags |= MD_MARK_POTENTIAL_CLOSER;
+ if(right_level > 0 && right_level >= left_level)
+ flags |= MD_MARK_POTENTIAL_OPENER;
+ if(left_level == 2 && right_level == 2)
+ flags |= MD_MARK_EMPH_INTRAWORD;
+
+ /* For "the rule of three" we need to remember the original
+ * size of the mark (modulo three), before we potentially
+ * split the mark when being later resolved partially by some
+ * shorter closer. */
+ switch((tmp - off) % 3) {
+ case 0: flags |= MD_MARK_EMPH_MOD3_0; break;
+ case 1: flags |= MD_MARK_EMPH_MOD3_1; break;
+ case 2: flags |= MD_MARK_EMPH_MOD3_2; break;
+ }
+
+ PUSH_MARK(ch, off, tmp, flags);
+
+ /* During resolving, multiple asterisks may have to be
+ * split into independent span start/ends. Consider e.g.
+ * "**foo* bar*". Therefore we push also some empty dummy
+ * marks to have enough space for that. */
+ off++;
+ while(off < tmp) {
+ PUSH_MARK('D', off, off, 0);
+ off++;
+ }
+ continue;
+ }
+
+ off = tmp;
+ continue;
+ }
+
+ /* A potential code span start/end. */
+ if(ch == _T('`')) {
+ OFF opener_beg, opener_end;
+ OFF closer_beg, closer_end;
+ int is_code_span;
+
+ is_code_span = md_is_code_span(ctx, line, line_term - line, off,
+ &opener_beg, &opener_end, &closer_beg, &closer_end,
+ codespan_last_potential_closers,
+ &codespan_scanned_till_paragraph_end);
+ if(is_code_span) {
+ PUSH_MARK(_T('`'), opener_beg, opener_end, MD_MARK_OPENER | MD_MARK_RESOLVED);
+ PUSH_MARK(_T('`'), closer_beg, closer_end, MD_MARK_CLOSER | MD_MARK_RESOLVED);
+ ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1;
+ ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2;
+
+ off = closer_end;
+
+ /* Advance the current line accordingly. */
+ if(off > line_end) {
+ line = md_lookup_line(off, line, line_term - line);
+ line_end = line->end;
+ }
+ continue;
+ }
+
+ off = opener_end;
+ continue;
+ }
+
+ /* A potential entity start. */
+ if(ch == _T('&')) {
+ PUSH_MARK(ch, off, off+1, MD_MARK_POTENTIAL_OPENER);
+ off++;
+ continue;
+ }
+
+ /* A potential entity end. */
+ if(ch == _T(';')) {
+ /* We surely cannot be entity unless the previous mark is '&'. */
+ if(ctx->n_marks > 0 && ctx->marks[ctx->n_marks-1].ch == _T('&'))
+ PUSH_MARK(ch, off, off+1, MD_MARK_POTENTIAL_CLOSER);
+
+ off++;
+ continue;
+ }
+
+ /* A potential autolink or raw HTML start/end. */
+ if(ch == _T('<')) {
+ int is_autolink;
+ OFF autolink_end;
+ int missing_mailto;
+
+ if(!(ctx->parser.flags & MD_FLAG_NOHTMLSPANS)) {
+ int is_html;
+ OFF html_end;
+
+ /* Given the nature of the raw HTML, we have to recognize
+ * it here. Doing so later in md_analyze_lt_gt() could
+ * open can of worms of quadratic complexity. */
+ is_html = md_is_html_any(ctx, line, line_term - line, off,
+ lines[n_lines-1].end, &html_end);
+ if(is_html) {
+ PUSH_MARK(_T('<'), off, off, MD_MARK_OPENER | MD_MARK_RESOLVED);
+ PUSH_MARK(_T('>'), html_end, html_end, MD_MARK_CLOSER | MD_MARK_RESOLVED);
+ ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1;
+ ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2;
+ off = html_end;
+
+ /* Advance the current line accordingly. */
+ if(off > line_end) {
+ line = md_lookup_line(off, line, line_term - line);
+ line_end = line->end;
+ }
+ continue;
+ }
+ }
+
+ is_autolink = md_is_autolink(ctx, off, lines[n_lines-1].end,
+ &autolink_end, &missing_mailto);
+ if(is_autolink) {
+ PUSH_MARK((missing_mailto ? _T('@') : _T('<')), off, off+1,
+ MD_MARK_OPENER | MD_MARK_RESOLVED | MD_MARK_AUTOLINK);
+ PUSH_MARK(_T('>'), autolink_end-1, autolink_end,
+ MD_MARK_CLOSER | MD_MARK_RESOLVED | MD_MARK_AUTOLINK);
+ ctx->marks[ctx->n_marks-2].next = ctx->n_marks-1;
+ ctx->marks[ctx->n_marks-1].prev = ctx->n_marks-2;
+ off = autolink_end;
+ continue;
+ }
+
+ off++;
+ continue;
+ }
+
+ /* A potential link or its part. */
+ if(ch == _T('[') || (ch == _T('!') && off+1 < line_end && CH(off+1) == _T('['))) {
+ OFF tmp = (ch == _T('[') ? off+1 : off+2);
+ PUSH_MARK(ch, off, tmp, MD_MARK_POTENTIAL_OPENER);
+ off = tmp;
+ /* Two dummies to make enough place for data we need if it is
+ * a link. */
+ PUSH_MARK('D', off, off, 0);
+ PUSH_MARK('D', off, off, 0);
+ continue;
+ }
+ if(ch == _T(']')) {
+ PUSH_MARK(ch, off, off+1, MD_MARK_POTENTIAL_CLOSER);
+ off++;
+ continue;
+ }
+
+ /* A potential permissive e-mail autolink. */
+ if(ch == _T('@')) {
+ if(line->beg + 1 <= off && ISALNUM(off-1) &&
+ off + 3 < line->end && ISALNUM(off+1))
+ {
+ PUSH_MARK(ch, off, off+1, MD_MARK_POTENTIAL_OPENER);
+ /* Push a dummy as a reserve for a closer. */
+ PUSH_MARK('D', off, off, 0);
+ }
+
+ off++;
+ continue;
+ }
+
+ /* A potential permissive URL autolink. */
+ if(ch == _T(':')) {
+ static struct {
+ const CHAR* scheme;
+ SZ scheme_size;
+ const CHAR* suffix;
+ SZ suffix_size;
+ } scheme_map[] = {
+ /* In the order from the most frequently used, arguably. */
+ { _T("http"), 4, _T("//"), 2 },
+ { _T("https"), 5, _T("//"), 2 },
+ { _T("ftp"), 3, _T("//"), 2 }
+ };
+ int scheme_index;
+
+ for(scheme_index = 0; scheme_index < (int) SIZEOF_ARRAY(scheme_map); scheme_index++) {
+ const CHAR* scheme = scheme_map[scheme_index].scheme;
+ const SZ scheme_size = scheme_map[scheme_index].scheme_size;
+ const CHAR* suffix = scheme_map[scheme_index].suffix;
+ const SZ suffix_size = scheme_map[scheme_index].suffix_size;
+
+ if(line->beg + scheme_size <= off && md_ascii_eq(STR(off-scheme_size), scheme, scheme_size) &&
+ (line->beg + scheme_size == off || ISWHITESPACE(off-scheme_size-1) || ISANYOF(off-scheme_size-1, _T("*_~(["))) &&
+ off + 1 + suffix_size < line->end && md_ascii_eq(STR(off+1), suffix, suffix_size))
+ {
+ PUSH_MARK(ch, off-scheme_size, off+1+suffix_size, MD_MARK_POTENTIAL_OPENER);
+ /* Push a dummy as a reserve for a closer. */
+ PUSH_MARK('D', off, off, 0);
+ off += 1 + suffix_size;
+ break;
+ }
+ }
+
+ off++;
+ continue;
+ }
+
+ /* A potential permissive WWW autolink. */
+ if(ch == _T('.')) {
+ if(line->beg + 3 <= off && md_ascii_eq(STR(off-3), _T("www"), 3) &&
+ (line->beg + 3 == off || ISWHITESPACE(off-4) || ISANYOF(off-4, _T("*_~(["))) &&
+ off + 1 < line_end)
+ {
+ PUSH_MARK(ch, off-3, off+1, MD_MARK_POTENTIAL_OPENER);
+ /* Push a dummy as a reserve for a closer. */
+ PUSH_MARK('D', off, off, 0);
+ off++;
+ continue;
+ }
+
+ off++;
+ continue;
+ }
+
+ /* A potential table cell boundary or wiki link label delimiter. */
+ if((table_mode || ctx->parser.flags & MD_FLAG_WIKILINKS) && ch == _T('|')) {
+ PUSH_MARK(ch, off, off+1, 0);
+ off++;
+ continue;
+ }
+
+ /* A potential strikethrough start/end. */
+ if(ch == _T('~')) {
+ OFF tmp = off+1;
+
+ while(tmp < line_end && CH(tmp) == _T('~'))
+ tmp++;
+
+ if(tmp - off < 3) {
+ unsigned flags = 0;
+
+ if(tmp < line_end && !ISUNICODEWHITESPACE(tmp))
+ flags |= MD_MARK_POTENTIAL_OPENER;
+ if(off > line->beg && !ISUNICODEWHITESPACEBEFORE(off))
+ flags |= MD_MARK_POTENTIAL_CLOSER;
+ if(flags != 0)
+ PUSH_MARK(ch, off, tmp, flags);
+ }
+
+ off = tmp;
+ continue;
+ }
+
+ /* A potential equation start/end */
+ if(ch == _T('$')) {
+ /* We can have at most two consecutive $ signs,
+ * where two dollar signs signify a display equation. */
+ OFF tmp = off+1;
+
+ while(tmp < line_end && CH(tmp) == _T('$'))
+ tmp++;
+
+ if (tmp - off <= 2)
+ PUSH_MARK(ch, off, tmp, MD_MARK_POTENTIAL_OPENER | MD_MARK_POTENTIAL_CLOSER);
+ off = tmp;
+ continue;
+ }
+
+ /* Turn non-trivial whitespace into single space. */
+ if(ISWHITESPACE_(ch)) {
+ OFF tmp = off+1;
+
+ while(tmp < line_end && ISWHITESPACE(tmp))
+ tmp++;
+
+ if(tmp - off > 1 || ch != _T(' '))
+ PUSH_MARK(ch, off, tmp, MD_MARK_RESOLVED);
+
+ off = tmp;
+ continue;
+ }
+
+ /* NULL character. */
+ if(ch == _T('\0')) {
+ PUSH_MARK(ch, off, off+1, MD_MARK_RESOLVED);
+ off++;
+ continue;
+ }
+
+ off++;
+ }
+ }
+
+ /* Add a dummy mark at the end of the mark vector to simplify
+ * process_inlines(). */
+ PUSH_MARK(127, ctx->size, ctx->size, MD_MARK_RESOLVED);
+
+abort:
+ return ret;
+}
+
+static void
+md_analyze_bracket(MD_CTX* ctx, int mark_index)
+{
+ /* We cannot really resolve links here as for that we would need
+ * more context. E.g. a following pair of brackets (reference link),
+ * or enclosing pair of brackets (if the inner is the link, the outer
+ * one cannot be.)
+ *
+ * Therefore we here only construct a list of '[' ']' pairs ordered by
+ * position of the closer. This allows us to analyze what is or is not
+ * link in the right order, from inside to outside in case of nested
+ * brackets.
+ *
+ * The resolving itself is deferred to md_resolve_links().
+ */
+
+ MD_MARK* mark = &ctx->marks[mark_index];
+
+ if(mark->flags & MD_MARK_POTENTIAL_OPENER) {
+ if(BRACKET_OPENERS.head != -1)
+ ctx->marks[BRACKET_OPENERS.tail].flags |= MD_MARK_HASNESTEDBRACKETS;
+
+ md_mark_chain_append(ctx, &BRACKET_OPENERS, mark_index);
+ return;
+ }
+
+ if(BRACKET_OPENERS.tail >= 0) {
+ /* Pop the opener from the chain. */
+ int opener_index = BRACKET_OPENERS.tail;
+ MD_MARK* opener = &ctx->marks[opener_index];
+ if(opener->prev >= 0)
+ ctx->marks[opener->prev].next = -1;
+ else
+ BRACKET_OPENERS.head = -1;
+ BRACKET_OPENERS.tail = opener->prev;
+
+ /* Interconnect the opener and closer. */
+ opener->next = mark_index;
+ mark->prev = opener_index;
+
+ /* Add the pair into chain of potential links for md_resolve_links().
+ * Note we misuse opener->prev for this as opener->next points to its
+ * closer. */
+ if(ctx->unresolved_link_tail >= 0)
+ ctx->marks[ctx->unresolved_link_tail].prev = opener_index;
+ else
+ ctx->unresolved_link_head = opener_index;
+ ctx->unresolved_link_tail = opener_index;
+ opener->prev = -1;
+ }
+}
+
+/* Forward declaration. */
+static void md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
+ int mark_beg, int mark_end);
+
+static int
+md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
+{
+ int opener_index = ctx->unresolved_link_head;
+ OFF last_link_beg = 0;
+ OFF last_link_end = 0;
+ OFF last_img_beg = 0;
+ OFF last_img_end = 0;
+
+ while(opener_index >= 0) {
+ MD_MARK* opener = &ctx->marks[opener_index];
+ int closer_index = opener->next;
+ MD_MARK* closer = &ctx->marks[closer_index];
+ int next_index = opener->prev;
+ MD_MARK* next_opener;
+ MD_MARK* next_closer;
+ MD_LINK_ATTR attr;
+ int is_link = FALSE;
+
+ if(next_index >= 0) {
+ next_opener = &ctx->marks[next_index];
+ next_closer = &ctx->marks[next_opener->next];
+ } else {
+ next_opener = NULL;
+ next_closer = NULL;
+ }
+
+ /* If nested ("[ [ ] ]"), we need to make sure that:
+ * - The outer does not end inside of (...) belonging to the inner.
+ * - The outer cannot be link if the inner is link (i.e. not image).
+ *
+ * (Note we here analyze from inner to outer as the marks are ordered
+ * by closer->beg.)
+ */
+ if((opener->beg < last_link_beg && closer->end < last_link_end) ||
+ (opener->beg < last_img_beg && closer->end < last_img_end) ||
+ (opener->beg < last_link_end && opener->ch == '['))
+ {
+ opener_index = next_index;
+ continue;
+ }
+
+ /* Recognize and resolve wiki links.
+ * Wiki-links maybe '[[destination]]' or '[[destination|label]]'.
+ */
+ if ((ctx->parser.flags & MD_FLAG_WIKILINKS) &&
+ (opener->end - opener->beg == 1) && /* not image */
+ next_opener != NULL && /* double '[' opener */
+ next_opener->ch == '[' &&
+ (next_opener->beg == opener->beg - 1) &&
+ (next_opener->end - next_opener->beg == 1) &&
+ next_closer != NULL && /* double ']' closer */
+ next_closer->ch == ']' &&
+ (next_closer->beg == closer->beg + 1) &&
+ (next_closer->end - next_closer->beg == 1))
+ {
+ MD_MARK* delim = NULL;
+ int delim_index;
+ OFF dest_beg, dest_end;
+
+ is_link = TRUE;
+
+ /* We don't allow destination to be longer than 100 characters.
+ * Lets scan to see whether there is '|'. (If not then the whole
+ * wiki-link has to be below the 100 characters.) */
+ delim_index = opener_index + 1;
+ while(delim_index < closer_index) {
+ MD_MARK* m = &ctx->marks[delim_index];
+ if(m->ch == '|') {
+ delim = m;
+ break;
+ }
+ if(m->ch != 'D' && m->beg - opener->end > 100)
+ break;
+ delim_index++;
+ }
+ dest_beg = opener->end;
+ dest_end = (delim != NULL) ? delim->beg : closer->beg;
+ if(dest_end - dest_beg == 0 || dest_end - dest_beg > 100)
+ is_link = FALSE;
+
+ /* There may not be any new line in the destination. */
+ if(is_link) {
+ OFF off;
+ for(off = dest_beg; off < dest_end; off++) {
+ if(ISNEWLINE(off)) {
+ is_link = FALSE;
+ break;
+ }
+ }
+ }
+
+ if(is_link) {
+ if(delim != NULL) {
+ if(delim->end < closer->beg) {
+ md_rollback(ctx, opener_index, delim_index, MD_ROLLBACK_ALL);
+ md_rollback(ctx, delim_index, closer_index, MD_ROLLBACK_CROSSING);
+ delim->flags |= MD_MARK_RESOLVED;
+ opener->end = delim->beg;
+ } else {
+ /* The pipe is just before the closer: [[foo|]] */
+ md_rollback(ctx, opener_index, closer_index, MD_ROLLBACK_ALL);
+ closer->beg = delim->beg;
+ delim = NULL;
+ }
+ }
+
+ opener->beg = next_opener->beg;
+ opener->next = closer_index;
+ opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED;
+
+ closer->end = next_closer->end;
+ closer->prev = opener_index;
+ closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED;
+
+ last_link_beg = opener->beg;
+ last_link_end = closer->end;
+
+ if(delim != NULL)
+ md_analyze_link_contents(ctx, lines, n_lines, delim_index+1, closer_index);
+
+ opener_index = next_opener->prev;
+ continue;
+ }
+ }
+
+ if(next_opener != NULL && next_opener->beg == closer->end) {
+ if(next_closer->beg > closer->end + 1) {
+ /* Might be full reference link. */
+ if(!(next_opener->flags & MD_MARK_HASNESTEDBRACKETS))
+ is_link = md_is_link_reference(ctx, lines, n_lines, next_opener->beg, next_closer->end, &attr);
+ } else {
+ /* Might be shortcut reference link. */
+ if(!(opener->flags & MD_MARK_HASNESTEDBRACKETS))
+ is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr);
+ }
+
+ if(is_link < 0)
+ return -1;
+
+ if(is_link) {
+ /* Eat the 2nd "[...]". */
+ closer->end = next_closer->end;
+
+ /* Do not analyze the label as a standalone link in the next
+ * iteration. */
+ next_index = ctx->marks[next_index].prev;
+ }
+ } else {
+ if(closer->end < ctx->size && CH(closer->end) == _T('(')) {
+ /* Might be inline link. */
+ OFF inline_link_end = UINT_MAX;
+
+ is_link = md_is_inline_link_spec(ctx, lines, n_lines, closer->end, &inline_link_end, &attr);
+ if(is_link < 0)
+ return -1;
+
+ /* Check the closing ')' is not inside an already resolved range
+ * (i.e. a range with a higher priority), e.g. a code span. */
+ if(is_link) {
+ int i = closer_index + 1;
+
+ while(i < ctx->n_marks) {
+ MD_MARK* mark = &ctx->marks[i];
+
+ if(mark->beg >= inline_link_end)
+ break;
+ if((mark->flags & (MD_MARK_OPENER | MD_MARK_RESOLVED)) == (MD_MARK_OPENER | MD_MARK_RESOLVED)) {
+ if(ctx->marks[mark->next].beg >= inline_link_end) {
+ /* Cancel the link status. */
+ if(attr.title_needs_free)
+ free(attr.title);
+ is_link = FALSE;
+ break;
+ }
+
+ i = mark->next + 1;
+ } else {
+ i++;
+ }
+ }
+ }
+
+ if(is_link) {
+ /* Eat the "(...)" */
+ closer->end = inline_link_end;
+ }
+ }
+
+ if(!is_link) {
+ /* Might be collapsed reference link. */
+ if(!(opener->flags & MD_MARK_HASNESTEDBRACKETS))
+ is_link = md_is_link_reference(ctx, lines, n_lines, opener->beg, closer->end, &attr);
+ if(is_link < 0)
+ return -1;
+ }
+ }
+
+ if(is_link) {
+ /* Resolve the brackets as a link. */
+ opener->flags |= MD_MARK_OPENER | MD_MARK_RESOLVED;
+ closer->flags |= MD_MARK_CLOSER | MD_MARK_RESOLVED;
+
+ /* If it is a link, we store the destination and title in the two
+ * dummy marks after the opener. */
+ MD_ASSERT(ctx->marks[opener_index+1].ch == 'D');
+ ctx->marks[opener_index+1].beg = attr.dest_beg;
+ ctx->marks[opener_index+1].end = attr.dest_end;
+
+ MD_ASSERT(ctx->marks[opener_index+2].ch == 'D');
+ md_mark_store_ptr(ctx, opener_index+2, attr.title);
+ /* The title might or might not have been allocated for us. */
+ if(attr.title_needs_free)
+ md_mark_chain_append(ctx, &PTR_CHAIN, opener_index+2);
+ ctx->marks[opener_index+2].prev = attr.title_size;
+
+ if(opener->ch == '[') {
+ last_link_beg = opener->beg;
+ last_link_end = closer->end;
+ } else {
+ last_img_beg = opener->beg;
+ last_img_end = closer->end;
+ }
+
+ md_analyze_link_contents(ctx, lines, n_lines, opener_index+1, closer_index);
+
+ /* If the link text is formed by nothing but permissive autolink,
+ * suppress the autolink.
+ * See https://github.com/mity/md4c/issues/152 for more info. */
+ if(ctx->parser.flags & MD_FLAG_PERMISSIVEAUTOLINKS) {
+ MD_MARK* first_nested;
+ MD_MARK* last_nested;
+
+ first_nested = opener + 1;
+ while(first_nested->ch == _T('D') && first_nested < closer)
+ first_nested++;
+
+ last_nested = closer - 1;
+ while(first_nested->ch == _T('D') && last_nested > opener)
+ last_nested--;
+
+ if((first_nested->flags & MD_MARK_RESOLVED) &&
+ first_nested->beg == opener->end &&
+ ISANYOF_(first_nested->ch, _T("@:.")) &&
+ first_nested->next == (last_nested - ctx->marks) &&
+ last_nested->end == closer->beg)
+ {
+ first_nested->ch = _T('D');
+ first_nested->flags &= ~MD_MARK_RESOLVED;
+ last_nested->ch = _T('D');
+ last_nested->flags &= ~MD_MARK_RESOLVED;
+ }
+ }
+ }
+
+ opener_index = next_index;
+ }
+
+ return 0;
+}
+
+/* Analyze whether the mark '&' starts a HTML entity.
+ * If so, update its flags as well as flags of corresponding closer ';'. */
+static void
+md_analyze_entity(MD_CTX* ctx, int mark_index)
+{
+ MD_MARK* opener = &ctx->marks[mark_index];
+ MD_MARK* closer;
+ OFF off;
+
+ /* Cannot be entity if there is no closer as the next mark.
+ * (Any other mark between would mean strange character which cannot be
+ * part of the entity.
+ *
+ * So we can do all the work on '&' and do not call this later for the
+ * closing mark ';'.
+ */
+ if(mark_index + 1 >= ctx->n_marks)
+ return;
+ closer = &ctx->marks[mark_index+1];
+ if(closer->ch != ';')
+ return;
+
+ if(md_is_entity(ctx, opener->beg, closer->end, &off)) {
+ MD_ASSERT(off == closer->end);
+
+ md_resolve_range(ctx, NULL, mark_index, mark_index+1);
+ opener->end = closer->end;
+ }
+}
+
+static void
+md_analyze_table_cell_boundary(MD_CTX* ctx, int mark_index)
+{
+ MD_MARK* mark = &ctx->marks[mark_index];
+ mark->flags |= MD_MARK_RESOLVED;
+
+ md_mark_chain_append(ctx, &TABLECELLBOUNDARIES, mark_index);
+ ctx->n_table_cell_boundaries++;
+}
+
+/* Split a longer mark into two. The new mark takes the given count of
+ * characters. May only be called if an adequate number of dummy 'D' marks
+ * follows.
+ */
+static int
+md_split_emph_mark(MD_CTX* ctx, int mark_index, SZ n)
+{
+ MD_MARK* mark = &ctx->marks[mark_index];
+ int new_mark_index = mark_index + (mark->end - mark->beg - n);
+ MD_MARK* dummy = &ctx->marks[new_mark_index];
+
+ MD_ASSERT(mark->end - mark->beg > n);
+ MD_ASSERT(dummy->ch == 'D');
+
+ memcpy(dummy, mark, sizeof(MD_MARK));
+ mark->end -= n;
+ dummy->beg = mark->end;
+
+ return new_mark_index;
+}
+
+static void
+md_analyze_emph(MD_CTX* ctx, int mark_index)
+{
+ MD_MARK* mark = &ctx->marks[mark_index];
+ MD_MARKCHAIN* chain = md_mark_chain(ctx, mark_index);
+
+ /* If we can be a closer, try to resolve with the preceding opener. */
+ if(mark->flags & MD_MARK_POTENTIAL_CLOSER) {
+ MD_MARK* opener = NULL;
+ int opener_index = 0;
+
+ if(mark->ch == _T('*')) {
+ MD_MARKCHAIN* opener_chains[6];
+ int i, n_opener_chains;
+ unsigned flags = mark->flags;
+
+ /* Apply the "rule of three". */
+ n_opener_chains = 0;
+ opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_intraword_mod3_0;
+ if((flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_2)
+ opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_intraword_mod3_1;
+ if((flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_1)
+ opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_intraword_mod3_2;
+ opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_extraword_mod3_0;
+ if(!(flags & MD_MARK_EMPH_INTRAWORD) || (flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_2)
+ opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_extraword_mod3_1;
+ if(!(flags & MD_MARK_EMPH_INTRAWORD) || (flags & MD_MARK_EMPH_MOD3_MASK) != MD_MARK_EMPH_MOD3_1)
+ opener_chains[n_opener_chains++] = &ASTERISK_OPENERS_extraword_mod3_2;
+
+ /* Opener is the most recent mark from the allowed chains. */
+ for(i = 0; i < n_opener_chains; i++) {
+ if(opener_chains[i]->tail >= 0) {
+ int tmp_index = opener_chains[i]->tail;
+ MD_MARK* tmp_mark = &ctx->marks[tmp_index];
+ if(opener == NULL || tmp_mark->end > opener->end) {
+ opener_index = tmp_index;
+ opener = tmp_mark;
+ }
+ }
+ }
+ } else {
+ /* Simple emph. mark */
+ if(chain->tail >= 0) {
+ opener_index = chain->tail;
+ opener = &ctx->marks[opener_index];
+ }
+ }
+
+ /* Resolve, if we have found matching opener. */
+ if(opener != NULL) {
+ SZ opener_size = opener->end - opener->beg;
+ SZ closer_size = mark->end - mark->beg;
+ MD_MARKCHAIN* opener_chain = md_mark_chain(ctx, opener_index);
+
+ if(opener_size > closer_size) {
+ opener_index = md_split_emph_mark(ctx, opener_index, closer_size);
+ md_mark_chain_append(ctx, opener_chain, opener_index);
+ } else if(opener_size < closer_size) {
+ md_split_emph_mark(ctx, mark_index, closer_size - opener_size);
+ }
+
+ md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_CROSSING);
+ md_resolve_range(ctx, opener_chain, opener_index, mark_index);
+ return;
+ }
+ }
+
+ /* If we could not resolve as closer, we may be yet be an opener. */
+ if(mark->flags & MD_MARK_POTENTIAL_OPENER)
+ md_mark_chain_append(ctx, chain, mark_index);
+}
+
+static void
+md_analyze_tilde(MD_CTX* ctx, int mark_index)
+{
+ MD_MARK* mark = &ctx->marks[mark_index];
+ MD_MARKCHAIN* chain = md_mark_chain(ctx, mark_index);
+
+ /* We attempt to be Github Flavored Markdown compatible here. GFM accepts
+ * only tildes sequences of length 1 and 2, and the length of the opener
+ * and closer has to match. */
+
+ if((mark->flags & MD_MARK_POTENTIAL_CLOSER) && chain->head >= 0) {
+ int opener_index = chain->head;
+
+ md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_CROSSING);
+ md_resolve_range(ctx, chain, opener_index, mark_index);
+ return;
+ }
+
+ if(mark->flags & MD_MARK_POTENTIAL_OPENER)
+ md_mark_chain_append(ctx, chain, mark_index);
+}
+
+static void
+md_analyze_dollar(MD_CTX* ctx, int mark_index)
+{
+ /* This should mimic the way inline equations work in LaTeX, so there
+ * can only ever be one item in the chain (i.e. the dollars can't be
+ * nested). This is basically the same as the md_analyze_tilde function,
+ * except that we require matching openers and closers to be of the same
+ * length.
+ *
+ * E.g.: $abc$$def$$ => abc (display equation) def (end equation) */
+ if(DOLLAR_OPENERS.head >= 0) {
+ /* If the potential closer has a non-matching number of $, discard */
+ MD_MARK* open = &ctx->marks[DOLLAR_OPENERS.head];
+ MD_MARK* close = &ctx->marks[mark_index];
+
+ int opener_index = DOLLAR_OPENERS.head;
+ md_rollback(ctx, opener_index, mark_index, MD_ROLLBACK_ALL);
+ if (open->end - open->beg == close->end - close->beg) {
+ /* We are the matching closer */
+ md_resolve_range(ctx, &DOLLAR_OPENERS, opener_index, mark_index);
+ return;
+ }
+ }
+
+ md_mark_chain_append(ctx, &DOLLAR_OPENERS, mark_index);
+}
+
+static void
+md_analyze_permissive_url_autolink(MD_CTX* ctx, int mark_index)
+{
+ MD_MARK* opener = &ctx->marks[mark_index];
+ int closer_index = mark_index + 1;
+ MD_MARK* closer = &ctx->marks[closer_index];
+ MD_MARK* next_resolved_mark;
+ OFF off = opener->end;
+ int n_dots = FALSE;
+ int has_underscore_in_last_seg = FALSE;
+ int has_underscore_in_next_to_last_seg = FALSE;
+ int n_opened_parenthesis = 0;
+ int n_excess_parenthesis = 0;
+
+ /* Check for domain. */
+ while(off < ctx->size) {
+ if(ISALNUM(off) || CH(off) == _T('-')) {
+ off++;
+ } else if(CH(off) == _T('.')) {
+ /* We must see at least one period. */
+ n_dots++;
+ has_underscore_in_next_to_last_seg = has_underscore_in_last_seg;
+ has_underscore_in_last_seg = FALSE;
+ off++;
+ } else if(CH(off) == _T('_')) {
+ /* No underscore may be present in the last two domain segments. */
+ has_underscore_in_last_seg = TRUE;
+ off++;
+ } else {
+ break;
+ }
+ }
+ if(off > opener->end && CH(off-1) == _T('.')) {
+ off--;
+ n_dots--;
+ }
+ if(off <= opener->end || n_dots == 0 || has_underscore_in_next_to_last_seg || has_underscore_in_last_seg)
+ return;
+
+ /* Check for path. */
+ next_resolved_mark = closer + 1;
+ while(next_resolved_mark->ch == 'D' || !(next_resolved_mark->flags & MD_MARK_RESOLVED))
+ next_resolved_mark++;
+ while(off < next_resolved_mark->beg && CH(off) != _T('<') && !ISWHITESPACE(off) && !ISNEWLINE(off)) {
+ /* Parenthesis must be balanced. */
+ if(CH(off) == _T('(')) {
+ n_opened_parenthesis++;
+ } else if(CH(off) == _T(')')) {
+ if(n_opened_parenthesis > 0)
+ n_opened_parenthesis--;
+ else
+ n_excess_parenthesis++;
+ }
+
+ off++;
+ }
+
+ /* Trim a trailing punctuation from the end. */
+ while(TRUE) {
+ if(ISANYOF(off-1, _T("?!.,:*_~"))) {
+ off--;
+ } else if(CH(off-1) == ')' && n_excess_parenthesis > 0) {
+ /* Unmatched ')' can be in an interior of the path but not at the
+ * of it, so the auto-link may be safely nested in a parenthesis
+ * pair. */
+ off--;
+ n_excess_parenthesis--;
+ } else {
+ break;
+ }
+ }
+
+ /* Ok. Lets call it an auto-link. Adapt opener and create closer to zero
+ * length so all the contents becomes the link text. */
+ MD_ASSERT(closer->ch == 'D' ||
+ ((ctx->parser.flags & MD_FLAG_PERMISSIVEWWWAUTOLINKS) &&
+ (closer->ch == '.' || closer->ch == ':' || closer->ch == '@')));
+ opener->end = opener->beg;
+ closer->ch = opener->ch;
+ closer->beg = off;
+ closer->end = off;
+ md_resolve_range(ctx, NULL, mark_index, closer_index);
+}
+
+/* The permissive autolinks do not have to be enclosed in '<' '>' but we
+ * instead impose stricter rules what is understood as an e-mail address
+ * here. Actually any non-alphanumeric characters with exception of '.'
+ * are prohibited both in username and after '@'. */
+static void
+md_analyze_permissive_email_autolink(MD_CTX* ctx, int mark_index)
+{
+ MD_MARK* opener = &ctx->marks[mark_index];
+ int closer_index;
+ MD_MARK* closer;
+ OFF beg = opener->beg;
+ OFF end = opener->end;
+ int dot_count = 0;
+
+ MD_ASSERT(opener->ch == _T('@'));
+
+ /* Scan for name before '@'. */
+ while(beg > 0 && (ISALNUM(beg-1) || ISANYOF(beg-1, _T(".-_+"))))
+ beg--;
+
+ /* Scan for domain after '@'. */
+ while(end < ctx->size && (ISALNUM(end) || ISANYOF(end, _T(".-_")))) {
+ if(CH(end) == _T('.'))
+ dot_count++;
+ end++;
+ }
+ if(CH(end-1) == _T('.')) { /* Final '.' not part of it. */
+ dot_count--;
+ end--;
+ }
+ else if(ISANYOF2(end-1, _T('-'), _T('_'))) /* These are forbidden at the end. */
+ return;
+ if(CH(end-1) == _T('@') || dot_count == 0)
+ return;
+
+ /* Ok. Lets call it auto-link. Adapt opener and create closer to zero
+ * length so all the contents becomes the link text. */
+ closer_index = mark_index + 1;
+ closer = &ctx->marks[closer_index];
+ if (closer->ch != 'D') return;
+
+ opener->beg = beg;
+ opener->end = beg;
+ closer->ch = opener->ch;
+ closer->beg = end;
+ closer->end = end;
+ md_resolve_range(ctx, NULL, mark_index, closer_index);
+}
+
+static inline void
+md_analyze_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
+ int mark_beg, int mark_end, const CHAR* mark_chars)
+{
+ int i = mark_beg;
+ MD_UNUSED(lines);
+ MD_UNUSED(n_lines);
+
+ while(i < mark_end) {
+ MD_MARK* mark = &ctx->marks[i];
+
+ /* Skip resolved spans. */
+ if(mark->flags & MD_MARK_RESOLVED) {
+ if(mark->flags & MD_MARK_OPENER) {
+ MD_ASSERT(i < mark->next);
+ i = mark->next + 1;
+ } else {
+ i++;
+ }
+ continue;
+ }
+
+ /* Skip marks we do not want to deal with. */
+ if(!ISANYOF_(mark->ch, mark_chars)) {
+ i++;
+ continue;
+ }
+
+ /* Analyze the mark. */
+ switch(mark->ch) {
+ case '[': /* Pass through. */
+ case '!': /* Pass through. */
+ case ']': md_analyze_bracket(ctx, i); break;
+ case '&': md_analyze_entity(ctx, i); break;
+ case '|': md_analyze_table_cell_boundary(ctx, i); break;
+ case '_': /* Pass through. */
+ case '*': md_analyze_emph(ctx, i); break;
+ case '~': md_analyze_tilde(ctx, i); break;
+ case '$': md_analyze_dollar(ctx, i); break;
+ case '.': /* Pass through. */
+ case ':': md_analyze_permissive_url_autolink(ctx, i); break;
+ case '@': md_analyze_permissive_email_autolink(ctx, i); break;
+ }
+
+ i++;
+ }
+}
+
+/* Analyze marks (build ctx->marks). */
+static int
+md_analyze_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mode)
+{
+ int ret;
+
+ /* Reset the previously collected stack of marks. */
+ ctx->n_marks = 0;
+
+ /* Collect all marks. */
+ MD_CHECK(md_collect_marks(ctx, lines, n_lines, table_mode));
+
+ /* (1) Links. */
+ md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("[]!"));
+ MD_CHECK(md_resolve_links(ctx, lines, n_lines));
+ BRACKET_OPENERS.head = -1;
+ BRACKET_OPENERS.tail = -1;
+ ctx->unresolved_link_head = -1;
+ ctx->unresolved_link_tail = -1;
+
+ if(table_mode) {
+ /* (2) Analyze table cell boundaries.
+ * Note we reset TABLECELLBOUNDARIES chain prior to the call md_analyze_marks(),
+ * not after, because caller may need it. */
+ MD_ASSERT(n_lines == 1);
+ TABLECELLBOUNDARIES.head = -1;
+ TABLECELLBOUNDARIES.tail = -1;
+ ctx->n_table_cell_boundaries = 0;
+ md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("|"));
+ return ret;
+ }
+
+ /* (3) Emphasis and strong emphasis; permissive autolinks. */
+ md_analyze_link_contents(ctx, lines, n_lines, 0, ctx->n_marks);
+
+abort:
+ return ret;
+}
+
+static void
+md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
+ int mark_beg, int mark_end)
+{
+ int i;
+
+ md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, _T("&"));
+ md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, _T("*_~$@:."));
+
+ for(i = OPENERS_CHAIN_FIRST; i <= OPENERS_CHAIN_LAST; i++) {
+ ctx->mark_chains[i].head = -1;
+ ctx->mark_chains[i].tail = -1;
+ }
+}
+
+static int
+md_enter_leave_span_a(MD_CTX* ctx, int enter, MD_SPANTYPE type,
+ const CHAR* dest, SZ dest_size, int prohibit_escapes_in_dest,
+ const CHAR* title, SZ title_size)
+{
+ MD_ATTRIBUTE_BUILD href_build = { 0 };
+ MD_ATTRIBUTE_BUILD title_build = { 0 };
+ MD_SPAN_A_DETAIL det;
+ int ret = 0;
+
+ /* Note we here rely on fact that MD_SPAN_A_DETAIL and
+ * MD_SPAN_IMG_DETAIL are binary-compatible. */
+ memset(&det, 0, sizeof(MD_SPAN_A_DETAIL));
+ MD_CHECK(md_build_attribute(ctx, dest, dest_size,
+ (prohibit_escapes_in_dest ? MD_BUILD_ATTR_NO_ESCAPES : 0),
+ &det.href, &href_build));
+ MD_CHECK(md_build_attribute(ctx, title, title_size, 0, &det.title, &title_build));
+
+ if(enter)
+ MD_ENTER_SPAN(type, &det);
+ else
+ MD_LEAVE_SPAN(type, &det);
+
+abort:
+ md_free_attribute(ctx, &href_build);
+ md_free_attribute(ctx, &title_build);
+ return ret;
+}
+
+static int
+md_enter_leave_span_wikilink(MD_CTX* ctx, int enter, const CHAR* target, SZ target_size)
+{
+ MD_ATTRIBUTE_BUILD target_build = { 0 };
+ MD_SPAN_WIKILINK_DETAIL det;
+ int ret = 0;
+
+ memset(&det, 0, sizeof(MD_SPAN_WIKILINK_DETAIL));
+ MD_CHECK(md_build_attribute(ctx, target, target_size, 0, &det.target, &target_build));
+
+ if (enter)
+ MD_ENTER_SPAN(MD_SPAN_WIKILINK, &det);
+ else
+ MD_LEAVE_SPAN(MD_SPAN_WIKILINK, &det);
+
+abort:
+ md_free_attribute(ctx, &target_build);
+ return ret;
+}
+
+
+/* Render the output, accordingly to the analyzed ctx->marks. */
+static int
+md_process_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
+{
+ MD_TEXTTYPE text_type;
+ const MD_LINE* line = lines;
+ MD_MARK* prev_mark = NULL;
+ MD_MARK* mark;
+ OFF off = lines[0].beg;
+ OFF end = lines[n_lines-1].end;
+ int enforce_hardbreak = 0;
+ int ret = 0;
+
+ /* Find first resolved mark. Note there is always at least one resolved
+ * mark, the dummy last one after the end of the latest line we actually
+ * never really reach. This saves us of a lot of special checks and cases
+ * in this function. */
+ mark = ctx->marks;
+ while(!(mark->flags & MD_MARK_RESOLVED))
+ mark++;
+
+ text_type = MD_TEXT_NORMAL;
+
+ while(1) {
+ /* Process the text up to the next mark or end-of-line. */
+ OFF tmp = (line->end < mark->beg ? line->end : mark->beg);
+ if(tmp > off) {
+ MD_TEXT(text_type, STR(off), tmp - off);
+ off = tmp;
+ }
+
+ /* If reached the mark, process it and move to next one. */
+ if(off >= mark->beg) {
+ switch(mark->ch) {
+ case '\\': /* Backslash escape. */
+ if(ISNEWLINE(mark->beg+1))
+ enforce_hardbreak = 1;
+ else
+ MD_TEXT(text_type, STR(mark->beg+1), 1);
+ break;
+
+ case ' ': /* Non-trivial space. */
+ MD_TEXT(text_type, _T(" "), 1);
+ break;
+
+ case '`': /* Code span. */
+ if(mark->flags & MD_MARK_OPENER) {
+ MD_ENTER_SPAN(MD_SPAN_CODE, NULL);
+ text_type = MD_TEXT_CODE;
+ } else {
+ MD_LEAVE_SPAN(MD_SPAN_CODE, NULL);
+ text_type = MD_TEXT_NORMAL;
+ }
+ break;
+
+ case '_': /* Underline (or emphasis if we fall through). */
+ if(ctx->parser.flags & MD_FLAG_UNDERLINE) {
+ if(mark->flags & MD_MARK_OPENER) {
+ while(off < mark->end) {
+ MD_ENTER_SPAN(MD_SPAN_U, NULL);
+ off++;
+ }
+ } else {
+ while(off < mark->end) {
+ MD_LEAVE_SPAN(MD_SPAN_U, NULL);
+ off++;
+ }
+ }
+ break;
+ }
+ MD_FALLTHROUGH();
+
+ case '*': /* Emphasis, strong emphasis. */
+ if(mark->flags & MD_MARK_OPENER) {
+ if((mark->end - off) % 2) {
+ MD_ENTER_SPAN(MD_SPAN_EM, NULL);
+ off++;
+ }
+ while(off + 1 < mark->end) {
+ MD_ENTER_SPAN(MD_SPAN_STRONG, NULL);
+ off += 2;
+ }
+ } else {
+ while(off + 1 < mark->end) {
+ MD_LEAVE_SPAN(MD_SPAN_STRONG, NULL);
+ off += 2;
+ }
+ if((mark->end - off) % 2) {
+ MD_LEAVE_SPAN(MD_SPAN_EM, NULL);
+ off++;
+ }
+ }
+ break;
+
+ case '~':
+ if(mark->flags & MD_MARK_OPENER)
+ MD_ENTER_SPAN(MD_SPAN_DEL, NULL);
+ else
+ MD_LEAVE_SPAN(MD_SPAN_DEL, NULL);
+ break;
+
+ case '$':
+ if(mark->flags & MD_MARK_OPENER) {
+ MD_ENTER_SPAN((mark->end - off) % 2 ? MD_SPAN_LATEXMATH : MD_SPAN_LATEXMATH_DISPLAY, NULL);
+ text_type = MD_TEXT_LATEXMATH;
+ } else {
+ MD_LEAVE_SPAN((mark->end - off) % 2 ? MD_SPAN_LATEXMATH : MD_SPAN_LATEXMATH_DISPLAY, NULL);
+ text_type = MD_TEXT_NORMAL;
+ }
+ break;
+
+ case '[': /* Link, wiki link, image. */
+ case '!':
+ case ']':
+ {
+ const MD_MARK* opener = (mark->ch != ']' ? mark : &ctx->marks[mark->prev]);
+ const MD_MARK* closer = &ctx->marks[opener->next];
+ const MD_MARK* dest_mark;
+ const MD_MARK* title_mark;
+
+ if ((opener->ch == '[' && closer->ch == ']') &&
+ opener->end - opener->beg >= 2 &&
+ closer->end - closer->beg >= 2)
+ {
+ int has_label = (opener->end - opener->beg > 2);
+ SZ target_sz;
+
+ if(has_label)
+ target_sz = opener->end - (opener->beg+2);
+ else
+ target_sz = closer->beg - opener->end;
+
+ MD_CHECK(md_enter_leave_span_wikilink(ctx, (mark->ch != ']'),
+ has_label ? STR(opener->beg+2) : STR(opener->end),
+ target_sz));
+
+ break;
+ }
+
+ dest_mark = opener+1;
+ MD_ASSERT(dest_mark->ch == 'D');
+ title_mark = opener+2;
+ if (title_mark->ch != 'D') break;
+
+ MD_CHECK(md_enter_leave_span_a(ctx, (mark->ch != ']'),
+ (opener->ch == '!' ? MD_SPAN_IMG : MD_SPAN_A),
+ STR(dest_mark->beg), dest_mark->end - dest_mark->beg, FALSE,
+ md_mark_get_ptr(ctx, (int)(title_mark - ctx->marks)),
+ title_mark->prev));
+
+ /* link/image closer may span multiple lines. */
+ if(mark->ch == ']') {
+ while(mark->end > line->end)
+ line++;
+ }
+
+ break;
+ }
+
+ case '<':
+ case '>': /* Autolink or raw HTML. */
+ if(!(mark->flags & MD_MARK_AUTOLINK)) {
+ /* Raw HTML. */
+ if(mark->flags & MD_MARK_OPENER)
+ text_type = MD_TEXT_HTML;
+ else
+ text_type = MD_TEXT_NORMAL;
+ break;
+ }
+ /* Pass through, if auto-link. */
+ MD_FALLTHROUGH();
+
+ case '@': /* Permissive e-mail autolink. */
+ case ':': /* Permissive URL autolink. */
+ case '.': /* Permissive WWW autolink. */
+ {
+ MD_MARK* opener = ((mark->flags & MD_MARK_OPENER) ? mark : &ctx->marks[mark->prev]);
+ MD_MARK* closer = &ctx->marks[opener->next];
+ const CHAR* dest = STR(opener->end);
+ SZ dest_size = closer->beg - opener->end;
+
+ /* For permissive auto-links we do not know closer mark
+ * position at the time of md_collect_marks(), therefore
+ * it can be out-of-order in ctx->marks[].
+ *
+ * With this flag, we make sure that we output the closer
+ * only if we processed the opener. */
+ if(mark->flags & MD_MARK_OPENER)
+ closer->flags |= MD_MARK_VALIDPERMISSIVEAUTOLINK;
+
+ if(opener->ch == '@' || opener->ch == '.') {
+ dest_size += 7;
+ MD_TEMP_BUFFER(dest_size * sizeof(CHAR));
+ memcpy(ctx->buffer,
+ (opener->ch == '@' ? _T("mailto:") : _T("http://")),
+ 7 * sizeof(CHAR));
+ memcpy(ctx->buffer + 7, dest, (dest_size-7) * sizeof(CHAR));
+ dest = ctx->buffer;
+ }
+
+ if(closer->flags & MD_MARK_VALIDPERMISSIVEAUTOLINK)
+ MD_CHECK(md_enter_leave_span_a(ctx, (mark->flags & MD_MARK_OPENER),
+ MD_SPAN_A, dest, dest_size, TRUE, NULL, 0));
+ break;
+ }
+
+ case '&': /* Entity. */
+ MD_TEXT(MD_TEXT_ENTITY, STR(mark->beg), mark->end - mark->beg);
+ break;
+
+ case '\0':
+ MD_TEXT(MD_TEXT_NULLCHAR, _T(""), 1);
+ break;
+
+ case 127:
+ goto abort;
+ }
+
+ off = mark->end;
+
+ /* Move to next resolved mark. */
+ prev_mark = mark;
+ mark++;
+ while(!(mark->flags & MD_MARK_RESOLVED) || mark->beg < off)
+ mark++;
+ }
+
+ /* If reached end of line, move to next one. */
+ if(off >= line->end) {
+ /* If it is the last line, we are done. */
+ if(off >= end)
+ break;
+
+ if(text_type == MD_TEXT_CODE || text_type == MD_TEXT_LATEXMATH) {
+ OFF tmp;
+
+ MD_ASSERT(prev_mark != NULL);
+ MD_ASSERT(ISANYOF2_(prev_mark->ch, '`', '$') && (prev_mark->flags & MD_MARK_OPENER));
+ MD_ASSERT(ISANYOF2_(mark->ch, '`', '$') && (mark->flags & MD_MARK_CLOSER));
+
+ /* Inside a code span, trailing line whitespace has to be
+ * outputted. */
+ tmp = off;
+ while(off < ctx->size && ISBLANK(off))
+ off++;
+ if(off > tmp)
+ MD_TEXT(text_type, STR(tmp), off-tmp);
+
+ /* and new lines are transformed into single spaces. */
+ if(prev_mark->end < off && off < mark->beg)
+ MD_TEXT(text_type, _T(" "), 1);
+ } else if(text_type == MD_TEXT_HTML) {
+ /* Inside raw HTML, we output the new line verbatim, including
+ * any trailing spaces. */
+ OFF tmp = off;
+
+ while(tmp < end && ISBLANK(tmp))
+ tmp++;
+ if(tmp > off)
+ MD_TEXT(MD_TEXT_HTML, STR(off), tmp - off);
+ MD_TEXT(MD_TEXT_HTML, _T("\n"), 1);
+ } else {
+ /* Output soft or hard line break. */
+ MD_TEXTTYPE break_type = MD_TEXT_SOFTBR;
+
+ if(text_type == MD_TEXT_NORMAL) {
+ if(enforce_hardbreak)
+ break_type = MD_TEXT_BR;
+ else if((CH(line->end) == _T(' ') && CH(line->end+1) == _T(' ')))
+ break_type = MD_TEXT_BR;
+ }
+
+ MD_TEXT(break_type, _T("\n"), 1);
+ }
+
+ /* Move to the next line. */
+ line++;
+ off = line->beg;
+
+ enforce_hardbreak = 0;
+ }
+ }
+
+abort:
+ return ret;
+}
+
+
+/***************************
+ *** Processing Tables ***
+ ***************************/
+
+static void
+md_analyze_table_alignment(MD_CTX* ctx, OFF beg, OFF end, MD_ALIGN* align, int n_align)
+{
+ static const MD_ALIGN align_map[] = { MD_ALIGN_DEFAULT, MD_ALIGN_LEFT, MD_ALIGN_RIGHT, MD_ALIGN_CENTER };
+ OFF off = beg;
+
+ while(n_align > 0) {
+ int index = 0; /* index into align_map[] */
+
+ while(CH(off) != _T('-'))
+ off++;
+ if(off > beg && CH(off-1) == _T(':'))
+ index |= 1;
+ while(off < end && CH(off) == _T('-'))
+ off++;
+ if(off < end && CH(off) == _T(':'))
+ index |= 2;
+
+ *align = align_map[index];
+ align++;
+ n_align--;
+ }
+
+}
+
+/* Forward declaration. */
+static int md_process_normal_block_contents(MD_CTX* ctx, const MD_LINE* lines, int n_lines);
+
+static int
+md_process_table_cell(MD_CTX* ctx, MD_BLOCKTYPE cell_type, MD_ALIGN align, OFF beg, OFF end)
+{
+ MD_LINE line;
+ MD_BLOCK_TD_DETAIL det;
+ int ret = 0;
+
+ while(beg < end && ISWHITESPACE(beg))
+ beg++;
+ while(end > beg && ISWHITESPACE(end-1))
+ end--;
+
+ det.align = align;
+ line.beg = beg;
+ line.end = end;
+
+ MD_ENTER_BLOCK(cell_type, &det);
+ MD_CHECK(md_process_normal_block_contents(ctx, &line, 1));
+ MD_LEAVE_BLOCK(cell_type, &det);
+
+abort:
+ return ret;
+}
+
+static int
+md_process_table_row(MD_CTX* ctx, MD_BLOCKTYPE cell_type, OFF beg, OFF end,
+ const MD_ALIGN* align, int col_count)
+{
+ MD_LINE line;
+ OFF* pipe_offs = NULL;
+ int i, j, k, n;
+ int ret = 0;
+
+ line.beg = beg;
+ line.end = end;
+
+ /* Break the line into table cells by identifying pipe characters who
+ * form the cell boundary. */
+ MD_CHECK(md_analyze_inlines(ctx, &line, 1, TRUE));
+
+ /* We have to remember the cell boundaries in local buffer because
+ * ctx->marks[] shall be reused during cell contents processing. */
+ n = ctx->n_table_cell_boundaries + 2;
+ pipe_offs = (OFF*) malloc(n * sizeof(OFF));
+ if(pipe_offs == NULL) {
+ MD_LOG("malloc() failed.");
+ ret = -1;
+ goto abort;
+ }
+ j = 0;
+ pipe_offs[j++] = beg;
+ for(i = TABLECELLBOUNDARIES.head; i >= 0; i = ctx->marks[i].next) {
+ MD_MARK* mark = &ctx->marks[i];
+ pipe_offs[j++] = mark->end;
+ }
+ pipe_offs[j++] = end+1;
+
+ /* Process cells. */
+ MD_ENTER_BLOCK(MD_BLOCK_TR, NULL);
+ k = 0;
+ for(i = 0; i < j-1 && k < col_count; i++) {
+ if(pipe_offs[i] < pipe_offs[i+1]-1)
+ MD_CHECK(md_process_table_cell(ctx, cell_type, align[k++], pipe_offs[i], pipe_offs[i+1]-1));
+ }
+ /* Make sure we call enough table cells even if the current table contains
+ * too few of them. */
+ while(k < col_count)
+ MD_CHECK(md_process_table_cell(ctx, cell_type, align[k++], 0, 0));
+ MD_LEAVE_BLOCK(MD_BLOCK_TR, NULL);
+
+abort:
+ free(pipe_offs);
+
+ /* Free any temporary memory blocks stored within some dummy marks. */
+ for(i = PTR_CHAIN.head; i >= 0; i = ctx->marks[i].next)
+ free(md_mark_get_ptr(ctx, i));
+ PTR_CHAIN.head = -1;
+ PTR_CHAIN.tail = -1;
+
+ return ret;
+}
+
+static int
+md_process_table_block_contents(MD_CTX* ctx, int col_count, const MD_LINE* lines, int n_lines)
+{
+ MD_ALIGN* align;
+ int i;
+ int ret = 0;
+
+ /* At least two lines have to be present: The column headers and the line
+ * with the underlines. */
+ MD_ASSERT(n_lines >= 2);
+
+ align = malloc(col_count * sizeof(MD_ALIGN));
+ if(align == NULL) {
+ MD_LOG("malloc() failed.");
+ ret = -1;
+ goto abort;
+ }
+
+ md_analyze_table_alignment(ctx, lines[1].beg, lines[1].end, align, col_count);
+
+ MD_ENTER_BLOCK(MD_BLOCK_THEAD, NULL);
+ MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TH,
+ lines[0].beg, lines[0].end, align, col_count));
+ MD_LEAVE_BLOCK(MD_BLOCK_THEAD, NULL);
+
+ if(n_lines > 2) {
+ MD_ENTER_BLOCK(MD_BLOCK_TBODY, NULL);
+ for(i = 2; i < n_lines; i++) {
+ MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TD,
+ lines[i].beg, lines[i].end, align, col_count));
+ }
+ MD_LEAVE_BLOCK(MD_BLOCK_TBODY, NULL);
+ }
+
+abort:
+ free(align);
+ return ret;
+}
+
+
+/**************************
+ *** Processing Block ***
+ **************************/
+
+#define MD_BLOCK_CONTAINER_OPENER 0x01
+#define MD_BLOCK_CONTAINER_CLOSER 0x02
+#define MD_BLOCK_CONTAINER (MD_BLOCK_CONTAINER_OPENER | MD_BLOCK_CONTAINER_CLOSER)
+#define MD_BLOCK_LOOSE_LIST 0x04
+#define MD_BLOCK_SETEXT_HEADER 0x08
+
+struct MD_BLOCK_tag {
+ MD_BLOCKTYPE type : 8;
+ unsigned flags : 8;
+
+ /* MD_BLOCK_H: Header level (1 - 6)
+ * MD_BLOCK_CODE: Non-zero if fenced, zero if indented.
+ * MD_BLOCK_LI: Task mark character (0 if not task list item, 'x', 'X' or ' ').
+ * MD_BLOCK_TABLE: Column count (as determined by the table underline).
+ */
+ unsigned data : 16;
+
+ /* Leaf blocks: Count of lines (MD_LINE or MD_VERBATIMLINE) on the block.
+ * MD_BLOCK_LI: Task mark offset in the input doc.
+ * MD_BLOCK_OL: Start item number.
+ */
+ unsigned n_lines;
+};
+
+struct MD_CONTAINER_tag {
+ CHAR ch;
+ unsigned is_loose : 8;
+ unsigned is_task : 8;
+ unsigned start;
+ unsigned mark_indent;
+ unsigned contents_indent;
+ OFF block_byte_off;
+ OFF task_mark_off;
+};
+
+
+static int
+md_process_normal_block_contents(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
+{
+ int i;
+ int ret;
+
+ MD_CHECK(md_analyze_inlines(ctx, lines, n_lines, FALSE));
+ MD_CHECK(md_process_inlines(ctx, lines, n_lines));
+
+abort:
+ /* Free any temporary memory blocks stored within some dummy marks. */
+ for(i = PTR_CHAIN.head; i >= 0; i = ctx->marks[i].next)
+ free(md_mark_get_ptr(ctx, i));
+ PTR_CHAIN.head = -1;
+ PTR_CHAIN.tail = -1;
+
+ return ret;
+}
+
+static int
+md_process_verbatim_block_contents(MD_CTX* ctx, MD_TEXTTYPE text_type, const MD_VERBATIMLINE* lines, int n_lines)
+{
+ static const CHAR indent_chunk_str[] = _T(" ");
+ static const SZ indent_chunk_size = SIZEOF_ARRAY(indent_chunk_str) - 1;
+
+ int i;
+ int ret = 0;
+
+ for(i = 0; i < n_lines; i++) {
+ const MD_VERBATIMLINE* line = &lines[i];
+ int indent = line->indent;
+
+ MD_ASSERT(indent >= 0);
+
+ /* Output code indentation. */
+ while(indent > (int) indent_chunk_size) {
+ MD_TEXT(text_type, indent_chunk_str, indent_chunk_size);
+ indent -= indent_chunk_size;
+ }
+ if(indent > 0)
+ MD_TEXT(text_type, indent_chunk_str, indent);
+
+ /* Output the code line itself. */
+ MD_TEXT_INSECURE(text_type, STR(line->beg), line->end - line->beg);
+
+ /* Enforce end-of-line. */
+ MD_TEXT(text_type, _T("\n"), 1);
+ }
+
+abort:
+ return ret;
+}
+
+static int
+md_process_code_block_contents(MD_CTX* ctx, int is_fenced, const MD_VERBATIMLINE* lines, int n_lines)
+{
+ if(is_fenced) {
+ /* Skip the first line in case of fenced code: It is the fence.
+ * (Only the starting fence is present due to logic in md_analyze_line().) */
+ lines++;
+ n_lines--;
+ } else {
+ /* Ignore blank lines at start/end of indented code block. */
+ while(n_lines > 0 && lines[0].beg == lines[0].end) {
+ lines++;
+ n_lines--;
+ }
+ while(n_lines > 0 && lines[n_lines-1].beg == lines[n_lines-1].end) {
+ n_lines--;
+ }
+ }
+
+ if(n_lines == 0)
+ return 0;
+
+ return md_process_verbatim_block_contents(ctx, MD_TEXT_CODE, lines, n_lines);
+}
+
+static int
+md_setup_fenced_code_detail(MD_CTX* ctx, const MD_BLOCK* block, MD_BLOCK_CODE_DETAIL* det,
+ MD_ATTRIBUTE_BUILD* info_build, MD_ATTRIBUTE_BUILD* lang_build)
+{
+ const MD_VERBATIMLINE* fence_line = (const MD_VERBATIMLINE*)(block + 1);
+ OFF beg = fence_line->beg;
+ OFF end = fence_line->end;
+ OFF lang_end;
+ CHAR fence_ch = CH(fence_line->beg);
+ int ret = 0;
+
+ /* Skip the fence itself. */
+ while(beg < ctx->size && CH(beg) == fence_ch)
+ beg++;
+ /* Trim initial spaces. */
+ while(beg < ctx->size && CH(beg) == _T(' '))
+ beg++;
+
+ /* Trim trailing spaces. */
+ while(end > beg && CH(end-1) == _T(' '))
+ end--;
+
+ /* Build info string attribute. */
+ MD_CHECK(md_build_attribute(ctx, STR(beg), end - beg, 0, &det->info, info_build));
+
+ /* Build info string attribute. */
+ lang_end = beg;
+ while(lang_end < end && !ISWHITESPACE(lang_end))
+ lang_end++;
+ MD_CHECK(md_build_attribute(ctx, STR(beg), lang_end - beg, 0, &det->lang, lang_build));
+
+ det->fence_char = fence_ch;
+
+abort:
+ return ret;
+}
+
+static int
+md_process_leaf_block(MD_CTX* ctx, const MD_BLOCK* block)
+{
+ union {
+ MD_BLOCK_H_DETAIL header;
+ MD_BLOCK_CODE_DETAIL code;
+ MD_BLOCK_TABLE_DETAIL table;
+ } det;
+ MD_ATTRIBUTE_BUILD info_build;
+ MD_ATTRIBUTE_BUILD lang_build;
+ int is_in_tight_list;
+ int clean_fence_code_detail = FALSE;
+ int ret = 0;
+
+ memset(&det, 0, sizeof(det));
+
+ if(ctx->n_containers == 0)
+ is_in_tight_list = FALSE;
+ else
+ is_in_tight_list = !ctx->containers[ctx->n_containers-1].is_loose;
+
+ switch(block->type) {
+ case MD_BLOCK_H:
+ det.header.level = block->data;
+ break;
+
+ case MD_BLOCK_CODE:
+ /* For fenced code block, we may need to set the info string. */
+ if(block->data != 0) {
+ memset(&det.code, 0, sizeof(MD_BLOCK_CODE_DETAIL));
+ clean_fence_code_detail = TRUE;
+ MD_CHECK(md_setup_fenced_code_detail(ctx, block, &det.code, &info_build, &lang_build));
+ }
+ break;
+
+ case MD_BLOCK_TABLE:
+ det.table.col_count = block->data;
+ det.table.head_row_count = 1;
+ det.table.body_row_count = block->n_lines - 2;
+ break;
+
+ default:
+ /* Noop. */
+ break;
+ }
+
+ if(!is_in_tight_list || block->type != MD_BLOCK_P)
+ MD_ENTER_BLOCK(block->type, (void*) &det);
+
+ /* Process the block contents accordingly to is type. */
+ switch(block->type) {
+ case MD_BLOCK_HR:
+ /* noop */
+ break;
+
+ case MD_BLOCK_CODE:
+ MD_CHECK(md_process_code_block_contents(ctx, (block->data != 0),
+ (const MD_VERBATIMLINE*)(block + 1), block->n_lines));
+ break;
+
+ case MD_BLOCK_HTML:
+ MD_CHECK(md_process_verbatim_block_contents(ctx, MD_TEXT_HTML,
+ (const MD_VERBATIMLINE*)(block + 1), block->n_lines));
+ break;
+
+ case MD_BLOCK_TABLE:
+ MD_CHECK(md_process_table_block_contents(ctx, block->data,
+ (const MD_LINE*)(block + 1), block->n_lines));
+ break;
+
+ default:
+ MD_CHECK(md_process_normal_block_contents(ctx,
+ (const MD_LINE*)(block + 1), block->n_lines));
+ break;
+ }
+
+ if(!is_in_tight_list || block->type != MD_BLOCK_P)
+ MD_LEAVE_BLOCK(block->type, (void*) &det);
+
+abort:
+ if(clean_fence_code_detail) {
+ md_free_attribute(ctx, &info_build);
+ md_free_attribute(ctx, &lang_build);
+ }
+ return ret;
+}
+
+static int
+md_process_all_blocks(MD_CTX* ctx)
+{
+ int byte_off = 0;
+ int ret = 0;
+
+ /* ctx->containers now is not needed for detection of lists and list items
+ * so we reuse it for tracking what lists are loose or tight. We rely
+ * on the fact the vector is large enough to hold the deepest nesting
+ * level of lists. */
+ ctx->n_containers = 0;
+
+ while(byte_off < ctx->n_block_bytes) {
+ MD_BLOCK* block = (MD_BLOCK*)((char*)ctx->block_bytes + byte_off);
+ union {
+ MD_BLOCK_UL_DETAIL ul;
+ MD_BLOCK_OL_DETAIL ol;
+ MD_BLOCK_LI_DETAIL li;
+ } det;
+
+ switch(block->type) {
+ case MD_BLOCK_UL:
+ det.ul.is_tight = (block->flags & MD_BLOCK_LOOSE_LIST) ? FALSE : TRUE;
+ det.ul.mark = (CHAR) block->data;
+ break;
+
+ case MD_BLOCK_OL:
+ det.ol.start = block->n_lines;
+ det.ol.is_tight = (block->flags & MD_BLOCK_LOOSE_LIST) ? FALSE : TRUE;
+ det.ol.mark_delimiter = (CHAR) block->data;
+ break;
+
+ case MD_BLOCK_LI:
+ det.li.is_task = (block->data != 0);
+ det.li.task_mark = (CHAR) block->data;
+ det.li.task_mark_offset = (OFF) block->n_lines;
+ break;
+
+ default:
+ /* noop */
+ break;
+ }
+
+ if(block->flags & MD_BLOCK_CONTAINER) {
+ if(block->flags & MD_BLOCK_CONTAINER_CLOSER) {
+ MD_LEAVE_BLOCK(block->type, &det);
+
+ if(block->type == MD_BLOCK_UL || block->type == MD_BLOCK_OL || block->type == MD_BLOCK_QUOTE)
+ ctx->n_containers--;
+ }
+
+ if(block->flags & MD_BLOCK_CONTAINER_OPENER) {
+ MD_ENTER_BLOCK(block->type, &det);
+
+ if(block->type == MD_BLOCK_UL || block->type == MD_BLOCK_OL) {
+ ctx->containers[ctx->n_containers].is_loose = (block->flags & MD_BLOCK_LOOSE_LIST);
+ ctx->n_containers++;
+ } else if(block->type == MD_BLOCK_QUOTE) {
+ /* This causes that any text in a block quote, even if
+ * nested inside a tight list item, is wrapped with
+ * <p>...</p>. */
+ ctx->containers[ctx->n_containers].is_loose = TRUE;
+ ctx->n_containers++;
+ }
+ }
+ } else {
+ MD_CHECK(md_process_leaf_block(ctx, block));
+
+ if(block->type == MD_BLOCK_CODE || block->type == MD_BLOCK_HTML)
+ byte_off += block->n_lines * sizeof(MD_VERBATIMLINE);
+ else
+ byte_off += block->n_lines * sizeof(MD_LINE);
+ }
+
+ byte_off += sizeof(MD_BLOCK);
+ }
+
+ ctx->n_block_bytes = 0;
+
+abort:
+ return ret;
+}
+
+
+/************************************
+ *** Grouping Lines into Blocks ***
+ ************************************/
+
+static void*
+md_push_block_bytes(MD_CTX* ctx, int n_bytes)
+{
+ void* ptr;
+
+ if(ctx->n_block_bytes + n_bytes > ctx->alloc_block_bytes) {
+ void* new_block_bytes;
+
+ ctx->alloc_block_bytes = (ctx->alloc_block_bytes > 0
+ ? ctx->alloc_block_bytes + ctx->alloc_block_bytes / 2
+ : 512);
+ new_block_bytes = realloc(ctx->block_bytes, ctx->alloc_block_bytes);
+ if(new_block_bytes == NULL) {
+ MD_LOG("realloc() failed.");
+ return NULL;
+ }
+
+ /* Fix the ->current_block after the reallocation. */
+ if(ctx->current_block != NULL) {
+ OFF off_current_block = (OFF) ((char*) ctx->current_block - (char*) ctx->block_bytes);
+ ctx->current_block = (MD_BLOCK*) ((char*) new_block_bytes + off_current_block);
+ }
+
+ ctx->block_bytes = new_block_bytes;
+ }
+
+ ptr = (char*)ctx->block_bytes + ctx->n_block_bytes;
+ ctx->n_block_bytes += n_bytes;
+ return ptr;
+}
+
+static int
+md_start_new_block(MD_CTX* ctx, const MD_LINE_ANALYSIS* line)
+{
+ MD_BLOCK* block;
+
+ MD_ASSERT(ctx->current_block == NULL);
+
+ block = (MD_BLOCK*) md_push_block_bytes(ctx, sizeof(MD_BLOCK));
+ if(block == NULL)
+ return -1;
+
+ switch(line->type) {
+ case MD_LINE_HR:
+ block->type = MD_BLOCK_HR;
+ break;
+
+ case MD_LINE_ATXHEADER:
+ case MD_LINE_SETEXTHEADER:
+ block->type = MD_BLOCK_H;
+ break;
+
+ case MD_LINE_FENCEDCODE:
+ case MD_LINE_INDENTEDCODE:
+ block->type = MD_BLOCK_CODE;
+ break;
+
+ case MD_LINE_TEXT:
+ block->type = MD_BLOCK_P;
+ break;
+
+ case MD_LINE_HTML:
+ block->type = MD_BLOCK_HTML;
+ break;
+
+ case MD_LINE_BLANK:
+ case MD_LINE_SETEXTUNDERLINE:
+ case MD_LINE_TABLEUNDERLINE:
+ default:
+ MD_UNREACHABLE();
+ break;
+ }
+
+ block->flags = 0;
+ block->data = line->data;
+ block->n_lines = 0;
+
+ ctx->current_block = block;
+ return 0;
+}
+
+/* Eat from start of current (textual) block any reference definitions and
+ * remember them so we can resolve any links referring to them.
+ *
+ * (Reference definitions can only be at start of it as they cannot break
+ * a paragraph.)
+ */
+static int
+md_consume_link_reference_definitions(MD_CTX* ctx)
+{
+ MD_LINE* lines = (MD_LINE*) (ctx->current_block + 1);
+ int n_lines = ctx->current_block->n_lines;
+ int n = 0;
+
+ /* Compute how many lines at the start of the block form one or more
+ * reference definitions. */
+ while(n < n_lines) {
+ int n_link_ref_lines;
+
+ n_link_ref_lines = md_is_link_reference_definition(ctx,
+ lines + n, n_lines - n);
+ /* Not a reference definition? */
+ if(n_link_ref_lines == 0)
+ break;
+
+ /* We fail if it is the ref. def. but it could not be stored due
+ * a memory allocation error. */
+ if(n_link_ref_lines < 0)
+ return -1;
+
+ n += n_link_ref_lines;
+ }
+
+ /* If there was at least one reference definition, we need to remove
+ * its lines from the block, or perhaps even the whole block. */
+ if(n > 0) {
+ if(n == n_lines) {
+ /* Remove complete block. */
+ ctx->n_block_bytes -= n * sizeof(MD_LINE);
+ ctx->n_block_bytes -= sizeof(MD_BLOCK);
+ ctx->current_block = NULL;
+ } else {
+ /* Remove just some initial lines from the block. */
+ memmove(lines, lines + n, (n_lines - n) * sizeof(MD_LINE));
+ ctx->current_block->n_lines -= n;
+ ctx->n_block_bytes -= n * sizeof(MD_LINE);
+ }
+ }
+
+ return 0;
+}
+
+static int
+md_end_current_block(MD_CTX* ctx)
+{
+ int ret = 0;
+
+ if(ctx->current_block == NULL)
+ return ret;
+
+ /* Check whether there is a reference definition. (We do this here instead
+ * of in md_analyze_line() because reference definition can take multiple
+ * lines.) */
+ if(ctx->current_block->type == MD_BLOCK_P ||
+ (ctx->current_block->type == MD_BLOCK_H && (ctx->current_block->flags & MD_BLOCK_SETEXT_HEADER)))
+ {
+ MD_LINE* lines = (MD_LINE*) (ctx->current_block + 1);
+ if(CH(lines[0].beg) == _T('[')) {
+ MD_CHECK(md_consume_link_reference_definitions(ctx));
+ if(ctx->current_block == NULL)
+ return ret;
+ }
+ }
+
+ if(ctx->current_block->type == MD_BLOCK_H && (ctx->current_block->flags & MD_BLOCK_SETEXT_HEADER)) {
+ int n_lines = ctx->current_block->n_lines;
+
+ if(n_lines > 1) {
+ /* Get rid of the underline. */
+ ctx->current_block->n_lines--;
+ ctx->n_block_bytes -= sizeof(MD_LINE);
+ } else {
+ /* Only the underline has left after eating the ref. defs.
+ * Keep the line as beginning of a new ordinary paragraph. */
+ ctx->current_block->type = MD_BLOCK_P;
+ return 0;
+ }
+ }
+
+ /* Mark we are not building any block anymore. */
+ ctx->current_block = NULL;
+
+abort:
+ return ret;
+}
+
+static int
+md_add_line_into_current_block(MD_CTX* ctx, const MD_LINE_ANALYSIS* analysis)
+{
+ MD_ASSERT(ctx->current_block != NULL);
+
+ if(ctx->current_block->type == MD_BLOCK_CODE || ctx->current_block->type == MD_BLOCK_HTML) {
+ MD_VERBATIMLINE* line;
+
+ line = (MD_VERBATIMLINE*) md_push_block_bytes(ctx, sizeof(MD_VERBATIMLINE));
+ if(line == NULL)
+ return -1;
+
+ line->indent = analysis->indent;
+ line->beg = analysis->beg;
+ line->end = analysis->end;
+ } else {
+ MD_LINE* line;
+
+ line = (MD_LINE*) md_push_block_bytes(ctx, sizeof(MD_LINE));
+ if(line == NULL)
+ return -1;
+
+ line->beg = analysis->beg;
+ line->end = analysis->end;
+ }
+ ctx->current_block->n_lines++;
+
+ return 0;
+}
+
+static int
+md_push_container_bytes(MD_CTX* ctx, MD_BLOCKTYPE type, unsigned start,
+ unsigned data, unsigned flags)
+{
+ MD_BLOCK* block;
+ int ret = 0;
+
+ MD_CHECK(md_end_current_block(ctx));
+
+ block = (MD_BLOCK*) md_push_block_bytes(ctx, sizeof(MD_BLOCK));
+ if(block == NULL)
+ return -1;
+
+ block->type = type;
+ block->flags = flags;
+ block->data = data;
+ block->n_lines = start;
+
+abort:
+ return ret;
+}
+
+
+
+/***********************
+ *** Line Analysis ***
+ ***********************/
+
+static int
+md_is_hr_line(MD_CTX* ctx, OFF beg, OFF* p_end, OFF* p_killer)
+{
+ OFF off = beg + 1;
+ int n = 1;
+
+ while(off < ctx->size && (CH(off) == CH(beg) || CH(off) == _T(' ') || CH(off) == _T('\t'))) {
+ if(CH(off) == CH(beg))
+ n++;
+ off++;
+ }
+
+ if(n < 3) {
+ *p_killer = off;
+ return FALSE;
+ }
+
+ /* Nothing else can be present on the line. */
+ if(off < ctx->size && !ISNEWLINE(off)) {
+ *p_killer = off;
+ return FALSE;
+ }
+
+ *p_end = off;
+ return TRUE;
+}
+
+static int
+md_is_atxheader_line(MD_CTX* ctx, OFF beg, OFF* p_beg, OFF* p_end, unsigned* p_level)
+{
+ int n;
+ OFF off = beg + 1;
+
+ while(off < ctx->size && CH(off) == _T('#') && off - beg < 7)
+ off++;
+ n = off - beg;
+
+ if(n > 6)
+ return FALSE;
+ *p_level = n;
+
+ if(!(ctx->parser.flags & MD_FLAG_PERMISSIVEATXHEADERS) && off < ctx->size &&
+ CH(off) != _T(' ') && CH(off) != _T('\t') && !ISNEWLINE(off))
+ return FALSE;
+
+ while(off < ctx->size && CH(off) == _T(' '))
+ off++;
+ *p_beg = off;
+ *p_end = off;
+ return TRUE;
+}
+
+static int
+md_is_setext_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_level)
+{
+ OFF off = beg + 1;
+
+ while(off < ctx->size && CH(off) == CH(beg))
+ off++;
+
+ /* Optionally, space(s) can follow. */
+ while(off < ctx->size && CH(off) == _T(' '))
+ off++;
+
+ /* But nothing more is allowed on the line. */
+ if(off < ctx->size && !ISNEWLINE(off))
+ return FALSE;
+
+ *p_level = (CH(beg) == _T('=') ? 1 : 2);
+ *p_end = off;
+ return TRUE;
+}
+
+static int
+md_is_table_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_col_count)
+{
+ OFF off = beg;
+ int found_pipe = FALSE;
+ unsigned col_count = 0;
+
+ if(off < ctx->size && CH(off) == _T('|')) {
+ found_pipe = TRUE;
+ off++;
+ while(off < ctx->size && ISWHITESPACE(off))
+ off++;
+ }
+
+ while(1) {
+ int delimited = FALSE;
+
+ /* Cell underline ("-----", ":----", "----:" or ":----:") */
+ if(off < ctx->size && CH(off) == _T(':'))
+ off++;
+ if(off >= ctx->size || CH(off) != _T('-'))
+ return FALSE;
+ while(off < ctx->size && CH(off) == _T('-'))
+ off++;
+ if(off < ctx->size && CH(off) == _T(':'))
+ off++;
+
+ col_count++;
+
+ /* Pipe delimiter (optional at the end of line). */
+ while(off < ctx->size && ISWHITESPACE(off))
+ off++;
+ if(off < ctx->size && CH(off) == _T('|')) {
+ delimited = TRUE;
+ found_pipe = TRUE;
+ off++;
+ while(off < ctx->size && ISWHITESPACE(off))
+ off++;
+ }
+
+ /* Success, if we reach end of line. */
+ if(off >= ctx->size || ISNEWLINE(off))
+ break;
+
+ if(!delimited)
+ return FALSE;
+ }
+
+ if(!found_pipe)
+ return FALSE;
+
+ *p_end = off;
+ *p_col_count = col_count;
+ return TRUE;
+}
+
+static int
+md_is_opening_code_fence(MD_CTX* ctx, OFF beg, OFF* p_end)
+{
+ OFF off = beg;
+
+ while(off < ctx->size && CH(off) == CH(beg))
+ off++;
+
+ /* Fence must have at least three characters. */
+ if(off - beg < 3)
+ return FALSE;
+
+ ctx->code_fence_length = off - beg;
+
+ /* Optionally, space(s) can follow. */
+ while(off < ctx->size && CH(off) == _T(' '))
+ off++;
+
+ /* Optionally, an info string can follow. */
+ while(off < ctx->size && !ISNEWLINE(off)) {
+ /* Backtick-based fence must not contain '`' in the info string. */
+ if(CH(beg) == _T('`') && CH(off) == _T('`'))
+ return FALSE;
+ off++;
+ }
+
+ *p_end = off;
+ return TRUE;
+}
+
+static int
+md_is_closing_code_fence(MD_CTX* ctx, CHAR ch, OFF beg, OFF* p_end)
+{
+ OFF off = beg;
+ int ret = FALSE;
+
+ /* Closing fence must have at least the same length and use same char as
+ * opening one. */
+ while(off < ctx->size && CH(off) == ch)
+ off++;
+ if(off - beg < ctx->code_fence_length)
+ goto out;
+
+ /* Optionally, space(s) can follow */
+ while(off < ctx->size && CH(off) == _T(' '))
+ off++;
+
+ /* But nothing more is allowed on the line. */
+ if(off < ctx->size && !ISNEWLINE(off))
+ goto out;
+
+ ret = TRUE;
+
+out:
+ /* Note we set *p_end even on failure: If we are not closing fence, caller
+ * would eat the line anyway without any parsing. */
+ *p_end = off;
+ return ret;
+}
+
+/* Returns type of the raw HTML block, or FALSE if it is not HTML block.
+ * (Refer to CommonMark specification for details about the types.)
+ */
+static int
+md_is_html_block_start_condition(MD_CTX* ctx, OFF beg)
+{
+ typedef struct TAG_tag TAG;
+ struct TAG_tag {
+ const CHAR* name;
+ unsigned len : 8;
+ };
+
+ /* Type 6 is started by a long list of allowed tags. We use two-level
+ * tree to speed-up the search. */
+#ifdef X
+ #undef X
+#endif
+#define X(name) { _T(name), (sizeof(name)-1) / sizeof(CHAR) }
+#define Xend { NULL, 0 }
+ static const TAG t1[] = { X("pre"), X("script"), X("style"), X("textarea"), Xend };
+
+ static const TAG a6[] = { X("address"), X("article"), X("aside"), Xend };
+ static const TAG b6[] = { X("base"), X("basefont"), X("blockquote"), X("body"), Xend };
+ static const TAG c6[] = { X("caption"), X("center"), X("col"), X("colgroup"), Xend };
+ static const TAG d6[] = { X("dd"), X("details"), X("dialog"), X("dir"),
+ X("div"), X("dl"), X("dt"), Xend };
+ static const TAG f6[] = { X("fieldset"), X("figcaption"), X("figure"), X("footer"),
+ X("form"), X("frame"), X("frameset"), Xend };
+ static const TAG h6[] = { X("h1"), X("head"), X("header"), X("hr"), X("html"), Xend };
+ static const TAG i6[] = { X("iframe"), Xend };
+ static const TAG l6[] = { X("legend"), X("li"), X("link"), Xend };
+ static const TAG m6[] = { X("main"), X("menu"), X("menuitem"), Xend };
+ static const TAG n6[] = { X("nav"), X("noframes"), Xend };
+ static const TAG o6[] = { X("ol"), X("optgroup"), X("option"), Xend };
+ static const TAG p6[] = { X("p"), X("param"), Xend };
+ static const TAG s6[] = { X("section"), X("source"), X("summary"), Xend };
+ static const TAG t6[] = { X("table"), X("tbody"), X("td"), X("tfoot"), X("th"),
+ X("thead"), X("title"), X("tr"), X("track"), Xend };
+ static const TAG u6[] = { X("ul"), Xend };
+ static const TAG xx[] = { Xend };
+#undef X
+
+ static const TAG* map6[26] = {
+ a6, b6, c6, d6, xx, f6, xx, h6, i6, xx, xx, l6, m6,
+ n6, o6, p6, xx, xx, s6, t6, u6, xx, xx, xx, xx, xx
+ };
+ OFF off = beg + 1;
+ int i;
+
+ /* Check for type 1: <script, <pre, or <style */
+ for(i = 0; t1[i].name != NULL; i++) {
+ if(off + t1[i].len <= ctx->size) {
+ if(md_ascii_case_eq(STR(off), t1[i].name, t1[i].len))
+ return 1;
+ }
+ }
+
+ /* Check for type 2: <!-- */
+ if(off + 3 < ctx->size && CH(off) == _T('!') && CH(off+1) == _T('-') && CH(off+2) == _T('-'))
+ return 2;
+
+ /* Check for type 3: <? */
+ if(off < ctx->size && CH(off) == _T('?'))
+ return 3;
+
+ /* Check for type 4 or 5: <! */
+ if(off < ctx->size && CH(off) == _T('!')) {
+ /* Check for type 4: <! followed by uppercase letter. */
+ if(off + 1 < ctx->size && ISASCII(off+1))
+ return 4;
+
+ /* Check for type 5: <![CDATA[ */
+ if(off + 8 < ctx->size) {
+ if(md_ascii_eq(STR(off), _T("![CDATA["), 8))
+ return 5;
+ }
+ }
+
+ /* Check for type 6: Many possible starting tags listed above. */
+ if(off + 1 < ctx->size && (ISALPHA(off) || (CH(off) == _T('/') && ISALPHA(off+1)))) {
+ int slot;
+ const TAG* tags;
+
+ if(CH(off) == _T('/'))
+ off++;
+
+ slot = (ISUPPER(off) ? CH(off) - 'A' : CH(off) - 'a');
+ tags = map6[slot];
+
+ for(i = 0; tags[i].name != NULL; i++) {
+ if(off + tags[i].len <= ctx->size) {
+ if(md_ascii_case_eq(STR(off), tags[i].name, tags[i].len)) {
+ OFF tmp = off + tags[i].len;
+ if(tmp >= ctx->size)
+ return 6;
+ if(ISBLANK(tmp) || ISNEWLINE(tmp) || CH(tmp) == _T('>'))
+ return 6;
+ if(tmp+1 < ctx->size && CH(tmp) == _T('/') && CH(tmp+1) == _T('>'))
+ return 6;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Check for type 7: any COMPLETE other opening or closing tag. */
+ if(off + 1 < ctx->size) {
+ OFF end;
+
+ if(md_is_html_tag(ctx, NULL, 0, beg, ctx->size, &end)) {
+ /* Only optional whitespace and new line may follow. */
+ while(end < ctx->size && ISWHITESPACE(end))
+ end++;
+ if(end >= ctx->size || ISNEWLINE(end))
+ return 7;
+ }
+ }
+
+ return FALSE;
+}
+
+/* Case sensitive check whether there is a substring 'what' between 'beg'
+ * and end of line. */
+static int
+md_line_contains(MD_CTX* ctx, OFF beg, const CHAR* what, SZ what_len, OFF* p_end)
+{
+ OFF i;
+ for(i = beg; i + what_len < ctx->size; i++) {
+ if(ISNEWLINE(i))
+ break;
+ if(memcmp(STR(i), what, what_len * sizeof(CHAR)) == 0) {
+ *p_end = i + what_len;
+ return TRUE;
+ }
+ }
+
+ *p_end = i;
+ return FALSE;
+}
+
+/* Returns type of HTML block end condition or FALSE if not an end condition.
+ *
+ * Note it fills p_end even when it is not end condition as the caller
+ * does not need to analyze contents of a raw HTML block.
+ */
+static int
+md_is_html_block_end_condition(MD_CTX* ctx, OFF beg, OFF* p_end)
+{
+ switch(ctx->html_block_type) {
+ case 1:
+ {
+ OFF off = beg;
+
+ while(off < ctx->size && !ISNEWLINE(off)) {
+ if(CH(off) == _T('<')) {
+ #define FIND_TAG_END(string, length) \
+ if(off + length <= ctx->size && \
+ md_ascii_case_eq(STR(off), _T(string), length)) { \
+ *p_end = off + length; \
+ return TRUE; \
+ }
+ FIND_TAG_END("</script>", 9)
+ FIND_TAG_END("</style>", 8)
+ FIND_TAG_END("</pre>", 6)
+ #undef FIND_TAG_END
+ }
+
+ off++;
+ }
+ *p_end = off;
+ return FALSE;
+ }
+
+ case 2:
+ return (md_line_contains(ctx, beg, _T("-->"), 3, p_end) ? 2 : FALSE);
+
+ case 3:
+ return (md_line_contains(ctx, beg, _T("?>"), 2, p_end) ? 3 : FALSE);
+
+ case 4:
+ return (md_line_contains(ctx, beg, _T(">"), 1, p_end) ? 4 : FALSE);
+
+ case 5:
+ return (md_line_contains(ctx, beg, _T("]]>"), 3, p_end) ? 5 : FALSE);
+
+ case 6: /* Pass through */
+ case 7:
+ *p_end = beg;
+ return (beg >= ctx->size || ISNEWLINE(beg) ? ctx->html_block_type : FALSE);
+
+ default:
+ MD_UNREACHABLE();
+ }
+ return FALSE;
+}
+
+
+static int
+md_is_container_compatible(const MD_CONTAINER* pivot, const MD_CONTAINER* container)
+{
+ /* Block quote has no "items" like lists. */
+ if(container->ch == _T('>'))
+ return FALSE;
+
+ if(container->ch != pivot->ch)
+ return FALSE;
+ if(container->mark_indent > pivot->contents_indent)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int
+md_push_container(MD_CTX* ctx, const MD_CONTAINER* container)
+{
+ if(ctx->n_containers >= ctx->alloc_containers) {
+ MD_CONTAINER* new_containers;
+
+ ctx->alloc_containers = (ctx->alloc_containers > 0
+ ? ctx->alloc_containers + ctx->alloc_containers / 2
+ : 16);
+ new_containers = realloc(ctx->containers, ctx->alloc_containers * sizeof(MD_CONTAINER));
+ if(new_containers == NULL) {
+ MD_LOG("realloc() failed.");
+ return -1;
+ }
+
+ ctx->containers = new_containers;
+ }
+
+ memcpy(&ctx->containers[ctx->n_containers++], container, sizeof(MD_CONTAINER));
+ return 0;
+}
+
+static int
+md_enter_child_containers(MD_CTX* ctx, int n_children)
+{
+ int i;
+ int ret = 0;
+
+ for(i = ctx->n_containers - n_children; i < ctx->n_containers; i++) {
+ MD_CONTAINER* c = &ctx->containers[i];
+ int is_ordered_list = FALSE;
+
+ switch(c->ch) {
+ case _T(')'):
+ case _T('.'):
+ is_ordered_list = TRUE;
+ MD_FALLTHROUGH();
+
+ case _T('-'):
+ case _T('+'):
+ case _T('*'):
+ /* Remember offset in ctx->block_bytes so we can revisit the
+ * block if we detect it is a loose list. */
+ md_end_current_block(ctx);
+ c->block_byte_off = ctx->n_block_bytes;
+
+ MD_CHECK(md_push_container_bytes(ctx,
+ (is_ordered_list ? MD_BLOCK_OL : MD_BLOCK_UL),
+ c->start, c->ch, MD_BLOCK_CONTAINER_OPENER));
+ MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
+ c->task_mark_off,
+ (c->is_task ? CH(c->task_mark_off) : 0),
+ MD_BLOCK_CONTAINER_OPENER));
+ break;
+
+ case _T('>'):
+ MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_QUOTE, 0, 0, MD_BLOCK_CONTAINER_OPENER));
+ break;
+
+ default:
+ MD_UNREACHABLE();
+ break;
+ }
+ }
+
+abort:
+ return ret;
+}
+
+static int
+md_leave_child_containers(MD_CTX* ctx, int n_keep)
+{
+ int ret = 0;
+
+ while(ctx->n_containers > n_keep) {
+ MD_CONTAINER* c = &ctx->containers[ctx->n_containers-1];
+ int is_ordered_list = FALSE;
+
+ switch(c->ch) {
+ case _T(')'):
+ case _T('.'):
+ is_ordered_list = TRUE;
+ MD_FALLTHROUGH();
+
+ case _T('-'):
+ case _T('+'):
+ case _T('*'):
+ MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
+ c->task_mark_off, (c->is_task ? CH(c->task_mark_off) : 0),
+ MD_BLOCK_CONTAINER_CLOSER));
+ MD_CHECK(md_push_container_bytes(ctx,
+ (is_ordered_list ? MD_BLOCK_OL : MD_BLOCK_UL), 0,
+ c->ch, MD_BLOCK_CONTAINER_CLOSER));
+ break;
+
+ case _T('>'):
+ MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_QUOTE, 0,
+ 0, MD_BLOCK_CONTAINER_CLOSER));
+ break;
+
+ default:
+ MD_UNREACHABLE();
+ break;
+ }
+
+ ctx->n_containers--;
+ }
+
+abort:
+ return ret;
+}
+
+static int
+md_is_container_mark(MD_CTX* ctx, unsigned indent, OFF beg, OFF* p_end, MD_CONTAINER* p_container)
+{
+ OFF off = beg;
+ OFF max_end;
+
+ if(off >= ctx->size || indent >= ctx->code_indent_offset)
+ return FALSE;
+
+ /* Check for block quote mark. */
+ if(CH(off) == _T('>')) {
+ off++;
+ p_container->ch = _T('>');
+ p_container->is_loose = FALSE;
+ p_container->is_task = FALSE;
+ p_container->mark_indent = indent;
+ p_container->contents_indent = indent + 1;
+ *p_end = off;
+ return TRUE;
+ }
+
+ /* Check for list item bullet mark. */
+ if(ISANYOF(off, _T("-+*")) && (off+1 >= ctx->size || ISBLANK(off+1) || ISNEWLINE(off+1))) {
+ p_container->ch = CH(off);
+ p_container->is_loose = FALSE;
+ p_container->is_task = FALSE;
+ p_container->mark_indent = indent;
+ p_container->contents_indent = indent + 1;
+ *p_end = off+1;
+ return TRUE;
+ }
+
+ /* Check for ordered list item marks. */
+ max_end = off + 9;
+ if(max_end > ctx->size)
+ max_end = ctx->size;
+ p_container->start = 0;
+ while(off < max_end && ISDIGIT(off)) {
+ p_container->start = p_container->start * 10 + CH(off) - _T('0');
+ off++;
+ }
+ if(off > beg &&
+ off < ctx->size &&
+ (CH(off) == _T('.') || CH(off) == _T(')')) &&
+ (off+1 >= ctx->size || ISBLANK(off+1) || ISNEWLINE(off+1)))
+ {
+ p_container->ch = CH(off);
+ p_container->is_loose = FALSE;
+ p_container->is_task = FALSE;
+ p_container->mark_indent = indent;
+ p_container->contents_indent = indent + off - beg + 1;
+ *p_end = off+1;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static unsigned
+md_line_indentation(MD_CTX* ctx, unsigned total_indent, OFF beg, OFF* p_end)
+{
+ OFF off = beg;
+ unsigned indent = total_indent;
+
+ while(off < ctx->size && ISBLANK(off)) {
+ if(CH(off) == _T('\t'))
+ indent = (indent + 4) & ~3;
+ else
+ indent++;
+ off++;
+ }
+
+ *p_end = off;
+ return indent - total_indent;
+}
+
+static const MD_LINE_ANALYSIS md_dummy_blank_line = { MD_LINE_BLANK, 0, 0, 0, 0 };
+
+/* Analyze type of the line and find some its properties. This serves as a
+ * main input for determining type and boundaries of a block. */
+static int
+md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end,
+ const MD_LINE_ANALYSIS* pivot_line, MD_LINE_ANALYSIS* line)
+{
+ unsigned total_indent = 0;
+ int n_parents = 0;
+ int n_brothers = 0;
+ int n_children = 0;
+ MD_CONTAINER container = { 0 };
+ int prev_line_has_list_loosening_effect = ctx->last_line_has_list_loosening_effect;
+ OFF off = beg;
+ OFF hr_killer = 0;
+ int ret = 0;
+
+ line->indent = md_line_indentation(ctx, total_indent, off, &off);
+ total_indent += line->indent;
+ line->beg = off;
+
+ /* Given the indentation and block quote marks '>', determine how many of
+ * the current containers are our parents. */
+ while(n_parents < ctx->n_containers) {
+ MD_CONTAINER* c = &ctx->containers[n_parents];
+
+ if(c->ch == _T('>') && line->indent < ctx->code_indent_offset &&
+ off < ctx->size && CH(off) == _T('>'))
+ {
+ /* Block quote mark. */
+ off++;
+ total_indent++;
+ line->indent = md_line_indentation(ctx, total_indent, off, &off);
+ total_indent += line->indent;
+
+ /* The optional 1st space after '>' is part of the block quote mark. */
+ if(line->indent > 0)
+ line->indent--;
+
+ line->beg = off;
+
+ } else if(c->ch != _T('>') && line->indent >= c->contents_indent) {
+ /* List. */
+ line->indent -= c->contents_indent;
+ } else {
+ break;
+ }
+
+ n_parents++;
+ }
+
+ if(off >= ctx->size || ISNEWLINE(off)) {
+ /* Blank line does not need any real indentation to be nested inside
+ * a list. */
+ if(n_brothers + n_children == 0) {
+ while(n_parents < ctx->n_containers && ctx->containers[n_parents].ch != _T('>'))
+ n_parents++;
+ }
+ }
+
+ while(TRUE) {
+ /* Check whether we are fenced code continuation. */
+ if(pivot_line->type == MD_LINE_FENCEDCODE) {
+ line->beg = off;
+
+ /* We are another MD_LINE_FENCEDCODE unless we are closing fence
+ * which we transform into MD_LINE_BLANK. */
+ if(line->indent < ctx->code_indent_offset) {
+ if(md_is_closing_code_fence(ctx, CH(pivot_line->beg), off, &off)) {
+ line->type = MD_LINE_BLANK;
+ ctx->last_line_has_list_loosening_effect = FALSE;
+ break;
+ }
+ }
+
+ /* Change indentation accordingly to the initial code fence. */
+ if(n_parents == ctx->n_containers) {
+ if(line->indent > pivot_line->indent)
+ line->indent -= pivot_line->indent;
+ else
+ line->indent = 0;
+
+ line->type = MD_LINE_FENCEDCODE;
+ break;
+ }
+ }
+
+ /* Check whether we are HTML block continuation. */
+ if(pivot_line->type == MD_LINE_HTML && ctx->html_block_type > 0) {
+ if(n_parents < ctx->n_containers) {
+ /* HTML block is implicitly ended if the enclosing container
+ * block ends. */
+ ctx->html_block_type = 0;
+ } else {
+ int html_block_type;
+
+ html_block_type = md_is_html_block_end_condition(ctx, off, &off);
+ if(html_block_type > 0) {
+ MD_ASSERT(html_block_type == ctx->html_block_type);
+
+ /* Make sure this is the last line of the block. */
+ ctx->html_block_type = 0;
+
+ /* Some end conditions serve as blank lines at the same time. */
+ if(html_block_type == 6 || html_block_type == 7) {
+ line->type = MD_LINE_BLANK;
+ line->indent = 0;
+ break;
+ }
+ }
+
+ line->type = MD_LINE_HTML;
+ n_parents = ctx->n_containers;
+ break;
+ }
+ }
+
+ /* Check for blank line. */
+ if(off >= ctx->size || ISNEWLINE(off)) {
+ if(pivot_line->type == MD_LINE_INDENTEDCODE && n_parents == ctx->n_containers) {
+ line->type = MD_LINE_INDENTEDCODE;
+ if(line->indent > ctx->code_indent_offset)
+ line->indent -= ctx->code_indent_offset;
+ else
+ line->indent = 0;
+ ctx->last_line_has_list_loosening_effect = FALSE;
+ } else {
+ line->type = MD_LINE_BLANK;
+ ctx->last_line_has_list_loosening_effect = (n_parents > 0 &&
+ n_brothers + n_children == 0 &&
+ ctx->containers[n_parents-1].ch != _T('>'));
+
+ #if 1
+ /* See https://github.com/mity/md4c/issues/6
+ *
+ * This ugly checking tests we are in (yet empty) list item but
+ * not its very first line (i.e. not the line with the list
+ * item mark).
+ *
+ * If we are such a blank line, then any following non-blank
+ * line which would be part of the list item actually has to
+ * end the list because according to the specification, "a list
+ * item can begin with at most one blank line."
+ */
+ if(n_parents > 0 && ctx->containers[n_parents-1].ch != _T('>') &&
+ n_brothers + n_children == 0 && ctx->current_block == NULL &&
+ ctx->n_block_bytes > (int) sizeof(MD_BLOCK))
+ {
+ MD_BLOCK* top_block = (MD_BLOCK*) ((char*)ctx->block_bytes + ctx->n_block_bytes - sizeof(MD_BLOCK));
+ if(top_block->type == MD_BLOCK_LI)
+ ctx->last_list_item_starts_with_two_blank_lines = TRUE;
+ }
+ #endif
+ }
+ break;
+ } else {
+ #if 1
+ /* This is the 2nd half of the hack. If the flag is set (i.e. there
+ * was a 2nd blank line at the beginning of the list item) and if
+ * we would otherwise still belong to the list item, we enforce
+ * the end of the list. */
+ ctx->last_line_has_list_loosening_effect = FALSE;
+ if(ctx->last_list_item_starts_with_two_blank_lines) {
+ if(n_parents > 0 && ctx->containers[n_parents-1].ch != _T('>') &&
+ n_brothers + n_children == 0 && ctx->current_block == NULL &&
+ ctx->n_block_bytes > (int) sizeof(MD_BLOCK))
+ {
+ MD_BLOCK* top_block = (MD_BLOCK*) ((char*)ctx->block_bytes + ctx->n_block_bytes - sizeof(MD_BLOCK));
+ if(top_block->type == MD_BLOCK_LI)
+ n_parents--;
+ }
+
+ ctx->last_list_item_starts_with_two_blank_lines = FALSE;
+ }
+ #endif
+ }
+
+ /* Check whether we are Setext underline. */
+ if(line->indent < ctx->code_indent_offset && pivot_line->type == MD_LINE_TEXT
+ && off < ctx->size && ISANYOF2(off, _T('='), _T('-'))
+ && (n_parents == ctx->n_containers))
+ {
+ unsigned level;
+
+ if(md_is_setext_underline(ctx, off, &off, &level)) {
+ line->type = MD_LINE_SETEXTUNDERLINE;
+ line->data = level;
+ break;
+ }
+ }
+
+ /* Check for thematic break line. */
+ if(line->indent < ctx->code_indent_offset
+ && off < ctx->size && off >= hr_killer
+ && ISANYOF(off, _T("-_*")))
+ {
+ if(md_is_hr_line(ctx, off, &off, &hr_killer)) {
+ line->type = MD_LINE_HR;
+ break;
+ }
+ }
+
+ /* Check for "brother" container. I.e. whether we are another list item
+ * in already started list. */
+ if(n_parents < ctx->n_containers && n_brothers + n_children == 0) {
+ OFF tmp;
+
+ if(md_is_container_mark(ctx, line->indent, off, &tmp, &container) &&
+ md_is_container_compatible(&ctx->containers[n_parents], &container))
+ {
+ pivot_line = &md_dummy_blank_line;
+
+ off = tmp;
+
+ total_indent += container.contents_indent - container.mark_indent;
+ line->indent = md_line_indentation(ctx, total_indent, off, &off);
+ total_indent += line->indent;
+ line->beg = off;
+
+ /* Some of the following whitespace actually still belongs to the mark. */
+ if(off >= ctx->size || ISNEWLINE(off)) {
+ container.contents_indent++;
+ } else if(line->indent <= ctx->code_indent_offset) {
+ container.contents_indent += line->indent;
+ line->indent = 0;
+ } else {
+ container.contents_indent += 1;
+ line->indent--;
+ }
+
+ ctx->containers[n_parents].mark_indent = container.mark_indent;
+ ctx->containers[n_parents].contents_indent = container.contents_indent;
+
+ n_brothers++;
+ continue;
+ }
+ }
+
+ /* Check for indented code.
+ * Note indented code block cannot interrupt a paragraph. */
+ if(line->indent >= ctx->code_indent_offset &&
+ (pivot_line->type == MD_LINE_BLANK || pivot_line->type == MD_LINE_INDENTEDCODE))
+ {
+ line->type = MD_LINE_INDENTEDCODE;
+ MD_ASSERT(line->indent >= ctx->code_indent_offset);
+ line->indent -= ctx->code_indent_offset;
+ line->data = 0;
+ break;
+ }
+
+ /* Check for start of a new container block. */
+ if(line->indent < ctx->code_indent_offset &&
+ md_is_container_mark(ctx, line->indent, off, &off, &container))
+ {
+ if(pivot_line->type == MD_LINE_TEXT && n_parents == ctx->n_containers &&
+ (off >= ctx->size || ISNEWLINE(off)) && container.ch != _T('>'))
+ {
+ /* Noop. List mark followed by a blank line cannot interrupt a paragraph. */
+ } else if(pivot_line->type == MD_LINE_TEXT && n_parents == ctx->n_containers &&
+ ISANYOF2_(container.ch, _T('.'), _T(')')) && container.start != 1)
+ {
+ /* Noop. Ordered list cannot interrupt a paragraph unless the start index is 1. */
+ } else {
+ total_indent += container.contents_indent - container.mark_indent;
+ line->indent = md_line_indentation(ctx, total_indent, off, &off);
+ total_indent += line->indent;
+
+ line->beg = off;
+ line->data = container.ch;
+
+ /* Some of the following whitespace actually still belongs to the mark. */
+ if(off >= ctx->size || ISNEWLINE(off)) {
+ container.contents_indent++;
+ } else if(line->indent <= ctx->code_indent_offset) {
+ container.contents_indent += line->indent;
+ line->indent = 0;
+ } else {
+ container.contents_indent += 1;
+ line->indent--;
+ }
+
+ if(n_brothers + n_children == 0)
+ pivot_line = &md_dummy_blank_line;
+
+ if(n_children == 0)
+ MD_CHECK(md_leave_child_containers(ctx, n_parents + n_brothers));
+
+ n_children++;
+ MD_CHECK(md_push_container(ctx, &container));
+ continue;
+ }
+ }
+
+ /* Check whether we are table continuation. */
+ if(pivot_line->type == MD_LINE_TABLE && n_parents == ctx->n_containers) {
+ line->type = MD_LINE_TABLE;
+ break;
+ }
+
+ /* Check for ATX header. */
+ if(line->indent < ctx->code_indent_offset &&
+ off < ctx->size && CH(off) == _T('#'))
+ {
+ unsigned level;
+
+ if(md_is_atxheader_line(ctx, off, &line->beg, &off, &level)) {
+ line->type = MD_LINE_ATXHEADER;
+ line->data = level;
+ break;
+ }
+ }
+
+ /* Check whether we are starting code fence. */
+ if(off < ctx->size && ISANYOF2(off, _T('`'), _T('~'))) {
+ if(md_is_opening_code_fence(ctx, off, &off)) {
+ line->type = MD_LINE_FENCEDCODE;
+ line->data = 1;
+ break;
+ }
+ }
+
+ /* Check for start of raw HTML block. */
+ if(off < ctx->size && CH(off) == _T('<')
+ && !(ctx->parser.flags & MD_FLAG_NOHTMLBLOCKS))
+ {
+ ctx->html_block_type = md_is_html_block_start_condition(ctx, off);
+
+ /* HTML block type 7 cannot interrupt paragraph. */
+ if(ctx->html_block_type == 7 && pivot_line->type == MD_LINE_TEXT)
+ ctx->html_block_type = 0;
+
+ if(ctx->html_block_type > 0) {
+ /* The line itself also may immediately close the block. */
+ if(md_is_html_block_end_condition(ctx, off, &off) == ctx->html_block_type) {
+ /* Make sure this is the last line of the block. */
+ ctx->html_block_type = 0;
+ }
+
+ line->type = MD_LINE_HTML;
+ break;
+ }
+ }
+
+ /* Check for table underline. */
+ if((ctx->parser.flags & MD_FLAG_TABLES) && pivot_line->type == MD_LINE_TEXT
+ && off < ctx->size && ISANYOF3(off, _T('|'), _T('-'), _T(':'))
+ && n_parents == ctx->n_containers)
+ {
+ unsigned col_count;
+
+ if(ctx->current_block != NULL && ctx->current_block->n_lines == 1 &&
+ md_is_table_underline(ctx, off, &off, &col_count))
+ {
+ line->data = col_count;
+ line->type = MD_LINE_TABLEUNDERLINE;
+ break;
+ }
+ }
+
+ /* By default, we are normal text line. */
+ line->type = MD_LINE_TEXT;
+ if(pivot_line->type == MD_LINE_TEXT && n_brothers + n_children == 0) {
+ /* Lazy continuation. */
+ n_parents = ctx->n_containers;
+ }
+
+ /* Check for task mark. */
+ if((ctx->parser.flags & MD_FLAG_TASKLISTS) && n_brothers + n_children > 0 &&
+ ISANYOF_(ctx->containers[ctx->n_containers-1].ch, _T("-+*.)")))
+ {
+ OFF tmp = off;
+
+ while(tmp < ctx->size && tmp < off + 3 && ISBLANK(tmp))
+ tmp++;
+ if(tmp + 2 < ctx->size && CH(tmp) == _T('[') &&
+ ISANYOF(tmp+1, _T("xX ")) && CH(tmp+2) == _T(']') &&
+ (tmp + 3 == ctx->size || ISBLANK(tmp+3) || ISNEWLINE(tmp+3)))
+ {
+ MD_CONTAINER* task_container = (n_children > 0 ? &ctx->containers[ctx->n_containers-1] : &container);
+ task_container->is_task = TRUE;
+ task_container->task_mark_off = tmp + 1;
+ off = tmp + 3;
+ while(off < ctx->size && ISWHITESPACE(off))
+ off++;
+ if (off == ctx->size) break;
+ line->beg = off;
+ }
+ }
+
+ break;
+ }
+
+ /* Scan for end of the line.
+ *
+ * Note this is quite a bottleneck of the parsing as we here iterate almost
+ * over compete document.
+ */
+#if defined __linux__ && !defined MD4C_USE_UTF16
+ /* Recent glibc versions have superbly optimized strcspn(), even using
+ * vectorization if available. */
+ if(ctx->doc_ends_with_newline && off < ctx->size) {
+ while(TRUE) {
+ off += (OFF) strcspn(STR(off), "\r\n");
+
+ /* strcspn() can stop on zero terminator; but that can appear
+ * anywhere in the Markfown input... */
+ if(CH(off) == _T('\0'))
+ off++;
+ else
+ break;
+ }
+ } else
+#endif
+ {
+ /* Optimization: Use some loop unrolling. */
+ while(off + 3 < ctx->size && !ISNEWLINE(off+0) && !ISNEWLINE(off+1)
+ && !ISNEWLINE(off+2) && !ISNEWLINE(off+3))
+ off += 4;
+ while(off < ctx->size && !ISNEWLINE(off))
+ off++;
+ }
+
+ /* Set end of the line. */
+ line->end = off;
+
+ /* But for ATX header, we should exclude the optional trailing mark. */
+ if(line->type == MD_LINE_ATXHEADER) {
+ OFF tmp = line->end;
+ while(tmp > line->beg && CH(tmp-1) == _T(' '))
+ tmp--;
+ while(tmp > line->beg && CH(tmp-1) == _T('#'))
+ tmp--;
+ if(tmp == line->beg || CH(tmp-1) == _T(' ') || (ctx->parser.flags & MD_FLAG_PERMISSIVEATXHEADERS))
+ line->end = tmp;
+ }
+
+ /* Trim trailing spaces. */
+ if(line->type != MD_LINE_INDENTEDCODE && line->type != MD_LINE_FENCEDCODE) {
+ while(line->end > line->beg && CH(line->end-1) == _T(' '))
+ line->end--;
+ }
+
+ /* Eat also the new line. */
+ if(off < ctx->size && CH(off) == _T('\r'))
+ off++;
+ if(off < ctx->size && CH(off) == _T('\n'))
+ off++;
+
+ *p_end = off;
+
+ /* If we belong to a list after seeing a blank line, the list is loose. */
+ if(prev_line_has_list_loosening_effect && line->type != MD_LINE_BLANK && n_parents + n_brothers > 0) {
+ MD_CONTAINER* c = &ctx->containers[n_parents + n_brothers - 1];
+ if(c->ch != _T('>')) {
+ MD_BLOCK* block = (MD_BLOCK*) (((char*)ctx->block_bytes) + c->block_byte_off);
+ block->flags |= MD_BLOCK_LOOSE_LIST;
+ }
+ }
+
+ /* Leave any containers we are not part of anymore. */
+ if(n_children == 0 && n_parents + n_brothers < ctx->n_containers)
+ MD_CHECK(md_leave_child_containers(ctx, n_parents + n_brothers));
+
+ /* Enter any container we found a mark for. */
+ if(n_brothers > 0) {
+ MD_ASSERT(n_brothers == 1);
+ MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
+ ctx->containers[n_parents].task_mark_off,
+ (ctx->containers[n_parents].is_task ? CH(ctx->containers[n_parents].task_mark_off) : 0),
+ MD_BLOCK_CONTAINER_CLOSER));
+ MD_CHECK(md_push_container_bytes(ctx, MD_BLOCK_LI,
+ container.task_mark_off,
+ (container.is_task ? CH(container.task_mark_off) : 0),
+ MD_BLOCK_CONTAINER_OPENER));
+ ctx->containers[n_parents].is_task = container.is_task;
+ ctx->containers[n_parents].task_mark_off = container.task_mark_off;
+ }
+
+ if(n_children > 0)
+ MD_CHECK(md_enter_child_containers(ctx, n_children));
+
+abort:
+ return ret;
+}
+
+static int
+md_process_line(MD_CTX* ctx, const MD_LINE_ANALYSIS** p_pivot_line, MD_LINE_ANALYSIS* line)
+{
+ const MD_LINE_ANALYSIS* pivot_line = *p_pivot_line;
+ int ret = 0;
+
+ /* Blank line ends current leaf block. */
+ if(line->type == MD_LINE_BLANK) {
+ MD_CHECK(md_end_current_block(ctx));
+ *p_pivot_line = &md_dummy_blank_line;
+ return 0;
+ }
+
+ /* Some line types form block on their own. */
+ if(line->type == MD_LINE_HR || line->type == MD_LINE_ATXHEADER) {
+ MD_CHECK(md_end_current_block(ctx));
+
+ /* Add our single-line block. */
+ MD_CHECK(md_start_new_block(ctx, line));
+ MD_CHECK(md_add_line_into_current_block(ctx, line));
+ MD_CHECK(md_end_current_block(ctx));
+ *p_pivot_line = &md_dummy_blank_line;
+ return 0;
+ }
+
+ /* MD_LINE_SETEXTUNDERLINE changes meaning of the current block and ends it. */
+ if(line->type == MD_LINE_SETEXTUNDERLINE) {
+ MD_ASSERT(ctx->current_block != NULL);
+ ctx->current_block->type = MD_BLOCK_H;
+ ctx->current_block->data = line->data;
+ ctx->current_block->flags |= MD_BLOCK_SETEXT_HEADER;
+ MD_CHECK(md_add_line_into_current_block(ctx, line));
+ MD_CHECK(md_end_current_block(ctx));
+ if(ctx->current_block == NULL) {
+ *p_pivot_line = &md_dummy_blank_line;
+ } else {
+ /* This happens if we have consumed all the body as link ref. defs.
+ * and downgraded the underline into start of a new paragraph block. */
+ line->type = MD_LINE_TEXT;
+ *p_pivot_line = line;
+ }
+ return 0;
+ }
+
+ /* MD_LINE_TABLEUNDERLINE changes meaning of the current block. */
+ if(line->type == MD_LINE_TABLEUNDERLINE) {
+ MD_ASSERT(ctx->current_block != NULL);
+ MD_ASSERT(ctx->current_block->n_lines == 1);
+ ctx->current_block->type = MD_BLOCK_TABLE;
+ ctx->current_block->data = line->data;
+ MD_ASSERT(pivot_line != &md_dummy_blank_line);
+ ((MD_LINE_ANALYSIS*)pivot_line)->type = MD_LINE_TABLE;
+ MD_CHECK(md_add_line_into_current_block(ctx, line));
+ return 0;
+ }
+
+ /* The current block also ends if the line has different type. */
+ if(line->type != pivot_line->type)
+ MD_CHECK(md_end_current_block(ctx));
+
+ /* The current line may start a new block. */
+ if(ctx->current_block == NULL) {
+ MD_CHECK(md_start_new_block(ctx, line));
+ *p_pivot_line = line;
+ }
+
+ /* In all other cases the line is just a continuation of the current block. */
+ MD_CHECK(md_add_line_into_current_block(ctx, line));
+
+abort:
+ return ret;
+}
+
+static int
+md_process_doc(MD_CTX *ctx)
+{
+ const MD_LINE_ANALYSIS* pivot_line = &md_dummy_blank_line;
+ MD_LINE_ANALYSIS line_buf[2];
+ MD_LINE_ANALYSIS* line = &line_buf[0];
+ OFF off = 0;
+ int ret = 0;
+
+ MD_ENTER_BLOCK(MD_BLOCK_DOC, NULL);
+
+ while(off < ctx->size) {
+ if(line == pivot_line)
+ line = (line == &line_buf[0] ? &line_buf[1] : &line_buf[0]);
+
+ MD_CHECK(md_analyze_line(ctx, off, &off, pivot_line, line));
+ MD_CHECK(md_process_line(ctx, &pivot_line, line));
+ }
+
+ md_end_current_block(ctx);
+
+ MD_CHECK(md_build_ref_def_hashtable(ctx));
+
+ /* Process all blocks. */
+ MD_CHECK(md_leave_child_containers(ctx, 0));
+ MD_CHECK(md_process_all_blocks(ctx));
+
+ MD_LEAVE_BLOCK(MD_BLOCK_DOC, NULL);
+
+abort:
+
+#if 0
+ /* Output some memory consumption statistics. */
+ {
+ char buffer[256];
+ sprintf(buffer, "Alloced %u bytes for block buffer.",
+ (unsigned)(ctx->alloc_block_bytes));
+ MD_LOG(buffer);
+
+ sprintf(buffer, "Alloced %u bytes for containers buffer.",
+ (unsigned)(ctx->alloc_containers * sizeof(MD_CONTAINER)));
+ MD_LOG(buffer);
+
+ sprintf(buffer, "Alloced %u bytes for marks buffer.",
+ (unsigned)(ctx->alloc_marks * sizeof(MD_MARK)));
+ MD_LOG(buffer);
+
+ sprintf(buffer, "Alloced %u bytes for aux. buffer.",
+ (unsigned)(ctx->alloc_buffer * sizeof(MD_CHAR)));
+ MD_LOG(buffer);
+ }
+#endif
+
+ return ret;
+}
+
+
+/********************
+ *** Public API ***
+ ********************/
+
+int
+md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userdata)
+{
+ MD_CTX ctx;
+ int i;
+ int ret;
+
+ if(parser->abi_version != 0) {
+ if(parser->debug_log != NULL)
+ parser->debug_log("Unsupported abi_version.", userdata);
+ return -1;
+ }
+
+ /* Setup context structure. */
+ memset(&ctx, 0, sizeof(MD_CTX));
+ ctx.text = text;
+ ctx.size = size;
+ memcpy(&ctx.parser, parser, sizeof(MD_PARSER));
+ ctx.userdata = userdata;
+ ctx.code_indent_offset = (ctx.parser.flags & MD_FLAG_NOINDENTEDCODEBLOCKS) ? (OFF)(-1) : 4;
+ md_build_mark_char_map(&ctx);
+ ctx.doc_ends_with_newline = (size > 0 && ISNEWLINE_(text[size-1]));
+
+ /* Reset all unresolved opener mark chains. */
+ for(i = 0; i < (int) SIZEOF_ARRAY(ctx.mark_chains); i++) {
+ ctx.mark_chains[i].head = -1;
+ ctx.mark_chains[i].tail = -1;
+ }
+ ctx.unresolved_link_head = -1;
+ ctx.unresolved_link_tail = -1;
+
+ /* All the work. */
+ ret = md_process_doc(&ctx);
+
+ /* Clean-up. */
+ md_free_ref_defs(&ctx);
+ md_free_ref_def_hashtable(&ctx);
+ free(ctx.buffer);
+ free(ctx.marks);
+ free(ctx.block_bytes);
+ free(ctx.containers);
+
+ return ret;
+}
diff --git a/src/third-party/md4c/md4c.h b/src/third-party/md4c/md4c.h
new file mode 100644
index 0000000..95f78f9
--- /dev/null
+++ b/src/third-party/md4c/md4c.h
@@ -0,0 +1,405 @@
+/*
+ * MD4C: Markdown parser for C
+ * (http://github.com/mity/md4c)
+ *
+ * Copyright (c) 2016-2020 Martin Mitas
+ *
+ * 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 MD4C_H
+#define MD4C_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#if defined MD4C_USE_UTF16
+ /* Magic to support UTF-16. Note that in order to use it, you have to define
+ * the macro MD4C_USE_UTF16 both when building MD4C as well as when
+ * including this header in your code. */
+ #ifdef _WIN32
+ #include <windows.h>
+ typedef WCHAR MD_CHAR;
+ #else
+ #error MD4C_USE_UTF16 is only supported on Windows.
+ #endif
+#else
+ typedef char MD_CHAR;
+#endif
+
+typedef unsigned MD_SIZE;
+typedef unsigned MD_OFFSET;
+
+
+/* Block represents a part of document hierarchy structure like a paragraph
+ * or list item.
+ */
+typedef enum MD_BLOCKTYPE {
+ /* <body>...</body> */
+ MD_BLOCK_DOC = 0,
+
+ /* <blockquote>...</blockquote> */
+ MD_BLOCK_QUOTE,
+
+ /* <ul>...</ul>
+ * Detail: Structure MD_BLOCK_UL_DETAIL. */
+ MD_BLOCK_UL,
+
+ /* <ol>...</ol>
+ * Detail: Structure MD_BLOCK_OL_DETAIL. */
+ MD_BLOCK_OL,
+
+ /* <li>...</li>
+ * Detail: Structure MD_BLOCK_LI_DETAIL. */
+ MD_BLOCK_LI,
+
+ /* <hr> */
+ MD_BLOCK_HR,
+
+ /* <h1>...</h1> (for levels up to 6)
+ * Detail: Structure MD_BLOCK_H_DETAIL. */
+ MD_BLOCK_H,
+
+ /* <pre><code>...</code></pre>
+ * Note the text lines within code blocks are terminated with '\n'
+ * instead of explicit MD_TEXT_BR. */
+ MD_BLOCK_CODE,
+
+ /* Raw HTML block. This itself does not correspond to any particular HTML
+ * tag. The contents of it _is_ raw HTML source intended to be put
+ * in verbatim form to the HTML output. */
+ MD_BLOCK_HTML,
+
+ /* <p>...</p> */
+ MD_BLOCK_P,
+
+ /* <table>...</table> and its contents.
+ * Detail: Structure MD_BLOCK_TABLE_DETAIL (for MD_BLOCK_TABLE),
+ * structure MD_BLOCK_TD_DETAIL (for MD_BLOCK_TH and MD_BLOCK_TD)
+ * Note all of these are used only if extension MD_FLAG_TABLES is enabled. */
+ MD_BLOCK_TABLE,
+ MD_BLOCK_THEAD,
+ MD_BLOCK_TBODY,
+ MD_BLOCK_TR,
+ MD_BLOCK_TH,
+ MD_BLOCK_TD
+} MD_BLOCKTYPE;
+
+/* Span represents an in-line piece of a document which should be rendered with
+ * the same font, color and other attributes. A sequence of spans forms a block
+ * like paragraph or list item. */
+typedef enum MD_SPANTYPE {
+ /* <em>...</em> */
+ MD_SPAN_EM,
+
+ /* <strong>...</strong> */
+ MD_SPAN_STRONG,
+
+ /* <a href="xxx">...</a>
+ * Detail: Structure MD_SPAN_A_DETAIL. */
+ MD_SPAN_A,
+
+ /* <img src="xxx">...</a>
+ * Detail: Structure MD_SPAN_IMG_DETAIL.
+ * Note: Image text can contain nested spans and even nested images.
+ * If rendered into ALT attribute of HTML <IMG> tag, it's responsibility
+ * of the parser to deal with it.
+ */
+ MD_SPAN_IMG,
+
+ /* <code>...</code> */
+ MD_SPAN_CODE,
+
+ /* <del>...</del>
+ * Note: Recognized only when MD_FLAG_STRIKETHROUGH is enabled.
+ */
+ MD_SPAN_DEL,
+
+ /* For recognizing inline ($) and display ($$) equations
+ * Note: Recognized only when MD_FLAG_LATEXMATHSPANS is enabled.
+ */
+ MD_SPAN_LATEXMATH,
+ MD_SPAN_LATEXMATH_DISPLAY,
+
+ /* Wiki links
+ * Note: Recognized only when MD_FLAG_WIKILINKS is enabled.
+ */
+ MD_SPAN_WIKILINK,
+
+ /* <u>...</u>
+ * Note: Recognized only when MD_FLAG_UNDERLINE is enabled. */
+ MD_SPAN_U
+} MD_SPANTYPE;
+
+/* Text is the actual textual contents of span. */
+typedef enum MD_TEXTTYPE {
+ /* Normal text. */
+ MD_TEXT_NORMAL = 0,
+
+ /* NULL character. CommonMark requires replacing NULL character with
+ * the replacement char U+FFFD, so this allows caller to do that easily. */
+ MD_TEXT_NULLCHAR,
+
+ /* Line breaks.
+ * Note these are not sent from blocks with verbatim output (MD_BLOCK_CODE
+ * or MD_BLOCK_HTML). In such cases, '\n' is part of the text itself. */
+ MD_TEXT_BR, /* <br> (hard break) */
+ MD_TEXT_SOFTBR, /* '\n' in source text where it is not semantically meaningful (soft break) */
+
+ /* Entity.
+ * (a) Named entity, e.g. &nbsp;
+ * (Note MD4C does not have a list of known entities.
+ * Anything matching the regexp /&[A-Za-z][A-Za-z0-9]{1,47};/ is
+ * treated as a named entity.)
+ * (b) Numerical entity, e.g. &#1234;
+ * (c) Hexadecimal entity, e.g. &#x12AB;
+ *
+ * As MD4C is mostly encoding agnostic, application gets the verbatim
+ * entity text into the MD_PARSER::text_callback(). */
+ MD_TEXT_ENTITY,
+
+ /* Text in a code block (inside MD_BLOCK_CODE) or inlined code (`code`).
+ * If it is inside MD_BLOCK_CODE, it includes spaces for indentation and
+ * '\n' for new lines. MD_TEXT_BR and MD_TEXT_SOFTBR are not sent for this
+ * kind of text. */
+ MD_TEXT_CODE,
+
+ /* Text is a raw HTML. If it is contents of a raw HTML block (i.e. not
+ * an inline raw HTML), then MD_TEXT_BR and MD_TEXT_SOFTBR are not used.
+ * The text contains verbatim '\n' for the new lines. */
+ MD_TEXT_HTML,
+
+ /* Text is inside an equation. This is processed the same way as inlined code
+ * spans (`code`). */
+ MD_TEXT_LATEXMATH
+} MD_TEXTTYPE;
+
+
+/* Alignment enumeration. */
+typedef enum MD_ALIGN {
+ MD_ALIGN_DEFAULT = 0, /* When unspecified. */
+ MD_ALIGN_LEFT,
+ MD_ALIGN_CENTER,
+ MD_ALIGN_RIGHT
+} MD_ALIGN;
+
+
+/* String attribute.
+ *
+ * This wraps strings which are outside of a normal text flow and which are
+ * propagated within various detailed structures, but which still may contain
+ * string portions of different types like e.g. entities.
+ *
+ * So, for example, lets consider this image:
+ *
+ * ![image alt text](http://example.org/image.png 'foo &quot; bar')
+ *
+ * The image alt text is propagated as a normal text via the MD_PARSER::text()
+ * callback. However, the image title ('foo &quot; bar') is propagated as
+ * MD_ATTRIBUTE in MD_SPAN_IMG_DETAIL::title.
+ *
+ * Then the attribute MD_SPAN_IMG_DETAIL::title shall provide the following:
+ * -- [0]: "foo " (substr_types[0] == MD_TEXT_NORMAL; substr_offsets[0] == 0)
+ * -- [1]: "&quot;" (substr_types[1] == MD_TEXT_ENTITY; substr_offsets[1] == 4)
+ * -- [2]: " bar" (substr_types[2] == MD_TEXT_NORMAL; substr_offsets[2] == 10)
+ * -- [3]: (n/a) (n/a ; substr_offsets[3] == 14)
+ *
+ * Note that these invariants are always guaranteed:
+ * -- substr_offsets[0] == 0
+ * -- substr_offsets[LAST+1] == size
+ * -- Currently, only MD_TEXT_NORMAL, MD_TEXT_ENTITY, MD_TEXT_NULLCHAR
+ * substrings can appear. This could change only of the specification
+ * changes.
+ */
+typedef struct MD_ATTRIBUTE {
+ const MD_CHAR* text;
+ MD_SIZE size;
+ const MD_TEXTTYPE* substr_types;
+ const MD_OFFSET* substr_offsets;
+} MD_ATTRIBUTE;
+
+
+/* Detailed info for MD_BLOCK_UL. */
+typedef struct MD_BLOCK_UL_DETAIL {
+ int is_tight; /* Non-zero if tight list, zero if loose. */
+ MD_CHAR mark; /* Item bullet character in MarkDown source of the list, e.g. '-', '+', '*'. */
+} MD_BLOCK_UL_DETAIL;
+
+/* Detailed info for MD_BLOCK_OL. */
+typedef struct MD_BLOCK_OL_DETAIL {
+ unsigned start; /* Start index of the ordered list. */
+ int is_tight; /* Non-zero if tight list, zero if loose. */
+ MD_CHAR mark_delimiter; /* Character delimiting the item marks in MarkDown source, e.g. '.' or ')' */
+} MD_BLOCK_OL_DETAIL;
+
+/* Detailed info for MD_BLOCK_LI. */
+typedef struct MD_BLOCK_LI_DETAIL {
+ int is_task; /* Can be non-zero only with MD_FLAG_TASKLISTS */
+ MD_CHAR task_mark; /* If is_task, then one of 'x', 'X' or ' '. Undefined otherwise. */
+ MD_OFFSET task_mark_offset; /* If is_task, then offset in the input of the char between '[' and ']'. */
+} MD_BLOCK_LI_DETAIL;
+
+/* Detailed info for MD_BLOCK_H. */
+typedef struct MD_BLOCK_H_DETAIL {
+ unsigned level; /* Header level (1 - 6) */
+} MD_BLOCK_H_DETAIL;
+
+/* Detailed info for MD_BLOCK_CODE. */
+typedef struct MD_BLOCK_CODE_DETAIL {
+ MD_ATTRIBUTE info;
+ MD_ATTRIBUTE lang;
+ MD_CHAR fence_char; /* The character used for fenced code block; or zero for indented code block. */
+} MD_BLOCK_CODE_DETAIL;
+
+/* Detailed info for MD_BLOCK_TABLE. */
+typedef struct MD_BLOCK_TABLE_DETAIL {
+ unsigned col_count; /* Count of columns in the table. */
+ unsigned head_row_count; /* Count of rows in the table header (currently always 1) */
+ unsigned body_row_count; /* Count of rows in the table body */
+} MD_BLOCK_TABLE_DETAIL;
+
+/* Detailed info for MD_BLOCK_TH and MD_BLOCK_TD. */
+typedef struct MD_BLOCK_TD_DETAIL {
+ MD_ALIGN align;
+} MD_BLOCK_TD_DETAIL;
+
+/* Detailed info for MD_SPAN_A. */
+typedef struct MD_SPAN_A_DETAIL {
+ MD_ATTRIBUTE href;
+ MD_ATTRIBUTE title;
+} MD_SPAN_A_DETAIL;
+
+/* Detailed info for MD_SPAN_IMG. */
+typedef struct MD_SPAN_IMG_DETAIL {
+ MD_ATTRIBUTE src;
+ MD_ATTRIBUTE title;
+} MD_SPAN_IMG_DETAIL;
+
+/* Detailed info for MD_SPAN_WIKILINK. */
+typedef struct MD_SPAN_WIKILINK {
+ MD_ATTRIBUTE target;
+} MD_SPAN_WIKILINK_DETAIL;
+
+/* Flags specifying extensions/deviations from CommonMark specification.
+ *
+ * By default (when MD_PARSER::flags == 0), we follow CommonMark specification.
+ * The following flags may allow some extensions or deviations from it.
+ */
+#define MD_FLAG_COLLAPSEWHITESPACE 0x0001 /* In MD_TEXT_NORMAL, collapse non-trivial whitespace into single ' ' */
+#define MD_FLAG_PERMISSIVEATXHEADERS 0x0002 /* Do not require space in ATX headers ( ###header ) */
+#define MD_FLAG_PERMISSIVEURLAUTOLINKS 0x0004 /* Recognize URLs as autolinks even without '<', '>' */
+#define MD_FLAG_PERMISSIVEEMAILAUTOLINKS 0x0008 /* Recognize e-mails as autolinks even without '<', '>' and 'mailto:' */
+#define MD_FLAG_NOINDENTEDCODEBLOCKS 0x0010 /* Disable indented code blocks. (Only fenced code works.) */
+#define MD_FLAG_NOHTMLBLOCKS 0x0020 /* Disable raw HTML blocks. */
+#define MD_FLAG_NOHTMLSPANS 0x0040 /* Disable raw HTML (inline). */
+#define MD_FLAG_TABLES 0x0100 /* Enable tables extension. */
+#define MD_FLAG_STRIKETHROUGH 0x0200 /* Enable strikethrough extension. */
+#define MD_FLAG_PERMISSIVEWWWAUTOLINKS 0x0400 /* Enable WWW autolinks (even without any scheme prefix, if they begin with 'www.') */
+#define MD_FLAG_TASKLISTS 0x0800 /* Enable task list extension. */
+#define MD_FLAG_LATEXMATHSPANS 0x1000 /* Enable $ and $$ containing LaTeX equations. */
+#define MD_FLAG_WIKILINKS 0x2000 /* Enable wiki links extension. */
+#define MD_FLAG_UNDERLINE 0x4000 /* Enable underline extension (and disables '_' for normal emphasis). */
+
+#define MD_FLAG_PERMISSIVEAUTOLINKS (MD_FLAG_PERMISSIVEEMAILAUTOLINKS | MD_FLAG_PERMISSIVEURLAUTOLINKS | MD_FLAG_PERMISSIVEWWWAUTOLINKS)
+#define MD_FLAG_NOHTML (MD_FLAG_NOHTMLBLOCKS | MD_FLAG_NOHTMLSPANS)
+
+/* Convenient sets of flags corresponding to well-known Markdown dialects.
+ *
+ * Note we may only support subset of features of the referred dialect.
+ * The constant just enables those extensions which bring us as close as
+ * possible given what features we implement.
+ *
+ * ABI compatibility note: Meaning of these can change in time as new
+ * extensions, bringing the dialect closer to the original, are implemented.
+ */
+#define MD_DIALECT_COMMONMARK 0
+#define MD_DIALECT_GITHUB (MD_FLAG_PERMISSIVEAUTOLINKS | MD_FLAG_TABLES | MD_FLAG_STRIKETHROUGH | MD_FLAG_TASKLISTS)
+
+/* Parser structure.
+ */
+typedef struct MD_PARSER {
+ /* Reserved. Set to zero.
+ */
+ unsigned abi_version;
+
+ /* Dialect options. Bitmask of MD_FLAG_xxxx values.
+ */
+ unsigned flags;
+
+ /* Caller-provided rendering callbacks.
+ *
+ * For some block/span types, more detailed information is provided in a
+ * type-specific structure pointed by the argument 'detail'.
+ *
+ * The last argument of all callbacks, 'userdata', is just propagated from
+ * md_parse() and is available for any use by the application.
+ *
+ * Note any strings provided to the callbacks as their arguments or as
+ * members of any detail structure are generally not zero-terminated.
+ * Application has to take the respective size information into account.
+ *
+ * Any rendering callback may abort further parsing of the document by
+ * returning non-zero.
+ */
+ int (*enter_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
+ int (*leave_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
+
+ int (*enter_span)(MD_SPANTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
+ int (*leave_span)(MD_SPANTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
+
+ int (*text)(MD_TEXTTYPE /*type*/, const MD_CHAR* /*text*/, MD_SIZE /*size*/, void* /*userdata*/);
+
+ /* Debug callback. Optional (may be NULL).
+ *
+ * If provided and something goes wrong, this function gets called.
+ * This is intended for debugging and problem diagnosis for developers;
+ * it is not intended to provide any errors suitable for displaying to an
+ * end user.
+ */
+ void (*debug_log)(const char* /*msg*/, void* /*userdata*/);
+
+ /* Reserved. Set to NULL.
+ */
+ void (*syntax)(void);
+} MD_PARSER;
+
+
+/* For backward compatibility. Do not use in new code.
+ */
+typedef MD_PARSER MD_RENDERER;
+
+
+/* Parse the Markdown document stored in the string 'text' of size 'size'.
+ * The parser provides callbacks to be called during the parsing so the
+ * caller can render the document on the screen or convert the Markdown
+ * to another format.
+ *
+ * Zero is returned on success. If a runtime error occurs (e.g. a memory
+ * fails), -1 is returned. If the processing is aborted due any callback
+ * returning non-zero, the return value of the callback is returned.
+ */
+int md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userdata);
+
+
+#ifdef __cplusplus
+ } /* extern "C" { */
+#endif
+
+#endif /* MD4C_H */
diff --git a/src/third-party/rapidyaml/ryml_all.hpp b/src/third-party/rapidyaml/ryml_all.hpp
new file mode 100644
index 0000000..ee5248b
--- /dev/null
+++ b/src/third-party/rapidyaml/ryml_all.hpp
@@ -0,0 +1,30945 @@
+#ifndef _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
+// - 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, just define the
+// preprocessor symbol RYML_SHARED . This will take
+// care of symbol export/import.
+//
+
+
+
+//********************************************************************************
+//--------------------------------------------------------------------------------
+// LICENSE.txt
+// https://github.com/biojppm/rapidyaml/LICENSE.txt
+//--------------------------------------------------------------------------------
+//********************************************************************************
+
+// Copyright (c) 2018, Joao Paulo Magalhaes <dev@jpmag.me>
+//
+// 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 <stdarg.h> missing in arm-none-eabi-g++
+// https://github.com/biojppm/rapidyaml/issues/193
+#include <stdarg.h>
+
+
+
+
+//********************************************************************************
+//--------------------------------------------------------------------------------
+// src/c4/c4core_all.hpp
+// https://github.com/biojppm/rapidyaml/src/c4/c4core_all.hpp
+//--------------------------------------------------------------------------------
+//********************************************************************************
+
+#ifndef _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 <dev@jpmag.me>
+//
+// 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/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)
+# define C4_UNIX
+# define C4_LINUX
+#elif defined(__unix)
+# define C4_UNIX
+#elif defined(__arm__) || defined(__aarch64__)
+# define C4_ARM
+#elif defined(SWIG)
+# define C4_SWIG
+#else
+# error "unknown platform"
+#endif
+
+#if defined(__posix) || defined(__unix__) || defined(__linux)
+# define C4_POSIX
+#endif
+
+
+#endif /* _C4_PLATFORM_HPP_ */
+
+
+// (end https://github.com/biojppm/c4core/src/c4/platform.hpp)
+
+
+#if 0
+//********************************************************************************
+//--------------------------------------------------------------------------------
+// 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 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
+
+#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64)
+ #define C4_CPU_X86_64
+ #define C4_WORDSIZE 8
+ #define C4_BYTE_ORDER _C4EL
+
+#elif defined(__i386) || defined(__i386__) || defined(_M_IX86)
+ #define C4_CPU_X86
+ #define C4_WORDSIZE 4
+ #define C4_BYTE_ORDER _C4EL
+
+#elif defined(__arm__) || defined(_M_ARM) \
+ || defined(__TARGET_ARCH_ARM) || defined(__aarch64__) || defined(_M_ARM64)
+ #if defined(__aarch64__) || defined(_M_ARM64)
+ #define C4_CPU_ARM64
+ #define C4_CPU_ARMV8
+ #define C4_WORDSIZE 8
+ #else
+ #define C4_CPU_ARM
+ #define C4_WORDSIZE 4
+ #if defined(__ARM_ARCH_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(__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(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 6)
+ #define C4_CPU_ARMV6
+ #elif defined(__ARM_ARCH_5TEJ__) \
+ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 5)
+ #define C4_CPU_ARMV5
+ #elif defined(__ARM_ARCH_4T__) \
+ || (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__))
+ #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(__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(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)
+#endif
+
+
+//********************************************************************************
+//--------------------------------------------------------------------------------
+// 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(C4_WIN) || defined(C4_XBOX) || defined(C4_UE4))
+# 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 C_4MSVC_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__)
+# 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)
+
+// these includes are needed to work around conditional
+// includes in the gcc4.8 shim
+#include <cstdint>
+#include <type_traits>
+#include <cstring>
+
+
+
+
+//********************************************************************************
+//--------------------------------------------------------------------------------
+// cmake/compat/c4/gcc-4.8.hpp
+// https://github.com/biojppm/c4core/cmake/compat/c4/gcc-4.8.hpp
+//--------------------------------------------------------------------------------
+//********************************************************************************
+
+#ifndef _C4_COMPAT_GCC_4_8_HPP_
+#define _C4_COMPAT_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 <cstdint>
+//included above:
+//#include <type_traits>
+
+namespace std {
+
+template<typename _Tp>
+struct is_trivially_copyable : public integral_constant<bool,
+ is_destructible<_Tp>::value && __has_trivial_destructor(_Tp) &&
+ (__has_trivial_constructor(_Tp) || __has_trivial_copy(_Tp) || __has_trivial_assign(_Tp))>
+{ };
+
+template<typename _Tp>
+using is_trivially_copy_constructible = has_trivial_copy_constructor<_Tp>;
+
+template<typename _Tp>
+using is_trivially_default_constructible = has_trivial_default_constructor<_Tp>;
+
+template<typename _Tp>
+using is_trivially_copy_assignable = has_trivial_copy_assign<_Tp>;
+
+/* not supported */
+template<typename _Tp>
+struct is_trivially_move_constructible : false_type
+{ };
+
+/* not supported */
+template<typename _Tp>
+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<uintptr_t>(__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<void*>(__aligned);
+ }
+}
+typedef long double max_align_t ;
+
+}
+#else // __cplusplus
+
+//included above:
+//#include <string.h>
+// 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_COMPAT_GCC_4_8_HPP_
+
+
+// (end https://github.com/biojppm/c4core/cmake/compat/c4/gcc-4.8.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
+# ifdef _MSC_VER
+# if _MSC_VER >= 1910 // >VS2015: VS2017, VS2019
+# 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 */
+#ifndef _MSC_VER
+# if __cplusplus < 201103
+# define C4_CONSTEXPR11
+# define C4_CONSTEXPR14
+//# define C4_NOEXCEPT
+# elif __cplusplus == 201103
+# define C4_CONSTEXPR11 constexpr
+# define C4_CONSTEXPR14
+//# define C4_NOEXCEPT noexcept
+# else
+# define C4_CONSTEXPR11 constexpr
+# define C4_CONSTEXPR14 constexpr
+//# define C4_NOEXCEPT noexcept
+# endif
+#else // _MSC_VER
+# if _MSC_VER < 1900
+# define C4_CONSTEXPR11
+# define C4_CONSTEXPR14
+//# define C4_NOEXCEPT
+# elif _MSC_VER < 2000
+# define C4_CONSTEXPR11 constexpr
+# define C4_CONSTEXPR14
+//# define C4_NOEXCEPT noexcept
+# else
+# define C4_CONSTEXPR11 constexpr
+# define C4_CONSTEXPR14 constexpr
+//# define C4_NOEXCEPT noexcept
+# 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
+
+
+//------------------------------------------------------------
+
+#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)
+# 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
+
+#ifndef _MSC_VER ///< @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))
+/** 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))
+#else
+# 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_FLATTEN
+# define C4_HOT /** @todo */
+# define C4_COLD /** @todo */
+# define C4_EXPECT(x, y) x /** @todo */
+# define C4_LIKELY(x) x /** @todo */
+# define C4_UNLIKELY(x) x /** @todo */
+# define C4_UNREACHABLE() /** @todo */
+# define C4_ATTR_FORMAT(...) /** */
+# define C4_NORETURN /** @todo */
+#endif
+
+#ifndef _MSC_VER
+# define C4_FUNC __FUNCTION__
+# define C4_PRETTY_FUNC __PRETTY_FUNCTION__
+#else /// @todo assuming gcc-like compiler. check it is actually so.
+# define C4_FUNC __FUNCTION__
+# define C4_PRETTY_FUNC __FUNCSIG__
+#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"); }
+#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 */
+#ifndef _MSC_VER
+# define C4_KEEP_EMPTY_LOOP { asm(""); }
+#else
+# define C4_KEEP_EMPTY_LOOP { char c; C4_DONT_OPTIMIZE(c); }
+#endif
+
+/** @def C4_VA_LIST_REUSE_MUST_COPY
+ * @todo <jpmag> 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 <stdint.h>
+#include <stddef.h>
+//included above:
+//#include <type_traits>
+
+#if __cplusplus >= 201103L
+#include <utility> // 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 */
+
+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<size_t>::type;
+
+/** @} */
+
+//--------------------------------------------------
+
+/** @defgroup utility_types Utility types
+ * @ingroup types
+ * @{ */
+
+// some tag types
+
+/** 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{};
+
+
+//--------------------------------------------------
+
+/** whether a value should be used in place of a const-reference in argument passing. */
+template<class T>
+struct cref_uses_val
+{
+ enum { value = (
+ std::is_scalar<T>::value
+ ||
+ (
+#if C4_CPP >= 20
+ (std::is_trivially_copyable<T>::value && std::is_standard_layout<T>::value)
+#else
+ std::is_pod<T>::value
+#endif
+ &&
+ sizeof(T) <= sizeof(size_t))) };
+};
+/** utility macro to override the default behaviour for c4::fastcref<T>
+ @see fastcref */
+#define C4_CREF_USES_VAL(T) \
+template<> \
+struct cref_uses_val<T> \
+{ \
+ enum { value = true }; \
+};
+
+/** Whether to use pass-by-value or pass-by-const-reference in a function argument
+ * or return type. */
+template<class T>
+using fastcref = typename std::conditional<c4::cref_uses_val<T>::value, T, T const&>::type;
+
+//--------------------------------------------------
+
+/** Just what its name says. Useful sometimes as a default empty policy class. */
+struct EmptyStruct
+{
+ template<class... T> EmptyStruct(T && ...){}
+};
+
+/** Just what its name says. Useful sometimes as a default policy class to
+ * be inherited from. */
+struct EmptyStructVirtual
+{
+ virtual ~EmptyStructVirtual() = default;
+ template<class... T> EmptyStructVirtual(T && ...){}
+};
+
+
+/** */
+template<class T>
+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<class T, size_t BytesToPadAtEnd>
+struct Padded : public T
+{
+ using T::T;
+ using T::operator=;
+ Padded(T const& val) : T(val) {}
+ Padded(T && val) : T(val) {}
+ char ___c4padspace___[BytesToPadAtEnd];
+};
+#pragma pack(pop)
+/** When the padding argument is 0, we cannot declare the char[] array. */
+template<class T>
+struct Padded<T, 0> : public T
+{
+ using T::T;
+ using T::operator=;
+ Padded(T const& val) : T(val) {}
+ Padded(T && val) : T(val) {}
+};
+
+/** make T have a size which is at least Min bytes */
+template<class T, size_t Min>
+using MinSized = Padded<T, min_remainder(sizeof(T), Min)>;
+
+/** make T have a size which is a multiple of Mult bytes */
+template<class T, size_t Mult>
+using MultSized = Padded<T, mult_remainder(sizeof(T), Mult)>;
+
+/** make T have a size which is simultaneously:
+ * -bigger or equal than Min
+ * -a multiple of Mult */
+template<class T, size_t Min, size_t Mult>
+using MinMultSized = MultSized<MinSized<T, Min>, Mult>;
+
+/** make T be suitable for use as a uniform buffer. (at least with DirectX). */
+template<class T>
+using UbufSized = MinMultSized<T, 64, 16>;
+
+
+//-----------------------------------------------------------------------------
+
+#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<ty>::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<template<typename...> class X, typename T> struct is_instance_of_tpl : std::false_type {};
+template<template<typename...> class X, typename... Y> struct is_instance_of_tpl<X, X<Y...>> : 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<class T, C4_REQUIRE_T(std::is_pod<T>::value)>
+void foo() { std::cout << "pod type\n"; }
+
+// define an overload for a non-pod type
+template<class T, C4_REQUIRE_T(!std::is_pod<T>::value)>
+void foo() { std::cout << "nonpod type\n"; }
+
+struct non_pod
+{
+ non_pod() : name("asdfkjhasdkjh") {}
+ const char *name;
+};
+
+int main()
+{
+ foo<float>(); // prints "pod type"
+ foo<non_pod>(); // prints "nonpod type"
+}
+@endcode */
+#define C4_REQUIRE_T(cond) typename std::enable_if<cond, bool>::type* = nullptr
+
+/** enable_if for a return type
+ * @see C4_REQUIRE_T */
+#define C4_REQUIRE_R(cond, type_) typename std::enable_if<cond, type_>::type
+
+//-----------------------------------------------------------------------------
+/** define a traits class reporting whether a type provides a member typedef */
+#define C4_DEFINE_HAS_TYPEDEF(member_typedef) \
+template<typename T> \
+struct has_##stype \
+{ \
+private: \
+ \
+ typedef char yes; \
+ typedef struct { char array[2]; } no; \
+ \
+ template<typename C> \
+ static yes _test(typename C::member_typedef*); \
+ \
+ template<typename C> \
+ static no _test(...); \
+ \
+public: \
+ \
+ enum { value = (sizeof(_test<T>(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<I>::type; \
+ using difference_type = typename std::make_signed<I>::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<I>::type; \
+ using difference_type = typename std::make_signed<I>::type; \
+ \
+ template<I n> using value_type = typename std::tuple_element< n, std::tuple<interior_types...>>::type; \
+ template<I n> using pointer = value_type<n>*; \
+ template<I n> using const_pointer = value_type<n> const*; \
+ template<I n> using reference = value_type<n>&; \
+ template<I n> using const_reference = value_type<n> 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<T*>; \
+ using const_reverse_iterator = std::reverse_iterator<T const*>
+
+
+#define _c4_DEFINE_TUPLE_ARRAY_TYPES(interior_types, I) \
+ \
+ _c4_DEFINE_TUPLE_ARRAY_TYPES_WITHOUT_ITERATOR(interior_types, I); \
+ \
+ template<I n> using iterator = value_type<n>*; \
+ template<I n> using const_iterator = value_type<n> const*; \
+ template<I n> using reverse_iterator = std::reverse_iterator< value_type<n>*>; \
+ template<I n> using const_reverse_iterator = std::reverse_iterator< value_type<n> 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<class _Tp, _Tp... _Ip>
+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<size_t... _Ip>
+using index_sequence = integer_sequence<size_t, _Ip...>;
+
+/** @cond DONT_DOCUMENT_THIS */
+namespace __detail {
+
+template<typename _Tp, size_t ..._Extra>
+struct __repeat;
+
+template<typename _Tp, _Tp ..._Np, size_t ..._Extra>
+struct __repeat<integer_sequence<_Tp, _Np...>, _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<size_t _Np> struct __parity;
+template<size_t _Np> struct __make : __parity<_Np % 8>::template __pmake<_Np> {};
+
+template<> struct __make<0> { using type = integer_sequence<size_t>; };
+template<> struct __make<1> { using type = integer_sequence<size_t, 0>; };
+template<> struct __make<2> { using type = integer_sequence<size_t, 0, 1>; };
+template<> struct __make<3> { using type = integer_sequence<size_t, 0, 1, 2>; };
+template<> struct __make<4> { using type = integer_sequence<size_t, 0, 1, 2, 3>; };
+template<> struct __make<5> { using type = integer_sequence<size_t, 0, 1, 2, 3, 4>; };
+template<> struct __make<6> { using type = integer_sequence<size_t, 0, 1, 2, 3, 4, 5>; };
+template<> struct __make<7> { using type = integer_sequence<size_t, 0, 1, 2, 3, 4, 5, 6>; };
+
+template<> struct __parity<0> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type> {}; };
+template<> struct __parity<1> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 1> {}; };
+template<> struct __parity<2> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 2, _Np - 1> {}; };
+template<> struct __parity<3> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 3, _Np - 2, _Np - 1> {}; };
+template<> struct __parity<4> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
+template<> struct __parity<5> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
+template<> struct __parity<6> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
+template<> struct __parity<7> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 7, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
+
+template<typename _Tp, typename _Up>
+struct __convert
+{
+ template<typename> struct __result;
+ template<_Tp ..._Np> struct __result<integer_sequence<_Tp, _Np...>>
+ {
+ using type = integer_sequence<_Up, _Np...>;
+ };
+};
+
+template<typename _Tp>
+struct __convert<_Tp, _Tp>
+{
+ template<typename _Up> struct __result
+ {
+ using type = _Up;
+ };
+};
+
+template<typename _Tp, _Tp _Np>
+using __make_integer_sequence_unchecked = typename __detail::__convert<size_t, _Tp>::template __result<typename __detail::__make<_Np>::type>::type;
+
+template<class _Tp, _Tp _Ep>
+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<class _Tp, _Tp _Np>
+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<size_t _Np>
+using make_index_sequence = make_integer_sequence<size_t, _Np>;
+
+/** 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<class... _Tp>
+using index_sequence_for = make_index_sequence<sizeof...(_Tp)>;
+#endif
+
+/** @} */
+
+
+} // namespace c4
+
+#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_BULTIN_TRAP 2
+#define DEBUG_BREAK_USE_SIGTRAP 3
+
+#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_BULTIN_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_BULTIN_DEBUGTRAP
+__attribute__((always_inline))
+__inline__ static void debug_break(void)
+{
+ __builtin_debugtrap();
+}
+#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BULTIN_TRAP
+__attribute__((always_inline))
+__inline__ static void debug_break(void)
+{
+ __builtin_trap();
+}
+#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_SIGTRAP
+#include <signal.h>
+__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<dummy_type, c4::detail::fail_type__>::value, errmsg)
+
+
+//-----------------------------------------------------------------------------
+
+#define C4_ASSERT_SAME_TYPE(ty1, ty2) \
+ C4_STATIC_ASSERT(std::is_same<ty1 C4_COMMA_X ty2>::value)
+
+#define C4_ASSERT_DIFF_TYPE(ty1, ty2) \
+ C4_STATIC_ASSERT( ! std::is_same<ty1 C4_COMMA_X ty2>::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_
+
+
+#ifdef NDEBUG
+# define C4_DEBUG_BREAK()
+#else
+# ifdef __clang__
+# pragma clang diagnostic push
+# 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__)
+# endif
+// amalgamate: removed include of
+// https://github.com/biojppm/c4core/src/c4/ext/debugbreak/debugbreak.h
+//# include <c4/ext/debugbreak/debugbreak.h>
+#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__)
+# 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 = 0x01 << 0,
+ /** when an error happens log a message. */
+ ON_ERROR_LOG = 0x01 << 1,
+ /** when an error happens invoke a callback if it was set with
+ * set_error_callback(). */
+ ON_ERROR_CALLBACK = 0x01 << 2,
+ /** when an error happens call std::terminate(). */
+ ON_ERROR_ABORT = 0x01 << 3,
+ /** when an error happens and exceptions are enabled throw an exception.
+ * Without effect otherwise. */
+ ON_ERROR_THROW = 0x01 << 4,
+ /** 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
+{
+ 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;
+
+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/
+
+
+#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))
+#define C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(w) \
+ C4_SUPPRESS_WARNING_MSVC_PUSH \
+ C4_SUPPRESS_WARNING_MSVC(w)
+#else // C4_MSVC
+#define C4_SUPPRESS_WARNING_MSVC_PUSH
+#define C4_SUPPRESS_WARNING_MSVC(w)
+#define C4_SUPPRESS_WARNING_MSVC_POP
+#define C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(w)
+#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")
+#define C4_SUPPRESS_WARNING_CLANG_WITH_PUSH(w) \
+ C4_SUPPRESS_WARNING_CLANG_PUSH \
+ C4_SUPPRESS_WARNING_CLANG(w)
+#else // C4_CLANG
+#define C4_SUPPRESS_WARNING_CLANG_PUSH
+#define C4_SUPPRESS_WARNING_CLANG(w)
+#define C4_SUPPRESS_WARNING_CLANG_POP
+#define C4_SUPPRESS_WARNING_CLANG_WITH_PUSH(w)
+#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")
+#define C4_SUPPRESS_WARNING_GCC_WITH_PUSH(w) \
+ C4_SUPPRESS_WARNING_GCC_PUSH \
+ C4_SUPPRESS_WARNING_GCC(w)
+#else // C4_GCC
+#define C4_SUPPRESS_WARNING_GCC_PUSH
+#define C4_SUPPRESS_WARNING_GCC(w)
+#define C4_SUPPRESS_WARNING_GCC_POP
+#define C4_SUPPRESS_WARNING_GCC_WITH_PUSH(w)
+#endif // C4_GCC
+
+
+#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_ */
+
+
+//included above:
+//#include <string.h>
+
+/** @file memory_util.hpp Some memory utilities. */
+
+namespace c4 {
+
+/** 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<class T>
+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<class T>
+C4_ALWAYS_INLINE void mem_zero(T* mem)
+{
+ memset(mem, 0, sizeof(T));
+}
+
+bool mem_overlaps(void const* a, void const* b, size_t sza, size_t szb);
+
+void mem_repeat(void* dest, void const* pattern, size_t pattern_size, size_t num_times);
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+template<class T>
+bool is_aligned(T *ptr, size_t alignment=alignof(T))
+{
+ return (uintptr_t(ptr) & (alignment - 1)) == 0u;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// least significant bit
+
+/** least significant bit; this function is constexpr-14 because of the local
+ * variable */
+template<class I>
+C4_CONSTEXPR14 I lsb(I v)
+{
+ if(!v) return 0;
+ I b = 0;
+ while((v & I(1)) == I(0))
+ {
+ v >>= 1;
+ ++b;
+ }
+ return b;
+}
+
+namespace detail {
+
+template<class I, I val, I num_bits, bool finished>
+struct _lsb11;
+
+template<class I, I val, I num_bits>
+struct _lsb11< I, val, num_bits, false>
+{
+ enum : I { num = _lsb11<I, (val>>1), num_bits+I(1), (((val>>1)&I(1))!=I(0))>::num };
+};
+
+template<class I, I val, I num_bits>
+struct _lsb11<I, val, num_bits, true>
+{
+ enum : I { 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<class I, I number>
+struct lsb11
+{
+ static_assert(number != 0, "lsb: number must be nonzero");
+ enum : I { value = detail::_lsb11<I, number, 0, ((number&I(1))!=I(0))>::num};
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// most significant bit
+
+/** most significant bit; this function is constexpr-14 because of the local
+ * variable
+ * @todo implement faster version
+ * @see https://stackoverflow.com/questions/2589096/find-most-significant-bit-left-most-that-is-set-in-a-bit-array
+ */
+template<class I>
+C4_CONSTEXPR14 I msb(I v)
+{
+ // TODO:
+ //
+ //int n;
+ //if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, n |= 32;
+ //if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, n |= 16;
+ //if(input_num & uint64_t( 0xff00)) input_num >>= 8, n |= 8;
+ //if(input_num & uint64_t( 0xf0)) input_num >>= 4, n |= 4;
+ //if(input_num & uint64_t( 0xc)) input_num >>= 2, n |= 2;
+ //if(input_num & uint64_t( 0x2)) input_num >>= 1, n |= 1;
+ if(!v) return static_cast<I>(-1);
+ I b = 0;
+ while(v != 0)
+ {
+ v >>= 1;
+ ++b;
+ }
+ return b-1;
+}
+
+namespace detail {
+
+template<class I, I val, I num_bits, bool finished>
+struct _msb11;
+
+template<class I, I val, I num_bits>
+struct _msb11< I, val, num_bits, false>
+{
+ enum : I { num = _msb11<I, (val>>1), num_bits+I(1), ((val>>1)==I(0))>::num };
+};
+
+template<class I, I val, I num_bits>
+struct _msb11<I, val, num_bits, true>
+{
+ static_assert(val == 0, "bad implementation");
+ enum : I { num = 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<class I, I number>
+struct msb11
+{
+ enum : I { value = detail::_msb11<I, number, 0, (number==I(0))>::num };
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** return a mask with all bits set [first_bit,last_bit[; this function
+ * is constexpr-14 because of the local variables */
+template<class I>
+C4_CONSTEXPR14 I contiguous_mask(I first_bit, I last_bit)
+{
+ I r = 0;
+ constexpr const I o = 1;
+ for(I i = first_bit; i < last_bit; ++i)
+ {
+ r |= (o << i);
+ }
+ return r;
+}
+
+
+namespace detail {
+
+template<class I, I val, I first, I last, bool finished>
+struct _ctgmsk11;
+
+template<class I, I val, I first, I last>
+struct _ctgmsk11< I, val, first, last, true>
+{
+ enum : I { value = _ctgmsk11<I, val|(I(1)<<first), first+I(1), last, (first+1!=last)>::value };
+};
+
+template<class I, I val, I first, I last>
+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<class I, I first_bit, I last_bit>
+struct contiguous_mask11
+{
+ enum : I { value = detail::_ctgmsk11<I, I(0), first_bit, last_bit, (first_bit!=last_bit)>::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<class First, class Second>
+constexpr TightPairCase_e tpc_which_case()
+{
+ return std::is_same<First, Second>::value ?
+ std::is_empty<First>::value ?
+ tpc_same_empty
+ :
+ tpc_same
+ :
+ std::is_empty<First>::value && std::is_empty<Second>::value ?
+ tpc_both_empty
+ :
+ std::is_empty<First>::value ?
+ tpc_first_empty
+ :
+ std::is_empty<Second>::value ?
+ tpc_second_empty
+ :
+ tpc_general
+ ;
+}
+
+template<class First, class Second, TightPairCase_e Case>
+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<class First, class Second>
+struct tight_pair<First, Second, tpc_same_empty> : public First
+{
+ static_assert(std::is_same<First, Second>::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<First &>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast<First const&>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return reinterpret_cast<Second &>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return reinterpret_cast<Second const&>(*this); }
+};
+
+template<class First, class Second>
+struct tight_pair<First, Second, tpc_both_empty> : 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<First &>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast<First const&>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return static_cast<Second &>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return static_cast<Second const&>(*this); }
+};
+
+template<class First, class Second>
+struct tight_pair<First, Second, tpc_same> : 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<First &>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast<First const&>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; }
+};
+
+template<class First, class Second>
+struct tight_pair<First, Second, tpc_first_empty> : 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<First &>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 First const& first () const { return static_cast<First const&>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 Second & second() { return m_second; }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return m_second; }
+};
+
+template<class First, class Second>
+struct tight_pair<First, Second, tpc_second_empty> : 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<Second &>(*this); }
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 Second const& second() const { return static_cast<Second const&>(*this); }
+};
+
+} // namespace detail
+
+template<class First, class Second>
+using tight_pair = detail::tight_pair<First, Second, detail::tpc_which_case<First,Second>()>;
+
+} // namespace c4
+
+#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
+{
+ const char *name = nullptr;
+ virtual ~MemoryResource() {}
+
+ 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
+{
+
+ MemoryResourceMalloc() { name = "malloc"; }
+ virtual ~MemoryResourceMalloc() override {}
+
+protected:
+
+ virtual void* do_allocate(size_t sz, size_t alignment, void *hint) override
+ {
+ C4_UNUSED(hint);
+ return c4::aalloc(sz, alignment);
+ }
+
+ virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override
+ {
+ C4_UNUSED(sz);
+ C4_UNUSED(alignment);
+ c4::afree(ptr);
+ }
+
+ virtual 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:
+
+ virtual void* do_allocate(size_t sz, size_t alignment, void* hint) override
+ {
+ return m_local->allocate(sz, alignment, hint);
+ }
+
+ virtual void* do_reallocate(void* ptr, size_t oldsz, size_t newsz, size_t alignment) override
+ {
+ return m_local->reallocate(ptr, oldsz, newsz, alignment);
+ }
+
+ virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override
+ {
+ return 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); }
+
+ virtual ~_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
+{
+
+ C4_NO_COPY_OR_MOVE(MemoryResourceLinear);
+
+public:
+
+ using detail::_MemoryResourceSingleChunk::_MemoryResourceSingleChunk;
+
+protected:
+
+ virtual void* do_allocate(size_t sz, size_t alignment, void *hint) override;
+ virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override;
+ virtual 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
+{
+
+ C4_NO_COPY_OR_MOVE(MemoryResourceStack);
+
+public:
+
+ using detail::_MemoryResourceSingleChunk::_MemoryResourceSingleChunk;
+
+protected:
+
+ virtual void* do_allocate(size_t sz, size_t alignment, void *hint) override;
+ virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override;
+ virtual 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<size_t N>
+struct MemoryResourceLinearArr : public MemoryResourceLinear
+{
+ #ifdef _MSC_VER
+ #pragma warning(push)
+ #pragma warning(disable: 4324) // structure was padded due to alignment specifier
+ #endif
+ alignas(alignof(max_align_t)) char m_arr[N];
+ #ifdef _MSC_VER
+ #pragma warning(pop)
+ #endif
+ MemoryResourceLinearArr() : MemoryResourceLinear(m_arr, N) { name = "linear_arr"; }
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+struct AllocationCounts
+{
+ struct Item
+ {
+ ssize_t allocs;
+ ssize_t size;
+
+ void add(size_t sz)
+ {
+ ++allocs;
+ size += static_cast<ssize_t>(sz);
+ }
+ void rem(size_t sz)
+ {
+ --allocs;
+ size -= static_cast<ssize_t>(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;
+ AllocationCounts m_counts;
+
+protected:
+
+ virtual 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;
+ }
+
+ virtual void do_deallocate(void* ptr, size_t sz, size_t alignment) override
+ {
+ m_counts.rem_counts(ptr, sz);
+ m_resource->deallocate(ptr, sz, alignment);
+ }
+
+ virtual 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
+{
+ 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
+{
+ 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 <type_traits>
+//included above:
+//#include <utility> // std::forward
+
+/** @file ctor_dtor.hpp object construction and destruction facilities.
+ * Some of these are not yet available in C++11. */
+
+namespace c4 {
+
+/** default-construct an object, trivial version */
+template <class U> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_default_constructible<U>::value, void>::type
+construct(U *ptr) noexcept
+{
+ memset(ptr, 0, sizeof(U));
+}
+/** default-construct an object, non-trivial version */
+template<class U> C4_ALWAYS_INLINE typename std ::enable_if< ! std::is_trivially_default_constructible<U>::value, void>::type
+construct(U* ptr) noexcept
+{
+ new ((void*)ptr) U();
+}
+
+/** default-construct n objects, trivial version */
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_default_constructible<U>::value, void>::type
+construct_n(U* ptr, I n) noexcept
+{
+ memset(ptr, 0, n * sizeof(U));
+}
+/** default-construct n objects, non-trivial version */
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_default_constructible<U>::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<class U, class ...Args>
+inline void construct(U* ptr, Args&&... args)
+{
+ new ((void*)ptr) U(std::forward<Args>(args)...);
+}
+template<class U, class I, class ...Args>
+inline void construct_n(U* ptr, I n, Args&&... args)
+{
+ 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<class U> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_copy_constructible<U>::value, void>::type
+copy_construct(U* dst, U const* src) noexcept
+{
+ C4_ASSERT(dst != src);
+ memcpy(dst, src, sizeof(U));
+}
+template<class U> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_constructible<U>::value, void>::type
+copy_construct(U* dst, U const* src)
+{
+ C4_ASSERT(dst != src);
+ new ((void*)dst) U(*src);
+}
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_copy_constructible<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_constructible<U>::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<class U> C4_ALWAYS_INLINE typename std::enable_if<std::is_scalar<U>::value, void>::type
+copy_construct(U* dst, U src) noexcept // pass by value for scalar types
+{
+ *dst = src;
+}
+template<class U> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_scalar<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar<U>::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<class U, size_t N>
+C4_ALWAYS_INLINE void copy_construct(U (&dst)[N], U const (&src)[N]) noexcept
+{
+ copy_construct_n(dst, src, N);
+}
+
+//-----------------------------------------------------------------------------
+// copy-assign
+
+template<class U> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_copy_assignable<U>::value, void>::type
+copy_assign(U* dst, U const* src) noexcept
+{
+ C4_ASSERT(dst != src);
+ memcpy(dst, src, sizeof(U));
+}
+template<class U> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_assignable<U>::value, void>::type
+copy_assign(U* dst, U const* src) noexcept
+{
+ C4_ASSERT(dst != src);
+ *dst = *src;
+}
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_copy_assignable<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_copy_assignable<U>::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<class U> C4_ALWAYS_INLINE typename std::enable_if<std::is_scalar<U>::value, void>::type
+copy_assign(U* dst, U src) noexcept // pass by value for scalar types
+{
+ *dst = src;
+}
+template<class U> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_scalar<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_scalar<U>::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<class U, size_t N>
+C4_ALWAYS_INLINE void copy_assign(U (&dst)[N], U const (&src)[N]) noexcept
+{
+ copy_assign_n(dst, src, N);
+}
+
+//-----------------------------------------------------------------------------
+// move-construct
+
+template<class U> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_move_constructible<U>::value, void>::type
+move_construct(U* dst, U* src) noexcept
+{
+ C4_ASSERT(dst != src);
+ memcpy(dst, src, sizeof(U));
+}
+template<class U> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible<U>::value, void>::type
+move_construct(U* dst, U* src) noexcept
+{
+ C4_ASSERT(dst != src);
+ new ((void*)dst) U(std::move(*src));
+}
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_move_constructible<U>::value, void>::type
+move_construct_n(U* dst, U* src, I n) noexcept
+{
+ C4_ASSERT(dst != src);
+ memcpy(dst, src, n * sizeof(U));
+}
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible<U>::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<class U> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_move_assignable<U>::value, void>::type
+move_assign(U* dst, U* src) noexcept
+{
+ C4_ASSERT(dst != src);
+ memcpy(dst, src, sizeof(U));
+}
+template<class U> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_assignable<U>::value, void>::type
+move_assign(U* dst, U* src) noexcept
+{
+ C4_ASSERT(dst != src);
+ *dst = std::move(*src);
+}
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_move_assignable<U>::value, void>::type
+move_assign_n(U* dst, U* src, I n) noexcept
+{
+ C4_ASSERT(dst != src);
+ memcpy(dst, src, n * sizeof(U));
+}
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_assignable<U>::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<class U> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_destructible<U>::value, void>::type
+destroy(U* ptr) noexcept
+{
+ C4_UNUSED(ptr); // nothing to do
+}
+template<class U> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_destructible<U>::value, void>::type
+destroy(U* ptr) noexcept
+{
+ ptr->~U();
+}
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_destructible<U>::value, void>::type
+destroy_n(U* ptr, I n) noexcept
+{
+ C4_UNUSED(ptr);
+ C4_UNUSED(n); // nothing to do
+}
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_destructible<U>::value, void>::type
+destroy_n(U* ptr, I n) noexcept
+{
+ for(I i = 0; i <n; ++i)
+ {
+ ptr[i].~U();
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+/** makes room at the beginning of buf, which has a current size of n */
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_move_constructible<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible<U>::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<class U, class I>
+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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_move_constructible<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible<U>::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<class U, class I>
+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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_scalar<U>::value || (std::is_standard_layout<U>::value && std::is_trivial<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! (std::is_scalar<U>::value || (std::is_standard_layout<U>::value && std::is_trivial<U>::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<class U, class I> C4_ALWAYS_INLINE typename std::enable_if<std::is_trivially_move_constructible<U>::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 <n);
+ C4_ASSERT(pos + room <= n);
+ memcpy(dst, src, pos * sizeof(U));
+ memcpy(dst + pos, src + room + pos, (n - pos - room) * sizeof(U));
+}
+/** destroy room to the right of pos, copying to a different buffer */
+template<class U, class I> C4_ALWAYS_INLINE typename std::enable_if< ! std::is_trivially_move_constructible<U>::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]));
+ }
+}
+
+} // namespace c4
+
+#undef _C4REQUIRE
+
+#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 <memory> // std::allocator_traits
+//included above:
+//#include <type_traits>
+
+/** @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 {
+
+namespace detail {
+template<class T> inline size_t size_for (size_t num_objs) noexcept { return num_objs * sizeof(T); }
+template< > inline size_t size_for<void>(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()) {}
+
+ inline 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() {}
+ MemResGlobal(MemoryResource* r) noexcept { C4_UNUSED(r); C4_ASSERT(r == get_memory_resource()); }
+
+ inline MemoryResource* resource() const { return get_memory_resource(); }
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+namespace detail {
+template<class MemRes>
+struct _AllocatorUtil;
+
+template<class T, class ...Args>
+struct has_no_alloc
+ : public std::integral_constant<bool,
+ !(std::uses_allocator<T, MemoryResource*>::value)
+ && std::is_constructible<T, Args...>::value> {};
+
+// std::uses_allocator_v<U, MemoryResource> && std::is_constructible<U, std::allocator_arg_t, MemoryResource*, Args...>
+// ie can construct(std::allocator_arg_t, MemoryResource*, Args...)
+template<class T, class ...Args>
+struct has_alloc_arg
+ : public std::integral_constant<bool,
+ std::uses_allocator<T, MemoryResource*>::value
+ && std::is_constructible<T, std::allocator_arg_t, MemoryResource*, Args...>::value> {};
+// std::uses_allocator<U> && std::is_constructible<U, Args..., MemoryResource*>
+// ie, can construct(Args..., MemoryResource*)
+template<class T, class ...Args>
+struct has_alloc
+ : public std::integral_constant<bool,
+ std::uses_allocator<T, MemoryResource*>::value
+ && std::is_constructible<T, Args..., MemoryResource*>::value> {};
+
+} // namespace detail
+
+
+template<class MemRes>
+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 <class U, class... Args>
+ C4_ALWAYS_INLINE typename std::enable_if<detail::has_no_alloc<U, Args...>::value, void>::type
+ construct(U *ptr, Args &&...args)
+ {
+ c4::construct(ptr, std::forward<Args>(args)...);
+ }
+ template<class U, class I, class... Args>
+ C4_ALWAYS_INLINE typename std::enable_if<detail::has_no_alloc<U, Args...>::value, void>::type
+ construct_n(U* ptr, I n, Args&&... args)
+ {
+ c4::construct_n(ptr, n, std::forward<Args>(args)...);
+ }
+
+ // 2. types using allocators (ie, containers)
+
+ // 2.1. can construct(std::allocator_arg_t, MemoryResource*, Args...)
+ template<class U, class... Args>
+ C4_ALWAYS_INLINE typename std::enable_if<detail::has_alloc_arg<U, Args...>::value, void>::type
+ construct(U* ptr, Args&&... args)
+ {
+ c4::construct(ptr, std::allocator_arg, this->resource(), std::forward<Args>(args)...);
+ }
+ template<class U, class I, class... Args>
+ C4_ALWAYS_INLINE typename std::enable_if<detail::has_alloc_arg<U, Args...>::value, void>::type
+ construct_n(U* ptr, I n, Args&&... args)
+ {
+ c4::construct_n(ptr, n, std::allocator_arg, this->resource(), std::forward<Args>(args)...);
+ }
+
+ // 2.2. can construct(Args..., MemoryResource*)
+ template<class U, class... Args>
+ C4_ALWAYS_INLINE typename std::enable_if<detail::has_alloc<U, Args...>::value, void>::type
+ construct(U* ptr, Args&&... args)
+ {
+ c4::construct(ptr, std::forward<Args>(args)..., this->resource());
+ }
+ template<class U, class I, class... Args>
+ C4_ALWAYS_INLINE typename std::enable_if<detail::has_alloc<U, Args...>::value, void>::type
+ construct_n(U* ptr, I n, Args&&... args)
+ {
+ c4::construct_n(ptr, n, std::forward<Args>(args)..., this->resource());
+ }
+
+ template<class U>
+ static C4_ALWAYS_INLINE void destroy(U* ptr)
+ {
+ c4::destroy(ptr);
+ }
+ template<class U, class I>
+ 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 T, class MemResProvider=MemResGlobal>
+class Allocator : public detail::_AllocatorUtil<MemResProvider>
+{
+public:
+
+ using impl_type = detail::_AllocatorUtil<MemResProvider>;
+
+ 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<class U, class MRProv>
+ bool operator== (Allocator<U, MRProv> const& that) const
+ {
+ return this->resource() == that.resource();
+ }
+ template<class U, class MRProv>
+ bool operator!= (Allocator<U, MRProv> const& that) const
+ {
+ return this->resource() != that.resource();
+ }
+
+public:
+
+ template<class U, class MRProv> friend class Allocator;
+ template<class U>
+ struct rebind
+ {
+ using other = Allocator<U, MemResProvider>;
+ };
+ template<class U>
+ typename rebind<U>::other rebound()
+ {
+ return typename rebind<U>::other(*this);
+ }
+
+public:
+
+ using impl_type::impl_type;
+ Allocator() : impl_type() {} // VS demands this
+
+ template<class U> Allocator(Allocator<U, MemResProvider> const& that) : impl_type(that.resource()) {}
+
+ Allocator(Allocator const&) = default;
+ Allocator(Allocator &&) = default;
+
+ Allocator& operator= (Allocator const&) = default; // WTF? 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<T>(num_objs), alignment);
+ T* mem = static_cast<T*>(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<T>(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<T>(oldnum), detail::size_for<T>(newnum), alignment);
+ T* mem = static_cast<T*>(vmem);
+ return mem;
+ }
+
+};
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** @ingroup allocators */
+template<class T, size_t N=16, size_t Alignment=alignof(T), class MemResProvider=MemResGlobal>
+class SmallAllocator : public detail::_AllocatorUtil<MemResProvider>
+{
+ static_assert(Alignment >= alignof(T), "invalid alignment");
+
+ using impl_type = detail::_AllocatorUtil<MemResProvider>;
+
+ 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<class U>
+ bool operator== (SmallAllocator<U,N,Alignment,MemResProvider> const&) const
+ {
+ return false;
+ }
+ template<class U>
+ bool operator!= (SmallAllocator<U,N,Alignment,MemResProvider> const&) const
+ {
+ return true;
+ }
+
+public:
+
+ template<class U, size_t, size_t, class> friend class SmallAllocator;
+ template<class U>
+ struct rebind
+ {
+ using other = SmallAllocator<U, N, alignof(U), MemResProvider>;
+ };
+ template<class U>
+ typename rebind<U>::other rebound()
+ {
+ return typename rebind<U>::other(*this);
+ }
+
+public:
+
+ using impl_type::impl_type;
+ SmallAllocator() : impl_type() {} // VS demands this
+
+ template<class U, size_t N2, size_t A2, class MP2>
+ SmallAllocator(SmallAllocator<U,N2,A2,MP2> 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<T*>(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<T*>(vmem);
+ return mem;
+ }
+
+};
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** An allocator making use of the global memory resource.
+ * @ingroup allocators */
+template<class T> using allocator = Allocator<T, MemResGlobal>;
+/** An allocator with a per-instance memory resource
+ * @ingroup allocators */
+template<class T> using allocator_mr = Allocator<T, MemRes>;
+
+/** @ingroup allocators */
+template<class T, size_t N=16, size_t Alignment=alignof(T)> using small_allocator = SmallAllocator<T, N, Alignment, MemResGlobal>;
+/** @ingroup allocators */
+template<class T, size_t N=16, size_t Alignment=alignof(T)> using small_allocator_mr = SmallAllocator<T, N, Alignment, MemRes>;
+
+} // 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 <string> // needed because of std::char_traits
+#include <cctype>
+#include <cwctype>
+
+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<wint_t>(c)) != 0; }
+
+//-----------------------------------------------------------------------------
+template<typename C>
+struct char_traits;
+
+template<>
+struct char_traits<char> : public std::char_traits<char>
+{
+ 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<wchar_t> : public std::char_traits<wchar_t>
+{
+ 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<typename C>
+struct needed_chars;
+template<>
+struct needed_chars<char>
+{
+ template<class SizeType>
+ C4_ALWAYS_INLINE constexpr static SizeType for_bytes(SizeType num_bytes)
+ {
+ return num_bytes;
+ }
+};
+template<>
+struct needed_chars<wchar_t>
+{
+ template<class SizeType>
+ C4_ALWAYS_INLINE constexpr static SizeType for_bytes(SizeType num_bytes)
+ {
+ // wchar_t is not necessarily 2 bytes.
+ return (num_bytes / static_cast<SizeType>(sizeof(wchar_t))) + ((num_bytes & static_cast<SizeType>(SizeType(sizeof(wchar_t)) - SizeType(1))) != 0);
+ }
+};
+} // namespace detail
+
+/** get the number of C characters needed to store a number of bytes */
+template<typename C, typename SizeType>
+C4_ALWAYS_INLINE constexpr SizeType num_needed_chars(SizeType num_bytes)
+{
+ return detail::needed_chars<C>::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<type>::get(txt, C4_WIDEN(txt))
+
+namespace detail {
+template<typename C>
+struct literal_as;
+
+template<>
+struct literal_as<char>
+{
+ C4_ALWAYS_INLINE static constexpr const char* get(const char* str, const wchar_t *)
+ {
+ return str;
+ }
+};
+template<>
+struct literal_as<wchar_t>
+{
+ 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 <climits>
+
+/** @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<typename ResultT, ResultT OffsetBasis, ResultT Prime>
+class basic_fnv1a final
+{
+
+ static_assert(std::is_unsigned<ResultT>::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 cdata = static_cast<const unsigned char *>(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<uint32_t, UINT32_C( 2166136261), UINT32_C( 16777619)>;
+using fnv1a_64 = basic_fnv1a<uint64_t, UINT64_C(14695981039346656037), UINT64_C(1099511628211)>;
+
+template<size_t Bits> struct fnv1a;
+template<> struct fnv1a<32> { using type = fnv1a_32; };
+template<> struct fnv1a<64> { using type = fnv1a_64; };
+
+} // namespace detail
+
+
+/** @ingroup hash */
+template<size_t Bits>
+using fnv1a_t = typename detail::fnv1a<Bits>::type;
+
+
+/** @ingroup hash */
+C4_CONSTEXPR14 inline size_t hash_bytes(const void *const data, const size_t size) noexcept
+{
+ fnv1a_t<CHAR_BIT * sizeof(size_t)> fn{};
+ fn.update(data, size);
+ return fn.digest();
+}
+
+/**
+ * @overload hash_bytes
+ * @ingroup hash */
+template<size_t N>
+C4_CONSTEXPR14 inline size_t hash_bytes(const char (&str)[N]) noexcept
+{
+ fnv1a_t<CHAR_BIT * sizeof(size_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 <limits>
+
+namespace c4 {
+
+/** @todo this would be so much easier with calls to numeric_limits::max()... */
+template<class SizeOut, class SizeIn>
+struct is_narrower_size : std::conditional
+<
+ (std::is_signed<SizeOut>::value == std::is_signed<SizeIn>::value)
+ ?
+ (sizeof(SizeOut) < sizeof(SizeIn))
+ :
+ (
+ (sizeof(SizeOut) < sizeof(SizeIn))
+ ||
+ (
+ (sizeof(SizeOut) == sizeof(SizeIn))
+ &&
+ (std::is_signed<SizeOut>::value && std::is_unsigned<SizeIn>::value)
+ )
+ ),
+ std::true_type,
+ std::false_type
+>::type
+{
+ static_assert(std::is_integral<SizeIn >::value, "must be integral type");
+ static_assert(std::is_integral<SizeOut>::value, "must be integral type");
+};
+
+
+/** when SizeOut is wider than SizeIn, assignment can occur without reservations */
+template<class SizeOut, class SizeIn>
+C4_ALWAYS_INLINE
+typename std::enable_if< ! is_narrower_size<SizeOut, SizeIn>::value, SizeOut>::type
+szconv(SizeIn sz) noexcept
+{
+ return static_cast<SizeOut>(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<class SizeOut, class SizeIn>
+C4_ALWAYS_INLINE
+typename std::enable_if<is_narrower_size<SizeOut, SizeIn>::value, SizeOut>::type
+szconv(SizeIn sz) C4_NOEXCEPT_X
+{
+ C4_XASSERT(sz >= 0);
+ C4_XASSERT_MSG((SizeIn)sz <= (SizeIn)std::numeric_limits<SizeOut>::max(), "size conversion overflow: in=%zu", (size_t)sz);
+ SizeOut szo = static_cast<SizeOut>(sz);
+ return szo;
+}
+
+} // 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<class T>
+struct blob_
+{
+ T * buf;
+ size_t len;
+
+ C4_ALWAYS_INLINE blob_() noexcept : buf(), len() {}
+
+ 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;
+
+ // need to sfinae out copy constructors! (why? isn't the above sufficient?)
+ #define _C4_REQUIRE_NOT_SAME class=typename std::enable_if<( ! std::is_same<U, blob_>::value) && ( ! std::is_pointer<U>::value), T>::type
+ template<class U, _C4_REQUIRE_NOT_SAME> C4_ALWAYS_INLINE blob_(U &var) noexcept : buf(reinterpret_cast<T*>(&var)), len(sizeof(U)) {}
+ template<class U, _C4_REQUIRE_NOT_SAME> C4_ALWAYS_INLINE blob_& operator= (U &var) noexcept { buf = reinterpret_cast<T*>(&var); len = sizeof(U); return *this; }
+ #undef _C4_REQUIRE_NOT_SAME
+
+ template<class U, size_t N> C4_ALWAYS_INLINE blob_(U (&arr)[N]) noexcept : buf(reinterpret_cast<T*>(arr)), len(sizeof(U) * N) {}
+ template<class U, size_t N> C4_ALWAYS_INLINE blob_& operator= (U (&arr)[N]) noexcept { buf = reinterpret_cast<T*>(arr); len = sizeof(U) * N; return *this; }
+
+ template<class U>
+ C4_ALWAYS_INLINE blob_(U *ptr, size_t n) noexcept : buf(reinterpret_cast<T*>(ptr)), len(sizeof(U) * n) { C4_ASSERT(is_aligned(ptr)); }
+ C4_ALWAYS_INLINE blob_(void *ptr, size_t n) noexcept : buf(reinterpret_cast<T*>(ptr)), len(n) {}
+ C4_ALWAYS_INLINE blob_(void const *ptr, size_t n) noexcept : buf(reinterpret_cast<T*>(ptr)), len(n) {}
+};
+
+/** an immutable binary blob */
+using cblob = blob_<cbyte>;
+/** 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<class C> struct basic_substring;
+using csubstr = C4CORE_EXPORT basic_substring<const char>;
+using substr = C4CORE_EXPORT basic_substring<char>;
+#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 <string.h>
+//included above:
+//#include <ctype.h>
+//included above:
+//#include <type_traits>
+
+// 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
+#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"
+#endif
+
+
+namespace c4 {
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+namespace detail {
+
+template<typename C>
+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
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+// 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 U=C> \
+ typename std::enable_if< ! std::is_const<U>::value, ret_type>::type
+// non-const-to-const
+#define C4_NC2C(ty) \
+ typename std::enable_if<std::is_const<C>::value && ( ! std::is_const<ty>::value), ty>::type
+
+
+/** a non-owning string-view, consisting of a character pointer
+ * and a length.
+ *
+ * @note The pointer is explicitly restricted.
+ * @note Because of a C++ limitation, there cannot coexist overloads for
+ * constructing from a char[N] and a char*; the latter will always be chosen
+ * by the compiler. To construct an object of this type, call to_substr() or
+ * to_csubstr(). For a more detailed explanation on why the overloads cannot
+ * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html
+ *
+ * @see to_substr()
+ * @see to_csubstr()
+ */
+template<class C>
+struct C4CORE_EXPORT basic_substring
+{
+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<C>::type; //!< CC=const char
+ using NCC_ = typename std::remove_const<C>::type; //!< NCC_=non const char
+
+ using ro_substr = basic_substring<CC>;
+ using rw_substr = basic_substring<NCC_>;
+
+ 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
+ operator ro_substr () const { ro_substr s(str, len); return s; }
+
+ /** @} */
+
+public:
+
+ /** @name Default construction and assignment */
+ /** @{ */
+
+ constexpr basic_substring() : str(nullptr), len(0) {}
+
+ constexpr basic_substring(basic_substring const&) = default;
+ constexpr basic_substring(basic_substring &&) = default;
+ constexpr basic_substring(std::nullptr_t) : str(nullptr), len(0) {}
+
+ basic_substring& operator= (basic_substring const&) = default;
+ basic_substring& operator= (basic_substring &&) = default;
+ basic_substring& operator= (std::nullptr_t) { str = nullptr; len = 0; return *this; }
+
+ /** @} */
+
+public:
+
+ /** @name Construction and assignment from characters with the same type */
+ /** @{ */
+
+ //basic_substring(C *s_) : str(s_), len(s_ ? strlen(s_) : 0) {}
+ /** the overload for receiving a single C* pointer will always
+ * hide the array[N] overload. So it is disabled. If you want to
+ * construct a substr from a single pointer containing a C-style string,
+ * you can call c4::to_substr()/c4::to_csubstr().
+ * @see c4::to_substr()
+ * @see c4::to_csubstr() */
+ template<size_t N>
+ constexpr basic_substring(C (&s_)[N]) noexcept : str(s_), len(N-1) {}
+ basic_substring(C *s_, size_t len_) : str(s_), len(len_) { C4_ASSERT(str || !len_); }
+ basic_substring(C *beg_, C *end_) : str(beg_), len(static_cast<size_t>(end_ - beg_)) { C4_ASSERT(end_ >= beg_); }
+
+ //basic_substring& operator= (C *s_) { this->assign(s_); return *this; }
+ template<size_t N>
+ basic_substring& operator= (C (&s_)[N]) { this->assign<N>(s_); return *this; }
+
+ //void assign(C *s_) { str = (s_); len = (s_ ? strlen(s_) : 0); }
+ /** the overload for receiving a single C* pointer will always
+ * hide the array[N] overload. So it is disabled. If you want to
+ * construct a substr from a single pointer containing a C-style string,
+ * you can call c4::to_substr()/c4::to_csubstr().
+ * @see c4::to_substr()
+ * @see c4::to_csubstr() */
+ template<size_t N>
+ void assign(C (&s_)[N]) { str = (s_); len = (N-1); }
+ void assign(C *s_, size_t len_) { str = s_; len = len_; C4_ASSERT(str || !len_); }
+ void assign(C *beg_, C *end_) { C4_ASSERT(end_ >= beg_); str = (beg_); len = (end_ - beg_); }
+
+ void clear() { str = nullptr; len = 0; }
+
+ /** @} */
+
+public:
+
+ /** @name Construction from non-const characters */
+ /** @{ */
+
+ // when the char type is const, allow construction and assignment from non-const chars
+
+ /** only available when the char type is const */
+ template<size_t N, class U=NCC_> explicit basic_substring(C4_NC2C(U) (&s_)[N]) { str = s_; len = N-1; }
+ /** only available when the char type is const */
+ template< class U=NCC_> basic_substring(C4_NC2C(U) *s_, size_t len_) { str = s_; len = len_; }
+ /** only available when the char type is const */
+ template< class U=NCC_> basic_substring(C4_NC2C(U) *beg_, C4_NC2C(U) *end_) { C4_ASSERT(end_ >= beg_); str = beg_; len = end_ - beg_; }
+
+ /** only available when the char type is const */
+ template<size_t N, class U=NCC_> void assign(C4_NC2C(U) (&s_)[N]) { str = s_; len = N-1; }
+ /** only available when the char type is const */
+ template< class U=NCC_> void assign(C4_NC2C(U) *s_, size_t len_) { str = s_; len = len_; }
+ /** only available when the char type is const */
+ template< class U=NCC_> void assign(C4_NC2C(U) *beg_, C4_NC2C(U) *end_) { C4_ASSERT(end_ >= beg_); str = beg_; len = end_ - beg_; }
+
+ /** only available when the char type is const */
+ template<size_t N, class U=NCC_>
+ basic_substring& operator=(C4_NC2C(U) (&s_)[N]) { str = s_; len = N-1; return *this; }
+
+ /** @} */
+
+public:
+
+ /** @name Standard accessor methods */
+ /** @{ */
+
+ bool has_str() const { return ! empty() && str[0] != C(0); }
+ bool empty() const { return (len == 0 || str == nullptr); }
+ bool not_empty() const { return (len != 0 && str != nullptr); }
+ size_t size() const { return len; }
+
+ iterator begin() { return str; }
+ iterator end () { return str + len; }
+
+ const_iterator begin() const { return str; }
+ const_iterator end () const { return str + len; }
+
+ C * data() { return str; }
+ C const* data() const { return str; }
+
+ inline C & operator[] (size_t i) { C4_ASSERT(i >= 0 && i < len); return str[i]; }
+ inline C const& operator[] (size_t i) const { C4_ASSERT(i >= 0 && i < len); return str[i]; }
+
+ inline C & front() { C4_ASSERT(len > 0 && str != nullptr); return *str; }
+ inline C const& front() const { C4_ASSERT(len > 0 && str != nullptr); return *str; }
+
+ inline C & back() { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); }
+ inline C const& back() const { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); }
+
+ /** @} */
+
+public:
+
+ /** @name Comparison methods */
+ /** @{ */
+
+ int compare(C const c) const
+ {
+ C4_XASSERT((str != nullptr) || len == 0);
+ if( ! len)
+ return -1;
+ if(*str == c)
+ return static_cast<int>(len - 1);
+ return *str - c;
+ }
+
+ int compare(const char *that, size_t sz) const
+ {
+ C4_XASSERT(that || sz == 0);
+ C4_XASSERT(str || len == 0);
+ if(C4_LIKELY(str && that))
+ {
+ int ret = strncmp(str, that, len < sz ? len : sz);
+ if(ret == 0 && len != sz)
+ ret = len < sz ? -1 : 1;
+ return ret;
+ }
+ if((!str && !that) || (len == sz))
+ {
+ C4_XASSERT(len == 0 && sz == 0);
+ return 0;
+ }
+ return len < sz ? -1 : 1;
+ }
+
+ C4_ALWAYS_INLINE int compare(ro_substr const that) const { return this->compare(that.str, that.len); }
+
+ C4_ALWAYS_INLINE bool operator== (std::nullptr_t) const { return str == nullptr || len == 0; }
+ C4_ALWAYS_INLINE bool operator!= (std::nullptr_t) const { return str != nullptr || len == 0; }
+
+ C4_ALWAYS_INLINE bool operator== (C const c) const { return this->compare(c) == 0; }
+ C4_ALWAYS_INLINE bool operator!= (C const c) const { return this->compare(c) != 0; }
+ C4_ALWAYS_INLINE bool operator< (C const c) const { return this->compare(c) < 0; }
+ C4_ALWAYS_INLINE bool operator> (C const c) const { return this->compare(c) > 0; }
+ C4_ALWAYS_INLINE bool operator<= (C const c) const { return this->compare(c) <= 0; }
+ C4_ALWAYS_INLINE bool operator>= (C const c) const { return this->compare(c) >= 0; }
+
+ template<class U> C4_ALWAYS_INLINE bool operator== (basic_substring<U> const that) const { return this->compare(that) == 0; }
+ template<class U> C4_ALWAYS_INLINE bool operator!= (basic_substring<U> const that) const { return this->compare(that) != 0; }
+ template<class U> C4_ALWAYS_INLINE bool operator< (basic_substring<U> const that) const { return this->compare(that) < 0; }
+ template<class U> C4_ALWAYS_INLINE bool operator> (basic_substring<U> const that) const { return this->compare(that) > 0; }
+ template<class U> C4_ALWAYS_INLINE bool operator<= (basic_substring<U> const that) const { return this->compare(that) <= 0; }
+ template<class U> C4_ALWAYS_INLINE bool operator>= (basic_substring<U> const that) const { return this->compare(that) >= 0; }
+
+ template<size_t N> C4_ALWAYS_INLINE bool operator== (const char (&that)[N]) const { return this->compare(that, N-1) == 0; }
+ template<size_t N> C4_ALWAYS_INLINE bool operator!= (const char (&that)[N]) const { return this->compare(that, N-1) != 0; }
+ template<size_t N> C4_ALWAYS_INLINE bool operator< (const char (&that)[N]) const { return this->compare(that, N-1) < 0; }
+ template<size_t N> C4_ALWAYS_INLINE bool operator> (const char (&that)[N]) const { return this->compare(that, N-1) > 0; }
+ template<size_t N> C4_ALWAYS_INLINE bool operator<= (const char (&that)[N]) const { return this->compare(that, N-1) <= 0; }
+ template<size_t N> C4_ALWAYS_INLINE bool operator>= (const char (&that)[N]) const { 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) */
+ inline bool is_sub(ro_substr const that) const
+ {
+ return that.is_super(*this);
+ }
+
+ /** true if that is a substring of *this (ie, from the same buffer) */
+ inline bool is_super(ro_substr const that) const
+ {
+ if(C4_UNLIKELY(len == 0))
+ {
+ return that.len == 0 && that.str == str && str != nullptr;
+ }
+ return that.begin() >= begin() && that.end() <= end();
+ }
+
+ /** true if there is overlap of at least one element between that and *this */
+ inline bool overlaps(ro_substr const that) const
+ {
+ // thanks @timwynants
+ return (that.end() > begin() && that.begin() < end());
+ }
+
+public:
+
+ /** return [first,len[ */
+ basic_substring sub(size_t first) const
+ {
+ C4_ASSERT(first >= 0 && first <= len);
+ return basic_substring(str + first, len - first);
+ }
+
+ /** return [first,first+num[. If num==npos, return [first,len[ */
+ basic_substring sub(size_t first, size_t num) const
+ {
+ 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[ */
+ basic_substring range(size_t first, size_t last=npos) const
+ {
+ 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 [0,num[*/
+ basic_substring first(size_t num) const
+ {
+ return sub(0, num);
+ }
+
+ /** return [len-num,len[*/
+ basic_substring last(size_t num) const
+ {
+ if(num == npos)
+ return *this;
+ return sub(len - num);
+ }
+
+ /** 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. */
+ basic_substring offs(size_t left, size_t right) const
+ {
+ 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+include_pos[ */
+ basic_substring left_of(size_t pos, bool include_pos=false) const
+ {
+ if(pos == npos)
+ return *this;
+ return first(pos + include_pos);
+ }
+
+ /** return [pos+!include_pos, len[ */
+ basic_substring right_of(size_t pos, bool include_pos=false) const
+ {
+ if(pos == npos)
+ return sub(len, 0);
+ return sub(pos + !include_pos);
+ }
+
+public:
+
+ /** given @p subs a substring of the current string, get the
+ * portion of the current string to the left of it */
+ basic_substring left_of(ro_substr const subs) const
+ {
+ 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<size_t>(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 */
+ basic_substring right_of(ro_substr const subs) const
+ {
+ 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<size_t>(sse - b), static_cast<size_t>(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 */
+ /** @{ */
+
+ inline size_t find(const C c, size_t start_pos=0) const
+ {
+ return first_of(c, start_pos);
+ }
+ inline 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 */
+ inline 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 */
+ inline 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 */
+ inline 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 */
+ inline 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;
+ inline 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<class It>
+ 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, size_t start=0) 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, 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;
+ }
+
+ size_t first_not_of(ro_substr chars, size_t start=0) 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, 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)
+ {
+ 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 = (ne.str[0] == '+') ? 1 : 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 = (ne.str[0] == '+' || ne.str[0] == '-') ? 1 : 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(first_of_any("0x", "0X")) // hexadecimal
+ {
+ skip_start += 2;
+ if(len == skip_start)
+ return first(0);
+ for(size_t i = skip_start; i < len; ++i)
+ {
+ if( ! _is_hex_char(str[i]))
+ return _is_delim_char(str[i]) ? first(i) : first(0);
+ }
+ }
+ else if(first_of_any("0o", "0O")) // octal
+ {
+ skip_start += 2;
+ if(len == skip_start)
+ return first(0);
+ for(size_t i = skip_start; i < len; ++i)
+ {
+ char c = str[i];
+ if(c < '0' || c > '7')
+ return _is_delim_char(str[i]) ? first(i) : first(0);
+ }
+ }
+ else if(first_of_any("0b", "0B")) // binary
+ {
+ skip_start += 2;
+ if(len == skip_start)
+ return first(0);
+ for(size_t i = skip_start; i < len; ++i)
+ {
+ char c = str[i];
+ if(c != '0' && c != '1')
+ return _is_delim_char(c) ? first(i) : first(0);
+ }
+ }
+ else // otherwise, decimal
+ {
+ if(len == skip_start)
+ return first(0);
+ for(size_t i = skip_start; i < len; ++i)
+ {
+ char c = str[i];
+ if(c < '0' || c > '9')
+ return _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;
+ size_t skip_start = (ne.str[0] == '+' || ne.str[0] == '-') ? 1 : 0;
+ if(ne.first_of_any("0x", "0X")) // hexadecimal
+ {
+ skip_start += 2;
+ if(ne.len == skip_start)
+ return ne.first(0);
+ for(size_t i = skip_start; i < ne.len; ++i)
+ {
+ char c = ne.str[i];
+ if(( ! _is_hex_char(c)) && c != '.' && c != 'p' && c != 'P')
+ {
+ if(c == '-' || c == '+')
+ {
+ // we can also have a sign for the exponent
+ if(i > 1 && (ne[i-1] == 'p' || ne[i-1] == 'P'))
+ {
+ continue;
+ }
+ }
+ return _is_delim_char(c) ? ne.first(i) : ne.first(0);
+ }
+ }
+ }
+ else if(ne.first_of_any("0b", "0B")) // binary
+ {
+ skip_start += 2;
+ if(ne.len == skip_start)
+ return ne.first(0);
+ for(size_t i = skip_start; i < ne.len; ++i)
+ {
+ char c = ne.str[i];
+ if(c != '0' && c != '1' && c != '.')
+ {
+ return _is_delim_char(c) ? ne.first(i) : ne.first(0);
+ }
+ }
+ }
+ else if(ne.first_of_any("0o", "0O")) // octal
+ {
+ skip_start += 2;
+ if(ne.len == skip_start)
+ return ne.first(0);
+ for(size_t i = skip_start; i < ne.len; ++i)
+ {
+ char c = ne.str[i];
+ if((c < '0' || c > '7') && c != '.')
+ {
+ return _is_delim_char(c) ? ne.first(i) : ne.first(0);
+ }
+ }
+ }
+ else // assume decimal
+ {
+ if(ne.len == skip_start)
+ return ne.first(0);
+ for(size_t i = skip_start; i < ne.len; ++i)
+ {
+ char c = ne.str[i];
+ if((c < '0' || c > '9') && (c != '.' && c != 'e' && c != 'E'))
+ {
+ if(c == '-' || c == '+')
+ {
+ // we can also have a sign for the exponent
+ if(i > 1 && (ne[i-1] == 'e' || ne[i-1] == 'E'))
+ {
+ continue;
+ }
+ }
+ else if(i == skip_start)
+ {
+ if(c == 'i')
+ {
+ if(ne.len >= skip_start + 8 && ne.sub(skip_start, 8) == "infinity")
+ return _is_delim_char(ne.str[skip_start + 8]) ? ne.first(skip_start + 8) : ne.first(0);
+ else if(ne.len >= skip_start + 3 && ne.sub(skip_start, 3) == "inf")
+ return _is_delim_char(ne.str[skip_start + 3]) ? ne.first(skip_start + 3) : ne.first(0);
+ else
+ return ne.first(0);
+ }
+ else if(c == 'n')
+ {
+ if(ne.len >= skip_start + 3 && ne.sub(skip_start, 3) == "nan")
+ return _is_delim_char(ne.str[skip_start + 3]) ? ne.first(skip_start + 3) : ne.first(0);
+ else
+ return ne.first(0);
+ }
+ else
+ {
+ return ne.first(0);
+ }
+ }
+ else
+ {
+ return _is_delim_char(c) ? ne.first(i) : ne.first(0);
+ }
+ }
+ }
+ }
+ return ne;
+ }
+
+ /** true if the character is a delimiter character *at the end* */
+ static constexpr C4_ALWAYS_INLINE bool _is_delim_char(char c) noexcept
+ {
+ return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\0'
+ || c == ']' || c == ')' || c == '}'
+ || c == ',' || c == ';';
+ }
+
+ /** true if the character is in [0-9a-fA-F] */
+ static constexpr C4_ALWAYS_INLINE bool _is_hex_char(char c) noexcept
+ {
+ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+ }
+
+ /** true if the character is in [0-9a-fA-F] */
+ static constexpr C4_ALWAYS_INLINE bool _is_oct_char(char c) noexcept
+ {
+ return (c >= '0' && c <= '7');
+ }
+
+ /** @} */
+
+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, e = len; i < e; 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 && !empty() && 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; }
+
+ 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<C>(::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<C>(::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);
+ 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<size_t>(last-first));
+ }
+
+ /** 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<size_t>(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
+ * @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<size_t>((last) - (first)); \
+ if(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
+#undef C4_REQUIRE_RO
+#undef C4_NC2C
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** Because of a C++ limitation, substr cannot provide simultaneous
+ * overloads for constructing from a char[N] and a char*; the latter
+ * will always be chosen by the compiler. So this specialization is
+ * provided to simplify obtaining a substr from a char*. Being a
+ * function has the advantage of highlighting the strlen() cost.
+ *
+ * @see to_csubstr
+ * @see For a more detailed explanation on why the overloads cannot
+ * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */
+inline substr to_substr(char *s)
+{
+ return substr(s, s ? strlen(s) : 0);
+}
+
+/** Because of a C++ limitation, substr cannot provide simultaneous
+ * overloads for constructing from a char[N] and a char*; the latter
+ * will always be chosen by the compiler. So this specialization is
+ * provided to simplify obtaining a substr from a char*. Being a
+ * function has the advantage of highlighting the strlen() cost.
+ *
+ * @see to_substr
+ * @see For a more detailed explanation on why the overloads cannot
+ * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */
+inline csubstr to_csubstr(char *s)
+{
+ return csubstr(s, s ? strlen(s) : 0);
+}
+
+/** Because of a C++ limitation, substr cannot provide simultaneous
+ * overloads for constructing from a const char[N] and a const char*;
+ * the latter will always be chosen by the compiler. So this
+ * specialization is provided to simplify obtaining a substr from a
+ * char*. Being a function has the advantage of highlighting the
+ * strlen() cost.
+ *
+ * @overload to_csubstr
+ * @see to_substr
+ * @see For a more detailed explanation on why the overloads cannot
+ * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */
+inline csubstr to_csubstr(const char *s)
+{
+ return csubstr(s, s ? strlen(s) : 0);
+}
+
+
+/** neutral version for use in generic code */
+inline csubstr to_csubstr(csubstr s)
+{
+ return s;
+}
+
+/** neutral version for use in generic code */
+inline csubstr to_csubstr(substr s)
+{
+ return s;
+}
+
+/** neutral version for use in generic code */
+inline substr to_substr(substr s)
+{
+ return s;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+template<typename C, size_t N> inline bool operator== (const C (&s)[N], basic_substring<C> const that) { return that.compare(s) == 0; }
+template<typename C, size_t N> inline bool operator!= (const C (&s)[N], basic_substring<C> const that) { return that.compare(s) != 0; }
+template<typename C, size_t N> inline bool operator< (const C (&s)[N], basic_substring<C> const that) { return that.compare(s) > 0; }
+template<typename C, size_t N> inline bool operator> (const C (&s)[N], basic_substring<C> const that) { return that.compare(s) < 0; }
+template<typename C, size_t N> inline bool operator<= (const C (&s)[N], basic_substring<C> const that) { return that.compare(s) >= 0; }
+template<typename C, size_t N> inline bool operator>= (const C (&s)[N], basic_substring<C> const that) { return that.compare(s) <= 0; }
+
+template<typename C> inline bool operator== (C const c, basic_substring<C> const that) { return that.compare(c) == 0; }
+template<typename C> inline bool operator!= (C const c, basic_substring<C> const that) { return that.compare(c) != 0; }
+template<typename C> inline bool operator< (C const c, basic_substring<C> const that) { return that.compare(c) > 0; }
+template<typename C> inline bool operator> (C const c, basic_substring<C> const that) { return that.compare(c) < 0; }
+template<typename C> inline bool operator<= (C const c, basic_substring<C> const that) { return that.compare(c) >= 0; }
+template<typename C> inline bool operator>= (C const c, basic_substring<C> const that) { return that.compare(c) <= 0; }
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** @define 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<class OStream, class C>
+inline OStream& operator<< (OStream& os, basic_substring<C> s)
+{
+ os.write(s.str, s.len);
+ return os;
+}
+
+// this causes ambiguity
+///** this is used by google test */
+//template<class OStream, class C>
+//inline void PrintTo(basic_substring<C> 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_
+
+#ifdef _MSC_VER
+# pragma warning(push)
+# 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"
+#elif defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wuseless-cast"
+#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
+//
+// 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_FAST_FLOAT_H
+#define FASTFLOAT_FAST_FLOAT_H
+
+#include <system_error>
+
+namespace fast_float {
+enum chars_format {
+ scientific = 1<<0,
+ fixed = 1<<2,
+ hex = 1<<3,
+ general = fixed | scientific
+};
+
+
+struct from_chars_result {
+ const char *ptr;
+ std::errc ec;
+};
+
+struct parse_options {
+ constexpr explicit parse_options(chars_format fmt = chars_format::general,
+ char dot = '.')
+ : format(fmt), decimal_point(dot) {}
+
+ /** Which number formats are accepted */
+ chars_format format;
+ /** The character used as decimal point */
+ char decimal_point;
+};
+
+/**
+ * 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 allowe the fixed point and scientific notation respectively.
+ * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`.
+ */
+template<typename T>
+from_chars_result from_chars(const char *first, const char *last,
+ T &value, chars_format fmt = chars_format::general) noexcept;
+
+/**
+ * Like from_chars, but accepts an `options` argument to govern number parsing.
+ */
+template<typename T>
+from_chars_result from_chars_advanced(const char *first, const char *last,
+ T &value, parse_options options) noexcept;
+
+}
+#endif // FASTFLOAT_FAST_FLOAT_H
+
+#ifndef FASTFLOAT_FLOAT_COMMON_H
+#define FASTFLOAT_FLOAT_COMMON_H
+
+#include <cfloat>
+//included above:
+//#include <cstdint>
+#include <cassert>
+//included above:
+//#include <cstring>
+//included above:
+//#include <type_traits>
+
+#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(__EMSCRIPTEN__))
+#define FASTFLOAT_64BIT
+#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \
+ || defined(__arm__) || defined(_M_ARM) \
+ || defined(__MINGW32__))
+#define FASTFLOAT_32BIT
+#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
+ #elif SIZE_MAX == 0xffffffffffffffff
+ #define FASTFLOAT_64BIT
+ #else
+ #error Unknown platform (not 32-bit, not 64-bit?)
+ #endif
+#endif
+
+#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__))
+#include <intrin.h>
+#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 <machine/endian.h>
+#elif defined(sun) || defined(__sun)
+#include <sys/byteorder.h>
+#else
+#include <endian.h>
+#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
+
+#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) { if (!(x)) abort(); }
+#endif
+
+#ifndef FASTFLOAT_DEBUG_ASSERT
+//included above:
+//#include <cassert>
+#define FASTFLOAT_DEBUG_ASSERT(x) assert(x)
+#endif
+
+// rust style `try!()` macro, or `?` operator
+#define FASTFLOAT_TRY(x) { if (!(x)) return false; }
+
+namespace fast_float {
+
+// Compares two ASCII strings in a case insensitive manner.
+inline bool fastfloat_strncasecmp(const char *input1, const char *input2,
+ size_t length) {
+ char running_diff{0};
+ for (size_t i = 0; i < length; i++) {
+ running_diff |= (input1[i] ^ input2[i]);
+ }
+ return (running_diff == 0) || (running_diff == 32);
+}
+
+#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 <typename T>
+struct span {
+ const T* ptr;
+ size_t length;
+ span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {}
+ span() : ptr(nullptr), length(0) {}
+
+ constexpr size_t len() const noexcept {
+ return length;
+ }
+
+ const T& operator[](size_t index) const noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ return ptr[index];
+ }
+};
+
+struct value128 {
+ uint64_t low;
+ uint64_t high;
+ value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {}
+ value128() : low(0), high(0) {}
+};
+
+/* result might be undefined when input_num is zero */
+fastfloat_really_inline int leading_zeroes(uint64_t input_num) {
+ assert(input_num > 0);
+#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
+ 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;
+ #endif
+#else
+ return __builtin_clzll(input_num);
+#endif
+}
+
+#ifdef FASTFLOAT_32BIT
+
+// slow emulation routine for 32-bit
+fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) {
+ return x * (uint64_t)y;
+}
+
+// slow emulation routine for 32-bit
+#if !defined(__MINGW64__)
+fastfloat_really_inline uint64_t _umul128(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 = !!(adbc < ad);
+ uint64_t lo = bd + (adbc << 32);
+ *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) +
+ (adbc_carry << 32) + !!(lo < bd);
+ return lo;
+}
+#endif // !__MINGW64__
+
+#endif // FASTFLOAT_32BIT
+
+
+// compute 64-bit a*b
+fastfloat_really_inline value128 full_multiplication(uint64_t a,
+ uint64_t b) {
+ value128 answer;
+#ifdef _M_ARM64
+ // ARM64 has native support for 64-bit multiplications, no need to emulate
+ answer.high = __umulh(a, b);
+ answer.low = a * b;
+#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__))
+ answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64
+#elif defined(FASTFLOAT_64BIT)
+ __uint128_t r = ((__uint128_t)a) * b;
+ answer.low = uint64_t(r);
+ answer.high = uint64_t(r >> 64);
+#else
+ #error Not implemented
+#endif
+ return answer;
+}
+
+struct adjusted_mantissa {
+ uint64_t mantissa{0};
+ int32_t power2{0}; // a negative value indicates an invalid result
+ adjusted_mantissa() = default;
+ bool operator==(const adjusted_mantissa &o) const {
+ return mantissa == o.mantissa && power2 == o.power2;
+ }
+ bool operator!=(const adjusted_mantissa &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;
+
+constexpr static double powers_of_ten_double[] = {
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
+ 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
+constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5,
+ 1e6, 1e7, 1e8, 1e9, 1e10};
+
+template <typename T> struct binary_format {
+ using equiv_uint = typename std::conditional<sizeof(T) == 4, uint32_t, uint64_t>::type;
+
+ static inline constexpr int mantissa_explicit_bits();
+ static inline constexpr int minimum_exponent();
+ static inline constexpr int infinite_power();
+ static inline constexpr int sign_index();
+ static inline constexpr int min_exponent_fast_path();
+ static inline constexpr int max_exponent_fast_path();
+ static inline constexpr int max_exponent_round_to_even();
+ static inline constexpr int min_exponent_round_to_even();
+ static inline constexpr uint64_t max_mantissa_fast_path();
+ static inline constexpr int largest_power_of_ten();
+ static inline constexpr int smallest_power_of_ten();
+ static inline constexpr T exact_power_of_ten(int64_t power);
+ static inline constexpr size_t max_digits();
+ static inline constexpr equiv_uint exponent_mask();
+ static inline constexpr equiv_uint mantissa_mask();
+ static inline constexpr equiv_uint hidden_bit_mask();
+};
+
+template <> inline constexpr int binary_format<double>::mantissa_explicit_bits() {
+ return 52;
+}
+template <> inline constexpr int binary_format<float>::mantissa_explicit_bits() {
+ return 23;
+}
+
+template <> inline constexpr int binary_format<double>::max_exponent_round_to_even() {
+ return 23;
+}
+
+template <> inline constexpr int binary_format<float>::max_exponent_round_to_even() {
+ return 10;
+}
+
+template <> inline constexpr int binary_format<double>::min_exponent_round_to_even() {
+ return -4;
+}
+
+template <> inline constexpr int binary_format<float>::min_exponent_round_to_even() {
+ return -17;
+}
+
+template <> inline constexpr int binary_format<double>::minimum_exponent() {
+ return -1023;
+}
+template <> inline constexpr int binary_format<float>::minimum_exponent() {
+ return -127;
+}
+
+template <> inline constexpr int binary_format<double>::infinite_power() {
+ return 0x7FF;
+}
+template <> inline constexpr int binary_format<float>::infinite_power() {
+ return 0xFF;
+}
+
+template <> inline constexpr int binary_format<double>::sign_index() { return 63; }
+template <> inline constexpr int binary_format<float>::sign_index() { return 31; }
+
+template <> inline constexpr int binary_format<double>::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<float>::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<double>::max_exponent_fast_path() {
+ return 22;
+}
+template <> inline constexpr int binary_format<float>::max_exponent_fast_path() {
+ return 10;
+}
+
+template <> inline constexpr uint64_t binary_format<double>::max_mantissa_fast_path() {
+ return uint64_t(2) << mantissa_explicit_bits();
+}
+template <> inline constexpr uint64_t binary_format<float>::max_mantissa_fast_path() {
+ return uint64_t(2) << mantissa_explicit_bits();
+}
+
+template <>
+inline constexpr double binary_format<double>::exact_power_of_ten(int64_t power) {
+ return powers_of_ten_double[power];
+}
+template <>
+inline constexpr float binary_format<float>::exact_power_of_ten(int64_t power) {
+
+ return powers_of_ten_float[power];
+}
+
+
+template <>
+inline constexpr int binary_format<double>::largest_power_of_ten() {
+ return 308;
+}
+template <>
+inline constexpr int binary_format<float>::largest_power_of_ten() {
+ return 38;
+}
+
+template <>
+inline constexpr int binary_format<double>::smallest_power_of_ten() {
+ return -342;
+}
+template <>
+inline constexpr int binary_format<float>::smallest_power_of_ten() {
+ return -65;
+}
+
+template <> inline constexpr size_t binary_format<double>::max_digits() {
+ return 769;
+}
+template <> inline constexpr size_t binary_format<float>::max_digits() {
+ return 114;
+}
+
+template <> inline constexpr binary_format<float>::equiv_uint
+ binary_format<float>::exponent_mask() {
+ return 0x7F800000;
+}
+template <> inline constexpr binary_format<double>::equiv_uint
+ binary_format<double>::exponent_mask() {
+ return 0x7FF0000000000000;
+}
+
+template <> inline constexpr binary_format<float>::equiv_uint
+ binary_format<float>::mantissa_mask() {
+ return 0x007FFFFF;
+}
+template <> inline constexpr binary_format<double>::equiv_uint
+ binary_format<double>::mantissa_mask() {
+ return 0x000FFFFFFFFFFFFF;
+}
+
+template <> inline constexpr binary_format<float>::equiv_uint
+ binary_format<float>::hidden_bit_mask() {
+ return 0x00800000;
+}
+template <> inline constexpr binary_format<double>::equiv_uint
+ binary_format<double>::hidden_bit_mask() {
+ return 0x0010000000000000;
+}
+
+template<typename T>
+fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) {
+ uint64_t word = am.mantissa;
+ word |= uint64_t(am.power2) << binary_format<T>::mantissa_explicit_bits();
+ word = negative
+ ? word | (uint64_t(1) << binary_format<T>::sign_index()) : word;
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ if (std::is_same<T, float>::value) {
+ ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian
+ } else {
+ ::memcpy(&value, &word, sizeof(T));
+ }
+#else
+ // For little-endian systems:
+ ::memcpy(&value, &word, sizeof(T));
+#endif
+}
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_ASCII_NUMBER_H
+#define FASTFLOAT_ASCII_NUMBER_H
+
+//included above:
+//#include <cctype>
+//included above:
+//#include <cstdint>
+//included above:
+//#include <cstring>
+#include <iterator>
+
+
+namespace fast_float {
+
+// Next function can be micro-optimized, but compilers are entirely
+// able to optimize it well.
+fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; }
+
+fastfloat_really_inline 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;
+}
+
+fastfloat_really_inline uint64_t read_u64(const char *chars) {
+ 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;
+}
+
+fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) {
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ // Need to read as-if the number was in little-endian order.
+ val = byteswap(val);
+#endif
+ ::memcpy(chars, &val, sizeof(uint64_t));
+}
+
+// credit @aqrit
+fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) {
+ const uint64_t mask = 0x000000FF000000FF;
+ const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
+ const uint64_t 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);
+}
+
+fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept {
+ return parse_eight_digits_unrolled(read_u64(chars));
+}
+
+// credit @aqrit
+fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept {
+ return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
+ 0x8080808080808080));
+}
+
+fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept {
+ return is_made_of_eight_digits_fast(read_u64(chars));
+}
+
+typedef span<const char> byte_span;
+
+struct parsed_number_string {
+ int64_t exponent{0};
+ uint64_t mantissa{0};
+ const char *lastmatch{nullptr};
+ bool negative{false};
+ bool valid{false};
+ bool too_many_digits{false};
+ // contains the range of the significant digits
+ byte_span integer{}; // non-nullable
+ byte_span fraction{}; // nullable
+};
+
+// Assuming that you use no more than 19 digits, this will
+// parse an ASCII string.
+fastfloat_really_inline
+parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept {
+ const chars_format fmt = options.format;
+ const char decimal_point = options.decimal_point;
+
+ parsed_number_string answer;
+ answer.valid = false;
+ answer.too_many_digits = false;
+ answer.negative = (*p == '-');
+ if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+ ++p;
+ if (p == pend) {
+ return answer;
+ }
+ if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot
+ return answer;
+ }
+ }
+ const char *const start_digits = p;
+
+ uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
+
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ // a multiplication by 10 is cheaper than an arbitrary integer
+ // multiplication
+ i = 10 * i +
+ uint64_t(*p - '0'); // might overflow, we will handle the overflow later
+ ++p;
+ }
+ const char *const end_of_integer_part = p;
+ int64_t digit_count = int64_t(end_of_integer_part - start_digits);
+ answer.integer = byte_span(start_digits, size_t(digit_count));
+ int64_t exponent = 0;
+ if ((p != pend) && (*p == decimal_point)) {
+ ++p;
+ const char* 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.
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '0');
+ ++p;
+ i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
+ }
+ exponent = before - p;
+ answer.fraction = byte_span(before, size_t(p - before));
+ digit_count -= exponent;
+ }
+ // we must have encountered at least one integer!
+ if (digit_count == 0) {
+ return answer;
+ }
+ int64_t exp_number = 0; // explicit exponential part
+ if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) {
+ const char * location_of_e = p;
+ ++p;
+ bool neg_exp = false;
+ if ((p != pend) && ('-' == *p)) {
+ neg_exp = true;
+ ++p;
+ } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
+ ++p;
+ }
+ if ((p == pend) || !is_integer(*p)) {
+ if(!(fmt & chars_format::fixed)) {
+ // We are in error.
+ return answer;
+ }
+ // Otherwise, we will be ignoring the 'e'.
+ p = location_of_e;
+ } else {
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '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((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; }
+ }
+ 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.
+ const char *start = start_digits;
+ while ((start != pend) && (*start == '0' || *start == decimal_point)) {
+ if(*start == '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;
+ const char* int_end = p + answer.integer.len();
+ const uint64_t minimal_nineteen_digit_integer{1000000000000000000};
+ while((i < minimal_nineteen_digit_integer) && (p != int_end)) {
+ i = i * 10 + uint64_t(*p - '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;
+ const char* frac_end = p + answer.fraction.len();
+ while((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
+ i = i * 10 + uint64_t(*p - '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;
+}
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_FAST_TABLE_H
+#define FASTFLOAT_FAST_TABLE_H
+
+//included above:
+//#include <cstdint>
+
+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 <class unused = void>
+struct powers_template {
+
+constexpr static int smallest_power_of_five = binary_format<double>::smallest_power_of_ten();
+constexpr static int largest_power_of_five = binary_format<double>::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.
+static const uint64_t power_of_five_128[number_of_entries];
+};
+
+template <class unused>
+const uint64_t powers_template<unused>::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,};
+using powers = powers_template<>;
+
+}
+
+#endif
+
+#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H
+#define FASTFLOAT_DECIMAL_TO_BINARY_H
+
+//included above:
+//#include <cfloat>
+#include <cinttypes>
+#include <cmath>
+//included above:
+//#include <cstdint>
+#include <cstdlib>
+//included above:
+//#include <cstring>
+
+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 <int bit_precision>
+fastfloat_really_inline
+value128 compute_product_approximation(int64_t q, uint64_t w) {
+ const int 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 <typename binary>
+fastfloat_really_inline
+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 <typename binary>
+fastfloat_really_inline
+adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept {
+ int lz = leading_zeroes(w);
+ w <<= lz;
+ value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
+ return compute_error_scaled<binary>(q, product.high, lz);
+}
+
+// w * 10 ** q
+// The returned value should be a valid ieee64 number that simply need 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 <typename binary>
+fastfloat_really_inline
+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<binary::mantissa_explicit_bits() + 3>(q, w);
+ if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further
+ // In some very rare cases, this could happen, in which case we might need a more accurate
+ // computation that what we can provide cheaply. This is very, very unlikely.
+ //
+ const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0,
+ // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation.
+ if(!inside_safe_exponent) {
+ return compute_error_scaled<binary>(q, product.high, lz);
+ }
+ }
+ // 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);
+
+ answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3);
+
+ 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.
+ 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 << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == 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 <algorithm>
+//included above:
+//#include <cstdint>
+//included above:
+//#include <climits>
+//included above:
+//#include <cstring>
+
+
+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
+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> 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 <uint16_t size>
+struct stackvec {
+ limb data[size];
+ // we never need more than 150 limbs
+ uint16_t length{0};
+
+ stackvec() = default;
+ stackvec(const stackvec &) = delete;
+ stackvec &operator=(const stackvec &) = delete;
+ stackvec(stackvec &&) = delete;
+ stackvec &operator=(stackvec &&other) = delete;
+
+ // create stack vector from existing limb span.
+ stackvec(limb_span s) {
+ FASTFLOAT_ASSERT(try_extend(s));
+ }
+
+ limb& operator[](size_t index) noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ return data[index];
+ }
+ const limb& operator[](size_t index) const noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ return data[index];
+ }
+ // index from the end of the container
+ 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.
+ 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
+ void push_unchecked(limb value) noexcept {
+ data[length] = value;
+ length++;
+ }
+ // append item to vector, returning if item was added
+ 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
+ void extend_unchecked(limb_span s) noexcept {
+ limb* ptr = data + length;
+ ::memcpy((void*)ptr, (const void*)s.ptr, sizeof(limb) * s.len());
+ set_len(len() + s.len());
+ }
+ // try to add items to the vector, returning if items were added
+ 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.
+ 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.
+ 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.
+ 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.
+ void normalize() noexcept {
+ while (len() > 0 && rindex(0) == 0) {
+ length--;
+ }
+ }
+};
+
+fastfloat_really_inline
+uint64_t empty_hi64(bool& truncated) noexcept {
+ truncated = false;
+ return 0;
+}
+
+fastfloat_really_inline
+uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept {
+ truncated = false;
+ int shl = leading_zeroes(r0);
+ return r0 << shl;
+}
+
+fastfloat_really_inline
+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
+uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept {
+ return uint64_hi64(r0, truncated);
+}
+
+fastfloat_really_inline
+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
+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
+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)
+ 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
+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 <uint16_t size>
+inline bool small_add_from(stackvec<size>& 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 <uint16_t size>
+fastfloat_really_inline bool small_add(stackvec<size>& vec, limb y) noexcept {
+ return small_add_from(vec, y, 0);
+}
+
+// multiply bigint by scalar value.
+template <uint16_t size>
+inline bool small_mul(stackvec<size>& 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 <uint16_t size>
+bool large_add_from(stackvec<size>& 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 <uint16_t size>
+fastfloat_really_inline bool large_add_from(stackvec<size>& x, limb_span y) noexcept {
+ return large_add_from(x, y, 0);
+}
+
+// grade-school multiplication algorithm
+template <uint16_t size>
+bool long_mul(stackvec<size>& x, limb_span y) noexcept {
+ limb_span xs = limb_span(x.data, x.len());
+ stackvec<size> 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<size> 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 <uint16_t size>
+bool large_mul(stackvec<size>& 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;
+}
+
+// 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 {
+ // storage of the limbs, in little-endian order.
+ stackvec<bigint_limbs> vec;
+
+ bigint(): vec() {}
+ bigint(const bigint &) = delete;
+ bigint &operator=(const bigint &) = delete;
+ bigint(bigint &&) = delete;
+ bigint &operator=(bigint &&other) = delete;
+
+ 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.
+ 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.
+ int compare(const bigint& 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.
+ 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.
+ 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;
+ const limb* src = vec.data;
+ ::memmove(dst, src, sizeof(limb) * 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.
+ 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.
+ 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.
+ int bit_length() const noexcept {
+ int lz = ctlz();
+ return int(limb_bits * vec.len()) - lz;
+ }
+
+ bool mul(limb y) noexcept {
+ return small_mul(vec, y);
+ }
+
+ bool add(limb y) noexcept {
+ return small_add(vec, y);
+ }
+
+ // multiply as if by 2 raised to a power.
+ bool pow2(uint32_t exp) noexcept {
+ return shl(exp);
+ }
+
+ // multiply as if by 5 raised to a power.
+ bool pow5(uint32_t exp) noexcept {
+ // multiply by a power of 5
+ 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
+ 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) {
+ FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp])));
+ }
+
+ return true;
+ }
+
+ // multiply as if by 10 raised to a power.
+ bool pow10(uint32_t exp) noexcept {
+ FASTFLOAT_TRY(pow5(exp));
+ return pow2(exp);
+ }
+};
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_ASCII_NUMBER_H
+#define FASTFLOAT_ASCII_NUMBER_H
+
+//included above:
+//#include <cctype>
+//included above:
+//#include <cstdint>
+//included above:
+//#include <cstring>
+//included above:
+//#include <iterator>
+
+
+namespace fast_float {
+
+// Next function can be micro-optimized, but compilers are entirely
+// able to optimize it well.
+fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; }
+
+fastfloat_really_inline 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;
+}
+
+fastfloat_really_inline uint64_t read_u64(const char *chars) {
+ 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;
+}
+
+fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) {
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ // Need to read as-if the number was in little-endian order.
+ val = byteswap(val);
+#endif
+ ::memcpy(chars, &val, sizeof(uint64_t));
+}
+
+// credit @aqrit
+fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) {
+ const uint64_t mask = 0x000000FF000000FF;
+ const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
+ const uint64_t 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);
+}
+
+fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept {
+ return parse_eight_digits_unrolled(read_u64(chars));
+}
+
+// credit @aqrit
+fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept {
+ return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
+ 0x8080808080808080));
+}
+
+fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept {
+ return is_made_of_eight_digits_fast(read_u64(chars));
+}
+
+typedef span<const char> byte_span;
+
+struct parsed_number_string {
+ int64_t exponent{0};
+ uint64_t mantissa{0};
+ const char *lastmatch{nullptr};
+ bool negative{false};
+ bool valid{false};
+ bool too_many_digits{false};
+ // contains the range of the significant digits
+ byte_span integer{}; // non-nullable
+ byte_span fraction{}; // nullable
+};
+
+// Assuming that you use no more than 19 digits, this will
+// parse an ASCII string.
+fastfloat_really_inline
+parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept {
+ const chars_format fmt = options.format;
+ const char decimal_point = options.decimal_point;
+
+ parsed_number_string answer;
+ answer.valid = false;
+ answer.too_many_digits = false;
+ answer.negative = (*p == '-');
+ if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+ ++p;
+ if (p == pend) {
+ return answer;
+ }
+ if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot
+ return answer;
+ }
+ }
+ const char *const start_digits = p;
+
+ uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
+
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ // a multiplication by 10 is cheaper than an arbitrary integer
+ // multiplication
+ i = 10 * i +
+ uint64_t(*p - '0'); // might overflow, we will handle the overflow later
+ ++p;
+ }
+ const char *const end_of_integer_part = p;
+ int64_t digit_count = int64_t(end_of_integer_part - start_digits);
+ answer.integer = byte_span(start_digits, size_t(digit_count));
+ int64_t exponent = 0;
+ if ((p != pend) && (*p == decimal_point)) {
+ ++p;
+ const char* 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.
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '0');
+ ++p;
+ i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
+ }
+ exponent = before - p;
+ answer.fraction = byte_span(before, size_t(p - before));
+ digit_count -= exponent;
+ }
+ // we must have encountered at least one integer!
+ if (digit_count == 0) {
+ return answer;
+ }
+ int64_t exp_number = 0; // explicit exponential part
+ if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) {
+ const char * location_of_e = p;
+ ++p;
+ bool neg_exp = false;
+ if ((p != pend) && ('-' == *p)) {
+ neg_exp = true;
+ ++p;
+ } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
+ ++p;
+ }
+ if ((p == pend) || !is_integer(*p)) {
+ if(!(fmt & chars_format::fixed)) {
+ // We are in error.
+ return answer;
+ }
+ // Otherwise, we will be ignoring the 'e'.
+ p = location_of_e;
+ } else {
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '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((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; }
+ }
+ 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.
+ const char *start = start_digits;
+ while ((start != pend) && (*start == '0' || *start == decimal_point)) {
+ if(*start == '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;
+ const char* int_end = p + answer.integer.len();
+ const uint64_t minimal_nineteen_digit_integer{1000000000000000000};
+ while((i < minimal_nineteen_digit_integer) && (p != int_end)) {
+ i = i * 10 + uint64_t(*p - '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;
+ const char* frac_end = p + answer.fraction.len();
+ while((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
+ i = i * 10 + uint64_t(*p - '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;
+}
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_DIGIT_COMPARISON_H
+#define FASTFLOAT_DIGIT_COMPARISON_H
+
+//included above:
+//#include <algorithm>
+//included above:
+//#include <cstdint>
+//included above:
+//#include <cstring>
+//included above:
+//#include <iterator>
+
+
+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.
+fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& 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 <typename T>
+fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept {
+ using equiv_uint = typename binary_format<T>::equiv_uint;
+ constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask();
+ constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask();
+ constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask();
+
+ adjusted_mantissa am;
+ int32_t bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent();
+ equiv_uint bits;
+ ::memcpy(&bits, &value, sizeof(T));
+ 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<T>::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 <typename T>
+fastfloat_really_inline 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 <typename T, typename callback>
+fastfloat_really_inline void round(adjusted_mantissa& am, callback cb) noexcept {
+ int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1;
+ if (-am.power2 >= mantissa_shift) {
+ // have a denormal float
+ int32_t shift = -am.power2 + 1;
+ cb(am, std::min<int32_t>(shift, 64));
+ // check for round-up: if rounding-nearest carried us to the hidden bit.
+ am.power2 = (am.mantissa < (uint64_t(1) << binary_format<T>::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<T>::mantissa_explicit_bits())) {
+ am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
+ am.power2++;
+ }
+
+ // check for infinite: we could have carried to an infinite power
+ am.mantissa &= ~(uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
+ if (am.power2 >= binary_format<T>::infinite_power()) {
+ am.power2 = binary_format<T>::infinite_power();
+ am.mantissa = 0;
+ }
+}
+
+template <typename callback>
+fastfloat_really_inline
+void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept {
+ uint64_t mask;
+ uint64_t halfway;
+ if (shift == 64) {
+ mask = UINT64_MAX;
+ } else {
+ mask = (uint64_t(1) << shift) - 1;
+ }
+ if (shift == 0) {
+ halfway = 0;
+ } else {
+ halfway = uint64_t(1) << (shift - 1);
+ }
+ uint64_t truncated_bits = am.mantissa & mask;
+ uint64_t is_above = truncated_bits > halfway;
+ uint64_t 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 void round_down(adjusted_mantissa& am, int32_t shift) noexcept {
+ if (shift == 64) {
+ am.mantissa = 0;
+ } else {
+ am.mantissa >>= shift;
+ }
+ am.power2 += shift;
+}
+
+fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept {
+ uint64_t val;
+ while (std::distance(first, last) >= 8) {
+ ::memcpy(&val, first, sizeof(uint64_t));
+ if (val != 0x3030303030303030) {
+ break;
+ }
+ first += 8;
+ }
+ while (first != last) {
+ if (*first != '0') {
+ break;
+ }
+ first++;
+ }
+}
+
+// determine if any non-zero digits were truncated.
+// all characters must be valid digits.
+fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept {
+ // do 8-bit optimizations, can just compare to 8 literal 0s.
+ uint64_t val;
+ while (std::distance(first, last) >= 8) {
+ ::memcpy(&val, first, sizeof(uint64_t));
+ if (val != 0x3030303030303030) {
+ return true;
+ }
+ first += 8;
+ }
+ while (first != last) {
+ if (*first != '0') {
+ return true;
+ }
+ first++;
+ }
+ return false;
+}
+
+fastfloat_really_inline bool is_truncated(byte_span s) noexcept {
+ return is_truncated(s.ptr, s.ptr + s.len());
+}
+
+fastfloat_really_inline
+void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept {
+ value = value * 100000000 + parse_eight_digits_unrolled(p);
+ p += 8;
+ counter += 8;
+ count += 8;
+}
+
+fastfloat_really_inline
+void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count) noexcept {
+ value = value * 10 + limb(*p - '0');
+ p++;
+ counter++;
+ count++;
+}
+
+fastfloat_really_inline
+void add_native(bigint& big, limb power, limb value) noexcept {
+ big.mul(power);
+ big.add(value);
+}
+
+fastfloat_really_inline 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
+inline void parse_mantissa(bigint& result, parsed_number_string& 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.
+ const char* p = num.integer.ptr;
+ const char* 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 <typename T>
+inline 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<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent();
+ answer.power2 = bigmant.bit_length() - 64 + bias;
+
+ round<T>(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 <typename T>
+inline 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<T>(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<T>(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 <typename T>
+inline adjusted_mantissa digit_comp(parsed_number_string& 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<T>::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<T>(bigmant, exponent);
+ } else {
+ return negative_digit_comp<T>(bigmant, am, exponent);
+ }
+}
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_PARSE_NUMBER_H
+#define FASTFLOAT_PARSE_NUMBER_H
+
+
+//included above:
+//#include <cmath>
+//included above:
+//#include <cstring>
+//included above:
+//#include <limits>
+//included above:
+//#include <system_error>
+
+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 <typename T>
+from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept {
+ from_chars_result answer;
+ answer.ptr = first;
+ answer.ec = std::errc(); // be optimistic
+ bool minusSign = false;
+ if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here
+ minusSign = true;
+ ++first;
+ }
+ if (last - first >= 3) {
+ if (fastfloat_strncasecmp(first, "nan", 3)) {
+ answer.ptr = (first += 3);
+ value = minusSign ? -std::numeric_limits<T>::quiet_NaN() : std::numeric_limits<T>::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 == '(') {
+ for(const char* ptr = first + 1; ptr != last; ++ptr) {
+ if (*ptr == ')') {
+ answer.ptr = ptr + 1; // valid nan(n-char-seq-opt)
+ break;
+ }
+ else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_'))
+ break; // forbidden char, not nan(n-char-seq-opt)
+ }
+ }
+ return answer;
+ }
+ if (fastfloat_strncasecmp(first, "inf", 3)) {
+ if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) {
+ answer.ptr = first + 8;
+ } else {
+ answer.ptr = first + 3;
+ }
+ value = minusSign ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity();
+ return answer;
+ }
+ }
+ answer.ec = std::errc::invalid_argument;
+ return answer;
+}
+
+} // namespace detail
+
+template<typename T>
+from_chars_result from_chars(const char *first, const char *last,
+ T &value, chars_format fmt /*= chars_format::general*/) noexcept {
+ return from_chars_advanced(first, last, value, parse_options{fmt});
+}
+
+template<typename T>
+from_chars_result from_chars_advanced(const char *first, const char *last,
+ T &value, parse_options options) noexcept {
+
+ static_assert (std::is_same<T, double>::value || std::is_same<T, float>::value, "only float and double are supported");
+
+
+ from_chars_result answer;
+ if (first == last) {
+ answer.ec = std::errc::invalid_argument;
+ answer.ptr = first;
+ return answer;
+ }
+ parsed_number_string pns = parse_number_string(first, last, options);
+ if (!pns.valid) {
+ return detail::parse_infnan(first, last, value);
+ }
+ answer.ec = std::errc(); // be optimistic
+ answer.ptr = pns.lastmatch;
+ // Next is Clinger's fast path.
+ if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && pns.mantissa <=binary_format<T>::max_mantissa_fast_path() && !pns.too_many_digits) {
+ value = T(pns.mantissa);
+ if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); }
+ else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); }
+ if (pns.negative) { value = -value; }
+ return answer;
+ }
+ adjusted_mantissa am = compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
+ if(pns.too_many_digits && am.power2 >= 0) {
+ if(am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) {
+ am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa);
+ }
+ }
+ // If we called compute_float<binary_format<T>>(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<T>(pns, am); }
+ to_float(pns.negative, am, value);
+ return answer;
+}
+
+} // 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 <cstddef>
+
+// forward declarations for std::vector
+#if defined(__GLIBCXX__) || defined(__GLIBCPP__) || defined(_MSC_VER)
+namespace std {
+template<typename> class allocator;
+template<typename T, typename Alloc> class vector;
+} // namespace std
+#elif defined(_LIBCPP_ABI_NAMESPACE)
+namespace std {
+inline namespace _LIBCPP_ABI_NAMESPACE {
+template<typename> class allocator;
+template<typename T, typename Alloc> 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<class Alloc> c4::substr to_substr(std::vector<char, Alloc> &vec);
+template<class Alloc> c4::csubstr to_csubstr(std::vector<char, Alloc> const& vec);
+
+template<class Alloc> bool operator!= (c4::csubstr ss, std::vector<char, Alloc> const& s);
+template<class Alloc> bool operator== (c4::csubstr ss, std::vector<char, Alloc> const& s);
+template<class Alloc> bool operator>= (c4::csubstr ss, std::vector<char, Alloc> const& s);
+template<class Alloc> bool operator> (c4::csubstr ss, std::vector<char, Alloc> const& s);
+template<class Alloc> bool operator<= (c4::csubstr ss, std::vector<char, Alloc> const& s);
+template<class Alloc> bool operator< (c4::csubstr ss, std::vector<char, Alloc> const& s);
+
+template<class Alloc> bool operator!= (std::vector<char, Alloc> const& s, c4::csubstr ss);
+template<class Alloc> bool operator== (std::vector<char, Alloc> const& s, c4::csubstr ss);
+template<class Alloc> bool operator>= (std::vector<char, Alloc> const& s, c4::csubstr ss);
+template<class Alloc> bool operator> (std::vector<char, Alloc> const& s, c4::csubstr ss);
+template<class Alloc> bool operator<= (std::vector<char, Alloc> const& s, c4::csubstr ss);
+template<class Alloc> bool operator< (std::vector<char, Alloc> const& s, c4::csubstr ss);
+
+template<class Alloc> size_t to_chars(c4::substr buf, std::vector<char, Alloc> const& s);
+template<class Alloc> bool from_chars(c4::csubstr buf, std::vector<char, Alloc> * s);
+
+} // namespace c4
+
+#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 <cstddef>
+
+// forward declarations for std::string
+#if defined(__GLIBCXX__) || defined(__GLIBCPP__)
+#include <bits/stringfwd.h> // use the fwd header in glibcxx
+#elif defined(_LIBCPP_VERSION) || defined(__APPLE_CC__)
+#include <iosfwd> // use the fwd header in stdlibc++
+#elif defined(_MSC_VER)
+//! @todo is there a fwd header in msvc?
+namespace std {
+template<typename> struct char_traits;
+template<typename> class allocator;
+template<typename _CharT, typename _Traits, typename _Alloc> class basic_string;
+using string = basic_string<char, char_traits<char>, allocator<char>>;
+} /* namespace std */
+#else
+#error "unknown standard library"
+#endif
+
+namespace c4 {
+
+c4::substr to_substr(std::string &s);
+c4::csubstr to_csubstr(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< (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)
+
+
+
+//********************************************************************************
+//--------------------------------------------------------------------------------
+// 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.
+ *
+ * These are the main functions:
+ *
+ * @code{.cpp}
+ * // 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.
+ * template<class T> size_t c4::to_chars(substr buf, T const& C4_RESTRICT val);
+ *
+ *
+ * // Convert the given value to a string using to_chars(), and
+ * // return the resulting string, up to and including the last
+ * // written character.
+ * template<class T> substr c4::to_chars_sub(substr buf, T const& C4_RESTRICT val);
+ *
+ *
+ * // Read a value from the string, which must be
+ * // trimmed to the value (ie, no leading/trailing whitespace).
+ * // return true if the conversion succeeded.
+ * template<class T> bool c4::from_chars(csubstr buf, T * C4_RESTRICT val);
+ *
+ *
+ * // Read the first valid sequence of characters from the string,
+ * // skipping leading whitespace, and convert it using from_chars().
+ * // Return the number of characters read for converting.
+ * template<class T> size_t c4::from_chars_first(csubstr buf, T * C4_RESTRICT val);
+ * @endcode
+ */
+
+// 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 <inttypes.h>
+//included above:
+//#include <type_traits>
+//included above:
+//#include <climits>
+//included above:
+//#include <limits>
+//included above:
+//#include <utility>
+
+// 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
+ C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wsign-conversion")
+ C4_SUPPRESS_WARNING_GCC("-Warray-bounds")
+#if __GNUC__ >= 5
+ C4_SUPPRESS_WARNING_GCC("-Wshift-count-overflow")
+#endif
+// 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_ */
+
+ C4_SUPPRESS_WARNING_GCC_POP
+# define C4CORE_HAVE_FAST_FLOAT 1
+# define C4CORE_HAVE_STD_FROMCHARS 0
+# if (C4_CPP >= 17)
+# if defined(_MSC_VER)
+# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019)
+# include <charconv>
+# define C4CORE_HAVE_STD_TOCHARS 1
+# else
+# define C4CORE_HAVE_STD_TOCHARS 0
+# endif
+# else // VS2017 and lower do not have these macros
+# if __has_include(<charconv>) && __cpp_lib_to_chars
+# define C4CORE_HAVE_STD_TOCHARS 1
+//included above:
+//# include <charconv>
+# else
+# define C4CORE_HAVE_STD_TOCHARS 0
+# endif
+# endif
+# else
+# define C4CORE_HAVE_STD_TOCHARS 0
+# endif
+#elif (C4_CPP >= 17)
+# if defined(_MSC_VER)
+# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019)
+//included above:
+//# include <charconv>
+# 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 // VS2017 and lower do not have these macros
+# if __has_include(<charconv>) && __cpp_lib_to_chars
+# define C4CORE_HAVE_STD_TOCHARS 1
+# define C4CORE_HAVE_STD_FROMCHARS 1
+//included above:
+//# include <charconv>
+# 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
+#endif
+
+
+#if !C4CORE_HAVE_STD_FROMCHARS && !defined(C4CORE_HAVE_FAST_FLOAT)
+#include <cstdio>
+#endif
+
+
+#ifdef _MSC_VER
+# pragma warning(push)
+# if C4_MSVC_VERSION != C4_MSVC_VERSION_2017
+# pragma warning(disable: 4800) //'int': 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
+# 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
+#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"
+#endif
+
+
+namespace c4 {
+
+typedef enum : uint8_t {
+ /** print the real number in floating point format (like %f) */
+ FTOA_FLOAT = 0,
+ /** print the real number in scientific format (like %e) */
+ FTOA_SCIENT = 1,
+ /** print the real number in flexible format (like %g) */
+ FTOA_FLEX = 2,
+ /** print the real number in hexadecimal format (like %a) */
+ FTOA_HEXA = 3,
+ _FTOA_COUNT
+} RealFormat_e;
+
+
+inline C4_CONSTEXPR14 char to_c_fmt(RealFormat_e f)
+{
+ constexpr const char fmt[] = {
+ 'f', // FTOA_FLOAT
+ 'e', // FTOA_SCIENT
+ 'g', // FTOA_FLEX
+ 'a', // FTOA_HEXA
+ };
+ C4_STATIC_ASSERT(C4_COUNTOF(fmt) == _FTOA_COUNT);
+ #if C4_CPP > 14
+ C4_ASSERT(f < _FTOA_COUNT);
+ #endif
+ return fmt[f];
+}
+
+
+#if C4CORE_HAVE_STD_TOCHARS
+inline C4_CONSTEXPR14 std::chars_format to_std_fmt(RealFormat_e f)
+{
+ constexpr const std::chars_format fmt[] = {
+ std::chars_format::fixed, // FTOA_FLOAT
+ std::chars_format::scientific, // FTOA_SCIENT
+ std::chars_format::general, // FTOA_FLEX
+ std::chars_format::hex, // FTOA_HEXA
+ };
+ C4_STATIC_ASSERT(C4_COUNTOF(fmt) == _FTOA_COUNT);
+ #if C4_CPP >= 14
+ C4_ASSERT(f < _FTOA_COUNT);
+ #endif
+ return fmt[f];
+}
+#endif // C4CORE_HAVE_STD_TOCHARS
+
+/** 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<class T>
+struct is_fixed_length
+{
+ enum : bool {
+ /** true if T is one of the fixed length signed types */
+ value_i = (std::is_integral<T>::value
+ && (std::is_same<T, int8_t>::value
+ || std::is_same<T, int16_t>::value
+ || std::is_same<T, int32_t>::value
+ || std::is_same<T, int64_t>::value)),
+ /** true if T is one of the fixed length unsigned types */
+ value_u = (std::is_integral<T>::value
+ && (std::is_same<T, uint8_t>::value
+ || std::is_same<T, uint16_t>::value
+ || std::is_same<T, uint32_t>::value
+ || std::is_same<T, uint64_t>::value)),
+ /** true if T is one of the fixed length signed or unsigned types */
+ value = value_i || value_u
+ };
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+#ifdef _MSC_VER
+# 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
+
+// Helper macros, undefined below
+
+#define _c4append(c) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = static_cast<char>(c); } else { ++pos; } }
+#define _c4appendhex(i) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = hexchars[i]; } else { ++pos; } }
+
+}
+#include <iostream>
+namespace c4 {
+C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef";
+
+/** 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
+ * @return the number of characters required for the string,
+ * even if the string is not long enough for the result.
+ * No writes are done past the end of the string. */
+template<class T>
+size_t write_dec(substr buf, T v)
+{
+ C4_STATIC_ASSERT(std::is_integral<T>::value);
+ C4_ASSERT(v >= 0);
+ size_t pos = 0;
+ do {
+ _c4append('0' + (v % T(10)));
+ v /= T(10);
+ } while(v);
+ buf.reverse_range(0, pos <= buf.len ? pos : buf.len);
+ return pos;
+}
+
+
+/** 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
+ * @return the number of characters required for the string,
+ * even if the string is not long enough for the result.
+ * No writes are done past the end of the string. */
+template<class T>
+size_t write_hex(substr buf, T v)
+{
+ C4_STATIC_ASSERT(std::is_integral<T>::value);
+ C4_ASSERT(v >= 0);
+ size_t pos = 0;
+ do {
+ _c4appendhex(v & T(15));
+ v >>= 4;
+ } while(v);
+ buf.reverse_range(0, pos <= buf.len ? pos : buf.len);
+ return pos;
+}
+
+/** 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
+ * @return the number of characters required for the string,
+ * even if the string is not long enough for the result.
+ * No writes are done past the end of the string. */
+template<class T>
+size_t write_oct(substr buf, T v)
+{
+ C4_STATIC_ASSERT(std::is_integral<T>::value);
+ C4_ASSERT(v >= 0);
+ size_t pos = 0;
+ do {
+ _c4append('0' + (v & T(7)));
+ v >>= 3;
+ } while(v);
+ buf.reverse_range(0, pos <= buf.len ? pos : buf.len);
+ return pos;
+}
+
+/** 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
+ * @return the number of characters required for the string,
+ * even if the string is not long enough for the result.
+ * No writes are done past the end of the string. */
+template<class T>
+size_t write_bin(substr buf, T v)
+{
+ C4_STATIC_ASSERT(std::is_integral<T>::value);
+ C4_ASSERT(v >= 0);
+ size_t pos = 0;
+ do {
+ _c4append('0' + (v & T(1)));
+ v >>= 1;
+ } while(v);
+ buf.reverse_range(0, pos <= buf.len ? pos : buf.len);
+ return pos;
+}
+
+
+namespace detail {
+template<class U> using NumberWriter = size_t (*)(substr, U);
+/** @todo pass the writer as a template parameter */
+template<class T, NumberWriter<T> writer>
+size_t write_num_digits(substr buf, T v, size_t num_digits)
+{
+ C4_STATIC_ASSERT(std::is_integral<T>::value);
+ 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);
+ size_t delta = static_cast<size_t>(num_digits - ret);
+ memmove(buf.str + delta, buf.str, ret);
+ memset(buf.str, '0', delta);
+ return num_digits;
+}
+} // namespace detail
+
+
+/** 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 wider than num_digits, then the number prevails. */
+template<class T>
+size_t write_dec(substr buf, T val, size_t num_digits)
+{
+ return detail::write_num_digits<T, &write_dec<T>>(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 wider than num_digits, then the number prevails. */
+template<class T>
+size_t write_hex(substr buf, T val, size_t num_digits)
+{
+ return detail::write_num_digits<T, &write_hex<T>>(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 wider than num_digits, then the number prevails. */
+template<class T>
+size_t write_bin(substr buf, T val, size_t num_digits)
+{
+ return detail::write_num_digits<T, &write_bin<T>>(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 wider than num_digits, then the number prevails. */
+template<class T>
+size_t write_oct(substr buf, T val, size_t num_digits)
+{
+ return detail::write_num_digits<T, &write_oct<T>>(buf, val, num_digits);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** 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.
+ * @return true if the conversion was successful */
+template<class I>
+C4_ALWAYS_INLINE bool read_dec(csubstr s, I *C4_RESTRICT v)
+{
+ C4_STATIC_ASSERT(std::is_integral<I>::value);
+ *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 be trimmed. Whitespace is not accepted.
+ * @return true if the conversion was successful */
+template<class I>
+C4_ALWAYS_INLINE bool read_hex(csubstr s, I *C4_RESTRICT v)
+{
+ C4_STATIC_ASSERT(std::is_integral<I>::value);
+ *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 be trimmed. Whitespace is not accepted.
+ * @return true if the conversion was successful */
+template<class I>
+C4_ALWAYS_INLINE bool read_bin(csubstr s, I *C4_RESTRICT v)
+{
+ C4_STATIC_ASSERT(std::is_integral<I>::value);
+ *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 be trimmed. Whitespace is not accepted.
+ * @return true if the conversion was successful */
+template<class I>
+C4_ALWAYS_INLINE bool read_oct(csubstr s, I *C4_RESTRICT v)
+{
+ C4_STATIC_ASSERT(std::is_integral<I>::value);
+ *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;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+namespace detail {
+// 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<size_t numbytes> struct itoa_min;
+template<> struct itoa_min<1>
+{
+ static csubstr value_dec() { return csubstr("128"); }
+ static csubstr value_hex() { return csubstr("80"); }
+ static csubstr value_oct() { return csubstr("200"); }
+ static csubstr value_bin() { return csubstr("10000000"); }
+};
+template<> struct itoa_min<2>
+{
+ static csubstr value_dec() { return csubstr("32768"); }
+ static csubstr value_hex() { return csubstr("8000"); }
+ static csubstr value_oct() { return csubstr("100000"); }
+ static csubstr value_bin() { return csubstr("1000000000000000"); }
+};
+template<> struct itoa_min<4>
+{
+ static csubstr value_dec() { return csubstr("2147483648"); }
+ static csubstr value_hex() { return csubstr("80000000"); }
+ static csubstr value_oct() { return csubstr("20000000000"); }
+ static csubstr value_bin() { return csubstr("10000000000000000000000000000000"); }
+};
+template<> struct itoa_min<8>
+{
+ static csubstr value_dec() { return csubstr("9223372036854775808"); }
+ static csubstr value_hex() { return csubstr("8000000000000000"); }
+ static csubstr value_oct() { return csubstr("1000000000000000000000"); }
+ static csubstr value_bin() { return csubstr("1000000000000000000000000000000000000000000000000000000000000000"); }
+};
+inline size_t _itoa2buf(substr buf, size_t pos, csubstr val)
+{
+ if(C4_LIKELY(pos + val.len <= buf.len))
+ 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)
+{
+ num_digits = num_digits > val.len ? num_digits - val.len : 0;
+ for(size_t i = 0; i < num_digits; ++i)
+ _c4append('0');
+ return _itoa2buf(buf, pos, val);
+}
+template<class T>
+size_t _itoadec2buf(substr buf)
+{
+ if(C4_LIKELY(buf.len > 0))
+ {
+ buf.str[0] = '-';
+ return detail::_itoa2buf(buf, 1, detail::itoa_min<sizeof(T)>::value_dec());
+ }
+ else
+ {
+ return detail::_itoa2buf({}, 1, detail::itoa_min<sizeof(T)>::value_dec());
+ }
+ C4_UNREACHABLE();
+}
+template<class I>
+size_t _itoa2buf(substr buf, I radix)
+{
+ size_t pos = 0;
+ _c4append('-');
+ switch(radix)
+ {
+ case I(10):
+ /*...........................*/ return _itoa2buf(buf, pos, itoa_min<sizeof(I)>::value_dec());
+ case I(16):
+ _c4append('0'); _c4append('x'); return _itoa2buf(buf, pos, itoa_min<sizeof(I)>::value_hex());
+ case I( 2):
+ _c4append('0'); _c4append('b'); return _itoa2buf(buf, pos, itoa_min<sizeof(I)>::value_bin());
+ case I( 8):
+ _c4append('0'); _c4append('o'); return _itoa2buf(buf, pos, itoa_min<sizeof(I)>::value_oct());
+ }
+ C4_ERROR("unknown radix");
+ return 0;
+}
+template<class I>
+size_t _itoa2buf(substr buf, I radix, size_t num_digits)
+{
+ size_t pos = 0;
+ _c4append('-');
+ switch(radix)
+ {
+ case I(10):
+ /*...........................*/ return _itoa2bufwithdigits(buf, pos, num_digits, itoa_min<sizeof(I)>::value_dec());
+ case I(16):
+ _c4append('0'); _c4append('x'); return _itoa2bufwithdigits(buf, pos, num_digits, itoa_min<sizeof(I)>::value_hex());
+ case I( 2):
+ _c4append('0'); _c4append('b'); return _itoa2bufwithdigits(buf, pos, num_digits, itoa_min<sizeof(I)>::value_bin());
+ case I( 8):
+ _c4append('0'); _c4append('o'); return _itoa2bufwithdigits(buf, pos, num_digits, itoa_min<sizeof(I)>::value_oct());
+ }
+ C4_ERROR("unknown radix");
+ return 0;
+}
+} // namespace detail
+
+
+/** convert an integral signed decimal to a string.
+ * The resulting string is NOT zero-terminated.
+ * Writing stops at the buffer's end.
+ * @return the number of characters needed for the result, even if the buffer size is insufficient */
+template<class T>
+size_t itoa(substr buf, T v)
+{
+ C4_STATIC_ASSERT(std::is_signed<T>::value);
+ if(v >= 0)
+ {
+ return write_dec(buf, v);
+ }
+ else
+ {
+ if(C4_LIKELY(v != std::numeric_limits<T>::min()))
+ {
+ if(C4_LIKELY(buf.len > 0))
+ {
+ buf.str[0] = '-';
+ return size_t(1) + write_dec(buf.sub(1), -v);
+ }
+ else
+ {
+ return size_t(1) + write_dec({}, -v);
+ }
+ C4_UNREACHABLE();
+ }
+ else
+ {
+ // when T is the min value (eg i8: -128), negating it
+ // will overflow. so we just use the explicit value
+ return detail::_itoadec2buf<T>(buf);
+ }
+ C4_UNREACHABLE();
+ }
+ C4_UNREACHABLE();
+}
+
+/** convert an integral signed integer to a string, using a specific
+ * radix. The radix must be 2, 8, 10 or 16.
+ *
+ * The resulting string is NOT zero-terminated.
+ * Writing stops at the buffer's end.
+ * @return the number of characters needed for the result, even if the buffer size is insufficient */
+template<class T>
+size_t itoa(substr buf, T v, T radix)
+{
+ C4_STATIC_ASSERT(std::is_signed<T>::value);
+ C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16);
+ // when T is the min value (eg i8: -128), negating it
+ // will overflow
+ if(C4_LIKELY(v != std::numeric_limits<T>::min()))
+ {
+ size_t pos = 0;
+ if(v < 0)
+ {
+ v = -v;
+ _c4append('-');
+ }
+ switch(radix)
+ {
+ case 10:
+ /*............................*/return pos + write_dec(pos < buf.len ? buf.sub(pos) : substr(), v);
+ case 16:
+ _c4append('0'); _c4append('x'); return pos + write_hex(pos < buf.len ? buf.sub(pos) : substr(), v);
+ case 2:
+ _c4append('0'); _c4append('b'); return pos + write_bin(pos < buf.len ? buf.sub(pos) : substr(), v);
+ case 8:
+ _c4append('0'); _c4append('o'); return pos + write_oct(pos < buf.len ? buf.sub(pos) : substr(), v);
+ }
+ }
+ // when T is the min value (eg i8: -128), negating it
+ // will overflow
+ return detail::_itoa2buf<T>(buf, radix);
+}
+
+
+/** same as c4::itoa(), 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. The resulting string is NOT zero-terminated. Writing
+ * stops at the buffer's end.
+ *
+ * @return the number of characters needed for the result, even if
+ * the buffer size is insufficient */
+template<class T>
+size_t itoa(substr buf, T v, T radix, size_t num_digits)
+{
+ C4_STATIC_ASSERT(std::is_signed<T>::value);
+ C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16);
+ if(C4_LIKELY(v != std::numeric_limits<T>::min()))
+ {
+ size_t pos = 0;
+ if(v < 0)
+ {
+ v = -v;
+ _c4append('-');
+ }
+ switch(radix)
+ {
+ case 10:
+ /*............................*/return pos + write_dec(pos < buf.len ? buf.sub(pos) : substr(), v, num_digits);
+ case 16:
+ _c4append('0'); _c4append('x'); return pos + write_hex(pos < buf.len ? buf.sub(pos) : substr(), v, num_digits);
+ case 2:
+ _c4append('0'); _c4append('b'); return pos + write_bin(pos < buf.len ? buf.sub(pos) : substr(), v, num_digits);
+ case 8:
+ _c4append('0'); _c4append('o'); return pos + write_oct(pos < buf.len ? buf.sub(pos) : substr(), v, num_digits);
+ }
+ }
+ // when T is the min value (eg i8: -128), negating it
+ // will overflow
+ return detail::_itoa2buf<T>(buf, radix, num_digits);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** convert an integral unsigned decimal to a string.
+ * The resulting string is NOT zero-terminated.
+ * Writing stops at the buffer's end.
+ * @return the number of characters needed for the result, even if the buffer size is insufficient */
+template<class T>
+size_t utoa(substr buf, T v)
+{
+ C4_STATIC_ASSERT(std::is_unsigned<T>::value);
+ 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.
+ * The resulting string is NOT zero-terminated.
+ * Writing stops at the buffer's end.
+ * @return the number of characters needed for the result, even if the buffer size is insufficient */
+template<class T>
+size_t utoa(substr buf, T v, T radix)
+{
+ C4_STATIC_ASSERT(std::is_unsigned<T>::value);
+ C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8);
+ size_t pos = 0;
+ switch(radix)
+ {
+ case 10:
+ /*............................*/return pos + write_dec(pos < buf.len ? buf.sub(pos) : substr(), v);
+ case 16:
+ _c4append('0'); _c4append('x'); return pos + write_hex(pos < buf.len ? buf.sub(pos) : substr(), v);
+ case 2:
+ _c4append('0'); _c4append('b'); return pos + write_bin(pos < buf.len ? buf.sub(pos) : substr(), v);
+ case 8:
+ _c4append('0'); _c4append('o'); return pos + write_oct(pos < buf.len ? buf.sub(pos) : substr(), v);
+ }
+ C4_UNREACHABLE();
+ return substr::npos;
+}
+
+/** 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. The resulting string is NOT zero-terminated. Writing
+ * stops at the buffer's end.
+ *
+ * @return the number of characters needed for the result, even if
+ * the buffer size is insufficient */
+template<class T>
+size_t utoa(substr buf, T v, T radix, size_t num_digits)
+{
+ C4_STATIC_ASSERT(std::is_unsigned<T>::value);
+ C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8);
+ size_t pos = 0;
+ switch(radix)
+ {
+ case 10:
+ /*............................*/return pos + write_dec(pos < buf.len ? buf.sub(pos) : substr(), v, num_digits);
+ case 16:
+ _c4append('0'); _c4append('x'); return pos + write_hex(pos < buf.len ? buf.sub(pos) : substr(), v, num_digits);
+ case 2:
+ _c4append('0'); _c4append('b'); return pos + write_bin(pos < buf.len ? buf.sub(pos) : substr(), v, num_digits);
+ case 8:
+ _c4append('0'); _c4append('o'); return pos + write_oct(pos < buf.len ? buf.sub(pos) : substr(), v, num_digits);
+ }
+ C4_UNREACHABLE();
+ return substr::npos;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** Convert a trimmed string to a signed 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). Strings with
+ * leading zeroes are considered as decimal. 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.
+ * This is similar to native behavior.
+ *
+ * @see atoi_first() if the string is not trimmed to the value to read. */
+template<class T>
+bool atoi(csubstr str, T * C4_RESTRICT v)
+{
+ C4_STATIC_ASSERT(std::is_integral<T>::value);
+ C4_STATIC_ASSERT(std::is_signed<T>::value);
+
+ if(C4_UNLIKELY(str.len == 0))
+ return false;
+
+ T sign = 1;
+ size_t start = 0;
+ if(str.str[0] == '-')
+ {
+ if(C4_UNLIKELY(str.len == 1))
+ return false;
+ ++start;
+ sign = -1;
+ }
+
+ if(str.str[start] != '0')
+ {
+ if(C4_UNLIKELY( ! read_dec(str.sub(start), v)))
+ return false;
+ }
+ else
+ {
+ if(str.len == start+1)
+ {
+ *v = 0; // because the first character is 0
+ return true;
+ }
+ else
+ {
+ char pfx = str.str[start+1];
+ if(pfx == 'x' || pfx == 'X') // hexadecimal
+ {
+ if(C4_UNLIKELY(str.len <= start + 2))
+ return false;
+ if(C4_UNLIKELY( ! read_hex(str.sub(start + 2), v)))
+ return false;
+ }
+ else if(pfx == 'b' || pfx == 'B') // binary
+ {
+ if(C4_UNLIKELY(str.len <= start + 2))
+ return false;
+ if(C4_UNLIKELY( ! read_bin(str.sub(start + 2), v)))
+ return false;
+ }
+ else if(pfx == 'o' || pfx == 'O') // octal
+ {
+ if(C4_UNLIKELY(str.len <= start + 2))
+ return false;
+ if(C4_UNLIKELY( ! read_oct(str.sub(start + 2), v)))
+ return false;
+ }
+ else
+ {
+ // we know the first character is 0
+ auto fno = str.first_not_of('0', start + 1);
+ if(fno == csubstr::npos)
+ {
+ *v = 0;
+ return true;
+ }
+ if(C4_UNLIKELY( ! read_dec(str.sub(fno), v)))
+ {
+ return false;
+ }
+ }
+ }
+ }
+ *v *= sign;
+ return true;
+}
+
+
+/** 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<class T>
+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<size_t>(trimmed.end() - str.begin());
+ return csubstr::npos;
+}
+
+
+//-----------------------------------------------------------------------------
+
+/** 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.
+ *
+ * @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<class T>
+bool atou(csubstr str, T * C4_RESTRICT v)
+{
+ C4_STATIC_ASSERT(std::is_integral<T>::value);
+
+ if(C4_UNLIKELY(str.len == 0 || str.front() == '-'))
+ return false;
+
+ if(str.str[0] != '0')
+ {
+ if(C4_UNLIKELY( ! read_dec(str, v)))
+ return false;
+ }
+ else
+ {
+ if(str.len == 1)
+ {
+ *v = 0; // we know the first character is 0
+ return true;
+ }
+ else
+ {
+ char pfx = str.str[1];
+ if(pfx == 'x' || pfx == 'X') // hexadecimal
+ {
+ if(C4_UNLIKELY(str.len <= 2))
+ return false;
+ return read_hex(str.sub(2), v);
+ }
+ else if(pfx == 'b' || pfx == 'B') // binary
+ {
+ if(C4_UNLIKELY(str.len <= 2))
+ return false;
+ return read_bin(str.sub(2), v);
+ }
+ else if(pfx == 'o' || pfx == 'O') // octal
+ {
+ if(C4_UNLIKELY(str.len <= 2))
+ return false;
+ return read_oct(str.sub(2), v);
+ }
+ else
+ {
+ // we know the first character is 0
+ auto fno = str.first_not_of('0');
+ if(fno == csubstr::npos)
+ {
+ *v = 0;
+ return true;
+ }
+ return read_dec(str.sub(fno), v);
+ }
+ }
+ }
+ return true;
+}
+
+
+/** 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<class T>
+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<size_t>(trimmed.end() - str.begin());
+ return csubstr::npos;
+}
+
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#elif defined(__clang__)
+# pragma clang diagnostic pop
+#elif defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+namespace detail {
+
+
+/** @see http://www.exploringbinary.com/ for many good examples on float-str conversion */
+template<size_t N>
+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, to_c_fmt(formatting));
+ else if(precision == 0)
+ iret = snprintf(fmt, sizeof(fmt), "%%.%s%c", length_modifier, to_c_fmt(formatting));
+ else
+ iret = snprintf(fmt, sizeof(fmt), "%%.%d%s%c", precision, length_modifier, to_c_fmt(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<class T>
+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
+}
+
+#if !C4CORE_HAVE_STD_FROMCHARS && !defined(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<typename T>
+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
+
+
+#if C4CORE_HAVE_STD_TOCHARS
+template<class T>
+size_t rtoa(substr buf, T v, int precision=-1, RealFormat_e formatting=FTOA_FLEX)
+{
+ std::to_chars_result result;
+ size_t pos = 0;
+ if(formatting == FTOA_HEXA)
+ {
+ _c4append('0');
+ _c4append('x');
+ }
+ if(precision == -1)
+ result = std::to_chars(buf.str + pos, buf.str + buf.len, v, to_std_fmt(formatting));
+ else
+ result = std::to_chars(buf.str + pos, buf.str + buf.len, v, to_std_fmt(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<size_t>(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<size_t>(std::numeric_limits<T>::max_digits10);
+ return ret > buf.len ? ret : buf.len + 1;
+}
+#endif // C4CORE_HAVE_STD_TOCHARS
+
+} // namespace detail
+
+
+#undef _c4appendhex
+#undef _c4append
+
+
+/** 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. */
+inline size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX)
+{
+#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
+}
+
+
+/** 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.
+ *
+ * @return the number of characters written.
+ */
+inline size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX)
+{
+#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
+}
+
+
+/** 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
+ */
+inline bool atof(csubstr str, float * C4_RESTRICT v)
+{
+ C4_ASSERT(str.triml(" \r\t\n").len == str.len);
+#if C4CORE_HAVE_FAST_FLOAT
+ fast_float::from_chars_result result;
+ result = fast_float::from_chars(str.str, str.str + str.len, *v);
+ return result.ec == std::errc();
+#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
+ size_t ret = detail::scan_one(str, "f", v);
+ return ret != csubstr::npos;
+#endif
+}
+
+
+/** 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
+ */
+inline bool atod(csubstr str, double * C4_RESTRICT v)
+{
+ C4_ASSERT(str.triml(" \r\t\n").len == str.len);
+#if C4CORE_HAVE_FAST_FLOAT
+ fast_float::from_chars_result result;
+ result = fast_float::from_chars(str.str, str.str + str.len, *v);
+ return result.ec == std::errc();
+#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
+ size_t ret = detail::scan_one(str, "lf", v);
+ return ret != 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)
+{
+ csubstr trimmed = str.first_real_span();
+ if(trimmed.len == 0)
+ return csubstr::npos;
+ if(atof(trimmed, v))
+ return static_cast<size_t>(trimmed.end() - str.begin());
+ return csubstr::npos;
+}
+
+
+/** 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)
+{
+ csubstr trimmed = str.first_real_span();
+ if(trimmed.len == 0)
+ return csubstr::npos;
+ if(atod(trimmed, v))
+ return static_cast<size_t>(trimmed.end() - str.begin());
+ return csubstr::npos;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// generic versions
+
+C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v) { return utoa(s, v); }
+C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v) { return utoa(s, v); }
+C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v) { return utoa(s, v); }
+C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v) { return utoa(s, v); }
+C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v) { return itoa(s, v); }
+C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v) { return itoa(s, v); }
+C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v) { return itoa(s, v); }
+C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v) { return itoa(s, v); }
+C4_ALWAYS_INLINE size_t xtoa(substr s, float v) { return ftoa(s, v); }
+C4_ALWAYS_INLINE size_t xtoa(substr s, double v) { return dtoa(s, v); }
+
+C4_ALWAYS_INLINE bool atox(csubstr s, uint8_t *C4_RESTRICT v) { return atou(s, v); }
+C4_ALWAYS_INLINE bool atox(csubstr s, uint16_t *C4_RESTRICT v) { return atou(s, v); }
+C4_ALWAYS_INLINE bool atox(csubstr s, uint32_t *C4_RESTRICT v) { return atou(s, v); }
+C4_ALWAYS_INLINE bool atox(csubstr s, uint64_t *C4_RESTRICT v) { return atou(s, v); }
+C4_ALWAYS_INLINE bool atox(csubstr s, int8_t *C4_RESTRICT v) { return atoi(s, v); }
+C4_ALWAYS_INLINE bool atox(csubstr s, int16_t *C4_RESTRICT v) { return atoi(s, v); }
+C4_ALWAYS_INLINE bool atox(csubstr s, int32_t *C4_RESTRICT v) { return atoi(s, v); }
+C4_ALWAYS_INLINE bool atox(csubstr s, int64_t *C4_RESTRICT v) { return atoi(s, v); }
+C4_ALWAYS_INLINE bool atox(csubstr s, float *C4_RESTRICT v) { return atof(s, v); }
+C4_ALWAYS_INLINE bool atox(csubstr s, double *C4_RESTRICT v) { return atod(s, v); }
+
+C4_ALWAYS_INLINE size_t to_chars(substr buf, uint8_t v) { return utoa(buf, v); }
+C4_ALWAYS_INLINE size_t to_chars(substr buf, uint16_t v) { return utoa(buf, v); }
+C4_ALWAYS_INLINE size_t to_chars(substr buf, uint32_t v) { return utoa(buf, v); }
+C4_ALWAYS_INLINE size_t to_chars(substr buf, uint64_t v) { return utoa(buf, v); }
+C4_ALWAYS_INLINE size_t to_chars(substr buf, int8_t v) { return itoa(buf, v); }
+C4_ALWAYS_INLINE size_t to_chars(substr buf, int16_t v) { return itoa(buf, v); }
+C4_ALWAYS_INLINE size_t to_chars(substr buf, int32_t v) { return itoa(buf, v); }
+C4_ALWAYS_INLINE size_t to_chars(substr buf, int64_t v) { return itoa(buf, v); }
+C4_ALWAYS_INLINE size_t to_chars(substr buf, float v) { return ftoa(buf, v); }
+C4_ALWAYS_INLINE size_t to_chars(substr buf, double v) { return dtoa(buf, v); }
+
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint8_t *C4_RESTRICT v) { return atou(buf, v); }
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint16_t *C4_RESTRICT v) { return atou(buf, v); }
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint32_t *C4_RESTRICT v) { return atou(buf, v); }
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint64_t *C4_RESTRICT v) { return atou(buf, v); }
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, int8_t *C4_RESTRICT v) { return atoi(buf, v); }
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, int16_t *C4_RESTRICT v) { return atoi(buf, v); }
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, int32_t *C4_RESTRICT v) { return atoi(buf, v); }
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, int64_t *C4_RESTRICT v) { return atoi(buf, v); }
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, float *C4_RESTRICT v) { return atof(buf, v); }
+C4_ALWAYS_INLINE bool from_chars(csubstr buf, double *C4_RESTRICT v) { return atod(buf, v); }
+
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint8_t *C4_RESTRICT v) { return atou_first(buf, v); }
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint16_t *C4_RESTRICT v) { return atou_first(buf, v); }
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint32_t *C4_RESTRICT v) { return atou_first(buf, v); }
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint64_t *C4_RESTRICT v) { return atou_first(buf, v); }
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int8_t *C4_RESTRICT v) { return atoi_first(buf, v); }
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int16_t *C4_RESTRICT v) { return atoi_first(buf, v); }
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int32_t *C4_RESTRICT v) { return atoi_first(buf, v); }
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int64_t *C4_RESTRICT v) { return atoi_first(buf, v); }
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, float *C4_RESTRICT v) { return atof_first(buf, v); }
+C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, double *C4_RESTRICT v) { return atod_first(buf, v); }
+
+
+//-----------------------------------------------------------------------------
+// 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) C4_ALWAYS_INLINE typename std::enable_if<std:: is_signed<T>::value && !is_fixed_length<T>::value_i, ty>
+#define _C4_IF_NOT_FIXED_LENGTH_U(T, ty) C4_ALWAYS_INLINE typename std::enable_if<std::is_unsigned<T>::value && !is_fixed_length<T>::value_u, ty>
+
+template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type xtoa(substr buf, T v) { return itoa(buf, v); }
+template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type xtoa(substr buf, T v) { return utoa(buf, v); }
+
+template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) { return atoi(buf, v); }
+template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) { return atou(buf, v); }
+
+template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type to_chars(substr buf, T v) { return itoa(buf, v); }
+template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type to_chars(substr buf, T v) { return utoa(buf, v); }
+
+template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) { return atoi(buf, v); }
+template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) { return atou(buf, v); }
+
+template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) { return atoi_first(buf, v); }
+template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) { return atou_first(buf, v); }
+
+#undef _C4_IF_NOT_FIXED_LENGTH_I
+#undef _C4_IF_NOT_FIXED_LENGTH_U
+
+
+//-----------------------------------------------------------------------------
+// for pointers
+
+template <class T> C4_ALWAYS_INLINE size_t xtoa(substr s, T *v) { return itoa(s, (intptr_t)v, (intptr_t)16); }
+template <class T> C4_ALWAYS_INLINE bool atox(csubstr s, T **v) { intptr_t tmp; bool ret = atox(s, &tmp); if(ret) { *v = (T*)tmp; } return ret; }
+template <class T> C4_ALWAYS_INLINE size_t to_chars(substr s, T *v) { return itoa(s, (intptr_t)v, (intptr_t)16); }
+template <class T> C4_ALWAYS_INLINE bool from_chars(csubstr buf, T **v) { intptr_t tmp; bool ret = from_chars(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; }
+template <class T> C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, T **v) { intptr_t tmp; bool ret = from_chars_first(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; }
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+/** 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.
+ *
+ * @see to_chars() */
+template<class T>
+inline substr to_chars_sub(substr buf, T const& C4_RESTRICT v)
+{
+ size_t sz = to_chars(buf, v);
+ return buf.left_of(sz <= buf.len ? sz : buf.len);
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// bool implementation
+
+inline size_t to_chars(substr buf, bool v)
+{
+ int val = v;
+ return to_chars(buf, val);
+}
+
+inline bool from_chars(csubstr buf, bool * C4_RESTRICT v)
+{
+ 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;
+}
+
+inline size_t from_chars_first(csubstr buf, bool * C4_RESTRICT v)
+{
+ csubstr trimmed = buf.first_non_empty_span();
+ if(trimmed.len == 0 || !from_chars(buf, v))
+ return csubstr::npos;
+ return trimmed.len;
+}
+
+
+//-----------------------------------------------------------------------------
+// single-char implementation
+
+inline size_t to_chars(substr buf, char v)
+{
+ if(buf.len > 0)
+ buf[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 */
+inline bool from_chars(csubstr buf, char * C4_RESTRICT v)
+{
+ if(buf.len != 1)
+ return false;
+ *v = buf[0];
+ return true;
+}
+
+inline size_t from_chars_first(csubstr buf, char * C4_RESTRICT v)
+{
+ if(buf.len < 1)
+ return csubstr::npos;
+ *v = buf[0];
+ return 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// csubstr implementation
+
+inline size_t to_chars(substr buf, csubstr v)
+{
+ C4_ASSERT(!buf.overlaps(v));
+ size_t len = buf.len < v.len ? buf.len : v.len;
+ memcpy(buf.str, v.str, len);
+ return v.len;
+}
+
+inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v)
+{
+ *v = buf;
+ return true;
+}
+
+inline size_t from_chars_first(substr buf, csubstr * C4_RESTRICT v)
+{
+ csubstr trimmed = buf.first_non_empty_span();
+ if(trimmed.len == 0)
+ return csubstr::npos;
+ *v = trimmed;
+ return static_cast<size_t>(trimmed.end() - buf.begin());
+}
+
+
+//-----------------------------------------------------------------------------
+// substr
+
+inline size_t to_chars(substr buf, substr v)
+{
+ C4_ASSERT(!buf.overlaps(v));
+ size_t len = buf.len < v.len ? buf.len : v.len;
+ memcpy(buf.str, v.str, len);
+ return v.len;
+}
+
+inline bool from_chars(csubstr buf, substr * C4_RESTRICT v)
+{
+ C4_ASSERT(!buf.overlaps(*v));
+ if(buf.len <= v->len)
+ {
+ memcpy(v->str, buf.str, buf.len);
+ v->len = buf.len;
+ return true;
+ }
+ memcpy(v->str, buf.str, v->len);
+ return false;
+}
+
+inline size_t from_chars_first(csubstr buf, substr * C4_RESTRICT v)
+{
+ 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;
+ memcpy(v->str, trimmed.str, len);
+ if(C4_UNLIKELY(trimmed.len > v->len))
+ return csubstr::npos;
+ return static_cast<size_t>(trimmed.end() - buf.begin());
+}
+
+
+//-----------------------------------------------------------------------------
+
+template<size_t N>
+inline size_t to_chars(substr buf, const char (& C4_RESTRICT v)[N])
+{
+ csubstr sp(v);
+ return to_chars(buf, sp);
+}
+
+inline size_t to_chars(substr buf, const char * C4_RESTRICT v)
+{
+ return to_chars(buf, to_csubstr(v));
+}
+
+} // namespace c4
+
+#ifdef _MSC_VER
+# 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 <stddef.h>
+//included above:
+//#include <stdint.h>
+
+namespace c4 {
+
+substr decode_code_point(substr out, csubstr code_point);
+size_t decode_code_point(uint8_t *C4_RESTRICT buf, size_t buflen, const 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_ */
+
+
+
+#ifdef _MSC_VER
+# 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
+
+namespace c4 {
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// formatting truthy types as booleans
+
+namespace fmt {
+
+/** write a variable as an alphabetic boolean, ie as either true or false
+ * @param strict_read */
+template<class T>
+struct boolalpha_
+{
+ boolalpha_(T val_, bool strict_read_=false) : val(val_ ? true : false), strict_read(strict_read_) {}
+ bool val;
+ bool strict_read;
+};
+
+template<class T>
+boolalpha_<T> boolalpha(T const& val, bool strict_read=false)
+{
+ return boolalpha_<T>(val, strict_read);
+}
+
+} // namespace fmt
+
+/** write a variable as an alphabetic boolean, ie as either true or false */
+template<class T>
+inline size_t to_chars(substr buf, fmt::boolalpha_<T> fmt)
+{
+ return to_chars(buf, fmt.val ? "true" : "false");
+}
+
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// formatting integral types
+
+namespace fmt {
+
+/** format an integral type with a custom radix */
+template<typename T>
+struct integral_
+{
+ 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<typename T>
+struct integral_padded_
+{
+ 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<class T>
+C4_ALWAYS_INLINE integral_<T> integral(T val, T radix=10)
+{
+ return integral_<T>(val, radix);
+}
+/** format an integral type with a custom radix */
+template<class T>
+C4_ALWAYS_INLINE integral_<intptr_t> integral(T const* val, T radix=10)
+{
+ return integral_<intptr_t>(reinterpret_cast<intptr_t>(val), static_cast<intptr_t>(radix));
+}
+/** format an integral type with a custom radix */
+template<class T>
+C4_ALWAYS_INLINE integral_<intptr_t> integral(std::nullptr_t, T radix=10)
+{
+ return integral_<intptr_t>(intptr_t(0), static_cast<intptr_t>(radix));
+}
+/** pad the argument with zeroes on the left, with decimal radix */
+template<class T>
+C4_ALWAYS_INLINE integral_padded_<T> zpad(T val, size_t num_digits)
+{
+ return integral_padded_<T>(val, T(10), num_digits);
+}
+/** pad the argument with zeroes on the left */
+template<class T>
+C4_ALWAYS_INLINE integral_padded_<T> zpad(integral_<T> val, size_t num_digits)
+{
+ return integral_padded_<T>(val.val, val.radix, num_digits);
+}
+/** pad the argument with zeroes on the left */
+C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(std::nullptr_t, size_t num_digits)
+{
+ return integral_padded_<intptr_t>(0, 16, num_digits);
+}
+/** pad the argument with zeroes on the left */
+template<class T>
+C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T const* val, size_t num_digits)
+{
+ return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits);
+}
+template<class T>
+C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T * val, size_t num_digits)
+{
+ return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits);
+}
+
+
+/** format the pointer as an hexadecimal value */
+template<class T>
+inline integral_<intptr_t> hex(T * v)
+{
+ return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16));
+}
+/** format the pointer as an hexadecimal value */
+template<class T>
+inline integral_<intptr_t> hex(T const* v)
+{
+ return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16));
+}
+/** format null as an hexadecimal value
+ * @overload hex */
+inline integral_<intptr_t> hex(std::nullptr_t)
+{
+ return integral_<intptr_t>(0, intptr_t(16));
+}
+/** format the integral_ argument as an hexadecimal value
+ * @overload hex */
+template<class T>
+inline integral_<T> hex(T v)
+{
+ return integral_<T>(v, T(16));
+}
+
+/** format the pointer as an octal value */
+template<class T>
+inline integral_<intptr_t> oct(T const* v)
+{
+ return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8));
+}
+/** format the pointer as an octal value */
+template<class T>
+inline integral_<intptr_t> oct(T * v)
+{
+ return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8));
+}
+/** format null as an octal value */
+inline integral_<intptr_t> oct(std::nullptr_t)
+{
+ return integral_<intptr_t>(intptr_t(0), intptr_t(8));
+}
+/** format the integral_ argument as an octal value */
+template<class T>
+inline integral_<T> oct(T v)
+{
+ return integral_<T>(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<class T>
+inline integral_<intptr_t> bin(T const* v)
+{
+ return integral_<intptr_t>(reinterpret_cast<intptr_t>(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<class T>
+inline integral_<intptr_t> bin(T * v)
+{
+ return integral_<intptr_t>(reinterpret_cast<intptr_t>(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_<intptr_t> bin(std::nullptr_t)
+{
+ return integral_<intptr_t>(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 binary memcpy instead of 0-1 formatting */
+template<class T>
+inline integral_<T> bin(T v)
+{
+ return integral_<T>(v, T(2));
+}
+
+} // namespace fmt
+
+
+/** format an integral_ signed type */
+template<typename T>
+C4_ALWAYS_INLINE
+typename std::enable_if<std::is_signed<T>::value, size_t>::type
+to_chars(substr buf, fmt::integral_<T> fmt)
+{
+ return itoa(buf, fmt.val, fmt.radix);
+}
+/** format an integral_ signed type, pad with zeroes */
+template<typename T>
+C4_ALWAYS_INLINE
+typename std::enable_if<std::is_signed<T>::value, size_t>::type
+to_chars(substr buf, fmt::integral_padded_<T> fmt)
+{
+ return itoa(buf, fmt.val, fmt.radix, fmt.num_digits);
+}
+
+/** format an integral_ unsigned type */
+template<typename T>
+C4_ALWAYS_INLINE
+typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
+to_chars(substr buf, fmt::integral_<T> fmt)
+{
+ return utoa(buf, fmt.val, fmt.radix);
+}
+/** format an integral_ unsigned type, pad with zeroes */
+template<typename T>
+C4_ALWAYS_INLINE
+typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
+to_chars(substr buf, fmt::integral_padded_<T> fmt)
+{
+ return utoa(buf, fmt.val, fmt.radix, fmt.num_digits);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// formatting real types
+
+namespace fmt {
+
+template<class T>
+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<class T>
+real_<T> real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
+{
+ return real_<T>(val, precision, fmt);
+}
+
+} // namespace fmt
+
+inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); }
+inline size_t to_chars(substr buf, fmt::real_<double> fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); }
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// writing raw binary data
+
+namespace fmt {
+
+/** @see blob_ */
+template<class T>
+struct raw_wrapper_ : public blob_<T>
+{
+ size_t alignment;
+
+ C4_ALWAYS_INLINE raw_wrapper_(blob_<T> data, size_t alignment_) noexcept
+ :
+ blob_<T>(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_<cbyte>;
+using raw_wrapper = raw_wrapper_<byte>;
+
+/** 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<class T>
+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<class T>
+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<class T>
+inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T))
+{
+ return raw_wrapper(blob(data), alignment);
+}
+
+} // namespace fmt
+
+
+/** write a variable in raw binary format, using memcpy */
+C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r);
+
+/** read a variable in raw binary format, using memcpy */
+C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r);
+/** read a variable in raw binary format, using memcpy */
+inline bool from_chars(csubstr buf, fmt::raw_wrapper r)
+{
+ return from_chars(buf, &r);
+}
+
+/** read a variable in raw binary format, using memcpy */
+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 */
+inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper r)
+{
+ return from_chars(buf, &r);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// formatting aligned to left/right
+
+namespace fmt {
+
+template<class T>
+struct left_
+{
+ T val;
+ size_t width;
+ char pad;
+ left_(T v, size_t w, char p) : val(v), width(w), pad(p) {}
+};
+
+template<class T>
+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<class T>
+left_<T> left(T val, size_t width, char padchar=' ')
+{
+ return left_<T>(val, width, padchar);
+}
+
+/** mark an argument to be aligned right */
+template<class T>
+right_<T> right(T val, size_t width, char padchar=' ')
+{
+ return right_<T>(val, width, padchar);
+}
+
+} // namespace fmt
+
+
+template<class T>
+size_t to_chars(substr buf, fmt::left_<T> 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;
+}
+
+template<class T>
+size_t to_chars(substr buf, fmt::right_<T> 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<size_t>(align.width - ret);
+ buf.first(rem).fill(align.pad);
+ to_chars(buf.sub(rem), align.val);
+ return align.width;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/// @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<class Arg, class... Args>
+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<class... Args>
+substr cat_sub(substr buf, Args && ...args)
+{
+ size_t sz = cat(buf, std::forward<Args>(args)...);
+ C4_CHECK(sz <= buf.len);
+ return {buf.str, sz <= buf.len ? sz : buf.len};
+}
+
+
+//-----------------------------------------------------------------------------
+
+/// @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<class Arg, class... Args>
+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;
+}
+
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+namespace detail {
+
+template<class Sep>
+inline size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
+{
+ return 0;
+}
+
+template<class Sep, class Arg, class... Args>
+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), 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<class Sep>
+inline size_t uncatsep_more(csubstr /*buf*/, Sep & /*sep*/)
+{
+ return 0;
+}
+
+template<class Sep, class Arg, class... Args>
+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), 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
+
+
+/** 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<class Sep, class Arg, class... Args>
+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<class... Args>
+substr catsep_sub(substr buf, Args && ...args)
+{
+ size_t sz = catsep(buf, std::forward<Args>(args)...);
+ C4_CHECK(sz <= buf.len);
+ return {buf.str, sz <= buf.len ? sz : buf.len};
+}
+
+/** 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<class Sep, class Arg, class... Args>
+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;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/// @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<class Arg, class... Args>
+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<class... Args>
+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};
+}
+
+
+//-----------------------------------------------------------------------------
+
+/// @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<class Arg, class... Args>
+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;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** a tag type for marking append to container
+ * @see c4::catrs() */
+struct append_t {};
+
+/** a tag variable
+ * @see c4::catrs() */
+constexpr const append_t append = {};
+
+
+//-----------------------------------------------------------------------------
+
+/** 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() */
+template<class CharOwningContainer, class... Args>
+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;
+}
+
+/** like c4::cat(), but creates and returns a new container sized as needed to contain
+ * the result.
+ * @see c4::cat() */
+template<class CharOwningContainer, class... Args>
+inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args)
+{
+ CharOwningContainer cont;
+ catrs(&cont, args...);
+ return cont;
+}
+
+/** 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() */
+template<class CharOwningContainer, class... Args>
+inline csubstr catrs(append_t, 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());
+}
+
+
+//-----------------------------------------------------------------------------
+
+/// @cond dev
+// terminates the recursion
+template<class CharOwningContainer, class Sep, class... Args>
+inline void catseprs(CharOwningContainer * C4_RESTRICT, Sep const& C4_RESTRICT)
+{
+ return;
+}
+/// @end cond
+
+
+/** 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() */
+template<class CharOwningContainer, class Sep, class... Args>
+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;
+}
+
+/** like c4::catsep(), but create a new container with the result.
+ * @return the requested container */
+template<class CharOwningContainer, class Sep, class... Args>
+inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
+{
+ CharOwningContainer cont;
+ catseprs(&cont, sep, args...);
+ return cont;
+}
+
+
+/// @cond dev
+// terminates the recursion
+template<class CharOwningContainer, class Sep, class... Args>
+inline csubstr catseprs(append_t, CharOwningContainer * C4_RESTRICT, Sep const& C4_RESTRICT)
+{
+ csubstr s;
+ return s;
+}
+/// @endcond
+
+/** 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 formatting_functions */
+template<class CharOwningContainer, class Sep, class... Args>
+inline csubstr catseprs(append_t, 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());
+}
+
+
+//-----------------------------------------------------------------------------
+
+/** 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() */
+template<class CharOwningContainer, class... Args>
+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;
+}
+
+/** like c4::format(), but create a new container with the result.
+ * @return the requested container */
+template<class CharOwningContainer, class... Args>
+inline CharOwningContainer formatrs(csubstr fmt, Args const& C4_RESTRICT ...args)
+{
+ CharOwningContainer cont;
+ formatrs(&cont, fmt, args...);
+ return cont;
+}
+
+/** 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 formatting_functions */
+template<class CharOwningContainer, class... Args>
+inline csubstr formatrs(append_t, 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
+
+#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 <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_ */
+
+
+namespace c4 {
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+/** type of the function to dump characters */
+using DumperPfn = void (*)(csubstr buf);
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+template<DumperPfn dumpfn, class Arg>
+inline size_t dump(substr buf, Arg const& a)
+{
+ size_t sz = to_chars(buf, a); // need to serialize to the buffer
+ if(C4_LIKELY(sz <= buf.len))
+ dumpfn(buf.first(sz));
+ return sz;
+}
+
+template<class DumperFn, class Arg>
+inline size_t dump(DumperFn &&dumpfn, substr buf, Arg const& a)
+{
+ size_t sz = to_chars(buf, a); // need to serialize to the buffer
+ if(C4_LIKELY(sz <= buf.len))
+ dumpfn(buf.first(sz));
+ return sz;
+}
+
+template<DumperPfn dumpfn>
+inline size_t dump(substr buf, csubstr a)
+{
+ if(buf.len)
+ dumpfn(a); // dump directly, no need to serialize to the buffer
+ return 0; // no space was used in the buffer
+}
+
+template<class DumperFn>
+inline size_t dump(DumperFn &&dumpfn, substr buf, csubstr a)
+{
+ if(buf.len)
+ dumpfn(a); // dump directly, no need to serialize to the buffer
+ return 0; // no space was used in the buffer
+}
+
+template<DumperPfn dumpfn, size_t N>
+inline size_t dump(substr buf, const char (&a)[N])
+{
+ if(buf.len)
+ dumpfn(csubstr(a)); // dump directly, no need to serialize to the buffer
+ return 0; // no space was used in the buffer
+}
+
+template<class DumperFn, size_t N>
+inline size_t dump(DumperFn &&dumpfn, substr buf, const char (&a)[N])
+{
+ if(buf.len)
+ dumpfn(csubstr(a)); // dump directly, no need to serialize to the buffer
+ return 0; // no space was used in the buffer
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** */
+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; }
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/// @cond dev
+// terminates the variadic recursion
+template<class DumperFn>
+size_t cat_dump(DumperFn &&, substr)
+{
+ return 0;
+}
+
+// terminates the variadic recursion
+template<DumperPfn dumpfn>
+size_t cat_dump(substr)
+{
+ return 0;
+}
+/// @endcond
+
+/** take the function pointer as a function argument */
+template<class DumperFn, class Arg, class... Args>
+size_t cat_dump(DumperFn &&dumpfn, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ size_t size_for_a = dump(dumpfn, buf, a);
+ if(C4_UNLIKELY(size_for_a > buf.len))
+ buf = buf.first(0); // ensure no more calls
+ size_t size_for_more = cat_dump(dumpfn, buf, more...);
+ return size_for_more > size_for_a ? size_for_more : size_for_a;
+}
+
+/** take the function pointer as a template argument */
+template<DumperPfn dumpfn,class Arg, class... Args>
+size_t cat_dump(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ size_t size_for_a = dump<dumpfn>(buf, a);
+ if(C4_LIKELY(size_for_a > buf.len))
+ buf = buf.first(0); // ensure no more calls
+ size_t size_for_more = cat_dump<dumpfn>(buf, more...);
+ return size_for_more > size_for_a ? size_for_more : size_for_a;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/// @cond dev
+namespace detail {
+
+// terminates the variadic recursion
+template<DumperPfn dumpfn, class Arg>
+DumpResults cat_dump_resume(size_t currarg, DumpResults results, substr buf, Arg const& C4_RESTRICT a)
+{
+ if(C4_LIKELY(results.write_arg(currarg)))
+ {
+ size_t sz = dump<dumpfn>(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 results;
+}
+
+// terminates the variadic recursion
+template<class DumperFn, class Arg>
+DumpResults cat_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a)
+{
+ if(C4_LIKELY(results.write_arg(currarg)))
+ {
+ size_t sz = dump(dumpfn, 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 results;
+}
+
+template<DumperPfn dumpfn, class Arg, class... Args>
+DumpResults cat_dump_resume(size_t currarg, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ results = detail::cat_dump_resume<dumpfn>(currarg, results, buf, a);
+ return detail::cat_dump_resume<dumpfn>(currarg + 1u, results, buf, more...);
+}
+
+template<class DumperFn, class Arg, class... Args>
+DumpResults cat_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ results = detail::cat_dump_resume(currarg, dumpfn, results, buf, a);
+ return detail::cat_dump_resume(currarg + 1u, dumpfn, results, buf, more...);
+}
+} // namespace detail
+/// @endcond
+
+
+template<DumperPfn dumpfn, class Arg, class... Args>
+C4_ALWAYS_INLINE DumpResults cat_dump_resume(DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ if(results.bufsize > buf.len)
+ return results;
+ return detail::cat_dump_resume<dumpfn>(0u, results, buf, a, more...);
+}
+
+template<class DumperFn, class Arg, class... Args>
+C4_ALWAYS_INLINE DumpResults cat_dump_resume(DumperFn &&dumpfn, DumpResults results, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ if(results.bufsize > buf.len)
+ return results;
+ return detail::cat_dump_resume(0u, dumpfn, results, buf, a, more...);
+}
+
+template<DumperPfn dumpfn, class Arg, class... Args>
+C4_ALWAYS_INLINE DumpResults cat_dump_resume(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ return detail::cat_dump_resume<dumpfn>(0u, DumpResults{}, buf, a, more...);
+}
+
+template<class DumperFn, class Arg, class... Args>
+C4_ALWAYS_INLINE DumpResults cat_dump_resume(DumperFn &&dumpfn, substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ return detail::cat_dump_resume(0u, dumpfn, DumpResults{}, buf, a, more...);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/// @cond dev
+// terminate the recursion
+template<class DumperFn, class Sep>
+size_t catsep_dump(DumperFn &&, substr, Sep const& C4_RESTRICT)
+{
+ return 0;
+}
+
+// terminate the recursion
+template<DumperPfn dumpfn, class Sep>
+size_t catsep_dump(substr, Sep const& C4_RESTRICT)
+{
+ return 0;
+}
+/// @endcond
+
+/** take the function pointer as a function argument */
+template<class DumperFn, class Sep, class Arg, class... Args>
+size_t catsep_dump(DumperFn &&dumpfn, substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ size_t sz = dump(dumpfn, buf, a);
+ if(C4_UNLIKELY(sz > buf.len))
+ buf = buf.first(0); // ensure no more calls
+ if C4_IF_CONSTEXPR (sizeof...(more) > 0)
+ {
+ size_t szsep = dump(dumpfn, buf, sep);
+ if(C4_UNLIKELY(szsep > buf.len))
+ buf = buf.first(0); // ensure no more calls
+ sz = sz > szsep ? sz : szsep;
+ }
+ size_t size_for_more = catsep_dump(dumpfn, buf, sep, more...);
+ return size_for_more > sz ? size_for_more : sz;
+}
+
+/** take the function pointer as a template argument */
+template<DumperPfn dumpfn, class Sep, class Arg, class... Args>
+size_t catsep_dump(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ size_t sz = dump<dumpfn>(buf, a);
+ if(C4_UNLIKELY(sz > buf.len))
+ buf = buf.first(0); // ensure no more calls
+ if C4_IF_CONSTEXPR (sizeof...(more) > 0)
+ {
+ size_t szsep = dump<dumpfn>(buf, sep);
+ if(C4_UNLIKELY(szsep > buf.len))
+ buf = buf.first(0); // ensure no more calls
+ sz = sz > szsep ? sz : szsep;
+ }
+ size_t size_for_more = catsep_dump<dumpfn>(buf, sep, more...);
+ return size_for_more > sz ? size_for_more : sz;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/// @cond dev
+namespace detail {
+template<DumperPfn dumpfn, class Arg>
+void catsep_dump_resume_(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Arg const& C4_RESTRICT a)
+{
+ if(C4_LIKELY(results->write_arg(currarg)))
+ {
+ size_t sz = dump<dumpfn>(*buf, a);
+ results->bufsize = sz > results->bufsize ? sz : results->bufsize;
+ if(C4_LIKELY(sz <= buf->len))
+ results->lastok = currarg;
+ else
+ buf->len = 0;
+ }
+}
+
+template<class DumperFn, class Arg>
+void catsep_dump_resume_(size_t currarg, DumperFn &&dumpfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Arg const& C4_RESTRICT a)
+{
+ if(C4_LIKELY(results->write_arg(currarg)))
+ {
+ size_t sz = dump(dumpfn, *buf, a);
+ results->bufsize = sz > results->bufsize ? sz : results->bufsize;
+ if(C4_LIKELY(sz <= buf->len))
+ results->lastok = currarg;
+ else
+ buf->len = 0;
+ }
+}
+
+template<DumperPfn dumpfn, class Sep, class Arg>
+C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& C4_RESTRICT, Arg const& C4_RESTRICT a)
+{
+ detail::catsep_dump_resume_<dumpfn>(currarg, results, buf, a);
+}
+
+template<class DumperFn, class Sep, class Arg>
+C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& C4_RESTRICT, Arg const& C4_RESTRICT a)
+{
+ detail::catsep_dump_resume_(currarg, dumpfn, results, buf, a);
+}
+
+template<DumperPfn dumpfn, class Sep, class Arg, class... Args>
+C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ detail::catsep_dump_resume_<dumpfn>(currarg , results, buf, a);
+ detail::catsep_dump_resume_<dumpfn>(currarg + 1u, results, buf, sep);
+ detail::catsep_dump_resume <dumpfn>(currarg + 2u, results, buf, sep, more...);
+}
+
+template<class DumperFn, class Sep, class Arg, class... Args>
+C4_ALWAYS_INLINE void catsep_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults *C4_RESTRICT results, substr *C4_RESTRICT buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ detail::catsep_dump_resume_(currarg , dumpfn, results, buf, a);
+ detail::catsep_dump_resume_(currarg + 1u, dumpfn, results, buf, sep);
+ detail::catsep_dump_resume (currarg + 2u, dumpfn, results, buf, sep, more...);
+}
+} // namespace detail
+/// @endcond
+
+
+template<DumperPfn dumpfn, class Sep, class... Args>
+C4_ALWAYS_INLINE DumpResults catsep_dump_resume(DumpResults results, substr buf, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...more)
+{
+ detail::catsep_dump_resume<dumpfn>(0u, &results, &buf, sep, more...);
+ return results;
+}
+
+template<class DumperFn, class Sep, class... Args>
+C4_ALWAYS_INLINE DumpResults catsep_dump_resume(DumperFn &&dumpfn, DumpResults results, substr buf, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...more)
+{
+ detail::catsep_dump_resume(0u, dumpfn, &results, &buf, sep, more...);
+ return results;
+}
+
+template<DumperPfn dumpfn, class Sep, class... Args>
+C4_ALWAYS_INLINE DumpResults catsep_dump_resume(substr buf, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...more)
+{
+ DumpResults results;
+ detail::catsep_dump_resume<dumpfn>(0u, &results, &buf, sep, more...);
+ return results;
+}
+
+template<class DumperFn, class Sep, class... Args>
+C4_ALWAYS_INLINE DumpResults catsep_dump_resume(DumperFn &&dumpfn, substr buf, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...more)
+{
+ DumpResults results;
+ detail::catsep_dump_resume(0u, dumpfn, &results, &buf, sep, more...);
+ return results;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** take the function pointer as a function argument */
+template<class DumperFn>
+C4_ALWAYS_INLINE size_t format_dump(DumperFn &&dumpfn, substr buf, csubstr fmt)
+{
+ // we can dump without using buf
+ // but we'll only dump if the buffer is ok
+ if(C4_LIKELY(buf.len > 0 && fmt.len))
+ dumpfn(fmt);
+ return 0u;
+}
+
+/** take the function pointer as a function argument */
+template<DumperPfn dumpfn>
+C4_ALWAYS_INLINE size_t format_dump(substr buf, csubstr fmt)
+{
+ // we can dump without using buf
+ // but we'll only dump if the buffer is ok
+ if(C4_LIKELY(buf.len > 0 && fmt.len > 0))
+ dumpfn(fmt);
+ return 0u;
+}
+
+/** take the function pointer as a function argument */
+template<class DumperFn, class Arg, class... Args>
+size_t format_dump(DumperFn &&dumpfn, substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...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))
+ {
+ if(C4_LIKELY(buf.len > 0 && fmt.len > 0))
+ dumpfn(fmt);
+ return 0u;
+ }
+ if(C4_LIKELY(buf.len > 0 && pos > 0))
+ dumpfn(fmt.first(pos)); // we can dump without using buf
+ fmt = fmt.sub(pos + 2); // skip {} do this before assigning to pos again
+ pos = dump(dumpfn, buf, a);
+ if(C4_UNLIKELY(pos > buf.len))
+ buf.len = 0; // ensure no more calls to dump
+ size_t size_for_more = format_dump(dumpfn, buf, fmt, more...);
+ return size_for_more > pos ? size_for_more : pos;
+}
+
+/** take the function pointer as a template argument */
+template<DumperPfn dumpfn, class Arg, class... Args>
+size_t format_dump(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...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))
+ {
+ if(C4_LIKELY(buf.len > 0 && fmt.len > 0))
+ dumpfn(fmt);
+ return 0u;
+ }
+ if(C4_LIKELY(buf.len > 0 && pos > 0))
+ dumpfn(fmt.first(pos)); // we can dump without using buf
+ fmt = fmt.sub(pos + 2); // skip {} do this before assigning to pos again
+ pos = dump<dumpfn>(buf, a);
+ if(C4_UNLIKELY(pos > buf.len))
+ buf.len = 0; // ensure no more calls to dump
+ size_t size_for_more = format_dump<dumpfn>(buf, fmt, more...);
+ return size_for_more > pos ? size_for_more : pos;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/// @cond dev
+namespace detail {
+
+template<DumperPfn dumpfn>
+DumpResults format_dump_resume(size_t currarg, DumpResults results, substr buf, csubstr fmt)
+{
+ // we can dump without using buf
+ // but we'll only dump if the buffer is ok
+ if(C4_LIKELY(buf.len > 0))
+ {
+ dumpfn(fmt);
+ results.lastok = currarg;
+ }
+ return results;
+}
+
+template<class DumperFn>
+DumpResults format_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults results, substr buf, csubstr fmt)
+{
+ // we can dump without using buf
+ // but we'll only dump if the buffer is ok
+ if(C4_LIKELY(buf.len > 0))
+ {
+ dumpfn(fmt);
+ results.lastok = currarg;
+ }
+ return results;
+}
+
+template<DumperPfn dumpfn, class Arg, class... Args>
+DumpResults format_dump_resume(size_t currarg, DumpResults results, substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ // we need to process the format even if we're not
+ // going to print the first arguments because we're resuming
+ size_t pos = fmt.find("{}"); // @todo use _find_fmt()
+ // we can dump without using buf
+ // but we'll only dump if the buffer is ok
+ if(C4_LIKELY(results.write_arg(currarg)))
+ {
+ if(C4_UNLIKELY(pos == csubstr::npos))
+ {
+ if(C4_LIKELY(buf.len > 0))
+ {
+ results.lastok = currarg;
+ dumpfn(fmt);
+ }
+ return results;
+ }
+ if(C4_LIKELY(buf.len > 0))
+ {
+ results.lastok = currarg;
+ dumpfn(fmt.first(pos));
+ }
+ }
+ fmt = fmt.sub(pos + 2);
+ if(C4_LIKELY(results.write_arg(currarg + 1)))
+ {
+ pos = dump<dumpfn>(buf, a);
+ results.bufsize = pos > results.bufsize ? pos : results.bufsize;
+ if(C4_LIKELY(pos <= buf.len))
+ results.lastok = currarg + 1;
+ else
+ buf.len = 0;
+ }
+ return detail::format_dump_resume<dumpfn>(currarg + 2u, results, buf, fmt, more...);
+}
+/// @endcond
+
+
+template<class DumperFn, class Arg, class... Args>
+DumpResults format_dump_resume(size_t currarg, DumperFn &&dumpfn, DumpResults results, substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
+{
+ // we need to process the format even if we're not
+ // going to print the first arguments because we're resuming
+ size_t pos = fmt.find("{}"); // @todo use _find_fmt()
+ // we can dump without using buf
+ // but we'll only dump if the buffer is ok
+ if(C4_LIKELY(results.write_arg(currarg)))
+ {
+ if(C4_UNLIKELY(pos == csubstr::npos))
+ {
+ if(C4_LIKELY(buf.len > 0))
+ {
+ results.lastok = currarg;
+ dumpfn(fmt);
+ }
+ return results;
+ }
+ if(C4_LIKELY(buf.len > 0))
+ {
+ results.lastok = currarg;
+ dumpfn(fmt.first(pos));
+ }
+ }
+ fmt = fmt.sub(pos + 2);
+ if(C4_LIKELY(results.write_arg(currarg + 1)))
+ {
+ pos = dump(dumpfn, buf, a);
+ results.bufsize = pos > results.bufsize ? pos : results.bufsize;
+ if(C4_LIKELY(pos <= buf.len))
+ results.lastok = currarg + 1;
+ else
+ buf.len = 0;
+ }
+ return detail::format_dump_resume(currarg + 2u, dumpfn, results, buf, fmt, more...);
+}
+} // namespace detail
+
+
+template<DumperPfn dumpfn, class... Args>
+C4_ALWAYS_INLINE DumpResults format_dump_resume(DumpResults results, substr buf, csubstr fmt, Args const& C4_RESTRICT ...more)
+{
+ return detail::format_dump_resume<dumpfn>(0u, results, buf, fmt, more...);
+}
+
+template<class DumperFn, class... Args>
+C4_ALWAYS_INLINE DumpResults format_dump_resume(DumperFn &&dumpfn, DumpResults results, substr buf, csubstr fmt, Args const& C4_RESTRICT ...more)
+{
+ return detail::format_dump_resume(0u, dumpfn, results, buf, fmt, more...);
+}
+
+
+template<DumperPfn dumpfn, class... Args>
+C4_ALWAYS_INLINE DumpResults format_dump_resume(substr buf, csubstr fmt, Args const& C4_RESTRICT ...more)
+{
+ return detail::format_dump_resume<dumpfn>(0u, DumpResults{}, buf, fmt, more...);
+}
+
+template<class DumperFn, class... Args>
+C4_ALWAYS_INLINE DumpResults format_dump_resume(DumperFn &&dumpfn, substr buf, csubstr fmt, Args const& C4_RESTRICT ...more)
+{
+ return detail::format_dump_resume(0u, dumpfn, DumpResults{}, buf, fmt, more...);
+}
+
+
+} // 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 <string.h>
+
+/** @file enum.hpp utilities for enums: convert to/from string
+ */
+
+
+namespace c4 {
+
+//! taken from http://stackoverflow.com/questions/15586163/c11-type-trait-to-differentiate-between-enum-class-and-regular-enum
+template<typename Enum>
+using is_scoped_enum = std::integral_constant<bool, std::is_enum<Enum>::value && !std::is_convertible<Enum, int>::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 Enum>
+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<size_t N>
+ 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;
+
+};
+
+//-----------------------------------------------------------------------------
+/** 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<class Enum>
+EnumSymbols<Enum> const esyms();
+
+
+/** return the offset for an enum symbol class. For example,
+ * eoffs_cls<MyEnumClass>() 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<class Enum>
+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<class Enum>
+size_t eoffs_pfx()
+{
+ return 0;
+}
+
+
+template<class Enum>
+size_t eoffs(EnumOffsetType which)
+{
+ switch(which)
+ {
+ case EOFFS_NONE:
+ return 0;
+ case EOFFS_CLS:
+ return eoffs_cls<Enum>();
+ case EOFFS_PFX:
+ {
+ size_t pfx = eoffs_pfx<Enum>();
+ return pfx > 0 ? pfx : eoffs_cls<Enum>();
+ }
+ default:
+ C4_ERROR("unknown offset type %d", (int)which);
+ return 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+/** 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<class Enum>
+Enum str2e(const char* str)
+{
+ auto pairs = esyms<Enum>();
+ 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<class Enum>
+const char* e2str(Enum e)
+{
+ auto es = esyms<Enum>();
+ 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<class Enum>
+const char* e2stroffs(Enum e, EnumOffsetType ot=EOFFS_PFX)
+{
+ const char *s = e2str<Enum>(e) + eoffs<Enum>(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<class Enum>
+typename EnumSymbols<Enum>::Sym const* EnumSymbols<Enum>::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<class Enum>
+typename EnumSymbols<Enum>::Sym const* EnumSymbols<Enum>::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<class Enum>
+typename EnumSymbols<Enum>::Sym const* EnumSymbols<Enum>::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<class Enum>
+bool EnumSymbols<Enum>::Sym::cmp(const char *s) const
+{
+ if(strcmp(name, s) == 0)
+ return true;
+
+ for(int i = 1; i < _EOFFS_LAST; ++i)
+ {
+ auto o = eoffs<Enum>((EnumOffsetType)i);
+ if(o > 0)
+ if(strcmp(name + o, s) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+template<class Enum>
+bool EnumSymbols<Enum>::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<Enum>((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<class Enum>
+const char* EnumSymbols<Enum>::Sym::name_offs(EnumOffsetType t) const
+{
+ C4_ASSERT(eoffs<Enum>(t) < strlen(name));
+ return name + eoffs<Enum>(t);
+}
+
+} // 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 <cstring>
+//included above:
+//#include <type_traits>
+
+// 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_ */
+
+
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable : 4996) // 'strncpy', fopen, etc: This function or variable may be unsafe
+#elif defined(__clang__)
+#elif defined(__GNUC__)
+# pragma GCC diagnostic push
+# 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<class Enum, class Stream>
+Stream& bm2stream(Stream &s, typename std::underlying_type<Enum>::type bits, EnumOffsetType offst=EOFFS_PFX)
+{
+ using I = typename std::underlying_type<Enum>::type;
+ bool written = false;
+
+ auto const& pairs = esyms<Enum>();
+
+ // 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<I>(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<I>(p.value));
+ if(b == 0)
+ {
+ s << p.name_offs(offst);
+ written = true;
+ break;
+ }
+ }
+ }
+ if(!written)
+ {
+ s << '0';
+ }
+ return s;
+}
+
+template<class Enum, class Stream>
+typename std::enable_if<is_scoped_enum<Enum>::value, Stream&>::type
+bm2stream(Stream &s, Enum value, EnumOffsetType offst=EOFFS_PFX)
+{
+ using I = typename std::underlying_type<Enum>::type;
+ return bm2stream<Enum>(s, static_cast<I>(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<class Enum>
+size_t bm2str
+(
+ typename std::underlying_type<Enum>::type bits,
+ char *str=nullptr,
+ size_t sz=0,
+ EnumOffsetType offst=EOFFS_PFX
+)
+{
+ using I = typename std::underlying_type<Enum>::type;
+ C4_ASSERT((str == nullptr) == (sz == 0));
+
+ auto syms = esyms<Enum>();
+ size_t pos = 0;
+ typename EnumSymbols<Enum>::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<I>(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<class Enum>
+typename std::enable_if<is_scoped_enum<Enum>::value, size_t>::type
+bm2str
+(
+ Enum bits,
+ char *str=nullptr,
+ size_t sz=0,
+ EnumOffsetType offst=EOFFS_PFX
+)
+{
+ using I = typename std::underlying_type<Enum>::type;
+ return bm2str<Enum>(static_cast<I>(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<class Enum>
+typename std::underlying_type<Enum>::type str2bm_read_one(const char *str, size_t sz, bool alnum)
+{
+ using I = typename std::underlying_type<Enum>::type;
+ auto pairs = esyms<Enum>();
+ 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<I>(p->value);
+ }
+ I tmp;
+ 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<class Enum>
+typename std::underlying_type<Enum>::type str2bm(const char *str, size_t sz)
+{
+ using I = typename std::underlying_type<Enum>::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<Enum>(f, static_cast<size_t>(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<Enum>(f, static_cast<size_t>(pc-f), alnum);
+ }
+
+ return val;
+}
+
+/** convert a string to a bitmask */
+template<class Enum>
+typename std::underlying_type<Enum>::type str2bm(const char *str)
+{
+ return str2bm<Enum>(str, strlen(str));
+}
+
+} // namespace c4
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#elif defined(__clang__)
+#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 <algorithm>
+
+namespace c4 {
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+/** 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 T, class I, class SpanImpl>
+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 + _c4sz); }
+ C4_ALWAYS_INLINE const_reverse_iterator crbegin() const noexcept { return reverse_iterator(_c4cptr + _c4sz); }
+
+ 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<size_t>(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<size_t>(sse - b), static_cast<size_t>(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<class I2, class Impl2>
+ C4_ALWAYS_INLINE bool same_span(span_crtp<T, I2, Impl2> const& that) const C4_NOEXCEPT_X
+ {
+ I tsz = szconv<I>(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
+};
+
+//-----------------------------------------------------------------------------
+template<class T, class Il, class Ir, class _Impll, class _Implr>
+inline constexpr bool operator==
+(
+ span_crtp<T, Il, _Impll> const& l,
+ span_crtp<T, Ir, _Implr> 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<class T, class Il, class Ir, class _Impll, class _Implr>
+inline constexpr bool operator!=
+(
+ span_crtp<T, Il, _Impll> const& l,
+ span_crtp<T, Ir, _Implr> const& r
+)
+{
+ return ! (l == r);
+}
+
+//-----------------------------------------------------------------------------
+template<class T, class Il, class Ir, class _Impll, class _Implr>
+inline constexpr bool operator<
+(
+ span_crtp<T, Il, _Impll> const& l,
+ span_crtp<T, Ir, _Implr> const& r
+)
+{
+ return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end());
+}
+
+template<class T, class Il, class Ir, class _Impll, class _Implr>
+inline constexpr bool operator<=
+(
+ span_crtp<T, Il, _Impll> const& l,
+ span_crtp<T, Ir, _Implr> const& r
+)
+{
+ return ! (l > r);
+}
+
+//-----------------------------------------------------------------------------
+template<class T, class Il, class Ir, class _Impll, class _Implr>
+inline constexpr bool operator>
+(
+ span_crtp<T, Il, _Impll> const& l,
+ span_crtp<T, Ir, _Implr> const& r
+)
+{
+ return r < l;
+}
+
+//-----------------------------------------------------------------------------
+template<class T, class Il, class Ir, class _Impll, class _Implr>
+inline constexpr bool operator>=
+(
+ span_crtp<T, Il, _Impll> const& l,
+ span_crtp<T, Ir, _Implr> const& r
+)
+{
+ return ! (l < r);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+/** A non-owning span of elements contiguously stored in memory. */
+template<class T, class I=C4_SIZE_TYPE>
+class span : public span_crtp<T, I, span<T, I>>
+{
+ friend class span_crtp<T, I, span<T, I>>;
+
+ 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<T>::type; //!< NCT=non const type
+ using CT = typename std::add_const<T>::type; //!< CT=const type
+ using const_type = span<CT, I>;
+
+ /// convert automatically to span of const T
+ operator span<CT, I> () const { span<CT, I> 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<size_t N> C4_ALWAYS_INLINE C4_CONSTEXPR14 span (T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N} {}
+ template<size_t N> 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<T> il) noexcept : m_ptr{&*il.begin()}, m_size{il.size()} {}
+ C4_ALWAYS_INLINE C4_CONSTEXPR14 void assign(c4::aggregate_t, std::initializer_list<T> 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<class T, class I=C4_SIZE_TYPE> using cspan = span<const T, I>;
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+/** 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 T, class I=C4_SIZE_TYPE>
+class spanrs : public span_crtp<T, I, spanrs<T, I>>
+{
+ friend class span_crtp<T, I, spanrs<T, I>>;
+
+ 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<size_t>(p - m_ptr);
+ C4_ASSERT(m_capacity >= delta);
+ return spanrs(p, sz, static_cast<size_t>(m_capacity - delta));
+ }
+
+public:
+
+ _c4_DEFINE_ARRAY_TYPES(T, I);
+ using NCT = typename std::remove_const<T>::type; //!< NCT=non const type
+ using CT = typename std::add_const<T>::type; //!< CT=const type
+ using const_type = spanrs<CT, I>;
+
+ /// convert automatically to span of T
+ C4_ALWAYS_INLINE operator span<T, I > () const noexcept { return span<T, I>(m_ptr, m_size); }
+ /// convert automatically to span of const T
+ //C4_ALWAYS_INLINE operator span<CT, I> () const noexcept { span<CT, I> s(m_ptr, m_size); return s; }
+ /// convert automatically to spanrs of const T
+ C4_ALWAYS_INLINE operator spanrs<CT, I> () const noexcept { spanrs<CT, I> 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<size_t N> C4_ALWAYS_INLINE spanrs(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N} {}
+ template<size_t N> 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<T> 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<T> 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<class T, class I=C4_SIZE_TYPE> using cspanrs = spanrs<const T, I>;
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+/** 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 T, class I=C4_SIZE_TYPE>
+class spanrsl : public span_crtp<T, I, spanrsl<T, I>>
+{
+ friend class span_crtp<T, I, spanrsl<T, I>>;
+
+ 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<I>(p - m_ptr);
+ C4_ASSERT(m_capacity >= delta);
+ return spanrsl(p, sz, static_cast<I>(m_capacity - delta), m_offset + delta);
+ }
+
+public:
+
+ _c4_DEFINE_ARRAY_TYPES(T, I);
+ using NCT = typename std::remove_const<T>::type; //!< NCT=non const type
+ using CT = typename std::add_const<T>::type; //!< CT=const type
+ using const_type = spanrsl<CT, I>;
+
+ C4_ALWAYS_INLINE operator span<T, I> () const noexcept { return span<T, I>(m_ptr, m_size); }
+ C4_ALWAYS_INLINE operator spanrs<T, I> () const noexcept { return spanrs<T, I>(m_ptr, m_size, m_capacity); }
+ C4_ALWAYS_INLINE operator spanrsl<CT, I> () const noexcept { return spanrsl<CT, I>(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<size_t N> C4_ALWAYS_INLINE spanrsl(T (&arr)[N]) noexcept : m_ptr{arr}, m_size{N}, m_capacity{N}, m_offset{0} {}
+ template<size_t N> 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<T> 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<T> 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<spanrs>(); */
+ template<template<class, class> class OtherSpanType>
+ C4_ALWAYS_INLINE OtherSpanType<T, I> original()
+ {
+ return OtherSpanType<T, I>(m_ptr - m_offset, m_capacity + m_offset);
+ }
+};
+template<class T, class I=C4_SIZE_TYPE> using cspanrsl = spanrsl<const T, I>;
+
+
+} // 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_ */
+
+
+/// @cond dev
+struct _c4t
+{
+ const char *str;
+ size_t sz;
+ template<size_t N>
+ 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<class T>
+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<class T>
+C4_CONSTEXPR14 cspan<char> type_name()
+{
+ const _c4t p = _c4tn<T>();
+
+#if (0) // _C4_THIS_IS_A_DEBUG_SCAFFOLD
+ 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(" %2d", (int)index);
+ }
+ printf("\n");
+#endif
+
+#if defined(_MSC_VER)
+# if defined(__clang__) // Visual Studio has the clang toolset
+ // example:
+ // ..........................xxx.
+ // _c4t __cdecl _c4tn() [T = int]
+ enum : size_t { tstart = 26, tend = 1};
+
+# 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<char>::size_type tstart = 26, tend = 7;
+
+ const char *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<char> o(p.str + tstart, p.sz - tstart - tend);
+
+ return o;
+}
+
+/** compile-time type name
+ * @overload */
+template<class T>
+C4_CONSTEXPR14 C4_ALWAYS_INLINE cspan<char> type_name(T const&)
+{
+ return type_name<T>();
+}
+
+} // 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 {
+
+/** check that the given buffer is a valid base64 encoding
+ * @see https://en.wikipedia.org/wiki/Base64 */
+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. No writes occur beyond the end of the output buffer.
+ * @see https://en.wikipedia.org/wiki/Base64 */
+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.. No writes occur beyond the end of the output buffer.
+ * @see https://en.wikipedia.org/wiki/Base64 */
+size_t base64_decode(csubstr encoded, blob data);
+
+
+namespace fmt {
+
+template<typename CharOrConstChar>
+struct base64_wrapper_
+{
+ blob_<CharOrConstChar> data;
+ base64_wrapper_() : data() {}
+ base64_wrapper_(blob_<CharOrConstChar> blob) : data(blob) {}
+};
+using const_base64_wrapper = base64_wrapper_<cbyte>;
+using base64_wrapper = base64_wrapper_<byte>;
+
+
+/** mark a variable to be written in base64 format */
+template<class ...Args>
+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<class ...Args>
+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<class ...Args>
+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));
+}
+
+} // namespace fmt
+
+
+/** write a variable in base64 format */
+inline size_t to_chars(substr buf, fmt::const_base64_wrapper b)
+{
+ return base64_encode(buf, b.data);
+}
+
+/** read a variable in base64 format */
+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 <string>
+
+namespace c4 {
+
+//-----------------------------------------------------------------------------
+
+/** get a writeable view to an existing std::string */
+inline c4::substr to_substr(std::string &s)
+{
+ char* data = ! s.empty() ? &s[0] : nullptr;
+ return c4::substr(data, s.size());
+}
+
+/** get a readonly view to an existing std::string */
+inline c4::csubstr to_csubstr(std::string const& s)
+{
+ const char* data = ! s.empty() ? &s[0] : nullptr;
+ return c4::csubstr(data, s.size());
+}
+
+//-----------------------------------------------------------------------------
+
+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();
+ 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)));
+ memcpy(&(*s)[0], buf.str, buf.len);
+ return true;
+}
+
+} // namespace c4
+
+#endif // _C4_STD_STRING_HPP_
+
+
+// (end https://github.com/biojppm/c4core/src/c4/std/string.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<char> 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 <vector>
+
+namespace c4 {
+
+//-----------------------------------------------------------------------------
+
+/** get a substr (writeable string view) of an existing std::vector<char> */
+template<class Alloc>
+c4::substr to_substr(std::vector<char, Alloc> &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<char> */
+template<class Alloc>
+c4::csubstr to_csubstr(std::vector<char, Alloc> 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<char>
+
+template<class Alloc> C4_ALWAYS_INLINE bool operator!= (c4::csubstr ss, std::vector<char, Alloc> const& s) { return ss != to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator== (c4::csubstr ss, std::vector<char, Alloc> const& s) { return ss == to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator>= (c4::csubstr ss, std::vector<char, Alloc> const& s) { return ss >= to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator> (c4::csubstr ss, std::vector<char, Alloc> const& s) { return ss > to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator<= (c4::csubstr ss, std::vector<char, Alloc> const& s) { return ss <= to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator< (c4::csubstr ss, std::vector<char, Alloc> const& s) { return ss < to_csubstr(s); }
+
+template<class Alloc> C4_ALWAYS_INLINE bool operator!= (std::vector<char, Alloc> const& s, c4::csubstr ss) { return ss != to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator== (std::vector<char, Alloc> const& s, c4::csubstr ss) { return ss == to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator>= (std::vector<char, Alloc> const& s, c4::csubstr ss) { return ss <= to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator> (std::vector<char, Alloc> const& s, c4::csubstr ss) { return ss < to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator<= (std::vector<char, Alloc> const& s, c4::csubstr ss) { return ss >= to_csubstr(s); }
+template<class Alloc> C4_ALWAYS_INLINE bool operator< (std::vector<char, Alloc> const& s, c4::csubstr ss) { return ss > to_csubstr(s); }
+
+//-----------------------------------------------------------------------------
+
+/** copy a std::vector<char> to a writeable string view */
+template<class Alloc>
+inline size_t to_chars(c4::substr buf, std::vector<char, Alloc> const& s)
+{
+ C4_ASSERT(!buf.overlaps(to_csubstr(s)));
+ size_t len = buf.len < s.size() ? buf.len : s.size();
+ memcpy(buf.str, s.data(), len);
+ return s.size(); // return the number of needed chars
+}
+
+/** copy a string view to an existing std::vector<char> */
+template<class Alloc>
+inline bool from_chars(c4::csubstr buf, std::vector<char, Alloc> * s)
+{
+ s->resize(buf.len);
+ C4_ASSERT(!buf.overlaps(to_csubstr(*s)));
+ memcpy(&(*s)[0], buf.str, buf.len);
+ 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 <tuple>
+
+/** 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<Curr>(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<Curr>(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<Curr>(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<Curr>(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<Curr>(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<Curr>(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<Types...> const& /*tp*/) { return 0; }
+ static size_t do_uncat(csubstr /*buf*/, std::tuple<Types...> & /*tp*/) { return 0; }
+
+ template< class Sep > static size_t do_catsep_more(substr /*buf*/, Sep const& /*sep*/, std::tuple<Types...> const& /*tp*/) { return 0; }
+ template< class Sep > static size_t do_uncatsep_more(csubstr /*buf*/, Sep & /*sep*/, std::tuple<Types...> & /*tp*/) { return 0; }
+
+ static size_t do_format(substr buf, csubstr fmt, std::tuple<Types...> const& /*tp*/)
+ {
+ return to_chars(buf, fmt);
+ }
+
+ static size_t do_unformat(csubstr buf, csubstr fmt, std::tuple<Types...> 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 <stdint.h>
+#include <random>
+
+
+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(std::random_device &rd)
+ {
+ seed(rd);
+ }
+
+ 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(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(std::random_device &rd)
+ {
+ seed(rd);
+ }
+
+ 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
+
+#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 <type_traits>
+//included above:
+//#include <utility>
+#include <functional>
+
+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<size_t Cap>
+union aligned_storage_helper {
+ struct double1 { double a; };
+ struct double4 { double a[4]; };
+ template<class T> using maybe = typename std::conditional<(Cap >= sizeof(T)), T, char>::type;
+ char real_data[Cap];
+ maybe<int> a;
+ maybe<long> b;
+ maybe<long long> c;
+ maybe<void*> d;
+ maybe<void(*)()> e;
+ maybe<double1> f;
+ maybe<double4> g;
+ maybe<long double> h;
+};
+
+template<size_t Cap, size_t Align = std::alignment_of<aligned_storage_helper<Cap>>::value>
+struct aligned_storage {
+ using type = typename std::aligned_storage<Cap, Align>::type;
+};
+#else
+using std::aligned_storage;
+#endif
+
+template<typename T> struct wrapper
+{
+ using type = T;
+};
+
+template<typename R, typename... Args> 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
+ { throw std::bad_function_call(); }
+ },
+ 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<typename C> explicit constexpr vtable(wrapper<C>) noexcept :
+ invoke_ptr{ [](storage_ptr_t storage_ptr, Args&&... args)
+ noexcept(noexcept(std::declval<C>()(args...))) -> R
+ { return (*static_cast<C*>(storage_ptr))(
+ std::forward<Args>(args)...
+ ); }
+ },
+ copy_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr)
+ noexcept(std::is_nothrow_copy_constructible<C>::value) -> void
+ { new (dst_ptr) C{ (*static_cast<C*>(src_ptr)) }; }
+ },
+ move_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr)
+ noexcept(std::is_nothrow_move_constructible<C>::value) -> void
+ { new (dst_ptr) C{ std::move(*static_cast<C*>(src_ptr)) }; }
+ },
+ destructor_ptr{ [](storage_ptr_t storage_ptr)
+ noexcept -> void
+ { static_cast<C*>(storage_ptr)->~C(); }
+ }
+ {}
+
+ vtable(const vtable&) = delete;
+ vtable(vtable&&) = delete;
+
+ vtable& operator= (const vtable&) = delete;
+ vtable& operator= (vtable&&) = delete;
+
+ ~vtable() = default;
+};
+
+template<size_t DstCap, size_t DstAlign, size_t SrcCap, size_t SrcAlign>
+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<typename inplace_function_detail::aligned_storage<Capacity>::type>::value
+>
+class inplace_function; // unspecified
+
+template<
+ typename R,
+ typename... Args,
+ size_t Capacity,
+ size_t Alignment
+>
+class inplace_function<R(Args...), Capacity, Alignment>
+{
+ static const constexpr inplace_function_detail::vtable<R, Args...> empty_vtable{};
+public:
+ using capacity = std::integral_constant<size_t, Capacity>;
+ using alignment = std::integral_constant<size_t, Alignment>;
+
+ using storage_t = typename inplace_function_detail::aligned_storage<Capacity, Alignment>::type;
+ using vtable_t = inplace_function_detail::vtable<R, Args...>;
+ using vtable_ptr_t = const vtable_t*;
+
+ template <typename, size_t, size_t> friend class inplace_function;
+
+ inplace_function() noexcept :
+ vtable_ptr_{std::addressof(empty_vtable)}
+ {}
+
+ template<
+ typename T,
+ typename C = typename std::decay<T>::type,
+ typename = typename std::enable_if<
+ !(std::is_same<C, inplace_function>::value
+ || std::is_convertible<C, inplace_function>::value)
+ >::type
+ >
+ inplace_function(T&& closure)
+ {
+#if __cplusplus >= 201703L
+ static_assert(std::is_invocable_r<R, C, Args...>::value,
+ "inplace_function cannot be constructed from non-callable type"
+ );
+#endif
+ static_assert(std::is_copy_constructible<C>::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<C>::value == 0,
+ "inplace_function cannot be constructed from object with this (large) alignment"
+ );
+
+ static const vtable_t vt{inplace_function_detail::wrapper<C>{}};
+ vtable_ptr_ = std::addressof(vt);
+
+ new (std::addressof(storage_)) C{std::forward<T>(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>(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<size_t Cap, size_t Align>
+ operator inplace_function<R(Args...), Cap, Align>() 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<size_t Cap, size_t Align>
+ operator inplace_function<R(Args...), Cap, Align>() &&
+ {
+ 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/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 <memory> // for std::align
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wformat-nonliteral"
+#elif defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+namespace c4 {
+
+
+size_t to_chars(substr buf, fmt::const_raw_wrapper r)
+{
+ void * vptr = buf.str;
+ size_t space = buf.len;
+ auto ptr = (decltype(buf.str)) 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<size_t>(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)
+{
+ void * vptr = (void*)buf.str;
+ size_t space = buf.len;
+ auto ptr = (decltype(buf.str)) std::align(r->alignment, r->len, vptr, space);
+ C4_CHECK(ptr != nullptr);
+ C4_CHECK(ptr >= buf.begin() && ptr <= buf.end());
+ //size_t dim = (ptr - buf.str) + r->len;
+ memcpy(r->buf, ptr, r->len);
+ 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 {
+
+/** returns true if the memory overlaps */
+bool mem_overlaps(void const* a, void const* b, size_t sza, size_t szb)
+{
+ if(a < b)
+ {
+ if(size_t(a) + sza > size_t(b))
+ return true;
+ }
+ else if(a > b)
+ {
+ if(size_t(b) + szb > size_t(a))
+ return true;
+ }
+ else if(a == b)
+ {
+ if(sza != 0 && szb != 0)
+ return true;
+ }
+ return false;
+}
+
+/** 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 = (char*)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 <<= 1; // double n
+ }
+ // copy the missing part
+ if(begin + n < end)
+ {
+ ::memcpy(begin + n, begin, static_cast<size_t>(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 <stdlib.h>
+//included above:
+//#include <string.h>
+#if defined(C4_POSIX) || defined(C4_IOS) || defined(C4_MACOS) || defined(C4_ARM)
+# include <errno.h>
+#endif
+#if defined(C4_ARM)
+# include <malloc.h>
+#endif
+
+//included above:
+//#include <memory>
+
+namespace c4 {
+
+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)
+{
+ void *mem;
+#if defined(C4_WIN) || defined(C4_XBOX)
+ mem = ::_aligned_malloc(size, alignment);
+ C4_CHECK(mem != nullptr || size == 0);
+#elif defined(C4_ARM)
+ // 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);
+#elif defined(C4_POSIX) || defined(C4_IOS) || defined(C4_MACOS)
+ // NOTE: alignment needs to be sized in multiples of sizeof(void*)
+ size_t amult = alignment;
+ if(C4_UNLIKELY(alignment < sizeof(void*)))
+ {
+ amult = sizeof(void*);
+ }
+ int ret = ::posix_memalign(&mem, amult, size);
+ if(C4_UNLIKELY(ret))
+ {
+ if(ret == EINVAL)
+ {
+ C4_ERROR("The alignment argument %zu was not a power of two, "
+ "or was not a multiple of sizeof(void*)", alignment);
+ }
+ else if(ret == ENOMEM)
+ {
+ C4_ERROR("There was insufficient memory to fulfill the "
+ "allocation request of %zu bytes (alignment=%lu)", size, size);
+ }
+ return nullptr;
+ }
+#else
+ 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");
+ return nullptr;
+ }
+ 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");
+ mem = nullptr;
+ }
+ 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
+ *
+ * */
+
+} // namespace c4
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+#ifdef C4_REDEFINE_CPPNEW
+#include <new>
+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 {
+
+size_t decode_code_point(uint8_t *C4_RESTRICT buf, size_t buflen, const uint32_t code)
+{
+ C4_UNUSED(buflen);
+ C4_ASSERT(buflen >= 4);
+ 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 >> 6)); /* 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 >> 12))); /* 1110xxxx */
+ buf[1] = (uint8_t)(UINT32_C(0x80) | ((code >> 6) & 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 >> 18))); /* 11110xxx */
+ buf[1] = (uint8_t)(UINT32_C(0x80) | ((code >> 12) & UINT32_C(0x3f))); /* 10xxxxxx */
+ buf[2] = (uint8_t)(UINT32_C(0x80) | ((code >> 6) & 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);
+ 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);
+}
+
+} // 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'
+#elif defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wchar-subscripts"
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+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_[(int)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_[(int)c2s];
+ C4_CHECK((size_t)s2c == i);
+ }
+}
+#endif
+} // namespace detail
+
+
+bool base64_valid(csubstr encoded)
+{
+ if(encoded.len % 4) 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 = (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<c4::byte>(c); } ++wpos; }
+ #define c4appendval_(c, shift)\
+ {\
+ C4_XASSERT(c >= 0);\
+ C4_XASSERT(size_t(c) < sizeof(detail::base64_char_to_sextet_));\
+ val |= static_cast<uint32_t>(detail::base64_char_to_sextet_[(c)]) << ((shift) * 6);\
+ }
+
+ C4_ASSERT(base64_valid(encoded));
+ C4_CHECK(encoded.len % 4 == 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
+
+#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 <windows.h>
+ *
+ * @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 <windows.h>
+// 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 <stdlib.h>
+//included above:
+//#include <stdio.h>
+//included above:
+//#include <stdarg.h>
+
+#define C4_LOGF_ERR(...) fprintf(stderr, __VA_ARGS__); fflush(stderr)
+#define C4_LOGF_WARN(...) fprintf(stderr, __VA_ARGS__); fflush(stderr)
+#define C4_LOGP(msg, ...) 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 <libdbg.h>
+#elif defined(C4_UNIX) || defined(C4_LINUX)
+# include <sys/stat.h>
+//included above:
+//# include <cstring>
+# include <fcntl.h>
+#elif defined(C4_MACOS) || defined(C4_IOS)
+//included above:
+//# include <assert.h>
+# include <stdbool.h>
+# include <sys/types.h>
+# include <sys/sysctl.h>
+#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 <unistd.h>
+#endif
+
+#if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION)
+# include <exception>
+#endif
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wformat-nonliteral"
+#elif defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+
+//-----------------------------------------------------------------------------
+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); // ss.vprintf(fmt, args);
+ va_end(args);
+ msglen = ilen >= 0 && ilen < (int)sizeof(buf) ? static_cast<size_t>(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/*ss.c_strp(), ss.tellp()*/);
+ }
+ }
+
+ if(s_error_flags & ON_ERROR_ABORT)
+ {
+ abort();
+ }
+
+ if(s_error_flags & ON_ERROR_THROW)
+ {
+#if defined(C4_EXCEPTIONS_ENABLED) && defined(C4_ERROR_THROWS_EXCEPTION)
+ throw Exception(buf);
+#else
+ abort();
+#endif
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void handle_warning(srcloc where, const char *fmt, ...)
+{
+ va_list args;
+ char buf[1024]; //sstream<c4::string> ss;
+ va_start(args, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ 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/*ss.c_strp()*/);
+ 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/*ss.c_strp()*/);
+#elif ! defined(C4_ERROR_SHOWS_FUNC)
+ C4_LOGF_WARN("WARNING: %s\n", buf/*ss.c_strp()*/);
+#endif
+ //c4::log.flush();
+}
+
+//-----------------------------------------------------------------------------
+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;
+ //! @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);
+ if (status_fd == -1)
+ {
+ return 0;
+ }
+
+ 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);
+ }
+ }
+ }
+ 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);
+
+ // 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
+
+
+#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/common.hpp
+// https://github.com/biojppm/rapidyaml/src/c4/yml/common.hpp
+//--------------------------------------------------------------------------------
+//********************************************************************************
+
+#ifndef _C4_YML_COMMON_HPP_
+#define _C4_YML_COMMON_HPP_
+
+//included above:
+//#include <cstddef>
+// amalgamate: removed include of
+// https://github.com/biojppm/rapidyaml/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/rapidyaml/src/c4/yml/export.hpp
+//#include <c4/yml/export.hpp>
+#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_ */
+
+
+
+#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)
+#else
+# define RYML_ASSERT(cond)
+# define RYML_ASSERT_MSG(cond, msg)
+#endif
+
+
+#define RYML_CHECK(cond) \
+ do { \
+ if(!(cond)) \
+ { \
+ C4_DEBUG_BREAK(); \
+ c4::yml::error("check failed: " #cond, c4::yml::Location(__FILE__, __LINE__, 0)); \
+ } \
+ } while(0)
+
+#define RYML_CHECK_MSG(cond, msg) \
+ do \
+ { \
+ if(!(cond)) \
+ { \
+ C4_DEBUG_BREAK(); \
+ c4::yml::error(msg ": check failed: " #cond, c4::yml::Location(__FILE__, __LINE__, 0)); \
+ } \
+ } while(0)
+
+
+#if C4_CPP >= 14
+# define RYML_DEPRECATED(msg) [[deprecated(msg)]]
+#else
+# if defined(_MSC_VER)
+# define RYML_DEPRECATED(msg) __declspec(deprecated)
+# else // defined(__GNUC__) || defined(__clang__)
+# define RYML_DEPRECATED(msg) __attribute__((deprecated))
+# endif
+#endif
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+namespace c4 {
+namespace yml {
+
+enum : size_t {
+ /** a null position */
+ npos = size_t(-1),
+ /** an index to none */
+ NONE = 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() : offset(), line(), col() {}
+ //! 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) {}
+};
+
+
+//! a source file position
+struct RYML_EXPORT Location : public LineCol
+{
+ csubstr name;
+
+ operator bool () const { return !name.empty() || line != 0 || offset != 0; }
+
+ Location() : LineCol(), name() {}
+ Location( size_t l, size_t c) : LineCol{ l, c}, name( ) {}
+ Location( csubstr n, size_t l, size_t c) : LineCol{ l, c}, name(n) {}
+ Location( csubstr n, size_t b, size_t l, size_t c) : LineCol{b, l, c}, name(n) {}
+ Location(const char *n, size_t l, size_t c) : LineCol{ l, c}, name(to_csubstr(n)) {}
+ Location(const char *n, size_t b, size_t l, size_t c) : LineCol{b, l, c}, name(to_csubstr(n)) {}
+};
+
+
+//-----------------------------------------------------------------------------
+
+/** the type of the function used to report errors. This function must
+ * interrupt execution, either by raising an exception or calling
+ * std::abort(). */
+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 */
+using pfn_allocate = void* (*)(size_t len, void* hint, void *user_data);
+/** the type of the function used to free memory */
+using pfn_free = void (*)(void* mem, size_t size, void *user_data);
+
+/** trigger an error: call the current error callback. */
+RYML_EXPORT void error(const char *msg, size_t msg_len, Location loc);
+/** @overload error */
+inline void error(const char *msg, size_t msg_len)
+{
+ error(msg, msg_len, Location{});
+}
+/** @overload error */
+template<size_t N>
+inline void error(const char (&msg)[N], Location loc)
+{
+ error(msg, N-1, loc);
+}
+/** @overload error */
+template<size_t N>
+inline void error(const char (&msg)[N])
+{
+ error(msg, N-1, Location{});
+}
+
+//-----------------------------------------------------------------------------
+
+/// a c-style callbacks class
+struct RYML_EXPORT Callbacks
+{
+ void * m_user_data;
+ pfn_allocate m_allocate;
+ pfn_free m_free;
+ pfn_error m_error;
+
+ Callbacks();
+ 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);
+ }
+};
+
+/// get the global callbacks
+RYML_EXPORT Callbacks const& get_callbacks();
+/// set the global callbacks
+RYML_EXPORT void set_callbacks(Callbacks const& c);
+/// set the global callbacks to their defaults
+RYML_EXPORT void reset_callbacks();
+
+/// @cond dev
+#define _RYML_CB_ERR(cb, msg_literal) \
+do \
+{ \
+ const char msg[] = msg_literal; \
+ C4_DEBUG_BREAK(); \
+ (cb).m_error(msg, sizeof(msg), c4::yml::Location(__FILE__, 0, __LINE__, 0), (cb).m_user_data); \
+} while(0)
+#define _RYML_CB_CHECK(cb, cond) \
+ do \
+ { \
+ if(!(cond)) \
+ { \
+ const char msg[] = "check failed: " #cond; \
+ C4_DEBUG_BREAK(); \
+ (cb).m_error(msg, sizeof(msg), c4::yml::Location(__FILE__, 0, __LINE__, 0), (cb).m_user_data); \
+ } \
+ } while(0)
+#ifdef RYML_USE_ASSERT
+#define _RYML_CB_ASSERT(cb, cond) _RYML_CB_CHECK((cb), (cond))
+#else
+#define _RYML_CB_ASSERT(cb, cond) do {} while(0)
+#endif
+#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)
+
+
+
+namespace detail {
+template<int8_t signedval, uint8_t unsignedval>
+struct _charconstant_t
+ : public std::conditional<std::is_signed<char>::value,
+ std::integral_constant<int8_t, signedval>,
+ std::integral_constant<uint8_t, unsignedval>>::type
+{};
+#define _RYML_CHCONST(signedval, unsignedval) ::c4::yml::detail::_charconstant_t<INT8_C(signedval), UINT8_C(unsignedval)>::value
+} // namespace detail
+
+
+namespace detail {
+struct _SubstrWriter
+{
+ substr buf;
+ size_t pos;
+ _SubstrWriter(substr buf_, size_t pos_=0) : buf(buf_), pos(pos_) {}
+ void append(csubstr s)
+ {
+ C4_ASSERT(!s.overlaps(buf));
+ if(pos + s.len <= buf.len)
+ memcpy(buf.str + pos, s.str, s.len);
+ pos += s.len;
+ }
+ void append(char c)
+ {
+ if(pos < buf.len)
+ buf.str[pos] = c;
+ ++pos;
+ }
+ void append_n(char c, size_t numtimes)
+ {
+ if(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() { return pos < buf.len ? buf.sub(pos) : buf.last(0); }
+
+ size_t advance(size_t more) { pos += more; return pos; }
+};
+} // namespace detail
+
+/// @endcond
+
+} // namespace yml
+} // namespace c4
+
+#endif /* _C4_YML_COMMON_HPP_ */
+
+
+// (end https://github.com/biojppm/rapidyaml/src/c4/yml/common.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_
+
+
+// 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_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
+
+// 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_ */
+
+//included above:
+//#include <cmath>
+//included above:
+//#include <limits>
+
+
+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("-Wtype-limits")
+
+
+namespace c4 {
+namespace yml {
+
+struct NodeScalar;
+struct NodeInit;
+struct NodeData;
+class NodeRef;
+class Tree;
+
+
+/** encode a floating point value to a string. */
+template<class T>
+size_t to_chars_float(substr buf, T val)
+{
+ C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal");
+ static_assert(std::is_floating_point<T>::value, "must be floating point");
+ if(C4_UNLIKELY(std::isnan(val)))
+ return to_chars(buf, csubstr(".nan"));
+ else if(C4_UNLIKELY(val == std::numeric_limits<T>::infinity()))
+ return to_chars(buf, csubstr(".inf"));
+ else if(C4_UNLIKELY(val == -std::numeric_limits<T>::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<class T>
+bool from_chars_float(csubstr buf, T *C4_RESTRICT val)
+{
+ static_assert(std::is_floating_point<T>::value, "must be floating point");
+ if(C4_LIKELY(from_chars(buf, val)))
+ {
+ return true;
+ }
+ else if(C4_UNLIKELY(buf == ".nan" || buf == ".NaN" || buf == ".NAN"))
+ {
+ *val = std::numeric_limits<T>::quiet_NaN();
+ return true;
+ }
+ else if(C4_UNLIKELY(buf == ".inf" || buf == ".Inf" || buf == ".INF"))
+ {
+ *val = std::numeric_limits<T>::infinity();
+ return true;
+ }
+ else if(C4_UNLIKELY(buf == "-.inf" || buf == "-.Inf" || buf == "-.INF"))
+ {
+ *val = -std::numeric_limits<T>::infinity();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** 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 {
+ // container types
+ TAG_NONE = 0,
+ 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;
+
+YamlTag_e to_tag(csubstr tag);
+csubstr from_tag(YamlTag_e tag);
+csubstr from_tag_long(YamlTag_e tag);
+csubstr normalize_tag(csubstr tag);
+csubstr normalize_tag_long(csubstr tag);
+
+struct 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 */
+ size_t next_node_id;
+};
+
+#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 types */
+using type_bits = uint64_t;
+
+
+/** a bit mask for marking node types */
+typedef enum : type_bits {
+ // a convenience define, undefined below
+ #define c4bit(v) (type_bits(1) << v)
+ NOTYPE = 0, ///< no node type is set
+ VAL = c4bit(0), ///< a leaf node, has a (possibly empty) value
+ KEY = c4bit(1), ///< is member of a map, must have non-empty key
+ MAP = c4bit(2), ///< a map: a parent of keyvals
+ SEQ = c4bit(3), ///< a seq: a parent of vals
+ DOC = c4bit(4), ///< a document
+ STREAM = c4bit(5)|SEQ, ///< a stream: a seq of docs
+ KEYREF = c4bit(6), ///< a *reference: the key references an &anchor
+ VALREF = c4bit(7), ///< a *reference: the val references an &anchor
+ KEYANCH = c4bit(8), ///< the key has an &anchor
+ VALANCH = c4bit(9), ///< the val has an &anchor
+ KEYTAG = c4bit(10), ///< the key has an explicit tag/type
+ VALTAG = c4bit(11), ///< the val has an explicit tag/type
+ _TYMASK = c4bit(12)-1, // all the bits up to here
+ VALQUO = c4bit(12), ///< the val is quoted by '', "", > or |
+ KEYQUO = c4bit(13), ///< the key is quoted by '', "", > or |
+ KEYVAL = KEY|VAL,
+ KEYSEQ = KEY|SEQ,
+ KEYMAP = KEY|MAP,
+ DOCMAP = DOC|MAP,
+ DOCSEQ = DOC|SEQ,
+ DOCVAL = DOC|VAL,
+ // these flags are from a work in progress and should not be used yet
+ _WIP_STYLE_FLOW_SL = c4bit(14), ///< mark container with single-line flow format (seqs as '[val1,val2], maps as '{key: val, key2: val2}')
+ _WIP_STYLE_FLOW_ML = c4bit(15), ///< mark container with multi-line flow format (seqs as '[val1,\nval2], maps as '{key: val,\nkey2: val2}')
+ _WIP_STYLE_BLOCK = c4bit(16), ///< mark container with block format (seqs as '- val\n', maps as 'key: val')
+ _WIP_KEY_LITERAL = c4bit(17), ///< mark key scalar as multiline, block literal |
+ _WIP_VAL_LITERAL = c4bit(18), ///< mark val scalar as multiline, block literal |
+ _WIP_KEY_FOLDED = c4bit(19), ///< mark key scalar as multiline, block folded >
+ _WIP_VAL_FOLDED = c4bit(20), ///< mark val scalar as multiline, block folded >
+ _WIP_KEY_SQUO = c4bit(21), ///< mark key scalar as single quoted
+ _WIP_VAL_SQUO = c4bit(22), ///< mark val scalar as single quoted
+ _WIP_KEY_DQUO = c4bit(23), ///< mark key scalar as double quoted
+ _WIP_VAL_DQUO = c4bit(24), ///< mark val scalar as double quoted
+ _WIP_KEY_PLAIN = c4bit(25), ///< mark key scalar as plain scalar (unquoted, even when multiline)
+ _WIP_VAL_PLAIN = c4bit(26), ///< mark val scalar as plain scalar (unquoted, even when multiline)
+ _WIP_KEY_STYLE = _WIP_KEY_LITERAL|_WIP_KEY_FOLDED|_WIP_KEY_SQUO|_WIP_KEY_DQUO|_WIP_KEY_PLAIN,
+ _WIP_VAL_STYLE = _WIP_VAL_LITERAL|_WIP_VAL_FOLDED|_WIP_VAL_SQUO|_WIP_VAL_DQUO|_WIP_VAL_PLAIN,
+ _WIP_KEY_FT_NL = c4bit(27), ///< features: mark key scalar as having \n in its contents
+ _WIP_VAL_FT_NL = c4bit(28), ///< features: mark val scalar as having \n in its contents
+ _WIP_KEY_FT_SQ = c4bit(29), ///< features: mark key scalar as having single quotes in its contents
+ _WIP_VAL_FT_SQ = c4bit(30), ///< features: mark val scalar as having single quotes in its contents
+ _WIP_KEY_FT_DQ = c4bit(31), ///< features: mark key scalar as having double quotes in its contents
+ _WIP_VAL_FT_DQ = c4bit(32), ///< features: mark val scalar as having double quotes in its contents
+ #undef c4bit
+} NodeType_e;
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** wraps a NodeType_e element with some syntactic sugar and predicates */
+struct NodeType
+{
+public:
+
+ NodeType_e type;
+
+public:
+
+ C4_ALWAYS_INLINE operator NodeType_e & C4_RESTRICT () { return type; }
+ C4_ALWAYS_INLINE operator NodeType_e const& C4_RESTRICT () const { return type; }
+
+ C4_ALWAYS_INLINE NodeType() : type(NOTYPE) {}
+ C4_ALWAYS_INLINE NodeType(NodeType_e t) : type(t) {}
+ C4_ALWAYS_INLINE NodeType(type_bits t) : type((NodeType_e)t) {}
+
+ C4_ALWAYS_INLINE const char *type_str() const { return type_str(type); }
+ static const char* type_str(NodeType_e t);
+
+ C4_ALWAYS_INLINE void set(NodeType_e t) { type = t; }
+ C4_ALWAYS_INLINE void set(type_bits t) { type = (NodeType_e)t; }
+
+ C4_ALWAYS_INLINE void add(NodeType_e t) { type = (NodeType_e)(type|t); }
+ C4_ALWAYS_INLINE void add(type_bits t) { type = (NodeType_e)(type|t); }
+
+ C4_ALWAYS_INLINE void rem(NodeType_e t) { type = (NodeType_e)(type & ~t); }
+ C4_ALWAYS_INLINE void rem(type_bits t) { type = (NodeType_e)(type & ~t); }
+
+ C4_ALWAYS_INLINE void clear() { type = NOTYPE; }
+
+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
+
+ C4_ALWAYS_INLINE bool is_stream() const { return ((type & STREAM) == STREAM) != 0; }
+ C4_ALWAYS_INLINE bool is_doc() const { return (type & DOC) != 0; }
+ C4_ALWAYS_INLINE bool is_container() const { return (type & (MAP|SEQ|STREAM)) != 0; }
+ C4_ALWAYS_INLINE bool is_map() const { return (type & MAP) != 0; }
+ C4_ALWAYS_INLINE bool is_seq() const { return (type & SEQ) != 0; }
+ C4_ALWAYS_INLINE bool has_val() const { return (type & VAL) != 0; }
+ C4_ALWAYS_INLINE bool has_key() const { return (type & KEY) != 0; }
+ C4_ALWAYS_INLINE bool is_val() const { return (type & (KEYVAL)) == VAL; }
+ C4_ALWAYS_INLINE bool is_keyval() const { return (type & KEYVAL) == KEYVAL; }
+ C4_ALWAYS_INLINE bool has_key_tag() const { return (type & (KEY|KEYTAG)) == (KEY|KEYTAG); }
+ C4_ALWAYS_INLINE bool has_val_tag() const { return ((type & (VALTAG)) && (type & (VAL|MAP|SEQ))); }
+ C4_ALWAYS_INLINE bool has_key_anchor() const { return (type & (KEY|KEYANCH)) == (KEY|KEYANCH); }
+ C4_ALWAYS_INLINE bool is_key_anchor() const { return (type & (KEY|KEYANCH)) == (KEY|KEYANCH); }
+ C4_ALWAYS_INLINE bool has_val_anchor() const { return (type & VALANCH) != 0 && (type & (VAL|SEQ|MAP)) != 0; }
+ C4_ALWAYS_INLINE bool is_val_anchor() const { return (type & VALANCH) != 0 && (type & (VAL|SEQ|MAP)) != 0; }
+ C4_ALWAYS_INLINE bool has_anchor() const { return (type & (KEYANCH|VALANCH)) != 0; }
+ C4_ALWAYS_INLINE bool is_anchor() const { return (type & (KEYANCH|VALANCH)) != 0; }
+ C4_ALWAYS_INLINE bool is_key_ref() const { return (type & KEYREF) != 0; }
+ C4_ALWAYS_INLINE bool is_val_ref() const { return (type & VALREF) != 0; }
+ C4_ALWAYS_INLINE bool is_ref() const { return (type & (KEYREF|VALREF)) != 0; }
+ C4_ALWAYS_INLINE bool is_anchor_or_ref() const { return (type & (KEYANCH|VALANCH|KEYREF|VALREF)) != 0; }
+ C4_ALWAYS_INLINE bool is_key_quoted() const { return (type & (KEY|KEYQUO)) == (KEY|KEYQUO); }
+ C4_ALWAYS_INLINE bool is_val_quoted() const { return (type & (VAL|VALQUO)) == (VAL|VALQUO); }
+ C4_ALWAYS_INLINE bool is_quoted() const { return (type & (KEY|KEYQUO)) == (KEY|KEYQUO) || (type & (VAL|VALQUO)) == (VAL|VALQUO); }
+
+ // these predicates are a work in progress and subject to change. Don't use yet.
+ C4_ALWAYS_INLINE bool default_block() const { return (type & (_WIP_STYLE_BLOCK|_WIP_STYLE_FLOW_ML|_WIP_STYLE_FLOW_SL)) == 0; }
+ C4_ALWAYS_INLINE bool marked_block() const { return (type & (_WIP_STYLE_BLOCK)) != 0; }
+ C4_ALWAYS_INLINE bool marked_flow_sl() const { return (type & (_WIP_STYLE_FLOW_SL)) != 0; }
+ C4_ALWAYS_INLINE bool marked_flow_ml() const { return (type & (_WIP_STYLE_FLOW_ML)) != 0; }
+ C4_ALWAYS_INLINE bool marked_flow() const { return (type & (_WIP_STYLE_FLOW_ML|_WIP_STYLE_FLOW_SL)) != 0; }
+ C4_ALWAYS_INLINE bool key_marked_literal() const { return (type & (_WIP_KEY_LITERAL)) != 0; }
+ C4_ALWAYS_INLINE bool val_marked_literal() const { return (type & (_WIP_VAL_LITERAL)) != 0; }
+ C4_ALWAYS_INLINE bool key_marked_folded() const { return (type & (_WIP_KEY_FOLDED)) != 0; }
+ C4_ALWAYS_INLINE bool val_marked_folded() const { return (type & (_WIP_VAL_FOLDED)) != 0; }
+ C4_ALWAYS_INLINE bool key_marked_squo() const { return (type & (_WIP_KEY_SQUO)) != 0; }
+ C4_ALWAYS_INLINE bool val_marked_squo() const { return (type & (_WIP_VAL_SQUO)) != 0; }
+ C4_ALWAYS_INLINE bool key_marked_dquo() const { return (type & (_WIP_KEY_DQUO)) != 0; }
+ C4_ALWAYS_INLINE bool val_marked_dquo() const { return (type & (_WIP_VAL_DQUO)) != 0; }
+ C4_ALWAYS_INLINE bool key_marked_plain() const { return (type & (_WIP_KEY_PLAIN)) != 0; }
+ C4_ALWAYS_INLINE bool val_marked_plain() const { return (type & (_WIP_VAL_PLAIN)) != 0; }
+
+ #if defined(__clang__)
+ # pragma clang diagnostic pop
+ #elif defined(__GNUC__)
+ # pragma GCC diagnostic pop
+ #endif
+
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** 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
+ inline NodeScalar() noexcept : tag(), scalar(), anchor() {}
+
+ /// initialize as an untagged scalar
+ template<size_t N>
+ inline NodeScalar(const char (&s)[N]) noexcept : tag(), scalar(s), anchor() {}
+ inline NodeScalar(csubstr s ) noexcept : tag(), scalar(s), anchor() {}
+
+ /// initialize as a tagged scalar
+ template<size_t N, size_t M>
+ inline NodeScalar(const char (&t)[N], const char (&s)[N]) noexcept : tag(t), scalar(s), anchor() {}
+ inline 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) 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 mapping member
+ NodeInit( NodeScalar const& k, NodeScalar const& v) : type(KEYVAL), key(k.tag, k.scalar), val(v.tag, v.scalar) { _add_flags(); }
+ /// initialize as a mapping member with explicit type
+ NodeInit(NodeType_e t, NodeScalar const& k, NodeScalar const& v) : type(t ), key(k.tag, k.scalar), val(v.tag, v.scalar) { _add_flags(); }
+ /// initialize as a mapping member with explicit type (eg SEQ or MAP)
+ NodeInit(NodeType_e t, NodeScalar const& k ) : type(t ), key(k.tag, k.scalar), 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;
+
+ size_t m_parent;
+ size_t m_first_child;
+ size_t m_last_child;
+ size_t m_next_sibling;
+ size_t 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(size_t node_capacity, size_t arena_capacity=0) : Tree(node_capacity, arena_capacity, get_callbacks()) {}
+ Tree(size_t node_capacity, size_t arena_capacity, Callbacks const& cb);
+
+ ~Tree();
+
+ Tree(Tree const& that) noexcept;
+ Tree(Tree && that) noexcept;
+
+ Tree& operator= (Tree const& that) noexcept;
+ Tree& operator= (Tree && that) noexcept;
+
+ /** @} */
+
+public:
+
+ /** @name memory and sizing */
+ /** @{ */
+
+ void reserve(size_t node_capacity);
+
+ /** clear the tree and zero every node
+ * @note does NOT clear the arena
+ * @see clear_arena() */
+ void clear();
+ inline void clear_arena() { m_arena_pos = 0; }
+
+ inline bool empty() const { return m_size == 0; }
+
+ inline size_t size () const { return m_size; }
+ inline size_t capacity() const { return m_cap; }
+ inline size_t slack() const { RYML_ASSERT(m_cap >= m_size); return m_cap - m_size; }
+
+ inline size_t arena_size() const { return m_arena_pos; }
+ inline size_t arena_capacity() const { return m_arena.len; }
+ inline size_t arena_slack() const { RYML_ASSERT(m_arena.len >= m_arena_pos); return m_arena.len - m_arena_pos; }
+
+ 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 a
+ size_t id(NodeData const* n) const
+ {
+ if( ! n)
+ {
+ return NONE;
+ }
+ RYML_ASSERT(n >= m_buf && n < m_buf + m_cap);
+ return static_cast<size_t>(n - m_buf);
+ }
+
+ //! get a pointer to a node's NodeData.
+ //! i can be NONE, in which case a nullptr is returned
+ inline NodeData *get(size_t i)
+ {
+ if(i == NONE)
+ return nullptr;
+ RYML_ASSERT(i >= 0 && i < m_cap);
+ return m_buf + i;
+ }
+ //! get a pointer to a node's NodeData.
+ //! i can be NONE, in which case a nullptr is returned.
+ inline NodeData const *get(size_t i) const
+ {
+ if(i == NONE)
+ return nullptr;
+ RYML_ASSERT(i >= 0 && i < m_cap);
+ return m_buf + i;
+ }
+
+ //! An if-less form of get() that demands a valid node index.
+ //! This function is implementation only; use at your own risk.
+ inline NodeData * _p(size_t i) { RYML_ASSERT(i != NONE && i >= 0 && i < m_cap); return m_buf + i; }
+ //! An if-less form of get() that demands a valid node index.
+ //! This function is implementation only; use at your own risk.
+ inline NodeData const * _p(size_t i) const { RYML_ASSERT(i != NONE && i >= 0 && i < m_cap); return m_buf + i; }
+
+ //! Get the id of the root node
+ size_t root_id() { if(m_cap == 0) { reserve(16); } RYML_ASSERT(m_cap > 0 && m_size > 0); return 0; }
+ //! Get the id of the root node
+ size_t root_id() const { RYML_ASSERT(m_cap > 0 && m_size > 0); return 0; }
+
+ //! Get a NodeRef of a node by id
+ NodeRef ref(size_t id);
+ //! Get a NodeRef of a node by id
+ NodeRef const ref(size_t id) const;
+
+ //! Get the root as a NodeRef
+ NodeRef rootref();
+ //! Get the root as a NodeRef
+ NodeRef const rootref() 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.
+ NodeRef const operator[] (csubstr key) const;
+
+ //! find a root child by index: return the root node's @p i-th child as a NodeRef
+ //! @note @i is NOT the node id, but the child's position
+ NodeRef operator[] (size_t i);
+ //! find a root child by index: return the root node's @p i-th child as a NodeRef
+ //! @note @i is NOT the node id, but the child's position
+ NodeRef const operator[] (size_t i) const;
+
+ //! get the i-th document of the stream
+ //! @note @i is NOT the node id, but the doc position within the stream
+ NodeRef docref(size_t i);
+ //! get the i-th document of the stream
+ //! @note @i is NOT the node id, but the doc position within the stream
+ NodeRef const docref(size_t i) const;
+
+ /** @} */
+
+public:
+
+ /** @name node property getters */
+ /** @{ */
+
+ NodeType type(size_t node) const { return _p(node)->m_type; }
+ const char* type_str(size_t node) const { return NodeType::type_str(_p(node)->m_type); }
+
+ csubstr const& key (size_t node) const { RYML_ASSERT(has_key(node)); return _p(node)->m_key.scalar; }
+ csubstr const& key_tag (size_t node) const { RYML_ASSERT(has_key_tag(node)); return _p(node)->m_key.tag; }
+ csubstr const& key_ref (size_t node) const { RYML_ASSERT(is_key_ref(node) && ! has_key_anchor(node)); return _p(node)->m_key.anchor; }
+ csubstr const& key_anchor(size_t node) const { RYML_ASSERT( ! is_key_ref(node) && has_key_anchor(node)); return _p(node)->m_key.anchor; }
+ NodeScalar const& keysc (size_t node) const { RYML_ASSERT(has_key(node)); return _p(node)->m_key; }
+
+ csubstr const& val (size_t node) const { RYML_ASSERT(has_val(node)); return _p(node)->m_val.scalar; }
+ csubstr const& val_tag (size_t node) const { RYML_ASSERT(has_val_tag(node)); return _p(node)->m_val.tag; }
+ csubstr const& val_ref (size_t node) const { RYML_ASSERT(is_val_ref(node) && ! has_val_anchor(node)); return _p(node)->m_val.anchor; }
+ csubstr const& val_anchor(size_t node) const { RYML_ASSERT( ! is_val_ref(node) && has_val_anchor(node)); return _p(node)->m_val.anchor; }
+ NodeScalar const& valsc (size_t node) const { RYML_ASSERT(has_val(node)); return _p(node)->m_val; }
+
+ bool key_is_null(size_t node) const { RYML_ASSERT(has_key(node)); if(is_key_quoted(node)) return false; csubstr s = _p(node)->m_key.scalar; return s == nullptr || s == "~" || s == "null" || s == "Null" || s == "NULL"; }
+ bool val_is_null(size_t node) const { RYML_ASSERT(has_val(node)); if(is_val_quoted(node)) return false; csubstr s = _p(node)->m_val.scalar; return s == nullptr || s == "~" || s == "null" || s == "Null" || s == "NULL"; }
+
+ /** @} */
+
+public:
+
+ /** @name node type predicates */
+ /** @{ */
+
+ C4_ALWAYS_INLINE bool is_stream(size_t node) const { return _p(node)->m_type.is_stream(); }
+ C4_ALWAYS_INLINE bool is_doc(size_t node) const { return _p(node)->m_type.is_doc(); }
+ C4_ALWAYS_INLINE bool is_container(size_t node) const { return _p(node)->m_type.is_container(); }
+ C4_ALWAYS_INLINE bool is_map(size_t node) const { return _p(node)->m_type.is_map(); }
+ C4_ALWAYS_INLINE bool is_seq(size_t node) const { return _p(node)->m_type.is_seq(); }
+ C4_ALWAYS_INLINE bool has_key(size_t node) const { return _p(node)->m_type.has_key(); }
+ C4_ALWAYS_INLINE bool has_val(size_t node) const { return _p(node)->m_type.has_val(); }
+ C4_ALWAYS_INLINE bool is_val(size_t node) const { return _p(node)->m_type.is_val(); }
+ C4_ALWAYS_INLINE bool is_keyval(size_t node) const { return _p(node)->m_type.is_keyval(); }
+ C4_ALWAYS_INLINE bool has_key_tag(size_t node) const { return _p(node)->m_type.has_key_tag(); }
+ C4_ALWAYS_INLINE bool has_val_tag(size_t node) const { return _p(node)->m_type.has_val_tag(); }
+ C4_ALWAYS_INLINE bool has_key_anchor(size_t node) const { return _p(node)->m_type.has_key_anchor(); }
+ C4_ALWAYS_INLINE bool is_key_anchor(size_t node) const { return _p(node)->m_type.is_key_anchor(); }
+ C4_ALWAYS_INLINE bool has_val_anchor(size_t node) const { return _p(node)->m_type.has_val_anchor(); }
+ C4_ALWAYS_INLINE bool is_val_anchor(size_t node) const { return _p(node)->m_type.is_val_anchor(); }
+ C4_ALWAYS_INLINE bool has_anchor(size_t node) const { return _p(node)->m_type.has_anchor(); }
+ C4_ALWAYS_INLINE bool is_anchor(size_t node) const { return _p(node)->m_type.is_anchor(); }
+ C4_ALWAYS_INLINE bool is_key_ref(size_t node) const { return _p(node)->m_type.is_key_ref(); }
+ C4_ALWAYS_INLINE bool is_val_ref(size_t node) const { return _p(node)->m_type.is_val_ref(); }
+ C4_ALWAYS_INLINE bool is_ref(size_t node) const { return _p(node)->m_type.is_ref(); }
+ C4_ALWAYS_INLINE bool is_anchor_or_ref(size_t node) const { return _p(node)->m_type.is_anchor_or_ref(); }
+ C4_ALWAYS_INLINE bool is_key_quoted(size_t node) const { return _p(node)->m_type.is_key_quoted(); }
+ C4_ALWAYS_INLINE bool is_val_quoted(size_t node) const { return _p(node)->m_type.is_val_quoted(); }
+ C4_ALWAYS_INLINE bool is_quoted(size_t node) const { return _p(node)->m_type.is_quoted(); }
+
+ C4_ALWAYS_INLINE bool parent_is_seq(size_t node) const { RYML_ASSERT(has_parent(node)); return is_seq(_p(node)->m_parent); }
+ C4_ALWAYS_INLINE bool parent_is_map(size_t node) const { RYML_ASSERT(has_parent(node)); return is_map(_p(node)->m_parent); }
+
+ /** true when key and val are empty, and has no children */
+ bool empty(size_t node) const { return ! has_children(node) && _p(node)->m_key.empty() && (( ! (_p(node)->m_type & VAL)) || _p(node)->m_val.empty()); }
+ /** true when the node has an anchor named a */
+ bool has_anchor(size_t node, csubstr a) const { return _p(node)->m_key.anchor == a || _p(node)->m_val.anchor == a; }
+
+ /** @} */
+
+public:
+
+ /** @name hierarchy predicates */
+ /** @{ */
+
+ bool is_root(size_t node) const { RYML_ASSERT(_p(node)->m_parent != NONE || node == 0); return _p(node)->m_parent == NONE; }
+
+ bool has_parent(size_t node) const { return _p(node)->m_parent != NONE; }
+
+ bool has_child(size_t node, csubstr key) const { return find_child(node, key) != npos; }
+ bool has_child(size_t node, size_t ch) const { return child_pos(node, ch) != npos; }
+ bool has_children(size_t node) const { return _p(node)->m_first_child != NONE; }
+
+ bool has_sibling(size_t node, size_t sib) const { return is_root(node) ? sib==node : child_pos(_p(node)->m_parent, sib) != npos; }
+ bool has_sibling(size_t node, csubstr key) const { return find_sibling(node, key) != npos; }
+ /** counts with *this */
+ bool has_siblings(size_t /*node*/) const { return true; }
+ /** does not count with *this */
+ bool has_other_siblings(size_t node) const { return is_root(node) ? false : (_p(_p(node)->m_parent)->m_first_child != _p(_p(node)->m_parent)->m_last_child); }
+
+ /** @} */
+
+public:
+
+ /** @name hierarchy getters */
+ /** @{ */
+
+ size_t parent(size_t node) const { return _p(node)->m_parent; }
+
+ size_t prev_sibling(size_t node) const { return _p(node)->m_prev_sibling; }
+ size_t next_sibling(size_t node) const { return _p(node)->m_next_sibling; }
+
+ /** O(#num_children) */
+ size_t num_children(size_t node) const;
+ size_t child_pos(size_t node, size_t ch) const;
+ size_t first_child(size_t node) const { return _p(node)->m_first_child; }
+ size_t last_child(size_t node) const { return _p(node)->m_last_child; }
+ size_t child(size_t node, size_t pos) const;
+ size_t find_child(size_t node, csubstr const& key) const;
+
+ /** O(#num_siblings) */
+ /** counts with this */
+ size_t num_siblings(size_t node) const { return is_root(node) ? 1 : num_children(_p(node)->m_parent); }
+ /** does not count with this */
+ size_t num_other_siblings(size_t node) const { size_t ns = num_siblings(node); RYML_ASSERT(ns > 0); return ns-1; }
+ size_t sibling_pos(size_t node, size_t sib) const { RYML_ASSERT( ! is_root(node) || node == root_id()); return child_pos(_p(node)->m_parent, sib); }
+ size_t first_sibling(size_t node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_first_child; }
+ size_t last_sibling(size_t node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_last_child; }
+ size_t sibling(size_t node, size_t pos) const { return child(_p(node)->m_parent, pos); }
+ size_t find_sibling(size_t node, csubstr const& key) const { return find_child(_p(node)->m_parent, key); }
+
+ size_t doc(size_t i) const { size_t rid = root_id(); RYML_ASSERT(is_stream(rid)); return child(rid, i); } //!< gets the @p i document node index. requires that the root node is a stream.
+
+ /** @} */
+
+public:
+
+ /** @name node modifiers */
+ /** @{ */
+
+ void to_keyval(size_t node, csubstr key, csubstr val, type_bits more_flags=0);
+ void to_map(size_t node, csubstr key, type_bits more_flags=0);
+ void to_seq(size_t node, csubstr key, type_bits more_flags=0);
+ void to_val(size_t node, csubstr val, type_bits more_flags=0);
+ void to_map(size_t node, type_bits more_flags=0);
+ void to_seq(size_t node, type_bits more_flags=0);
+ void to_doc(size_t node, type_bits more_flags=0);
+ void to_stream(size_t node, type_bits more_flags=0);
+
+ void set_key(size_t node, csubstr key) { RYML_ASSERT(has_key(node)); _p(node)->m_key.scalar = key; }
+ void set_val(size_t node, csubstr val) { RYML_ASSERT(has_val(node)); _p(node)->m_val.scalar = val; }
+
+ void set_key_tag(size_t node, csubstr tag) { RYML_ASSERT(has_key(node)); _p(node)->m_key.tag = tag; _add_flags(node, KEYTAG); }
+ void set_val_tag(size_t node, csubstr tag) { RYML_ASSERT(has_val(node) || is_container(node)); _p(node)->m_val.tag = tag; _add_flags(node, VALTAG); }
+
+ void set_key_anchor(size_t node, csubstr anchor) { RYML_ASSERT( ! is_key_ref(node)); _p(node)->m_key.anchor = anchor.triml('&'); _add_flags(node, KEYANCH); }
+ void set_val_anchor(size_t node, csubstr anchor) { RYML_ASSERT( ! is_val_ref(node)); _p(node)->m_val.anchor = anchor.triml('&'); _add_flags(node, VALANCH); }
+ void set_key_ref (size_t node, csubstr ref ) { RYML_ASSERT( ! 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 (size_t node, csubstr ref ) { RYML_ASSERT( ! 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(size_t node) { _p(node)->m_key.anchor.clear(); _rem_flags(node, KEYANCH); }
+ void rem_val_anchor(size_t node) { _p(node)->m_val.anchor.clear(); _rem_flags(node, VALANCH); }
+ void rem_key_ref (size_t node) { _p(node)->m_key.anchor.clear(); _rem_flags(node, KEYREF); }
+ void rem_val_ref (size_t node) { _p(node)->m_val.anchor.clear(); _rem_flags(node, VALREF); }
+ void rem_anchor_ref(size_t 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 node array. */
+ void reorder();
+
+ /** Resolve references (aliases <- anchors) in the tree.
+ *
+ * Dereferencing is opt-in; after parsing, Tree::resolve()
+ * has to be called explicitly for obtaining resolved references in the
+ * tree. This method will resolve all references and substitute the
+ * anchored values in place of the reference.
+ *
+ * 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 the reason for
+ * requiring an explicit call.
+ */
+ void resolve();
+
+ /** @} */
+
+public:
+
+ /** @name tag directives */
+ /** @{ */
+
+ void resolve_tags();
+
+ size_t num_tag_directives() const;
+ size_t add_tag_directive(TagDirective const& td);
+ void clear_tag_directives();
+
+ size_t resolve_tag(substr output, csubstr tag, size_t node_id) const;
+ csubstr resolve_tag_sub(substr output, csubstr tag, size_t node_id) const
+ {
+ size_t needed = resolve_tag(output, tag, node_id);
+ return needed <= output.len ? output.first(needed) : output;
+ }
+
+ using tag_directive_const_iterator = TagDirective const*;
+ tag_directive_const_iterator begin_tag_directives() const { return m_tag_directives; }
+ tag_directive_const_iterator end_tag_directives() const { return m_tag_directives + num_tag_directives(); }
+
+ struct TagDirectiveProxy
+ {
+ tag_directive_const_iterator b, e;
+ tag_directive_const_iterator begin() const { return b; }
+ tag_directive_const_iterator end() const { return e; }
+ };
+
+ TagDirectiveProxy tag_directives() const { return TagDirectiveProxy{begin_tag_directives(), end_tag_directives()}; }
+
+ /** @} */
+
+public:
+
+ /** @name modifying hierarchy */
+ /** @{ */
+
+ /** create and insert a new child of "parent". insert after the (to-be)
+ * sibling "after", which must be a child of "parent". To insert as the
+ * first child, set after to NONE */
+ inline size_t insert_child(size_t parent, size_t after)
+ {
+ RYML_ASSERT(parent != NONE);
+ RYML_ASSERT(is_container(parent) || is_root(parent));
+ RYML_ASSERT(after == NONE || has_child(parent, after));
+ size_t child = _claim();
+ _set_hierarchy(child, parent, after);
+ return child;
+ }
+ inline size_t prepend_child(size_t parent) { return insert_child(parent, NONE); }
+ inline size_t append_child(size_t parent) { return insert_child(parent, last_child(parent)); }
+
+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"
+ inline size_t insert_sibling(size_t node, size_t after)
+ {
+ RYML_ASSERT(node != NONE);
+ RYML_ASSERT( ! is_root(node));
+ RYML_ASSERT(parent(node) != NONE);
+ RYML_ASSERT(after == NONE || (has_sibling(node, after) && has_sibling(after, node)));
+ RYML_ASSERT(get(node) != nullptr);
+ return insert_child(get(node)->m_parent, after);
+ }
+ inline size_t prepend_sibling(size_t node) { return insert_sibling(node, NONE); }
+ inline size_t append_sibling(size_t node) { return insert_sibling(node, last_sibling(node)); }
+
+public:
+
+ /** remove an entire branch at once: ie remove the children and the node itself */
+ inline void remove(size_t node)
+ {
+ remove_children(node);
+ _release(node);
+ }
+
+ /** remove all the node's children, but keep the node itself */
+ void remove_children(size_t 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(size_t node, NodeType type);
+
+ bool change_type(size_t 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(size_t node, size_t after);
+
+ /** change the node's parent and position */
+ void move(size_t node, size_t new_parent, size_t after);
+
+ /** change the node's parent and position to a different tree
+ * @return the index of the new node in the destination tree */
+ size_t move(Tree * src, size_t node, size_t new_parent, size_t 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 */
+ size_t duplicate(size_t node, size_t new_parent, size_t 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 */
+ size_t duplicate(Tree const* src, size_t node, size_t new_parent, size_t after);
+
+ /** recursively duplicate the node's children (but not the node)
+ * @return the index of the last duplicated child */
+ size_t duplicate_children(size_t node, size_t parent, size_t 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 */
+ size_t duplicate_children(Tree const* src, size_t node, size_t parent, size_t after);
+
+ void duplicate_contents(size_t node, size_t where);
+ void duplicate_contents(Tree const* src, size_t node, size_t where);
+
+ /** 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. */
+ size_t duplicate_children_no_rep(size_t node, size_t parent, size_t after);
+ size_t duplicate_children_no_rep(Tree const* src, size_t node, size_t parent, size_t after);
+
+public:
+
+ void merge_with(Tree const* src, size_t src_node=NONE, size_t dst_root=NONE);
+
+ /** @} */
+
+public:
+
+ /** @name internal string arena */
+ /** @{ */
+
+ /** get the current size of the tree's internal arena */
+ size_t arena_pos() const { return m_arena_pos; }
+
+ /** get the current arena */
+ substr arena() const { return m_arena.first(m_arena_pos); }
+
+ /** 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 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.
+ * @see alloc_arena() */
+ template<class T>
+ typename std::enable_if<!std::is_floating_point<T>::value, csubstr>::type
+ to_arena(T const& C4_RESTRICT a)
+ {
+ 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_ASSERT(num <= rem.len);
+ }
+ rem = _request_span(num);
+ return rem;
+ }
+
+ /** 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.
+ * @see alloc_arena() */
+ template<class T>
+ typename std::enable_if<std::is_floating_point<T>::value, csubstr>::type
+ to_arena(T const& C4_RESTRICT a)
+ {
+ 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_ASSERT(num <= rem.len);
+ }
+ rem = _request_span(num);
+ return rem;
+ }
+
+ /** 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.
+ * @see alloc_arena() */
+ substr copy_to_arena(csubstr s)
+ {
+ substr cp = alloc_arena(s.len);
+ RYML_ASSERT(cp.len == s.len);
+ RYML_ASSERT(!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
+ 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. */
+ 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
+ * @note 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_ASSERT(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_pos + 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)
+ {
+ 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_ASSERT(m_arena.is_super(s));
+ RYML_ASSERT(m_arena.sub(0, m_arena_pos).is_super(s));
+ auto pos = (s.str - m_arena.str);
+ substr r(next_arena.str + pos, s.len);
+ RYML_ASSERT(r.str - next_arena.str == pos);
+ RYML_ASSERT(next_arena.sub(0, m_arena_pos).is_super(r));
+ return r;
+ }
+
+public:
+
+ /** @name lookup */
+ /** @{ */
+
+ struct lookup_result
+ {
+ size_t target;
+ size_t closest;
+ size_t path_pos;
+ csubstr path;
+
+ inline operator bool() const { return target != NONE; }
+
+ lookup_result() : target(NONE), closest(NONE), path_pos(0), path() {}
+ lookup_result(csubstr path_, size_t 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, size_t 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() */
+ size_t lookup_path_or_modify(csubstr default_value, csubstr path, size_t 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() */
+ size_t lookup_path_or_modify(Tree const *src, size_t src_node, csubstr path, size_t 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) {}
+ inline operator bool() const { return type != NOTYPE; }
+ bool is_index() const { return value.begins_with('[') && value.ends_with(']'); }
+ };
+
+ size_t _lookup_path_or_create(csubstr path, size_t start);
+
+ void _lookup_path (lookup_result *r) const;
+ void _lookup_path_modify(lookup_result *r);
+
+ size_t _next_node (lookup_result *r, _lookup_path_token *parent) const;
+ size_t _next_node_modify(lookup_result *r, _lookup_path_token *parent);
+
+ void _advance(lookup_result *r, size_t more) const;
+
+ _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);
+
+ void _relocate(substr next_arena);
+
+public:
+
+ #if ! RYML_USE_ASSERT
+ C4_ALWAYS_INLINE void _check_next_flags(size_t, type_bits) {}
+ #else
+ void _check_next_flags(size_t node, type_bits f)
+ {
+ auto 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_ASSERT(!is_root(node));
+ auto pid = parent(node); C4_UNUSED(pid);
+ RYML_ASSERT(is_map(pid));
+ }
+ if((f & VAL) && !is_root(node))
+ {
+ auto pid = parent(node); C4_UNUSED(pid);
+ RYML_ASSERT(is_map(pid) || is_seq(pid));
+ }
+ }
+ #endif
+
+ inline void _set_flags(size_t node, NodeType_e f) { _check_next_flags(node, f); _p(node)->m_type = f; }
+ inline void _set_flags(size_t node, type_bits f) { _check_next_flags(node, f); _p(node)->m_type = f; }
+
+ inline void _add_flags(size_t 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; }
+ inline void _add_flags(size_t node, type_bits f) { NodeData *d = _p(node); f |= d->m_type; _check_next_flags(node, f); d->m_type = f; }
+
+ inline void _rem_flags(size_t 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; }
+ inline void _rem_flags(size_t 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(size_t node, csubstr key, type_bits more_flags=0)
+ {
+ _p(node)->m_key.scalar = key;
+ _add_flags(node, KEY|more_flags);
+ }
+ void _set_key(size_t node, NodeScalar const& key, type_bits more_flags=0)
+ {
+ _p(node)->m_key = key;
+ _add_flags(node, KEY|more_flags);
+ }
+
+ void _set_val(size_t node, csubstr val, type_bits more_flags=0)
+ {
+ RYML_ASSERT(num_children(node) == 0);
+ RYML_ASSERT(!is_seq(node) && !is_map(node));
+ _p(node)->m_val.scalar = val;
+ _add_flags(node, VAL|more_flags);
+ }
+ void _set_val(size_t node, NodeScalar const& val, type_bits more_flags=0)
+ {
+ RYML_ASSERT(num_children(node) == 0);
+ RYML_ASSERT( ! is_container(node));
+ _p(node)->m_val = val;
+ _add_flags(node, VAL|more_flags);
+ }
+
+ void _set(size_t node, NodeInit const& i)
+ {
+ RYML_ASSERT(i._check());
+ NodeData *n = _p(node);
+ RYML_ASSERT(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(size_t in)
+ {
+ NodeData const* n = _p(in);
+ size_t 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(size_t node)
+ {
+ RYML_ASSERT(is_seq(node));
+ for(size_t 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);
+ }
+
+ size_t _do_reorder(size_t *node, size_t count);
+
+ void _swap(size_t n_, size_t m_);
+ void _swap_props(size_t n_, size_t m_);
+ void _swap_hierarchy(size_t n_, size_t m_);
+ void _copy_hierarchy(size_t dst_, size_t src_);
+
+ void _copy_props(size_t dst_, size_t src_)
+ {
+ auto & C4_RESTRICT dst = *_p(dst_);
+ auto const& C4_RESTRICT src = *_p(src_);
+ dst.m_type = src.m_type;
+ dst.m_key = src.m_key;
+ dst.m_val = src.m_val;
+ }
+
+ void _copy_props_wo_key(size_t dst_, size_t src_)
+ {
+ auto & C4_RESTRICT dst = *_p(dst_);
+ auto const& C4_RESTRICT src = *_p(src_);
+ dst.m_type = src.m_type;
+ dst.m_val = src.m_val;
+ }
+
+ void _copy_props(size_t dst_, Tree const* that_tree, size_t 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_wo_key(size_t dst_, Tree const* that_tree, size_t src_)
+ {
+ auto & C4_RESTRICT dst = *_p(dst_);
+ auto const& C4_RESTRICT src = *that_tree->_p(src_);
+ dst.m_type = src.m_type;
+ dst.m_val = src.m_val;
+ }
+
+ inline void _clear_type(size_t node)
+ {
+ _p(node)->m_type = NOTYPE;
+ }
+
+ inline void _clear(size_t 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;
+ }
+
+ inline void _clear_key(size_t node)
+ {
+ _p(node)->m_key.clear();
+ _rem_flags(node, KEY);
+ }
+
+ inline void _clear_val(size_t node)
+ {
+ _p(node)->m_key.clear();
+ _rem_flags(node, VAL);
+ }
+
+private:
+
+ void _clear_range(size_t first, size_t num);
+
+ size_t _claim();
+ void _claim_root();
+ void _release(size_t node);
+ void _free_list_add(size_t node);
+ void _free_list_rem(size_t node);
+
+ void _set_hierarchy(size_t node, size_t parent, size_t after_sibling);
+ void _rem_hierarchy(size_t node);
+
+public:
+
+ // members are exposed, but you should NOT access them directly
+
+ NodeData * m_buf;
+ size_t m_cap;
+
+ size_t m_size;
+
+ size_t m_free_head;
+ size_t m_free_tail;
+
+ substr m_arena;
+ size_t m_arena_pos;
+
+ Callbacks m_callbacks;
+
+ TagDirective m_tag_directives[RYML_MAX_TAG_DIRECTIVES];
+
+};
+
+} // 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
+ * @see NodeRef */
+
+//included above:
+//#include <cstddef>
+
+// 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 __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#if 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 {
+
+template<class K> struct Key { K & k; };
+template<> struct Key<fmt::const_base64_wrapper> { fmt::const_base64_wrapper wrapper; };
+template<> struct Key<fmt::base64_wrapper> { fmt::base64_wrapper wrapper; };
+
+template<class K> C4_ALWAYS_INLINE Key<K> key(K & k) { return Key<K>{k}; }
+C4_ALWAYS_INLINE Key<fmt::const_base64_wrapper> key(fmt::const_base64_wrapper w) { return {w}; }
+C4_ALWAYS_INLINE Key<fmt::base64_wrapper> key(fmt::base64_wrapper w) { return {w}; }
+
+template<class T> void write(NodeRef *n, T const& v);
+
+template<class T>
+typename std::enable_if< ! std::is_floating_point<T>::value, bool>::type
+read(NodeRef const& n, T *v);
+
+template<class T>
+typename std::enable_if< std::is_floating_point<T>::value, bool>::type
+read(NodeRef const& n, T *v);
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** a reference to a node in an existing yaml tree, offering a more
+ * convenient API than the index-based API used in the tree. */
+class RYML_EXPORT NodeRef
+{
+private:
+
+ // require valid: a helper macro, undefined at the end
+ #define _C4RV() RYML_ASSERT(valid() && !is_seed())
+
+ Tree *C4_RESTRICT m_tree;
+ size_t 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;
+
+public:
+
+ /** @name node construction */
+ /** @{ */
+
+ NodeRef() : m_tree(nullptr), m_id(NONE), m_seed() { _clear_seed(); }
+ NodeRef(Tree &t) : m_tree(&t), m_id(t .root_id()), m_seed() { _clear_seed(); }
+ NodeRef(Tree *t) : m_tree(t ), m_id(t->root_id()), m_seed() { _clear_seed(); }
+ NodeRef(Tree *t, size_t id) : m_tree(t), m_id(id), m_seed() { _clear_seed(); }
+ NodeRef(Tree *t, size_t id, size_t seed_pos) : m_tree(t), m_id(id), m_seed() { m_seed.str = nullptr; m_seed.len = seed_pos; }
+ NodeRef(Tree *t, size_t id, csubstr seed_key) : m_tree(t), m_id(id), m_seed(seed_key) {}
+ NodeRef(std::nullptr_t) : m_tree(nullptr), m_id(NONE), m_seed() {}
+
+ NodeRef(NodeRef const&) = default;
+ NodeRef(NodeRef &&) = default;
+
+ NodeRef& operator= (NodeRef const&) = default;
+ NodeRef& operator= (NodeRef &&) = default;
+
+ /** @} */
+
+public:
+
+ inline Tree * tree() { return m_tree; }
+ inline Tree const* tree() const { return m_tree; }
+
+ inline size_t id() const { return m_id; }
+
+ inline NodeData * get() { return m_tree->get(m_id); }
+ inline NodeData const* get() const { return m_tree->get(m_id); }
+
+ inline bool operator== (NodeRef const& that) const { _C4RV(); RYML_ASSERT(that.valid() && !that.is_seed()); RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; }
+ inline bool operator!= (NodeRef const& that) const { return ! this->operator==(that); }
+
+ inline bool operator== (std::nullptr_t) const { return m_tree == nullptr || m_id == NONE || is_seed(); }
+ inline bool operator!= (std::nullptr_t) const { return ! this->operator== (nullptr); }
+
+ inline bool operator== (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; }
+ inline bool operator!= (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; }
+
+ //inline operator bool () const { return m_tree == nullptr || m_id == NONE || is_seed(); }
+
+public:
+
+ inline bool valid() const { return m_tree != nullptr && m_id != NONE; }
+ inline bool is_seed() const { return m_seed.str != nullptr || m_seed.len != NONE; }
+
+ inline void _clear_seed() { /*do this manually or an assert is triggered*/ m_seed.str = nullptr; m_seed.len = NONE; }
+
+public:
+
+ /** @name node property getters */
+ /** @{ */
+
+ inline NodeType type() const { _C4RV(); return m_tree->type(m_id); }
+ inline const char* type_str() const { _C4RV(); RYML_ASSERT(valid() && ! is_seed()); return m_tree->type_str(m_id); }
+
+ inline csubstr key() const { _C4RV(); return m_tree->key(m_id); }
+ inline csubstr key_tag() const { _C4RV(); return m_tree->key_tag(m_id); }
+ inline csubstr key_ref() const { _C4RV(); return m_tree->key_ref(m_id); }
+ inline csubstr key_anchor() const { _C4RV(); return m_tree->key_anchor(m_id); }
+ inline NodeScalar keysc() const { _C4RV(); return m_tree->keysc(m_id); }
+
+ inline csubstr val() const { _C4RV(); return m_tree->val(m_id); }
+ inline csubstr val_tag() const { _C4RV(); return m_tree->val_tag(m_id); }
+ inline csubstr val_ref() const { _C4RV(); return m_tree->val_ref(m_id); }
+ inline csubstr val_anchor() const { _C4RV(); return m_tree->val_anchor(m_id); }
+ inline NodeScalar valsc() const { _C4RV(); return m_tree->valsc(m_id); }
+
+ inline bool key_is_null() const { _C4RV(); return m_tree->key_is_null(m_id); }
+ inline bool val_is_null() const { _C4RV(); return m_tree->val_is_null(m_id); }
+
+ /** decode the base64-encoded key deserialize 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;
+ /** decode the base64-encoded key deserialize 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;
+
+ /** @} */
+
+public:
+
+ /** @name node property predicates */
+ /** @{ */
+
+ C4_ALWAYS_INLINE bool is_stream() const { _C4RV(); return m_tree->is_stream(m_id); }
+ C4_ALWAYS_INLINE bool is_doc() const { _C4RV(); return m_tree->is_doc(m_id); }
+ C4_ALWAYS_INLINE bool is_container() const { _C4RV(); return m_tree->is_container(m_id); }
+ C4_ALWAYS_INLINE bool is_map() const { _C4RV(); return m_tree->is_map(m_id); }
+ C4_ALWAYS_INLINE bool is_seq() const { _C4RV(); return m_tree->is_seq(m_id); }
+ C4_ALWAYS_INLINE bool has_val() const { _C4RV(); return m_tree->has_val(m_id); }
+ C4_ALWAYS_INLINE bool has_key() const { _C4RV(); return m_tree->has_key(m_id); }
+ C4_ALWAYS_INLINE bool is_val() const { _C4RV(); return m_tree->is_val(m_id); }
+ C4_ALWAYS_INLINE bool is_keyval() const { _C4RV(); return m_tree->is_keyval(m_id); }
+ C4_ALWAYS_INLINE bool has_key_tag() const { _C4RV(); return m_tree->has_key_tag(m_id); }
+ C4_ALWAYS_INLINE bool has_val_tag() const { _C4RV(); return m_tree->has_val_tag(m_id); }
+ C4_ALWAYS_INLINE bool has_key_anchor() const { _C4RV(); return m_tree->has_key_anchor(m_id); }
+ C4_ALWAYS_INLINE bool is_key_anchor() const { _C4RV(); return m_tree->is_key_anchor(m_id); }
+ C4_ALWAYS_INLINE bool has_val_anchor() const { _C4RV(); return m_tree->has_val_anchor(m_id); }
+ C4_ALWAYS_INLINE bool is_val_anchor() const { _C4RV(); return m_tree->is_val_anchor(m_id); }
+ C4_ALWAYS_INLINE bool has_anchor() const { _C4RV(); return m_tree->has_anchor(m_id); }
+ C4_ALWAYS_INLINE bool is_anchor() const { _C4RV(); return m_tree->is_anchor(m_id); }
+ C4_ALWAYS_INLINE bool is_key_ref() const { _C4RV(); return m_tree->is_key_ref(m_id); }
+ C4_ALWAYS_INLINE bool is_val_ref() const { _C4RV(); return m_tree->is_val_ref(m_id); }
+ C4_ALWAYS_INLINE bool is_ref() const { _C4RV(); return m_tree->is_ref(m_id); }
+ C4_ALWAYS_INLINE bool is_anchor_or_ref() const { _C4RV(); return m_tree->is_anchor_or_ref(m_id); }
+ C4_ALWAYS_INLINE bool is_key_quoted() const { _C4RV(); return m_tree->is_key_quoted(m_id); }
+ C4_ALWAYS_INLINE bool is_val_quoted() const { _C4RV(); return m_tree->is_val_quoted(m_id); }
+ C4_ALWAYS_INLINE bool is_quoted() const { _C4RV(); return m_tree->is_quoted(m_id); }
+
+ C4_ALWAYS_INLINE bool parent_is_seq() const { _C4RV(); return m_tree->parent_is_seq(m_id); }
+ C4_ALWAYS_INLINE bool parent_is_map() const { _C4RV(); return m_tree->parent_is_map(m_id); }
+
+ /** true when name and value are empty, and has no children */
+ C4_ALWAYS_INLINE bool empty() const { _C4RV(); return m_tree->empty(m_id); }
+
+ /** @} */
+
+public:
+
+ /** @name hierarchy predicates */
+ /** @{ */
+
+ inline bool is_root() const { _C4RV(); return m_tree->is_root(m_id); }
+ inline bool has_parent() const { _C4RV(); return m_tree->has_parent(m_id); }
+
+ inline bool has_child(NodeRef const& ch) const { _C4RV(); return m_tree->has_child(m_id, ch.m_id); }
+ inline bool has_child(csubstr name) const { _C4RV(); return m_tree->has_child(m_id, name); }
+ inline bool has_children() const { _C4RV(); return m_tree->has_children(m_id); }
+
+ inline bool has_sibling(NodeRef const& n) const { _C4RV(); return m_tree->has_sibling(m_id, n.m_id); }
+ inline bool has_sibling(csubstr name) const { _C4RV(); return m_tree->has_sibling(m_id, name); }
+ /** counts with this */
+ inline bool has_siblings() const { _C4RV(); return m_tree->has_siblings(m_id); }
+ /** does not count with this */
+ inline bool has_other_siblings() const { _C4RV(); return m_tree->has_other_siblings(m_id); }
+
+ /** @} */
+
+public:
+
+ /** @name hierarchy getters */
+ /** @{ */
+
+ NodeRef parent() { _C4RV(); return {m_tree, m_tree->parent(m_id)}; }
+ NodeRef const parent() const { _C4RV(); return {m_tree, m_tree->parent(m_id)}; }
+
+ NodeRef prev_sibling() { _C4RV(); return {m_tree, m_tree->prev_sibling(m_id)}; }
+ NodeRef const prev_sibling() const { _C4RV(); return {m_tree, m_tree->prev_sibling(m_id)}; }
+
+ NodeRef next_sibling() { _C4RV(); return {m_tree, m_tree->next_sibling(m_id)}; }
+ NodeRef const next_sibling() const { _C4RV(); return {m_tree, m_tree->next_sibling(m_id)}; }
+
+ /** O(#num_children) */
+ size_t num_children() const { _C4RV(); return m_tree->num_children(m_id); }
+ size_t child_pos(NodeRef const& n) const { _C4RV(); return m_tree->child_pos(m_id, n.m_id); }
+ NodeRef first_child() { _C4RV(); return {m_tree, m_tree->first_child(m_id)}; }
+ NodeRef const first_child() const { _C4RV(); return {m_tree, m_tree->first_child(m_id)}; }
+ NodeRef last_child () { _C4RV(); return {m_tree, m_tree->last_child (m_id)}; }
+ NodeRef const last_child () const { _C4RV(); return {m_tree, m_tree->last_child (m_id)}; }
+ NodeRef child(size_t pos) { _C4RV(); return {m_tree, m_tree->child(m_id, pos)}; }
+ NodeRef const child(size_t pos) const { _C4RV(); return {m_tree, m_tree->child(m_id, pos)}; }
+ NodeRef find_child(csubstr name) { _C4RV(); return {m_tree, m_tree->find_child(m_id, name)}; }
+ NodeRef const find_child(csubstr name) const { _C4RV(); return {m_tree, m_tree->find_child(m_id, name)}; }
+
+ /** O(#num_siblings) */
+ size_t num_siblings() const { _C4RV(); return m_tree->num_siblings(m_id); }
+ size_t num_other_siblings() const { _C4RV(); return m_tree->num_other_siblings(m_id); }
+ size_t sibling_pos(NodeRef const& n) const { _C4RV(); return m_tree->child_pos(m_tree->parent(m_id), n.m_id); }
+ NodeRef first_sibling() { _C4RV(); return {m_tree, m_tree->first_sibling(m_id)}; }
+ NodeRef const first_sibling() const { _C4RV(); return {m_tree, m_tree->first_sibling(m_id)}; }
+ NodeRef last_sibling () { _C4RV(); return {m_tree, m_tree->last_sibling(m_id)}; }
+ NodeRef const last_sibling () const { _C4RV(); return {m_tree, m_tree->last_sibling(m_id)}; }
+ NodeRef sibling(size_t pos) { _C4RV(); return {m_tree, m_tree->sibling(m_id, pos)}; }
+ NodeRef const sibling(size_t pos) const { _C4RV(); return {m_tree, m_tree->sibling(m_id, pos)}; }
+ NodeRef find_sibling(csubstr name) { _C4RV(); return {m_tree, m_tree->find_sibling(m_id, name)}; }
+ NodeRef const find_sibling(csubstr name) const { _C4RV(); return {m_tree, m_tree->find_sibling(m_id, name)}; }
+
+ NodeRef doc(size_t num) { _C4RV(); return {m_tree, m_tree->doc(num)}; }
+ NodeRef const doc(size_t num) const { _C4RV(); return {m_tree, m_tree->doc(num)}; }
+
+ /** @} */
+
+public:
+
+ /** @name node modifiers */
+ /** @{ */
+
+ void change_type(NodeType t) { _C4RV(); m_tree->change_type(m_id, t); }
+ void set_type(NodeType t) { _C4RV(); m_tree->_set_flags(m_id, t); }
+ void set_key(csubstr key) { _C4RV(); m_tree->_set_key(m_id, key); }
+ void set_val(csubstr val) { _C4RV(); m_tree->_set_val(m_id, val); }
+ void set_key_tag(csubstr key_tag) { _C4RV(); m_tree->set_key_tag(m_id, key_tag); }
+ void set_val_tag(csubstr val_tag) { _C4RV(); m_tree->set_val_tag(m_id, val_tag); }
+ void set_key_anchor(csubstr key_anchor) { _C4RV(); m_tree->set_key_anchor(m_id, key_anchor); }
+ void set_val_anchor(csubstr val_anchor) { _C4RV(); m_tree->set_val_anchor(m_id, val_anchor); }
+ void set_key_ref(csubstr key_ref) { _C4RV(); m_tree->set_key_ref(m_id, key_ref); }
+ void set_val_ref(csubstr val_ref) { _C4RV(); m_tree->set_val_ref(m_id, val_ref); }
+
+ template<class T>
+ size_t set_key_serialized(T const& C4_RESTRICT k)
+ {
+ _C4RV();
+ csubstr s = m_tree->to_arena(k);
+ m_tree->_set_key(m_id, s);
+ return s.len;
+ }
+ template<class T>
+ size_t set_val_serialized(T const& C4_RESTRICT v)
+ {
+ _C4RV();
+ csubstr s = m_tree->to_arena(v);
+ m_tree->_set_val(m_id, s);
+ return s.len;
+ }
+
+ /** encode a blob as base64, 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, 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);
+
+public:
+
+ inline void clear()
+ {
+ if(is_seed())
+ return;
+ m_tree->remove_children(m_id);
+ m_tree->_clear(m_id);
+ }
+
+ inline void clear_key()
+ {
+ if(is_seed())
+ return;
+ m_tree->_clear_key(m_id);
+ }
+
+ inline void clear_val()
+ {
+ if(is_seed())
+ return;
+ m_tree->_clear_val(m_id);
+ }
+
+ inline void clear_children()
+ {
+ if(is_seed())
+ return;
+ m_tree->remove_children(m_id);
+ }
+
+ /** @} */
+
+public:
+
+ /** hierarchy getters */
+ /** @{ */
+
+ /** O(num_children) */
+ NodeRef operator[] (csubstr k)
+ {
+ RYML_ASSERT( ! is_seed());
+ RYML_ASSERT(valid());
+ size_t ch = m_tree->find_child(m_id, k);
+ NodeRef r = ch != NONE ? NodeRef(m_tree, ch) : NodeRef(m_tree, m_id, k);
+ return r;
+ }
+
+ /** O(num_children) */
+ NodeRef const operator[] (csubstr k) const
+ {
+ RYML_ASSERT( ! is_seed());
+ RYML_ASSERT(valid());
+ size_t ch = m_tree->find_child(m_id, k);
+ RYML_ASSERT(ch != NONE);
+ NodeRef const r(m_tree, ch);
+ return r;
+ }
+
+ /** O(num_children) */
+ NodeRef operator[] (size_t pos)
+ {
+ RYML_ASSERT( ! is_seed());
+ RYML_ASSERT(valid());
+ size_t ch = m_tree->child(m_id, pos);
+ NodeRef r = ch != NONE ? NodeRef(m_tree, ch) : NodeRef(m_tree, m_id, pos);
+ return r;
+ }
+
+ /** O(num_children) */
+ NodeRef const operator[] (size_t pos) const
+ {
+ RYML_ASSERT( ! is_seed());
+ RYML_ASSERT(valid());
+ size_t ch = m_tree->child(m_id, pos);
+ RYML_ASSERT(ch != NONE);
+ NodeRef const r(m_tree, ch);
+ return r;
+ }
+
+ /** @} */
+
+public:
+
+ /** node modification */
+ /** @{ */
+
+ void create() { _apply_seed(); }
+
+ inline void operator= (NodeType_e t)
+ {
+ _apply_seed();
+ m_tree->_add_flags(m_id, t);
+ }
+
+ inline void operator|= (NodeType_e t)
+ {
+ _apply_seed();
+ m_tree->_add_flags(m_id, t);
+ }
+
+ inline void operator= (NodeInit const& v)
+ {
+ _apply_seed();
+ _apply(v);
+ }
+
+ inline void operator= (NodeScalar const& v)
+ {
+ _apply_seed();
+ _apply(v);
+ }
+
+ inline void operator= (csubstr v)
+ {
+ _apply_seed();
+ _apply(v);
+ }
+
+ template<size_t N>
+ inline void operator= (const char (&v)[N])
+ {
+ _apply_seed();
+ csubstr sv;
+ sv.assign<N>(v);
+ _apply(sv);
+ }
+
+ /** @} */
+
+public:
+
+ /** serialize a variable to the arena */
+ template<class T>
+ inline csubstr to_arena(T const& C4_RESTRICT s) const
+ {
+ _C4RV();
+ return m_tree->to_arena(s);
+ }
+
+ /** serialize a variable, then assign the result to the node's val */
+ inline 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_ASSERT(val() == s);
+ return *this;
+ }
+
+ template<class T>
+ inline NodeRef& operator<< (T const& C4_RESTRICT v)
+ {
+ _apply_seed();
+ write(this, v);
+ return *this;
+ }
+
+ template<class T>
+ inline NodeRef const& operator>> (T &v) const
+ {
+ RYML_ASSERT( ! is_seed());
+ RYML_ASSERT(valid());
+ RYML_ASSERT(get() != nullptr);
+ if( ! read(*this, &v))
+ {
+ c4::yml::error("could not deserialize value");
+ }
+ return *this;
+ }
+
+public:
+
+ /** serialize a variable, then assign the result to the node's key */
+ template<class T>
+ inline NodeRef& operator<< (Key<const T> 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<class T>
+ inline NodeRef& operator<< (Key<T> const& C4_RESTRICT v)
+ {
+ _apply_seed();
+ set_key_serialized(v.k);
+ return *this;
+ }
+
+ /** deserialize the node's key to the given variable */
+ template<class T>
+ inline NodeRef const& operator>> (Key<T> v) const
+ {
+ RYML_ASSERT( ! is_seed());
+ RYML_ASSERT(valid());
+ RYML_ASSERT(get() != nullptr);
+ from_chars(key(), &v.k);
+ return *this;
+ }
+
+public:
+
+ NodeRef& operator<< (Key<fmt::const_base64_wrapper> w)
+ {
+ set_key_serialized(w.wrapper);
+ return *this;
+ }
+
+ NodeRef& operator<< (fmt::const_base64_wrapper w)
+ {
+ set_val_serialized(w);
+ return *this;
+ }
+
+ NodeRef const& operator>> (Key<fmt::base64_wrapper> w) const
+ {
+ deserialize_key(w.wrapper);
+ return *this;
+ }
+
+ NodeRef const& operator>> (fmt::base64_wrapper w) const
+ {
+ deserialize_val(w);
+ return *this;
+ }
+
+public:
+
+ template<class T>
+ void get_if(csubstr name, T *var) const
+ {
+ auto ch = find_child(name);
+ if(ch.valid())
+ {
+ ch >> *var;
+ }
+ }
+
+ template<class T>
+ void get_if(csubstr name, T *var, T fallback) const
+ {
+ auto ch = find_child(name);
+ if(ch.valid())
+ {
+ ch >> *var;
+ }
+ else
+ {
+ *var = fallback;
+ }
+ }
+
+private:
+
+ void _apply_seed()
+ {
+ if(m_seed.str) // we have a seed key: use it to create the new child
+ {
+ //RYML_ASSERT(i.key.scalar.empty() || m_key == i.key.scalar || m_key.empty());
+ m_id = m_tree->append_child(m_id);
+ m_tree->_set_key(m_id, m_seed);
+ m_seed.str = nullptr;
+ m_seed.len = NONE;
+ }
+ else if(m_seed.len != NONE) // we have a seed index: create a child at that position
+ {
+ RYML_ASSERT(m_tree->num_children(m_id) == m_seed.len);
+ m_id = m_tree->append_child(m_id);
+ m_seed.str = nullptr;
+ m_seed.len = NONE;
+ }
+ else
+ {
+ RYML_ASSERT(valid());
+ }
+ }
+
+ inline void _apply(csubstr v)
+ {
+ m_tree->_set_val(m_id, v);
+ }
+
+ inline void _apply(NodeScalar const& v)
+ {
+ m_tree->_set_val(m_id, v);
+ }
+
+ inline void _apply(NodeInit const& i)
+ {
+ m_tree->_set(m_id, i);
+ }
+
+public:
+
+ inline NodeRef insert_child(NodeRef after)
+ {
+ _C4RV();
+ RYML_ASSERT(after.m_tree == m_tree);
+ NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id));
+ return r;
+ }
+
+ inline NodeRef insert_child(NodeInit const& i, NodeRef after)
+ {
+ _C4RV();
+ RYML_ASSERT(after.m_tree == m_tree);
+ NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id));
+ r._apply(i);
+ return r;
+ }
+
+ inline NodeRef prepend_child()
+ {
+ _C4RV();
+ NodeRef r(m_tree, m_tree->insert_child(m_id, NONE));
+ return r;
+ }
+
+ inline NodeRef prepend_child(NodeInit const& i)
+ {
+ _C4RV();
+ NodeRef r(m_tree, m_tree->insert_child(m_id, NONE));
+ r._apply(i);
+ return r;
+ }
+
+ inline NodeRef append_child()
+ {
+ _C4RV();
+ NodeRef r(m_tree, m_tree->append_child(m_id));
+ return r;
+ }
+
+ inline NodeRef append_child(NodeInit const& i)
+ {
+ _C4RV();
+ NodeRef r(m_tree, m_tree->append_child(m_id));
+ r._apply(i);
+ return r;
+ }
+
+public:
+
+ inline NodeRef insert_sibling(NodeRef const after)
+ {
+ _C4RV();
+ RYML_ASSERT(after.m_tree == m_tree);
+ NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id));
+ return r;
+ }
+
+ inline NodeRef insert_sibling(NodeInit const& i, NodeRef const after)
+ {
+ _C4RV();
+ RYML_ASSERT(after.m_tree == m_tree);
+ NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id));
+ r._apply(i);
+ return r;
+ }
+
+ inline NodeRef prepend_sibling()
+ {
+ _C4RV();
+ NodeRef r(m_tree, m_tree->prepend_sibling(m_id));
+ return r;
+ }
+
+ inline NodeRef prepend_sibling(NodeInit const& i)
+ {
+ _C4RV();
+ NodeRef r(m_tree, m_tree->prepend_sibling(m_id));
+ r._apply(i);
+ return r;
+ }
+
+ inline NodeRef append_sibling()
+ {
+ _C4RV();
+ NodeRef r(m_tree, m_tree->append_sibling(m_id));
+ return r;
+ }
+
+ inline NodeRef append_sibling(NodeInit const& i)
+ {
+ _C4RV();
+ NodeRef r(m_tree, m_tree->append_sibling(m_id));
+ r._apply(i);
+ return r;
+ }
+
+public:
+
+ inline void remove_child(NodeRef & child)
+ {
+ _C4RV();
+ RYML_ASSERT(has_child(child));
+ RYML_ASSERT(child.parent().id() == id());
+ m_tree->remove(child.id());
+ child.clear();
+ }
+
+ //! remove the nth child of this node
+ inline void remove_child(size_t pos)
+ {
+ _C4RV();
+ RYML_ASSERT(pos >= 0 && pos < num_children());
+ size_t child = m_tree->child(m_id, pos);
+ RYML_ASSERT(child != NONE);
+ m_tree->remove(child);
+ }
+
+ //! remove a child by name
+ inline void remove_child(csubstr key)
+ {
+ _C4RV();
+ size_t child = m_tree->find_child(m_id, key);
+ RYML_ASSERT(child != NONE);
+ m_tree->remove(child);
+ }
+
+public:
+
+ /** change the node's position within its parent */
+ inline void move(NodeRef const after)
+ {
+ _C4RV();
+ m_tree->move(m_id, after.m_id);
+ }
+
+ /** move the node to a different parent, which may belong to a different
+ * tree. When this is the case, then this node's tree pointer is reset to
+ * the tree of the parent node. */
+ inline void move(NodeRef const parent, NodeRef const after)
+ {
+ _C4RV();
+ RYML_ASSERT(parent.m_tree == after.m_tree);
+ 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;
+ }
+ }
+
+ inline NodeRef duplicate(NodeRef const parent, NodeRef const after) const
+ {
+ _C4RV();
+ RYML_ASSERT(parent.m_tree == after.m_tree);
+ if(parent.m_tree == m_tree)
+ {
+ size_t dup = m_tree->duplicate(m_id, parent.m_id, after.m_id);
+ NodeRef r(m_tree, dup);
+ return r;
+ }
+ else
+ {
+ size_t dup = parent.m_tree->duplicate(m_tree, m_id, parent.m_id, after.m_id);
+ NodeRef r(parent.m_tree, dup);
+ return r;
+ }
+ }
+
+ inline void duplicate_children(NodeRef const parent, NodeRef const after) const
+ {
+ _C4RV();
+ RYML_ASSERT(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);
+ }
+ }
+
+private:
+
+ template<class Nd>
+ struct child_iterator
+ {
+ Tree * m_tree;
+ size_t m_child_id;
+
+ using value_type = NodeRef;
+
+ child_iterator(Tree * t, size_t 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; }
+
+ Nd operator* () const { return Nd(m_tree, m_child_id); }
+ Nd operator-> () const { return Nd(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; }
+ };
+
+public:
+
+ using iterator = child_iterator< NodeRef>;
+ using const_iterator = child_iterator<const NodeRef>;
+
+ inline iterator begin() { return iterator(m_tree, m_tree->first_child(m_id)); }
+ inline iterator end () { return iterator(m_tree, NONE); }
+
+ inline const_iterator begin() const { return const_iterator(m_tree, m_tree->first_child(m_id)); }
+ inline const_iterator end () const { return const_iterator(m_tree, NONE); }
+
+private:
+
+ template<class Nd>
+ struct children_view_
+ {
+ using n_iterator = child_iterator<Nd>;
+
+ n_iterator b, e;
+
+ inline children_view_(n_iterator const& b_, n_iterator const& e_) : b(b_), e(e_) {}
+
+ inline n_iterator begin() const { return b; }
+ inline n_iterator end () const { return e; }
+ };
+
+public:
+
+ using children_view = children_view_< NodeRef>;
+ using const_children_view = children_view_<const NodeRef>;
+
+ children_view children() { return children_view(begin(), end()); }
+ const_children_view children() const { return const_children_view(begin(), end()); }
+
+ #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
+
+ children_view siblings() { if(is_root()) { return children_view(end(), end()); } else { size_t p = get()->m_parent; return children_view(iterator(m_tree, m_tree->get(p)->m_first_child), iterator(m_tree, NONE)); } }
+ const_children_view siblings() const { if(is_root()) { return const_children_view(end(), end()); } else { size_t p = get()->m_parent; return const_children_view(const_iterator(m_tree, m_tree->get(p)->m_first_child), const_iterator(m_tree, NONE)); } }
+
+ #if defined(__clang__)
+ # pragma clang diagnostic pop
+ #elif defined(__GNUC__)
+ # pragma GCC diagnostic pop
+ #endif
+
+public:
+
+ /** visit every child node calling fn(node) */
+ template<class Visitor> bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true);
+ /** visit every child node calling fn(node) */
+ template<class Visitor> bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) const;
+
+ /** visit every child node calling fn(node, level) */
+ template<class Visitor> bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true);
+ /** visit every child node calling fn(node, level) */
+ template<class Visitor> bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) const;
+
+#undef _C4RV
+};
+
+//-----------------------------------------------------------------------------
+template<class T>
+inline void write(NodeRef *n, T const& v)
+{
+ n->set_val_serialized(v);
+}
+
+template<class T>
+typename std::enable_if< ! std::is_floating_point<T>::value, bool>::type
+inline read(NodeRef const& n, T *v)
+{
+ return from_chars(n.val(), v);
+}
+
+template<class T>
+typename std::enable_if< std::is_floating_point<T>::value, bool>::type
+inline read(NodeRef const& n, T *v)
+{
+ return from_chars_float(n.val(), v);
+}
+
+
+//-----------------------------------------------------------------------------
+template<class Visitor>
+bool NodeRef::visit(Visitor fn, size_t indentation_level, bool skip_root)
+{
+ return const_cast<NodeRef const*>(this)->visit(fn, indentation_level, skip_root);
+}
+
+template<class Visitor>
+bool NodeRef::visit(Visitor fn, size_t indentation_level, bool skip_root) const
+{
+ size_t increment = 0;
+ if( ! (is_root() && skip_root))
+ {
+ if(fn(this, indentation_level))
+ {
+ return true;
+ }
+ ++increment;
+ }
+ if(has_children())
+ {
+ for(auto ch : children())
+ {
+ if(ch.visit(fn, indentation_level + increment)) // no need to forward skip_root as it won't be root
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+template<class Visitor>
+bool NodeRef::visit_stacked(Visitor fn, size_t indentation_level, bool skip_root)
+{
+ return const_cast< NodeRef const* >(this)->visit_stacked(fn, indentation_level, skip_root);
+}
+
+template<class Visitor>
+bool NodeRef::visit_stacked(Visitor fn, size_t indentation_level, bool skip_root) const
+{
+ size_t increment = 0;
+ if( ! (is_root() && skip_root))
+ {
+ if(fn(this, indentation_level))
+ {
+ return true;
+ }
+ ++increment;
+ }
+ if(has_children())
+ {
+ fn.push(this, indentation_level);
+ for(auto ch : children())
+ {
+ if(ch.visit(fn, indentation_level + increment)) // no need to forward skip_root as it won't be root
+ {
+ fn.pop(this, indentation_level);
+ return true;
+ }
+ }
+ fn.pop(this, indentation_level);
+ }
+ return false;
+}
+
+} // namespace yml
+} // namespace c4
+
+
+#if defined(_MSC_VER)
+# pragma warning(pop)
+#endif
+
+#ifdef __GNUC__
+# pragma GCC diagnostic 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 <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_ */
+
+//included above:
+//#include <stdio.h> // fwrite(), fputc()
+//included above:
+//#include <string.h> // memcpy()
+
+
+namespace c4 {
+namespace yml {
+
+
+/** Repeat-Character: a character to be written a number of times. */
+struct RepC
+{
+ char c;
+ size_t num_times;
+};
+inline RepC indent_to(size_t num_levels)
+{
+ return {' ', size_t(2) * num_levels};
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+/** 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) {}
+
+ inline substr _get(bool /*error_on_excess*/)
+ {
+ substr sp;
+ sp.str = nullptr;
+ sp.len = m_pos;
+ return sp;
+ }
+
+ template<size_t N>
+ inline void _do_write(const char (&a)[N])
+ {
+ fwrite(a, sizeof(char), N - 1, m_file);
+ m_pos += N - 1;
+ }
+
+ inline void _do_write(csubstr sp)
+ {
+ #if defined(__clang__)
+ # pragma clang diagnostic push
+ # pragma GCC diagnostic ignored "-Wsign-conversion"
+ #elif defined(__GNUC__)
+ # pragma GCC diagnostic push
+ # pragma GCC diagnostic ignored "-Wsign-conversion"
+ #endif
+ if(sp.empty()) return;
+ fwrite(sp.str, sizeof(csubstr::char_type), sp.len, m_file);
+ m_pos += sp.len;
+ #if defined(__clang__)
+ # pragma clang diagnostic pop
+ #elif defined(__GNUC__)
+ # pragma GCC diagnostic pop
+ #endif
+ }
+
+ inline void _do_write(const char c)
+ {
+ fputc(c, m_file);
+ ++m_pos;
+ }
+
+ inline void _do_write(RepC const rc)
+ {
+ for(size_t i = 0; i < rc.num_times; ++i)
+ {
+ fputc(rc.c, m_file);
+ }
+ m_pos += rc.num_times;
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+/** A writer that outputs to an STL-like ostream. */
+template<class OStream>
+struct WriterOStream
+{
+ OStream& m_stream;
+ size_t m_pos;
+
+ WriterOStream(OStream &s) : m_stream(s), m_pos(0) {}
+
+ inline substr _get(bool /*error_on_excess*/)
+ {
+ substr sp;
+ sp.str = nullptr;
+ sp.len = m_pos;
+ return sp;
+ }
+
+ template<size_t N>
+ inline void _do_write(const char (&a)[N])
+ {
+ m_stream.write(a, N - 1);
+ m_pos += N - 1;
+ }
+
+ inline void _do_write(csubstr sp)
+ {
+ #if defined(__clang__)
+ # pragma clang diagnostic push
+ # pragma GCC diagnostic ignored "-Wsign-conversion"
+ #elif defined(__GNUC__)
+ # pragma GCC diagnostic push
+ # pragma GCC diagnostic ignored "-Wsign-conversion"
+ #endif
+ if(sp.empty()) return;
+ m_stream.write(sp.str, sp.len);
+ m_pos += sp.len;
+ #if defined(__clang__)
+ # pragma clang diagnostic pop
+ #elif defined(__GNUC__)
+ # pragma GCC diagnostic pop
+ #endif
+ }
+
+ inline void _do_write(const char c)
+ {
+ m_stream.put(c);
+ ++m_pos;
+ }
+
+ inline void _do_write(RepC const rc)
+ {
+ for(size_t i = 0; i < rc.num_times; ++i)
+ {
+ m_stream.put(rc.c);
+ }
+ m_pos += rc.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) {}
+
+ inline substr _get(bool error_on_excess)
+ {
+ if(m_pos <= m_buf.len)
+ {
+ return m_buf.first(m_pos);
+ }
+ 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<size_t N>
+ inline 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;
+ }
+
+ inline 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;
+ }
+
+ inline void _do_write(const char c)
+ {
+ if(m_pos + 1 <= m_buf.len)
+ {
+ m_buf[m_pos] = c;
+ }
+ ++m_pos;
+ }
+
+ inline void _do_write(RepC const rc)
+ {
+ if(m_pos + rc.num_times <= m_buf.len)
+ {
+ for(size_t i = 0; i < rc.num_times; ++i)
+ {
+ m_buf[m_pos + i] = rc.c;
+ }
+ }
+ m_pos += rc.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
+//included above:
+//#include <cstdio>
+
+//-----------------------------------------------------------------------------
+// some debugging scaffolds
+
+#if defined(_MSC_VER)
+# pragma warning(push)
+# pragma warning(disable: 4068/*unknown pragma*/)
+#endif
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunknown-pragmas"
+//#pragma GCC diagnostic ignored "-Wpragma-system-header-outside-header"
+#pragma GCC system_header
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Werror"
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+
+// some debugging scaffolds
+#ifdef RYML_DBG
+// amalgamate: removed include of
+// https://github.com/biojppm/rapidyaml/src/c4/dump.hpp
+//#include <c4/dump.hpp>
+#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 void _dbg_dumper(csubstr s) { fwrite(s.str, 1, s.len, stdout); };
+template<class ...Args>
+void _dbg_printf(c4::csubstr fmt, Args&& ...args)
+{
+ static char writebuf[256];
+ auto results = c4::format_dump_resume<&_dbg_dumper>(writebuf, fmt, std::forward<Args>(args)...);
+ // resume writing if the results failed to fit the buffer
+ if(C4_UNLIKELY(results.bufsize > sizeof(writebuf))) // bufsize will be that of the largest element serialized. Eg int(1), will require 1 byte.
+ {
+ results = format_dump_resume<&_dbg_dumper>(results, writebuf, fmt, std::forward<Args>(args)...);
+ if(C4_UNLIKELY(results.bufsize > sizeof(writebuf)))
+ {
+ results = format_dump_resume<&_dbg_dumper>(results, writebuf, fmt, std::forward<Args>(args)...);
+ }
+ }
+}
+} // namespace c4
+
+# define _c4dbgt(fmt, ...) this->_dbg ("{}:{}: " fmt , __FILE__, __LINE__, ## __VA_ARGS__)
+# define _c4dbgpf(fmt, ...) _dbg_printf("{}:{}: " fmt "\n", __FILE__, __LINE__, ## __VA_ARGS__)
+# define _c4dbgp(msg) _dbg_printf("{}:{}: " msg "\n", __FILE__, __LINE__ )
+# define _c4dbgq(msg) _dbg_printf(msg "\n")
+# define _c4err(fmt, ...) \
+ do { if(c4::is_debugger_attached()) { C4_DEBUG_BREAK(); } \
+ this->_err("ERROR:\n" "{}:{}: " fmt, __FILE__, __LINE__, ## __VA_ARGS__); } while(0)
+#else
+# define _c4dbgt(fmt, ...)
+# define _c4dbgpf(fmt, ...)
+# define _c4dbgp(msg)
+# define _c4dbgq(msg)
+# define _c4err(fmt, ...) \
+ do { if(c4::is_debugger_attached()) { C4_DEBUG_BREAK(); } \
+ this->_err("ERROR: " fmt, ## __VA_ARGS__); } while(0)
+#endif
+
+#define _c4prsp(sp) sp
+#define _c4presc(s) __c4presc(s.str, s.len)
+inline c4::csubstr _c4prc(const char &C4_RESTRICT c)
+{
+ switch(c)
+ {
+ case '\n': return c4::csubstr("\\n");
+ case '\t': return c4::csubstr("\\t");
+ case '\0': return c4::csubstr("\\0");
+ case '\r': return c4::csubstr("\\r");
+ case '\f': return c4::csubstr("\\f");
+ case '\b': return c4::csubstr("\\b");
+ case '\v': return c4::csubstr("\\v");
+ case '\a': return c4::csubstr("\\a");
+ default: return c4::csubstr(&c, 1);
+ }
+}
+inline void __c4presc(const char *s, size_t len)
+{
+ size_t prev = 0;
+ for(size_t i = 0; i < len; ++i)
+ {
+ switch(s[i])
+ {
+ case '\n' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('n'); putchar('\n'); prev = i+1; break;
+ case '\t' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('t'); prev = i+1; break;
+ case '\0' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('0'); prev = i+1; break;
+ case '\r' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('r'); prev = i+1; break;
+ case '\f' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('f'); prev = i+1; break;
+ case '\b' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('b'); prev = i+1; break;
+ case '\v' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('v'); prev = i+1; break;
+ case '\a' : fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('a'); prev = i+1; break;
+ case '\x1b': fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('e'); prev = i+1; break;
+ case -0x3e/*0xc2u*/:
+ if(i+1 < len)
+ {
+ if(s[i+1] == -0x60/*0xa0u*/)
+ {
+ fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('_'); prev = i+2; ++i;
+ }
+ else if(s[i+1] == -0x7b/*0x85u*/)
+ {
+ fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('N'); prev = i+2; ++i;
+ }
+ break;
+ }
+ case -0x1e/*0xe2u*/:
+ if(i+2 < len && s[i+1] == -0x80/*0x80u*/)
+ {
+ if(s[i+2] == -0x58/*0xa8u*/)
+ {
+ fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('L'); prev = i+3; i += 2;
+ }
+ else if(s[i+2] == -0x57/*0xa9u*/)
+ {
+ fwrite(s+prev, 1, i-prev, stdout); putchar('\\'); putchar('P'); prev = i+3; i += 2;
+ }
+ break;
+ }
+ }
+ }
+ fwrite(s + prev, 1, len - prev, stdout);
+}
+
+#pragma clang diagnostic pop
+#pragma GCC diagnostic pop
+
+#if defined(_MSC_VER)
+# pragma warning(pop)
+#endif
+
+
+#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_
+
+#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
+
+namespace c4 {
+namespace yml {
+
+template<class Writer> class Emitter;
+
+template<class OStream>
+using EmitterOStream = Emitter<WriterOStream<OStream>>;
+using EmitterFile = Emitter<WriterFile>;
+using EmitterBuf = Emitter<WriterBuf>;
+
+typedef enum {
+ EMIT_YAML = 0,
+ EMIT_JSON = 1
+} EmitType_e;
+
+
+/** mark a tree or node to be emitted as json */
+struct as_json
+{
+ Tree const* tree;
+ size_t node;
+ as_json(Tree const& t) : tree(&t), node(t.empty() ? NONE : t.root_id()) {}
+ as_json(Tree const& t, size_t id) : tree(&t), node(id) {}
+ as_json(NodeRef const& n) : tree(n.tree()), node(n.id()) {}
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+template<class Writer>
+class Emitter : public Writer
+{
+public:
+
+ using Writer::Writer;
+
+ /** emit!
+ *
+ * When writing to a buffer, returns a substr of the emitted YAML.
+ * If the given buffer has insufficient space, the returned span will
+ * be null and its size will be the needed space. No writes are done
+ * after the end of the buffer.
+ *
+ * When writing to a file, the returned substr will be null, but its
+ * length will be set to the number of bytes written. */
+ substr emit(EmitType_e type, Tree const& t, size_t id, bool error_on_excess);
+ /** emit starting at the root node */
+ substr emit(EmitType_e type, Tree const& t, bool error_on_excess=true);
+ /** emit the given node */
+ substr emit(EmitType_e type, NodeRef const& n, bool error_on_excess=true);
+
+private:
+
+ Tree const* C4_RESTRICT m_tree;
+
+ void _emit_yaml(size_t id);
+ void _do_visit_flow_sl(size_t id, size_t ilevel=0);
+ void _do_visit_flow_ml(size_t id, size_t ilevel=0, size_t do_indent=1);
+ void _do_visit_block(size_t id, size_t ilevel=0, size_t do_indent=1);
+ void _do_visit_block_container(size_t id, size_t next_level, size_t do_indent);
+ void _do_visit_json(size_t id);
+
+private:
+
+ void _write(NodeScalar const& C4_RESTRICT sc, NodeType flags, size_t level);
+ void _write_json(NodeScalar const& C4_RESTRICT sc, NodeType flags);
+
+ void _write_doc(size_t id);
+ void _write_scalar(csubstr s, bool was_quoted);
+ void _write_scalar_json(csubstr s, bool as_key, bool was_quoted);
+ void _write_scalar_literal(csubstr s, size_t level, bool as_key, bool explicit_indentation=false);
+ void _write_scalar_folded(csubstr s, size_t level, bool as_key);
+ void _write_scalar_squo(csubstr s, size_t level);
+ void _write_scalar_dquo(csubstr s, size_t level);
+ void _write_scalar_plain(csubstr s, size_t 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|_WIP_KEY_STYLE) | ~(VAL|VALREF|VALANCH|VALQUO|_WIP_VAL_STYLE),
+ _valsc = ~(KEY|KEYREF|KEYANCH|KEYQUO|_WIP_KEY_STYLE) | (VAL|VALREF|VALANCH|VALQUO|_WIP_VAL_STYLE),
+ _keysc_json = (KEY) | ~(VAL),
+ _valsc_json = ~(KEY) | (VAL),
+ };
+
+ C4_ALWAYS_INLINE void _writek(size_t id, size_t level) { _write(m_tree->keysc(id), m_tree->_p(id)->m_type.type & ~_valsc, level); }
+ C4_ALWAYS_INLINE void _writev(size_t id, size_t level) { _write(m_tree->valsc(id), m_tree->_p(id)->m_type.type & ~_keysc, level); }
+
+ C4_ALWAYS_INLINE void _writek_json(size_t id) { _write_json(m_tree->keysc(id), m_tree->_p(id)->m_type.type & ~(VAL)); }
+ C4_ALWAYS_INLINE void _writev_json(size_t id) { _write_json(m_tree->valsc(id), m_tree->_p(id)->m_type.type & ~(KEY)); }
+
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** emit YAML to the given file. A null file defaults to stdout.
+ * Return the number of bytes written. */
+inline size_t emit(Tree const& t, size_t id, FILE *f)
+{
+ EmitterFile em(f);
+ return em.emit(EMIT_YAML, t, id, /*error_on_excess*/true).len;
+}
+/** 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, size_t id, FILE *f)
+{
+ EmitterFile em(f);
+ return em.emit(EMIT_JSON, t, id, /*error_on_excess*/true).len;
+}
+
+
+/** emit YAML to the given file. A null file defaults to stdout.
+ * Return the number of bytes written.
+ * @overload */
+inline size_t emit(Tree const& t, FILE *f=nullptr)
+{
+ EmitterFile em(f);
+ return em.emit(EMIT_YAML, t, /*error_on_excess*/true).len;
+}
+
+/** emit JSON to the given file. A null file defaults to stdout.
+ * Return the number of bytes written.
+ * @overload */
+inline size_t emit_json(Tree const& t, FILE *f=nullptr)
+{
+ EmitterFile em(f);
+ return em.emit(EMIT_JSON, t, /*error_on_excess*/true).len;
+}
+
+
+/** emit YAML to the given file. A null file defaults to stdout.
+ * Return the number of bytes written.
+ * @overload */
+inline size_t emit(NodeRef const& r, FILE *f=nullptr)
+{
+ EmitterFile em(f);
+ return em.emit(EMIT_YAML, r, /*error_on_excess*/true).len;
+}
+
+/** emit JSON to the given file. A null file defaults to stdout.
+ * Return the number of bytes written.
+ * @overload */
+inline size_t emit_json(NodeRef const& r, FILE *f=nullptr)
+{
+ EmitterFile em(f);
+ return em.emit(EMIT_JSON, r, /*error_on_excess*/true).len;
+}
+
+
+//-----------------------------------------------------------------------------
+
+/** emit YAML to an STL-like ostream */
+template<class OStream>
+inline OStream& operator<< (OStream& s, Tree const& t)
+{
+ EmitterOStream<OStream> em(s);
+ em.emit(EMIT_YAML, t);
+ return s;
+}
+
+/** emit YAML to an STL-like ostream
+ * @overload */
+template<class OStream>
+inline OStream& operator<< (OStream& s, NodeRef const& n)
+{
+ EmitterOStream<OStream> em(s);
+ em.emit(EMIT_YAML, n);
+ return s;
+}
+
+/** emit json to an STL-like stream */
+template<class OStream>
+inline OStream& operator<< (OStream& s, as_json const& j)
+{
+ EmitterOStream<OStream> em(s);
+ em.emit(EMIT_JSON, *j.tree, j.node, true);
+ return s;
+}
+
+
+//-----------------------------------------------------------------------------
+
+
+/** emit YAML to the given buffer. Return a substr trimmed to the emitted YAML.
+ * @param error_on_excess Raise an error if the space in the buffer is insufficient.
+ * @overload */
+inline substr emit(Tree const& t, size_t id, substr buf, bool error_on_excess=true)
+{
+ EmitterBuf em(buf);
+ return em.emit(EMIT_YAML, t, id, error_on_excess);
+}
+
+/** emit JSON to the given buffer. Return a substr trimmed to the emitted JSON.
+ * @param error_on_excess Raise an error if the space in the buffer is insufficient.
+ * @overload */
+inline substr emit_json(Tree const& t, size_t id, substr buf, bool error_on_excess=true)
+{
+ EmitterBuf em(buf);
+ return em.emit(EMIT_JSON, t, id, error_on_excess);
+}
+
+
+/** emit YAML to the given buffer. Return a substr trimmed to the emitted YAML.
+ * @param error_on_excess Raise an error if the space in the buffer is insufficient.
+ * @overload */
+inline substr emit(Tree const& t, substr buf, bool error_on_excess=true)
+{
+ EmitterBuf em(buf);
+ return em.emit(EMIT_YAML, t, error_on_excess);
+}
+
+/** emit JSON to the given buffer. Return a substr trimmed to the emitted JSON.
+ * @param error_on_excess Raise an error if the space in the buffer is insufficient.
+ * @overload */
+inline substr emit_json(Tree const& t, substr buf, bool error_on_excess=true)
+{
+ EmitterBuf em(buf);
+ return em.emit(EMIT_JSON, t, error_on_excess);
+}
+
+
+/** emit YAML to the given buffer. Return a substr trimmed to the emitted YAML.
+ * @param error_on_excess Raise an error if the space in the buffer is insufficient.
+ * @overload
+ */
+inline substr emit(NodeRef const& r, substr buf, bool error_on_excess=true)
+{
+ EmitterBuf em(buf);
+ return em.emit(EMIT_YAML, r, error_on_excess);
+}
+
+/** emit JSON to the given buffer. Return a substr trimmed to the emitted JSON.
+ * @param error_on_excess Raise an error if the space in the buffer is insufficient.
+ * @overload
+ */
+inline substr emit_json(NodeRef const& r, substr buf, bool error_on_excess=true)
+{
+ EmitterBuf em(buf);
+ return em.emit(EMIT_JSON, r, error_on_excess);
+}
+
+
+//-----------------------------------------------------------------------------
+
+/** emit+resize: emit YAML to the given std::string/std::vector-like
+ * container, resizing it as needed to fit the emitted YAML. */
+template<class CharOwningContainer>
+substr emitrs(Tree const& t, size_t id, CharOwningContainer * cont)
+{
+ substr buf = to_substr(*cont);
+ substr ret = emit(t, id, buf, /*error_on_excess*/false);
+ if(ret.str == nullptr && ret.len > 0)
+ {
+ cont->resize(ret.len);
+ buf = to_substr(*cont);
+ ret = emit(t, id, buf, /*error_on_excess*/true);
+ }
+ return ret;
+}
+
+/** emit+resize: emit JSON to the given std::string/std::vector-like
+ * container, resizing it as needed to fit the emitted JSON. */
+template<class CharOwningContainer>
+substr emitrs_json(Tree const& t, size_t id, CharOwningContainer * cont)
+{
+ substr buf = to_substr(*cont);
+ substr ret = emit_json(t, id, buf, /*error_on_excess*/false);
+ if(ret.str == nullptr && ret.len > 0)
+ {
+ cont->resize(ret.len);
+ buf = to_substr(*cont);
+ ret = emit_json(t, id, buf, /*error_on_excess*/true);
+ }
+ return ret;
+}
+
+
+/** emit+resize: emit YAML to the given std::string/std::vector-like
+ * container, resizing it as needed to fit the emitted YAML. */
+template<class CharOwningContainer>
+CharOwningContainer emitrs(Tree const& t, size_t id)
+{
+ CharOwningContainer c;
+ emitrs(t, id, &c);
+ return c;
+}
+
+/** emit+resize: emit JSON to the given std::string/std::vector-like container,
+ * resizing it as needed to fit the emitted JSON. */
+template<class CharOwningContainer>
+CharOwningContainer emitrs_json(Tree const& t, size_t id)
+{
+ CharOwningContainer c;
+ emitrs_json(t, id, &c);
+ return c;
+}
+
+
+/** emit+resize: YAML to the given std::string/std::vector-like container,
+ * resizing it as needed to fit the emitted YAML. */
+template<class CharOwningContainer>
+substr emitrs(Tree const& t, CharOwningContainer * cont)
+{
+ if(t.empty())
+ return {};
+ return emitrs(t, t.root_id(), cont);
+}
+
+/** emit+resize: JSON to the given std::string/std::vector-like container,
+ * resizing it as needed to fit the emitted JSON. */
+template<class CharOwningContainer>
+substr emitrs_json(Tree const& t, CharOwningContainer * cont)
+{
+ if(t.empty())
+ return {};
+ return emitrs_json(t, t.root_id(), cont);
+}
+
+
+/** emit+resize: YAML to the given std::string/std::vector-like container,
+ * resizing it as needed to fit the emitted YAML. */
+template<class CharOwningContainer>
+CharOwningContainer emitrs(Tree const& t)
+{
+ CharOwningContainer c;
+ if(t.empty())
+ return c;
+ emitrs(t, t.root_id(), &c);
+ return c;
+}
+
+/** emit+resize: JSON to the given std::string/std::vector-like container,
+ * resizing it as needed to fit the emitted JSON. */
+template<class CharOwningContainer>
+CharOwningContainer emitrs_json(Tree const& t)
+{
+ CharOwningContainer c;
+ if(t.empty())
+ return c;
+ emitrs_json(t, t.root_id(), &c);
+ return c;
+}
+
+
+/** emit+resize: YAML to the given std::string/std::vector-like container,
+ * resizing it as needed to fit the emitted YAML. */
+template<class CharOwningContainer>
+substr emitrs(NodeRef const& n, CharOwningContainer * cont)
+{
+ _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
+ return emitrs(*n.tree(), n.id(), cont);
+}
+
+/** emit+resize: JSON to the given std::string/std::vector-like container,
+ * resizing it as needed to fit the emitted JSON. */
+template<class CharOwningContainer>
+substr emitrs_json(NodeRef const& n, CharOwningContainer * cont)
+{
+ _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
+ return emitrs_json(*n.tree(), n.id(), cont);
+}
+
+
+/** emit+resize: YAML to the given std::string/std::vector-like container,
+ * resizing it as needed to fit the emitted YAML. */
+template<class CharOwningContainer>
+CharOwningContainer emitrs(NodeRef const& n)
+{
+ _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
+ CharOwningContainer c;
+ emitrs(*n.tree(), n.id(), &c);
+ return c;
+}
+
+/** emit+resize: JSON to the given std::string/std::vector-like container,
+ * resizing it as needed to fit the emitted JSON. */
+template<class CharOwningContainer>
+CharOwningContainer emitrs_json(NodeRef const& n)
+{
+ _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
+ CharOwningContainer c;
+ emitrs_json(*n.tree(), n.id(), &c);
+ return c;
+}
+
+} // namespace yml
+} // namespace c4
+
+// amalgamate: removed include of
+// https://github.com/biojppm/rapidyaml/src/c4/yml/emit.def.hpp
+//#include "c4/yml/emit.def.hpp"
+#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
+
+namespace c4 {
+namespace yml {
+
+template<class Writer>
+substr Emitter<Writer>::emit(EmitType_e type, Tree const& t, size_t id, bool error_on_excess)
+{
+ if(t.empty())
+ {
+ _RYML_CB_ASSERT(t.callbacks(), id == NONE);
+ return {};
+ }
+ _RYML_CB_CHECK(t.callbacks(), id < t.size());
+ m_tree = &t;
+ if(type == EMIT_YAML)
+ _emit_yaml(id);
+ else if(type == EMIT_JSON)
+ _do_visit_json(id);
+ else
+ _RYML_CB_ERR(m_tree->callbacks(), "unknown emit type");
+ return this->Writer::_get(error_on_excess);
+}
+
+template<class Writer>
+substr Emitter<Writer>::emit(EmitType_e type, Tree const& t, bool error_on_excess)
+{
+ if(t.empty())
+ return {};
+ return emit(type, t, t.root_id(), error_on_excess);
+}
+
+template<class Writer>
+substr Emitter<Writer>::emit(EmitType_e type, NodeRef const& n, bool error_on_excess)
+{
+ _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
+ return emit(type, *n.tree(), n.id(), error_on_excess);
+}
+
+
+//-----------------------------------------------------------------------------
+
+template<class Writer>
+void Emitter<Writer>::_emit_yaml(size_t 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](size_t node){
+ NodeType ty = m_tree->type(node);
+ if(ty.marked_flow_sl())
+ _do_visit_flow_sl(node, 0);
+ else if(ty.marked_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).marked_flow())
+ {
+ size_t 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, ilevel, ilevel);
+ return;
+ }
+ }
+
+ auto *btd = m_tree->tag_directives().b;
+ auto *etd = m_tree->tag_directives().e;
+ auto write_tag_directives = [&btd, etd, this](size_t next_node){
+ auto end = btd;
+ while(end < etd)
+ {
+ if(end->next_node_id > next_node)
+ break;
+ ++end;
+ }
+ for( ; btd != end; ++btd)
+ {
+ if(next_node != m_tree->first_child(m_tree->parent(next_node)))
+ this->Writer::_do_write("...\n");
+ this->Writer::_do_write("%TAG ");
+ this->Writer::_do_write(btd->handle);
+ this->Writer::_do_write(' ');
+ this->Writer::_do_write(btd->prefix);
+ this->Writer::_do_write('\n');
+ }
+ };
+ if(m_tree->is_stream(id))
+ {
+ if(m_tree->first_child(id) != NONE)
+ write_tag_directives(m_tree->first_child(id));
+ for(size_t child = m_tree->first_child(id); child != NONE; child = m_tree->next_sibling(child))
+ {
+ dispatch(child);
+ 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).marked_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).marked_flow())
+ this->Writer::_do_write('\n');
+ }
+ else if(m_tree->type(id) == NOTYPE)
+ {
+ ;
+ }
+ else
+ {
+ _RYML_CB_ERR(m_tree->callbacks(), "unknown type");
+ }
+}
+
+template<class Writer>
+void Emitter<Writer>::_write_doc(size_t id)
+{
+ RYML_ASSERT(m_tree->is_doc(id));
+ if(!m_tree->is_root(id))
+ {
+ RYML_ASSERT(m_tree->is_stream(m_tree->parent(id)));
+ this->Writer::_do_write("---");
+ }
+ if(!m_tree->has_val(id)) // this is more frequent
+ {
+ if(m_tree->has_val_tag(id))
+ {
+ if(!m_tree->is_root(id))
+ this->Writer::_do_write(' ');
+ _write_tag(m_tree->val_tag(id));
+ }
+ if(m_tree->has_val_anchor(id))
+ {
+ if(!m_tree->is_root(id))
+ this->Writer::_do_write(' ');
+ this->Writer::_do_write('&');
+ this->Writer::_do_write(m_tree->val_anchor(id));
+ }
+ }
+ else // docval
+ {
+ RYML_ASSERT(m_tree->has_val(id));
+ RYML_ASSERT(!m_tree->has_key(id));
+ if(!m_tree->is_root(id))
+ this->Writer::_do_write(' ');
+ _writev(id, 0);
+ }
+ this->Writer::_do_write('\n');
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_flow_sl(size_t node, size_t ilevel)
+{
+ RYML_ASSERT(!m_tree->is_stream(node));
+ RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node));
+ RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node)));
+
+ if(m_tree->is_doc(node))
+ {
+ _write_doc(node);
+ if(!m_tree->has_children(node))
+ return;
+ }
+ else if(m_tree->is_container(node))
+ {
+ RYML_ASSERT(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(size_t 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, ilevel + 1);
+ }
+ }
+
+ if(m_tree->is_map(node))
+ {
+ this->Writer::_do_write('}');
+ }
+ else if(m_tree->is_seq(node))
+ {
+ this->Writer::_do_write(']');
+ }
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_flow_ml(size_t id, size_t ilevel, size_t do_indent)
+{
+ C4_UNUSED(id);
+ C4_UNUSED(ilevel);
+ C4_UNUSED(do_indent);
+ RYML_CHECK(false/*not implemented*/);
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_block_container(size_t node, size_t next_level, size_t do_indent)
+{
+ RepC ind = indent_to(do_indent * next_level);
+
+ if(m_tree->is_seq(node))
+ {
+ for(size_t 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))
+ {
+ this->Writer::_do_write(ind);
+ this->Writer::_do_write("- ");
+ _writev(child, next_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.marked_flow_sl())
+ {
+ this->Writer::_do_write(ind);
+ this->Writer::_do_write("- ");
+ _do_visit_flow_sl(child, 0u);
+ this->Writer::_do_write('\n');
+ }
+ else if(ty.marked_flow_ml())
+ {
+ this->Writer::_do_write(ind);
+ this->Writer::_do_write("- ");
+ _do_visit_flow_ml(child, next_level, do_indent);
+ this->Writer::_do_write('\n');
+ }
+ else
+ {
+ _do_visit_block(child, next_level, do_indent);
+ }
+ }
+ do_indent = true;
+ ind = indent_to(do_indent * next_level);
+ }
+ }
+ else // map
+ {
+ _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_map(node));
+ for(size_t 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))
+ {
+ this->Writer::_do_write(ind);
+ _writek(ich, next_level);
+ this->Writer::_do_write(": ");
+ _writev(ich, next_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.marked_flow_sl())
+ {
+ this->Writer::_do_write(ind);
+ _do_visit_flow_sl(ich, 0u);
+ this->Writer::_do_write('\n');
+ }
+ else if(ty.marked_flow_ml())
+ {
+ this->Writer::_do_write(ind);
+ _do_visit_flow_ml(ich, 0u);
+ this->Writer::_do_write('\n');
+ }
+ else
+ {
+ _do_visit_block(ich, next_level, do_indent);
+ }
+ }
+ do_indent = true;
+ ind = indent_to(do_indent * next_level);
+ }
+ }
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_block(size_t node, size_t ilevel, size_t do_indent)
+{
+ RYML_ASSERT(!m_tree->is_stream(node));
+ RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node));
+ RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node)));
+ RepC ind = indent_to(do_indent * ilevel);
+
+ if(m_tree->is_doc(node))
+ {
+ _write_doc(node);
+ if(!m_tree->has_children(node))
+ return;
+ }
+ else if(m_tree->is_container(node))
+ {
+ RYML_ASSERT(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))
+ {
+ this->Writer::_do_write(ind);
+ _writek(node, ilevel);
+ this->Writer::_do_write(':');
+ spc = true;
+ }
+ else if(!m_tree->is_root(node))
+ {
+ this->Writer::_do_write(ind);
+ 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
+
+ size_t 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, next_level, do_indent);
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_json(size_t id)
+{
+ _RYML_CB_CHECK(m_tree->callbacks(), !m_tree->is_stream(id)); // JSON does not have streams
+ 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(size_t 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);
+ }
+
+ if(m_tree->is_seq(id))
+ this->Writer::_do_write(']');
+ else if(m_tree->is_map(id))
+ this->Writer::_do_write('}');
+}
+
+template<class Writer>
+void Emitter<Writer>::_write(NodeScalar const& C4_RESTRICT sc, NodeType flags, size_t 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);
+ return;
+ }
+
+ // ensure the style flags only have one of KEY or VAL
+ _RYML_CB_ASSERT(m_tree->callbacks(), ((flags & (_WIP_KEY_STYLE|_WIP_VAL_STYLE)) == 0) || (((flags&_WIP_KEY_STYLE) == 0) != ((flags&_WIP_VAL_STYLE) == 0)));
+
+ auto style_marks = flags & (_WIP_KEY_STYLE|_WIP_VAL_STYLE);
+ if(style_marks & (_WIP_KEY_LITERAL|_WIP_VAL_LITERAL))
+ {
+ _write_scalar_literal(sc.scalar, ilevel, flags.has_key());
+ }
+ else if(style_marks & (_WIP_KEY_FOLDED|_WIP_VAL_FOLDED))
+ {
+ _write_scalar_folded(sc.scalar, ilevel, flags.has_key());
+ }
+ else if(style_marks & (_WIP_KEY_SQUO|_WIP_VAL_SQUO))
+ {
+ _write_scalar_squo(sc.scalar, ilevel);
+ }
+ else if(style_marks & (_WIP_KEY_DQUO|_WIP_VAL_DQUO))
+ {
+ _write_scalar_dquo(sc.scalar, ilevel);
+ }
+ else if(style_marks & (_WIP_KEY_PLAIN|_WIP_VAL_PLAIN))
+ {
+ _write_scalar_plain(sc.scalar, ilevel);
+ }
+ else if(!style_marks)
+ {
+ size_t first_non_nl = sc.scalar.first_not_of('\n');
+ bool all_newlines = first_non_nl == npos;
+ bool has_leading_ws = (!all_newlines) && sc.scalar.sub(first_non_nl).begins_with_any(" \t");
+ bool do_literal = ((!sc.scalar.empty() && all_newlines) || (has_leading_ws && !sc.scalar.trim(' ').empty()));
+ if(do_literal)
+ {
+ _write_scalar_literal(sc.scalar, ilevel, flags.has_key(), /*explicit_indentation*/has_leading_ws);
+ }
+ else
+ {
+ for(size_t i = 0; i < sc.scalar.len; ++i)
+ {
+ if(sc.scalar.str[i] == '\n')
+ {
+ _write_scalar_literal(sc.scalar, ilevel, flags.has_key(), /*explicit_indentation*/has_leading_ws);
+ goto wrote_special;
+ }
+ // todo: check for escaped characters requiring double quotes
+ }
+ _write_scalar(sc.scalar, flags.is_quoted());
+ wrote_special:
+ ;
+ }
+ }
+ else
+ {
+ _RYML_CB_ERR(m_tree->callbacks(), "not implemented");
+ }
+}
+template<class Writer>
+void Emitter<Writer>::_write_json(NodeScalar const& C4_RESTRICT sc, NodeType flags)
+{
+ if(C4_UNLIKELY( ! sc.tag.empty()))
+ _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have tags");
+ if(C4_UNLIKELY(flags.has_anchor()))
+ _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have anchors");
+ _write_scalar_json(sc.scalar, flags.has_key(), flags.is_quoted());
+}
+
+#define _rymlindent_nextline() for(size_t lv = 0; lv < ilevel+1; ++lv) { this->Writer::_do_write(' '); this->Writer::_do_write(' '); }
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar_literal(csubstr s, size_t ilevel, bool explicit_key, bool explicit_indentation)
+{
+ if(explicit_key)
+ this->Writer::_do_write("? ");
+ csubstr trimmed = s.trimr("\n\r");
+ size_t numnewlines_at_end = s.len - trimmed.len - s.sub(trimmed.len).count('\r');
+ //
+ if(!explicit_indentation)
+ this->Writer::_do_write('|');
+ else
+ this->Writer::_do_write("|2");
+ //
+ if(numnewlines_at_end > 1 || (trimmed.len == 0 && s.len > 0)/*only newlines*/)
+ this->Writer::_do_write("+\n");
+ else if(numnewlines_at_end == 1)
+ this->Writer::_do_write('\n');
+ else
+ this->Writer::_do_write("-\n");
+ //
+ if(trimmed.len)
+ {
+ 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));
+ }
+ if(numnewlines_at_end)
+ {
+ this->Writer::_do_write('\n');
+ --numnewlines_at_end;
+ }
+ }
+ for(size_t i = 0; i < numnewlines_at_end; ++i)
+ {
+ _rymlindent_nextline()
+ if(i+1 < numnewlines_at_end || explicit_key)
+ this->Writer::_do_write('\n');
+ }
+ if(explicit_key && !numnewlines_at_end)
+ this->Writer::_do_write('\n');
+}
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar_folded(csubstr s, size_t ilevel, bool explicit_key)
+{
+ if(explicit_key)
+ {
+ this->Writer::_do_write("? ");
+ }
+ RYML_ASSERT(s.find("\r") == csubstr::npos);
+ csubstr trimmed = s.trimr('\n');
+ size_t numnewlines_at_end = s.len - trimmed.len;
+ if(numnewlines_at_end == 0)
+ {
+ this->Writer::_do_write(">-\n");
+ }
+ else if(numnewlines_at_end == 1)
+ {
+ this->Writer::_do_write(">\n");
+ }
+ else if(numnewlines_at_end > 1)
+ {
+ this->Writer::_do_write(">+\n");
+ }
+ if(trimmed.len)
+ {
+ 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
+ pos = i+1; // because of the newline
+ _rymlindent_nextline()
+ this->Writer::_do_write(since_pos);
+ this->Writer::_do_write('\n'); // write the newline twice
+ }
+ if(pos < trimmed.len)
+ {
+ _rymlindent_nextline()
+ this->Writer::_do_write(trimmed.sub(pos));
+ }
+ if(numnewlines_at_end)
+ {
+ this->Writer::_do_write('\n');
+ --numnewlines_at_end;
+ }
+ }
+ for(size_t i = 0; i < numnewlines_at_end; ++i)
+ {
+ _rymlindent_nextline()
+ if(i+1 < numnewlines_at_end || explicit_key)
+ this->Writer::_do_write('\n');
+ }
+ if(explicit_key && !numnewlines_at_end)
+ this->Writer::_do_write('\n');
+}
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar_squo(csubstr s, size_t 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')
+ {
+ csubstr sub = s.range(pos, i+1);
+ this->Writer::_do_write(sub); // write everything up to (including) this char
+ this->Writer::_do_write('\n'); // write the character again
+ if(i + 1 < s.len)
+ _rymlindent_nextline() // indent the next line
+ pos = i+1;
+ }
+ else if(s[i] == '\'')
+ {
+ csubstr sub = s.range(pos, i+1);
+ this->Writer::_do_write(sub); // write everything up to (including) this char
+ this->Writer::_do_write('\''); // write the character 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<class Writer>
+void Emitter<Writer>::_write_scalar_dquo(csubstr s, size_t 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];
+ if(curr == '"' || curr == '\\')
+ {
+ 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;
+ }
+ else if(s[i] == '\n')
+ {
+ csubstr sub = s.range(pos, i+1);
+ this->Writer::_do_write(sub); // write everything up to (including) this newline
+ this->Writer::_do_write('\n'); // write the newline again
+ if(i + 1 < s.len)
+ _rymlindent_nextline() // indent the next line
+ pos = i+1;
+ if(i+1 < s.len) // escape leading whitespace after the newline
+ {
+ const char next = s.str[i+1];
+ if(next == ' ' || next == '\t')
+ this->Writer::_do_write('\\');
+ }
+ }
+ else if(curr == ' ' || curr == '\t')
+ {
+ // escape trailing whitespace before a newline
+ size_t next = s.first_not_of(" \t\r", i);
+ if(next != npos && s[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;
+ }
+ }
+ }
+ // write missing characters at the end of the string
+ if(pos < s.len)
+ {
+ csubstr sub = s.sub(pos);
+ this->Writer::_do_write(sub);
+ }
+ this->Writer::_do_write('"');
+}
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar_plain(csubstr s, size_t 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+1);
+ this->Writer::_do_write(sub); // write everything up to (including) this newline
+ this->Writer::_do_write('\n'); // write the newline again
+ if(i + 1 < s.len)
+ _rymlindent_nextline() // indent the next line
+ pos = i+1;
+ }
+ }
+ // write missing characters at the end of the string
+ if(pos < s.len)
+ {
+ csubstr sub = s.sub(pos);
+ this->Writer::_do_write(sub);
+ }
+}
+
+#undef _rymlindent_nextline
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar(csubstr s, bool was_quoted)
+{
+ // this block of code needed to be moved to before the needs_quotes
+ // assignment to work around a g++ optimizer bug where (s.str != nullptr)
+ // was evaluated as true even if s.str was actually a nullptr (!!!)
+ if(s.len == size_t(0))
+ {
+ if(was_quoted)
+ this->Writer::_do_write("''");
+ return;
+ }
+
+ const bool needs_quotes = (
+ was_quoted
+ ||
+ (
+ ( ! s.is_number())
+ &&
+ (
+ // has leading whitespace
+ s.begins_with_any(" \n\t\r")
+ ||
+ // looks like reference or anchor or would be treated as a directive
+ s.begins_with_any("*&%")
+ ||
+ s.begins_with("<<")
+ ||
+ // has trailing whitespace
+ s.ends_with_any(" \n\t\r")
+ ||
+ // has special chars
+ (s.first_of("#:-?,\n{}[]'\"") != npos)
+ )
+ )
+ );
+
+ if( ! needs_quotes)
+ {
+ this->Writer::_do_write(s);
+ }
+ else
+ {
+ const bool has_dquotes = s.first_of( '"') != npos;
+ const bool has_squotes = s.first_of('\'') != npos;
+ if(!has_squotes && has_dquotes)
+ {
+ this->Writer::_do_write('\'');
+ this->Writer::_do_write(s);
+ this->Writer::_do_write('\'');
+ }
+ else if(has_squotes && !has_dquotes)
+ {
+ RYML_ASSERT(s.count('\n') == 0);
+ this->Writer::_do_write('"');
+ this->Writer::_do_write(s);
+ this->Writer::_do_write('"');
+ }
+ else
+ {
+ _write_scalar_squo(s, /*FIXME FIXME FIXME*/0);
+ }
+ }
+}
+template<class Writer>
+void Emitter<Writer>::_write_scalar_json(csubstr s, bool as_key, bool was_quoted)
+{
+ if(was_quoted)
+ {
+ this->Writer::_do_write('"');
+ this->Writer::_do_write(s);
+ this->Writer::_do_write('"');
+ }
+ // json only allows strings as keys
+ else if(!as_key && (s.is_number() || s == "true" || s == "null" || s == "false"))
+ {
+ this->Writer::_do_write(s);
+ }
+ else
+ {
+ size_t pos = 0;
+ this->Writer::_do_write('"');
+ for(size_t i = 0; i < s.len; ++i)
+ {
+ switch (s[i])
+ {
+ case '"':
+ case '\n': {
+ if(i > 0)
+ {
+ csubstr sub = s.range(pos, i);
+ this->Writer::_do_write(sub);
+ }
+ pos = i + 1;
+ switch (s[i]) {
+ case '"':
+ this->Writer::_do_write("\\\"");
+ break;
+ case '\n':
+ this->Writer::_do_write("\\n");
+ break;
+ }
+ 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 <type_traits>
+#endif
+
+//included above:
+//#include <string.h>
+
+namespace c4 {
+namespace yml {
+namespace detail {
+
+/** A lightweight contiguous stack with SSO. This avoids a dependency on std. */
+template<class T, size_t N=16>
+class stack
+{
+ static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
+ static_assert(std::is_trivially_destructible<T>::value, "T must be trivially destructible");
+
+ enum : size_t { sso_size = N };
+
+public:
+
+ T m_buf[N];
+ T * m_stack;
+ size_t m_size;
+ size_t 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) 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) noexcept
+ {
+ _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:
+
+ size_t size() const { return m_size; }
+ size_t empty() const { return m_size == 0; }
+ size_t capacity() const { return m_capacity; }
+
+ void clear()
+ {
+ m_size = 0;
+ }
+
+ void resize(size_t sz)
+ {
+ reserve(sz);
+ m_size = sz;
+ }
+
+ void reserve(size_t sz);
+
+ void push(T const& C4_RESTRICT n)
+ {
+ RYML_ASSERT((const char*)&n + sizeof(T) < (const char*)m_stack || &n > m_stack + m_capacity);
+ if(m_size == m_capacity)
+ {
+ size_t cap = m_capacity == 0 ? N : 2 * m_capacity;
+ reserve(cap);
+ }
+ m_stack[m_size] = n;
+ ++m_size;
+ }
+
+ void push_top()
+ {
+ RYML_ASSERT(m_size > 0);
+ if(m_size == m_capacity)
+ {
+ size_t 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_ASSERT(m_size > 0);
+ --m_size;
+ return m_stack[m_size];
+ }
+
+ C4_ALWAYS_INLINE T const& C4_RESTRICT top() const { RYML_ASSERT(m_size > 0); return m_stack[m_size - 1]; }
+ C4_ALWAYS_INLINE T & C4_RESTRICT top() { RYML_ASSERT(m_size > 0); return m_stack[m_size - 1]; }
+
+ C4_ALWAYS_INLINE T const& C4_RESTRICT bottom() const { RYML_ASSERT(m_size > 0); return m_stack[0]; }
+ C4_ALWAYS_INLINE T & C4_RESTRICT bottom() { RYML_ASSERT(m_size > 0); return m_stack[0]; }
+
+ C4_ALWAYS_INLINE T const& C4_RESTRICT top(size_t i) const { RYML_ASSERT(i < m_size); return m_stack[m_size - 1 - i]; }
+ C4_ALWAYS_INLINE T & C4_RESTRICT top(size_t i) { RYML_ASSERT(i < m_size); return m_stack[m_size - 1 - i]; }
+
+ C4_ALWAYS_INLINE T const& C4_RESTRICT bottom(size_t i) const { RYML_ASSERT(i < m_size); return m_stack[i]; }
+ C4_ALWAYS_INLINE T & C4_RESTRICT bottom(size_t i) { RYML_ASSERT(i < m_size); return m_stack[i]; }
+
+ C4_ALWAYS_INLINE T const& C4_RESTRICT operator[](size_t i) const { RYML_ASSERT(i < m_size); return m_stack[i]; }
+ C4_ALWAYS_INLINE T & C4_RESTRICT operator[](size_t i) { RYML_ASSERT(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<class T, size_t N>
+void stack<T, N>::reserve(size_t sz)
+{
+ if(sz <= m_size)
+ return;
+ if(sz <= N)
+ {
+ m_stack = m_buf;
+ m_capacity = N;
+ return;
+ }
+ T *buf = (T*) m_callbacks.m_allocate(sz * sizeof(T), m_stack, m_callbacks.m_user_data);
+ memcpy(buf, m_stack, m_size * sizeof(T));
+ if(m_stack != m_buf)
+ {
+ m_callbacks.m_free(m_stack, m_capacity * sizeof(T), m_callbacks.m_user_data);
+ }
+ m_stack = buf;
+ m_capacity = sz;
+}
+
+
+//-----------------------------------------------------------------------------
+
+template<class T, size_t N>
+void stack<T, N>::_free()
+{
+ RYML_ASSERT(m_stack != nullptr); // this structure cannot be memset() to zero
+ if(m_stack != m_buf)
+ {
+ m_callbacks.m_free(m_stack, m_capacity * sizeof(T), m_callbacks.m_user_data);
+ m_stack = m_buf;
+ m_size = N;
+ m_capacity = N;
+ }
+ else
+ {
+ RYML_ASSERT(m_capacity == N);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+
+template<class T, size_t N>
+void stack<T, N>::_cp(stack const* C4_RESTRICT that)
+{
+ if(that->m_stack != that->m_buf)
+ {
+ RYML_ASSERT(that->m_capacity > N);
+ RYML_ASSERT(that->m_size <= that->m_capacity);
+ }
+ else
+ {
+ RYML_ASSERT(that->m_capacity <= N);
+ RYML_ASSERT(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<class T, size_t N>
+void stack<T, N>::_mv(stack * that)
+{
+ if(that->m_stack != that->m_buf)
+ {
+ RYML_ASSERT(that->m_capacity > N);
+ RYML_ASSERT(that->m_size <= that->m_capacity);
+ m_stack = that->m_stack;
+ }
+ else
+ {
+ RYML_ASSERT(that->m_capacity <= N);
+ RYML_ASSERT(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_ASSERT(that->m_stack != m_buf);
+ that->m_stack = that->m_buf;
+ that->m_capacity = N;
+ that->m_size = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+
+template<class T, size_t N>
+void stack<T, N>::_cb(Callbacks const& cb)
+{
+ if(cb != m_callbacks)
+ {
+ _free();
+ m_callbacks = cb;
+ }
+}
+
+} // namespace detail
+} // 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/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_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_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_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
+
+//included above:
+//#include <stdarg.h>
+
+#if defined(_MSC_VER)
+# pragma warning(push)
+# pragma warning(disable: 4251/*needs to have dll-interface to be used by clients of struct*/)
+#endif
+
+namespace c4 {
+namespace yml {
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+class RYML_EXPORT Parser
+{
+public:
+
+ /** @name construction and assignment */
+ /** @{ */
+
+ Parser() : Parser(get_callbacks()) {}
+ Parser(Callbacks const& cb);
+ ~Parser();
+
+ Parser(Parser &&);
+ Parser(Parser const&);
+ Parser& operator=(Parser &&);
+ Parser& operator=(Parser 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 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.
+ *
+ * @note the tree and the arena can (and should) also be reserved. */
+ void reserve_stack(size_t capacity)
+ {
+ 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);
+ }
+
+ /** Reserve a certain capacity for the character arena used to
+ * filter scalars. */
+ void reserve_filter_arena(size_t num_characters)
+ {
+ _resize_filter_arena(num_characters);
+ }
+
+ /** @} */
+
+public:
+
+ /** @name getters and modifiers */
+ /** @{ */
+
+ /** Get the current callbacks in the parser. */
+ Callbacks callbacks() const { return 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; }
+
+ size_t stack_capacity() const { return m_stack.capacity(); }
+ size_t locations_capacity() const { return m_newline_offsets_capacity; }
+ size_t filter_arena_capacity() const { return m_filter_arena.len; }
+
+ /** @} */
+
+public:
+
+ /** @name parse_in_place */
+ /** @{ */
+
+ /** Create a new tree and parse into its root.
+ * The tree is created with the callbacks currently in the parser. */
+ Tree parse_in_place(csubstr filename, substr src)
+ {
+ Tree t(callbacks());
+ t.reserve(_estimate_capacity(src));
+ this->parse_in_place(filename, src, &t, t.root_id());
+ return t;
+ }
+
+ /** Parse into an existing tree, starting at its root node.
+ * The callbacks in the tree are kept, and used to allocate
+ * the tree members, if any allocation is required. */
+ void parse_in_place(csubstr filename, substr src, Tree *t)
+ {
+ this->parse_in_place(filename, src, t, t->root_id());
+ }
+
+ /** Parse into an existing node.
+ * The callbacks in the tree are kept, and used to allocate
+ * the tree members, if any allocation is required. */
+ void parse_in_place(csubstr filename, substr src, Tree *t, size_t node_id);
+ // ^^^^^^^^^^^^^ this is the workhorse overload; everything else is syntactic candy
+
+ /** Parse into an existing node.
+ * The callbacks in the tree are kept, and used to allocate
+ * the tree members, if any allocation is required. */
+ void parse_in_place(csubstr filename, substr src, NodeRef node)
+ {
+ this->parse_in_place(filename, src, node.tree(), node.id());
+ }
+
+ RYML_DEPRECATED("use parse_in_place() instead") Tree parse(csubstr filename, substr src) { return parse_in_place(filename, src); }
+ RYML_DEPRECATED("use parse_in_place() instead") void parse(csubstr filename, substr src, Tree *t) { parse_in_place(filename, src, t); }
+ RYML_DEPRECATED("use parse_in_place() instead") void parse(csubstr filename, substr src, Tree *t, size_t node_id) { parse_in_place(filename, src, t, node_id); }
+ RYML_DEPRECATED("use parse_in_place() instead") void parse(csubstr filename, substr src, NodeRef node) { parse_in_place(filename, src, node); }
+
+ /** @} */
+
+public:
+
+ /** @name parse_in_arena: copy the YAML source buffer to the
+ * tree's arena, then parse the copy in situ
+ *
+ * @note 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
+ * is implicitly convertible to csubstr. 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. */
+ /** @{ */
+
+ // READ THE NOTE ABOVE!
+ #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 and will cause a compiler error."
+ RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) Tree parse_in_arena(csubstr filename, substr csrc);
+ RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr csrc, Tree *t);
+ RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr csrc, Tree *t, size_t node_id);
+ RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr csrc, NodeRef node);
+
+ /** Create a new tree and parse into its root.
+ * The immutable YAML source is first copied to the tree's arena,
+ * and parsed from there.
+ * The callbacks in the tree are kept, and used to allocate
+ * the tree members, if any allocation is required. */
+ Tree parse_in_arena(csubstr filename, csubstr csrc)
+ {
+ Tree t(callbacks());
+ substr src = t.copy_to_arena(csrc);
+ t.reserve(_estimate_capacity(csrc));
+ this->parse_in_place(filename, src, &t, t.root_id());
+ return t;
+ }
+
+ /** Parse into an existing tree, starting at its root node.
+ * The immutable YAML source is first copied to the tree's arena,
+ * and parsed from there.
+ * The callbacks in the tree are kept, and used to allocate
+ * the tree members, if any allocation is required. */
+ void parse_in_arena(csubstr filename, csubstr csrc, Tree *t)
+ {
+ substr src = t->copy_to_arena(csrc);
+ this->parse_in_place(filename, src, t, t->root_id());
+ }
+
+ /** Parse into a specific node in an existing tree.
+ * The immutable YAML source is first copied to the tree's arena,
+ * and parsed from there.
+ * The callbacks in the tree are kept, and used to allocate
+ * the tree members, if any allocation is required. */
+ void parse_in_arena(csubstr filename, csubstr csrc, Tree *t, size_t node_id)
+ {
+ substr src = t->copy_to_arena(csrc);
+ this->parse_in_place(filename, src, t, node_id);
+ }
+
+ /** Parse into a specific node in an existing tree.
+ * The immutable YAML source is first copied to the tree's arena,
+ * and parsed from there.
+ * The callbacks in the tree are kept, and used to allocate
+ * the tree members, if any allocation is required. */
+ void parse_in_arena(csubstr filename, csubstr csrc, NodeRef node)
+ {
+ substr src = node.tree()->copy_to_arena(csrc);
+ this->parse_in_place(filename, src, node.tree(), node.id());
+ }
+
+ RYML_DEPRECATED("use parse_in_arena() instead") Tree parse(csubstr filename, csubstr csrc) { return parse_in_arena(filename, csrc); }
+ RYML_DEPRECATED("use parse_in_arena() instead") void parse(csubstr filename, csubstr csrc, Tree *t) { parse_in_arena(filename, csrc, t); }
+ RYML_DEPRECATED("use parse_in_arena() instead") void parse(csubstr filename, csubstr csrc, Tree *t, size_t node_id) { parse_in_arena(filename, csrc, t, node_id); }
+ RYML_DEPRECATED("use parse_in_arena() instead") void parse(csubstr filename, csubstr csrc, NodeRef node) { parse_in_arena(filename, csrc, node); }
+
+ /** @} */
+
+public:
+
+ /** @name locations */
+ /** @{ */
+
+ /** Get the location of a node of the last tree to be parsed by this parser. */
+ Location location(Tree const& tree, size_t node_id) const;
+ /** Get the location of a node of the last tree to be parsed by this parser. */
+ Location location(NodeRef 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. @p val
+ * must be pointing to somewhere in the source buffer that was
+ * last parsed by this object. */
+ Location val_location(const char *val) const;
+
+ /** @} */
+
+private:
+
+ 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;
+
+private:
+
+ using flag_t = int;
+
+ static size_t _estimate_capacity(csubstr src) { size_t c = _count_nlines(src); c = c >= 16 ? c : 16; return c; }
+
+ void _reset();
+
+ bool _finished_file() const;
+ bool _finished_line() const;
+
+ csubstr _peek_next_line(size_t pos=npos) const;
+ bool _advance_to_peeked();
+ void _scan_line();
+
+ csubstr _slurp_doc_scalar();
+
+ /**
+ * @param [out] quoted
+ * Will only be written to if this method returns true.
+ * Will be set to true if the scanned scalar was quoted, by '', "", > or |.
+ */
+ bool _scan_scalar(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted);
+
+ csubstr _scan_comment();
+ csubstr _scan_squot_scalar();
+ csubstr _scan_dquot_scalar();
+ csubstr _scan_block();
+ substr _scan_plain_scalar_blck(csubstr currscalar, csubstr peeked_line, size_t indentation);
+ substr _scan_plain_scalar_flow(csubstr currscalar, csubstr peeked_line);
+ substr _scan_complex_key(csubstr currscalar, csubstr peeked_line);
+ csubstr _scan_to_next_nonempty_line(size_t indentation);
+ csubstr _extend_scanned_scalar(csubstr currscalar);
+
+ csubstr _filter_squot_scalar(const substr s);
+ csubstr _filter_dquot_scalar(substr s);
+ csubstr _filter_plain_scalar(substr s, size_t indentation);
+ csubstr _filter_block_scalar(substr s, BlockStyle_e style, BlockChomp_e chomp, size_t indentation);
+ template<bool backslash_is_escape, bool keep_trailing_whitespace>
+ bool _filter_nl(substr scalar, size_t *C4_RESTRICT pos, size_t *C4_RESTRICT filter_arena_pos, size_t indentation);
+ template<bool keep_trailing_whitespace>
+ void _filter_ws(substr scalar, size_t *C4_RESTRICT pos, size_t *C4_RESTRICT filter_arena_pos);
+ bool _apply_chomp(substr buf, size_t *C4_RESTRICT pos, BlockChomp_e chomp);
+
+ void _handle_finished_file();
+ void _handle_line();
+
+ bool _handle_indentation();
+
+ bool _handle_unk();
+ bool _handle_map_flow();
+ bool _handle_map_blck();
+ bool _handle_seq_flow();
+ bool _handle_seq_blck();
+ bool _handle_top();
+ bool _handle_types();
+ bool _handle_key_anchors_and_refs();
+ bool _handle_val_anchors_and_refs();
+ void _move_val_tag_to_key_tag();
+ void _move_key_tag_to_val_tag();
+ void _move_key_tag2_to_key_tag();
+ void _move_val_anchor_to_key_anchor();
+ void _move_key_anchor_to_val_anchor();
+
+ void _push_level(bool explicit_flow_chars = false);
+ void _pop_level();
+
+ void _start_unk(bool as_child=true);
+
+ void _start_map(bool as_child=true);
+ void _start_map_unk(bool as_child);
+ void _stop_map();
+
+ void _start_seq(bool as_child=true);
+ void _stop_seq();
+
+ void _start_seqimap();
+ void _stop_seqimap();
+
+ void _start_doc(bool as_child=true);
+ void _stop_doc();
+ void _start_new_doc(csubstr rem);
+ void _end_stream();
+
+ NodeData* _append_val(csubstr val, flag_t quoted=false);
+ NodeData* _append_key_val(csubstr val, flag_t val_quoted=false);
+ bool _rval_dash_start_or_continue_seq();
+
+ void _store_scalar(csubstr s, flag_t is_quoted);
+ csubstr _consume_scalar();
+ void _move_scalar_from_top();
+
+ inline NodeData* _append_val_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); return _append_val({str, size_t(0)}); }
+ inline NodeData* _append_key_val_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); return _append_key_val({str, size_t(0)}); }
+ inline void _store_scalar_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); _store_scalar({str, size_t(0)}, false); }
+
+ void _set_indentation(size_t behind);
+ void _save_indentation(size_t behind=0);
+ bool _maybe_set_indentation_from_anchor_or_tag();
+
+ void _write_key_anchor(size_t node_id);
+ void _write_val_anchor(size_t node_id);
+
+ void _handle_directive(csubstr directive);
+
+ void _skipchars(char c);
+ template<size_t N>
+ void _skipchars(const char (&chars)[N]);
+
+private:
+
+ static size_t _count_nlines(csubstr src);
+
+private:
+
+ typedef enum : flag_t {
+ RTOP = 0x01 << 0, ///< reading at top level
+ RUNK = 0x01 << 1, ///< reading an unknown: 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 {}
+ QMRK = 0x01 << 5, ///< reading an explicit key (`? key`)
+ RKEY = 0x01 << 6, ///< reading a scalar as key
+ RVAL = 0x01 << 7, ///< reading a scalar as val
+ RNXT = 0x01 << 8, ///< read next val or keyval
+ SSCL = 0x01 << 9, ///< there's a stored scalar
+ QSCL = 0x01 << 10, ///< stored scalar was quoted
+ RSET = 0x01 << 11, ///< the (implicit) map being read is a !!set. @see https://yaml.org/type/set.html
+ NDOC = 0x01 << 12, ///< no document mode. a document has ended and another has not started yet.
+ //! 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 << 13,
+ } State_e;
+
+ struct LineContents
+ {
+ csubstr full; ///< the full line, including newlines on the right
+ csubstr stripped; ///< the stripped line, excluding newlines on the right
+ csubstr 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
+
+ LineContents() : full(), stripped(), rem(), indentation() {}
+
+ void reset_with_next_line(csubstr buf, size_t pos);
+
+ void reset(csubstr full_, csubstr stripped_)
+ {
+ full = full_;
+ stripped = stripped_;
+ rem = stripped_;
+ // find the first column where the character is not a space
+ indentation = full.first_not_of(' ');
+ }
+
+ size_t current_col() const
+ {
+ return current_col(rem);
+ }
+
+ size_t current_col(csubstr s) const
+ {
+ RYML_ASSERT(s.str >= full.str);
+ RYML_ASSERT(full.is_super(s));
+ size_t col = static_cast<size_t>(s.str - full.str);
+ return col;
+ }
+ };
+
+ struct State
+ {
+ flag_t flags;
+ size_t level;
+ size_t node_id; // don't hold a pointer to the node as it will be relocated during tree resizes
+ csubstr scalar;
+ size_t scalar_col; // the column where the scalar (or its quotes) begin
+
+ Location pos;
+ LineContents line_contents;
+ size_t indref;
+
+ State() : flags(), level(), node_id(), scalar(), scalar_col(), pos(), line_contents(), indref() {}
+
+ void reset(const char *file, size_t node_id_)
+ {
+ flags = RUNK|RTOP;
+ level = 0;
+ pos.name = to_csubstr(file);
+ pos.offset = 0;
+ pos.line = 1;
+ pos.col = 1;
+ node_id = node_id_;
+ scalar_col = 0;
+ scalar.clear();
+ indref = 0;
+ }
+ };
+
+ void _line_progressed(size_t ahead);
+ void _line_ended();
+ void _line_ended_undo();
+
+ void _prepare_pop()
+ {
+ RYML_ASSERT(m_stack.size() > 1);
+ State const& curr = m_stack.top();
+ State & next = m_stack.top(1);
+ next.pos = curr.pos;
+ next.line_contents = curr.line_contents;
+ next.scalar = curr.scalar;
+ }
+
+ inline bool _at_line_begin() const
+ {
+ return m_state->line_contents.rem.begin() == m_state->line_contents.full.begin();
+ }
+ inline bool _at_line_end() const
+ {
+ csubstr r = m_state->line_contents.rem;
+ return r.empty() || r.begins_with(' ', r.len);
+ }
+ inline bool _token_is_from_this_line(csubstr token) const
+ {
+ return token.is_sub(m_state->line_contents.full);
+ }
+
+ inline NodeData * node(State const* s) const { return m_tree->get(s->node_id); }
+ inline NodeData * node(State const& s) const { return m_tree->get(s .node_id); }
+ inline NodeData * node(size_t node_id) const { return m_tree->get( node_id); }
+
+ inline bool has_all(flag_t f) const { return (m_state->flags & f) == f; }
+ inline bool has_any(flag_t f) const { return (m_state->flags & f) != 0; }
+ inline bool has_none(flag_t f) const { return (m_state->flags & f) == 0; }
+
+ static inline bool has_all(flag_t f, State const* s) { return (s->flags & f) == f; }
+ static inline bool has_any(flag_t f, State const* s) { return (s->flags & f) != 0; }
+ static inline bool has_none(flag_t f, State const* s) { return (s->flags & f) == 0; }
+
+ inline void set_flags(flag_t f) { set_flags(f, m_state); }
+ inline void add_flags(flag_t on) { add_flags(on, m_state); }
+ inline void addrem_flags(flag_t on, flag_t off) { addrem_flags(on, off, m_state); }
+ inline void rem_flags(flag_t off) { rem_flags(off, m_state); }
+
+ void set_flags(flag_t f, State * s);
+ void add_flags(flag_t on, State * s);
+ void addrem_flags(flag_t on, flag_t off, State * s);
+ void rem_flags(flag_t off, State * s);
+
+ void _resize_filter_arena(size_t num_characters);
+ void _grow_filter_arena(size_t num_characters);
+ substr _finish_filter_arena(substr dst, size_t pos);
+
+ void _prepare_locations() const; // only changes mutable members
+ void _resize_locations(size_t sz) const; // only changes mutable members
+ void _mark_locations_dirty();
+ bool _locations_dirty() const;
+
+private:
+
+ void _free();
+ void _clr();
+ void _cp(Parser const* that);
+ void _mv(Parser *that);
+
+#ifdef RYML_DBG
+ template<class ...Args> void _dbg(csubstr fmt, Args const& C4_RESTRICT ...args) const;
+#endif
+ template<class ...Args> void _err(csubstr fmt, Args const& C4_RESTRICT ...args) const;
+ template<class DumpFn> void _fmt_msg(DumpFn &&dumpfn) const;
+ static csubstr _prfl(substr buf, flag_t v);
+
+private:
+
+ csubstr m_file;
+ substr m_buf;
+
+ size_t m_root_id;
+ Tree * m_tree;
+
+ detail::stack<State> m_stack;
+ State * m_state;
+
+ size_t m_key_tag_indentation;
+ size_t m_key_tag2_indentation;
+ csubstr m_key_tag;
+ csubstr m_key_tag2;
+ size_t m_val_tag_indentation;
+ csubstr m_val_tag;
+
+ bool m_key_anchor_was_before;
+ size_t m_key_anchor_indentation;
+ csubstr m_key_anchor;
+ size_t m_val_anchor_indentation;
+ csubstr m_val_anchor;
+
+ substr m_filter_arena;
+
+ mutable size_t *m_newline_offsets;
+ mutable size_t m_newline_offsets_size;
+ mutable size_t m_newline_offsets_capacity;
+ mutable csubstr m_newline_offsets_buf;
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+/** @name parse_in_place
+ *
+ * @desc parse a mutable YAML source buffer.
+ *
+ * @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 object after it has parsed the
+ * code. If you need access to any of these properties, use
+ * Parser::parse_in_place() */
+/** @{ */
+
+inline Tree parse_in_place( substr yaml ) { Parser np; return np.parse_in_place({} , yaml); } //!< parse in-situ a modifiable YAML source buffer.
+inline Tree parse_in_place(csubstr filename, substr yaml ) { Parser np; return np.parse_in_place(filename, yaml); } //!< parse in-situ a modifiable YAML source buffer, providing a filename for error messages.
+inline void parse_in_place( substr yaml, Tree *t ) { Parser np; np.parse_in_place({} , yaml, t); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer
+inline void parse_in_place(csubstr filename, substr yaml, Tree *t ) { Parser np; np.parse_in_place(filename, yaml, t); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer, providing a filename for error messages.
+inline void parse_in_place( substr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_place({} , yaml, t, node_id); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer
+inline void parse_in_place(csubstr filename, substr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_place(filename, yaml, t, node_id); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer, providing a filename for error messages.
+inline void parse_in_place( substr yaml, NodeRef node ) { Parser np; np.parse_in_place({} , yaml, node); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer
+inline void parse_in_place(csubstr filename, substr yaml, NodeRef node ) { Parser np; np.parse_in_place(filename, yaml, node); } //!< reusing the YAML tree, parse in-situ a modifiable YAML source buffer, providing a filename for error messages.
+
+RYML_DEPRECATED("use parse_in_place() instead") inline Tree parse( substr yaml ) { Parser np; return np.parse_in_place({} , yaml); }
+RYML_DEPRECATED("use parse_in_place() instead") inline Tree parse(csubstr filename, substr yaml ) { Parser np; return np.parse_in_place(filename, yaml); }
+RYML_DEPRECATED("use parse_in_place() instead") inline void parse( substr yaml, Tree *t ) { Parser np; np.parse_in_place({} , yaml, t); }
+RYML_DEPRECATED("use parse_in_place() instead") inline void parse(csubstr filename, substr yaml, Tree *t ) { Parser np; np.parse_in_place(filename, yaml, t); }
+RYML_DEPRECATED("use parse_in_place() instead") inline void parse( substr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_place({} , yaml, t, node_id); }
+RYML_DEPRECATED("use parse_in_place() instead") inline void parse(csubstr filename, substr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_place(filename, yaml, t, node_id); }
+RYML_DEPRECATED("use parse_in_place() instead") inline void parse( substr yaml, NodeRef node ) { Parser np; np.parse_in_place({} , yaml, node); }
+RYML_DEPRECATED("use parse_in_place() instead") inline void parse(csubstr filename, substr yaml, NodeRef node ) { Parser np; np.parse_in_place(filename, yaml, node); }
+
+/** @} */
+
+
+//-----------------------------------------------------------------------------
+
+/** @name parse_in_arena
+ * @desc parse a read-only YAML source buffer, copying it first to the tree's arena.
+ *
+ * @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 object after it has parsed the
+ * code. If you need access to any of these properties, use
+ * Parser::parse_in_arena().
+ *
+ * @note 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
+ * is implicitly convertible to csubstr. 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. */
+/** @{ */
+
+/* READ THE NOTE ABOVE! */
+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_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, Tree *t, size_t node_id);
+RYML_DEPRECATED(RYML_DONT_PARSE_SUBSTR_IN_ARENA) void parse_in_arena(csubstr filename, substr yaml, Tree *t, size_t node_id);
+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 );
+
+inline Tree parse_in_arena( csubstr yaml ) { Parser np; return np.parse_in_arena({} , yaml); } //!< parse a read-only YAML source buffer, copying it first to the tree's source arena.
+inline Tree parse_in_arena(csubstr filename, csubstr yaml ) { Parser np; return np.parse_in_arena(filename, yaml); } //!< parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages.
+inline void parse_in_arena( csubstr yaml, Tree *t ) { Parser np; np.parse_in_arena({} , yaml, t); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena.
+inline void parse_in_arena(csubstr filename, csubstr yaml, Tree *t ) { Parser np; np.parse_in_arena(filename, yaml, t); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages.
+inline void parse_in_arena( csubstr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_arena({} , yaml, t, node_id); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena.
+inline void parse_in_arena(csubstr filename, csubstr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_arena(filename, yaml, t, node_id); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages.
+inline void parse_in_arena( csubstr yaml, NodeRef node ) { Parser np; np.parse_in_arena({} , yaml, node); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena.
+inline void parse_in_arena(csubstr filename, csubstr yaml, NodeRef node ) { Parser np; np.parse_in_arena(filename, yaml, node); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages.
+
+RYML_DEPRECATED("use parse_in_arena() instead") inline Tree parse( csubstr yaml ) { Parser np; return np.parse_in_arena({} , yaml); } //!< parse a read-only YAML source buffer, copying it first to the tree's source arena.
+RYML_DEPRECATED("use parse_in_arena() instead") inline Tree parse(csubstr filename, csubstr yaml ) { Parser np; return np.parse_in_arena(filename, yaml); } //!< parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages.
+RYML_DEPRECATED("use parse_in_arena() instead") inline void parse( csubstr yaml, Tree *t ) { Parser np; np.parse_in_arena({} , yaml, t); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena.
+RYML_DEPRECATED("use parse_in_arena() instead") inline void parse(csubstr filename, csubstr yaml, Tree *t ) { Parser np; np.parse_in_arena(filename, yaml, t); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages.
+RYML_DEPRECATED("use parse_in_arena() instead") inline void parse( csubstr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_arena({} , yaml, t, node_id); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena.
+RYML_DEPRECATED("use parse_in_arena() instead") inline void parse(csubstr filename, csubstr yaml, Tree *t, size_t node_id) { Parser np; np.parse_in_arena(filename, yaml, t, node_id); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages.
+RYML_DEPRECATED("use parse_in_arena() instead") inline void parse( csubstr yaml, NodeRef node ) { Parser np; np.parse_in_arena({} , yaml, node); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena.
+RYML_DEPRECATED("use parse_in_arena() instead") inline void parse(csubstr filename, csubstr yaml, NodeRef node ) { Parser np; np.parse_in_arena(filename, yaml, node); } //!< reusing the YAML tree, parse a read-only YAML source buffer, copying it first to the tree's source arena, providing a filename for error messages.
+
+/** @} */
+
+} // namespace yml
+} // namespace c4
+
+#if defined(_MSC_VER)
+# pragma warning(pop)
+#endif
+
+#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 <map>
+
+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<class K, class V, class Less, class Alloc>
+void write(c4::yml::NodeRef *n, std::map<K, V, Less, Alloc> const& m)
+{
+ *n |= c4::yml::MAP;
+ for(auto const& p : m)
+ {
+ auto ch = n->append_child();
+ ch << c4::yml::key(p.first);
+ ch << p.second;
+ }
+}
+
+template<class K, class V, class Less, class Alloc>
+bool read(c4::yml::NodeRef const& n, std::map<K, V, Less, Alloc> * m)
+{
+ K k{};
+ V v;
+ for(auto const ch : n)
+ {
+ ch >> c4::yml::key(k);
+ ch >> v;
+ m->emplace(std::make_pair(std::move(k), std::move(v)));
+ }
+ 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 <c4/std/string.hpp>
+#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 <c4/std/vector.hpp>
+#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 <vector>
+
+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<class V, class Alloc>
+void write(c4::yml::NodeRef *n, std::vector<V, Alloc> const& vec)
+{
+ *n |= c4::yml::SEQ;
+ for(auto const& v : vec)
+ {
+ n->append_child() << v;
+ }
+}
+
+template<class V, class Alloc>
+bool read(c4::yml::NodeRef const& n, std::vector<V, Alloc> *vec)
+{
+ vec->resize(n.num_children());
+ size_t pos = 0;
+ for(auto const ch : n)
+ {
+ ch >> (*vec)[pos++];
+ }
+ 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/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 <stdlib.h>
+//included above:
+//# include <stdio.h>
+#endif // RYML_NO_DEFAULT_CALLBACKS
+
+namespace c4 {
+namespace yml {
+
+namespace {
+thread_local 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())
+ {
+ fwrite(loc.name.str, 1, loc.name.len, f);
+ fputc(':', f);
+ }
+ fprintf(f, "%zu:", loc.line);
+ if(loc.col)
+ fprintf(f, "%zu:", loc.col);
+ if(loc.offset)
+ fprintf(f, " (%zuB):", loc.offset);
+ }
+ fprintf(f, "%.*s\n", (int)length, msg);
+ fflush(f);
+}
+
+void error_impl(const char* msg, size_t length, Location loc, void * /*user_data*/)
+{
+ report_error_impl(msg, length, loc, nullptr);
+ ::abort();
+}
+
+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()
+ :
+ 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
+{
+ C4_CHECK(m_allocate);
+ C4_CHECK(m_free);
+ C4_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());
+}
+
+void error(const char *msg, size_t msg_len, Location loc)
+{
+ s_default_callbacks.m_error(msg, msg_len, loc, s_default_callbacks.m_user_data);
+}
+
+} // namespace yml
+} // namespace c4
+
+#endif /* RYML_SINGLE_HDR_DEFINE_NOW */
+
+
+// (end https://github.com/biojppm/rapidyaml/src/c4/yml/common.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/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_ */
+
+
+
+C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wtype-limits")
+C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4296/*expression is always 'boolean_value'*/)
+
+namespace c4 {
+namespace yml {
+
+
+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("<!"))
+ return tag;
+ return tag;
+}
+
+csubstr normalize_tag_long(csubstr tag)
+{
+ YamlTag_e t = to_tag(tag);
+ if(t != TAG_NONE)
+ return from_tag_long(t);
+ if(tag.begins_with("!<"))
+ tag = tag.sub(1);
+ if(tag.begins_with("<!"))
+ return tag;
+ return tag;
+}
+
+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("<tag:yaml.org,2002:"))
+ {
+ RYML_ASSERT(csubstr("<tag:yaml.org,2002:").len == 19);
+ tag = tag.sub(19);
+ if(!tag.len)
+ return TAG_NONE;
+ tag = tag.offs(0, 1);
+ }
+
+ if(tag == "map")
+ return TAG_MAP;
+ else if(tag == "omap")
+ return TAG_OMAP;
+ else if(tag == "pairs")
+ return TAG_PAIRS;
+ else if(tag == "set")
+ return TAG_SET;
+ else if(tag == "seq")
+ return TAG_SEQ;
+ else if(tag == "binary")
+ return TAG_BINARY;
+ else if(tag == "bool")
+ return TAG_BOOL;
+ else if(tag == "float")
+ return TAG_FLOAT;
+ else if(tag == "int")
+ return TAG_INT;
+ else if(tag == "merge")
+ return TAG_MERGE;
+ else if(tag == "null")
+ return TAG_NULL;
+ else if(tag == "str")
+ return TAG_STR;
+ else if(tag == "timestamp")
+ return TAG_TIMESTAMP;
+ else if(tag == "value")
+ return TAG_VALUE;
+
+ return TAG_NONE;
+}
+
+csubstr from_tag_long(YamlTag_e tag)
+{
+ switch(tag)
+ {
+ case TAG_MAP:
+ return {"<tag:yaml.org,2002:map>"};
+ case TAG_OMAP:
+ return {"<tag:yaml.org,2002:omap>"};
+ case TAG_PAIRS:
+ return {"<tag:yaml.org,2002:pairs>"};
+ case TAG_SET:
+ return {"<tag:yaml.org,2002:set>"};
+ case TAG_SEQ:
+ return {"<tag:yaml.org,2002:seq>"};
+ case TAG_BINARY:
+ return {"<tag:yaml.org,2002:binary>"};
+ case TAG_BOOL:
+ return {"<tag:yaml.org,2002:bool>"};
+ case TAG_FLOAT:
+ return {"<tag:yaml.org,2002:float>"};
+ case TAG_INT:
+ return {"<tag:yaml.org,2002:int>"};
+ case TAG_MERGE:
+ return {"<tag:yaml.org,2002:merge>"};
+ case TAG_NULL:
+ return {"<tag:yaml.org,2002:null>"};
+ case TAG_STR:
+ return {"<tag:yaml.org,2002:str>"};
+ case TAG_TIMESTAMP:
+ return {"<tag:yaml.org,2002:timestamp>"};
+ case TAG_VALUE:
+ return {"<tag:yaml.org,2002:value>"};
+ case TAG_YAML:
+ return {"<tag:yaml.org,2002:yaml>"};
+ case TAG_NONE:
+ return {""};
+ }
+ 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:
+ return {""};
+ }
+ return {""};
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+const char* NodeType::type_str(NodeType_e ty)
+{
+ 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)";
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+NodeRef Tree::rootref()
+{
+ return NodeRef(this, root_id());
+}
+NodeRef const Tree::rootref() const
+{
+ return NodeRef(const_cast<Tree*>(this), root_id());
+}
+
+NodeRef Tree::ref(size_t id)
+{
+ _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size);
+ return NodeRef(this, id);
+}
+NodeRef const Tree::ref(size_t id) const
+{
+ _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size);
+ return NodeRef(const_cast<Tree*>(this), id);
+}
+
+NodeRef Tree::operator[] (csubstr key)
+{
+ return rootref()[key];
+}
+NodeRef const Tree::operator[] (csubstr key) const
+{
+ return rootref()[key];
+}
+
+NodeRef Tree::operator[] (size_t i)
+{
+ return rootref()[i];
+}
+NodeRef const Tree::operator[] (size_t i) const
+{
+ return rootref()[i];
+}
+
+NodeRef Tree::docref(size_t i)
+{
+ return ref(doc(i));
+}
+NodeRef const Tree::docref(size_t i) const
+{
+ return ref(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)
+{
+}
+
+Tree::Tree(size_t 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) noexcept : Tree(that.m_callbacks)
+{
+ _copy(that);
+}
+
+Tree& Tree::operator= (Tree const& that) noexcept
+{
+ _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
+{
+ _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, 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(size_t 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);
+ m_buf = _RYML_CB_ALLOC_HINT(m_callbacks, NodeData, that.m_cap, that.m_buf);
+ memcpy(m_buf, that.m_buf, 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(size_t i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i)
+ m_tag_directives[i] = that.m_tag_directives[i];
+}
+
+void Tree::_move(Tree & 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);
+ 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(size_t 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);
+ 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(size_t cap)
+{
+ if(cap > m_cap)
+ {
+ NodeData *buf = _RYML_CB_ALLOC_HINT(m_callbacks, NodeData, cap, m_buf);
+ if(m_buf)
+ {
+ memcpy(buf, m_buf, m_cap * sizeof(NodeData));
+ _RYML_CB_FREE(m_callbacks, m_buf, NodeData, m_cap);
+ }
+ size_t 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(size_t i = 0; i < RYML_MAX_TAG_DIRECTIVES; ++i)
+ m_tag_directives[i] = {};
+}
+
+void Tree::_claim_root()
+{
+ size_t r = _claim();
+ _RYML_CB_ASSERT(m_callbacks, r == 0);
+ _set_hierarchy(r, NONE, NONE);
+}
+
+
+//-----------------------------------------------------------------------------
+void Tree::_clear_range(size_t first, size_t 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, num * sizeof(NodeData)); // TODO we should not need this
+ for(size_t 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(size_t 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(size_t 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(size_t i)
+{
+ if(m_free_head == i)
+ m_free_head = _p(i)->m_next_sibling;
+ _rem_hierarchy(i);
+}
+
+//-----------------------------------------------------------------------------
+size_t Tree::_claim()
+{
+ if(m_free_head == NONE || m_buf == nullptr)
+ {
+ size_t 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);
+
+ size_t 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__) && (__GNUC__ >= 6)
+C4_SUPPRESS_WARNING_GCC("-Wnull-dereference")
+#endif
+
+void Tree::_set_hierarchy(size_t ichild, size_t iparent, size_t iprev_sibling)
+{
+ _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 = get(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;
+
+ size_t 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(size_t 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;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void Tree::reorder()
+{
+ size_t r = root_id();
+ _do_reorder(&r, 0);
+}
+
+//-----------------------------------------------------------------------------
+size_t Tree::_do_reorder(size_t *node, size_t 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(size_t 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;
+}
+
+//-----------------------------------------------------------------------------
+void Tree::_swap(size_t n_, size_t 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(size_t ia, size_t ib)
+{
+ if(ia == ib) return;
+
+ for(size_t i = first_child(ia); i != NONE; i = next_sibling(i))
+ {
+ if(i == ib || i == ia)
+ continue;
+ _p(i)->m_parent = ib;
+ }
+
+ for(size_t 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;
+ }
+ size_t 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;
+ }
+ size_t 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(size_t dst_, size_t src_)
+{
+ auto const& C4_RESTRICT src = *_p(src_);
+ auto & C4_RESTRICT dst = *_p(dst_);
+ auto & C4_RESTRICT prt = *_p(src.m_parent);
+ for(size_t 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(size_t n_, size_t 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);
+}
+
+//-----------------------------------------------------------------------------
+void Tree::move(size_t node, size_t after)
+{
+ _RYML_CB_ASSERT(m_callbacks, node != NONE);
+ _RYML_CB_ASSERT(m_callbacks, ! is_root(node));
+ _RYML_CB_ASSERT(m_callbacks, has_sibling(node, after) && has_sibling(after, node));
+
+ _rem_hierarchy(node);
+ _set_hierarchy(node, parent(node), after);
+}
+
+//-----------------------------------------------------------------------------
+
+void Tree::move(size_t node, size_t new_parent, size_t after)
+{
+ _RYML_CB_ASSERT(m_callbacks, node != NONE);
+ _RYML_CB_ASSERT(m_callbacks, new_parent != NONE);
+ _RYML_CB_ASSERT(m_callbacks, ! is_root(node));
+
+ _rem_hierarchy(node);
+ _set_hierarchy(node, new_parent, after);
+}
+
+size_t Tree::move(Tree *src, size_t node, size_t new_parent, size_t after)
+{
+ _RYML_CB_ASSERT(m_callbacks, node != NONE);
+ _RYML_CB_ASSERT(m_callbacks, new_parent != NONE);
+
+ size_t dup = duplicate(src, node, new_parent, after);
+ src->remove(node);
+ return dup;
+}
+
+void Tree::set_root_as_stream()
+{
+ size_t 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);
+ size_t 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));
+ size_t next_doc = append_child(root);
+ _copy_props_wo_key(next_doc, root);
+ _add_flags(next_doc, DOC);
+ for(size_t 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(size_t node)
+{
+ _RYML_CB_ASSERT(m_callbacks, get(node) != nullptr);
+ size_t ich = get(node)->m_first_child;
+ while(ich != NONE)
+ {
+ remove_children(ich);
+ _RYML_CB_ASSERT(m_callbacks, get(ich) != nullptr);
+ size_t next = get(ich)->m_next_sibling;
+ _release(ich);
+ if(ich == get(node)->m_last_child)
+ break;
+ ich = next;
+ }
+}
+
+bool Tree::change_type(size_t 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;
+}
+
+
+//-----------------------------------------------------------------------------
+size_t Tree::duplicate(size_t node, size_t parent, size_t after)
+{
+ return duplicate(this, node, parent, after);
+}
+
+size_t Tree::duplicate(Tree const* src, size_t node, size_t parent, size_t 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));
+
+ size_t copy = _claim();
+
+ _copy_props(copy, src, node);
+ _set_hierarchy(copy, parent, after);
+ duplicate_children(src, node, copy, NONE);
+
+ return copy;
+}
+
+//-----------------------------------------------------------------------------
+size_t Tree::duplicate_children(size_t node, size_t parent, size_t after)
+{
+ return duplicate_children(this, node, parent, after);
+}
+
+size_t Tree::duplicate_children(Tree const* src, size_t node, size_t parent, size_t 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));
+
+ size_t prev = after;
+ for(size_t i = src->first_child(node); i != NONE; i = src->next_sibling(i))
+ {
+ prev = duplicate(src, i, parent, prev);
+ }
+
+ return prev;
+}
+
+//-----------------------------------------------------------------------------
+void Tree::duplicate_contents(size_t node, size_t where)
+{
+ duplicate_contents(this, node, where);
+}
+
+void Tree::duplicate_contents(Tree const *src, size_t node, size_t 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));
+}
+
+//-----------------------------------------------------------------------------
+size_t Tree::duplicate_children_no_rep(size_t node, size_t parent, size_t after)
+{
+ return duplicate_children_no_rep(this, node, parent, after);
+}
+
+size_t Tree::duplicate_children_no_rep(Tree const *src, size_t node, size_t parent, size_t 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
+ size_t after_pos = NONE;
+ if(after != NONE)
+ {
+ for(size_t 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...
+ size_t prev = after;
+ for(size_t i = src->first_child(node), icount = 0; i != NONE; ++icount, i = src->next_sibling(i))
+ {
+ if(is_seq(parent))
+ {
+ prev = duplicate(i, parent, prev);
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_callbacks, is_map(parent));
+ // does the parent already have a node with key equal to that of the current duplicate?
+ size_t rep = NONE, rep_pos = NONE;
+ for(size_t j = first_child(parent), jcount = 0; j != NONE; ++jcount, j = next_sibling(j))
+ {
+ if(key(j) == key(i))
+ {
+ rep = j;
+ rep_pos = jcount;
+ break;
+ }
+ }
+ if(rep == NONE) // there is no repetition; just duplicate
+ {
+ prev = duplicate(src, i, parent, prev);
+ }
+ else // yes, there is a repetition
+ {
+ if(after_pos != NONE && rep_pos < after_pos)
+ {
+ // rep is located before the node which will be inserted,
+ // and will be overridden by the duplicate. So replace it.
+ remove(rep);
+ prev = duplicate(src, i, parent, prev);
+ }
+ else if(after_pos == NONE || rep_pos >= after_pos)
+ {
+ // rep is located after the node which will be inserted
+ // and overrides it. So move the rep into this node's place.
+ if(rep != prev)
+ {
+ move(rep, prev);
+ prev = rep;
+ }
+ }
+ } // there's a repetition
+ }
+ }
+
+ return prev;
+}
+
+
+//-----------------------------------------------------------------------------
+
+void Tree::merge_with(Tree const *src, size_t src_node, size_t 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))
+ {
+ if( ! has_val(dst_node))
+ {
+ if(has_children(dst_node))
+ remove_children(dst_node);
+ }
+ if(src->is_keyval(src_node))
+ _copy_props(dst_node, src, src_node);
+ else if(src->is_val(src_node))
+ _copy_props_wo_key(dst_node, src, src_node);
+ else
+ C4_NEVER_REACH();
+ }
+ 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);
+ }
+ for(size_t sch = src->first_child(src_node); sch != NONE; sch = src->next_sibling(sch))
+ {
+ size_t dch = append_child(dst_node);
+ _copy_props_wo_key(dch, src, sch);
+ merge_with(src, sch, dch);
+ }
+ }
+ else if(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);
+ }
+ for(size_t sch = src->first_child(src_node); sch != NONE; sch = src->next_sibling(sch))
+ {
+ size_t 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);
+ }
+ }
+ else
+ {
+ C4_NEVER_REACH();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+
+namespace detail {
+/** @todo make this part of the public API, refactoring as appropriate
+ * to be able to use the same resolver to handle multiple trees (one
+ * at a time) */
+struct ReferenceResolver
+{
+ struct refdata
+ {
+ NodeType type;
+ size_t node;
+ size_t prev_anchor;
+ size_t target;
+ size_t parent_ref;
+ size_t parent_ref_sibling;
+ };
+
+ Tree *t;
+ /** 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 */
+ stack<refdata> refs;
+
+ ReferenceResolver(Tree *t_) : t(t_), refs(t_->callbacks())
+ {
+ resolve();
+ }
+
+ void store_anchors_and_refs()
+ {
+ // minimize (re-)allocations by counting first
+ size_t num_anchors_and_refs = count_anchors_and_refs(t->root_id());
+ if(!num_anchors_and_refs)
+ return;
+ refs.reserve(num_anchors_and_refs);
+
+ // now descend through the hierarchy
+ _store_anchors_and_refs(t->root_id());
+
+ // finally connect the reference list
+ size_t prev_anchor = npos;
+ size_t count = 0;
+ for(auto &rd : refs)
+ {
+ rd.prev_anchor = prev_anchor;
+ if(rd.type.is_anchor())
+ prev_anchor = count;
+ ++count;
+ }
+ }
+
+ size_t count_anchors_and_refs(size_t n)
+ {
+ size_t c = 0;
+ c += t->has_key_anchor(n);
+ c += t->has_val_anchor(n);
+ c += t->is_key_ref(n);
+ c += t->is_val_ref(n);
+ for(size_t ch = t->first_child(n); ch != NONE; ch = t->next_sibling(ch))
+ c += count_anchors_and_refs(ch);
+ return c;
+ }
+
+ void _store_anchors_and_refs(size_t n)
+ {
+ if(t->is_key_ref(n) || t->is_val_ref(n) || (t->has_key(n) && t->key(n) == "<<"))
+ {
+ if(t->is_seq(n))
+ {
+ // for merging multiple inheritance targets
+ // <<: [ *CENTER, *BIG ]
+ for(size_t ich = t->first_child(n); ich != NONE; ich = t->next_sibling(ich))
+ {
+ RYML_ASSERT(t->num_children(ich) == 0);
+ refs.push({VALREF, ich, npos, npos, n, t->next_sibling(n)});
+ }
+ return;
+ }
+ if(t->is_key_ref(n) && t->key(n) != "<<") // insert key refs BEFORE inserting val refs
+ {
+ RYML_CHECK((!t->has_key(n)) || t->key(n).ends_with(t->key_ref(n)));
+ refs.push({KEYREF, n, npos, npos, NONE, NONE});
+ }
+ if(t->is_val_ref(n))
+ {
+ RYML_CHECK((!t->has_val(n)) || t->val(n).ends_with(t->val_ref(n)));
+ refs.push({VALREF, n, npos, npos, NONE, NONE});
+ }
+ }
+ if(t->has_key_anchor(n))
+ {
+ RYML_CHECK(t->has_key(n));
+ refs.push({KEYANCH, n, npos, npos, NONE, NONE});
+ }
+ if(t->has_val_anchor(n))
+ {
+ RYML_CHECK(t->has_val(n) || t->is_container(n));
+ refs.push({VALANCH, n, npos, npos, NONE, NONE});
+ }
+ for(size_t ch = t->first_child(n); ch != NONE; ch = t->next_sibling(ch))
+ {
+ _store_anchors_and_refs(ch);
+ }
+ }
+
+ size_t lookup_(refdata *C4_RESTRICT ra)
+ {
+ 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;
+ if(ra->type.is_val_ref())
+ {
+ refname = t->val_ref(ra->node);
+ }
+ else
+ {
+ RYML_ASSERT(ra->type.is_key_ref());
+ refname = t->key_ref(ra->node);
+ }
+ while(ra->prev_anchor != npos)
+ {
+ ra = &refs[ra->prev_anchor];
+ if(t->has_anchor(ra->node, refname))
+ return ra->node;
+ }
+
+ #ifndef RYML_ERRMSG_SIZE
+ #define RYML_ERRMSG_SIZE 1024
+ #endif
+
+ char errmsg[RYML_ERRMSG_SIZE];
+ snprintf(errmsg, RYML_ERRMSG_SIZE, "anchor does not exist: '%.*s'",
+ static_cast<int>(refname.size()), refname.data());
+ c4::yml::error(errmsg);
+ return NONE;
+ }
+
+ void resolve()
+ {
+ store_anchors_and_refs();
+ if(refs.empty())
+ return;
+
+ /* 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 */
+ for(size_t i = 0, e = refs.size(); i < e; ++i)
+ {
+ auto &C4_RESTRICT rd = refs.top(i);
+ if( ! rd.type.is_ref())
+ continue;
+ rd.target = lookup_(&rd);
+ }
+ }
+
+}; // ReferenceResolver
+} // namespace detail
+
+void Tree::resolve()
+{
+ if(m_size == 0)
+ return;
+
+ detail::ReferenceResolver rr(this);
+
+ // insert the resolved references
+ size_t prev_parent_ref = NONE;
+ size_t prev_parent_ref_after = NONE;
+ for(auto const& C4_RESTRICT rd : rr.refs)
+ {
+ if( ! rd.type.is_ref())
+ continue;
+ if(rd.parent_ref != NONE)
+ {
+ _RYML_CB_ASSERT(m_callbacks, is_seq(rd.parent_ref));
+ size_t after, p = parent(rd.parent_ref);
+ if(prev_parent_ref != rd.parent_ref)
+ {
+ after = rd.parent_ref;//prev_sibling(rd.parent_ref_sibling);
+ prev_parent_ref_after = after;
+ }
+ else
+ {
+ after = prev_parent_ref_after;
+ }
+ prev_parent_ref = rd.parent_ref;
+ prev_parent_ref_after = duplicate_children_no_rep(rd.target, p, after);
+ remove(rd.node);
+ }
+ else
+ {
+ if(has_key(rd.node) && is_key_ref(rd.node) && key(rd.node) == "<<")
+ {
+ _RYML_CB_ASSERT(m_callbacks, is_keyval(rd.node));
+ size_t p = parent(rd.node);
+ size_t after = prev_sibling(rd.node);
+ duplicate_children_no_rep(rd.target, p, after);
+ remove(rd.node);
+ }
+ else if(rd.type.is_key_ref())
+ {
+ _RYML_CB_ASSERT(m_callbacks, is_key_ref(rd.node));
+ _RYML_CB_ASSERT(m_callbacks, has_key_anchor(rd.target) || has_val_anchor(rd.target));
+ if(has_val_anchor(rd.target) && val_anchor(rd.target) == key_ref(rd.node))
+ {
+ _RYML_CB_CHECK(m_callbacks, !is_container(rd.target));
+ _RYML_CB_CHECK(m_callbacks, has_val(rd.target));
+ _p(rd.node)->m_key.scalar = val(rd.target);
+ _add_flags(rd.node, KEY);
+ }
+ else
+ {
+ _RYML_CB_CHECK(m_callbacks, key_anchor(rd.target) == key_ref(rd.node));
+ _p(rd.node)->m_key.scalar = key(rd.target);
+ _add_flags(rd.node, VAL);
+ }
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_callbacks, rd.type.is_val_ref());
+ if(has_key_anchor(rd.target) && key_anchor(rd.target) == val_ref(rd.node))
+ {
+ _RYML_CB_CHECK(m_callbacks, !is_container(rd.target));
+ _RYML_CB_CHECK(m_callbacks, has_val(rd.target));
+ _p(rd.node)->m_val.scalar = key(rd.target);
+ _add_flags(rd.node, VAL);
+ }
+ else
+ {
+ duplicate_contents(rd.target, rd.node);
+ }
+ }
+ }
+ }
+
+ // clear anchors and refs
+ for(auto const& C4_RESTRICT ar : rr.refs)
+ {
+ rem_anchor_ref(ar.node);
+ if(ar.parent_ref != NONE)
+ if(type(ar.parent_ref) != NOTYPE)
+ remove(ar.parent_ref);
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+
+size_t Tree::num_children(size_t node) const
+{
+ size_t count = 0;
+ for(size_t i = first_child(node); i != NONE; i = next_sibling(i))
+ {
+ ++count;
+ }
+ return count;
+}
+
+size_t Tree::child(size_t node, size_t pos) const
+{
+ _RYML_CB_ASSERT(m_callbacks, node != NONE);
+ size_t count = 0;
+ for(size_t i = first_child(node); i != NONE; i = next_sibling(i))
+ {
+ if(count++ == pos)
+ return i;
+ }
+ return NONE;
+}
+
+size_t Tree::child_pos(size_t node, size_t ch) const
+{
+ size_t count = 0;
+ for(size_t i = first_child(node); i != NONE; i = next_sibling(i))
+ {
+ if(i == ch)
+ return count;
+ ++count;
+ }
+ return npos;
+}
+
+#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
+#endif
+
+size_t Tree::find_child(size_t 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(size_t 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
+
+
+//-----------------------------------------------------------------------------
+
+void Tree::to_val(size_t 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(size_t 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(size_t 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(size_t 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(size_t 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(size_t 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(size_t 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(size_t 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();
+}
+
+
+//-----------------------------------------------------------------------------
+size_t Tree::num_tag_directives() const
+{
+ // this assumes we have a very small number of tag directives
+ for(size_t 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 = {};
+}
+
+size_t 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_ASSERT(m_callbacks, td.handle.begins_with('!'));
+ _RYML_CB_ASSERT(m_callbacks, td.handle.ends_with('!'));
+ // https://yaml.org/spec/1.2.2/#rule-ns-word-char
+ _RYML_CB_ASSERT(m_callbacks, td.handle == '!' || td.handle == "!!" || td.handle.trim('!').first_not_of("01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-") == npos);
+ size_t pos = num_tag_directives();
+ _RYML_CB_CHECK(m_callbacks, pos < RYML_MAX_TAG_DIRECTIVES);
+ m_tag_directives[pos] = td;
+ return pos;
+}
+
+size_t Tree::resolve_tag(substr output, csubstr tag, size_t 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(size_t i = RYML_MAX_TAG_DIRECTIVES-1; i != (size_t)-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)
+ {
+ _RYML_CB_ASSERT(m_callbacks, tag.len >= td.handle.len);
+ csubstr rest = tag.sub(td.handle.len);
+ size_t len = 1u + td.prefix.len + rest.len + 1u;
+ size_t numpc = rest.count('%');
+ if(numpc == 0)
+ {
+ if(len <= output.len)
+ {
+ output.str[0] = '<';
+ memcpy(1u + output.str, td.prefix.str, td.prefix.len);
+ memcpy(1u + output.str + td.prefix.len, rest.str, rest.len);
+ output.str[1u + td.prefix.len + rest.len] = '>';
+ }
+ }
+ else
+ {
+ // need to decode URI % sequences
+ size_t pos = rest.find('%');
+ _RYML_CB_ASSERT(m_callbacks, pos != npos);
+ do {
+ size_t next = rest.first_not_of("0123456789abcdefABCDEF", pos+1);
+ if(next == npos)
+ next = rest.len;
+ _RYML_CB_CHECK(m_callbacks, pos+1 < next);
+ _RYML_CB_CHECK(m_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(td.prefix);
+ pos = rest.find('%');
+ _RYML_CB_ASSERT(m_callbacks, pos != npos);
+ do {
+ size_t next = rest.first_not_of("0123456789abcdefABCDEF", pos+1);
+ if(next == npos)
+ next = rest.len;
+ _RYML_CB_CHECK(m_callbacks, pos+1 < next);
+ _RYML_CB_CHECK(m_callbacks, pos+1 + 2 <= next);
+ uint8_t val;
+ if(C4_UNLIKELY(!read_hex(rest.range(pos+1, next), &val) || val > 127))
+ _RYML_CB_ERR(m_callbacks, "invalid URI character");
+ appendstr(rest.range(prev, pos));
+ appendchar((char)val);
+ prev = next;
+ pos = rest.find('%', pos+1);
+ } while(pos != npos);
+ _RYML_CB_ASSERT(m_callbacks, pos == npos);
+ _RYML_CB_ASSERT(m_callbacks, prev > 0);
+ _RYML_CB_ASSERT(m_callbacks, rest.len >= prev);
+ appendstr(rest.sub(prev));
+ appendchar('>');
+ _RYML_CB_ASSERT(m_callbacks, wpos == len);
+ }
+ }
+ return len;
+ }
+ }
+ return 0; // return 0 to signal that the tag is local and cannot be resolved
+}
+
+namespace {
+csubstr _transform_tag(Tree *t, csubstr tag, size_t node)
+{
+ size_t required_size = t->resolve_tag(substr{}, tag, node);
+ if(!required_size)
+ return tag;
+ const char *prev_arena = t->arena().str;
+ 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);
+ return buf.first(actual_size);
+}
+void _resolve_tags(Tree *t, size_t node)
+{
+ for(size_t child = t->first_child(node); child != NONE; child = t->next_sibling(child))
+ {
+ if(t->has_key(child) && t->has_key_tag(child))
+ t->set_key_tag(child, _transform_tag(t, t->key_tag(child), child));
+ if(t->has_val(child) && t->has_val_tag(child))
+ t->set_val_tag(child, _transform_tag(t, t->val_tag(child), child));
+ _resolve_tags(t, child);
+ }
+}
+size_t _count_resolved_tags_size(Tree const* t, size_t node)
+{
+ size_t sz = 0;
+ for(size_t child = t->first_child(node); child != NONE; child = t->next_sibling(child))
+ {
+ if(t->has_key(child) && t->has_key_tag(child))
+ sz += t->resolve_tag(substr{}, t->key_tag(child), child);
+ if(t->has_val(child) && t->has_val_tag(child))
+ sz += t->resolve_tag(substr{}, t->val_tag(child), child);
+ sz += _count_resolved_tags_size(t, child);
+ }
+ return sz;
+}
+} // namespace
+
+void Tree::resolve_tags()
+{
+ if(empty())
+ return;
+ if(num_tag_directives() == 0)
+ return;
+ size_t needed_size = _count_resolved_tags_size(this, root_id());
+ if(needed_size)
+ reserve_arena(arena_pos() + needed_size);
+ _resolve_tags(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) const
+{
+ r->path_pos += more;
+ if(r->path.sub(r->path_pos).begins_with('.'))
+ ++r->path_pos;
+}
+
+Tree::lookup_result Tree::lookup_path(csubstr path, size_t 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;
+}
+
+size_t Tree::lookup_path_or_modify(csubstr default_value, csubstr path, size_t start)
+{
+ size_t 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;
+}
+
+size_t Tree::lookup_path_or_modify(Tree const *src, size_t src_node, csubstr path, size_t start)
+{
+ size_t target = _lookup_path_or_create(path, start);
+ merge_with(src, src_node, target);
+ return target;
+}
+
+size_t Tree::_lookup_path_or_create(csubstr path, size_t 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)};
+ size_t 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)};
+ size_t 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);
+}
+
+size_t Tree::_next_node(lookup_result * r, _lookup_path_token *parent) const
+{
+ _lookup_path_token token = _next_token(r, *parent);
+ if( ! token)
+ return NONE;
+
+ size_t 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(' ');
+ size_t 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;
+}
+
+size_t Tree::_next_node_modify(lookup_result * r, _lookup_path_token *parent)
+{
+ _lookup_path_token token = _next_token(r, *parent);
+ if( ! token)
+ return NONE;
+
+ size_t 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
+ {
+ size_t 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(' ');
+ size_t 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(size_t 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 ryml
+} // namespace c4
+
+
+C4_SUPPRESS_WARNING_GCC_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.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_ */
+
+// 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/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/rapidyaml/src/c4/dump.hpp
+//#include <c4/dump.hpp>
+#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_ */
+
+
+//included above:
+//#include <ctype.h>
+//included above:
+//#include <stdarg.h>
+//included above:
+//#include <stdio.h>
+
+// 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_ */
+
+#endif
+
+#ifndef RYML_ERRMSG_SIZE
+ #define RYML_ERRMSG_SIZE 1024
+#endif
+
+//#define RYML_WITH_TAB_TOKENS
+#ifdef RYML_WITH_TAB_TOKENS
+#define _RYML_WITH_TAB_TOKENS(...) __VA_ARGS__
+#define _RYML_WITH_OR_WITHOUT_TAB_TOKENS(with, without) with
+#else
+#define _RYML_WITH_TAB_TOKENS(...)
+#define _RYML_WITH_OR_WITHOUT_TAB_TOKENS(with, without) without
+#endif
+
+
+#if defined(_MSC_VER)
+# pragma warning(push)
+# pragma warning(disable: 4296/*expression is always 'boolean_value'*/)
+#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"
+#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"
+# if __GNUC__ >= 7
+# pragma GCC diagnostic ignored "-Wduplicated-branches"
+# endif
+#endif
+
+namespace c4 {
+namespace yml {
+
+namespace {
+
+template<class DumpFn, class ...Args>
+void _parse_dump(DumpFn dumpfn, c4::csubstr fmt, Args&& ...args)
+{
+ char writebuf[256];
+ auto results = c4::format_dump_resume(dumpfn, writebuf, fmt, std::forward<Args>(args)...);
+ // resume writing if the results failed to fit the buffer
+ if(C4_UNLIKELY(results.bufsize > sizeof(writebuf))) // bufsize will be that of the largest element serialized. Eg int(1), will require 1 byte.
+ {
+ results = format_dump_resume(dumpfn, results, writebuf, fmt, std::forward<Args>(args)...);
+ if(C4_UNLIKELY(results.bufsize > sizeof(writebuf)))
+ {
+ results = format_dump_resume(dumpfn, results, writebuf, fmt, std::forward<Args>(args)...);
+ }
+ }
+}
+
+bool _is_scalar_next__runk(csubstr s)
+{
+ return !(s.begins_with(": ") || s.begins_with_any("#,:{}[]%&") || s.begins_with("? ") || s == "-" || s.begins_with("- "));
+}
+
+bool _is_scalar_next__rseq_rval(csubstr s)
+{
+ return !(s.begins_with_any("[{!&") || s.begins_with("? ") || s.begins_with("- ") || s == "-");
+}
+
+bool _is_scalar_next__rmap(csubstr s)
+{
+ return !(s.begins_with(": ") || s.begins_with_any("#,!&") || s.begins_with("? ") _RYML_WITH_TAB_TOKENS(|| s.begins_with(":\t")));
+}
+
+bool _is_scalar_next__rmap_val(csubstr s)
+{
+ return !(s.begins_with("- ") || s.begins_with_any("{[") || s == "-");
+}
+
+bool _is_doc_sep(csubstr s)
+{
+ constexpr const csubstr dashes = "---";
+ constexpr const csubstr ellipsis = "...";
+ constexpr const csubstr whitesp = " \t";
+ if(s.begins_with(dashes))
+ return s == dashes || s.sub(3).begins_with_any(whitesp);
+ else if(s.begins_with(ellipsis))
+ return s == ellipsis || s.sub(3).begins_with_any(whitesp);
+ return false;
+}
+
+/** @p i is set to the first non whitespace character after the line
+ * @return the number of empty lines after the initial position */
+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);
+ for( ; *i < r.len; ++(*i))
+ {
+ if(r.str[*i] == '\n')
+ {
+ ++numnl_following;
+ if(indentation) // 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);
+ }
+ }
+ else if(r.str[*i] == ' ' || r.str[*i] == '\t' || r.str[*i] == '\r') // skip leading whitespace
+ ;
+ else
+ break;
+ }
+ return numnl_following;
+}
+
+} // anon namespace
+
+
+//-----------------------------------------------------------------------------
+
+Parser::~Parser()
+{
+ _free();
+ _clr();
+}
+
+Parser::Parser(Callbacks const& cb)
+ : m_file()
+ , m_buf()
+ , m_root_id(NONE)
+ , m_tree()
+ , m_stack(cb)
+ , m_state()
+ , m_key_tag_indentation(0)
+ , m_key_tag2_indentation(0)
+ , m_key_tag()
+ , m_key_tag2()
+ , m_val_tag_indentation(0)
+ , m_val_tag()
+ , m_key_anchor_was_before(false)
+ , m_key_anchor_indentation(0)
+ , m_key_anchor()
+ , m_val_anchor_indentation(0)
+ , m_val_anchor()
+ , m_filter_arena()
+ , m_newline_offsets()
+ , m_newline_offsets_size(0)
+ , m_newline_offsets_capacity(0)
+ , m_newline_offsets_buf()
+{
+ m_stack.push(State{});
+ m_state = &m_stack.top();
+}
+
+Parser::Parser(Parser &&that)
+ : m_file(that.m_file)
+ , m_buf(that.m_buf)
+ , m_root_id(that.m_root_id)
+ , m_tree(that.m_tree)
+ , m_stack(std::move(that.m_stack))
+ , m_state(&m_stack.top())
+ , m_key_tag_indentation(that.m_key_tag_indentation)
+ , m_key_tag2_indentation(that.m_key_tag2_indentation)
+ , m_key_tag(that.m_key_tag)
+ , m_key_tag2(that.m_key_tag2)
+ , m_val_tag_indentation(that.m_val_tag_indentation)
+ , m_val_tag(that.m_val_tag)
+ , m_key_anchor_was_before(that.m_key_anchor_was_before)
+ , m_key_anchor_indentation(that.m_key_anchor_indentation)
+ , m_key_anchor(that.m_key_anchor)
+ , m_val_anchor_indentation(that.m_val_anchor_indentation)
+ , m_val_anchor(that.m_val_anchor)
+ , m_filter_arena(that.m_filter_arena)
+ , 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();
+}
+
+Parser::Parser(Parser const& that)
+ : m_file(that.m_file)
+ , m_buf(that.m_buf)
+ , m_root_id(that.m_root_id)
+ , m_tree(that.m_tree)
+ , m_stack(that.m_stack)
+ , m_state(&m_stack.top())
+ , m_key_tag_indentation(that.m_key_tag_indentation)
+ , m_key_tag2_indentation(that.m_key_tag2_indentation)
+ , m_key_tag(that.m_key_tag)
+ , m_key_tag2(that.m_key_tag2)
+ , m_val_tag_indentation(that.m_val_tag_indentation)
+ , m_val_tag(that.m_val_tag)
+ , m_key_anchor_was_before(that.m_key_anchor_was_before)
+ , m_key_anchor_indentation(that.m_key_anchor_indentation)
+ , m_key_anchor(that.m_key_anchor)
+ , m_val_anchor_indentation(that.m_val_anchor_indentation)
+ , m_val_anchor(that.m_val_anchor)
+ , m_filter_arena()
+ , 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_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;
+ }
+ if(that.m_filter_arena.len)
+ {
+ _resize_filter_arena(that.m_filter_arena.len);
+ }
+}
+
+Parser& Parser::operator=(Parser &&that)
+{
+ _free();
+ m_file = (that.m_file);
+ m_buf = (that.m_buf);
+ m_root_id = (that.m_root_id);
+ m_tree = (that.m_tree);
+ m_stack = std::move(that.m_stack);
+ m_state = (&m_stack.top());
+ m_key_tag_indentation = (that.m_key_tag_indentation);
+ m_key_tag2_indentation = (that.m_key_tag2_indentation);
+ m_key_tag = (that.m_key_tag);
+ m_key_tag2 = (that.m_key_tag2);
+ m_val_tag_indentation = (that.m_val_tag_indentation);
+ m_val_tag = (that.m_val_tag);
+ m_key_anchor_was_before = (that.m_key_anchor_was_before);
+ m_key_anchor_indentation = (that.m_key_anchor_indentation);
+ m_key_anchor = (that.m_key_anchor);
+ m_val_anchor_indentation = (that.m_val_anchor_indentation);
+ m_val_anchor = (that.m_val_anchor);
+ m_filter_arena = that.m_filter_arena;
+ 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;
+}
+
+Parser& Parser::operator=(Parser const& that)
+{
+ _free();
+ m_file = (that.m_file);
+ m_buf = (that.m_buf);
+ m_root_id = (that.m_root_id);
+ m_tree = (that.m_tree);
+ m_stack = that.m_stack;
+ m_state = &m_stack.top();
+ m_key_tag_indentation = (that.m_key_tag_indentation);
+ m_key_tag2_indentation = (that.m_key_tag2_indentation);
+ m_key_tag = (that.m_key_tag);
+ m_key_tag2 = (that.m_key_tag2);
+ m_val_tag_indentation = (that.m_val_tag_indentation);
+ m_val_tag = (that.m_val_tag);
+ m_key_anchor_was_before = (that.m_key_anchor_was_before);
+ m_key_anchor_indentation = (that.m_key_anchor_indentation);
+ m_key_anchor = (that.m_key_anchor);
+ m_val_anchor_indentation = (that.m_val_anchor_indentation);
+ m_val_anchor = (that.m_val_anchor);
+ if(that.m_filter_arena.len > 0)
+ _resize_filter_arena(that.m_filter_arena.len);
+ if(that.m_newline_offsets_capacity > m_newline_offsets_capacity)
+ _resize_locations(that.m_newline_offsets_capacity);
+ _RYML_CB_CHECK(m_stack.m_callbacks, m_newline_offsets_capacity >= that.m_newline_offsets_capacity);
+ _RYML_CB_CHECK(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;
+}
+
+void Parser::_clr()
+{
+ m_file = {};
+ m_buf = {};
+ m_root_id = {};
+ m_tree = {};
+ m_stack.clear();
+ m_state = {};
+ m_key_tag_indentation = {};
+ m_key_tag2_indentation = {};
+ m_key_tag = {};
+ m_key_tag2 = {};
+ m_val_tag_indentation = {};
+ m_val_tag = {};
+ m_key_anchor_was_before = {};
+ m_key_anchor_indentation = {};
+ m_key_anchor = {};
+ m_val_anchor_indentation = {};
+ m_val_anchor = {};
+ m_filter_arena = {};
+ m_newline_offsets = {};
+ m_newline_offsets_size = {};
+ m_newline_offsets_capacity = {};
+ m_newline_offsets_buf = {};
+}
+
+void Parser::_free()
+{
+ if(m_newline_offsets)
+ {
+ _RYML_CB_FREE(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 = 0u;
+ }
+ if(m_filter_arena.len)
+ {
+ _RYML_CB_FREE(m_stack.m_callbacks, m_filter_arena.str, char, m_filter_arena.len);
+ m_filter_arena = {};
+ }
+ m_stack._free();
+}
+
+
+//-----------------------------------------------------------------------------
+void Parser::_reset()
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_stack.size() == 1);
+ m_stack.clear();
+ m_stack.push({});
+ m_state = &m_stack.top();
+ m_state->reset(m_file.str, m_root_id);
+
+ m_key_tag_indentation = 0;
+ m_key_tag2_indentation = 0;
+ m_key_tag.clear();
+ m_key_tag2.clear();
+ m_val_tag_indentation = 0;
+ m_val_tag.clear();
+ m_key_anchor_was_before = false;
+ m_key_anchor_indentation = 0;
+ m_key_anchor.clear();
+ m_val_anchor_indentation = 0;
+ m_val_anchor.clear();
+
+ _mark_locations_dirty();
+}
+
+//-----------------------------------------------------------------------------
+template<class DumpFn>
+void Parser::_fmt_msg(DumpFn &&dumpfn) const
+{
+ auto const& lc = m_state->line_contents;
+ csubstr contents = lc.stripped;
+ if(contents.len)
+ {
+ // print the yaml src line
+ size_t offs = 3u + to_chars(substr{}, m_state->pos.line) + to_chars(substr{}, m_state->pos.col);
+ if(m_file.len)
+ {
+ _parse_dump(dumpfn, "{}:", m_file);
+ offs += m_file.len + 1;
+ }
+ _parse_dump(dumpfn, "{}:{}: ", m_state->pos.line, m_state->pos.col);
+ csubstr maybe_full_content = (contents.len < 80u ? contents : contents.first(80u));
+ csubstr maybe_ellipsis = (contents.len < 80u ? csubstr{} : csubstr("..."));
+ _parse_dump(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)
+ dumpfn(" ");
+ dumpfn("^");
+ for(size_t i = 1, e = (lc.rem.len < 80u ? lc.rem.len : 80u); i < e; ++i)
+ dumpfn("~");
+ _parse_dump(dumpfn, "{} (cols {}-{})\n", maybe_ellipsis, firstcol+1, lastcol+1);
+ }
+ else
+ {
+ dumpfn("\n");
+ }
+
+#ifdef RYML_DBG
+ // next line: print the state flags
+ {
+ char flagbuf_[64];
+ _parse_dump(dumpfn, "top state: {}\n", _prfl(flagbuf_, m_state->flags));
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+template<class ...Args>
+void Parser::_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); };
+ _parse_dump(dumpfn, fmt, args...);
+ writer.append('\n');
+ _fmt_msg(dumpfn);
+ size_t len = writer.pos < RYML_ERRMSG_SIZE ? writer.pos : RYML_ERRMSG_SIZE;
+ m_tree->m_callbacks.m_error(errmsg, len, m_state->pos, m_tree->m_callbacks.m_user_data);
+}
+
+//-----------------------------------------------------------------------------
+#ifdef RYML_DBG
+template<class ...Args>
+void Parser::_dbg(csubstr fmt, Args const& C4_RESTRICT ...args) const
+{
+ auto dumpfn = [](csubstr s){ fwrite(s.str, 1, s.len, stdout); };
+ _parse_dump(dumpfn, fmt, args...);
+ dumpfn("\n");
+ _fmt_msg(dumpfn);
+}
+#endif
+
+//-----------------------------------------------------------------------------
+bool Parser::_finished_file() const
+{
+ bool ret = m_state->pos.offset >= m_buf.len;
+ if(ret)
+ {
+ _c4dbgp("finished file!!!");
+ }
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+bool Parser::_finished_line() const
+{
+ return m_state->line_contents.rem.empty();
+}
+
+//-----------------------------------------------------------------------------
+void Parser::parse_in_place(csubstr file, substr buf, Tree *t, size_t node_id)
+{
+ m_file = file;
+ m_buf = buf;
+ m_root_id = node_id;
+ m_tree = t;
+ _reset();
+ while( ! _finished_file())
+ {
+ _scan_line();
+ while( ! _finished_line())
+ _handle_line();
+ if(_finished_file())
+ break; // it may have finished because of multiline blocks
+ _line_ended();
+ }
+ _handle_finished_file();
+}
+
+//-----------------------------------------------------------------------------
+void Parser::_handle_finished_file()
+{
+ _end_stream();
+}
+
+//-----------------------------------------------------------------------------
+void Parser::_handle_line()
+{
+ _c4dbgq("\n-----------");
+ _c4dbgt("handling line={}, offset={}B", m_state->pos.line, m_state->pos.offset);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ! m_state->line_contents.rem.empty());
+ if(has_any(RSEQ))
+ {
+ if(has_any(FLOW))
+ {
+ if(_handle_seq_flow())
+ return;
+ }
+ else
+ {
+ if(_handle_seq_blck())
+ return;
+ }
+ }
+ else if(has_any(RMAP))
+ {
+ if(has_any(FLOW))
+ {
+ if(_handle_map_flow())
+ return;
+ }
+ else
+ {
+ if(_handle_map_blck())
+ return;
+ }
+ }
+ else if(has_any(RUNK))
+ {
+ if(_handle_unk())
+ return;
+ }
+
+ if(_handle_top())
+ return;
+}
+
+
+//-----------------------------------------------------------------------------
+bool Parser::_handle_unk()
+{
+ _c4dbgp("handle_unk");
+
+ csubstr rem = m_state->line_contents.rem;
+ const bool start_as_child = (node(m_state) == nullptr);
+
+ if(C4_UNLIKELY(has_any(NDOC)))
+ {
+ if(rem == "---" || rem.begins_with("--- "))
+ {
+ _start_new_doc(rem);
+ return true;
+ }
+ auto trimmed = rem.triml(' ');
+ if(trimmed == "---" || trimmed.begins_with("--- "))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, rem.len >= trimmed.len);
+ _line_progressed(rem.len - trimmed.len);
+ _start_new_doc(trimmed);
+ _save_indentation();
+ return true;
+ }
+ else if(trimmed.begins_with("..."))
+ {
+ _end_stream();
+ }
+ else if(trimmed.first_of("#%") == csubstr::npos) // neither a doc nor a tag
+ {
+ _c4dbgpf("starting implicit doc to accomodate unexpected tokens: '{}'", rem);
+ size_t indref = m_state->indref;
+ _push_level();
+ _start_doc();
+ _set_indentation(indref);
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, !trimmed.empty());
+ }
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT|RSEQ|RMAP));
+ if(m_state->indref > 0)
+ {
+ csubstr ws = rem.left_of(rem.first_not_of(' '));
+ if(m_state->indref <= ws.len)
+ {
+ _c4dbgpf("skipping base indentation of {}", m_state->indref);
+ _line_progressed(m_state->indref);
+ rem = rem.sub(m_state->indref);
+ }
+ }
+
+ if(rem.begins_with("- ") _RYML_WITH_TAB_TOKENS( || rem.begins_with("-\t")))
+ {
+ _c4dbgpf("it's a seq (as_child={})", start_as_child);
+ _move_key_anchor_to_val_anchor();
+ _move_key_tag_to_val_tag();
+ _push_level();
+ _start_seq(start_as_child);
+ _save_indentation();
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem == '-')
+ {
+ _c4dbgpf("it's a seq (as_child={})", start_as_child);
+ _move_key_anchor_to_val_anchor();
+ _move_key_tag_to_val_tag();
+ _push_level();
+ _start_seq(start_as_child);
+ _save_indentation();
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('['))
+ {
+ _c4dbgpf("it's a seq, flow (as_child={})", start_as_child);
+ _move_key_anchor_to_val_anchor();
+ _move_key_tag_to_val_tag();
+ _push_level(/*explicit flow*/true);
+ _start_seq(start_as_child);
+ add_flags(FLOW);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('{'))
+ {
+ _c4dbgpf("it's a map, flow (as_child={})", start_as_child);
+ _move_key_anchor_to_val_anchor();
+ _move_key_tag_to_val_tag();
+ _push_level(/*explicit flow*/true);
+ _start_map(start_as_child);
+ addrem_flags(FLOW|RKEY, RVAL);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with("? "))
+ {
+ _c4dbgpf("it's a map (as_child={}) + this key is complex", start_as_child);
+ _move_key_anchor_to_val_anchor();
+ _move_key_tag_to_val_tag();
+ _push_level();
+ _start_map(start_as_child);
+ addrem_flags(RKEY|QMRK, RVAL);
+ _save_indentation();
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem.begins_with(": ") && !has_all(SSCL))
+ {
+ _c4dbgp("it's a map with an empty key");
+ _move_key_anchor_to_val_anchor();
+ _move_key_tag_to_val_tag();
+ _push_level();
+ _start_map(start_as_child);
+ _store_scalar_null(rem.str);
+ addrem_flags(RVAL, RKEY);
+ _save_indentation();
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem == ':' && !has_all(SSCL))
+ {
+ _c4dbgp("it's a map with an empty key");
+ _move_key_anchor_to_val_anchor();
+ _move_key_tag_to_val_tag();
+ _push_level();
+ _start_map(start_as_child);
+ _store_scalar_null(rem.str);
+ addrem_flags(RVAL, RKEY);
+ _save_indentation();
+ _line_progressed(1);
+ return true;
+ }
+ else if(_handle_types())
+ {
+ return true;
+ }
+ else if(!rem.begins_with('*') && _handle_key_anchors_and_refs())
+ {
+ return true;
+ }
+ else if(has_all(SSCL))
+ {
+ _c4dbgpf("there's a stored scalar: '{}'", m_state->scalar);
+
+ csubstr saved_scalar;
+ bool is_quoted;
+ if(_scan_scalar(&saved_scalar, &is_quoted))
+ {
+ rem = m_state->line_contents.rem;
+ _c4dbgpf("... and there's also a scalar next! '{}'", saved_scalar);
+ if(rem.begins_with_any(" \t"))
+ {
+ size_t n = rem.first_not_of(" \t");
+ _c4dbgpf("skipping {} spaces/tabs", n);
+ rem = rem.sub(n);
+ _line_progressed(n);
+ }
+ }
+
+ _c4dbgpf("rem='{}'", rem);
+
+ if(rem.begins_with(", "))
+ {
+ _c4dbgpf("got a ',' -- it's a seq (as_child={})", start_as_child);
+ _start_seq(start_as_child);
+ add_flags(FLOW);
+ _append_val(_consume_scalar());
+ _line_progressed(2);
+ }
+ else if(rem.begins_with(','))
+ {
+ _c4dbgpf("got a ',' -- it's a seq (as_child={})", start_as_child);
+ _start_seq(start_as_child);
+ add_flags(FLOW);
+ _append_val(_consume_scalar());
+ _line_progressed(1);
+ }
+ else if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t")))
+ {
+ _c4dbgpf("got a ': ' -- it's a map (as_child={})", start_as_child);
+ _start_map_unk(start_as_child); // wait for the val scalar to append the key-val pair
+ _line_progressed(2);
+ }
+ else if(rem == ":" || rem.begins_with(":\"") || rem.begins_with(":'"))
+ {
+ if(rem == ":") { _c4dbgpf("got a ':' -- it's a map (as_child={})", start_as_child); }
+ else { _c4dbgpf("got a '{}' -- it's a map (as_child={})", rem.first(2), start_as_child); }
+ _start_map_unk(start_as_child); // wait for the val scalar to append the key-val pair
+ _line_progressed(1); // advance only 1
+ }
+ else if(rem.begins_with('}'))
+ {
+ if(!has_all(RMAP|FLOW))
+ {
+ _c4err("invalid token: not reading a map");
+ }
+ if(!has_all(SSCL))
+ {
+ _c4err("no scalar stored");
+ }
+ _append_key_val(saved_scalar);
+ _stop_map();
+ _line_progressed(1);
+ }
+ else if(rem.begins_with("..."))
+ {
+ _c4dbgp("got stream end '...'");
+ _end_stream();
+ _line_progressed(3);
+ }
+ else if(rem.begins_with('#'))
+ {
+ _c4dbgpf("it's a comment: '{}'", rem);
+ _scan_comment();
+ return true;
+ }
+ else if(_handle_key_anchors_and_refs())
+ {
+ return true;
+ }
+ else if(rem.begins_with(" ") || rem.begins_with("\t"))
+ {
+ size_t n = rem.first_not_of(" \t");
+ if(n == npos)
+ n = rem.len;
+ _c4dbgpf("has {} spaces/tabs, skip...", n);
+ _line_progressed(n);
+ return true;
+ }
+ else if(rem.empty())
+ {
+ // nothing to do
+ }
+ else if(rem == "---" || rem.begins_with("--- "))
+ {
+ _c4dbgp("caught ---: starting doc");
+ _start_new_doc(rem);
+ return true;
+ }
+ else if(rem.begins_with('%'))
+ {
+ _c4dbgp("caught a directive: ignoring...");
+ _line_progressed(rem.len);
+ return true;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+
+ if( ! saved_scalar.empty())
+ {
+ _store_scalar(saved_scalar, is_quoted);
+ }
+
+ return true;
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_any(SSCL));
+ csubstr scalar;
+ size_t indentation = m_state->line_contents.indentation; // save
+ bool is_quoted;
+ if(_scan_scalar(&scalar, &is_quoted))
+ {
+ _c4dbgpf("got a {} scalar", is_quoted ? "quoted" : "");
+ rem = m_state->line_contents.rem;
+ {
+ size_t first = rem.first_not_of(" \t");
+ if(first && first != npos)
+ {
+ _c4dbgpf("skip {} whitespace characters", first);
+ _line_progressed(first);
+ rem = rem.sub(first);
+ }
+ }
+ _store_scalar(scalar, is_quoted);
+ if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t")))
+ {
+ _c4dbgpf("got a ': ' next -- it's a map (as_child={})", start_as_child);
+ _push_level();
+ _start_map(start_as_child); // wait for the val scalar to append the key-val pair
+ _set_indentation(indentation);
+ _line_progressed(2); // call this AFTER saving the indentation
+ }
+ else if(rem == ":")
+ {
+ _c4dbgpf("got a ':' next -- it's a map (as_child={})", start_as_child);
+ _push_level();
+ _start_map(start_as_child); // wait for the val scalar to append the key-val pair
+ _set_indentation(indentation);
+ _line_progressed(1); // call this AFTER saving the indentation
+ }
+ else
+ {
+ // we still don't know whether it's a seq or a map
+ // so just store the scalar
+ }
+ return true;
+ }
+ else if(rem.begins_with_any(" \t"))
+ {
+ csubstr ws = rem.left_of(rem.first_not_of(" \t"));
+ rem = rem.right_of(ws);
+ if(has_all(RTOP) && rem.begins_with("---"))
+ {
+ _c4dbgp("there's a doc starting, and it's indented");
+ _set_indentation(ws.len);
+ }
+ _c4dbgpf("skipping {} spaces/tabs", ws.len);
+ _line_progressed(ws.len);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+C4_ALWAYS_INLINE void Parser::_skipchars(char c)
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.rem.begins_with(c));
+ size_t pos = m_state->line_contents.rem.first_not_of(c);
+ if(pos == npos)
+ pos = m_state->line_contents.rem.len; // maybe the line is just whitespace
+ _c4dbgpf("skip {} '{}'", pos, c);
+ _line_progressed(pos);
+}
+
+template<size_t N>
+C4_ALWAYS_INLINE void Parser::_skipchars(const char (&chars)[N])
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.rem.begins_with_any(chars));
+ size_t pos = m_state->line_contents.rem.first_not_of(chars);
+ if(pos == npos)
+ pos = m_state->line_contents.rem.len; // maybe the line is just whitespace
+ _c4dbgpf("skip {} characters", pos);
+ _line_progressed(pos);
+}
+
+
+//-----------------------------------------------------------------------------
+bool Parser::_handle_seq_flow()
+{
+ _c4dbgpf("handle_seq_flow: node_id={} level={}", m_state->node_id, m_state->level);
+ csubstr rem = m_state->line_contents.rem;
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RSEQ|FLOW));
+
+ if(rem.begins_with(' '))
+ {
+ // with explicit flow, indentation does not matter
+ _c4dbgp("starts with spaces");
+ _skipchars(' ');
+ return true;
+ }
+ _RYML_WITH_TAB_TOKENS(else if(rem.begins_with('\t'))
+ {
+ _c4dbgp("starts with tabs");
+ _skipchars('\t');
+ return true;
+ })
+ else if(rem.begins_with('#'))
+ {
+ _c4dbgp("it's a comment");
+ rem = _scan_comment(); // also progresses the line
+ return true;
+ }
+ else if(rem.begins_with(']'))
+ {
+ _c4dbgp("end the sequence");
+ _pop_level();
+ _line_progressed(1);
+ if(has_all(RSEQIMAP))
+ {
+ _stop_seqimap();
+ _pop_level();
+ }
+ return true;
+ }
+
+ if(has_any(RVAL))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT));
+ bool is_quoted;
+ if(_scan_scalar(&rem, &is_quoted))
+ {
+ _c4dbgp("it's a scalar");
+ addrem_flags(RNXT, RVAL);
+ _append_val(rem, is_quoted);
+ return true;
+ }
+ else if(rem.begins_with('['))
+ {
+ _c4dbgp("val is a child seq");
+ addrem_flags(RNXT, RVAL); // before _push_level!
+ _push_level(/*explicit flow*/true);
+ _start_seq();
+ add_flags(FLOW);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('{'))
+ {
+ _c4dbgp("val is a child map");
+ addrem_flags(RNXT, RVAL); // before _push_level!
+ _push_level(/*explicit flow*/true);
+ _start_map();
+ addrem_flags(FLOW|RKEY, RVAL);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem == ':')
+ {
+ _c4dbgpf("found ':' -- there's an implicit map in the seq node[{}]", m_state->node_id);
+ _start_seqimap();
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t")))
+ {
+ _c4dbgpf("found ': ' -- there's an implicit map in the seq node[{}]", m_state->node_id);
+ _start_seqimap();
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem.begins_with("? "))
+ {
+ _c4dbgpf("found '? ' -- there's an implicit map in the seq node[{}]", m_state->node_id);
+ _start_seqimap();
+ _line_progressed(2);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(SSCL) && m_state->scalar == "");
+ addrem_flags(QMRK|RKEY, RVAL|SSCL);
+ return true;
+ }
+ else if(_handle_types())
+ {
+ return true;
+ }
+ else if(_handle_val_anchors_and_refs())
+ {
+ return true;
+ }
+ else if(rem.begins_with(", "))
+ {
+ _c4dbgp("found ',' -- the value was null");
+ _append_val_null(rem.str - 1);
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem.begins_with(','))
+ {
+ _c4dbgp("found ',' -- the value was null");
+ _append_val_null(rem.str - 1);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('\t'))
+ {
+ _skipchars('\t');
+ return true;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+ }
+ else if(has_any(RNXT))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL));
+ if(rem.begins_with(", "))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(FLOW));
+ _c4dbgp("seq: expect next val");
+ addrem_flags(RVAL, RNXT);
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem.begins_with(','))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(FLOW));
+ _c4dbgp("seq: expect next val");
+ addrem_flags(RVAL, RNXT);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem == ':')
+ {
+ _c4dbgpf("found ':' -- there's an implicit map in the seq node[{}]", m_state->node_id);
+ _start_seqimap();
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t")))
+ {
+ _c4dbgpf("found ': ' -- there's an implicit map in the seq node[{}]", m_state->node_id);
+ _start_seqimap();
+ _line_progressed(2);
+ return true;
+ }
+ else
+ {
+ _c4err("was expecting a comma");
+ }
+ }
+ else
+ {
+ _c4err("internal error");
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+bool Parser::_handle_seq_blck()
+{
+ _c4dbgpf("handle_seq_impl: node_id={} level={}", m_state->node_id, m_state->level);
+ csubstr rem = m_state->line_contents.rem;
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RSEQ));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(FLOW));
+
+ if(rem.begins_with('#'))
+ {
+ _c4dbgp("it's a comment");
+ rem = _scan_comment();
+ return true;
+ }
+
+ if(has_any(RNXT))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL));
+
+ if(_handle_indentation())
+ return true;
+
+ if(rem.begins_with("- ") _RYML_WITH_TAB_TOKENS( || rem.begins_with("-\t")))
+ {
+ _c4dbgp("expect another val");
+ addrem_flags(RVAL, RNXT);
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem == '-')
+ {
+ _c4dbgp("expect another val");
+ addrem_flags(RVAL, RNXT);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with_any(" \t"))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ! _at_line_begin());
+ _skipchars(" \t");
+ return true;
+ }
+ else if(rem.begins_with("..."))
+ {
+ _c4dbgp("got stream end '...'");
+ _end_stream();
+ _line_progressed(3);
+ return true;
+ }
+ else if(rem.begins_with("---"))
+ {
+ _c4dbgp("got document start '---'");
+ _start_new_doc(rem);
+ return true;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+ }
+ else if(has_any(RVAL))
+ {
+ // there can be empty values
+ if(_handle_indentation())
+ return true;
+
+ csubstr s;
+ bool is_quoted;
+ if(_scan_scalar(&s, &is_quoted)) // this also progresses the line
+ {
+ _c4dbgpf("it's a{} scalar", is_quoted ? " quoted" : "");
+
+ rem = m_state->line_contents.rem;
+ if(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(rem.begins_with_any(" \t"), rem.begins_with(' ')))
+ {
+ _c4dbgp("skipping whitespace...");
+ size_t skip = rem.first_not_of(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' '));
+ if(skip == csubstr::npos)
+ skip = rem.len; // maybe the line is just whitespace
+ _line_progressed(skip);
+ rem = rem.sub(skip);
+ }
+
+ _c4dbgpf("rem=[{}]~~~{}~~~", rem.len, rem);
+ if(!rem.begins_with('#') && (rem.ends_with(':') || rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t"))))
+ {
+ _c4dbgp("actually, the scalar is the first key of a map, and it opens a new scope");
+ if(m_key_anchor.empty())
+ _move_val_anchor_to_key_anchor();
+ if(m_key_tag.empty())
+ _move_val_tag_to_key_tag();
+ addrem_flags(RNXT, RVAL); // before _push_level! This prepares the current level for popping by setting it to RNXT
+ _push_level();
+ _start_map();
+ _store_scalar(s, is_quoted);
+ if( ! _maybe_set_indentation_from_anchor_or_tag())
+ {
+ _c4dbgpf("set indentation from scalar: {}", m_state->scalar_col);
+ _set_indentation(m_state->scalar_col); // this is the column where the scalar starts
+ }
+ _move_key_tag2_to_key_tag();
+ addrem_flags(RVAL, RKEY);
+ _line_progressed(1);
+ }
+ else
+ {
+ _c4dbgp("appending val to current seq");
+ _append_val(s, is_quoted);
+ addrem_flags(RNXT, RVAL);
+ }
+ return true;
+ }
+ else if(rem.begins_with("- ") _RYML_WITH_TAB_TOKENS( || rem.begins_with("-\t")))
+ {
+ if(_rval_dash_start_or_continue_seq())
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem == '-')
+ {
+ if(_rval_dash_start_or_continue_seq())
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('['))
+ {
+ _c4dbgp("val is a child seq, flow");
+ addrem_flags(RNXT, RVAL); // before _push_level!
+ _push_level(/*explicit flow*/true);
+ _start_seq();
+ add_flags(FLOW);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('{'))
+ {
+ _c4dbgp("val is a child map, flow");
+ addrem_flags(RNXT, RVAL); // before _push_level!
+ _push_level(/*explicit flow*/true);
+ _start_map();
+ addrem_flags(FLOW|RKEY, RVAL);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with("? "))
+ {
+ _c4dbgp("val is a child map + this key is complex");
+ addrem_flags(RNXT, RVAL); // before _push_level!
+ _push_level();
+ _start_map();
+ addrem_flags(QMRK|RKEY, RVAL);
+ _save_indentation();
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem.begins_with(' '))
+ {
+ csubstr spc = rem.left_of(rem.first_not_of(' '));
+ if(_at_line_begin())
+ {
+ _c4dbgpf("skipping value indentation: {} spaces", spc.len);
+ _line_progressed(spc.len);
+ return true;
+ }
+ else
+ {
+ _c4dbgpf("skipping {} spaces", spc.len);
+ _line_progressed(spc.len);
+ return true;
+ }
+ }
+ else if(_handle_types())
+ {
+ return true;
+ }
+ else if(_handle_val_anchors_and_refs())
+ {
+ return true;
+ }
+ /* pathological case:
+ * - &key : val
+ * - &key :
+ * - : val
+ */
+ else if((!has_all(SSCL)) &&
+ (rem.begins_with(": ") || rem.left_of(rem.find("#")).trimr("\t") == ":"))
+ {
+ if(!m_val_anchor.empty() || !m_val_tag.empty())
+ {
+ _c4dbgp("val is a child map + this key is empty, with anchors or tags");
+ addrem_flags(RNXT, RVAL); // before _push_level!
+ _move_val_tag_to_key_tag();
+ _move_val_anchor_to_key_anchor();
+ _push_level();
+ _start_map();
+ _store_scalar_null(rem.str);
+ addrem_flags(RVAL, RKEY);
+ RYML_CHECK(_maybe_set_indentation_from_anchor_or_tag()); // one of them must exist
+ _line_progressed(rem.begins_with(": ") ? 2u : 1u);
+ return true;
+ }
+ else
+ {
+ _c4dbgp("val is a child map + this key is empty, no anchors or tags");
+ addrem_flags(RNXT, RVAL); // before _push_level!
+ size_t ind = m_state->indref;
+ _push_level();
+ _start_map();
+ _store_scalar_null(rem.str);
+ addrem_flags(RVAL, RKEY);
+ _c4dbgpf("set indentation from map anchor: {}", ind + 2);
+ _set_indentation(ind + 2); // this is the column where the map starts
+ _line_progressed(rem.begins_with(": ") ? 2u : 1u);
+ return true;
+ }
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+bool Parser::_rval_dash_start_or_continue_seq()
+{
+ size_t ind = m_state->line_contents.current_col();
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ind >= m_state->indref);
+ size_t delta_ind = ind - m_state->indref;
+ if( ! delta_ind)
+ {
+ _c4dbgp("prev val was empty");
+ addrem_flags(RNXT, RVAL);
+ _append_val_null(&m_state->line_contents.full[ind]);
+ return false;
+ }
+ _c4dbgp("val is a nested seq, indented");
+ addrem_flags(RNXT, RVAL); // before _push_level!
+ _push_level();
+ _start_seq();
+ _save_indentation();
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+bool Parser::_handle_map_flow()
+{
+ // explicit flow, ie, inside {}, separated by commas
+ _c4dbgpf("handle_map_flow: node_id={} level={}", m_state->node_id, m_state->level);
+ csubstr rem = m_state->line_contents.rem;
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RMAP|FLOW));
+
+ if(rem.begins_with(' '))
+ {
+ // with explicit flow, indentation does not matter
+ _c4dbgp("starts with spaces");
+ _skipchars(' ');
+ return true;
+ }
+ _RYML_WITH_TAB_TOKENS(else if(rem.begins_with('\t'))
+ {
+ // with explicit flow, indentation does not matter
+ _c4dbgp("starts with tabs");
+ _skipchars('\t');
+ return true;
+ })
+ else if(rem.begins_with('#'))
+ {
+ _c4dbgp("it's a comment");
+ rem = _scan_comment(); // also progresses the line
+ return true;
+ }
+ else if(rem.begins_with('}'))
+ {
+ _c4dbgp("end the map");
+ if(has_all(SSCL))
+ {
+ _c4dbgp("the last val was null");
+ _append_key_val_null(rem.str - 1);
+ rem_flags(RVAL);
+ }
+ _pop_level();
+ _line_progressed(1);
+ if(has_all(RSEQIMAP))
+ {
+ _c4dbgp("stopping implicitly nested 1x map");
+ _stop_seqimap();
+ _pop_level();
+ }
+ return true;
+ }
+
+ if(has_any(RNXT))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RSEQIMAP));
+
+ if(rem.begins_with(", "))
+ {
+ _c4dbgp("seq: expect next keyval");
+ addrem_flags(RKEY, RNXT);
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem.begins_with(','))
+ {
+ _c4dbgp("seq: expect next keyval");
+ addrem_flags(RKEY, RNXT);
+ _line_progressed(1);
+ return true;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+ }
+ else if(has_any(RKEY))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL));
+
+ bool is_quoted;
+ if(has_none(SSCL) && _scan_scalar(&rem, &is_quoted))
+ {
+ _c4dbgp("it's a scalar");
+ _store_scalar(rem, is_quoted);
+ rem = m_state->line_contents.rem;
+ csubstr trimmed = rem.triml(" \t");
+ if(trimmed.len && (trimmed.begins_with(": ") || trimmed.begins_with_any(":,}") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t"))))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, trimmed.str >= rem.str);
+ size_t num = static_cast<size_t>(trimmed.str - rem.str);
+ _c4dbgpf("trimming {} whitespace after the scalar: '{}' --> '{}'", num, rem, rem.sub(num));
+ rem = rem.sub(num);
+ _line_progressed(num);
+ }
+ }
+
+ if(rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t")))
+ {
+ _c4dbgp("wait for val");
+ addrem_flags(RVAL, RKEY|QMRK);
+ _line_progressed(2);
+ if(!has_all(SSCL))
+ {
+ _c4dbgp("no key was found, defaulting to empty key ''");
+ _store_scalar_null(rem.str);
+ }
+ return true;
+ }
+ else if(rem == ':')
+ {
+ _c4dbgp("wait for val");
+ addrem_flags(RVAL, RKEY|QMRK);
+ _line_progressed(1);
+ if(!has_all(SSCL))
+ {
+ _c4dbgp("no key was found, defaulting to empty key ''");
+ _store_scalar_null(rem.str);
+ }
+ return true;
+ }
+ else if(rem.begins_with('?'))
+ {
+ _c4dbgp("complex key");
+ add_flags(QMRK);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with(','))
+ {
+ _c4dbgp("prev scalar was a key with null value");
+ _append_key_val_null(rem.str - 1);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('}'))
+ {
+ _c4dbgp("map terminates after a key...");
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(SSCL));
+ _c4dbgp("the last val was null");
+ _append_key_val_null(rem.str - 1);
+ rem_flags(RVAL);
+ if(has_all(RSEQIMAP))
+ {
+ _c4dbgp("stopping implicitly nested 1x map");
+ _stop_seqimap();
+ _pop_level();
+ }
+ _pop_level();
+ _line_progressed(1);
+ return true;
+ }
+ else if(_handle_types())
+ {
+ return true;
+ }
+ else if(_handle_key_anchors_and_refs())
+ {
+ return true;
+ }
+ else if(rem == "")
+ {
+ return true;
+ }
+ else
+ {
+ size_t pos = rem.first_not_of(" \t");
+ if(pos == csubstr::npos)
+ pos = 0;
+ rem = rem.sub(pos);
+ if(rem.begins_with(':'))
+ {
+ _c4dbgp("wait for val");
+ addrem_flags(RVAL, RKEY|QMRK);
+ _line_progressed(pos + 1);
+ if(!has_all(SSCL))
+ {
+ _c4dbgp("no key was found, defaulting to empty key ''");
+ _store_scalar_null(rem.str);
+ }
+ return true;
+ }
+ else if(rem.begins_with('#'))
+ {
+ _c4dbgp("it's a comment");
+ _line_progressed(pos);
+ rem = _scan_comment(); // also progresses the line
+ return true;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+ }
+ }
+ else if(has_any(RVAL))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(SSCL));
+ bool is_quoted;
+ if(_scan_scalar(&rem, &is_quoted))
+ {
+ _c4dbgp("it's a scalar");
+ addrem_flags(RNXT, RVAL|RKEY);
+ _append_key_val(rem, is_quoted);
+ if(has_all(RSEQIMAP))
+ {
+ _c4dbgp("stopping implicitly nested 1x map");
+ _stop_seqimap();
+ _pop_level();
+ }
+ return true;
+ }
+ else if(rem.begins_with('['))
+ {
+ _c4dbgp("val is a child seq");
+ addrem_flags(RNXT, RVAL|RKEY); // before _push_level!
+ _push_level(/*explicit flow*/true);
+ _move_scalar_from_top();
+ _start_seq();
+ add_flags(FLOW);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('{'))
+ {
+ _c4dbgp("val is a child map");
+ addrem_flags(RNXT, RVAL|RKEY); // before _push_level!
+ _push_level(/*explicit flow*/true);
+ _move_scalar_from_top();
+ _start_map();
+ addrem_flags(FLOW|RKEY, RNXT|RVAL);
+ _line_progressed(1);
+ return true;
+ }
+ else if(_handle_types())
+ {
+ return true;
+ }
+ else if(_handle_val_anchors_and_refs())
+ {
+ return true;
+ }
+ else if(rem.begins_with(','))
+ {
+ _c4dbgp("appending empty val");
+ _append_key_val_null(rem.str - 1);
+ addrem_flags(RKEY, RVAL);
+ _line_progressed(1);
+ if(has_any(RSEQIMAP))
+ {
+ _c4dbgp("stopping implicitly nested 1x map");
+ _stop_seqimap();
+ _pop_level();
+ }
+ return true;
+ }
+ else if(has_any(RSEQIMAP) && rem.begins_with(']'))
+ {
+ _c4dbgp("stopping implicitly nested 1x map");
+ if(has_any(SSCL))
+ {
+ _append_key_val_null(rem.str - 1);
+ }
+ _stop_seqimap();
+ _pop_level();
+ return true;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+ }
+ else
+ {
+ _c4err("internal error");
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+bool Parser::_handle_map_blck()
+{
+ _c4dbgpf("handle_map_impl: node_id={} level={}", m_state->node_id, m_state->level);
+ csubstr rem = m_state->line_contents.rem;
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RMAP));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(FLOW));
+
+ if(rem.begins_with('#'))
+ {
+ _c4dbgp("it's a comment");
+ rem = _scan_comment();
+ return true;
+ }
+
+ if(has_any(RNXT))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL));
+ // actually, we don't need RNXT in indent-based maps.
+ addrem_flags(RKEY, RNXT);
+ }
+
+ if(_handle_indentation())
+ return true;
+
+ if(has_any(RKEY))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RVAL));
+
+ _c4dbgp("read scalar?");
+ bool is_quoted;
+ if(_scan_scalar(&rem, &is_quoted)) // this also progresses the line
+ {
+ _c4dbgpf("it's a{} scalar", is_quoted ? " quoted" : "");
+ if(has_all(QMRK|SSCL))
+ {
+ _c4dbgpf("current key is QMRK; SSCL is set. so take store scalar='{}' as key and add an empty val", m_state->scalar);
+ _append_key_val_null(rem.str - 1);
+ }
+ _store_scalar(rem, is_quoted);
+ if(has_all(QMRK|RSET))
+ {
+ _c4dbgp("it's a complex key, so use null value '~'");
+ _append_key_val_null(rem.str);
+ }
+ rem = m_state->line_contents.rem;
+
+ if(rem.begins_with(':'))
+ {
+ _c4dbgp("wait for val");
+ addrem_flags(RVAL, RKEY|QMRK);
+ _line_progressed(1);
+ rem = m_state->line_contents.rem;
+ if(rem.begins_with_any(" \t"))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ! _at_line_begin());
+ rem = rem.left_of(rem.first_not_of(" \t"));
+ _c4dbgpf("skip {} spaces/tabs", rem.len);
+ _line_progressed(rem.len);
+ }
+ }
+ return true;
+ }
+ else if(rem.begins_with_any(" \t"))
+ {
+ size_t pos = rem.first_not_of(" \t");
+ if(pos == npos)
+ pos = rem.len;
+ _c4dbgpf("skip {} spaces/tabs", pos);
+ _line_progressed(pos);
+ return true;
+ }
+ else if(rem == '?' || rem.begins_with("? "))
+ {
+ _c4dbgp("it's a complex key");
+ _line_progressed(rem.begins_with("? ") ? 2u : 1u);
+ if(has_any(SSCL))
+ _append_key_val_null(rem.str - 1);
+ add_flags(QMRK);
+ return true;
+ }
+ else if(has_all(QMRK) && rem.begins_with(':'))
+ {
+ _c4dbgp("complex key finished");
+ if(!has_any(SSCL))
+ _store_scalar_null(rem.str);
+ addrem_flags(RVAL, RKEY|QMRK);
+ _line_progressed(1);
+ rem = m_state->line_contents.rem;
+ if(rem.begins_with(' '))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ! _at_line_begin());
+ _skipchars(' ');
+ }
+ return true;
+ }
+ else if(rem == ':' || rem.begins_with(": ") _RYML_WITH_TAB_TOKENS( || rem.begins_with(":\t")))
+ {
+ _c4dbgp("key finished");
+ if(!has_all(SSCL))
+ {
+ _c4dbgp("key was empty...");
+ _store_scalar_null(rem.str);
+ rem_flags(QMRK);
+ }
+ addrem_flags(RVAL, RKEY);
+ _line_progressed(rem == ':' ? 1 : 2);
+ return true;
+ }
+ else if(rem.begins_with("..."))
+ {
+ _c4dbgp("end current document");
+ _end_stream();
+ _line_progressed(3);
+ return true;
+ }
+ else if(rem.begins_with("---"))
+ {
+ _c4dbgp("start new document '---'");
+ _start_new_doc(rem);
+ return true;
+ }
+ else if(_handle_types())
+ {
+ return true;
+ }
+ else if(_handle_key_anchors_and_refs())
+ {
+ return true;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+ }
+ else if(has_any(RVAL))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RNXT));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RKEY));
+
+ csubstr s;
+ bool is_quoted;
+ if(_scan_scalar(&s, &is_quoted)) // this also progresses the line
+ {
+ _c4dbgpf("it's a{} scalar", is_quoted ? " quoted" : "");
+
+ rem = m_state->line_contents.rem;
+
+ if(rem.begins_with(": "))
+ {
+ _c4dbgp("actually, the scalar is the first key of a map");
+ addrem_flags(RKEY, RVAL); // before _push_level! This prepares the current level for popping by setting it to RNXT
+ _push_level();
+ _move_scalar_from_top();
+ _move_val_anchor_to_key_anchor();
+ _start_map();
+ _save_indentation(m_state->scalar_col);
+ addrem_flags(RVAL, RKEY);
+ _line_progressed(2);
+ }
+ else if(rem.begins_with(':'))
+ {
+ _c4dbgp("actually, the scalar is the first key of a map, and it opens a new scope");
+ addrem_flags(RKEY, RVAL); // before _push_level! This prepares the current level for popping by setting it to RNXT
+ _push_level();
+ _move_scalar_from_top();
+ _move_val_anchor_to_key_anchor();
+ _start_map();
+ _save_indentation(/*behind*/s.len);
+ addrem_flags(RVAL, RKEY);
+ _line_progressed(1);
+ }
+ else
+ {
+ _c4dbgp("appending keyval to current map");
+ _append_key_val(s, is_quoted);
+ addrem_flags(RKEY, RVAL);
+ }
+ return true;
+ }
+ else if(rem.begins_with("- ") _RYML_WITH_TAB_TOKENS( || rem.begins_with("-\t")))
+ {
+ _c4dbgp("val is a nested seq, indented");
+ addrem_flags(RKEY, RVAL); // before _push_level!
+ _push_level();
+ _move_scalar_from_top();
+ _start_seq();
+ _save_indentation();
+ _line_progressed(2);
+ return true;
+ }
+ else if(rem == '-')
+ {
+ _c4dbgp("maybe a seq. start unknown, indented");
+ _start_unk();
+ _save_indentation();
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('['))
+ {
+ _c4dbgp("val is a child seq, flow");
+ addrem_flags(RKEY, RVAL); // before _push_level!
+ _push_level(/*explicit flow*/true);
+ _move_scalar_from_top();
+ _start_seq();
+ add_flags(FLOW);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with('{'))
+ {
+ _c4dbgp("val is a child map, flow");
+ addrem_flags(RKEY, RVAL); // before _push_level!
+ _push_level(/*explicit flow*/true);
+ _move_scalar_from_top();
+ _start_map();
+ addrem_flags(FLOW|RKEY, RVAL);
+ _line_progressed(1);
+ return true;
+ }
+ else if(rem.begins_with(' '))
+ {
+ csubstr spc = rem.left_of(rem.first_not_of(' '));
+ if(_at_line_begin())
+ {
+ _c4dbgpf("skipping value indentation: {} spaces", spc.len);
+ _line_progressed(spc.len);
+ return true;
+ }
+ else
+ {
+ _c4dbgpf("skipping {} spaces", spc.len);
+ _line_progressed(spc.len);
+ return true;
+ }
+ }
+ else if(_handle_types())
+ {
+ return true;
+ }
+ else if(_handle_val_anchors_and_refs())
+ {
+ return true;
+ }
+ else if(rem.begins_with("--- ") || rem == "---" || rem.begins_with("---\t"))
+ {
+ _start_new_doc(rem);
+ return true;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+ }
+ else
+ {
+ _c4err("internal error");
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+bool Parser::_handle_top()
+{
+ _c4dbgp("handle_top");
+ csubstr rem = m_state->line_contents.rem;
+
+ if(rem.begins_with('#'))
+ {
+ _c4dbgp("a comment line");
+ _scan_comment();
+ return true;
+ }
+
+ csubstr trimmed = rem.triml(' ');
+
+ if(trimmed.begins_with('%'))
+ {
+ _handle_directive(trimmed);
+ _line_progressed(rem.len);
+ return true;
+ }
+ else if(trimmed.begins_with("--- ") || trimmed == "---" || trimmed.begins_with("---\t"))
+ {
+ _start_new_doc(rem);
+ if(trimmed.len < rem.len)
+ {
+ _line_progressed(rem.len - trimmed.len);
+ _save_indentation();
+ }
+ return true;
+ }
+ else if(trimmed.begins_with("..."))
+ {
+ _c4dbgp("end current document");
+ _end_stream();
+ if(trimmed.len < rem.len)
+ {
+ _line_progressed(rem.len - trimmed.len);
+ }
+ _line_progressed(3);
+ return true;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+
+bool Parser::_handle_key_anchors_and_refs()
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, !has_any(RVAL));
+ const csubstr rem = m_state->line_contents.rem;
+ if(rem.begins_with('&'))
+ {
+ _c4dbgp("found a key anchor!!!");
+ if(has_all(QMRK|SSCL))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RKEY));
+ _c4dbgp("there is a stored key, so this anchor is for the next element");
+ _append_key_val_null(rem.str - 1);
+ rem_flags(QMRK);
+ return true;
+ }
+ csubstr anchor = rem.left_of(rem.first_of(' '));
+ _line_progressed(anchor.len);
+ anchor = anchor.sub(1); // skip the first character
+ _move_key_anchor_to_val_anchor();
+ _c4dbgpf("key anchor value: '{}'", anchor);
+ m_key_anchor = anchor;
+ m_key_anchor_indentation = m_state->line_contents.current_col(rem);
+ return true;
+ }
+ else if(C4_UNLIKELY(rem.begins_with('*')))
+ {
+ _c4err("not implemented - this should have been catched elsewhere");
+ C4_NEVER_REACH();
+ return false;
+ }
+ return false;
+}
+
+bool Parser::_handle_val_anchors_and_refs()
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, !has_any(RKEY));
+ const csubstr rem = m_state->line_contents.rem;
+ if(rem.begins_with('&'))
+ {
+ csubstr anchor = rem.left_of(rem.first_of(' '));
+ _line_progressed(anchor.len);
+ anchor = anchor.sub(1); // skip the first character
+ _c4dbgpf("val: found an anchor: '{}', indentation={}!!!", anchor, m_state->line_contents.current_col(rem));
+ if(m_val_anchor.empty())
+ {
+ _c4dbgpf("save val anchor: '{}'", anchor);
+ m_val_anchor = anchor;
+ m_val_anchor_indentation = m_state->line_contents.current_col(rem);
+ }
+ else
+ {
+ _c4dbgpf("there is a pending val anchor '{}'", m_val_anchor);
+ if(m_tree->is_seq(m_state->node_id))
+ {
+ if(m_tree->has_children(m_state->node_id))
+ {
+ _c4dbgpf("current node={} is a seq, has {} children", m_state->node_id, m_tree->num_children(m_state->node_id));
+ _c4dbgpf("... so take the new one as a key anchor '{}'", anchor);
+ m_key_anchor = anchor;
+ m_key_anchor_indentation = m_state->line_contents.current_col(rem);
+ }
+ else
+ {
+ _c4dbgpf("current node={} is a seq, has no children", m_state->node_id);
+ if(m_tree->has_val_anchor(m_state->node_id))
+ {
+ _c4dbgpf("... node={} already has val anchor: '{}'", m_state->node_id, m_tree->val_anchor(m_state->node_id));
+ _c4dbgpf("... so take the new one as a key anchor '{}'", anchor);
+ m_key_anchor = anchor;
+ m_key_anchor_indentation = m_state->line_contents.current_col(rem);
+ }
+ else
+ {
+ _c4dbgpf("... so set pending val anchor: '{}' on current node {}", m_val_anchor, m_state->node_id);
+ m_tree->set_val_anchor(m_state->node_id, m_val_anchor);
+ m_val_anchor = anchor;
+ m_val_anchor_indentation = m_state->line_contents.current_col(rem);
+ }
+ }
+ }
+ }
+ return true;
+ }
+ else if(C4_UNLIKELY(rem.begins_with('*')))
+ {
+ _c4err("not implemented - this should have been catched elsewhere");
+ C4_NEVER_REACH();
+ return false;
+ }
+ return false;
+}
+
+void Parser::_move_key_anchor_to_val_anchor()
+{
+ if(m_key_anchor.empty())
+ return;
+ _c4dbgpf("move current key anchor to val slot: key='{}' -> val='{}'", m_key_anchor, m_val_anchor);
+ if(!m_val_anchor.empty())
+ _c4err("triple-pending anchor");
+ m_val_anchor = m_key_anchor;
+ m_val_anchor_indentation = m_key_anchor_indentation;
+ m_key_anchor = {};
+ m_key_anchor_indentation = {};
+}
+
+void Parser::_move_val_anchor_to_key_anchor()
+{
+ if(m_val_anchor.empty())
+ return;
+ if(!_token_is_from_this_line(m_val_anchor))
+ return;
+ _c4dbgpf("move current val anchor to key slot: key='{}' <- val='{}'", m_key_anchor, m_val_anchor);
+ if(!m_key_anchor.empty())
+ _c4err("triple-pending anchor");
+ m_key_anchor = m_val_anchor;
+ m_key_anchor_indentation = m_val_anchor_indentation;
+ m_val_anchor = {};
+ m_val_anchor_indentation = {};
+}
+
+void Parser::_move_key_tag_to_val_tag()
+{
+ if(m_key_tag.empty())
+ return;
+ _c4dbgpf("move key tag to val tag: key='{}' -> val='{}'", m_key_tag, m_val_tag);
+ m_val_tag = m_key_tag;
+ m_val_tag_indentation = m_key_tag_indentation;
+ m_key_tag.clear();
+ m_key_tag_indentation = 0;
+}
+
+void Parser::_move_val_tag_to_key_tag()
+{
+ if(m_val_tag.empty())
+ return;
+ if(!_token_is_from_this_line(m_val_tag))
+ return;
+ _c4dbgpf("move val tag to key tag: key='{}' <- val='{}'", m_key_tag, m_val_tag);
+ m_key_tag = m_val_tag;
+ m_key_tag_indentation = m_val_tag_indentation;
+ m_val_tag.clear();
+ m_val_tag_indentation = 0;
+}
+
+void Parser::_move_key_tag2_to_key_tag()
+{
+ if(m_key_tag2.empty())
+ return;
+ _c4dbgpf("move key tag2 to key tag: key='{}' <- key2='{}'", m_key_tag, m_key_tag2);
+ m_key_tag = m_key_tag2;
+ m_key_tag_indentation = m_key_tag2_indentation;
+ m_key_tag2.clear();
+ m_key_tag2_indentation = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+
+bool Parser::_handle_types()
+{
+ csubstr rem = m_state->line_contents.rem.triml(' ');
+ csubstr t;
+
+ if(rem.begins_with("!!"))
+ {
+ _c4dbgp("begins with '!!'");
+ t = rem.left_of(rem.first_of(" ,"));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 2);
+ //t = t.sub(2);
+ if(t == "!!set")
+ add_flags(RSET);
+ }
+ else if(rem.begins_with("!<"))
+ {
+ _c4dbgp("begins with '!<'");
+ t = rem.left_of(rem.first_of('>'), true);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 2);
+ //t = t.sub(2, t.len-1);
+ }
+ else if(rem.begins_with("!h!"))
+ {
+ _c4dbgp("begins with '!h!'");
+ t = rem.left_of(rem.first_of(' '));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 3);
+ //t = t.sub(3);
+ }
+ else if(rem.begins_with('!'))
+ {
+ _c4dbgp("begins with '!'");
+ t = rem.left_of(rem.first_of(' '));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 1);
+ //t = t.sub(1);
+ }
+
+ if(t.empty())
+ return false;
+
+ if(has_all(QMRK|SSCL))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RKEY));
+ _c4dbgp("there is a stored key, so this tag is for the next element");
+ _append_key_val_null(rem.str - 1);
+ rem_flags(QMRK);
+ }
+
+ #ifdef RYML_NO_COVERAGE__TO_BE_DELETED
+ const char *tag_beginning = rem.str;
+ #endif
+ size_t tag_indentation = m_state->line_contents.current_col(t);
+ _c4dbgpf("there was a tag: '{}', indentation={}", t, tag_indentation);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, t.end() > m_state->line_contents.rem.begin());
+ _line_progressed(static_cast<size_t>(t.end() - m_state->line_contents.rem.begin()));
+ {
+ size_t pos = m_state->line_contents.rem.first_not_of(" \t");
+ if(pos != csubstr::npos)
+ _line_progressed(pos);
+ }
+
+ if(has_all(RMAP|RKEY))
+ {
+ _c4dbgpf("saving map key tag '{}'", t);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_key_tag.empty());
+ m_key_tag = t;
+ m_key_tag_indentation = tag_indentation;
+ }
+ else if(has_all(RMAP|RVAL))
+ {
+ /* foo: !!str
+ * !!str : bar */
+ rem = m_state->line_contents.rem;
+ rem = rem.left_of(rem.find("#"));
+ rem = rem.trimr(" \t");
+ _c4dbgpf("rem='{}'", rem);
+ #ifdef RYML_NO_COVERAGE__TO_BE_DELETED
+ if(rem == ':' || rem.begins_with(": "))
+ {
+ _c4dbgp("the last val was null, and this is a tag from a null key");
+ _append_key_val_null(tag_beginning - 1);
+ _store_scalar_null(rem.str - 1);
+ // do not change the flag to key, it is ~
+ _RYML_CB_ASSERT(m_stack.m_callbacks, rem.begin() > m_state->line_contents.rem.begin());
+ size_t token_len = rem == ':' ? 1 : 2;
+ _line_progressed(static_cast<size_t>(token_len + rem.begin() - m_state->line_contents.rem.begin()));
+ }
+ #endif
+ _c4dbgpf("saving map val tag '{}'", t);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_val_tag.empty());
+ m_val_tag = t;
+ m_val_tag_indentation = tag_indentation;
+ }
+ else if(has_all(RSEQ|RVAL) || has_all(RTOP|RUNK|NDOC))
+ {
+ if(m_val_tag.empty())
+ {
+ _c4dbgpf("saving seq/doc val tag '{}'", t);
+ m_val_tag = t;
+ m_val_tag_indentation = tag_indentation;
+ }
+ else
+ {
+ _c4dbgpf("saving seq/doc key tag '{}'", t);
+ m_key_tag = t;
+ m_key_tag_indentation = tag_indentation;
+ }
+ }
+ else if(has_all(RTOP|RUNK) || has_any(RUNK))
+ {
+ rem = m_state->line_contents.rem;
+ rem = rem.left_of(rem.find("#"));
+ rem = rem.trimr(" \t");
+ if(rem.empty())
+ {
+ _c4dbgpf("saving val tag '{}'", t);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_val_tag.empty());
+ m_val_tag = t;
+ m_val_tag_indentation = tag_indentation;
+ }
+ else
+ {
+ _c4dbgpf("saving key tag '{}'", t);
+ if(m_key_tag.empty())
+ {
+ m_key_tag = t;
+ m_key_tag_indentation = tag_indentation;
+ }
+ else
+ {
+ /* handle this case:
+ * !!str foo: !!map
+ * !!int 1: !!float 20.0
+ * !!int 3: !!float 40.0
+ *
+ * (m_key_tag would be !!str and m_key_tag2 would be !!int)
+ */
+ m_key_tag2 = t;
+ m_key_tag2_indentation = tag_indentation;
+ }
+ }
+ }
+ else
+ {
+ _c4err("internal error");
+ }
+
+ if(m_val_tag.not_empty())
+ {
+ YamlTag_e tag = to_tag(t);
+ if(tag == TAG_STR)
+ {
+ _c4dbgpf("tag '{}' is a str-type tag", t);
+ if(has_all(RTOP|RUNK|NDOC))
+ {
+ _c4dbgpf("docval. slurping the string. pos={}", m_state->pos.offset);
+ csubstr scalar = _slurp_doc_scalar();
+ _c4dbgpf("docval. after slurp: {}, at node {}: '{}'", m_state->pos.offset, m_state->node_id, scalar);
+ m_tree->to_val(m_state->node_id, scalar, DOC);
+ _c4dbgpf("docval. val tag {} -> {}", m_val_tag, normalize_tag(m_val_tag));
+ m_tree->set_val_tag(m_state->node_id, normalize_tag(m_val_tag));
+ m_val_tag.clear();
+ if(!m_val_anchor.empty())
+ {
+ _c4dbgpf("setting val anchor[{}]='{}'", m_state->node_id, m_val_anchor);
+ m_tree->set_val_anchor(m_state->node_id, m_val_anchor);
+ m_val_anchor.clear();
+ }
+ _end_stream();
+ }
+ }
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+csubstr Parser::_slurp_doc_scalar()
+{
+ csubstr s = m_state->line_contents.rem;
+ size_t pos = m_state->pos.offset;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.full.find("---") != csubstr::npos);
+ _c4dbgpf("slurp 0 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset));
+ if(s.len == 0)
+ {
+ _line_ended();
+ _scan_line();
+ s = m_state->line_contents.rem;
+ pos = m_state->pos.offset;
+ }
+
+ size_t skipws = s.first_not_of(" \t");
+ _c4dbgpf("slurp 1 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset));
+ if(skipws != npos)
+ {
+ _line_progressed(skipws);
+ s = m_state->line_contents.rem;
+ pos = m_state->pos.offset;
+ _c4dbgpf("slurp 2 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset));
+ }
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_val_anchor.empty());
+ _handle_val_anchors_and_refs();
+ if(!m_val_anchor.empty())
+ {
+ s = m_state->line_contents.rem;
+ skipws = s.first_not_of(" \t");
+ if(skipws != npos)
+ {
+ _line_progressed(skipws);
+ }
+ s = m_state->line_contents.rem;
+ pos = m_state->pos.offset;
+ _c4dbgpf("slurp 3 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset));
+ }
+
+ if(s.begins_with('\''))
+ {
+ m_state->scalar_col = m_state->line_contents.current_col(s);
+ return _scan_squot_scalar();
+ }
+ else if(s.begins_with('"'))
+ {
+ m_state->scalar_col = m_state->line_contents.current_col(s);
+ return _scan_dquot_scalar();
+ }
+ else if(s.begins_with('|') || s.begins_with('>'))
+ {
+ return _scan_block();
+ }
+
+ _c4dbgpf("slurp 4 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset));
+
+ m_state->scalar_col = m_state->line_contents.current_col(s);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() >= m_buf.begin() + pos);
+ _line_progressed(static_cast<size_t>(s.end() - (m_buf.begin() + pos)));
+
+ _c4dbgpf("slurp 5 '{}'. REM='{}'", s, m_buf.sub(m_state->pos.offset));
+
+ if(_at_line_end())
+ {
+ _c4dbgpf("at line end. curr='{}'", s);
+ s = _extend_scanned_scalar(s);
+ }
+
+ _c4dbgpf("scalar was '{}'", s);
+
+ return s;
+}
+
+//-----------------------------------------------------------------------------
+bool Parser::_scan_scalar(csubstr *C4_RESTRICT scalar, bool *C4_RESTRICT quoted)
+{
+ csubstr s = m_state->line_contents.rem;
+ if(s.len == 0)
+ return false;
+ s = s.trim(" \t");
+ if(s.len == 0)
+ return false;
+
+ if(s.begins_with('\''))
+ {
+ _c4dbgp("got a ': scanning single-quoted scalar");
+ m_state->scalar_col = m_state->line_contents.current_col(s);
+ *scalar = _scan_squot_scalar();
+ *quoted = true;
+ return true;
+ }
+ else if(s.begins_with('"'))
+ {
+ _c4dbgp("got a \": scanning double-quoted scalar");
+ m_state->scalar_col = m_state->line_contents.current_col(s);
+ *scalar = _scan_dquot_scalar();
+ *quoted = true;
+ return true;
+ }
+ else if(s.begins_with('|') || s.begins_with('>'))
+ {
+ *scalar = _scan_block();
+ *quoted = false;
+ return true;
+ }
+ else if(has_any(RTOP) && _is_doc_sep(s))
+ {
+ return false;
+ }
+ else if(has_any(RSEQ))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_all(RKEY));
+ if(has_all(RVAL))
+ {
+ _c4dbgp("RSEQ|RVAL");
+ if( ! _is_scalar_next__rseq_rval(s))
+ return false;
+ _RYML_WITH_TAB_TOKENS(else if(s.begins_with("-\t"))
+ return false;
+ )
+ if(s.ends_with(':'))
+ {
+ --s.len;
+ }
+ else
+ {
+ auto first = s.first_of_any(": " _RYML_WITH_TAB_TOKENS( , ":\t"), " #");
+ if(first)
+ s.len = first.pos;
+ }
+ if(has_all(FLOW))
+ {
+ _c4dbgp("RSEQ|RVAL|EXPL");
+ s = s.left_of(s.first_of(",]"));
+ }
+ s = s.trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' '));
+ }
+ else
+ {
+ _c4err("internal error");
+ }
+ }
+ else if(has_any(RMAP))
+ {
+ if( ! _is_scalar_next__rmap(s))
+ return false;
+ size_t colon_space = s.find(": ");
+ if(colon_space == npos)
+ {
+ _RYML_WITH_OR_WITHOUT_TAB_TOKENS(
+ // with tab tokens
+ colon_space = s.find(":\t");
+ if(colon_space == npos)
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.len > 0);
+ colon_space = s.find(':');
+ if(colon_space != s.len-1)
+ colon_space = npos;
+ }
+ ,
+ // without tab tokens
+ colon_space = s.find(':');
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.len > 0);
+ if(colon_space != s.len-1)
+ colon_space = npos;
+ )
+ }
+
+ if(has_all(RKEY))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, !s.begins_with(' '));
+ if(has_any(QMRK))
+ {
+ _c4dbgp("RMAP|RKEY|CPLX");
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_any(RMAP));
+ if(s.begins_with("? ") || s == '?')
+ return false;
+ s = s.left_of(colon_space);
+ s = s.left_of(s.first_of("#"));
+ if(has_any(FLOW))
+ s = s.left_of(s.first_of(':'));
+ s = s.trimr(" \t");
+ if(s.begins_with("---"))
+ return false;
+ else if(s.begins_with("..."))
+ return false;
+ }
+ else
+ {
+ _c4dbgp("RMAP|RKEY");
+ _RYML_CB_CHECK(m_stack.m_callbacks, !s.begins_with('{'));
+ if(s.begins_with("? ") || s == '?')
+ return false;
+ s = s.left_of(colon_space);
+ s = s.trimr(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' '));
+ if(has_any(FLOW))
+ {
+ _c4dbgpf("RMAP|RKEY|EXPL: '{}'", s);
+ s = s.left_of(s.first_of(",}"));
+ if(s.ends_with(':'))
+ s = s.offs(0, 1);
+ }
+ else if(s.begins_with("---"))
+ {
+ return false;
+ }
+ else if(s.begins_with("..."))
+ {
+ return false;
+ }
+ }
+ }
+ else if(has_all(RVAL))
+ {
+ _c4dbgp("RMAP|RVAL");
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(QMRK));
+ if( ! _is_scalar_next__rmap_val(s))
+ return false;
+ _RYML_WITH_TAB_TOKENS(else if(s.begins_with("-\t"))
+ return false;
+ )
+ s = s.left_of(s.find(" #")); // is there a comment?
+ s = s.left_of(s.find("\t#")); // is there a comment?
+ if(has_any(FLOW))
+ {
+ _c4dbgp("RMAP|RVAL|EXPL");
+ if(has_none(RSEQIMAP))
+ s = s.left_of(s.first_of(",}"));
+ else
+ s = s.left_of(s.first_of(",]"));
+ }
+ s = s.trim(_RYML_WITH_OR_WITHOUT_TAB_TOKENS(" \t", ' '));
+ if(s.begins_with("---"))
+ return false;
+ else if(s.begins_with("..."))
+ return false;
+ }
+ else
+ {
+ _c4err("parse error");
+ }
+ }
+ else if(has_all(RUNK))
+ {
+ _c4dbgpf("RUNK '[{}]~~~{}~~~", s.len, s);
+ if( ! _is_scalar_next__runk(s))
+ {
+ _c4dbgp("RUNK: no scalar next");
+ return false;
+ }
+ s = s.left_of(s.find(" #"));
+ size_t pos = s.find(": ");
+ if(pos != npos)
+ s = s.left_of(pos);
+ else if(s.ends_with(':'))
+ s = s.left_of(s.len-1);
+ _RYML_WITH_TAB_TOKENS(
+ else if((pos = s.find(":\t")) != npos) // TABS
+ s = s.left_of(pos);
+ )
+ else
+ s = s.left_of(s.first_of(','));
+ s = s.trim(" \t");
+ _c4dbgpf("RUNK: scalar='{}'", s);
+ }
+ else
+ {
+ _c4err("not implemented");
+ }
+
+ if(s.empty())
+ return false;
+
+ m_state->scalar_col = m_state->line_contents.current_col(s);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.str >= m_state->line_contents.rem.str);
+ _line_progressed(static_cast<size_t>(s.str - m_state->line_contents.rem.str) + s.len);
+
+ if(_at_line_end() && s != '~')
+ {
+ _c4dbgpf("at line end. curr='{}'", s);
+ s = _extend_scanned_scalar(s);
+ }
+
+ _c4dbgpf("scalar was '{}'", s);
+
+ *scalar = s;
+ *quoted = false;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+
+csubstr Parser::_extend_scanned_scalar(csubstr s)
+{
+ if(has_all(RMAP|RKEY|QMRK))
+ {
+ size_t scalar_indentation = has_any(FLOW) ? 0 : m_state->scalar_col;
+ _c4dbgpf("extend_scalar: explicit key! indref={} scalar_indentation={} scalar_col={}", m_state->indref, scalar_indentation, m_state->scalar_col);
+ csubstr n = _scan_to_next_nonempty_line(scalar_indentation);
+ if(!n.empty())
+ {
+ substr full = _scan_complex_key(s, n).trimr(" \t\r\n");
+ if(full != s)
+ s = _filter_plain_scalar(full, scalar_indentation);
+ }
+ }
+ // deal with plain (unquoted) scalars that continue to the next line
+ else if(!s.begins_with_any("*")) // cannot be a plain scalar if it starts with * (that's an anchor reference)
+ {
+ _c4dbgpf("extend_scalar: line ended, scalar='{}'", s);
+ if(has_none(FLOW))
+ {
+ size_t scalar_indentation = m_state->indref + 1;
+ if(has_all(RUNK) && scalar_indentation == 1)
+ scalar_indentation = 0;
+ csubstr n = _scan_to_next_nonempty_line(scalar_indentation);
+ if(!n.empty())
+ {
+ _c4dbgpf("rscalar[IMPL]: state_indref={} state_indentation={} scalar_indentation={}", m_state->indref, m_state->line_contents.indentation, scalar_indentation);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.full.is_super(n));
+ substr full = _scan_plain_scalar_blck(s, n, scalar_indentation);
+ if(full.len >= s.len)
+ s = _filter_plain_scalar(full, scalar_indentation);
+ }
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(FLOW));
+ csubstr n = _scan_to_next_nonempty_line(/*indentation*/0);
+ if(!n.empty())
+ {
+ _c4dbgp("rscalar[FLOW]");
+ substr full = _scan_plain_scalar_flow(s, n);
+ s = _filter_plain_scalar(full, /*indentation*/0);
+ }
+ }
+ }
+
+ return s;
+}
+
+
+//-----------------------------------------------------------------------------
+
+substr Parser::_scan_plain_scalar_flow(csubstr currscalar, csubstr peeked_line)
+{
+ static constexpr const csubstr chars = "[]{}?#,";
+ size_t pos = peeked_line.first_of(chars);
+ bool first = true;
+ while(pos != 0)
+ {
+ if(has_all(RMAP|RKEY) || has_any(RUNK))
+ {
+ csubstr tpkl = peeked_line.triml(' ').trimr("\r\n");
+ if(tpkl.begins_with(": ") || tpkl == ':')
+ {
+ _c4dbgpf("rscalar[EXPL]: map value starts on the peeked line: '{}'", peeked_line);
+ peeked_line = peeked_line.first(0);
+ break;
+ }
+ else
+ {
+ auto colon_pos = peeked_line.first_of_any(": ", ":");
+ if(colon_pos && colon_pos.pos < pos)
+ {
+ peeked_line = peeked_line.first(colon_pos.pos);
+ _c4dbgpf("rscalar[EXPL]: found colon at {}. peeked='{}'", colon_pos.pos, peeked_line);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, peeked_line.end() >= m_state->line_contents.rem.begin());
+ _line_progressed(static_cast<size_t>(peeked_line.end() - m_state->line_contents.rem.begin()));
+ break;
+ }
+ }
+ }
+ if(pos != npos)
+ {
+ _c4dbgpf("rscalar[EXPL]: found special character '{}' at {}, stopping: '{}'", peeked_line[pos], pos, peeked_line.left_of(pos).trimr("\r\n"));
+ peeked_line = peeked_line.left_of(pos);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, peeked_line.end() >= m_state->line_contents.rem.begin());
+ _line_progressed(static_cast<size_t>(peeked_line.end() - m_state->line_contents.rem.begin()));
+ break;
+ }
+ _c4dbgpf("rscalar[EXPL]: append another line, full: '{}'", peeked_line.trimr("\r\n"));
+ if(!first)
+ {
+ RYML_CHECK(_advance_to_peeked());
+ }
+ peeked_line = _scan_to_next_nonempty_line(/*indentation*/0);
+ if(peeked_line.empty())
+ {
+ _c4err("expected token or continuation");
+ }
+ pos = peeked_line.first_of(chars);
+ first = false;
+ }
+ substr full(m_buf.str + (currscalar.str - m_buf.str), m_buf.begin() + m_state->pos.offset);
+ full = full.trimr("\n\r ");
+ return full;
+}
+
+
+//-----------------------------------------------------------------------------
+
+substr Parser::_scan_plain_scalar_blck(csubstr currscalar, csubstr peeked_line, size_t indentation)
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.is_super(currscalar));
+ // NOTE. there's a problem with _scan_to_next_nonempty_line(), as it counts newlines twice
+ // size_t offs = m_state->pos.offset; // so we workaround by directly counting from the end of the given scalar
+ _RYML_CB_ASSERT(m_stack.m_callbacks, currscalar.end() >= m_buf.begin());
+ size_t offs = static_cast<size_t>(currscalar.end() - m_buf.begin());
+ _RYML_CB_ASSERT(m_stack.m_callbacks, peeked_line.begins_with(' ', indentation));
+ while(true)
+ {
+ _c4dbgpf("rscalar[IMPL]: continuing... ref_indentation={}", indentation);
+ if(peeked_line.begins_with("...") || peeked_line.begins_with("---"))
+ {
+ _c4dbgpf("rscalar[IMPL]: document termination next -- bail now '{}'", peeked_line.trimr("\r\n"));
+ break;
+ }
+ else if(( ! peeked_line.begins_with(' ', indentation))) // is the line deindented?
+ {
+ if(!peeked_line.trim(" \r\n\t").empty()) // is the line not blank?
+ {
+ _c4dbgpf("rscalar[IMPL]: deindented line, not blank -- bail now '{}'", peeked_line.trimr("\r\n"));
+ break;
+ }
+ _c4dbgpf("rscalar[IMPL]: line is blank and has less indentation: ref={} line={}: '{}'", indentation, peeked_line.first_not_of(' ') == csubstr::npos ? 0 : peeked_line.first_not_of(' '), peeked_line.trimr("\r\n"));
+ _c4dbgpf("rscalar[IMPL]: ... searching for a line starting at indentation {}", indentation);
+ csubstr next_peeked = _scan_to_next_nonempty_line(indentation);
+ if(next_peeked.empty())
+ {
+ _c4dbgp("rscalar[IMPL]: ... finished.");
+ break;
+ }
+ _c4dbgp("rscalar[IMPL]: ... continuing.");
+ peeked_line = next_peeked;
+ }
+
+ _c4dbgpf("rscalar[IMPL]: line contents: '{}'", peeked_line.right_of(indentation, true).trimr("\r\n"));
+ size_t token_pos;
+ if(peeked_line.find(": ") != npos)
+ {
+ _line_progressed(peeked_line.find(": "));
+ _c4err("': ' is not a valid token in plain flow (unquoted) scalars");
+ }
+ else if(peeked_line.ends_with(':'))
+ {
+ _line_progressed(peeked_line.find(':'));
+ _c4err("lines cannot end with ':' in plain flow (unquoted) scalars");
+ }
+ else if((token_pos = peeked_line.find(" #")) != npos)
+ {
+ _line_progressed(token_pos);
+ break;
+ //_c4err("' #' is not a valid token in plain flow (unquoted) scalars");
+ }
+
+ _c4dbgpf("rscalar[IMPL]: append another line: (len={})'{}'", peeked_line.len, peeked_line.trimr("\r\n"));
+ if(!_advance_to_peeked())
+ {
+ _c4dbgp("rscalar[IMPL]: file finishes after the scalar");
+ break;
+ }
+ peeked_line = m_state->line_contents.rem;
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.offset >= offs);
+ substr full(m_buf.str + (currscalar.str - m_buf.str),
+ currscalar.len + (m_state->pos.offset - offs));
+ full = full.trimr("\r\n ");
+ return full;
+}
+
+substr Parser::_scan_complex_key(csubstr currscalar, csubstr peeked_line)
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.is_super(currscalar));
+ // NOTE. there's a problem with _scan_to_next_nonempty_line(), as it counts newlines twice
+ // size_t offs = m_state->pos.offset; // so we workaround by directly counting from the end of the given scalar
+ _RYML_CB_ASSERT(m_stack.m_callbacks, currscalar.end() >= m_buf.begin());
+ size_t offs = static_cast<size_t>(currscalar.end() - m_buf.begin());
+ while(true)
+ {
+ _c4dbgp("rcplxkey: continuing...");
+ if(peeked_line.begins_with("...") || peeked_line.begins_with("---"))
+ {
+ _c4dbgpf("rcplxkey: document termination next -- bail now '{}'", peeked_line.trimr("\r\n"));
+ break;
+ }
+ else
+ {
+ size_t pos = peeked_line.first_of("?:[]{}");
+ if(pos == csubstr::npos)
+ {
+ pos = peeked_line.find("- ");
+ }
+ if(pos != csubstr::npos)
+ {
+ _c4dbgpf("rcplxkey: found special characters at pos={}: '{}'", pos, peeked_line.trimr("\r\n"));
+ _line_progressed(pos);
+ break;
+ }
+ }
+
+ _c4dbgpf("rcplxkey: no special chars found '{}'", peeked_line.trimr("\r\n"));
+ csubstr next_peeked = _scan_to_next_nonempty_line(0);
+ if(next_peeked.empty())
+ {
+ _c4dbgp("rcplxkey: empty ... finished.");
+ break;
+ }
+ _c4dbgp("rcplxkey: ... continuing.");
+ peeked_line = next_peeked;
+
+ _c4dbgpf("rcplxkey: line contents: '{}'", peeked_line.trimr("\r\n"));
+ size_t colpos;
+ if((colpos = peeked_line.find(": ")) != npos)
+ {
+ _c4dbgp("rcplxkey: found ': ', stopping.");
+ _line_progressed(colpos);
+ break;
+ }
+ #ifdef RYML_NO_COVERAGE__TO_BE_DELETED
+ else if((colpos = peeked_line.ends_with(':')))
+ {
+ _c4dbgp("rcplxkey: ends with ':', stopping.");
+ _line_progressed(colpos);
+ break;
+ }
+ #endif
+ _c4dbgpf("rcplxkey: append another line: (len={})'{}'", peeked_line.len, peeked_line.trimr("\r\n"));
+ if(!_advance_to_peeked())
+ {
+ _c4dbgp("rcplxkey: file finishes after the scalar");
+ break;
+ }
+ peeked_line = m_state->line_contents.rem;
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.offset >= offs);
+ substr full(m_buf.str + (currscalar.str - m_buf.str),
+ currscalar.len + (m_state->pos.offset - offs));
+ return full;
+}
+
+//! scans to the next non-blank line starting with the given indentation
+csubstr Parser::_scan_to_next_nonempty_line(size_t indentation)
+{
+ csubstr next_peeked;
+ while(true)
+ {
+ _c4dbgpf("rscalar: ... curr offset: {} indentation={}", m_state->pos.offset, indentation);
+ next_peeked = _peek_next_line(m_state->pos.offset);
+ csubstr next_peeked_triml = next_peeked.triml(' ');
+ _c4dbgpf("rscalar: ... next peeked line='{}'", next_peeked.trimr("\r\n"));
+ if(next_peeked_triml.begins_with('#'))
+ {
+ _c4dbgp("rscalar: ... first non-space character is #");
+ return {};
+ }
+ else if(next_peeked.begins_with(' ', indentation))
+ {
+ _c4dbgpf("rscalar: ... begins at same indentation {}, assuming continuation", indentation);
+ _advance_to_peeked();
+ return next_peeked;
+ }
+ else // check for de-indentation
+ {
+ csubstr trimmed = next_peeked_triml.trimr("\t\r\n");
+ _c4dbgpf("rscalar: ... deindented! trimmed='{}'", trimmed);
+ if(!trimmed.empty())
+ {
+ _c4dbgp("rscalar: ... and not empty. bailing out.");
+ return {};
+ }
+ }
+ if(!_advance_to_peeked())
+ {
+ _c4dbgp("rscalar: file finished");
+ return {};
+ }
+ }
+ return {};
+}
+
+// returns false when the file finished
+bool Parser::_advance_to_peeked()
+{
+ _line_progressed(m_state->line_contents.rem.len);
+ _line_ended(); // advances to the peeked-at line, consuming all remaining (probably newline) characters on the current line
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.rem.first_of("\r\n") == csubstr::npos);
+ _c4dbgpf("advance to peeked: scan more... pos={} len={}", m_state->pos.offset, m_buf.len);
+ _scan_line(); // puts the peeked-at line in the buffer
+ if(_finished_file())
+ {
+ _c4dbgp("rscalar: finished file!");
+ return false;
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+
+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
+csubstr from_next_line(csubstr 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;
+}
+
+csubstr Parser::_peek_next_line(size_t pos) const
+{
+ csubstr rem{}; // declare here because of the goto
+ size_t nlpos{}; // declare here because of the goto
+ pos = pos == npos ? m_state->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 {};
+}
+
+
+//-----------------------------------------------------------------------------
+void Parser::LineContents::reset_with_next_line(csubstr buf, size_t offset)
+{
+ RYML_ASSERT(offset <= buf.len);
+ char const* C4_RESTRICT b = &buf[offset];
+ char const* C4_RESTRICT e = b;
+ // get the current line stripped of newline chars
+ while(e < buf.end() && (*e != '\n' && *e != '\r'))
+ ++e;
+ RYML_ASSERT(e >= b);
+ const csubstr stripped_ = buf.sub(offset, static_cast<size_t>(e - b));
+ // advance pos to include the first line ending
+ if(e != buf.end() && *e == '\r')
+ ++e;
+ if(e != buf.end() && *e == '\n')
+ ++e;
+ RYML_ASSERT(e >= b);
+ const csubstr full_ = buf.sub(offset, static_cast<size_t>(e - b));
+ reset(full_, stripped_);
+}
+
+void Parser::_scan_line()
+{
+ if(m_state->pos.offset >= m_buf.len)
+ {
+ m_state->line_contents.reset(m_buf.last(0), m_buf.last(0));
+ return;
+ }
+ m_state->line_contents.reset_with_next_line(m_buf, m_state->pos.offset);
+}
+
+
+//-----------------------------------------------------------------------------
+void Parser::_line_progressed(size_t ahead)
+{
+ _c4dbgpf("line[{}] ({} cols) progressed by {}: col {}-->{} offset {}-->{}", m_state->pos.line, m_state->line_contents.full.len, ahead, m_state->pos.col, m_state->pos.col+ahead, m_state->pos.offset, m_state->pos.offset+ahead);
+ m_state->pos.offset += ahead;
+ m_state->pos.col += ahead;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.col <= m_state->line_contents.stripped.len+1);
+ m_state->line_contents.rem = m_state->line_contents.rem.sub(ahead);
+}
+
+void Parser::_line_ended()
+{
+ _c4dbgpf("line[{}] ({} cols) ended! offset {}-->{}", m_state->pos.line, m_state->line_contents.full.len, m_state->pos.offset, m_state->pos.offset+m_state->line_contents.full.len - m_state->line_contents.stripped.len);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.col == m_state->line_contents.stripped.len+1);
+ m_state->pos.offset += m_state->line_contents.full.len - m_state->line_contents.stripped.len;
+ ++m_state->pos.line;
+ m_state->pos.col = 1;
+}
+
+void Parser::_line_ended_undo()
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.col == 1u);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.line > 0u);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.offset >= m_state->line_contents.full.len - m_state->line_contents.stripped.len);
+ _c4dbgpf("line[{}] undo ended! line {}-->{}, offset {}-->{}", m_state->pos.line, m_state->pos.line, m_state->pos.line - 1, m_state->pos.offset, m_state->pos.offset - (m_state->line_contents.full.len - m_state->line_contents.stripped.len));
+ m_state->pos.offset -= m_state->line_contents.full.len - m_state->line_contents.stripped.len;
+ --m_state->pos.line;
+ m_state->pos.col = m_state->line_contents.stripped.len + 1u;
+}
+
+//-----------------------------------------------------------------------------
+void Parser::_set_indentation(size_t indentation)
+{
+ m_state->indref = indentation;
+ _c4dbgpf("state[{}]: saving indentation: {}", m_state-m_stack.begin(), m_state->indref);
+}
+
+void Parser::_save_indentation(size_t behind)
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->line_contents.rem.begin() >= m_state->line_contents.full.begin());
+ m_state->indref = static_cast<size_t>(m_state->line_contents.rem.begin() - m_state->line_contents.full.begin());
+ _RYML_CB_ASSERT(m_stack.m_callbacks, behind <= m_state->indref);
+ m_state->indref -= behind;
+ _c4dbgpf("state[{}]: saving indentation: {}", m_state-m_stack.begin(), m_state->indref);
+}
+
+bool Parser::_maybe_set_indentation_from_anchor_or_tag()
+{
+ if(m_key_anchor.not_empty())
+ {
+ _c4dbgpf("set indentation from key anchor: {}", m_key_anchor_indentation);
+ _set_indentation(m_key_anchor_indentation); // this is the column where the anchor starts
+ return true;
+ }
+ else if(m_key_tag.not_empty())
+ {
+ _c4dbgpf("set indentation from key tag: {}", m_key_tag_indentation);
+ _set_indentation(m_key_tag_indentation); // this is the column where the tag starts
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+void Parser::_write_key_anchor(size_t node_id)
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->has_key(node_id));
+ if( ! m_key_anchor.empty())
+ {
+ _c4dbgpf("node={}: set key anchor to '{}'", node_id, m_key_anchor);
+ m_tree->set_key_anchor(node_id, m_key_anchor);
+ m_key_anchor.clear();
+ m_key_anchor_was_before = false;
+ m_key_anchor_indentation = 0;
+ }
+ else if( ! m_tree->is_key_quoted(node_id))
+ {
+ csubstr r = m_tree->key(node_id);
+ if(r.begins_with('*'))
+ {
+ _c4dbgpf("node={}: set key reference: '{}'", node_id, r);
+ m_tree->set_key_ref(node_id, r.sub(1));
+ }
+ else if(r == "<<")
+ {
+ m_tree->set_key_ref(node_id, r);
+ _c4dbgpf("node={}: it's an inheriting reference", node_id);
+ if(m_tree->is_seq(node_id))
+ {
+ _c4dbgpf("node={}: inheriting from seq of {}", node_id, m_tree->num_children(node_id));
+ for(size_t i = m_tree->first_child(node_id); i != NONE; i = m_tree->next_sibling(i))
+ {
+ if( ! (m_tree->val(i).begins_with('*')))
+ _c4err("malformed reference: '{}'", m_tree->val(i));
+ }
+ }
+ else if( ! m_tree->val(node_id).begins_with('*'))
+ {
+ _c4err("malformed reference: '{}'", m_tree->val(node_id));
+ }
+ //m_tree->set_key_ref(node_id, r);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void Parser::_write_val_anchor(size_t node_id)
+{
+ if( ! m_val_anchor.empty())
+ {
+ _c4dbgpf("node={}: set val anchor to '{}'", node_id, m_val_anchor);
+ m_tree->set_val_anchor(node_id, m_val_anchor);
+ m_val_anchor.clear();
+ }
+ csubstr r = m_tree->has_val(node_id) ? m_tree->val(node_id) : "";
+ if(!m_tree->is_val_quoted(node_id) && r.begins_with('*'))
+ {
+ _c4dbgpf("node={}: set val reference: '{}'", node_id, r);
+ RYML_CHECK(!m_tree->has_val_anchor(node_id));
+ m_tree->set_val_ref(node_id, r.sub(1));
+ }
+}
+
+//-----------------------------------------------------------------------------
+void Parser::_push_level(bool explicit_flow_chars)
+{
+ _c4dbgpf("pushing level! currnode={} currlevel={} stacksize={} stackcap={}", m_state->node_id, m_state->level, m_stack.size(), m_stack.capacity());
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state == &m_stack.top());
+ if(node(m_state) == nullptr)
+ {
+ _c4dbgp("pushing level! actually no, current node is null");
+ //_RYML_CB_ASSERT(m_stack.m_callbacks, ! explicit_flow_chars);
+ return;
+ }
+ flag_t st = RUNK;
+ if(explicit_flow_chars || has_all(FLOW))
+ {
+ st |= FLOW;
+ }
+ m_stack.push_top();
+ m_state = &m_stack.top();
+ set_flags(st);
+ m_state->node_id = (size_t)NONE;
+ m_state->indref = (size_t)NONE;
+ ++m_state->level;
+ _c4dbgpf("pushing level: now, currlevel={}", m_state->level);
+}
+
+void Parser::_pop_level()
+{
+ _c4dbgpf("popping level! currnode={} currlevel={}", m_state->node_id, m_state->level);
+ if(has_any(RMAP) || m_tree->is_map(m_state->node_id))
+ {
+ _stop_map();
+ }
+ if(has_any(RSEQ) || m_tree->is_seq(m_state->node_id))
+ {
+ _stop_seq();
+ }
+ if(m_tree->is_doc(m_state->node_id))
+ {
+ _stop_doc();
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_stack.size() > 1);
+ _prepare_pop();
+ m_stack.pop();
+ m_state = &m_stack.top();
+ /*if(has_any(RMAP))
+ {
+ _toggle_key_val();
+ }*/
+ if(m_state->line_contents.indentation == 0)
+ {
+ //_RYML_CB_ASSERT(m_stack.m_callbacks, has_none(RTOP));
+ add_flags(RTOP);
+ }
+ _c4dbgpf("popping level: now, currnode={} currlevel={}", m_state->node_id, m_state->level);
+}
+
+//-----------------------------------------------------------------------------
+void Parser::_start_unk(bool /*as_child*/)
+{
+ _c4dbgp("start_unk");
+ _push_level();
+ _move_scalar_from_top();
+}
+
+//-----------------------------------------------------------------------------
+void Parser::_start_doc(bool as_child)
+{
+ _c4dbgpf("start_doc (as child={})", as_child);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_stack.bottom()) == node(m_root_id));
+ size_t parent_id = m_stack.size() < 2 ? m_root_id : m_stack.top(1).node_id;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, parent_id != NONE);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_root(parent_id));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_state) == nullptr || node(m_state) == node(m_root_id));
+ if(as_child)
+ {
+ _c4dbgpf("start_doc: parent={}", parent_id);
+ if( ! m_tree->is_stream(parent_id))
+ {
+ _c4dbgp("start_doc: rearranging with root as STREAM");
+ m_tree->set_root_as_stream();
+ }
+ m_state->node_id = m_tree->append_child(parent_id);
+ m_tree->to_doc(m_state->node_id);
+ }
+ #ifdef RYML_NO_COVERAGE__TO_BE_DELETED
+ else
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_seq(parent_id) || m_tree->empty(parent_id));
+ m_state->node_id = parent_id;
+ if( ! m_tree->is_doc(parent_id))
+ {
+ m_tree->to_doc(parent_id, DOC);
+ }
+ }
+ #endif
+ _c4dbgpf("start_doc: id={}", m_state->node_id);
+ add_flags(RUNK|RTOP|NDOC);
+ _handle_types();
+ rem_flags(NDOC);
+}
+
+void Parser::_stop_doc()
+{
+ size_t doc_node = m_state->node_id;
+ _c4dbgpf("stop_doc[{}]", doc_node);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_doc(doc_node));
+ if(!m_tree->is_seq(doc_node) && !m_tree->is_map(doc_node) && !m_tree->is_val(doc_node))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(SSCL));
+ _c4dbgpf("stop_doc[{}]: there was nothing; adding null val", doc_node);
+ m_tree->to_val(doc_node, {}, DOC);
+ }
+}
+
+void Parser::_end_stream()
+{
+ _c4dbgpf("end_stream, level={} node_id={}", m_state->level, m_state->node_id);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ! m_stack.empty());
+ NodeData *added = nullptr;
+ if(has_any(SSCL))
+ {
+ if(m_tree->is_seq(m_state->node_id))
+ {
+ _c4dbgp("append val...");
+ added = _append_val(_consume_scalar());
+ }
+ else if(m_tree->is_map(m_state->node_id))
+ {
+ _c4dbgp("append null key val...");
+ added = _append_key_val_null(m_state->line_contents.rem.str);
+ #ifdef RYML_NO_COVERAGE__TO_BE_DELETED
+ if(has_any(RSEQIMAP))
+ {
+ _stop_seqimap();
+ _pop_level();
+ }
+ #endif
+ }
+ else if(m_tree->is_doc(m_state->node_id) || m_tree->type(m_state->node_id) == NOTYPE)
+ {
+ NodeType_e quoted = has_any(QSCL) ? VALQUO : NOTYPE; // do this before consuming the scalar
+ csubstr scalar = _consume_scalar();
+ _c4dbgpf("node[{}]: to docval '{}'{}", m_state->node_id, scalar, quoted == VALQUO ? ", quoted" : "");
+ m_tree->to_val(m_state->node_id, scalar, DOC|quoted);
+ added = m_tree->get(m_state->node_id);
+ }
+ else
+ {
+ _c4err("internal error");
+ }
+ }
+ else if(has_all(RSEQ|RVAL) && has_none(FLOW))
+ {
+ _c4dbgp("add last...");
+ added = _append_val_null(m_state->line_contents.rem.str);
+ }
+ else if(!m_val_tag.empty() && (m_tree->is_doc(m_state->node_id) || m_tree->type(m_state->node_id) == NOTYPE))
+ {
+ csubstr scalar = m_state->line_contents.rem.first(0);
+ _c4dbgpf("node[{}]: add null scalar as docval", m_state->node_id);
+ m_tree->to_val(m_state->node_id, scalar, DOC);
+ added = m_tree->get(m_state->node_id);
+ }
+
+ if(added)
+ {
+ size_t added_id = m_tree->id(added);
+ if(m_tree->is_seq(m_state->node_id) || m_tree->is_doc(m_state->node_id))
+ {
+ if(!m_key_anchor.empty())
+ {
+ _c4dbgpf("node[{}]: move key to val anchor: '{}'", added_id, m_key_anchor);
+ m_val_anchor = m_key_anchor;
+ m_key_anchor = {};
+ }
+ if(!m_key_tag.empty())
+ {
+ _c4dbgpf("node[{}]: move key to val tag: '{}'", added_id, m_key_tag);
+ m_val_tag = m_key_tag;
+ m_key_tag = {};
+ }
+ }
+ #ifdef RYML_NO_COVERAGE__TO_BE_DELETED
+ if(!m_key_anchor.empty())
+ {
+ _c4dbgpf("node[{}]: set key anchor='{}'", added_id, m_key_anchor);
+ m_tree->set_key_anchor(added_id, m_key_anchor);
+ m_key_anchor = {};
+ }
+ #endif
+ if(!m_val_anchor.empty())
+ {
+ _c4dbgpf("node[{}]: set val anchor='{}'", added_id, m_val_anchor);
+ m_tree->set_val_anchor(added_id, m_val_anchor);
+ m_val_anchor = {};
+ }
+ #ifdef RYML_NO_COVERAGE__TO_BE_DELETED
+ if(!m_key_tag.empty())
+ {
+ _c4dbgpf("node[{}]: set key tag='{}' -> '{}'", added_id, m_key_tag, normalize_tag(m_key_tag));
+ m_tree->set_key_tag(added_id, normalize_tag(m_key_tag));
+ m_key_tag = {};
+ }
+ #endif
+ if(!m_val_tag.empty())
+ {
+ _c4dbgpf("node[{}]: set val tag='{}' -> '{}'", added_id, m_val_tag, normalize_tag(m_val_tag));
+ m_tree->set_val_tag(added_id, normalize_tag(m_val_tag));
+ m_val_tag = {};
+ }
+ }
+
+ while(m_stack.size() > 1)
+ {
+ _c4dbgpf("popping level: {} (stack sz={})", m_state->level, m_stack.size());
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_any(SSCL, &m_stack.top()));
+ if(has_all(RSEQ|FLOW))
+ _err("closing ] not found");
+ _pop_level();
+ }
+ add_flags(NDOC);
+}
+
+void Parser::_start_new_doc(csubstr rem)
+{
+ _c4dbgp("_start_new_doc");
+ _RYML_CB_ASSERT(m_stack.m_callbacks, rem.begins_with("---"));
+ C4_UNUSED(rem);
+
+ _end_stream();
+
+ size_t indref = m_state->indref;
+ _c4dbgpf("start a document, indentation={}", indref);
+ _line_progressed(3);
+ _push_level();
+ _start_doc();
+ _set_indentation(indref);
+}
+
+
+//-----------------------------------------------------------------------------
+void Parser::_start_map(bool as_child)
+{
+ _c4dbgpf("start_map (as child={})", as_child);
+ addrem_flags(RMAP|RVAL, RKEY|RUNK);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_stack.bottom()) == node(m_root_id));
+ size_t parent_id = m_stack.size() < 2 ? m_root_id : m_stack.top(1).node_id;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, parent_id != NONE);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_state) == nullptr || node(m_state) == node(m_root_id));
+ if(as_child)
+ {
+ m_state->node_id = m_tree->append_child(parent_id);
+ if(has_all(SSCL))
+ {
+ type_bits key_quoted = NOTYPE;
+ if(m_state->flags & QSCL) // before consuming the scalar
+ key_quoted |= KEYQUO;
+ csubstr key = _consume_scalar();
+ m_tree->to_map(m_state->node_id, key, key_quoted);
+ _c4dbgpf("start_map: id={} key='{}'", m_state->node_id, m_tree->key(m_state->node_id));
+ _write_key_anchor(m_state->node_id);
+ if( ! m_key_tag.empty())
+ {
+ _c4dbgpf("node[{}]: set key tag='{}' -> '{}'", m_state->node_id, m_key_tag, normalize_tag(m_key_tag));
+ m_tree->set_key_tag(m_state->node_id, normalize_tag(m_key_tag));
+ m_key_tag.clear();
+ }
+ }
+ else
+ {
+ m_tree->to_map(m_state->node_id);
+ _c4dbgpf("start_map: id={}", m_state->node_id);
+ }
+ m_tree->_p(m_state->node_id)->m_val.scalar.str = m_state->line_contents.rem.str;
+ _write_val_anchor(m_state->node_id);
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, parent_id != NONE);
+ m_state->node_id = parent_id;
+ _c4dbgpf("start_map: id={}", m_state->node_id);
+ type_bits as_doc = 0;
+ if(m_tree->is_doc(m_state->node_id))
+ as_doc |= DOC;
+ if(!m_tree->is_map(parent_id))
+ {
+ RYML_CHECK(!m_tree->has_children(parent_id));
+ m_tree->to_map(parent_id, as_doc);
+ }
+ else
+ {
+ m_tree->_add_flags(parent_id, as_doc);
+ }
+ _move_scalar_from_top();
+ if(m_key_anchor.not_empty())
+ m_key_anchor_was_before = true;
+ _write_val_anchor(parent_id);
+ if(m_stack.size() >= 2)
+ {
+ State const& parent_state = m_stack.top(1);
+ if(parent_state.flags & RSET)
+ add_flags(RSET);
+ }
+ m_tree->_p(parent_id)->m_val.scalar.str = m_state->line_contents.rem.str;
+ }
+ if( ! m_val_tag.empty())
+ {
+ _c4dbgpf("node[{}]: set val tag='{}' -> '{}'", m_state->node_id, m_val_tag, normalize_tag(m_val_tag));
+ m_tree->set_val_tag(m_state->node_id, normalize_tag(m_val_tag));
+ m_val_tag.clear();
+ }
+}
+
+void Parser::_start_map_unk(bool as_child)
+{
+ if(!m_key_anchor_was_before)
+ {
+ _c4dbgpf("stash key anchor before starting map... '{}'", m_key_anchor);
+ csubstr ka = m_key_anchor;
+ m_key_anchor = {};
+ _start_map(as_child);
+ m_key_anchor = ka;
+ }
+ else
+ {
+ _start_map(as_child);
+ m_key_anchor_was_before = false;
+ }
+ if(m_key_tag2.not_empty())
+ {
+ m_key_tag = m_key_tag2;
+ m_key_tag_indentation = m_key_tag2_indentation;
+ m_key_tag2.clear();
+ m_key_tag2_indentation = 0;
+ }
+}
+
+void Parser::_stop_map()
+{
+ _c4dbgpf("stop_map[{}]", m_state->node_id);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_map(m_state->node_id));
+ if(has_all(QMRK|RKEY) && !has_all(SSCL))
+ {
+ _c4dbgpf("stop_map[{}]: RKEY", m_state->node_id);
+ _store_scalar_null(m_state->line_contents.rem.str);
+ _append_key_val_null(m_state->line_contents.rem.str);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void Parser::_start_seq(bool as_child)
+{
+ _c4dbgpf("start_seq (as child={})", as_child);
+ if(has_all(RTOP|RUNK))
+ {
+ _c4dbgpf("start_seq: moving key tag to val tag: '{}'", m_key_tag);
+ m_val_tag = m_key_tag;
+ m_key_tag.clear();
+ }
+ addrem_flags(RSEQ|RVAL, RUNK);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_stack.bottom()) == node(m_root_id));
+ size_t parent_id = m_stack.size() < 2 ? m_root_id : m_stack.top(1).node_id;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, parent_id != NONE);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_state) == nullptr || node(m_state) == node(m_root_id));
+ if(as_child)
+ {
+ m_state->node_id = m_tree->append_child(parent_id);
+ if(has_all(SSCL))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_map(parent_id));
+ type_bits key_quoted = 0;
+ if(m_state->flags & QSCL) // before consuming the scalar
+ key_quoted |= KEYQUO;
+ csubstr key = _consume_scalar();
+ m_tree->to_seq(m_state->node_id, key, key_quoted);
+ _c4dbgpf("start_seq: id={} name='{}'", m_state->node_id, m_tree->key(m_state->node_id));
+ _write_key_anchor(m_state->node_id);
+ if( ! m_key_tag.empty())
+ {
+ _c4dbgpf("start_seq[{}]: set key tag='{}' -> '{}'", m_state->node_id, m_key_tag, normalize_tag(m_key_tag));
+ m_tree->set_key_tag(m_state->node_id, normalize_tag(m_key_tag));
+ m_key_tag.clear();
+ }
+ }
+ else
+ {
+ type_bits as_doc = 0;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, !m_tree->is_doc(m_state->node_id));
+ m_tree->to_seq(m_state->node_id, as_doc);
+ _c4dbgpf("start_seq: id={}{}", m_state->node_id, as_doc ? " as doc" : "");
+ }
+ _write_val_anchor(m_state->node_id);
+ m_tree->_p(m_state->node_id)->m_val.scalar.str = m_state->line_contents.rem.str;
+ }
+ else
+ {
+ m_state->node_id = parent_id;
+ type_bits as_doc = 0;
+ if(m_tree->is_doc(m_state->node_id))
+ as_doc |= DOC;
+ if(!m_tree->is_seq(parent_id))
+ {
+ RYML_CHECK(!m_tree->has_children(parent_id));
+ m_tree->to_seq(parent_id, as_doc);
+ }
+ else
+ {
+ m_tree->_add_flags(parent_id, as_doc);
+ }
+ _move_scalar_from_top();
+ _c4dbgpf("start_seq: id={}{}", m_state->node_id, as_doc ? " as_doc" : "");
+ _write_val_anchor(parent_id);
+ m_tree->_p(parent_id)->m_val.scalar.str = m_state->line_contents.rem.str;
+ }
+ if( ! m_val_tag.empty())
+ {
+ _c4dbgpf("start_seq[{}]: set val tag='{}' -> '{}'", m_state->node_id, m_val_tag, normalize_tag(m_val_tag));
+ m_tree->set_val_tag(m_state->node_id, normalize_tag(m_val_tag));
+ m_val_tag.clear();
+ }
+}
+
+void Parser::_stop_seq()
+{
+ _c4dbgp("stop_seq");
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_seq(m_state->node_id));
+}
+
+
+//-----------------------------------------------------------------------------
+void Parser::_start_seqimap()
+{
+ _c4dbgpf("start_seqimap at node={}. has_children={}", m_state->node_id, m_tree->has_children(m_state->node_id));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RSEQ|FLOW));
+ // create a map, and turn the last scalar of this sequence
+ // into the key of the map's first child. This scalar was
+ // understood to be a value in the sequence, but it is
+ // actually a key of a map, implicitly opened here.
+ // Eg [val, key: val]
+ //
+ // Yep, YAML is crazy.
+ if(m_tree->has_children(m_state->node_id) && m_tree->has_val(m_tree->last_child(m_state->node_id)))
+ {
+ size_t prev = m_tree->last_child(m_state->node_id);
+ NodeType ty = m_tree->_p(prev)->m_type; // don't use type() because it masks out the quotes
+ NodeScalar tmp = m_tree->valsc(prev);
+ _c4dbgpf("has children and last child={} has val. saving the scalars, val='{}' quoted={}", prev, tmp.scalar, ty.is_val_quoted());
+ m_tree->remove(prev);
+ _push_level();
+ _start_map();
+ _store_scalar(tmp.scalar, ty.is_val_quoted());
+ m_key_anchor = tmp.anchor;
+ m_key_tag = tmp.tag;
+ }
+ else
+ {
+ _c4dbgpf("node {} has no children yet, using empty key", m_state->node_id);
+ _push_level();
+ _start_map();
+ _store_scalar_null(m_state->line_contents.rem.str);
+ }
+ add_flags(RSEQIMAP|FLOW);
+}
+
+void Parser::_stop_seqimap()
+{
+ _c4dbgp("stop_seqimap");
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(RSEQIMAP));
+}
+
+
+//-----------------------------------------------------------------------------
+NodeData* Parser::_append_val(csubstr val, flag_t quoted)
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ! has_all(SSCL));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, node(m_state) != nullptr);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_seq(m_state->node_id));
+ type_bits additional_flags = quoted ? VALQUO : NOTYPE;
+ _c4dbgpf("append val: '{}' to parent id={} (level={}){}", val, m_state->node_id, m_state->level, quoted ? " VALQUO!" : "");
+ size_t nid = m_tree->append_child(m_state->node_id);
+ m_tree->to_val(nid, val, additional_flags);
+
+ _c4dbgpf("append val: id={} val='{}'", nid, m_tree->get(nid)->m_val.scalar);
+ if( ! m_val_tag.empty())
+ {
+ _c4dbgpf("append val[{}]: set val tag='{}' -> '{}'", nid, m_val_tag, normalize_tag(m_val_tag));
+ m_tree->set_val_tag(nid, normalize_tag(m_val_tag));
+ m_val_tag.clear();
+ }
+ _write_val_anchor(nid);
+ return m_tree->get(nid);
+}
+
+NodeData* Parser::_append_key_val(csubstr val, flag_t val_quoted)
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_tree->is_map(m_state->node_id));
+ type_bits additional_flags = 0;
+ if(m_state->flags & QSCL)
+ additional_flags |= KEYQUO;
+ if(val_quoted)
+ additional_flags |= VALQUO;
+
+ csubstr key = _consume_scalar();
+ _c4dbgpf("append keyval: '{}' '{}' to parent id={} (level={}){}{}", key, val, m_state->node_id, m_state->level, (additional_flags & KEYQUO) ? " KEYQUO!" : "", (additional_flags & VALQUO) ? " VALQUO!" : "");
+ size_t nid = m_tree->append_child(m_state->node_id);
+ m_tree->to_keyval(nid, key, val, additional_flags);
+ _c4dbgpf("append keyval: id={} key='{}' val='{}'", nid, m_tree->key(nid), m_tree->val(nid));
+ if( ! m_key_tag.empty())
+ {
+ _c4dbgpf("append keyval[{}]: set key tag='{}' -> '{}'", nid, m_key_tag, normalize_tag(m_key_tag));
+ m_tree->set_key_tag(nid, normalize_tag(m_key_tag));
+ m_key_tag.clear();
+ }
+ if( ! m_val_tag.empty())
+ {
+ _c4dbgpf("append keyval[{}]: set val tag='{}' -> '{}'", nid, m_val_tag, normalize_tag(m_val_tag));
+ m_tree->set_val_tag(nid, normalize_tag(m_val_tag));
+ m_val_tag.clear();
+ }
+ _write_key_anchor(nid);
+ _write_val_anchor(nid);
+ rem_flags(QMRK);
+ return m_tree->get(nid);
+}
+
+
+//-----------------------------------------------------------------------------
+void Parser::_store_scalar(csubstr s, flag_t is_quoted)
+{
+ _c4dbgpf("state[{}]: storing scalar '{}' (flag: {}) (old scalar='{}')",
+ m_state-m_stack.begin(), s, m_state->flags & SSCL, m_state->scalar);
+ RYML_CHECK(has_none(SSCL));
+ add_flags(SSCL | (is_quoted * QSCL));
+ m_state->scalar = s;
+}
+
+csubstr Parser::_consume_scalar()
+{
+ _c4dbgpf("state[{}]: consuming scalar '{}' (flag: {}))", m_state-m_stack.begin(), m_state->scalar, m_state->flags & SSCL);
+ RYML_CHECK(m_state->flags & SSCL);
+ csubstr s = m_state->scalar;
+ rem_flags(SSCL | QSCL);
+ m_state->scalar.clear();
+ return s;
+}
+
+void Parser::_move_scalar_from_top()
+{
+ if(m_stack.size() < 2) return;
+ State &prev = m_stack.top(1);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state == &m_stack.top());
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state != &prev);
+ if(prev.flags & SSCL)
+ {
+ _c4dbgpf("moving scalar '{}' from state[{}] to state[{}] (overwriting '{}')", prev.scalar, &prev-m_stack.begin(), m_state-m_stack.begin(), m_state->scalar);
+ add_flags(prev.flags & (SSCL | QSCL));
+ m_state->scalar = prev.scalar;
+ rem_flags(SSCL | QSCL, &prev);
+ prev.scalar.clear();
+ }
+}
+
+//-----------------------------------------------------------------------------
+/** @todo this function is a monster and needs love. */
+bool Parser::_handle_indentation()
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(FLOW));
+ if( ! _at_line_begin())
+ return false;
+
+ size_t ind = m_state->line_contents.indentation;
+ csubstr rem = m_state->line_contents.rem;
+ /** @todo instead of trimming, we should use the indentation index from above */
+ csubstr remt = rem.triml(' ');
+
+ if(remt.empty() || remt.begins_with('#')) // this is a blank or comment line
+ {
+ _line_progressed(rem.size());
+ return true;
+ }
+
+ _c4dbgpf("indentation? ind={} indref={}", ind, m_state->indref);
+ if(ind == m_state->indref)
+ {
+ if(has_all(SSCL|RVAL) && ! rem.sub(ind).begins_with('-'))
+ {
+ if(has_all(RMAP))
+ {
+ _append_key_val_null(rem.str + ind - 1);
+ addrem_flags(RKEY, RVAL);
+ }
+ #ifdef RYML_NO_COVERAGE__TO_BE_DELETED
+ else if(has_all(RSEQ))
+ {
+ _append_val(_consume_scalar());
+ addrem_flags(RNXT, RVAL);
+ }
+ else
+ {
+ _c4err("internal error");
+ }
+ #endif
+ }
+ else if(has_all(RSEQ|RNXT) && ! rem.sub(ind).begins_with('-'))
+ {
+ if(m_stack.size() > 2) // do not pop to root level
+ {
+ _c4dbgp("end the indentless seq");
+ _pop_level();
+ return true;
+ }
+ }
+ else
+ {
+ _c4dbgpf("same indentation ({}) -- nothing to see here", ind);
+ }
+ _line_progressed(ind);
+ return ind > 0;
+ }
+ else if(ind < m_state->indref)
+ {
+ _c4dbgpf("smaller indentation ({} < {})!!!", ind, m_state->indref);
+ if(has_all(RVAL))
+ {
+ _c4dbgp("there was an empty val -- appending");
+ if(has_all(RMAP))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_all(SSCL));
+ _append_key_val_null(rem.sub(ind).str - 1);
+ }
+ else if(has_all(RSEQ))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, has_none(SSCL));
+ _append_val_null(rem.sub(ind).str - 1);
+ }
+ }
+ // search the stack frame to jump to based on its indentation
+ State const* popto = nullptr;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_stack.is_contiguous()); // this search relies on the stack being contiguous
+ for(State const* s = m_state-1; s >= m_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;
+ // while it may be tempting to think we're done at this
+ // point, we must still determine whether we're jumping to a
+ // parent with the same indentation. Consider this case with
+ // an indentless sequence:
+ //
+ // product:
+ // - sku: BL394D
+ // quantity: 4
+ // description: Basketball
+ // price: 450.00
+ // - sku: BL4438H
+ // quantity: 1
+ // description: Super Hoop
+ // price: 2392.00 # jumping one level here would be wrong.
+ // tax: 1234.5 # we must jump two levels
+ if(popto > m_stack.begin())
+ {
+ auto parent = popto - 1;
+ if(parent->indref == popto->indref)
+ {
+ _c4dbgpf("the parent (level={},node={}) has the same indentation ({}). is this in an indentless sequence?", parent->level, parent->node_id, popto->indref);
+ _c4dbgpf("isseq(popto)={} ismap(parent)={}", m_tree->is_seq(popto->node_id), m_tree->is_map(parent->node_id));
+ if(m_tree->is_seq(popto->node_id) && m_tree->is_map(parent->node_id))
+ {
+ if( ! remt.begins_with('-'))
+ {
+ _c4dbgp("this is an indentless sequence");
+ popto = parent;
+ }
+ else
+ {
+ _c4dbgp("not an indentless sequence");
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ if(!popto || popto >= m_state || popto->level >= m_state->level)
+ {
+ _c4err("parse error: incorrect indentation?");
+ }
+ _c4dbgpf("popping {} levels: from level {} to level {}", m_state->level-popto->level, m_state->level, popto->level);
+ while(m_state != popto)
+ {
+ _c4dbgpf("popping level {} (indentation={})", m_state->level, m_state->indref);
+ _pop_level();
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ind == m_state->indref);
+ _line_progressed(ind);
+ return true;
+ }
+ else
+ {
+ _c4dbgpf("larger indentation ({} > {})!!!", ind, m_state->indref);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ind > m_state->indref);
+ if(has_all(RMAP|RVAL))
+ {
+ if(_is_scalar_next__rmap_val(remt) && remt.first_of(":?") == npos)
+ {
+ _c4dbgpf("actually it seems a value: '{}'", remt);
+ }
+ else
+ {
+ addrem_flags(RKEY, RVAL);
+ _start_unk();
+ //_move_scalar_from_top();
+ _line_progressed(ind);
+ _save_indentation();
+ return true;
+ }
+ }
+ else if(has_all(RSEQ|RVAL))
+ {
+ // nothing to do here
+ }
+ else
+ {
+ _c4err("parse error - indentation should not increase at this point");
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+csubstr Parser::_scan_comment()
+{
+ csubstr s = m_state->line_contents.rem;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.begins_with('#'));
+ _line_progressed(s.len);
+ // skip the # character
+ s = s.sub(1);
+ // skip leading whitespace
+ s = s.right_of(s.first_not_of(' '), /*include_pos*/true);
+ _c4dbgpf("comment was '{}'", s);
+ return s;
+}
+
+//-----------------------------------------------------------------------------
+csubstr Parser::_scan_squot_scalar()
+{
+ // 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_state->pos.offset;
+ substr s = m_buf.sub(b);
+ if(s.begins_with(' '))
+ {
+ s = s.triml(' ');
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.sub(b).is_super(s));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.begin() >= m_buf.sub(b).begin());
+ _line_progressed((size_t)(s.begin() - m_buf.sub(b).begin()));
+ }
+ b = m_state->pos.offset; // take this into account
+ _RYML_CB_ASSERT(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_state->line_contents.rem;
+ bool line_is_blank = true;
+ _c4dbgpf("scanning single quoted scalar @ line[{}]: ~~~{}~~~", m_state->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(' '))
+ || (m_state->line_contents.full.last_of('\r') != csubstr::npos);
+
+ if(pos == npos)
+ {
+ _line_progressed(line.len);
+ ++numlines;
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos >= 0 && pos < m_buf.len);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf[m_state->pos.offset + pos] == '\'');
+ _line_progressed(pos + 1); // progress beyond the quote
+ pos = m_state->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_stack.m_callbacks, pos > 0);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() >= m_buf.begin() && s.end() <= m_buf.end());
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() == m_buf.end() || *s.end() == '\'');
+ s = s.sub(0, pos-1);
+ }
+
+ if(needs_filter)
+ {
+ csubstr ret = _filter_squot_scalar(s);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ret.len <= s.len || s.empty() || s.trim(' ').empty());
+ _c4dbgpf("final scalar: \"{}\"", ret);
+ return ret;
+ }
+
+ _c4dbgpf("final scalar: \"{}\"", s);
+
+ return s;
+}
+
+//-----------------------------------------------------------------------------
+csubstr Parser::_scan_dquot_scalar()
+{
+ // 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_state->pos.offset;
+ substr s = m_buf.sub(b);
+ if(s.begins_with(' '))
+ {
+ s = s.triml(' ');
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.sub(b).is_super(s));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.begin() >= m_buf.sub(b).begin());
+ _line_progressed((size_t)(s.begin() - m_buf.sub(b).begin()));
+ }
+ b = m_state->pos.offset; // take this into account
+ _RYML_CB_ASSERT(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_state->line_contents.rem;
+ bool line_is_blank = true;
+ _c4dbgpf("scanning double quoted scalar @ line[{}]: line='{}'", m_state->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(' '))
+ || (m_state->line_contents.full.last_of('\r') != csubstr::npos);
+
+ if(pos == npos)
+ {
+ _line_progressed(line.len);
+ ++numlines;
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos >= 0 && pos < m_buf.len);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf[m_state->pos.offset + pos] == '"');
+ _line_progressed(pos + 1); // progress beyond the quote
+ pos = m_state->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_stack.m_callbacks, pos > 0);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() == m_buf.end() || *s.end() == '"');
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.end() >= m_buf.begin() && s.end() <= m_buf.end());
+ s = s.sub(0, pos-1);
+ }
+
+ if(needs_filter)
+ {
+ csubstr ret = _filter_dquot_scalar(s);
+ _c4dbgpf("final scalar: [{}]\"{}\"", ret.len, ret);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, ret.len <= s.len || s.empty() || s.trim(' ').empty());
+ return ret;
+ }
+
+ _c4dbgpf("final scalar: \"{}\"", s);
+
+ return s;
+}
+
+//-----------------------------------------------------------------------------
+csubstr Parser::_scan_block()
+{
+ // nice explanation here: http://yaml-multiline.info/
+ csubstr s = m_state->line_contents.rem;
+ csubstr trimmed = s.triml(' ');
+ if(trimmed.str > s.str)
+ {
+ _c4dbgp("skipping whitespace");
+ _RYML_CB_ASSERT(m_stack.m_callbacks, trimmed.str >= s.str);
+ _line_progressed(static_cast<size_t>(trimmed.str - s.str));
+ s = trimmed;
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.begins_with('|') || s.begins_with('>'));
+
+ _c4dbgpf("scanning block: specs=\"{}\"", s);
+
+ // parse the spec
+ BlockStyle_e newline = s.begins_with('>') ? BLOCK_FOLD : BLOCK_LITERAL;
+ 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_stack.m_callbacks, s.begins_with_any("|>"));
+ csubstr t = s.sub(1);
+ _c4dbgpf("scanning block: spec is multichar: '{}'", t);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, t.len >= 1);
+ size_t pos = t.first_of("-+");
+ _c4dbgpf("scanning block: 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::atou(digits, &indentation))
+ _c4err("parse error: could not read decimal");
+ _c4dbgpf("scanning block: indentation specified: {}. add {} from curr state -> {}", indentation, m_state->indref, indentation+m_state->indref);
+ indentation += m_state->indref;
+ }
+ }
+
+ // finish the current line
+ _line_progressed(s.len);
+ _line_ended();
+ _scan_line();
+
+ _c4dbgpf("scanning block: style={} chomp={} indentation={}", newline==BLOCK_FOLD ? "fold" : "literal",
+ chomp==CHOMP_CLIP ? "clip" : (chomp==CHOMP_STRIP ? "strip" : "keep"), indentation);
+
+ // start with a zero-length block, already pointing at the right place
+ substr raw_block(m_buf.data() + m_state->pos.offset, size_t(0));// m_state->line_contents.full.sub(0, 0);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, raw_block.begin() == m_state->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, first = m_state->pos.line, provisional_indentation = npos;
+ LineContents lc;
+ while(( ! _finished_file()))
+ {
+ // peek next line, but do not advance immediately
+ lc.reset_with_next_line(m_buf, m_state->pos.offset);
+ _c4dbgpf("scanning block: peeking at '{}'", lc.stripped);
+ // evaluate termination conditions
+ if(indentation != npos)
+ {
+ // stop when the line is deindented and not empty
+ if(lc.indentation < indentation && ( ! lc.rem.trim(" \t\r\n").empty()))
+ {
+ _c4dbgpf("scanning block: indentation decreased ref={} thisline={}", indentation, lc.indentation);
+ break;
+ }
+ else if(indentation == 0)
+ {
+ if((lc.rem == "..." || lc.rem.begins_with("... "))
+ ||
+ (lc.rem == "---" || lc.rem.begins_with("--- ")))
+ {
+ _c4dbgp("scanning block: stop. indentation=0 and stream ended");
+ break;
+ }
+ }
+ }
+ else
+ {
+ _c4dbgpf("scanning block: indentation ref not set. firstnonws={}", lc.stripped.first_not_of(' '));
+ if(lc.stripped.first_not_of(' ') != npos) // non-empty line
+ {
+ _c4dbgpf("scanning block: line not empty. indref={} indprov={} indentation={}", m_state->indref, provisional_indentation, lc.indentation);
+ if(provisional_indentation == npos)
+ {
+ #ifdef RYML_NO_COVERAGE__TO_BE_DELETED
+ if(lc.indentation < m_state->indref)
+ {
+ _c4dbgpf("scanning block: block terminated indentation={} < indref={}", lc.indentation, m_state->indref);
+ break;
+ }
+ else
+ #endif
+ if(lc.indentation == m_state->indref)
+ {
+ if(has_any(RSEQ|RMAP))
+ {
+ _c4dbgpf("scanning block: block terminated. reading container and indentation={}==indref={}", lc.indentation, m_state->indref);
+ break;
+ }
+ }
+ _c4dbgpf("scanning block: set indentation ref from this line: ref={}", lc.indentation);
+ indentation = lc.indentation;
+ }
+ else
+ {
+ if(lc.indentation >= provisional_indentation)
+ {
+ _c4dbgpf("scanning block: 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("scanning block: 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("scanning block: 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("scanning block: increase provisional_ref {} -> {}", provisional_indentation, lc.indentation);
+ provisional_indentation = lc.indentation;
+ }
+ #endif
+ }
+ else
+ {
+ provisional_indentation = lc.indentation ? lc.indentation : has_any(RSEQ|RVAL);
+ _c4dbgpf("scanning block: initialize provisional_ref={}", provisional_indentation);
+ if(provisional_indentation == npos)
+ {
+ provisional_indentation = lc.stripped.len ? lc.stripped.len : has_any(RSEQ|RVAL);
+ _c4dbgpf("scanning block: initialize provisional_ref={}", provisional_indentation);
+ }
+ }
+ }
+ }
+ // advance now that we know the folded scalar continues
+ m_state->line_contents = lc;
+ _c4dbgpf("scanning block: append '{}'", m_state->line_contents.rem);
+ raw_block.len += m_state->line_contents.full.len;
+ _line_progressed(m_state->line_contents.rem.len);
+ _line_ended();
+ ++num_lines;
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_state->pos.line == (first + num_lines));
+ C4_UNUSED(num_lines);
+ C4_UNUSED(first);
+
+ if(indentation == npos)
+ {
+ _c4dbgpf("scanning block: set indentation from provisional: {}", provisional_indentation);
+ indentation = provisional_indentation;
+ }
+
+ if(num_lines)
+ _line_ended_undo();
+
+ _c4dbgpf("scanning block: raw=~~~{}~~~", raw_block);
+
+ // ok! now we strip the newlines and spaces according to the specs
+ s = _filter_block_scalar(raw_block, newline, chomp, indentation);
+
+ _c4dbgpf("scanning block: final=~~~{}~~~", s);
+
+ return s;
+}
+
+
+//-----------------------------------------------------------------------------
+
+template<bool backslash_is_escape, bool keep_trailing_whitespace>
+bool Parser::_filter_nl(substr r, size_t *C4_RESTRICT i, size_t *C4_RESTRICT pos, size_t indentation)
+{
+ // a debugging scaffold:
+ #if 0
+ #define _c4dbgfnl(fmt, ...) _c4dbgpf("filter_nl[{}]: " fmt, *i, __VA_ARGS__)
+ #else
+ #define _c4dbgfnl(...)
+ #endif
+
+ const char curr = r[*i];
+ bool replaced = false;
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, indentation != npos);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, curr == '\n');
+
+ _c4dbgfnl("found newline. sofar=[{}]~~~{}~~~", *pos, m_filter_arena.first(*pos));
+ size_t ii = *i;
+ size_t numnl_following = count_following_newlines(r, &ii, indentation);
+ if(numnl_following)
+ {
+ _c4dbgfnl("{} consecutive (empty) lines {} in the middle. totalws={}", 1+numnl_following, ii < r.len ? "in the middle" : "at the end", ii - *i);
+ for(size_t j = 0; j < numnl_following; ++j)
+ m_filter_arena.str[(*pos)++] = '\n';
+ }
+ else
+ {
+ if(r.first_not_of(" \t", *i+1) != npos)
+ {
+ m_filter_arena.str[(*pos)++] = ' ';
+ _c4dbgfnl("single newline. convert to space. ii={}/{}. sofar=[{}]~~~{}~~~", ii, r.len, *pos, m_filter_arena.first(*pos));
+ replaced = true;
+ }
+ else
+ {
+ if C4_IF_CONSTEXPR (keep_trailing_whitespace)
+ {
+ m_filter_arena.str[(*pos)++] = ' ';
+ _c4dbgfnl("single newline. convert to space. ii={}/{}. sofar=[{}]~~~{}~~~", ii, r.len, *pos, m_filter_arena.first(*pos));
+ replaced = true;
+ }
+ else
+ {
+ _c4dbgfnl("last newline, everything else is whitespace. ii={}/{}", ii, r.len);
+ *i = r.len;
+ }
+ }
+ if C4_IF_CONSTEXPR (backslash_is_escape)
+ {
+ if(ii < r.len && r.str[ii] == '\\')
+ {
+ const char next = ii+1 < r.len ? r.str[ii+1] : '\0';
+ if(next == ' ' || next == '\t')
+ {
+ _c4dbgfnl("extend skip to backslash{}", "");
+ ++ii;
+ }
+ }
+ }
+ }
+ *i = ii - 1; // correct for the loop increment
+
+ #undef _c4dbgfnl
+
+ return replaced;
+}
+
+
+//-----------------------------------------------------------------------------
+
+template<bool keep_trailing_whitespace>
+void Parser::_filter_ws(substr r, size_t *C4_RESTRICT i, size_t *C4_RESTRICT pos)
+{
+ // a debugging scaffold:
+ #if 0
+ #define _c4dbgfws(fmt, ...) _c4dbgpf("filt_nl[{}]: " fmt, *i, __VA_ARGS__)
+ #else
+ #define _c4dbgfws(...)
+ #endif
+
+ const char curr = r[*i];
+ _c4dbgfws("found whitespace '{}'", _c4prc(curr));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, curr == ' ' || curr == '\t');
+
+ size_t first = *i > 0 ? r.first_not_of(" \t", *i) : r.first_not_of(' ', *i);
+ if(first != npos)
+ {
+ if(r[first] == '\n' || r[first] == '\r') // skip trailing whitespace
+ {
+ _c4dbgfws("whitespace is trailing on line. firstnonws='{}'@{}", _c4prc(r[first]), first);
+ *i = first - 1; // correct for the loop increment
+ }
+ else // a legit whitespace
+ {
+ m_filter_arena.str[(*pos)++] = curr;
+ _c4dbgfws("legit whitespace. sofar=[{}]~~~{}~~~", *pos, m_filter_arena.first(*pos));
+ }
+ }
+ else
+ {
+ _c4dbgfws("... everything else is trailing whitespace{}", "");
+ if C4_IF_CONSTEXPR (keep_trailing_whitespace)
+ for(size_t j = *i; j < r.len; ++j)
+ m_filter_arena.str[(*pos)++] = r[j];
+ *i = r.len;
+ }
+
+ #undef _c4dbgfws
+}
+
+
+//-----------------------------------------------------------------------------
+csubstr Parser::_filter_plain_scalar(substr s, size_t indentation)
+{
+ // a debugging scaffold:
+ #if 0
+ #define _c4dbgfps(...) _c4dbgpf("filt_plain_scalar" __VA_ARGS__)
+ #else
+ #define _c4dbgfps(...)
+ #endif
+
+ _c4dbgfps("before=~~~{}~~~", s);
+
+ substr r = s.triml(" \t");
+ _grow_filter_arena(r.len);
+ size_t pos = 0; // the filtered size
+ bool filtered_chars = false;
+ for(size_t i = 0; i < r.len; ++i)
+ {
+ const char curr = r.str[i];
+ _c4dbgfps("[{}]: '{}'", i, _c4prc(curr));
+ if(curr == ' ' || curr == '\t')
+ {
+ _filter_ws</*keep_trailing_ws*/false>(r, &i, &pos);
+ }
+ else if(curr == '\n')
+ {
+ filtered_chars = _filter_nl</*backslash_is_escape*/false, /*keep_trailing_ws*/false>(r, &i, &pos, indentation);
+ }
+ else if(curr == '\r') // skip \r --- https://stackoverflow.com/questions/1885900
+ {
+ ;
+ }
+ else
+ {
+ m_filter_arena.str[pos++] = r[i];
+ }
+ }
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len);
+ if(pos < r.len || filtered_chars)
+ {
+ r = _finish_filter_arena(r, pos);
+ }
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.len >= r.len);
+ _c4dbgfps("#filteredchars={} after=~~~{}~~~", s.len - r.len, r);
+
+ #undef _c4dbgfps
+ return r;
+}
+
+
+//-----------------------------------------------------------------------------
+csubstr Parser::_filter_squot_scalar(substr s)
+{
+ // a debugging scaffold:
+ #if 0
+ #define _c4dbgfsq(...) _c4dbgpf("filt_squo_scalar")
+ #else
+ #define _c4dbgfsq(...)
+ #endif
+
+ // from the YAML spec for double-quoted scalars:
+ // https://yaml.org/spec/1.2-old/spec.html#style/flow/single-quoted
+
+ _c4dbgfsq(": before=~~~{}~~~", s);
+
+ _grow_filter_arena(s.len);
+ substr r = s;
+ size_t pos = 0; // the filtered size
+ bool filtered_chars = false;
+ for(size_t i = 0; i < r.len; ++i)
+ {
+ const char curr = r[i];
+ _c4dbgfsq("[{}]: '{}'", i, _c4prc(curr));
+ if(curr == ' ' || curr == '\t')
+ {
+ _filter_ws</*keep_trailing_ws*/true>(r, &i, &pos);
+ }
+ else if(curr == '\n')
+ {
+ filtered_chars = _filter_nl</*backslash_is_escape*/false, /*keep_trailing_ws*/true>(r, &i, &pos, /*indentation*/0);
+ }
+ else if(curr == '\r') // skip \r --- https://stackoverflow.com/questions/1885900
+ {
+ ;
+ }
+ else if(curr == '\'')
+ {
+ char next = i+1 < r.len ? r[i+1] : '\0';
+ if(next == '\'')
+ {
+ _c4dbgfsq("[{}]: two consecutive quotes", i);
+ filtered_chars = true;
+ m_filter_arena.str[pos++] = '\'';
+ ++i;
+ }
+ }
+ else
+ {
+ m_filter_arena.str[pos++] = curr;
+ }
+ }
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len);
+ if(pos < r.len || filtered_chars)
+ {
+ r = _finish_filter_arena(r, pos);
+ }
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.len >= r.len);
+ _c4dbgpf(": #filteredchars={} after=~~~{}~~~", s.len - r.len, r);
+
+ #undef _c4dbgfsq
+ return r;
+}
+
+
+//-----------------------------------------------------------------------------
+csubstr Parser::_filter_dquot_scalar(substr s)
+{
+ // a debugging scaffold:
+ #if 0
+ #define _c4dbgfdq(...) _c4dbgpf("filt_dquo_scalar" __VA_ARGS__)
+ #else
+ #define _c4dbgfdq(...)
+ #endif
+
+ _c4dbgfdq(": before=~~~{}~~~", s);
+
+ // from the YAML spec for double-quoted scalars:
+ // https://yaml.org/spec/1.2-old/spec.html#style/flow/double-quoted
+ //
+ // All leading and trailing white space characters are excluded
+ // from the content. Each continuation line must therefore contain
+ // at least one non-space character. Empty lines, if any, are
+ // consumed as part of the line folding.
+
+ _grow_filter_arena(s.len + 2u * s.count('\\'));
+ substr r = s;
+ size_t pos = 0; // the filtered size
+ bool filtered_chars = false;
+ for(size_t i = 0; i < r.len; ++i)
+ {
+ const char curr = r[i];
+ _c4dbgfdq("[{}]: '{}'", i, _c4prc(curr));
+ if(curr == ' ' || curr == '\t')
+ {
+ _filter_ws</*keep_trailing_ws*/true>(r, &i, &pos);
+ }
+ else if(curr == '\n')
+ {
+ filtered_chars = _filter_nl</*backslash_is_escape*/true, /*keep_trailing_ws*/true>(r, &i, &pos, /*indentation*/0);
+ }
+ else if(curr == '\r') // skip \r --- https://stackoverflow.com/questions/1885900
+ {
+ ;
+ }
+ else if(curr == '\\')
+ {
+ char next = i+1 < r.len ? r[i+1] : '\0';
+ _c4dbgfdq("[{}]: backslash, next='{}'", i, _c4prc(next));
+ filtered_chars = true;
+ if(next == '\r')
+ {
+ if(i+2 < r.len && r[i+2] == '\n')
+ {
+ ++i; // newline escaped with \ -- skip both (add only one as i is loop-incremented)
+ next = '\n';
+ _c4dbgfdq("[{}]: was \\r\\n, now next='\\n'", i);
+ }
+ }
+ // remember the loop will also increment i
+ if(next == '\n')
+ {
+ size_t ii = i + 2;
+ for( ; ii < r.len; ++ii)
+ {
+ if(r.str[ii] == ' ' || r.str[ii] == '\t') // skip leading whitespace
+ ;
+ else
+ break;
+ }
+ i += ii - i - 1;
+ }
+ else if(next == '"' || next == '/' || next == ' ' || next == '\t') // escapes for json compatibility
+ {
+ m_filter_arena.str[pos++] = next;
+ ++i;
+ }
+ else if(next == '\r')
+ {
+ //++i;
+ }
+ else if(next == 'n')
+ {
+ m_filter_arena.str[pos++] = '\n';
+ ++i;
+ }
+ else if(next == 'r')
+ {
+ m_filter_arena.str[pos++] = '\r';
+ ++i; // skip
+ }
+ else if(next == 't')
+ {
+ m_filter_arena.str[pos++] = '\t';
+ ++i;
+ }
+ else if(next == '\\')
+ {
+ m_filter_arena.str[pos++] = '\\';
+ ++i;
+ }
+ else if(next == 'x') // UTF8
+ {
+ if(i + 1u + 2u >= r.len)
+ _c4err("\\x requires 2 hex digits");
+ uint8_t byteval = {};
+ if(!read_hex(r.sub(i + 2u, 2u), &byteval))
+ _c4err("failed to read \\x codepoint");
+ m_filter_arena.str[pos++] = *(char*)&byteval;
+ i += 1u + 2u;
+ }
+ else if(next == 'u') // UTF16
+ {
+ if(i + 1u + 4u >= r.len)
+ _c4err("\\u requires 4 hex digits");
+ char readbuf[8];
+ csubstr codepoint = r.sub(i + 2u, 4u);
+ uint32_t codepoint_val = {};
+ if(!read_hex(codepoint, &codepoint_val))
+ _c4err("failed to parse \\u codepoint");
+ size_t numbytes = decode_code_point((uint8_t*)readbuf, sizeof(readbuf), codepoint_val);
+ C4_ASSERT(numbytes <= 4);
+ memcpy(m_filter_arena.str + pos, readbuf, numbytes);
+ pos += numbytes;
+ i += 1u + 4u;
+ }
+ else if(next == 'U') // UTF32
+ {
+ if(i + 1u + 8u >= r.len)
+ _c4err("\\U requires 8 hex digits");
+ char readbuf[8];
+ csubstr codepoint = r.sub(i + 2u, 8u);
+ uint32_t codepoint_val = {};
+ if(!read_hex(codepoint, &codepoint_val))
+ _c4err("failed to parse \\U codepoint");
+ size_t numbytes = decode_code_point((uint8_t*)readbuf, sizeof(readbuf), codepoint_val);
+ C4_ASSERT(numbytes <= 4);
+ memcpy(m_filter_arena.str + pos, readbuf, numbytes);
+ pos += numbytes;
+ i += 1u + 8u;
+ }
+ // https://yaml.org/spec/1.2.2/#rule-c-ns-esc-char
+ else if(next == '0')
+ {
+ m_filter_arena.str[pos++] = '\0';
+ ++i;
+ }
+ else if(next == 'b') // backspace
+ {
+ m_filter_arena.str[pos++] = '\b';
+ ++i;
+ }
+ else if(next == 'f') // form feed
+ {
+ m_filter_arena.str[pos++] = '\f';
+ ++i;
+ }
+ else if(next == 'a') // bell character
+ {
+ m_filter_arena.str[pos++] = '\a';
+ ++i;
+ }
+ else if(next == 'v') // vertical tab
+ {
+ m_filter_arena.str[pos++] = '\v';
+ ++i;
+ }
+ else if(next == 'e') // escape character
+ {
+ m_filter_arena.str[pos++] = '\x1b';
+ ++i;
+ }
+ else if(next == '_') // unicode non breaking space \u00a0
+ {
+ // https://www.compart.com/en/unicode/U+00a0
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x3e, 0xc2);
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x60, 0xa0);
+ ++i;
+ }
+ else if(next == 'N') // unicode next line \u0085
+ {
+ // https://www.compart.com/en/unicode/U+0085
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x3e, 0xc2);
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x7b, 0x85);
+ ++i;
+ }
+ 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
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x1e, 0xe2);
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x80, 0x80);
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x58, 0xa8);
+ ++i;
+ }
+ 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
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x1e, 0xe2);
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x80, 0x80);
+ m_filter_arena.str[pos++] = _RYML_CHCONST(-0x57, 0xa9);
+ ++i;
+ }
+ _c4dbgfdq("[{}]: backslash...sofar=[{}]~~~{}~~~", i, pos, m_filter_arena.first(pos));
+ }
+ else
+ {
+ m_filter_arena.str[pos++] = curr;
+ }
+ }
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len);
+ if(pos < r.len || filtered_chars)
+ {
+ r = _finish_filter_arena(r, pos);
+ }
+
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.len >= r.len);
+ _c4dbgpf(": #filteredchars={} after=~~~{}~~~", s.len - r.len, r);
+
+ #undef _c4dbgfdq
+
+ return r;
+}
+
+
+//-----------------------------------------------------------------------------
+bool Parser::_apply_chomp(substr buf, size_t *C4_RESTRICT pos, BlockChomp_e chomp)
+{
+ substr trimmed = buf.first(*pos).trimr('\n');
+ bool added_newline = false;
+ switch(chomp)
+ {
+ case CHOMP_KEEP:
+ if(trimmed.len == *pos)
+ {
+ _c4dbgpf("chomp=KEEP: add missing newline @{}", *pos);
+ //m_filter_arena.str[(*pos)++] = '\n';
+ added_newline = true;
+ }
+ break;
+ case CHOMP_CLIP:
+ if(trimmed.len == *pos)
+ {
+ _c4dbgpf("chomp=CLIP: add missing newline @{}", *pos);
+ m_filter_arena.str[(*pos)++] = '\n';
+ added_newline = true;
+ }
+ else
+ {
+ _c4dbgpf("chomp=CLIP: include single trailing newline @{}", trimmed.len+1);
+ *pos = trimmed.len + 1;
+ }
+ break;
+ case CHOMP_STRIP:
+ _c4dbgpf("chomp=STRIP: strip {}-{}-{} newlines", *pos, trimmed.len, *pos-trimmed.len);
+ *pos = trimmed.len;
+ break;
+ default:
+ _c4err("unknown chomp style");
+ }
+ return added_newline;
+}
+
+
+//-----------------------------------------------------------------------------
+csubstr Parser::_filter_block_scalar(substr s, BlockStyle_e style, BlockChomp_e chomp, size_t indentation)
+{
+ // a debugging scaffold:
+ #if 0
+ #define _c4dbgfbl(fmt, ...) _c4dbgpf("filt_block" fmt, __VA_ARGS__)
+ #else
+ #define _c4dbgfbl(...)
+ #endif
+
+ _c4dbgfbl(": indentation={} before=[{}]~~~{}~~~", indentation, s.len, s);
+
+ if(chomp != CHOMP_KEEP && s.trim(" \n\r\t").len == 0u)
+ {
+ _c4dbgp("filt_block: empty scalar");
+ return s.first(0);
+ }
+
+ substr r = s;
+
+ switch(style)
+ {
+ case BLOCK_LITERAL:
+ {
+ _c4dbgp("filt_block: style=literal");
+ // trim leading whitespace up to indentation
+ {
+ size_t numws = r.first_not_of(' ');
+ if(numws != npos)
+ {
+ if(numws > indentation)
+ r = r.sub(indentation);
+ else
+ r = r.sub(numws);
+ _c4dbgfbl(": after triml=[{}]~~~{}~~~", r.len, r);
+ }
+ else
+ {
+ if(chomp != CHOMP_KEEP || r.len == 0)
+ {
+ _c4dbgfbl(": all spaces {}, return empty", r.len);
+ return r.first(0);
+ }
+ else
+ {
+ r[0] = '\n';
+ return r.first(1);
+ }
+ }
+ }
+ _grow_filter_arena(s.len + 2u); // use s.len! because we may need to add a newline at the end, so the leading indentation will allow space for that newline
+ size_t pos = 0; // the filtered size
+ for(size_t i = 0; i < r.len; ++i)
+ {
+ const char curr = r.str[i];
+ _c4dbgfbl("[{}]='{}' pos={}", i, _c4prc(curr), pos);
+ if(curr == '\r')
+ continue;
+ m_filter_arena.str[pos++] = curr;
+ if(curr == '\n')
+ {
+ _c4dbgfbl("[{}]: found newline", i);
+ // skip indentation on the next line
+ csubstr rem = r.sub(i+1);
+ size_t first = rem.first_not_of(' ');
+ if(first != npos)
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, first < rem.len);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, i+1+first < r.len);
+ _c4dbgfbl("[{}]: {} spaces follow before next nonws character @ [{}]='{}'", i, first, i+1+first, rem.str[first]);
+ if(first < indentation)
+ {
+ _c4dbgfbl("[{}]: skip {}<{} spaces from indentation", i, first, indentation);
+ i += first;
+ }
+ else
+ {
+ _c4dbgfbl("[{}]: skip {} spaces from indentation", i, indentation);
+ i += indentation;
+ }
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, i+1 <= r.len);
+ first = rem.len;
+ _c4dbgfbl("[{}]: {} spaces to the end", i, first);
+ if(first)
+ {
+ if(first < indentation)
+ {
+ _c4dbgfbl("[{}]: skip everything", i);
+ --pos;
+ break;
+ }
+ else
+ {
+ _c4dbgfbl("[{}]: skip {} spaces from indentation", i, indentation);
+ i += indentation;
+ }
+ }
+ else if(i+1 == r.len)
+ {
+ if(chomp == CHOMP_STRIP)
+ --pos;
+ break;
+ }
+ }
+ }
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, s.len >= pos);
+ _c4dbgfbl(": #filteredchars={} after=~~~{}~~~", s.len - r.len, r);
+ bool changed = _apply_chomp(m_filter_arena, &pos, chomp);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= s.len);
+ if(pos < r.len || changed)
+ {
+ r = _finish_filter_arena(s, pos); // write into s
+ }
+ break;
+ }
+ case BLOCK_FOLD:
+ {
+ _c4dbgp("filt_block: style=fold");
+ _grow_filter_arena(r.len + 2);
+ size_t pos = 0; // the filtered size
+ bool filtered_chars = false;
+ bool started = false;
+ bool is_indented = false;
+ size_t i = r.first_not_of(' ');
+ _c4dbgfbl(": first non space at {}", i);
+ if(i > indentation)
+ {
+ is_indented = true;
+ i = indentation;
+ }
+ _c4dbgfbl(": start folding at {}, is_indented={}", i, (int)is_indented);
+ auto on_change_indentation = [&](size_t numnl_following, size_t last_newl, size_t first_non_whitespace){
+ _c4dbgfbl("[{}]: add 1+{} newlines", i, numnl_following);
+ for(size_t j = 0; j < 1 + numnl_following; ++j)
+ m_filter_arena.str[pos++] = '\n';
+ for(i = last_newl + 1 + indentation; i < first_non_whitespace; ++i)
+ {
+ if(r.str[i] == '\r')
+ continue;
+ _c4dbgfbl("[{}]: add '{}'", i, _c4prc(r.str[i]));
+ m_filter_arena.str[pos++] = r.str[i];
+ }
+ --i;
+ };
+ for( ; i < r.len; ++i)
+ {
+ const char curr = r.str[i];
+ _c4dbgfbl("[{}]='{}'", i, _c4prc(curr));
+ if(curr == '\n')
+ {
+ filtered_chars = true;
+ // skip indentation on the next line, and advance over the next non-indented blank lines as well
+ size_t first_non_whitespace;
+ size_t numnl_following = (size_t)-1;
+ while(r[i] == '\n')
+ {
+ ++numnl_following;
+ csubstr rem = r.sub(i+1);
+ size_t first = rem.first_not_of(' ');
+ _c4dbgfbl("[{}]: found newline. first={} rem.len={}", i, first, rem.len);
+ if(first != npos)
+ {
+ first_non_whitespace = first + i+1;
+ while(first_non_whitespace < r.len && r[first_non_whitespace] == '\r')
+ ++first_non_whitespace;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, first < rem.len);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, i+1+first < r.len);
+ _c4dbgfbl("[{}]: {} spaces follow before next nonws character @ [{}]='{}'", i, first, i+1+first, _c4prc(rem.str[first]));
+ if(first < indentation)
+ {
+ _c4dbgfbl("[{}]: skip {}<{} spaces from indentation", i, first, indentation);
+ i += first;
+ }
+ else
+ {
+ _c4dbgfbl("[{}]: skip {} spaces from indentation", i, indentation);
+ i += indentation;
+ if(first > indentation)
+ {
+ _c4dbgfbl("[{}]: {} further indented than {}, stop newlining", i, first, indentation);
+ goto finished_counting_newlines;
+ }
+ }
+ // prepare the next while loop iteration
+ // by setting i at the next newline after
+ // an empty line
+ if(r[first_non_whitespace] == '\n')
+ i = first_non_whitespace;
+ else
+ goto finished_counting_newlines;
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, i+1 <= r.len);
+ first = rem.len;
+ first_non_whitespace = first + i+1;
+ if(first)
+ {
+ _c4dbgfbl("[{}]: {} spaces to the end", i, first);
+ if(first < indentation)
+ {
+ _c4dbgfbl("[{}]: skip everything", i);
+ i += first;
+ }
+ else
+ {
+ _c4dbgfbl("[{}]: skip {} spaces from indentation", i, indentation);
+ i += indentation;
+ if(first > indentation)
+ {
+ _c4dbgfbl("[{}]: {} spaces missing. not done yet", i, indentation - first);
+ goto finished_counting_newlines;
+ }
+ }
+ }
+ else // if(i+1 == r.len)
+ {
+ _c4dbgfbl("[{}]: it's the final newline", i);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, i+1 == r.len);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, rem.len == 0);
+ }
+ goto end_of_scalar;
+ }
+ }
+ end_of_scalar:
+ // Write all the trailing newlines. Since we're
+ // at the end no folding is needed, so write every
+ // newline (add 1).
+ _c4dbgfbl("[{}]: add {} trailing newlines", i, 1+numnl_following);
+ for(size_t j = 0; j < 1 + numnl_following; ++j)
+ m_filter_arena.str[pos++] = '\n';
+ break;
+ finished_counting_newlines:
+ _c4dbgfbl("[{}]: #newlines={} firstnonws={}", i, numnl_following, first_non_whitespace);
+ while(first_non_whitespace < r.len && r[first_non_whitespace] == '\t')
+ ++first_non_whitespace;
+ _c4dbgfbl("[{}]: #newlines={} firstnonws={}", i, numnl_following, first_non_whitespace);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, first_non_whitespace <= r.len);
+ size_t last_newl = r.last_of('\n', first_non_whitespace);
+ size_t this_indentation = first_non_whitespace - last_newl - 1;
+ _c4dbgfbl("[{}]: #newlines={} firstnonws={} lastnewl={} this_indentation={} vs indentation={}", i, numnl_following, first_non_whitespace, last_newl, this_indentation, indentation);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, first_non_whitespace >= last_newl + 1);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, this_indentation >= indentation);
+ if(!started)
+ {
+ _c4dbgfbl("[{}]: #newlines={}. write all leading newlines", i, numnl_following);
+ for(size_t j = 0; j < 1 + numnl_following; ++j)
+ m_filter_arena.str[pos++] = '\n';
+ if(this_indentation > indentation)
+ {
+ is_indented = true;
+ _c4dbgfbl("[{}]: advance ->{}", i, last_newl + indentation);
+ i = last_newl + indentation;
+ }
+ else
+ {
+ i = first_non_whitespace - 1;
+ _c4dbgfbl("[{}]: advance ->{}", i, first_non_whitespace);
+ }
+ }
+ else if(this_indentation == indentation)
+ {
+ _c4dbgfbl("[{}]: same indentation", i);
+ if(!is_indented)
+ {
+ if(numnl_following == 0)
+ {
+ _c4dbgfbl("[{}]: fold!", i);
+ m_filter_arena.str[pos++] = ' ';
+ }
+ else
+ {
+ _c4dbgfbl("[{}]: add {} newlines", i, 1 + numnl_following);
+ for(size_t j = 0; j < numnl_following; ++j)
+ m_filter_arena.str[pos++] = '\n';
+ }
+ i = first_non_whitespace - 1;
+ _c4dbgfbl("[{}]: advance {}->{}", i, i, first_non_whitespace);
+ }
+ else
+ {
+ _c4dbgfbl("[{}]: back to ref indentation", i);
+ is_indented = false;
+ on_change_indentation(numnl_following, last_newl, first_non_whitespace);
+ _c4dbgfbl("[{}]: advance {}->{}", i, i, first_non_whitespace);
+ }
+ }
+ else
+ {
+ _c4dbgfbl("[{}]: increased indentation.", i);
+ is_indented = true;
+ _RYML_CB_ASSERT(m_stack.m_callbacks, this_indentation > indentation);
+ on_change_indentation(numnl_following, last_newl, first_non_whitespace);
+ _c4dbgfbl("[{}]: advance {}->{}", i, i, first_non_whitespace);
+ }
+ }
+ else if(curr != '\r')
+ {
+ if(curr != '\t')
+ started = true;
+ m_filter_arena.str[pos++] = curr;
+ }
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len);
+ _c4dbgfbl(": #filteredchars={} after=[{}]~~~{}~~~", (int)s.len - (int)pos, pos, m_filter_arena.first(pos));
+ bool changed = _apply_chomp(m_filter_arena, &pos, chomp);
+ if(pos < r.len || filtered_chars || changed)
+ {
+ r = _finish_filter_arena(s, pos); // write into s
+ }
+ }
+ break;
+ default:
+ _c4err("unknown block style");
+ }
+
+ _c4dbgfbl(": final=[{}]~~~{}~~~", r.len, r);
+
+ #undef _c4dbgfbl
+
+ return r;
+}
+
+//-----------------------------------------------------------------------------
+size_t Parser::_count_nlines(csubstr src)
+{
+ return 1 + src.count('\n');
+}
+
+//-----------------------------------------------------------------------------
+void Parser::_handle_directive(csubstr directive_)
+{
+ csubstr directive = directive_;
+ if(directive.begins_with("%TAG"))
+ {
+ TagDirective td;
+ _c4dbgpf("%TAG directive: {}", directive_);
+ directive = directive.sub(4);
+ if(!directive.begins_with(' '))
+ _c4err("malformed tag directive: {}", directive_);
+ directive = directive.triml(' ');
+ size_t pos = directive.find(' ');
+ if(pos == npos)
+ _c4err("malformed tag directive: {}", directive_);
+ td.handle = directive.first(pos);
+ directive = directive.sub(td.handle.len).triml(' ');
+ pos = directive.find(' ');
+ if(pos != npos)
+ directive = directive.first(pos);
+ td.prefix = directive;
+ td.next_node_id = m_tree->size();
+ if(m_tree->size() > 0)
+ {
+ size_t prev = m_tree->size() - 1;
+ if(m_tree->is_root(prev) && m_tree->type(prev) != NOTYPE && !m_tree->is_stream(prev))
+ ++td.next_node_id;
+ }
+ _c4dbgpf("%TAG: handle={} prefix={} next_node={}", td.handle, td.prefix, td.next_node_id);
+ m_tree->add_tag_directive(td);
+ }
+ else if(directive.begins_with("%YAML"))
+ {
+ _c4dbgpf("%YAML directive! ignoring...: {}", directive);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void Parser::set_flags(flag_t f, State * s)
+{
+#ifdef RYML_DBG
+ char buf1_[64], buf2_[64];
+ csubstr buf1 = _prfl(buf1_, f);
+ csubstr buf2 = _prfl(buf2_, s->flags);
+ _c4dbgpf("state[{}]: setting flags to {}: before={}", s-m_stack.begin(), buf1, buf2);
+#endif
+ s->flags = f;
+}
+
+void Parser::add_flags(flag_t on, State * s)
+{
+#ifdef RYML_DBG
+ char buf1_[64], buf2_[64], buf3_[64];
+ csubstr buf1 = _prfl(buf1_, on);
+ csubstr buf2 = _prfl(buf2_, s->flags);
+ csubstr buf3 = _prfl(buf3_, s->flags|on);
+ _c4dbgpf("state[{}]: adding flags {}: before={} after={}", s-m_stack.begin(), buf1, buf2, buf3);
+#endif
+ s->flags |= on;
+}
+
+void Parser::addrem_flags(flag_t on, flag_t off, State * s)
+{
+#ifdef RYML_DBG
+ char buf1_[64], buf2_[64], buf3_[64], buf4_[64];
+ csubstr buf1 = _prfl(buf1_, on);
+ csubstr buf2 = _prfl(buf2_, off);
+ csubstr buf3 = _prfl(buf3_, s->flags);
+ csubstr buf4 = _prfl(buf4_, ((s->flags|on)&(~off)));
+ _c4dbgpf("state[{}]: adding flags {} / removing flags {}: before={} after={}", s-m_stack.begin(), buf1, buf2, buf3, buf4);
+#endif
+ s->flags |= on;
+ s->flags &= ~off;
+}
+
+void Parser::rem_flags(flag_t off, State * s)
+{
+#ifdef RYML_DBG
+ char buf1_[64], buf2_[64], buf3_[64];
+ csubstr buf1 = _prfl(buf1_, off);
+ csubstr buf2 = _prfl(buf2_, s->flags);
+ csubstr buf3 = _prfl(buf3_, s->flags&(~off));
+ _c4dbgpf("state[{}]: removing flags {}: before={} after={}", s-m_stack.begin(), buf1, buf2, buf3);
+#endif
+ s->flags &= ~off;
+}
+
+//-----------------------------------------------------------------------------
+
+csubstr Parser::_prfl(substr buf, flag_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(QMRK);
+ _prflag(RKEY);
+ _prflag(RVAL);
+ _prflag(RNXT);
+ _prflag(SSCL);
+ _prflag(QSCL);
+ _prflag(RSET);
+ _prflag(NDOC);
+ _prflag(RSEQIMAP);
+
+ #undef _prflag
+
+ RYML_ASSERT(pos <= buf.len);
+
+ return buf.first(pos);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+void Parser::_grow_filter_arena(size_t num_characters_needed)
+{
+ _c4dbgpf("grow: arena={} numchars={}", m_filter_arena.len, num_characters_needed);
+ if(num_characters_needed <= m_filter_arena.len)
+ return;
+ size_t sz = m_filter_arena.len << 1;
+ _c4dbgpf("grow: sz={}", sz);
+ sz = num_characters_needed > sz ? num_characters_needed : sz;
+ _c4dbgpf("grow: sz={}", sz);
+ sz = sz < 128u ? 128u : sz;
+ _c4dbgpf("grow: sz={}", sz);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, sz >= num_characters_needed);
+ _resize_filter_arena(sz);
+}
+
+void Parser::_resize_filter_arena(size_t num_characters)
+{
+ if(num_characters > m_filter_arena.len)
+ {
+ _c4dbgpf("resize: sz={}", num_characters);
+ char *prev = m_filter_arena.str;
+ if(m_filter_arena.str)
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_filter_arena.len > 0);
+ _RYML_CB_FREE(m_stack.m_callbacks, m_filter_arena.str, char, m_filter_arena.len);
+ }
+ m_filter_arena.str = _RYML_CB_ALLOC_HINT(m_stack.m_callbacks, char, num_characters, prev);
+ m_filter_arena.len = num_characters;
+ }
+}
+
+substr Parser::_finish_filter_arena(substr dst, size_t pos)
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= m_filter_arena.len);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, pos <= dst.len);
+ memcpy(dst.str, m_filter_arena.str, pos);
+ return dst.first(pos);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+csubstr Parser::location_contents(Location const& loc) const
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, loc.offset < m_buf.len);
+ return m_buf.sub(loc.offset);
+}
+
+Location Parser::location(NodeRef node) const
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, node.valid());
+ return location(*node.tree(), node.id());
+}
+
+Location Parser::location(Tree const& tree, size_t node) const
+{
+ _RYML_CB_CHECK(m_stack.m_callbacks, m_buf.str == m_newline_offsets_buf.str);
+ _RYML_CB_CHECK(m_stack.m_callbacks, m_buf.len == m_newline_offsets_buf.len);
+ if(tree.has_key(node))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, tree.key(node).is_sub(m_buf));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.is_super(tree.key(node)));
+ return val_location(tree.key(node).str);
+ }
+ else if(tree.has_val(node))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, tree.val(node).is_sub(m_buf));
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.is_super(tree.val(node)));
+ return val_location(tree.val(node).str);
+ }
+ else if(tree.is_container(node))
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, !tree.has_key(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))
+ {
+ size_t 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(node_start > k.str)
+ node_start = k.str;
+ }
+ }
+ return val_location(node_start);
+ }
+ else // it's a stream
+ {
+ return val_location(m_buf.str); // just return the front of the buffer
+ }
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, tree.type(node) == NOTYPE);
+ return val_location(m_buf.str);
+}
+
+Location Parser::val_location(const char *val) const
+{
+ if(_locations_dirty())
+ _prepare_locations();
+ csubstr src = m_buf;
+ _RYML_CB_CHECK(m_stack.m_callbacks, src.str == m_newline_offsets_buf.str);
+ _RYML_CB_CHECK(m_stack.m_callbacks, src.len == m_newline_offsets_buf.len);
+ _RYML_CB_CHECK(m_stack.m_callbacks, val >= src.begin() && val <= src.end());
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_newline_offsets != nullptr);
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_newline_offsets_size > 0);
+ using linetype = size_t const* C4_RESTRICT;
+ linetype line = nullptr;
+ size_t offset = (size_t)(val - src.begin());
+ if(m_newline_offsets_size < 30)
+ {
+ // do a linear search if the size is small.
+ for(linetype curr = m_newline_offsets; curr < m_newline_offsets + m_newline_offsets_size; ++curr)
+ {
+ if(*curr > offset)
+ {
+ line = 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 include of <algorithm>.
+ size_t count = m_newline_offsets_size;
+ size_t step;
+ linetype it;
+ line = m_newline_offsets;
+ while(count)
+ {
+ step = count >> 1;
+ it = line + step;
+ if(*it < offset)
+ {
+ line = ++it;
+ count -= step + 1;
+ }
+ else
+ {
+ count = step;
+ }
+ }
+ }
+ if(line)
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, *line > offset);
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_buf.empty());
+ _RYML_CB_ASSERT(m_stack.m_callbacks, m_newline_offsets_size == 1);
+ line = m_newline_offsets;
+ }
+ _RYML_CB_ASSERT(m_stack.m_callbacks, line >= m_newline_offsets && line < m_newline_offsets + m_newline_offsets_size);;
+ Location loc = {};
+ loc.name = m_file;
+ loc.offset = offset;
+ loc.line = (size_t)(line - m_newline_offsets);
+ if(line > m_newline_offsets)
+ loc.col = (offset - *(line-1) - 1u);
+ else
+ loc.col = offset;
+ return loc;
+}
+
+void Parser::_prepare_locations() const
+{
+ _RYML_CB_ASSERT(m_stack.m_callbacks, !m_file.empty());
+ 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_stack.m_callbacks, m_newline_offsets_size == numnewlines);
+}
+
+void Parser::_resize_locations(size_t numnewlines) const
+{
+ if(numnewlines > m_newline_offsets_capacity)
+ {
+ if(m_newline_offsets)
+ _RYML_CB_FREE(m_stack.m_callbacks, m_newline_offsets, size_t, m_newline_offsets_capacity);
+ m_newline_offsets = _RYML_CB_ALLOC_HINT(m_stack.m_callbacks, size_t, numnewlines, m_newline_offsets);
+ m_newline_offsets_capacity = numnewlines;
+ }
+}
+
+void Parser::_mark_locations_dirty()
+{
+ m_newline_offsets_size = 0u;
+ m_newline_offsets_buf = m_buf;
+}
+
+bool Parser::_locations_dirty() const
+{
+ return !m_newline_offsets_size;
+}
+
+} // namespace yml
+} // namespace c4
+
+
+#if defined(_MSC_VER)
+# pragma warning(pop)
+#elif defined(__clang__)
+# pragma clang diagnostic pop
+#elif defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+#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;
+}
+
+size_t NodeRef::deserialize_key(c4::fmt::base64_wrapper w) const
+{
+ RYML_ASSERT( ! is_seed());
+ RYML_ASSERT(valid());
+ RYML_ASSERT(get() != nullptr);
+ return from_chars(key(), &w);
+}
+
+size_t NodeRef::deserialize_val(c4::fmt::base64_wrapper w) const
+{
+ RYML_ASSERT( ! is_seed());
+ RYML_ASSERT(valid());
+ RYML_ASSERT(get() != nullptr);
+ return from_chars(val(), &w);
+}
+
+} // 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.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. */
+
+/** @defgroup Preprocessors Preprocessor functions
+ *
+ * These are the existing preprocessors:
+ *
+ * @code{.cpp}
+ * size_t preprocess_json(csubstr json, substr buf)
+ * size_t preprocess_rxmap(csubstr json, substr buf)
+ * @endcode
+ */
+
+#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 <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_ */
+
+
+
+namespace c4 {
+namespace yml {
+
+namespace detail {
+using Preprocessor = size_t(csubstr, substr);
+template<Preprocessor PP, class CharContainer>
+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
+
+
+//-----------------------------------------------------------------------------
+
+/** @name preprocess_rxmap
+ * 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<class CharContainer>
+substr preprocess_rxmap(csubstr rxmap, CharContainer *out)
+{
+ return detail::preprocess_into_container<preprocess_rxmap>(rxmap, out);
+}
+
+
+/** Create a container with the result.
+ * @overload preprocess_rxmap */
+template<class CharContainer>
+CharContainer preprocess_rxmap(csubstr rxmap)
+{
+ CharContainer out;
+ preprocess_rxmap(rxmap, &out);
+ return out;
+}
+
+//@}
+
+} // namespace yml
+} // namespace c4
+
+#endif /* _C4_YML_PREPROCESS_HPP_ */
+
+
+// (end https://github.com/biojppm/rapidyaml/src/c4/yml/preprocess.hpp)
+
+
+
+//********************************************************************************
+//--------------------------------------------------------------------------------
+// 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 {
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+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 == '$');
+}
+
+typedef enum { kReadPending = 0, kKeyPending = 1, kValPending = 2 } _ppstate;
+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<size_t>(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<size_t>(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;
+}
+
+
+} // 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, size_t node=NONE);
+void check_free_list(Tree const& t);
+void check_arena(Tree const& t);
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+inline void check_invariants(Tree const& t, size_t node)
+{
+ if(node == NONE)
+ {
+ if(t.size() == 0) return;
+ node = t.root_id();
+ }
+
+ auto const& n = *t._p(node);
+#ifdef RYML_DBG
+ 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);
+ }
+
+ size_t count = 0;
+ for(size_t i = n.m_first_child; i != NONE; i = t.next_sibling(i))
+ {
+#ifdef RYML_DBG
+ 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(size_t 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);
+
+ size_t count = 0;
+ for(size_t 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_ */
+
+
+
+namespace c4 {
+namespace yml {
+
+
+inline size_t print_node(Tree const& p, size_t node, int level, size_t count, bool print_children)
+{
+ printf("[%zd]%*s[%zd] %p", count, (2*level), "", node, (void*)p.get(node));
+ if(p.is_root(node))
+ {
+ printf(" [ROOT]");
+ }
+ printf(" %s:", p.type_str(node));
+ 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);
+ csubstr k = p.key(node);
+ printf(" %.*s '%.*s'", (int)kt.len, kt.str, (int)k.len, k.str);
+ }
+ else
+ {
+ csubstr k = p.key(node);
+ printf(" '%.*s'", (int)k.len, k.str);
+ }
+ }
+ else
+ {
+ RYML_ASSERT( ! p.has_key_tag(node));
+ }
+ if(p.has_val(node))
+ {
+ if(p.has_val_tag(node))
+ {
+ csubstr vt = p.val_tag(node);
+ csubstr v = p.val(node);
+ printf(" %.*s '%.*s'", (int)vt.len, vt.str, (int)v.len, v.str);
+ }
+ else
+ {
+ csubstr v = p.val(node);
+ printf(" '%.*s'", (int)v.len, v.str);
+ }
+ }
+ else
+ {
+ if(p.has_val_tag(node))
+ {
+ csubstr vt = p.val_tag(node);
+ printf(" %.*s", (int)vt.len, vt.str);
+ }
+ }
+ if(p.has_val_anchor(node))
+ {
+ auto &a = p.val_anchor(node);
+ printf(" valanchor='&%.*s'", (int)a.len, a.str);
+ }
+ printf(" (%zd sibs)", p.num_siblings(node));
+
+ ++count;
+
+ if(p.is_container(node))
+ {
+ printf(" %zd children:\n", p.num_children(node));
+ if(print_children)
+ {
+ for(size_t i = p.first_child(node); i != NONE; i = p.next_sibling(i))
+ {
+ count = print_node(p, i, level+1, count, print_children);
+ }
+ }
+ }
+ else
+ {
+ printf("\n");
+ }
+
+ return count;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+inline void print_node(NodeRef const& p, int level=0)
+{
+ print_node(*p.tree(), p.id(), level, 0, true);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+inline size_t print_tree(Tree const& p, size_t node=NONE)
+{
+ printf("--------------------------------------\n");
+ size_t ret = 0;
+ if(!p.empty())
+ {
+ if(node == NONE)
+ node = p.root_id();
+ ret = print_node(p, node, 0, 0, true);
+ }
+ printf("#nodes=%zd vs #printed=%zd\n", p.size(), ret);
+ printf("--------------------------------------\n");
+ return ret;
+}
+
+
+} /* 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/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/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_ */
+
+
+#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_ */
+
diff --git a/src/third-party/robin_hood/robin_hood.h b/src/third-party/robin_hood/robin_hood.h
new file mode 100644
index 0000000..0af031f
--- /dev/null
+++ b/src/third-party/robin_hood/robin_hood.h
@@ -0,0 +1,2544 @@
+// ______ _____ ______ _________
+// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ /
+// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ /
+// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ /
+// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/
+// _/_____/
+//
+// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20
+// https://github.com/martinus/robin-hood-hashing
+//
+// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2018-2021 Martin Ankerl <http://martin.ankerl.com>
+//
+// 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 ROBIN_HOOD_H_INCLUDED
+#define ROBIN_HOOD_H_INCLUDED
+
+// see https://semver.org/
+#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes
+#define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner
+#define ROBIN_HOOD_VERSION_PATCH 5 // for backwards-compatible bug fixes
+
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <limits>
+#include <memory> // only to support hash of smart pointers
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+#if __cplusplus >= 201703L
+# include <string_view>
+#endif
+
+// #define ROBIN_HOOD_LOG_ENABLED
+#ifdef ROBIN_HOOD_LOG_ENABLED
+# include <iostream>
+# define ROBIN_HOOD_LOG(...) \
+ std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl;
+#else
+# define ROBIN_HOOD_LOG(x)
+#endif
+
+// #define ROBIN_HOOD_TRACE_ENABLED
+#ifdef ROBIN_HOOD_TRACE_ENABLED
+# include <iostream>
+# define ROBIN_HOOD_TRACE(...) \
+ std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl;
+#else
+# define ROBIN_HOOD_TRACE(x)
+#endif
+
+// #define ROBIN_HOOD_COUNT_ENABLED
+#ifdef ROBIN_HOOD_COUNT_ENABLED
+# include <iostream>
+# define ROBIN_HOOD_COUNT(x) ++counts().x;
+namespace robin_hood {
+struct Counts {
+ uint64_t shiftUp{};
+ uint64_t shiftDown{};
+};
+inline std::ostream& operator<<(std::ostream& os, Counts const& c) {
+ return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl;
+}
+
+static Counts& counts() {
+ static Counts counts{};
+ return counts;
+}
+} // namespace robin_hood
+#else
+# define ROBIN_HOOD_COUNT(x)
+#endif
+
+// all non-argument macros should use this facility. See
+// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/
+#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x()
+
+// mark unused members with this macro
+#define ROBIN_HOOD_UNUSED(identifier)
+
+// bitness
+#if SIZE_MAX == UINT32_MAX
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32
+#elif SIZE_MAX == UINT64_MAX
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64
+#else
+# error Unsupported bitness
+#endif
+
+// endianess
+#ifdef _MSC_VER
+# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \
+ (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#endif
+
+// inline
+#ifdef _MSC_VER
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline)
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline))
+#endif
+
+// exceptions
+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1
+#endif
+
+// count leading/trailing bits
+#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS)
+# ifdef _MSC_VER
+# if ROBIN_HOOD(BITNESS) == 32
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward
+# else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64
+# endif
+# include <intrin.h>
+# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD))
+# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \
+ [](size_t mask) noexcept -> int { \
+ unsigned long index; \
+ return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast<int>(index) \
+ : ROBIN_HOOD(BITNESS); \
+ }(x)
+# else
+# if ROBIN_HOOD(BITNESS) == 32
+# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl
+# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl
+# else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll
+# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll
+# endif
+# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS))
+# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS))
+# endif
+#endif
+
+// fallthrough
+#ifndef __has_cpp_attribute // For backwards compatibility
+# define __has_cpp_attribute(x) 0
+#endif
+#if __has_cpp_attribute(clang::fallthrough)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]]
+#elif __has_cpp_attribute(gnu::fallthrough)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]]
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH()
+#endif
+
+// likely/unlikely
+#ifdef _MSC_VER
+# define ROBIN_HOOD_LIKELY(condition) condition
+# define ROBIN_HOOD_UNLIKELY(condition) condition
+#else
+# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1)
+# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0)
+#endif
+
+// detect if native wchar_t type is availiable in MSVC
+#ifdef _MSC_VER
+# ifdef _NATIVE_WCHAR_T_DEFINED
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1
+# else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0
+# endif
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1
+#endif
+
+// detect if MSVC supports the pair(std::piecewise_construct_t,...) consructor being constexpr
+#ifdef _MSC_VER
+# if _MSC_VER <= 1900
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 1
+# else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0
+# endif
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0
+#endif
+
+// workaround missing "is_trivially_copyable" in g++ < 5.0
+// See https://stackoverflow.com/a/31798726/48181
+#if defined(__GNUC__) && __GNUC__ < 5
+# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__)
+#else
+# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value
+#endif
+
+// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]]
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD()
+#endif
+
+namespace robin_hood {
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14)
+# define ROBIN_HOOD_STD std
+#else
+
+// c++11 compatibility layer
+namespace ROBIN_HOOD_STD {
+template <class T>
+struct alignment_of
+ : std::integral_constant<std::size_t, alignof(typename std::remove_all_extents<T>::type)> {};
+
+template <class T, T... Ints>
+class integer_sequence {
+public:
+ using value_type = T;
+ static_assert(std::is_integral<value_type>::value, "not integral type");
+ static constexpr std::size_t size() noexcept {
+ return sizeof...(Ints);
+ }
+};
+template <std::size_t... Inds>
+using index_sequence = integer_sequence<std::size_t, Inds...>;
+
+namespace detail_ {
+template <class T, T Begin, T End, bool>
+struct IntSeqImpl {
+ using TValue = T;
+ static_assert(std::is_integral<TValue>::value, "not integral type");
+ static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)");
+
+ template <class, class>
+ struct IntSeqCombiner;
+
+ template <TValue... Inds0, TValue... Inds1>
+ struct IntSeqCombiner<integer_sequence<TValue, Inds0...>, integer_sequence<TValue, Inds1...>> {
+ using TResult = integer_sequence<TValue, Inds0..., Inds1...>;
+ };
+
+ using TResult =
+ typename IntSeqCombiner<typename IntSeqImpl<TValue, Begin, Begin + (End - Begin) / 2,
+ (End - Begin) / 2 == 1>::TResult,
+ typename IntSeqImpl<TValue, Begin + (End - Begin) / 2, End,
+ (End - Begin + 1) / 2 == 1>::TResult>::TResult;
+};
+
+template <class T, T Begin>
+struct IntSeqImpl<T, Begin, Begin, false> {
+ using TValue = T;
+ static_assert(std::is_integral<TValue>::value, "not integral type");
+ static_assert(Begin >= 0, "unexpected argument (Begin<0)");
+ using TResult = integer_sequence<TValue>;
+};
+
+template <class T, T Begin, T End>
+struct IntSeqImpl<T, Begin, End, true> {
+ using TValue = T;
+ static_assert(std::is_integral<TValue>::value, "not integral type");
+ static_assert(Begin >= 0, "unexpected argument (Begin<0)");
+ using TResult = integer_sequence<TValue, Begin>;
+};
+} // namespace detail_
+
+template <class T, T N>
+using make_integer_sequence = typename detail_::IntSeqImpl<T, 0, N, (N - 0) == 1>::TResult;
+
+template <std::size_t N>
+using make_index_sequence = make_integer_sequence<std::size_t, N>;
+
+template <class... T>
+using index_sequence_for = make_index_sequence<sizeof...(T)>;
+
+} // namespace ROBIN_HOOD_STD
+
+#endif
+
+namespace detail {
+
+// make sure we static_cast to the correct type for hash_int
+#if ROBIN_HOOD(BITNESS) == 64
+using SizeT = uint64_t;
+#else
+using SizeT = uint32_t;
+#endif
+
+template <typename T>
+T rotr(T x, unsigned k) {
+ return (x >> k) | (x << (8U * sizeof(T) - k));
+}
+
+// This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to
+// 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with
+// care!
+template <typename T>
+inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept {
+ return reinterpret_cast<T>(ptr);
+}
+
+template <typename T>
+inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept {
+ return reinterpret_cast<T>(ptr);
+}
+
+// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other
+// inlinings more difficult. Throws are also generally the slow path.
+template <typename E, typename... Args>
+[[noreturn]] ROBIN_HOOD(NOINLINE)
+#if ROBIN_HOOD(HAS_EXCEPTIONS)
+ void doThrow(Args&&... args) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ throw E(std::forward<Args>(args)...);
+}
+#else
+ void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) {
+ abort();
+}
+#endif
+
+template <typename E, typename T, typename... Args>
+T* assertNotNull(T* t, Args&&... args) {
+ if (ROBIN_HOOD_UNLIKELY(nullptr == t)) {
+ doThrow<E>(std::forward<Args>(args)...);
+ }
+ return t;
+}
+
+template <typename T>
+inline T unaligned_load(void const* ptr) noexcept {
+ // using memcpy so we don't get into unaligned load problems.
+ // compiler should optimize this very well anyways.
+ T t;
+ std::memcpy(&t, ptr, sizeof(T));
+ return t;
+}
+
+// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor,
+// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a
+// pointer.
+template <typename T, size_t MinNumAllocs = 4, size_t MaxNumAllocs = 256>
+class BulkPoolAllocator {
+public:
+ BulkPoolAllocator() noexcept = default;
+
+ // does not copy anything, just creates a new allocator.
+ BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept
+ : mHead(nullptr)
+ , mListForFree(nullptr) {}
+
+ BulkPoolAllocator(BulkPoolAllocator&& o) noexcept
+ : mHead(o.mHead)
+ , mListForFree(o.mListForFree) {
+ o.mListForFree = nullptr;
+ o.mHead = nullptr;
+ }
+
+ BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept {
+ reset();
+ mHead = o.mHead;
+ mListForFree = o.mListForFree;
+ o.mListForFree = nullptr;
+ o.mHead = nullptr;
+ return *this;
+ }
+
+ BulkPoolAllocator&
+ // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp)
+ operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept {
+ // does not do anything
+ return *this;
+ }
+
+ ~BulkPoolAllocator() noexcept {
+ reset();
+ }
+
+ // Deallocates all allocated memory.
+ void reset() noexcept {
+ while (mListForFree) {
+ T* tmp = *mListForFree;
+ ROBIN_HOOD_LOG("std::free")
+ std::free(mListForFree);
+ mListForFree = reinterpret_cast_no_cast_align_warning<T**>(tmp);
+ }
+ mHead = nullptr;
+ }
+
+ // allocates, but does NOT initialize. Use in-place new constructor, e.g.
+ // T* obj = pool.allocate();
+ // ::new (static_cast<void*>(obj)) T();
+ T* allocate() {
+ T* tmp = mHead;
+ if (!tmp) {
+ tmp = performAllocation();
+ }
+
+ mHead = *reinterpret_cast_no_cast_align_warning<T**>(tmp);
+ return tmp;
+ }
+
+ // does not actually deallocate but puts it in store.
+ // make sure you have already called the destructor! e.g. with
+ // obj->~T();
+ // pool.deallocate(obj);
+ void deallocate(T* obj) noexcept {
+ *reinterpret_cast_no_cast_align_warning<T**>(obj) = mHead;
+ mHead = obj;
+ }
+
+ // Adds an already allocated block of memory to the allocator. This allocator is from now on
+ // responsible for freeing the data (with free()). If the provided data is not large enough to
+ // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor.
+ void addOrFree(void* ptr, const size_t numBytes) noexcept {
+ // calculate number of available elements in ptr
+ if (numBytes < ALIGNMENT + ALIGNED_SIZE) {
+ // not enough data for at least one element. Free and return.
+ ROBIN_HOOD_LOG("std::free")
+ std::free(ptr);
+ } else {
+ ROBIN_HOOD_LOG("add to buffer")
+ add(ptr, numBytes);
+ }
+ }
+
+ void swap(BulkPoolAllocator<T, MinNumAllocs, MaxNumAllocs>& other) noexcept {
+ using std::swap;
+ swap(mHead, other.mHead);
+ swap(mListForFree, other.mListForFree);
+ }
+
+private:
+ // iterates the list of allocated memory to calculate how many to alloc next.
+ // Recalculating this each time saves us a size_t member.
+ // This ignores the fact that memory blocks might have been added manually with addOrFree. In
+ // practice, this should not matter much.
+ ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept {
+ auto tmp = mListForFree;
+ size_t numAllocs = MinNumAllocs;
+
+ while (numAllocs * 2 <= MaxNumAllocs && tmp) {
+ auto x = reinterpret_cast<T***>(tmp);
+ tmp = *x;
+ numAllocs *= 2;
+ }
+
+ return numAllocs;
+ }
+
+ // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree().
+ void add(void* ptr, const size_t numBytes) noexcept {
+ const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE;
+
+ auto data = reinterpret_cast<T**>(ptr);
+
+ // link free list
+ auto x = reinterpret_cast<T***>(data);
+ *x = mListForFree;
+ mListForFree = data;
+
+ // create linked list for newly allocated data
+ auto* const headT =
+ reinterpret_cast_no_cast_align_warning<T*>(reinterpret_cast<char*>(ptr) + ALIGNMENT);
+
+ auto* const head = reinterpret_cast<char*>(headT);
+
+ // Visual Studio compiler automatically unrolls this loop, which is pretty cool
+ for (size_t i = 0; i < numElements; ++i) {
+ *reinterpret_cast_no_cast_align_warning<char**>(head + i * ALIGNED_SIZE) =
+ head + (i + 1) * ALIGNED_SIZE;
+ }
+
+ // last one points to 0
+ *reinterpret_cast_no_cast_align_warning<T**>(head + (numElements - 1) * ALIGNED_SIZE) =
+ mHead;
+ mHead = headT;
+ }
+
+ // Called when no memory is available (mHead == 0).
+ // Don't inline this slow path.
+ ROBIN_HOOD(NOINLINE) T* performAllocation() {
+ size_t const numElementsToAlloc = calcNumElementsToAlloc();
+
+ // alloc new memory: [prev |T, T, ... T]
+ size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc;
+ ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE
+ << " * " << numElementsToAlloc)
+ add(assertNotNull<std::bad_alloc>(std::malloc(bytes)), bytes);
+ return mHead;
+ }
+
+ // enforce byte alignment of the T's
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14)
+ static constexpr size_t ALIGNMENT =
+ (std::max)(std::alignment_of<T>::value, std::alignment_of<T*>::value);
+#else
+ static const size_t ALIGNMENT =
+ (ROBIN_HOOD_STD::alignment_of<T>::value > ROBIN_HOOD_STD::alignment_of<T*>::value)
+ ? ROBIN_HOOD_STD::alignment_of<T>::value
+ : +ROBIN_HOOD_STD::alignment_of<T*>::value; // the + is for walkarround
+#endif
+
+ static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT;
+
+ static_assert(MinNumAllocs >= 1, "MinNumAllocs");
+ static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs");
+ static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE");
+ static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod");
+ static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT");
+
+ T* mHead{nullptr};
+ T** mListForFree{nullptr};
+};
+
+template <typename T, size_t MinSize, size_t MaxSize, bool IsFlat>
+struct NodeAllocator;
+
+// dummy allocator that does nothing
+template <typename T, size_t MinSize, size_t MaxSize>
+struct NodeAllocator<T, MinSize, MaxSize, true> {
+
+ // we are not using the data, so just free it.
+ void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept {
+ ROBIN_HOOD_LOG("std::free")
+ std::free(ptr);
+ }
+};
+
+template <typename T, size_t MinSize, size_t MaxSize>
+struct NodeAllocator<T, MinSize, MaxSize, false> : public BulkPoolAllocator<T, MinSize, MaxSize> {};
+
+// c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making
+// my own here.
+namespace swappable {
+#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17)
+using std::swap;
+template <typename T>
+struct nothrow {
+ static const bool value = noexcept(swap(std::declval<T&>(), std::declval<T&>()));
+};
+#else
+template <typename T>
+struct nothrow {
+ static const bool value = std::is_nothrow_swappable<T>::value;
+};
+#endif
+} // namespace swappable
+
+} // namespace detail
+
+struct is_transparent_tag {};
+
+// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable,
+// which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is
+// also tested.
+template <typename T1, typename T2>
+struct pair {
+ using first_type = T1;
+ using second_type = T2;
+
+ template <typename U1 = T1, typename U2 = T2,
+ typename = typename std::enable_if<std::is_default_constructible<U1>::value &&
+ std::is_default_constructible<U2>::value>::type>
+ constexpr pair() noexcept(noexcept(U1()) && noexcept(U2()))
+ : first()
+ , second() {}
+
+ // pair constructors are explicit so we don't accidentally call this ctor when we don't have to.
+ explicit constexpr pair(std::pair<T1, T2> const& o) noexcept(
+ noexcept(T1(std::declval<T1 const&>())) && noexcept(T2(std::declval<T2 const&>())))
+ : first(o.first)
+ , second(o.second) {}
+
+ // pair constructors are explicit so we don't accidentally call this ctor when we don't have to.
+ explicit constexpr pair(std::pair<T1, T2>&& o) noexcept(noexcept(
+ T1(std::move(std::declval<T1&&>()))) && noexcept(T2(std::move(std::declval<T2&&>()))))
+ : first(std::move(o.first))
+ , second(std::move(o.second)) {}
+
+ constexpr pair(T1&& a, T2&& b) noexcept(noexcept(
+ T1(std::move(std::declval<T1&&>()))) && noexcept(T2(std::move(std::declval<T2&&>()))))
+ : first(std::move(a))
+ , second(std::move(b)) {}
+
+ template <typename U1, typename U2>
+ constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward<U1>(
+ std::declval<U1&&>()))) && noexcept(T2(std::forward<U2>(std::declval<U2&&>()))))
+ : first(std::forward<U1>(a))
+ , second(std::forward<U2>(b)) {}
+
+ template <typename... U1, typename... U2>
+ // MSVC 2015 produces error "C2476: ‘constexpr’ constructor does not initialize all members"
+ // if this constructor is constexpr
+#if !ROBIN_HOOD(BROKEN_CONSTEXPR)
+ constexpr
+#endif
+ pair(std::piecewise_construct_t /*unused*/, std::tuple<U1...> a,
+ std::tuple<U2...>
+ b) noexcept(noexcept(pair(std::declval<std::tuple<U1...>&>(),
+ std::declval<std::tuple<U2...>&>(),
+ ROBIN_HOOD_STD::index_sequence_for<U1...>(),
+ ROBIN_HOOD_STD::index_sequence_for<U2...>())))
+ : pair(a, b, ROBIN_HOOD_STD::index_sequence_for<U1...>(),
+ ROBIN_HOOD_STD::index_sequence_for<U2...>()) {
+ }
+
+ // constructor called from the std::piecewise_construct_t ctor
+ template <typename... U1, size_t... I1, typename... U2, size_t... I2>
+ pair(std::tuple<U1...>& a, std::tuple<U2...>& b, ROBIN_HOOD_STD::index_sequence<I1...> /*unused*/, ROBIN_HOOD_STD::index_sequence<I2...> /*unused*/) noexcept(
+ noexcept(T1(std::forward<U1>(std::get<I1>(
+ std::declval<std::tuple<
+ U1...>&>()))...)) && noexcept(T2(std::
+ forward<U2>(std::get<I2>(
+ std::declval<std::tuple<U2...>&>()))...)))
+ : first(std::forward<U1>(std::get<I1>(a))...)
+ , second(std::forward<U2>(std::get<I2>(b))...) {
+ // make visual studio compiler happy about warning about unused a & b.
+ // Visual studio's pair implementation disables warning 4100.
+ (void)a;
+ (void)b;
+ }
+
+ void swap(pair<T1, T2>& o) noexcept((detail::swappable::nothrow<T1>::value) &&
+ (detail::swappable::nothrow<T2>::value)) {
+ using std::swap;
+ swap(first, o.first);
+ swap(second, o.second);
+ }
+
+ T1 first; // NOLINT(misc-non-private-member-variables-in-classes)
+ T2 second; // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+template <typename A, typename B>
+inline void swap(pair<A, B>& a, pair<A, B>& b) noexcept(
+ noexcept(std::declval<pair<A, B>&>().swap(std::declval<pair<A, B>&>()))) {
+ a.swap(b);
+}
+
+template <typename A, typename B>
+inline constexpr bool operator==(pair<A, B> const& x, pair<A, B> const& y) {
+ return (x.first == y.first) && (x.second == y.second);
+}
+template <typename A, typename B>
+inline constexpr bool operator!=(pair<A, B> const& x, pair<A, B> const& y) {
+ return !(x == y);
+}
+template <typename A, typename B>
+inline constexpr bool operator<(pair<A, B> const& x, pair<A, B> const& y) noexcept(noexcept(
+ std::declval<A const&>() < std::declval<A const&>()) && noexcept(std::declval<B const&>() <
+ std::declval<B const&>())) {
+ return x.first < y.first || (!(y.first < x.first) && x.second < y.second);
+}
+template <typename A, typename B>
+inline constexpr bool operator>(pair<A, B> const& x, pair<A, B> const& y) {
+ return y < x;
+}
+template <typename A, typename B>
+inline constexpr bool operator<=(pair<A, B> const& x, pair<A, B> const& y) {
+ return !(x > y);
+}
+template <typename A, typename B>
+inline constexpr bool operator>=(pair<A, B> const& x, pair<A, B> const& y) {
+ return !(x < y);
+}
+
+inline size_t hash_bytes(void const* ptr, size_t len) noexcept {
+ static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995);
+ static constexpr uint64_t seed = UINT64_C(0xe17a1465);
+ static constexpr unsigned int r = 47;
+
+ auto const* const data64 = static_cast<uint64_t const*>(ptr);
+ uint64_t h = seed ^ (len * m);
+
+ size_t const n_blocks = len / 8;
+ for (size_t i = 0; i < n_blocks; ++i) {
+ auto k = detail::unaligned_load<uint64_t>(data64 + i);
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h ^= k;
+ h *= m;
+ }
+
+ auto const* const data8 = reinterpret_cast<uint8_t const*>(data64 + n_blocks);
+ switch (len & 7U) {
+ case 7:
+ h ^= static_cast<uint64_t>(data8[6]) << 48U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 6:
+ h ^= static_cast<uint64_t>(data8[5]) << 40U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 5:
+ h ^= static_cast<uint64_t>(data8[4]) << 32U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 4:
+ h ^= static_cast<uint64_t>(data8[3]) << 24U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 3:
+ h ^= static_cast<uint64_t>(data8[2]) << 16U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 2:
+ h ^= static_cast<uint64_t>(data8[1]) << 8U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 1:
+ h ^= static_cast<uint64_t>(data8[0]);
+ h *= m;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ default:
+ break;
+ }
+
+ h ^= h >> r;
+
+ // not doing the final step here, because this will be done by keyToIdx anyways
+ // h *= m;
+ // h ^= h >> r;
+ return static_cast<size_t>(h);
+}
+
+inline size_t hash_int(uint64_t x) noexcept {
+ // tried lots of different hashes, let's stick with murmurhash3. It's simple, fast, well tested,
+ // and doesn't need any special 128bit operations.
+ x ^= x >> 33U;
+ x *= UINT64_C(0xff51afd7ed558ccd);
+ x ^= x >> 33U;
+
+ // not doing the final step here, because this will be done by keyToIdx anyways
+ // x *= UINT64_C(0xc4ceb9fe1a85ec53);
+ // x ^= x >> 33U;
+ return static_cast<size_t>(x);
+}
+
+// A thin wrapper around std::hash, performing an additional simple mixing step of the result.
+template <typename T, typename Enable = void>
+struct hash : public std::hash<T> {
+ size_t operator()(T const& obj) const
+ noexcept(noexcept(std::declval<std::hash<T>>().operator()(std::declval<T const&>()))) {
+ // call base hash
+ auto result = std::hash<T>::operator()(obj);
+ // return mixed of that, to be save against identity has
+ return hash_int(static_cast<detail::SizeT>(result));
+ }
+};
+
+template <typename CharT>
+struct hash<std::basic_string<CharT>> {
+ size_t operator()(std::basic_string<CharT> const& str) const noexcept {
+ return hash_bytes(str.data(), sizeof(CharT) * str.size());
+ }
+};
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17)
+template <typename CharT>
+struct hash<std::basic_string_view<CharT>> {
+ size_t operator()(std::basic_string_view<CharT> const& sv) const noexcept {
+ return hash_bytes(sv.data(), sizeof(CharT) * sv.size());
+ }
+};
+#endif
+
+template <class T>
+struct hash<T*> {
+ size_t operator()(T* ptr) const noexcept {
+ return hash_int(reinterpret_cast<detail::SizeT>(ptr));
+ }
+};
+
+template <class T>
+struct hash<std::unique_ptr<T>> {
+ size_t operator()(std::unique_ptr<T> const& ptr) const noexcept {
+ return hash_int(reinterpret_cast<detail::SizeT>(ptr.get()));
+ }
+};
+
+template <class T>
+struct hash<std::shared_ptr<T>> {
+ size_t operator()(std::shared_ptr<T> const& ptr) const noexcept {
+ return hash_int(reinterpret_cast<detail::SizeT>(ptr.get()));
+ }
+};
+
+template <typename Enum>
+struct hash<Enum, typename std::enable_if<std::is_enum<Enum>::value>::type> {
+ size_t operator()(Enum e) const noexcept {
+ using Underlying = typename std::underlying_type<Enum>::type;
+ return hash<Underlying>{}(static_cast<Underlying>(e));
+ }
+};
+
+#define ROBIN_HOOD_HASH_INT(T) \
+ template <> \
+ struct hash<T> { \
+ size_t operator()(T const& obj) const noexcept { \
+ return hash_int(static_cast<uint64_t>(obj)); \
+ } \
+ }
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wuseless-cast"
+#endif
+// see https://en.cppreference.com/w/cpp/utility/hash
+ROBIN_HOOD_HASH_INT(bool);
+ROBIN_HOOD_HASH_INT(char);
+ROBIN_HOOD_HASH_INT(signed char);
+ROBIN_HOOD_HASH_INT(unsigned char);
+ROBIN_HOOD_HASH_INT(char16_t);
+ROBIN_HOOD_HASH_INT(char32_t);
+#if ROBIN_HOOD(HAS_NATIVE_WCHART)
+ROBIN_HOOD_HASH_INT(wchar_t);
+#endif
+ROBIN_HOOD_HASH_INT(short);
+ROBIN_HOOD_HASH_INT(unsigned short);
+ROBIN_HOOD_HASH_INT(int);
+ROBIN_HOOD_HASH_INT(unsigned int);
+ROBIN_HOOD_HASH_INT(long);
+ROBIN_HOOD_HASH_INT(long long);
+ROBIN_HOOD_HASH_INT(unsigned long);
+ROBIN_HOOD_HASH_INT(unsigned long long);
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+namespace detail {
+
+template <typename T>
+struct void_type {
+ using type = void;
+};
+
+template <typename T, typename = void>
+struct has_is_transparent : public std::false_type {};
+
+template <typename T>
+struct has_is_transparent<T, typename void_type<typename T::is_transparent>::type>
+ : public std::true_type {};
+
+// using wrapper classes for hash and key_equal prevents the diamond problem when the same type
+// is used. see https://stackoverflow.com/a/28771920/48181
+template <typename T>
+struct WrapHash : public T {
+ WrapHash() = default;
+ explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval<T const&>())))
+ : T(o) {}
+};
+
+template <typename T>
+struct WrapKeyEqual : public T {
+ WrapKeyEqual() = default;
+ explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval<T const&>())))
+ : T(o) {}
+};
+
+// A highly optimized hashmap implementation, using the Robin Hood algorithm.
+//
+// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but
+// be about 2x faster in most cases and require much less allocations.
+//
+// This implementation uses the following memory layout:
+//
+// [Node, Node, ... Node | info, info, ... infoSentinel ]
+//
+// * Node: either a DataNode that directly has the std::pair<key, val> as member,
+// or a DataNode with a pointer to std::pair<key,val>. Which DataNode representation to use
+// depends on how fast the swap() operation is. Heuristically, this is automatically choosen
+// based on sizeof(). there are always 2^n Nodes.
+//
+// * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes.
+// Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the
+// corresponding node contains data. Set to 2 means the corresponding Node is filled, but it
+// actually belongs to the previous position and was pushed out because that place is already
+// taken.
+//
+// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the
+// need for a idx variable.
+//
+// According to STL, order of templates has effect on throughput. That's why I've moved the
+// boolean to the front.
+// https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/
+template <bool IsFlat, size_t MaxLoadFactor100, typename Key, typename T, typename Hash,
+ typename KeyEqual>
+class Table
+ : public WrapHash<Hash>,
+ public WrapKeyEqual<KeyEqual>,
+ detail::NodeAllocator<
+ typename std::conditional<
+ std::is_void<T>::value, Key,
+ robin_hood::pair<typename std::conditional<IsFlat, Key, Key const>::type, T>>::type,
+ 4, 16384, IsFlat> {
+public:
+ static constexpr bool is_flat = IsFlat;
+ static constexpr bool is_map = !std::is_void<T>::value;
+ static constexpr bool is_set = !is_map;
+ static constexpr bool is_transparent =
+ has_is_transparent<Hash>::value && has_is_transparent<KeyEqual>::value;
+
+ using key_type = Key;
+ using mapped_type = T;
+ using value_type = typename std::conditional<
+ is_set, Key,
+ robin_hood::pair<typename std::conditional<is_flat, Key, Key const>::type, T>>::type;
+ using size_type = size_t;
+ using hasher = Hash;
+ using key_equal = KeyEqual;
+ using Self = Table<IsFlat, MaxLoadFactor100, key_type, mapped_type, hasher, key_equal>;
+
+private:
+ static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100,
+ "MaxLoadFactor100 needs to be >10 && < 100");
+
+ using WHash = WrapHash<Hash>;
+ using WKeyEqual = WrapKeyEqual<KeyEqual>;
+
+ // configuration defaults
+
+ // make sure we have 8 elements, needed to quickly rehash mInfo
+ static constexpr size_t InitialNumElements = sizeof(uint64_t);
+ static constexpr uint32_t InitialInfoNumBits = 5;
+ static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits;
+ static constexpr size_t InfoMask = InitialInfoInc - 1U;
+ static constexpr uint8_t InitialInfoHashShift = 0;
+ using DataPool = detail::NodeAllocator<value_type, 4, 16384, IsFlat>;
+
+ // type needs to be wider than uint8_t.
+ using InfoType = uint32_t;
+
+ // DataNode ////////////////////////////////////////////////////////
+
+ // Primary template for the data node. We have special implementations for small and big
+ // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these
+ // on the heap so swap merely swaps a pointer.
+ template <typename M, bool>
+ class DataNode {};
+
+ // Small: just allocate on the stack.
+ template <typename M>
+ class DataNode<M, true> final {
+ public:
+ template <typename... Args>
+ explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept(
+ noexcept(value_type(std::forward<Args>(args)...)))
+ : mData(std::forward<Args>(args)...) {}
+
+ DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, true>&& n) noexcept(
+ std::is_nothrow_move_constructible<value_type>::value)
+ : mData(std::move(n.mData)) {}
+
+ // doesn't do anything
+ void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {}
+ void destroyDoNotDeallocate() noexcept {}
+
+ value_type const* operator->() const noexcept {
+ return &mData;
+ }
+ value_type* operator->() noexcept {
+ return &mData;
+ }
+
+ const value_type& operator*() const noexcept {
+ return mData;
+ }
+
+ value_type& operator*() noexcept {
+ return mData;
+ }
+
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, typename VT::first_type&>::type getFirst() noexcept {
+ return mData.first;
+ }
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, VT&>::type getFirst() noexcept {
+ return mData;
+ }
+
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, typename VT::first_type const&>::type
+ getFirst() const noexcept {
+ return mData.first;
+ }
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, VT const&>::type getFirst() const noexcept {
+ return mData;
+ }
+
+ template <typename MT = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, MT&>::type getSecond() noexcept {
+ return mData.second;
+ }
+
+ template <typename MT = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, MT const&>::type getSecond() const noexcept {
+ return mData.second;
+ }
+
+ void swap(DataNode<M, true>& o) noexcept(
+ noexcept(std::declval<value_type>().swap(std::declval<value_type>()))) {
+ mData.swap(o.mData);
+ }
+
+ private:
+ value_type mData;
+ };
+
+ // big object: allocate on heap.
+ template <typename M>
+ class DataNode<M, false> {
+ public:
+ template <typename... Args>
+ explicit DataNode(M& map, Args&&... args)
+ : mData(map.allocate()) {
+ ::new (static_cast<void*>(mData)) value_type(std::forward<Args>(args)...);
+ }
+
+ DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, false>&& n) noexcept
+ : mData(std::move(n.mData)) {}
+
+ void destroy(M& map) noexcept {
+ // don't deallocate, just put it into list of datapool.
+ mData->~value_type();
+ map.deallocate(mData);
+ }
+
+ void destroyDoNotDeallocate() noexcept {
+ mData->~value_type();
+ }
+
+ value_type const* operator->() const noexcept {
+ return mData;
+ }
+
+ value_type* operator->() noexcept {
+ return mData;
+ }
+
+ const value_type& operator*() const {
+ return *mData;
+ }
+
+ value_type& operator*() {
+ return *mData;
+ }
+
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, typename VT::first_type&>::type getFirst() noexcept {
+ return mData->first;
+ }
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, VT&>::type getFirst() noexcept {
+ return *mData;
+ }
+
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, typename VT::first_type const&>::type
+ getFirst() const noexcept {
+ return mData->first;
+ }
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, VT const&>::type getFirst() const noexcept {
+ return *mData;
+ }
+
+ template <typename MT = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, MT&>::type getSecond() noexcept {
+ return mData->second;
+ }
+
+ template <typename MT = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, MT const&>::type getSecond() const noexcept {
+ return mData->second;
+ }
+
+ void swap(DataNode<M, false>& o) noexcept {
+ using std::swap;
+ swap(mData, o.mData);
+ }
+
+ private:
+ value_type* mData;
+ };
+
+ using Node = DataNode<Self, IsFlat>;
+
+ // helpers for insertKeyPrepareEmptySpot: extract first entry (only const required)
+ ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept {
+ return n.getFirst();
+ }
+
+ // in case we have void mapped_type, we are not using a pair, thus we just route k through.
+ // No need to disable this because it's just not used if not applicable.
+ ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept {
+ return k;
+ }
+
+ // in case we have non-void mapped_type, we have a standard robin_hood::pair
+ template <typename Q = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<!std::is_void<Q>::value, key_type const&>::type
+ getFirstConst(value_type const& vt) const noexcept {
+ return vt.first;
+ }
+
+ // Cloner //////////////////////////////////////////////////////////
+
+ template <typename M, bool UseMemcpy>
+ struct Cloner;
+
+ // fast path: Just copy data, without allocating anything.
+ template <typename M>
+ struct Cloner<M, true> {
+ void operator()(M const& source, M& target) const {
+ auto const* const src = reinterpret_cast<char const*>(source.mKeyVals);
+ auto* tgt = reinterpret_cast<char*>(target.mKeyVals);
+ auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1);
+ std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt);
+ }
+ };
+
+ template <typename M>
+ struct Cloner<M, false> {
+ void operator()(M const& s, M& t) const {
+ auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1);
+ std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo);
+
+ for (size_t i = 0; i < numElementsWithBuffer; ++i) {
+ if (t.mInfo[i]) {
+ ::new (static_cast<void*>(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]);
+ }
+ }
+ }
+ };
+
+ // Destroyer ///////////////////////////////////////////////////////
+
+ template <typename M, bool IsFlatAndTrivial>
+ struct Destroyer {};
+
+ template <typename M>
+ struct Destroyer<M, true> {
+ void nodes(M& m) const noexcept {
+ m.mNumElements = 0;
+ }
+
+ void nodesDoNotDeallocate(M& m) const noexcept {
+ m.mNumElements = 0;
+ }
+ };
+
+ template <typename M>
+ struct Destroyer<M, false> {
+ void nodes(M& m) const noexcept {
+ m.mNumElements = 0;
+ // clear also resets mInfo to 0, that's sometimes not necessary.
+ auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1);
+
+ for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) {
+ if (0 != m.mInfo[idx]) {
+ Node& n = m.mKeyVals[idx];
+ n.destroy(m);
+ n.~Node();
+ }
+ }
+ }
+
+ void nodesDoNotDeallocate(M& m) const noexcept {
+ m.mNumElements = 0;
+ // clear also resets mInfo to 0, that's sometimes not necessary.
+ auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1);
+ for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) {
+ if (0 != m.mInfo[idx]) {
+ Node& n = m.mKeyVals[idx];
+ n.destroyDoNotDeallocate();
+ n.~Node();
+ }
+ }
+ }
+ };
+
+ // Iter ////////////////////////////////////////////////////////////
+
+ struct fast_forward_tag {};
+
+ // generic iterator for both const_iterator and iterator.
+ template <bool IsConst>
+ // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions)
+ class Iter {
+ private:
+ using NodePtr = typename std::conditional<IsConst, Node const*, Node*>::type;
+
+ public:
+ using difference_type = std::ptrdiff_t;
+ using value_type = typename Self::value_type;
+ using reference = typename std::conditional<IsConst, value_type const&, value_type&>::type;
+ using pointer = typename std::conditional<IsConst, value_type const*, value_type*>::type;
+ using iterator_category = std::forward_iterator_tag;
+
+ // default constructed iterator can be compared to itself, but WON'T return true when
+ // compared to end().
+ Iter() = default;
+
+ // Rule of zero: nothing specified. The conversion constructor is only enabled for
+ // iterator to const_iterator, so it doesn't accidentally work as a copy ctor.
+
+ // Conversion constructor from iterator to const_iterator.
+ template <bool OtherIsConst,
+ typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ Iter(Iter<OtherIsConst> const& other) noexcept
+ : mKeyVals(other.mKeyVals)
+ , mInfo(other.mInfo) {}
+
+ Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept
+ : mKeyVals(valPtr)
+ , mInfo(infoPtr) {}
+
+ Iter(NodePtr valPtr, uint8_t const* infoPtr,
+ fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept
+ : mKeyVals(valPtr)
+ , mInfo(infoPtr) {
+ fastForward();
+ }
+
+ template <bool OtherIsConst,
+ typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
+ Iter& operator=(Iter<OtherIsConst> const& other) noexcept {
+ mKeyVals = other.mKeyVals;
+ mInfo = other.mInfo;
+ return *this;
+ }
+
+ // prefix increment. Undefined behavior if we are at end()!
+ Iter& operator++() noexcept {
+ mInfo++;
+ mKeyVals++;
+ fastForward();
+ return *this;
+ }
+
+ Iter operator++(int) noexcept {
+ Iter tmp = *this;
+ ++(*this);
+ return tmp;
+ }
+
+ reference operator*() const {
+ return **mKeyVals;
+ }
+
+ pointer operator->() const {
+ return &**mKeyVals;
+ }
+
+ template <bool O>
+ bool operator==(Iter<O> const& o) const noexcept {
+ return mKeyVals == o.mKeyVals;
+ }
+
+ template <bool O>
+ bool operator!=(Iter<O> const& o) const noexcept {
+ return mKeyVals != o.mKeyVals;
+ }
+
+ private:
+ // fast forward to the next non-free info byte
+ // I've tried a few variants that don't depend on intrinsics, but unfortunately they are
+ // quite a bit slower than this one. So I've reverted that change again. See map_benchmark.
+ void fastForward() noexcept {
+ size_t n = 0;
+ while (0U == (n = detail::unaligned_load<size_t>(mInfo))) {
+ mInfo += sizeof(size_t);
+ mKeyVals += sizeof(size_t);
+ }
+#if defined(ROBIN_HOOD_DISABLE_INTRINSICS)
+ // we know for certain that within the next 8 bytes we'll find a non-zero one.
+ if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load<uint32_t>(mInfo))) {
+ mInfo += 4;
+ mKeyVals += 4;
+ }
+ if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load<uint16_t>(mInfo))) {
+ mInfo += 2;
+ mKeyVals += 2;
+ }
+ if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) {
+ mInfo += 1;
+ mKeyVals += 1;
+ }
+#else
+# if ROBIN_HOOD(LITTLE_ENDIAN)
+ auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8;
+# else
+ auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8;
+# endif
+ mInfo += inc;
+ mKeyVals += inc;
+#endif
+ }
+
+ friend class Table<IsFlat, MaxLoadFactor100, key_type, mapped_type, hasher, key_equal>;
+ NodePtr mKeyVals{nullptr};
+ uint8_t const* mInfo{nullptr};
+ };
+
+ ////////////////////////////////////////////////////////////////////
+
+ // highly performance relevant code.
+ // Lower bits are used for indexing into the array (2^n size)
+ // The upper 1-5 bits need to be a reasonable good hash, to save comparisons.
+ template <typename HashKey>
+ void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const {
+ // In addition to whatever hash is used, add another mul & shift so we get better hashing.
+ // This serves as a bad hash prevention, if the given data is
+ // badly mixed.
+ auto h = static_cast<uint64_t>(WHash::operator()(key));
+
+ h *= mHashMultiplier;
+ h ^= h >> 33U;
+
+ // the lower InitialInfoNumBits are reserved for info.
+ *info = mInfoInc + static_cast<InfoType>((h & InfoMask) >> mInfoHashShift);
+ *idx = (static_cast<size_t>(h) >> InitialInfoNumBits) & mMask;
+ }
+
+ // forwards the index by one, wrapping around at the end
+ void next(InfoType* info, size_t* idx) const noexcept {
+ *idx = *idx + 1;
+ *info += mInfoInc;
+ }
+
+ void nextWhileLess(InfoType* info, size_t* idx) const noexcept {
+ // unrolling this by hand did not bring any speedups.
+ while (*info < mInfo[*idx]) {
+ next(info, idx);
+ }
+ }
+
+ // Shift everything up by one element. Tries to move stuff around.
+ void
+ shiftUp(size_t startIdx,
+ size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable<Node>::value) {
+ auto idx = startIdx;
+ ::new (static_cast<void*>(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1]));
+ while (--idx != insertion_idx) {
+ mKeyVals[idx] = std::move(mKeyVals[idx - 1]);
+ }
+
+ idx = startIdx;
+ while (idx != insertion_idx) {
+ ROBIN_HOOD_COUNT(shiftUp)
+ mInfo[idx] = static_cast<uint8_t>(mInfo[idx - 1] + mInfoInc);
+ if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) {
+ mMaxNumElementsAllowed = 0;
+ }
+ --idx;
+ }
+ }
+
+ void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable<Node>::value) {
+ // until we find one that is either empty or has zero offset.
+ // TODO(martinus) we don't need to move everything, just the last one for the same
+ // bucket.
+ mKeyVals[idx].destroy(*this);
+
+ // until we find one that is either empty or has zero offset.
+ while (mInfo[idx + 1] >= 2 * mInfoInc) {
+ ROBIN_HOOD_COUNT(shiftDown)
+ mInfo[idx] = static_cast<uint8_t>(mInfo[idx + 1] - mInfoInc);
+ mKeyVals[idx] = std::move(mKeyVals[idx + 1]);
+ ++idx;
+ }
+
+ mInfo[idx] = 0;
+ // don't destroy, we've moved it
+ // mKeyVals[idx].destroy(*this);
+ mKeyVals[idx].~Node();
+ }
+
+ // copy of find(), except that it returns iterator instead of const_iterator.
+ template <typename Other>
+ ROBIN_HOOD(NODISCARD)
+ size_t findIdx(Other const& key) const {
+ size_t idx{};
+ InfoType info{};
+ keyToIdx(key, &idx, &info);
+
+ do {
+ // unrolling this twice gives a bit of a speedup. More unrolling did not help.
+ if (info == mInfo[idx] &&
+ ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) {
+ return idx;
+ }
+ next(&info, &idx);
+ if (info == mInfo[idx] &&
+ ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) {
+ return idx;
+ }
+ next(&info, &idx);
+ } while (info <= mInfo[idx]);
+
+ // nothing found!
+ return mMask == 0 ? 0
+ : static_cast<size_t>(std::distance(
+ mKeyVals, reinterpret_cast_no_cast_align_warning<Node*>(mInfo)));
+ }
+
+ void cloneData(const Table& o) {
+ Cloner<Table, IsFlat && ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(Node)>()(o, *this);
+ }
+
+ // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized.
+ // @return True on success, false if something went wrong
+ void insert_move(Node&& keyval) {
+ // we don't retry, fail if overflowing
+ // don't need to check max num elements
+ if (0 == mMaxNumElementsAllowed && !try_increase_info()) {
+ throwOverflowError();
+ }
+
+ size_t idx{};
+ InfoType info{};
+ keyToIdx(keyval.getFirst(), &idx, &info);
+
+ // skip forward. Use <= because we are certain that the element is not there.
+ while (info <= mInfo[idx]) {
+ idx = idx + 1;
+ info += mInfoInc;
+ }
+
+ // key not found, so we are now exactly where we want to insert it.
+ auto const insertion_idx = idx;
+ auto const insertion_info = static_cast<uint8_t>(info);
+ if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) {
+ mMaxNumElementsAllowed = 0;
+ }
+
+ // find an empty spot
+ while (0 != mInfo[idx]) {
+ next(&info, &idx);
+ }
+
+ auto& l = mKeyVals[insertion_idx];
+ if (idx == insertion_idx) {
+ ::new (static_cast<void*>(&l)) Node(std::move(keyval));
+ } else {
+ shiftUp(idx, insertion_idx);
+ l = std::move(keyval);
+ }
+
+ // put at empty spot
+ mInfo[insertion_idx] = insertion_info;
+
+ ++mNumElements;
+ }
+
+public:
+ using iterator = Iter<false>;
+ using const_iterator = Iter<true>;
+
+ Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual()))
+ : WHash()
+ , WKeyEqual() {
+ ROBIN_HOOD_TRACE(this)
+ }
+
+ // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert.
+ // This tremendously speeds up ctor & dtor of a map that never receives an element. The
+ // penalty is payed at the first insert, and not before. Lookup of this empty map works
+ // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the
+ // standard, but we can ignore it.
+ explicit Table(
+ size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{},
+ const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal)))
+ : WHash(h)
+ , WKeyEqual(equal) {
+ ROBIN_HOOD_TRACE(this)
+ }
+
+ template <typename Iter>
+ Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0,
+ const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{})
+ : WHash(h)
+ , WKeyEqual(equal) {
+ ROBIN_HOOD_TRACE(this)
+ insert(first, last);
+ }
+
+ Table(std::initializer_list<value_type> initlist,
+ size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{},
+ const KeyEqual& equal = KeyEqual{})
+ : WHash(h)
+ , WKeyEqual(equal) {
+ ROBIN_HOOD_TRACE(this)
+ insert(initlist.begin(), initlist.end());
+ }
+
+ Table(Table&& o) noexcept
+ : WHash(std::move(static_cast<WHash&>(o)))
+ , WKeyEqual(std::move(static_cast<WKeyEqual&>(o)))
+ , DataPool(std::move(static_cast<DataPool&>(o))) {
+ ROBIN_HOOD_TRACE(this)
+ if (o.mMask) {
+ mHashMultiplier = std::move(o.mHashMultiplier);
+ mKeyVals = std::move(o.mKeyVals);
+ mInfo = std::move(o.mInfo);
+ mNumElements = std::move(o.mNumElements);
+ mMask = std::move(o.mMask);
+ mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
+ mInfoInc = std::move(o.mInfoInc);
+ mInfoHashShift = std::move(o.mInfoHashShift);
+ // set other's mask to 0 so its destructor won't do anything
+ o.init();
+ }
+ }
+
+ Table& operator=(Table&& o) noexcept {
+ ROBIN_HOOD_TRACE(this)
+ if (&o != this) {
+ if (o.mMask) {
+ // only move stuff if the other map actually has some data
+ destroy();
+ mHashMultiplier = std::move(o.mHashMultiplier);
+ mKeyVals = std::move(o.mKeyVals);
+ mInfo = std::move(o.mInfo);
+ mNumElements = std::move(o.mNumElements);
+ mMask = std::move(o.mMask);
+ mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
+ mInfoInc = std::move(o.mInfoInc);
+ mInfoHashShift = std::move(o.mInfoHashShift);
+ WHash::operator=(std::move(static_cast<WHash&>(o)));
+ WKeyEqual::operator=(std::move(static_cast<WKeyEqual&>(o)));
+ DataPool::operator=(std::move(static_cast<DataPool&>(o)));
+
+ o.init();
+
+ } else {
+ // nothing in the other map => just clear us.
+ clear();
+ }
+ }
+ return *this;
+ }
+
+ Table(const Table& o)
+ : WHash(static_cast<const WHash&>(o))
+ , WKeyEqual(static_cast<const WKeyEqual&>(o))
+ , DataPool(static_cast<const DataPool&>(o)) {
+ ROBIN_HOOD_TRACE(this)
+ if (!o.empty()) {
+ // not empty: create an exact copy. it is also possible to just iterate through all
+ // elements and insert them, but copying is probably faster.
+
+ auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1);
+ auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer);
+
+ ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal("
+ << numElementsWithBuffer << ")")
+ mHashMultiplier = o.mHashMultiplier;
+ mKeyVals = static_cast<Node*>(
+ detail::assertNotNull<std::bad_alloc>(std::malloc(numBytesTotal)));
+ // no need for calloc because clonData does memcpy
+ mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
+ mNumElements = o.mNumElements;
+ mMask = o.mMask;
+ mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
+ mInfoInc = o.mInfoInc;
+ mInfoHashShift = o.mInfoHashShift;
+ cloneData(o);
+ }
+ }
+
+ // Creates a copy of the given map. Copy constructor of each entry is used.
+ // Not sure why clang-tidy thinks this doesn't handle self assignment, it does
+ // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp)
+ Table& operator=(Table const& o) {
+ ROBIN_HOOD_TRACE(this)
+ if (&o == this) {
+ // prevent assigning of itself
+ return *this;
+ }
+
+ // we keep using the old allocator and not assign the new one, because we want to keep
+ // the memory available. when it is the same size.
+ if (o.empty()) {
+ if (0 == mMask) {
+ // nothing to do, we are empty too
+ return *this;
+ }
+
+ // not empty: destroy what we have there
+ // clear also resets mInfo to 0, that's sometimes not necessary.
+ destroy();
+ init();
+ WHash::operator=(static_cast<const WHash&>(o));
+ WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
+ DataPool::operator=(static_cast<DataPool const&>(o));
+
+ return *this;
+ }
+
+ // clean up old stuff
+ Destroyer<Self, IsFlat && std::is_trivially_destructible<Node>::value>{}.nodes(*this);
+
+ if (mMask != o.mMask) {
+ // no luck: we don't have the same array size allocated, so we need to realloc.
+ if (0 != mMask) {
+ // only deallocate if we actually have data!
+ ROBIN_HOOD_LOG("std::free")
+ std::free(mKeyVals);
+ }
+
+ auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1);
+ auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer);
+ ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal("
+ << numElementsWithBuffer << ")")
+ mKeyVals = static_cast<Node*>(
+ detail::assertNotNull<std::bad_alloc>(std::malloc(numBytesTotal)));
+
+ // no need for calloc here because cloneData performs a memcpy.
+ mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
+ // sentinel is set in cloneData
+ }
+ WHash::operator=(static_cast<const WHash&>(o));
+ WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
+ DataPool::operator=(static_cast<DataPool const&>(o));
+ mHashMultiplier = o.mHashMultiplier;
+ mNumElements = o.mNumElements;
+ mMask = o.mMask;
+ mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
+ mInfoInc = o.mInfoInc;
+ mInfoHashShift = o.mInfoHashShift;
+ cloneData(o);
+
+ return *this;
+ }
+
+ // Swaps everything between the two maps.
+ void swap(Table& o) {
+ ROBIN_HOOD_TRACE(this)
+ using std::swap;
+ swap(o, *this);
+ }
+
+ // Clears all data, without resizing.
+ void clear() {
+ ROBIN_HOOD_TRACE(this)
+ if (empty()) {
+ // don't do anything! also important because we don't want to write to
+ // DummyInfoByte::b, even though we would just write 0 to it.
+ return;
+ }
+
+ Destroyer<Self, IsFlat && std::is_trivially_destructible<Node>::value>{}.nodes(*this);
+
+ auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1);
+ // clear everything, then set the sentinel again
+ uint8_t const z = 0;
+ std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z);
+ mInfo[numElementsWithBuffer] = 1;
+
+ mInfoInc = InitialInfoInc;
+ mInfoHashShift = InitialInfoHashShift;
+ }
+
+ // Destroys the map and all it's contents.
+ ~Table() {
+ ROBIN_HOOD_TRACE(this)
+ destroy();
+ }
+
+ // Checks if both tables contain the same entries. Order is irrelevant.
+ bool operator==(const Table& other) const {
+ ROBIN_HOOD_TRACE(this)
+ if (other.size() != size()) {
+ return false;
+ }
+ for (auto const& otherEntry : other) {
+ if (!has(otherEntry)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool operator!=(const Table& other) const {
+ ROBIN_HOOD_TRACE(this)
+ return !operator==(other);
+ }
+
+ template <typename Q = mapped_type>
+ typename std::enable_if<!std::is_void<Q>::value, Q&>::type operator[](const key_type& key) {
+ ROBIN_HOOD_TRACE(this)
+ auto idxAndState = insertKeyPrepareEmptySpot(key);
+ switch (idxAndState.second) {
+ case InsertionState::key_found:
+ break;
+
+ case InsertionState::new_node:
+ ::new (static_cast<void*>(&mKeyVals[idxAndState.first]))
+ Node(*this, std::piecewise_construct, std::forward_as_tuple(key),
+ std::forward_as_tuple());
+ break;
+
+ case InsertionState::overwrite_node:
+ mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct,
+ std::forward_as_tuple(key), std::forward_as_tuple());
+ break;
+
+ case InsertionState::overflow_error:
+ throwOverflowError();
+ }
+
+ return mKeyVals[idxAndState.first].getSecond();
+ }
+
+ template <typename Q = mapped_type>
+ typename std::enable_if<!std::is_void<Q>::value, Q&>::type operator[](key_type&& key) {
+ ROBIN_HOOD_TRACE(this)
+ auto idxAndState = insertKeyPrepareEmptySpot(key);
+ switch (idxAndState.second) {
+ case InsertionState::key_found:
+ break;
+
+ case InsertionState::new_node:
+ ::new (static_cast<void*>(&mKeyVals[idxAndState.first]))
+ Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)),
+ std::forward_as_tuple());
+ break;
+
+ case InsertionState::overwrite_node:
+ mKeyVals[idxAndState.first] =
+ Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)),
+ std::forward_as_tuple());
+ break;
+
+ case InsertionState::overflow_error:
+ throwOverflowError();
+ }
+
+ return mKeyVals[idxAndState.first].getSecond();
+ }
+
+ template <typename Iter>
+ void insert(Iter first, Iter last) {
+ for (; first != last; ++first) {
+ // value_type ctor needed because this might be called with std::pair's
+ insert(value_type(*first));
+ }
+ }
+
+ void insert(std::initializer_list<value_type> ilist) {
+ for (auto&& vt : ilist) {
+ insert(std::move(vt));
+ }
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> emplace(Args&&... args) {
+ ROBIN_HOOD_TRACE(this)
+ Node n{*this, std::forward<Args>(args)...};
+ auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n));
+ switch (idxAndState.second) {
+ case InsertionState::key_found:
+ n.destroy(*this);
+ break;
+
+ case InsertionState::new_node:
+ ::new (static_cast<void*>(&mKeyVals[idxAndState.first])) Node(*this, std::move(n));
+ break;
+
+ case InsertionState::overwrite_node:
+ mKeyVals[idxAndState.first] = std::move(n);
+ break;
+
+ case InsertionState::overflow_error:
+ n.destroy(*this);
+ throwOverflowError();
+ break;
+ }
+
+ return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first),
+ InsertionState::key_found != idxAndState.second);
+ }
+
+ template <typename... Args>
+ iterator emplace_hint(const_iterator position, Args&&... args) {
+ (void)position;
+ return emplace(std::forward<Args>(args)...).first;
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
+ return try_emplace_impl(key, std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> try_emplace(key_type&& key, Args&&... args) {
+ return try_emplace_impl(std::move(key), std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) {
+ (void)hint;
+ return try_emplace_impl(key, std::forward<Args>(args)...).first;
+ }
+
+ template <typename... Args>
+ iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) {
+ (void)hint;
+ return try_emplace_impl(std::move(key), std::forward<Args>(args)...).first;
+ }
+
+ template <typename Mapped>
+ std::pair<iterator, bool> insert_or_assign(const key_type& key, Mapped&& obj) {
+ return insertOrAssignImpl(key, std::forward<Mapped>(obj));
+ }
+
+ template <typename Mapped>
+ std::pair<iterator, bool> insert_or_assign(key_type&& key, Mapped&& obj) {
+ return insertOrAssignImpl(std::move(key), std::forward<Mapped>(obj));
+ }
+
+ template <typename Mapped>
+ iterator insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) {
+ (void)hint;
+ return insertOrAssignImpl(key, std::forward<Mapped>(obj)).first;
+ }
+
+ template <typename Mapped>
+ iterator insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) {
+ (void)hint;
+ return insertOrAssignImpl(std::move(key), std::forward<Mapped>(obj)).first;
+ }
+
+ std::pair<iterator, bool> insert(const value_type& keyval) {
+ ROBIN_HOOD_TRACE(this)
+ return emplace(keyval);
+ }
+
+ iterator insert(const_iterator hint, const value_type& keyval) {
+ (void)hint;
+ return emplace(keyval).first;
+ }
+
+ std::pair<iterator, bool> insert(value_type&& keyval) {
+ return emplace(std::move(keyval));
+ }
+
+ iterator insert(const_iterator hint, value_type&& keyval) {
+ (void)hint;
+ return emplace(std::move(keyval)).first;
+ }
+
+ // Returns 1 if key is found, 0 otherwise.
+ size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ auto kv = mKeyVals + findIdx(key);
+ if (kv != reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ template <typename OtherKey, typename Self_ = Self>
+ // NOLINTNEXTLINE(modernize-use-nodiscard)
+ typename std::enable_if<Self_::is_transparent, size_t>::type count(const OtherKey& key) const {
+ ROBIN_HOOD_TRACE(this)
+ auto kv = mKeyVals + findIdx(key);
+ if (kv != reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard)
+ return 1U == count(key);
+ }
+
+ template <typename OtherKey, typename Self_ = Self>
+ // NOLINTNEXTLINE(modernize-use-nodiscard)
+ typename std::enable_if<Self_::is_transparent, bool>::type contains(const OtherKey& key) const {
+ return 1U == count(key);
+ }
+
+ // Returns a reference to the value found for key.
+ // Throws std::out_of_range if element cannot be found
+ template <typename Q = mapped_type>
+ // NOLINTNEXTLINE(modernize-use-nodiscard)
+ typename std::enable_if<!std::is_void<Q>::value, Q&>::type at(key_type const& key) {
+ ROBIN_HOOD_TRACE(this)
+ auto kv = mKeyVals + findIdx(key);
+ if (kv == reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+ doThrow<std::out_of_range>("key not found");
+ }
+ return kv->getSecond();
+ }
+
+ // Returns a reference to the value found for key.
+ // Throws std::out_of_range if element cannot be found
+ template <typename Q = mapped_type>
+ // NOLINTNEXTLINE(modernize-use-nodiscard)
+ typename std::enable_if<!std::is_void<Q>::value, Q const&>::type at(key_type const& key) const {
+ ROBIN_HOOD_TRACE(this)
+ auto kv = mKeyVals + findIdx(key);
+ if (kv == reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+ doThrow<std::out_of_range>("key not found");
+ }
+ return kv->getSecond();
+ }
+
+ const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ const size_t idx = findIdx(key);
+ return const_iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey>
+ const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const {
+ ROBIN_HOOD_TRACE(this)
+ const size_t idx = findIdx(key);
+ return const_iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey, typename Self_ = Self>
+ typename std::enable_if<Self_::is_transparent, // NOLINT(modernize-use-nodiscard)
+ const_iterator>::type // NOLINT(modernize-use-nodiscard)
+ find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ const size_t idx = findIdx(key);
+ return const_iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ iterator find(const key_type& key) {
+ ROBIN_HOOD_TRACE(this)
+ const size_t idx = findIdx(key);
+ return iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey>
+ iterator find(const OtherKey& key, is_transparent_tag /*unused*/) {
+ ROBIN_HOOD_TRACE(this)
+ const size_t idx = findIdx(key);
+ return iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey, typename Self_ = Self>
+ typename std::enable_if<Self_::is_transparent, iterator>::type find(const OtherKey& key) {
+ ROBIN_HOOD_TRACE(this)
+ const size_t idx = findIdx(key);
+ return iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ iterator begin() {
+ ROBIN_HOOD_TRACE(this)
+ if (empty()) {
+ return end();
+ }
+ return iterator(mKeyVals, mInfo, fast_forward_tag{});
+ }
+ const_iterator begin() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return cbegin();
+ }
+ const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ if (empty()) {
+ return cend();
+ }
+ return const_iterator(mKeyVals, mInfo, fast_forward_tag{});
+ }
+
+ iterator end() {
+ ROBIN_HOOD_TRACE(this)
+ // no need to supply valid info pointer: end() must not be dereferenced, and only node
+ // pointer is compared.
+ return iterator{reinterpret_cast_no_cast_align_warning<Node*>(mInfo), nullptr};
+ }
+ const_iterator end() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return cend();
+ }
+ const_iterator cend() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return const_iterator{reinterpret_cast_no_cast_align_warning<Node*>(mInfo), nullptr};
+ }
+
+ iterator erase(const_iterator pos) {
+ ROBIN_HOOD_TRACE(this)
+ // its safe to perform const cast here
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+ return erase(iterator{const_cast<Node*>(pos.mKeyVals), const_cast<uint8_t*>(pos.mInfo)});
+ }
+
+ // Erases element at pos, returns iterator to the next element.
+ iterator erase(iterator pos) {
+ ROBIN_HOOD_TRACE(this)
+ // we assume that pos always points to a valid entry, and not end().
+ auto const idx = static_cast<size_t>(pos.mKeyVals - mKeyVals);
+
+ shiftDown(idx);
+ --mNumElements;
+
+ if (*pos.mInfo) {
+ // we've backward shifted, return this again
+ return pos;
+ }
+
+ // no backward shift, return next element
+ return ++pos;
+ }
+
+ size_t erase(const key_type& key) {
+ ROBIN_HOOD_TRACE(this)
+ size_t idx{};
+ InfoType info{};
+ keyToIdx(key, &idx, &info);
+
+ // check while info matches with the source idx
+ do {
+ if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+ shiftDown(idx);
+ --mNumElements;
+ return 1;
+ }
+ next(&info, &idx);
+ } while (info <= mInfo[idx]);
+
+ // nothing found to delete
+ return 0;
+ }
+
+ // reserves space for the specified number of elements. Makes sure the old data fits.
+ // exactly the same as reserve(c).
+ void rehash(size_t c) {
+ // forces a reserve
+ reserve(c, true);
+ }
+
+ // reserves space for the specified number of elements. Makes sure the old data fits.
+ // Exactly the same as rehash(c). Use rehash(0) to shrink to fit.
+ void reserve(size_t c) {
+ // reserve, but don't force rehash
+ reserve(c, false);
+ }
+
+ // If possible reallocates the map to a smaller one. This frees the underlying table.
+ // Does not do anything if load_factor is too large for decreasing the table's size.
+ void compact() {
+ ROBIN_HOOD_TRACE(this)
+ auto newSize = InitialNumElements;
+ while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) {
+ newSize *= 2;
+ }
+ if (ROBIN_HOOD_UNLIKELY(newSize == 0)) {
+ throwOverflowError();
+ }
+
+ ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1")
+
+ // only actually do anything when the new size is bigger than the old one. This prevents to
+ // continuously allocate for each reserve() call.
+ if (newSize < mMask + 1) {
+ rehashPowerOfTwo(newSize, true);
+ }
+ }
+
+ size_type size() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return mNumElements;
+ }
+
+ size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return static_cast<size_type>(-1);
+ }
+
+ ROBIN_HOOD(NODISCARD) bool empty() const noexcept {
+ ROBIN_HOOD_TRACE(this)
+ return 0 == mNumElements;
+ }
+
+ float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return MaxLoadFactor100 / 100.0F;
+ }
+
+ // Average number of elements per bucket. Since we allow only 1 per bucket
+ float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return static_cast<float>(size()) / static_cast<float>(mMask + 1);
+ }
+
+ ROBIN_HOOD(NODISCARD) size_t mask() const noexcept {
+ ROBIN_HOOD_TRACE(this)
+ return mMask;
+ }
+
+ ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept {
+ if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits<size_t>::max)() / 100)) {
+ return maxElements * MaxLoadFactor100 / 100;
+ }
+
+ // we might be a bit inprecise, but since maxElements is quite large that doesn't matter
+ return (maxElements / 100) * MaxLoadFactor100;
+ }
+
+ ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept {
+ // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load
+ // 64bit types.
+ return numElements + sizeof(uint64_t);
+ }
+
+ ROBIN_HOOD(NODISCARD)
+ size_t calcNumElementsWithBuffer(size_t numElements) const noexcept {
+ auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements);
+ return numElements + (std::min)(maxNumElementsAllowed, (static_cast<size_t>(0xFF)));
+ }
+
+ // calculation only allowed for 2^n values
+ ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const {
+#if ROBIN_HOOD(BITNESS) == 64
+ return numElements * sizeof(Node) + calcNumBytesInfo(numElements);
+#else
+ // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows.
+ auto const ne = static_cast<uint64_t>(numElements);
+ auto const s = static_cast<uint64_t>(sizeof(Node));
+ auto const infos = static_cast<uint64_t>(calcNumBytesInfo(numElements));
+
+ auto const total64 = ne * s + infos;
+ auto const total = static_cast<size_t>(total64);
+
+ if (ROBIN_HOOD_UNLIKELY(static_cast<uint64_t>(total) != total64)) {
+ throwOverflowError();
+ }
+ return total;
+#endif
+ }
+
+private:
+ template <typename Q = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<!std::is_void<Q>::value, bool>::type has(const value_type& e) const {
+ ROBIN_HOOD_TRACE(this)
+ auto it = find(e.first);
+ return it != end() && it->second == e.second;
+ }
+
+ template <typename Q = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<std::is_void<Q>::value, bool>::type has(const value_type& e) const {
+ ROBIN_HOOD_TRACE(this)
+ return find(e) != end();
+ }
+
+ void reserve(size_t c, bool forceRehash) {
+ ROBIN_HOOD_TRACE(this)
+ auto const minElementsAllowed = (std::max)(c, mNumElements);
+ auto newSize = InitialNumElements;
+ while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) {
+ newSize *= 2;
+ }
+ if (ROBIN_HOOD_UNLIKELY(newSize == 0)) {
+ throwOverflowError();
+ }
+
+ ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1")
+
+ // only actually do anything when the new size is bigger than the old one. This prevents to
+ // continuously allocate for each reserve() call.
+ if (forceRehash || newSize > mMask + 1) {
+ rehashPowerOfTwo(newSize, false);
+ }
+ }
+
+ // reserves space for at least the specified number of elements.
+ // only works if numBuckets if power of two
+ // True on success, false otherwise
+ void rehashPowerOfTwo(size_t numBuckets, bool forceFree) {
+ ROBIN_HOOD_TRACE(this)
+
+ Node* const oldKeyVals = mKeyVals;
+ uint8_t const* const oldInfo = mInfo;
+
+ const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1);
+
+ // resize operation: move stuff
+ initData(numBuckets);
+ if (oldMaxElementsWithBuffer > 1) {
+ for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) {
+ if (oldInfo[i] != 0) {
+ // might throw an exception, which is really bad since we are in the middle of
+ // moving stuff.
+ insert_move(std::move(oldKeyVals[i]));
+ // destroy the node but DON'T destroy the data.
+ oldKeyVals[i].~Node();
+ }
+ }
+
+ // this check is not necessary as it's guarded by the previous if, but it helps
+ // silence g++'s overeager "attempt to free a non-heap object 'map'
+ // [-Werror=free-nonheap-object]" warning.
+ if (oldKeyVals != reinterpret_cast_no_cast_align_warning<Node*>(&mMask)) {
+ // don't destroy old data: put it into the pool instead
+ if (forceFree) {
+ std::free(oldKeyVals);
+ } else {
+ DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer));
+ }
+ }
+ }
+ }
+
+ ROBIN_HOOD(NOINLINE) void throwOverflowError() const {
+#if ROBIN_HOOD(HAS_EXCEPTIONS)
+ throw std::overflow_error("robin_hood::map overflow");
+#else
+ abort();
+#endif
+ }
+
+ template <typename OtherKey, typename... Args>
+ std::pair<iterator, bool> try_emplace_impl(OtherKey&& key, Args&&... args) {
+ ROBIN_HOOD_TRACE(this)
+ auto idxAndState = insertKeyPrepareEmptySpot(key);
+ switch (idxAndState.second) {
+ case InsertionState::key_found:
+ break;
+
+ case InsertionState::new_node:
+ ::new (static_cast<void*>(&mKeyVals[idxAndState.first])) Node(
+ *this, std::piecewise_construct, std::forward_as_tuple(std::forward<OtherKey>(key)),
+ std::forward_as_tuple(std::forward<Args>(args)...));
+ break;
+
+ case InsertionState::overwrite_node:
+ mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct,
+ std::forward_as_tuple(std::forward<OtherKey>(key)),
+ std::forward_as_tuple(std::forward<Args>(args)...));
+ break;
+
+ case InsertionState::overflow_error:
+ throwOverflowError();
+ break;
+ }
+
+ return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first),
+ InsertionState::key_found != idxAndState.second);
+ }
+
+ template <typename OtherKey, typename Mapped>
+ std::pair<iterator, bool> insertOrAssignImpl(OtherKey&& key, Mapped&& obj) {
+ ROBIN_HOOD_TRACE(this)
+ auto idxAndState = insertKeyPrepareEmptySpot(key);
+ switch (idxAndState.second) {
+ case InsertionState::key_found:
+ mKeyVals[idxAndState.first].getSecond() = std::forward<Mapped>(obj);
+ break;
+
+ case InsertionState::new_node:
+ ::new (static_cast<void*>(&mKeyVals[idxAndState.first])) Node(
+ *this, std::piecewise_construct, std::forward_as_tuple(std::forward<OtherKey>(key)),
+ std::forward_as_tuple(std::forward<Mapped>(obj)));
+ break;
+
+ case InsertionState::overwrite_node:
+ mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct,
+ std::forward_as_tuple(std::forward<OtherKey>(key)),
+ std::forward_as_tuple(std::forward<Mapped>(obj)));
+ break;
+
+ case InsertionState::overflow_error:
+ throwOverflowError();
+ break;
+ }
+
+ return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first),
+ InsertionState::key_found != idxAndState.second);
+ }
+
+ void initData(size_t max_elements) {
+ mNumElements = 0;
+ mMask = max_elements - 1;
+ mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements);
+
+ auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements);
+
+ // malloc & zero mInfo. Faster than calloc everything.
+ auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer);
+ ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal("
+ << numElementsWithBuffer << ")")
+ mKeyVals = reinterpret_cast<Node*>(
+ detail::assertNotNull<std::bad_alloc>(std::malloc(numBytesTotal)));
+ mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
+ std::memset(mInfo, 0, numBytesTotal - numElementsWithBuffer * sizeof(Node));
+
+ // set sentinel
+ mInfo[numElementsWithBuffer] = 1;
+
+ mInfoInc = InitialInfoInc;
+ mInfoHashShift = InitialInfoHashShift;
+ }
+
+ enum class InsertionState { overflow_error, key_found, new_node, overwrite_node };
+
+ // Finds key, and if not already present prepares a spot where to pot the key & value.
+ // This potentially shifts nodes out of the way, updates mInfo and number of inserted
+ // elements, so the only operation left to do is create/assign a new node at that spot.
+ template <typename OtherKey>
+ std::pair<size_t, InsertionState> insertKeyPrepareEmptySpot(OtherKey&& key) {
+ for (int i = 0; i < 256; ++i) {
+ size_t idx{};
+ InfoType info{};
+ keyToIdx(key, &idx, &info);
+ nextWhileLess(&info, &idx);
+
+ // while we potentially have a match
+ while (info == mInfo[idx]) {
+ if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+ // key already exists, do NOT insert.
+ // see http://en.cppreference.com/w/cpp/container/unordered_map/insert
+ return std::make_pair(idx, InsertionState::key_found);
+ }
+ next(&info, &idx);
+ }
+
+ // unlikely that this evaluates to true
+ if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) {
+ if (!increase_size()) {
+ return std::make_pair(size_t(0), InsertionState::overflow_error);
+ }
+ continue;
+ }
+
+ // key not found, so we are now exactly where we want to insert it.
+ auto const insertion_idx = idx;
+ auto const insertion_info = info;
+ if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) {
+ mMaxNumElementsAllowed = 0;
+ }
+
+ // find an empty spot
+ while (0 != mInfo[idx]) {
+ next(&info, &idx);
+ }
+
+ if (idx != insertion_idx) {
+ shiftUp(idx, insertion_idx);
+ }
+ // put at empty spot
+ mInfo[insertion_idx] = static_cast<uint8_t>(insertion_info);
+ ++mNumElements;
+ return std::make_pair(insertion_idx, idx == insertion_idx
+ ? InsertionState::new_node
+ : InsertionState::overwrite_node);
+ }
+
+ // enough attempts failed, so finally give up.
+ return std::make_pair(size_t(0), InsertionState::overflow_error);
+ }
+
+ bool try_increase_info() {
+ ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements
+ << ", maxNumElementsAllowed="
+ << calcMaxNumElementsAllowed(mMask + 1))
+ if (mInfoInc <= 2) {
+ // need to be > 2 so that shift works (otherwise undefined behavior!)
+ return false;
+ }
+ // we got space left, try to make info smaller
+ mInfoInc = static_cast<uint8_t>(mInfoInc >> 1U);
+
+ // remove one bit of the hash, leaving more space for the distance info.
+ // This is extremely fast because we can operate on 8 bytes at once.
+ ++mInfoHashShift;
+ auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1);
+
+ for (size_t i = 0; i < numElementsWithBuffer; i += 8) {
+ auto val = unaligned_load<uint64_t>(mInfo + i);
+ val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f);
+ std::memcpy(mInfo + i, &val, sizeof(val));
+ }
+ // update sentinel, which might have been cleared out!
+ mInfo[numElementsWithBuffer] = 1;
+
+ mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1);
+ return true;
+ }
+
+ // True if resize was possible, false otherwise
+ bool increase_size() {
+ // nothing allocated yet? just allocate InitialNumElements
+ if (0 == mMask) {
+ initData(InitialNumElements);
+ return true;
+ }
+
+ auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1);
+ if (mNumElements < maxNumElementsAllowed && try_increase_info()) {
+ return true;
+ }
+
+ ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed="
+ << maxNumElementsAllowed << ", load="
+ << (static_cast<double>(mNumElements) * 100.0 /
+ (static_cast<double>(mMask) + 1)))
+
+ if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) {
+ // we have to resize, even though there would still be plenty of space left!
+ // Try to rehash instead. Delete freed memory so we don't steadyily increase mem in case
+ // we have to rehash a few times
+ nextHashMultiplier();
+ rehashPowerOfTwo(mMask + 1, true);
+ } else {
+ // we've reached the capacity of the map, so the hash seems to work nice. Keep using it.
+ rehashPowerOfTwo((mMask + 1) * 2, false);
+ }
+ return true;
+ }
+
+ void nextHashMultiplier() {
+ // adding an *even* number, so that the multiplier will always stay odd. This is necessary
+ // so that the hash stays a mixing function (and thus doesn't have any information loss).
+ mHashMultiplier += UINT64_C(0xc4ceb9fe1a85ec54);
+ }
+
+ void destroy() {
+ if (0 == mMask) {
+ // don't deallocate!
+ return;
+ }
+
+ Destroyer<Self, IsFlat && std::is_trivially_destructible<Node>::value>{}
+ .nodesDoNotDeallocate(*this);
+
+ // This protection against not deleting mMask shouldn't be needed as it's sufficiently
+ // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise
+ // reports a compile error: attempt to free a non-heap object 'fm'
+ // [-Werror=free-nonheap-object]
+ if (mKeyVals != reinterpret_cast_no_cast_align_warning<Node*>(&mMask)) {
+ ROBIN_HOOD_LOG("std::free")
+ std::free(mKeyVals);
+ }
+ }
+
+ void init() noexcept {
+ mKeyVals = reinterpret_cast_no_cast_align_warning<Node*>(&mMask);
+ mInfo = reinterpret_cast<uint8_t*>(&mMask);
+ mNumElements = 0;
+ mMask = 0;
+ mMaxNumElementsAllowed = 0;
+ mInfoInc = InitialInfoInc;
+ mInfoHashShift = InitialInfoHashShift;
+ }
+
+ // members are sorted so no padding occurs
+ uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8
+ Node* mKeyVals = reinterpret_cast_no_cast_align_warning<Node*>(&mMask); // 8 byte 16
+ uint8_t* mInfo = reinterpret_cast<uint8_t*>(&mMask); // 8 byte 24
+ size_t mNumElements = 0; // 8 byte 32
+ size_t mMask = 0; // 8 byte 40
+ size_t mMaxNumElementsAllowed = 0; // 8 byte 48
+ InfoType mInfoInc = InitialInfoInc; // 4 byte 52
+ InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 56
+ // 16 byte 56 if NodeAllocator
+};
+
+} // namespace detail
+
+// map
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+ typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_flat_map = detail::Table<true, MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+ typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_node_map = detail::Table<false, MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+ typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_map =
+ detail::Table<sizeof(robin_hood::pair<Key, T>) <= sizeof(size_t) * 6 &&
+ std::is_nothrow_move_constructible<robin_hood::pair<Key, T>>::value &&
+ std::is_nothrow_move_assignable<robin_hood::pair<Key, T>>::value,
+ MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+// set
+
+template <typename Key, typename Hash = hash<Key>, typename KeyEqual = std::equal_to<Key>,
+ size_t MaxLoadFactor100 = 80>
+using unordered_flat_set = detail::Table<true, MaxLoadFactor100, Key, void, Hash, KeyEqual>;
+
+template <typename Key, typename Hash = hash<Key>, typename KeyEqual = std::equal_to<Key>,
+ size_t MaxLoadFactor100 = 80>
+using unordered_node_set = detail::Table<false, MaxLoadFactor100, Key, void, Hash, KeyEqual>;
+
+template <typename Key, typename Hash = hash<Key>, typename KeyEqual = std::equal_to<Key>,
+ size_t MaxLoadFactor100 = 80>
+using unordered_set = detail::Table<sizeof(Key) <= sizeof(size_t) * 6 &&
+ std::is_nothrow_move_constructible<Key>::value &&
+ std::is_nothrow_move_assignable<Key>::value,
+ MaxLoadFactor100, Key, void, Hash, KeyEqual>;
+
+} // namespace robin_hood
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/all.h b/src/third-party/scnlib/include/scn/all.h
new file mode 100644
index 0000000..b69e822
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/all.h
@@ -0,0 +1,26 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_ALL_H
+#define SCN_ALL_H
+
+#include "scn.h"
+
+#include "istream.h"
+#include "tuple_return.h"
+
+#endif // SCN_ALL_H
diff --git a/src/third-party/scnlib/include/scn/detail/args.h b/src/third-party/scnlib/include/scn/detail/args.h
new file mode 100644
index 0000000..dd67852
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/args.h
@@ -0,0 +1,619 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_ARGS_H
+#define SCN_DETAIL_ARGS_H
+
+#include "../reader/common.h"
+#include "../util/array.h"
+
+SCN_GCC_PUSH
+SCN_GCC_IGNORE("-Wnoexcept")
+#include <string>
+SCN_GCC_POP
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ /**
+ * Allows reading an rvalue.
+ * Stores an rvalue and returns an lvalue reference to it via `operator()`.
+ * Create one with \ref temp.
+ */
+ template <typename T>
+ struct temporary {
+ temporary(T&& val) : value(SCN_MOVE(val)) {}
+
+ T& operator()() && noexcept
+ {
+ return value;
+ }
+
+ T value;
+ };
+ /**
+ * Factory function for \ref temporary.
+ *
+ * Canonical use case is with \ref scn::span:
+ * \code{.cpp}
+ * std::vector<char> buffer(32, '\0');
+ * auto result = scn::scan("123", "{}", scn::temp(scn::make_span(buffer)));
+ * // buffer == "123"
+ * \endcode
+ */
+ template <typename T,
+ typename std::enable_if<
+ !std::is_lvalue_reference<T>::value>::type* = nullptr>
+ temporary<T> temp(T&& val)
+ {
+ return {SCN_FWD(val)};
+ }
+
+ namespace detail {
+ enum type {
+ none_type = 0,
+ // signed integer
+ schar_type,
+ short_type,
+ int_type,
+ long_type,
+ long_long_type,
+ // unsigned integer
+ uchar_type,
+ ushort_type,
+ uint_type,
+ ulong_type,
+ ulong_long_type,
+ // other integral types
+ bool_type,
+ char_type,
+ code_point_type,
+ last_integer_type = code_point_type,
+ // floats
+ float_type,
+ double_type,
+ long_double_type,
+ last_numeric_type = long_double_type,
+ // other
+ buffer_type,
+ string_type,
+ string_view_type,
+
+ custom_type,
+ last_type = custom_type
+ };
+
+ constexpr bool is_integral(type t) noexcept
+ {
+ return t > none_type && t <= last_integer_type;
+ }
+ constexpr bool is_arithmetic(type t) noexcept
+ {
+ return t > none_type && t <= last_numeric_type;
+ }
+
+ struct custom_value {
+ // using scan_type = error (*)(void*, Context&, ParseCtx&);
+
+ void* value;
+ void (*scan)();
+ };
+
+ template <typename Context, typename ParseCtx, typename T>
+ error scan_custom_arg(void* arg, Context& ctx, ParseCtx& pctx) noexcept
+ {
+ return visitor_boilerplate<scanner<T>>(*static_cast<T*>(arg), ctx,
+ pctx);
+ }
+
+ struct monostate {
+ };
+
+ template <typename Ctx>
+ struct ctx_tag {
+ };
+ template <typename ParseCtx>
+ struct parse_ctx_tag {
+ };
+
+ class value {
+ public:
+ constexpr value() noexcept : m_empty{} {}
+
+ template <typename T>
+ explicit SCN_CONSTEXPR14 value(T& val) noexcept
+ : m_value(std::addressof(val))
+ {
+ }
+
+ template <typename Ctx, typename ParseCtx, typename T>
+ value(ctx_tag<Ctx>, parse_ctx_tag<ParseCtx>, T& val) noexcept
+ : m_custom(
+ custom_value{std::addressof(val),
+ reinterpret_cast<void (*)()>(
+ &scan_custom_arg<Ctx, ParseCtx, T>)})
+ {
+ }
+
+ template <typename T>
+ SCN_CONSTEXPR14 T& get_as() noexcept
+ {
+ return *static_cast<T*>(m_value);
+ }
+ template <typename T>
+ constexpr const T& get_as() const noexcept
+ {
+ return *static_cast<const T*>(m_value);
+ }
+
+ SCN_CONSTEXPR14 custom_value& get_custom() noexcept
+ {
+ return m_custom;
+ }
+ SCN_NODISCARD constexpr const custom_value& get_custom()
+ const noexcept
+ {
+ return m_custom;
+ }
+
+ private:
+ union {
+ monostate m_empty;
+ void* m_value;
+ custom_value m_custom;
+ };
+ };
+
+ template <typename CharT, typename T, type Type>
+ struct init {
+ T* val;
+ static const type type_tag = Type;
+
+ constexpr init(T& v) : val(std::addressof(v)) {}
+ template <typename Ctx, typename ParseCtx>
+ SCN_CONSTEXPR14 value get()
+ {
+ SCN_EXPECT(val != nullptr);
+ return value{*val};
+ }
+ };
+ template <typename CharT, typename T>
+ struct init<CharT, T, custom_type> {
+ T* val;
+ static const type type_tag = custom_type;
+
+ constexpr init(T& v) : val(std::addressof(v)) {}
+ template <typename Ctx, typename ParseCtx>
+ SCN_CONSTEXPR14 value get()
+ {
+ SCN_EXPECT(val != nullptr);
+ return {ctx_tag<Ctx>{}, parse_ctx_tag<ParseCtx>{}, *val};
+ }
+ };
+
+ template <typename Context,
+ typename ParseCtx,
+ typename T,
+ typename CharT = typename Context::char_type>
+ SCN_CONSTEXPR14 basic_arg<CharT> make_arg(T& value) noexcept;
+
+#define SCN_MAKE_VALUE(Tag, Type) \
+ template <typename CharT> \
+ constexpr init<CharT, Type, Tag> make_value(Type& val, \
+ priority_tag<1>) noexcept \
+ { \
+ return val; \
+ }
+
+ SCN_MAKE_VALUE(schar_type, signed char)
+ SCN_MAKE_VALUE(short_type, short)
+ SCN_MAKE_VALUE(int_type, int)
+ SCN_MAKE_VALUE(long_type, long)
+ SCN_MAKE_VALUE(long_long_type, long long)
+
+ SCN_MAKE_VALUE(uchar_type, unsigned char)
+ SCN_MAKE_VALUE(ushort_type, unsigned short)
+ SCN_MAKE_VALUE(uint_type, unsigned)
+ SCN_MAKE_VALUE(ulong_type, unsigned long)
+ SCN_MAKE_VALUE(ulong_long_type, unsigned long long)
+
+ SCN_MAKE_VALUE(bool_type, bool)
+ SCN_MAKE_VALUE(code_point_type, code_point)
+
+ SCN_MAKE_VALUE(float_type, float)
+ SCN_MAKE_VALUE(double_type, double)
+ SCN_MAKE_VALUE(long_double_type, long double)
+
+ SCN_MAKE_VALUE(buffer_type, span<CharT>)
+ SCN_MAKE_VALUE(string_type, std::basic_string<CharT>)
+ SCN_MAKE_VALUE(string_view_type, basic_string_view<CharT>)
+
+ template <typename CharT>
+ constexpr init<CharT, CharT, char_type> make_value(
+ CharT& val,
+ priority_tag<1>) noexcept
+ {
+ return val;
+ }
+
+ template <typename CharT, typename T>
+ constexpr inline auto make_value(T& val, priority_tag<0>) noexcept
+ -> init<CharT, T, custom_type>
+ {
+ return val;
+ }
+
+ enum : size_t {
+ packed_arg_bitsize = 5,
+ packed_arg_mask = (1 << packed_arg_bitsize) - 1,
+ max_packed_args = (sizeof(size_t) * 8 - 1) / packed_arg_bitsize,
+ is_unpacked_bit = size_t{1} << (sizeof(size_t) * 8ull - 1ull)
+ };
+ } // namespace detail
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wpadded")
+
+ /// Type-erased scanning argument.
+ template <typename CharT>
+ class SCN_TRIVIAL_ABI basic_arg {
+ public:
+ using char_type = CharT;
+
+ class handle {
+ public:
+ explicit handle(detail::custom_value custom) : m_custom(custom) {}
+
+ template <typename Context, typename ParseCtx>
+ error scan(Context& ctx, ParseCtx& pctx)
+ {
+ return reinterpret_cast<error (*)(void*, Context&, ParseCtx&)>(
+ m_custom.scan)(m_custom.value, ctx, pctx);
+ }
+
+ private:
+ detail::custom_value m_custom;
+ };
+
+ constexpr basic_arg() = default;
+
+ constexpr explicit operator bool() const noexcept
+ {
+ return m_type != detail::none_type;
+ }
+
+ SCN_NODISCARD constexpr detail::type type() const noexcept
+ {
+ return type;
+ }
+ SCN_NODISCARD constexpr bool is_integral() const noexcept
+ {
+ return detail::is_integral(m_type);
+ }
+ SCN_NODISCARD constexpr bool is_arithmetic() const noexcept
+ {
+ return detail::is_arithmetic(m_type);
+ }
+
+ private:
+ constexpr basic_arg(detail::value v, detail::type t) noexcept
+ : m_value(v), m_type(t)
+ {
+ }
+
+ template <typename Ctx, typename ParseCtx, typename T, typename C>
+ friend SCN_CONSTEXPR14 basic_arg<C> detail::make_arg(T& value) noexcept;
+
+ template <typename C, typename Visitor>
+ friend SCN_CONSTEXPR14 error visit_arg(Visitor&& vis,
+ basic_arg<C>& arg);
+
+ friend class basic_args<CharT>;
+
+ detail::value m_value;
+ detail::type m_type{detail::none_type};
+ };
+
+ SCN_CLANG_POP
+
+ template <typename CharT, typename Visitor>
+ SCN_CONSTEXPR14 error visit_arg(Visitor&& vis, basic_arg<CharT>& arg)
+ {
+ switch (arg.m_type) {
+ case detail::none_type:
+ break;
+
+ case detail::schar_type:
+ return vis(arg.m_value.template get_as<signed char>());
+ case detail::short_type:
+ return vis(arg.m_value.template get_as<short>());
+ case detail::int_type:
+ return vis(arg.m_value.template get_as<int>());
+ case detail::long_type:
+ return vis(arg.m_value.template get_as<long>());
+ case detail::long_long_type:
+ return vis(arg.m_value.template get_as<long long>());
+
+ case detail::uchar_type:
+ return vis(arg.m_value.template get_as<unsigned char>());
+ case detail::ushort_type:
+ return vis(arg.m_value.template get_as<unsigned short>());
+ case detail::uint_type:
+ return vis(arg.m_value.template get_as<unsigned int>());
+ case detail::ulong_type:
+ return vis(arg.m_value.template get_as<unsigned long>());
+ case detail::ulong_long_type:
+ return vis(arg.m_value.template get_as<unsigned long long>());
+
+ case detail::bool_type:
+ return vis(arg.m_value.template get_as<bool>());
+ case detail::char_type:
+ return vis(arg.m_value.template get_as<CharT>());
+ case detail::code_point_type:
+ return vis(arg.m_value.template get_as<code_point>());
+
+ case detail::float_type:
+ return vis(arg.m_value.template get_as<float>());
+ case detail::double_type:
+ return vis(arg.m_value.template get_as<double>());
+ case detail::long_double_type:
+ return vis(arg.m_value.template get_as<long double>());
+
+ case detail::buffer_type:
+ return vis(arg.m_value.template get_as<span<CharT>>());
+ case detail::string_type:
+ return vis(
+ arg.m_value.template get_as<std::basic_string<CharT>>());
+ case detail::string_view_type:
+ return vis(
+ arg.m_value.template get_as<basic_string_view<CharT>>());
+
+ case detail::custom_type:
+ return vis(typename basic_arg<CharT>::handle(
+ arg.m_value.get_custom()));
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wcovered-switch-default")
+ default:
+ return vis(detail::monostate{});
+ SCN_CLANG_POP
+ }
+ SCN_UNREACHABLE;
+ }
+
+ namespace detail {
+ template <typename CharT, typename T>
+ struct get_type {
+ using value_type = decltype(make_value<CharT>(
+ SCN_DECLVAL(typename std::remove_reference<
+ typename std::remove_cv<T>::type>::type&),
+ SCN_DECLVAL(priority_tag<1>)));
+ static const type value = value_type::type_tag;
+ };
+
+ template <typename CharT>
+ constexpr size_t get_types()
+ {
+ return 0;
+ }
+ template <typename CharT, typename Arg, typename... Args>
+ constexpr size_t get_types()
+ {
+ return static_cast<size_t>(get_type<CharT, Arg>::value) |
+ (get_types<CharT, Args...>() << 5);
+ }
+
+ template <typename Context,
+ typename ParseCtx,
+ typename T,
+ typename CharT>
+ SCN_CONSTEXPR14 basic_arg<CharT> make_arg(T& value) noexcept
+ {
+ basic_arg<CharT> arg;
+ arg.m_type = get_type<CharT, T>::value;
+ arg.m_value = make_value<CharT>(value, priority_tag<1>{})
+ .template get<Context, ParseCtx>();
+ return arg;
+ }
+
+ template <bool Packed,
+ typename Context,
+ typename ParseCtx,
+ typename T,
+ typename CharT = typename Context::char_type>
+ inline auto make_arg(T& v) ->
+ typename std::enable_if<Packed, value>::type
+ {
+ return make_value<CharT>(v, priority_tag<1>{})
+ .template get<Context, ParseCtx>();
+ }
+ template <bool Packed, typename Context, typename ParseCtx, typename T>
+ inline auto make_arg(T& v) -> typename std::
+ enable_if<!Packed, basic_arg<typename Context::char_type>>::type
+ {
+ return make_arg<Context, ParseCtx>(v);
+ }
+ } // namespace detail
+
+ template <typename CharT, typename... Args>
+ class arg_store {
+ static constexpr const size_t num_args = sizeof...(Args);
+ static const bool is_packed = num_args < detail::max_packed_args;
+
+ friend class basic_args<CharT>;
+
+ static constexpr size_t get_types()
+ {
+ return is_packed ? detail::get_types<CharT, Args...>()
+ : detail::is_unpacked_bit | num_args;
+ }
+
+ public:
+ static constexpr size_t types = get_types();
+ using arg_type = basic_arg<CharT>;
+
+ using value_type =
+ typename std::conditional<is_packed, detail::value, arg_type>::type;
+ static constexpr size_t data_size =
+ num_args + (is_packed && num_args != 0 ? 0 : 1);
+
+ template <typename Ctx, typename ParseCtx>
+ SCN_CONSTEXPR14 arg_store(detail::ctx_tag<Ctx>,
+ detail::parse_ctx_tag<ParseCtx>,
+ Args&... a) noexcept
+ : m_data{{detail::make_arg<is_packed, Ctx, ParseCtx>(a)...}}
+ {
+ }
+
+ SCN_CONSTEXPR14 span<value_type> data() noexcept
+ {
+ return make_span(m_data.data(),
+ static_cast<std::ptrdiff_t>(m_data.size()));
+ }
+
+ private:
+ detail::array<value_type, data_size> m_data;
+ };
+
+ template <typename Context, typename ParseCtx, typename... Args>
+ arg_store<typename Context::char_type, Args...> make_args(Args&... args)
+ {
+ return {detail::ctx_tag<Context>(), detail::parse_ctx_tag<ParseCtx>(),
+ args...};
+ }
+ template <typename WrappedRange,
+ typename Format,
+ typename... Args,
+ typename CharT = typename WrappedRange::char_type>
+ arg_store<CharT, Args...> make_args_for(WrappedRange&,
+ Format,
+ Args&... args)
+ {
+ using context_type = basic_context<WrappedRange>;
+ using parse_context_type =
+ typename detail::parse_context_template_for_format<
+ Format>::template type<typename context_type::char_type>;
+ return {detail::ctx_tag<context_type>(),
+ detail::parse_ctx_tag<parse_context_type>(), args...};
+ }
+
+ template <typename CharT>
+ class basic_args {
+ public:
+ using arg_type = basic_arg<CharT>;
+
+ constexpr basic_args() noexcept = default;
+
+ template <typename... Args>
+ SCN_CONSTEXPR14 basic_args(arg_store<CharT, Args...>& store) noexcept
+ : m_types(store.types)
+ {
+ set_data(store.m_data.data());
+ }
+
+ SCN_CONSTEXPR14 basic_args(span<arg_type> args) noexcept
+ : m_types(detail::is_unpacked_bit | args.size())
+ {
+ set_data(args.data());
+ }
+
+ SCN_CONSTEXPR14 arg_type get(std::ptrdiff_t i) const noexcept
+ {
+ return do_get(i);
+ }
+
+ SCN_NODISCARD SCN_CONSTEXPR14 bool check_id(
+ std::ptrdiff_t i) const noexcept
+ {
+ if (!is_packed()) {
+ return static_cast<size_t>(i) <
+ (m_types &
+ ~static_cast<size_t>(detail::is_unpacked_bit));
+ }
+ return type(i) != detail::none_type;
+ }
+
+ SCN_NODISCARD constexpr size_t max_size() const noexcept
+ {
+ return is_packed()
+ ? static_cast<size_t>(detail::max_packed_args)
+ : m_types &
+ ~static_cast<size_t>(detail::is_unpacked_bit);
+ }
+
+ private:
+ size_t m_types{0};
+ union {
+ detail::value* m_values;
+ arg_type* m_args;
+ };
+
+ SCN_NODISCARD constexpr bool is_packed() const noexcept
+ {
+ return (m_types & detail::is_unpacked_bit) == 0;
+ }
+
+ SCN_NODISCARD SCN_CONSTEXPR14 typename detail::type type(
+ std::ptrdiff_t i) const noexcept
+ {
+ size_t shift = static_cast<size_t>(i) * detail::packed_arg_bitsize;
+ return static_cast<typename detail::type>(
+ (static_cast<size_t>(m_types) >> shift) &
+ detail::packed_arg_mask);
+ }
+
+ SCN_CONSTEXPR14 void set_data(detail::value* values) noexcept
+ {
+ m_values = values;
+ }
+ SCN_CONSTEXPR14 void set_data(arg_type* args) noexcept
+ {
+ m_args = args;
+ }
+
+ SCN_CONSTEXPR14 arg_type do_get(std::ptrdiff_t i) const noexcept
+ {
+ SCN_EXPECT(i >= 0);
+
+ arg_type arg;
+ if (!is_packed()) {
+ auto num_args = static_cast<std::ptrdiff_t>(max_size());
+ if (SCN_LIKELY(i < num_args)) {
+ arg = m_args[i];
+ }
+ return arg;
+ }
+
+ SCN_EXPECT(m_values);
+ if (SCN_UNLIKELY(
+ i > static_cast<std::ptrdiff_t>(detail::max_packed_args))) {
+ return arg;
+ }
+
+ arg.m_type = type(i);
+ if (arg.m_type == detail::none_type) {
+ return arg;
+ }
+ arg.m_value = m_values[i];
+ return arg;
+ }
+ };
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_DETAIL_ARGS_H
diff --git a/src/third-party/scnlib/include/scn/detail/config.h b/src/third-party/scnlib/include/scn/detail/config.h
new file mode 100644
index 0000000..81d054c
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/config.h
@@ -0,0 +1,466 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_CONFIG_H
+#define SCN_DETAIL_CONFIG_H
+
+#include <cassert>
+
+#define SCN_STD_11 201103L
+#define SCN_STD_14 201402L
+#define SCN_STD_17 201703L
+
+#define SCN_COMPILER(major, minor, patch) \
+ ((major)*10000000 /* 10,000,000 */ + (minor)*10000 /* 10,000 */ + (patch))
+#define SCN_VERSION SCN_COMPILER(1, 1, 2)
+
+#ifdef __INTEL_COMPILER
+// Intel
+#define SCN_INTEL \
+ SCN_COMPILER(__INTEL_COMPILER / 100, (__INTEL_COMPILER / 10) % 10, \
+ __INTEL_COMPILER % 10)
+#elif defined(_MSC_VER) && defined(_MSC_FULL_VER)
+// MSVC
+#if _MSC_VER == _MSC_FULL_VER / 10000
+#define SCN_MSVC \
+ SCN_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000)
+#else
+#define SCN_MSVC \
+ SCN_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 10, \
+ _MSC_FULL_VER % 100000)
+#endif // _MSC_VER == _MSC_FULL_VER / 10000
+#elif defined(__clang__) && defined(__clang_minor__) && \
+ defined(__clang_patchlevel__)
+// Clang
+#define SCN_CLANG \
+ SCN_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__)
+#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && \
+ defined(__GNUC_PATCHLEVEL__)
+// GCC
+#define SCN_GCC SCN_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
+#endif
+
+#ifndef SCN_INTEL
+#define SCN_INTEL 0
+#endif
+#ifndef SCN_MSVC
+#define SCN_MSVC 0
+#endif
+#ifndef SCN_CLANG
+#define SCN_CLANG 0
+#endif
+#ifndef SCN_GCC
+#define SCN_GCC 0
+#endif
+
+// Pretending to be gcc (clang, icc, etc.)
+#ifdef __GNUC__
+
+#ifdef __GNUC_MINOR__
+#define SCN_GCC_COMPAT_MINOR __GNUC_MINOR__
+#else
+#define SCN_GCC_COMPAT_MINOR 0
+#endif
+
+#ifdef __GNUC_PATCHLEVEL__
+#define SCN_GCC_COMPAT_PATCHLEVEL __GNUC_PATCHLEVEL__
+#else
+#define SCN_GCC_COMPAT_PATCHLEVEL 0
+#endif
+
+#define SCN_GCC_COMPAT \
+ SCN_COMPILER(__GNUC__, SCN_GCC_COMPAT_MINOR, SCN_GCC_COMPAT_PATCHLEVEL)
+#else
+#define SCN_GCC_COMPAT 0
+#endif // #ifdef __GNUC__
+
+#define SCN_STRINGIFY_APPLY(x) #x
+#define SCN_STRINGIFY(x) SCN_STRINGIFY_APPLY(x)
+
+// POSIX
+#if defined(__unix__) || defined(__APPLE__)
+#define SCN_POSIX 1
+#else
+#define SCN_POSIX 0
+#endif
+
+#if defined(__APPLE__)
+#define SCN_APPLE 1
+#else
+#define SCN_APPLE 0
+#endif
+
+// Windows
+#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && \
+ !defined(__CYGWIN__)
+#define SCN_WINDOWS 1
+#else
+#define SCN_WINDOWS 0
+#endif
+
+#ifdef _MSVC_LANG
+#define SCN_MSVC_LANG _MSVC_LANG
+#else
+#define SCN_MSVC_LANG 0
+#endif
+
+// Standard version
+#if SCN_MSVC
+#define SCN_STD SCN_MSVC_LANG
+#else
+#define SCN_STD __cplusplus
+#endif
+
+// Warning control
+#if SCN_GCC
+#define SCN_PRAGMA_APPLY(x) _Pragma(#x)
+
+#define SCN_GCC_PUSH _Pragma("GCC diagnostic push")
+#define SCN_GCC_POP _Pragma("GCC diagnostic pop")
+
+#define SCN_GCC_IGNORE(x) SCN_PRAGMA_APPLY(GCC diagnostic ignored x)
+#else
+#define SCN_GCC_PUSH
+#define SCN_GCC_POP
+#define SCN_GCC_IGNORE(x)
+#endif
+
+#if SCN_CLANG
+#define SCN_PRAGMA_APPLY(x) _Pragma(#x)
+
+#define SCN_CLANG_PUSH _Pragma("clang diagnostic push")
+#define SCN_CLANG_POP _Pragma("clang diagnostic pop")
+
+#define SCN_CLANG_IGNORE(x) SCN_PRAGMA_APPLY(clang diagnostic ignored x)
+
+#if SCN_CLANG >= SCN_COMPILER(3, 9, 0)
+#define SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE \
+ SCN_CLANG_PUSH SCN_CLANG_IGNORE("-Wundefined-func-template")
+#define SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE SCN_CLANG_POP
+#else
+#define SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+#define SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+#endif
+
+#else
+#define SCN_CLANG_PUSH
+#define SCN_CLANG_POP
+#define SCN_CLANG_IGNORE(x)
+#define SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+#define SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+#endif
+
+#if SCN_GCC_COMPAT && defined(SCN_PRAGMA_APPLY)
+#define SCN_GCC_COMPAT_PUSH SCN_PRAGMA_APPLY(GCC diagnostic push)
+#define SCN_GCC_COMPAT_POP SCN_PRAGMA_APPLY(GCC diagnostic pop)
+#define SCN_GCC_COMPAT_IGNORE(x) SCN_PRAGMA_APPLY(GCC diagnostic ignored x)
+#else
+#define SCN_GCC_COMPAT_PUSH
+#define SCN_GCC_COMPAT_POP
+#define SCN_GCC_COMPAT_IGNORE(x)
+#endif
+
+#if SCN_MSVC
+#define SCN_MSVC_PUSH __pragma(warning(push))
+#define SCN_MSVC_POP __pragma(warning(pop))
+
+#define SCN_MSVC_IGNORE(x) __pragma(warning(disable : x))
+#else
+#define SCN_MSVC_PUSH
+#define SCN_MSVC_POP
+#define SCN_MSVC_IGNORE(x)
+#endif
+
+#ifndef SCN_PREDEFINE_VSCAN_OVERLOADS
+#define SCN_PREDEFINE_VSCAN_OVERLOADS 0
+#endif
+
+#ifdef __cpp_exceptions
+#define SCN_HAS_EXCEPTIONS 1
+#endif
+#if !defined(SCN_HAS_EXCEPTIONS) && defined(__EXCEPTIONS)
+#define SCN_HAS_EXCEPTIONS 1
+#endif
+#if !defined(SCN_HAS_EXCEPTIONS) && defined(_HAS_EXCEPTIONS)
+#if _HAS_EXCEPTIONS
+#define SCN_HAS_EXCEPTIONS 1
+#else
+#define SCN_HAS_EXCEPTIONS 0
+#endif
+#endif
+#if !defined(SCN_HAS_EXCEPTIONS) && !defined(_CPPUNWIND)
+#define SCN_HAS_EXCEPTIONS 0
+#endif
+#ifndef SCN_HAS_EXCEPTIONS
+#define SCN_HAS_EXCEPTIONS 0
+#endif
+
+#if SCN_HAS_EXCEPTIONS
+#define SCN_TRY try
+#define SCN_CATCH(x) catch (x)
+#define SCN_THROW(x) throw x
+#define SCN_RETHROW throw
+#else
+#define SCN_TRY if (true)
+#define SCN_CATCH(x) if (false)
+#define SCN_THROW(x) ::std::abort()
+#define SCN_RETHROW ::std::abort()
+#endif
+
+#ifdef __has_include
+#define SCN_HAS_INCLUDE(x) __has_include(x)
+#else
+#define SCN_HAS_INCLUDE(x) 0
+#endif
+
+#ifdef __has_cpp_attribute
+#define SCN_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
+#else
+#define SCN_HAS_CPP_ATTRIBUTE(x) 0
+#endif
+
+#ifdef __has_feature
+#define SCN_HAS_FEATURE(x) __has_feature(x)
+#else
+#define SCN_HAS_FEATURE(x) 0
+#endif
+
+#ifdef __has_builtin
+#define SCN_HAS_BUILTIN(x) __has_builtin(x)
+#else
+#define SCN_HAS_BUILTIN(x) 0
+#endif
+
+#if SCN_HAS_INCLUDE(<version>)
+#include <version>
+#endif
+
+#if defined(_SCN_DOXYGEN) && _SCN_DOXYGEN
+#define SCN_DOXYGEN 1
+#else
+#define SCN_DOXYGEN 0
+#endif
+
+// Detect constexpr
+#if defined(__cpp_constexpr)
+#if __cpp_constexpr >= 201304
+#define SCN_HAS_RELAXED_CONSTEXPR 1
+#else
+#define SCN_HAS_RELAXED_CONSTEXPR 0
+#endif
+#endif
+
+#ifndef SCN_HAS_RELAXED_CONSTEXPR
+#if SCN_HAS_FEATURE(cxx_relaxed_constexpr) || \
+ SCN_MSVC >= SCN_COMPILER(19, 10, 0) || \
+ ((SCN_GCC >= SCN_COMPILER(6, 0, 0) || \
+ SCN_INTEL >= SCN_COMPILER(17, 0, 0)) && \
+ SCN_STD >= SCN_STD_14)
+#define SCN_HAS_RELAXED_CONSTEXPR 1
+#else
+#define SCN_HAS_RELAXED_CONSTEXPR 0
+#endif
+#endif
+
+#if SCN_HAS_RELAXED_CONSTEXPR || SCN_DOXYGEN
+#define SCN_CONSTEXPR14 constexpr
+#else
+#define SCN_CONSTEXPR14 inline
+#endif
+
+// Detect string_view
+#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201603 && \
+ SCN_STD >= SCN_STD_17
+#define SCN_HAS_STRING_VIEW 1
+#else
+#define SCN_HAS_STRING_VIEW 0
+#endif
+
+// Detect [[nodiscard]]
+#if (SCN_HAS_CPP_ATTRIBUTE(nodiscard) && __cplusplus >= SCN_STD_17) || \
+ (SCN_MSVC >= SCN_COMPILER(19, 11, 0) && SCN_MSVC_LANG >= SCN_STD_17) || \
+ ((SCN_GCC >= SCN_COMPILER(7, 0, 0) || \
+ SCN_INTEL >= SCN_COMPILER(18, 0, 0)) && \
+ __cplusplus >= SCN_STD_17) && !SCN_DOXYGEN
+#define SCN_NODISCARD [[nodiscard]]
+#else
+#define SCN_NODISCARD /*nodiscard*/
+#endif
+
+// Detect [[clang::trivial_abi]]
+#if SCN_HAS_CPP_ATTRIBUTE(clang::trivial_abi)
+#define SCN_TRIVIAL_ABI [[clang::trivial_abi]]
+#else
+#define SCN_TRIVIAL_ABI /*trivial_abi*/
+#endif
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY
+#define SCN_FUNC inline
+#else
+#define SCN_FUNC
+#endif
+
+// Detect <charconv>
+
+#if defined(_GLIBCXX_RELEASE) && __cplusplus >= SCN_STD_17
+#define SCN_HAS_INTEGER_CHARCONV (_GLIBCXX_RELEASE >= 9)
+#define SCN_HAS_FLOAT_CHARCONV (_GLIBCXX_RELEASE >= 11)
+#elif SCN_MSVC >= SCN_COMPILER(19, 14, 0)
+#define SCN_HAS_INTEGER_CHARCONV 1
+#define SCN_HAS_FLOAT_CHARCONV (SCN_MSVC >= SCN_COMPILER(19, 21, 0))
+#elif defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201606
+#define SCN_HAS_INTEGER_CHARCONV 1
+#define SCN_HAS_FLOAT_CHARCONV 1
+#endif // _GLIBCXX_RELEASE
+
+#ifndef SCN_HAS_INTEGER_CHARCONV
+#define SCN_HAS_INTEGER_CHARCONV 0
+#define SCN_HAS_FLOAT_CHARCONV 0
+#endif
+
+// Detect std::launder
+#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606
+#define SCN_HAS_LAUNDER 1
+#else
+#define SCN_HAS_LAUNDER 0
+#endif
+
+// Detect __assume
+#if SCN_INTEL || SCN_MSVC
+#define SCN_HAS_ASSUME 1
+#else
+#define SCN_HAS_ASSUME 0
+#endif
+
+// Detect __builtin_assume
+#if SCN_HAS_BUILTIN(__builtin_assume)
+#define SCN_HAS_BUILTIN_ASSUME 1
+#else
+#define SCN_HAS_BUILTIN_ASSUME 0
+#endif
+
+// Detect __builtin_unreachable
+#if SCN_HAS_BUILTIN(__builtin_unreachable) || SCN_GCC_COMPAT
+#define SCN_HAS_BUILTIN_UNREACHABLE 1
+#else
+#define SCN_HAS_BUILTIN_UNREACHABLE 0
+#endif
+
+#if SCN_HAS_ASSUME
+#define SCN_ASSUME(x) __assume(x)
+#elif SCN_HAS_BUILTIN_ASSUME
+#define SCN_ASSUME(x) __builtin_assume(x)
+#elif SCN_HAS_BUILTIN_UNREACHABLE
+#define SCN_ASSUME(x) ((x) ? static_cast<void>(0) : __builtin_unreachable())
+#else
+#define SCN_ASSUME(x) static_cast<void>((x) ? 0 : 0)
+#endif
+
+#if SCN_HAS_BUILTIN_UNREACHABLE
+#define SCN_UNREACHABLE __builtin_unreachable()
+#else
+#define SCN_UNREACHABLE SCN_ASSUME(0)
+#endif
+
+// Detect __builtin_expect
+#if SCN_HAS_BUILTIN(__builtin_expect) || SCN_GCC_COMPAT
+#define SCN_HAS_BUILTIN_EXPECT 1
+#else
+#define SCN_HAS_BUILTIN_EXPECT 0
+#endif
+
+#if SCN_HAS_BUILTIN_EXPECT
+#define SCN_LIKELY(x) __builtin_expect(!!(x), 1)
+#define SCN_UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+#define SCN_LIKELY(x) (x)
+#define SCN_UNLIKELY(x) (x)
+#endif
+
+#ifndef SCN_DEPRECATED
+
+#if (SCN_HAS_CPP_ATTRIBUTE(deprecated) && SCN_STD >= 201402L) || \
+ SCN_MSVC >= SCN_COMPILER(19, 0, 0) || SCN_DOXYGEN
+#define SCN_DEPRECATED [[deprecated]]
+#else
+
+#if SCN_GCC_COMPAT
+#define SCN_DEPRECATED __attribute__((deprecated))
+#elif SCN_MSVC
+#define SCN_DEPRECATED __declspec(deprecated)
+#else
+#define SCN_DEPRECATED /* deprecated */
+#endif
+
+#endif
+
+#endif // !defined(SCN_DEPRECATED)
+
+// Detect concepts
+#if defined(__cpp_concepts) && __cpp_concepts >= 201907L
+#define SCN_HAS_CONCEPTS 1
+#else
+#define SCN_HAS_CONCEPTS 0
+#endif
+
+// Detect ranges
+#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
+#define SCN_HAS_RANGES 1
+#else
+#define SCN_HAS_RANGES 0
+#endif
+
+// Detect char8_t
+#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L
+#define SCN_HAS_CHAR8 1
+#else
+#define SCN_HAS_CHAR8 0
+#endif
+
+#define SCN_UNUSED(x) static_cast<void>(sizeof(x))
+
+#if SCN_HAS_RELAXED_CONSTEXPR
+#define SCN_ASSERT(cond, msg) \
+ do { \
+ static_cast<void>(SCN_LIKELY(cond)); \
+ assert((cond) && msg); \
+ } while (false)
+#define SCN_EXPECT(cond) SCN_ASSERT(cond, "Precondition violation")
+#define SCN_ENSURE(cond) SCN_ASSERT(cond, "Postcondition violation")
+#else
+#define SCN_ASSERT(cond, msg) SCN_UNUSED(cond)
+#define SCN_EXPECT(cond) SCN_UNUSED(cond)
+#define SCN_ENSURE(cond) SCN_UNUSED(cond)
+#endif
+
+#define SCN_MOVE(x) \
+ static_cast< \
+ typename ::scn::detail::remove_reference \
+ <decltype(x)>::type&&>(x)
+#define SCN_FWD(x) static_cast<decltype(x)&&>(x)
+#define SCN_DECLVAL(T) static_cast<T (*)()>(nullptr)()
+
+#define SCN_BEGIN_NAMESPACE inline namespace v1 {
+#define SCN_END_NAMESPACE }
+
+#if defined(SCN_HEADER_ONLY)
+#define SCN_INCLUDE_SOURCE_DEFINITIONS !SCN_HEADER_ONLY
+#else
+#define SCN_INCLUDE_SOURCE_DEFINITIONS 1
+#endif
+
+#endif // SCN_DETAIL_CONFIG_H
diff --git a/src/third-party/scnlib/include/scn/detail/context.h b/src/third-party/scnlib/include/scn/detail/context.h
new file mode 100644
index 0000000..5dff3b3
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/context.h
@@ -0,0 +1,126 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_CONTEXT_H
+#define SCN_DETAIL_CONTEXT_H
+
+#include "args.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ template <typename WrappedRange>
+ class basic_context {
+ public:
+ using range_type = WrappedRange;
+ using iterator = typename range_type::iterator;
+ using sentinel = typename range_type::sentinel;
+ using char_type = typename range_type::char_type;
+ using locale_type = basic_locale_ref<char_type>;
+
+ basic_context(range_type&& r) : m_range(SCN_MOVE(r)) {}
+ basic_context(range_type&& r, locale_type&& loc)
+ : m_range(SCN_MOVE(r)), m_locale(SCN_MOVE(loc))
+ {
+ }
+
+ SCN_NODISCARD iterator& begin()
+ {
+ return m_range.begin();
+ }
+ const sentinel& end() const
+ {
+ return m_range.end();
+ }
+
+ range_type& range() & noexcept
+ {
+ return m_range;
+ }
+ const range_type& range() const& noexcept
+ {
+ return m_range;
+ }
+ range_type range() && noexcept
+ {
+ return m_range;
+ }
+
+ locale_type& locale() noexcept
+ {
+ return m_locale;
+ }
+ const locale_type& locale() const noexcept
+ {
+ return m_locale;
+ }
+
+ private:
+ range_type m_range;
+ locale_type m_locale{};
+ };
+
+ template <typename WrappedRange,
+ typename CharT = typename WrappedRange::char_type>
+ basic_context<WrappedRange> make_context(WrappedRange r)
+ {
+ return {SCN_MOVE(r)};
+ }
+ template <typename WrappedRange, typename LocaleRef>
+ basic_context<WrappedRange> make_context(WrappedRange r, LocaleRef&& loc)
+ {
+ return {SCN_MOVE(r), SCN_FWD(loc)};
+ }
+
+ template <typename CharT>
+ auto get_arg(const basic_args<CharT>& args, std::ptrdiff_t id)
+ -> expected<basic_arg<CharT>>
+ {
+ auto a = args.get(id);
+ if (!a) {
+ return error(error::invalid_format_string,
+ "Argument id out of range");
+ }
+ return a;
+ }
+ template <typename CharT, typename ParseCtx>
+ auto get_arg(const basic_args<CharT>& args,
+ ParseCtx& pctx,
+ std::ptrdiff_t id) -> expected<basic_arg<CharT>>
+ {
+ return pctx.check_arg_id(id) ? get_arg(args, id)
+ : error(error::invalid_format_string,
+ "Argument id out of range");
+ }
+ template <typename CharT, typename ParseCtx>
+ auto get_arg(const basic_args<CharT>&, ParseCtx&, basic_string_view<CharT>)
+ -> expected<basic_arg<CharT>>
+ {
+ return error(error::invalid_format_string, "Argument id out of range");
+ }
+
+ template <typename CharT, typename ParseCtx>
+ auto next_arg(const basic_args<CharT>& args, ParseCtx& pctx)
+ -> expected<basic_arg<CharT>>
+ {
+ return get_arg(args, pctx.next_arg_id());
+ }
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_DETAIL_CONTEXT_H
diff --git a/src/third-party/scnlib/include/scn/detail/error.h b/src/third-party/scnlib/include/scn/detail/error.h
new file mode 100644
index 0000000..f79e741
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/error.h
@@ -0,0 +1,136 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_ERROR_H
+#define SCN_DETAIL_ERROR_H
+
+#include "fwd.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ /**
+ * Error class.
+ * Used as a return value for functions without a success value.
+ */
+ class SCN_TRIVIAL_ABI error {
+ public:
+ /// Error code
+ enum code : char {
+ /// No error
+ good = 0,
+ /// EOF
+ end_of_range,
+ /// Format string was invalid
+ invalid_format_string,
+ /// Scanned value was invalid for given type.
+ /// e.g. a period '.' when scanning for an int
+ invalid_scanned_value,
+ /// Stream does not support the performed operation
+ invalid_operation,
+ /// Scanned value was out of range for the desired type.
+ /// (e.g. `>2^32` for an `uint32_t`)
+ value_out_of_range,
+ /// Invalid argument given to operation
+ invalid_argument,
+ /// Source range has invalid (utf-8 or utf-16) encoding
+ invalid_encoding,
+ /// This operation is only possible with exceptions enabled
+ exceptions_required,
+ /// The source range emitted an error.
+ source_error,
+ /// The source range emitted an error that cannot be recovered
+ /// from. The stream is now unusable.
+ unrecoverable_source_error,
+
+ unrecoverable_internal_error,
+
+ max_error
+ };
+
+ struct success_tag_t {
+ };
+ static constexpr success_tag_t success_tag() noexcept
+ {
+ return {};
+ }
+
+ constexpr error() noexcept = default;
+ constexpr error(success_tag_t) noexcept : error() {}
+ constexpr error(enum code c, const char* m) noexcept
+ : m_msg(m), m_code(c)
+ {
+ }
+
+ /// Evaluated to true if there was no error
+ constexpr explicit operator bool() const noexcept
+ {
+ return m_code == good;
+ }
+ constexpr bool operator!() const noexcept
+ {
+ return !(operator bool());
+ }
+
+ constexpr operator enum code() const noexcept { return m_code; }
+
+ /// Get error code
+ SCN_NODISCARD constexpr enum code code() const noexcept
+ {
+ return m_code;
+ }
+ SCN_NODISCARD constexpr const char* msg() const noexcept
+ {
+ return m_msg;
+ }
+
+ /// Returns `true` if, after this error, the state of the given input
+ /// range is consistent, and thus, the range can be used for new
+ /// scanning operations.
+ SCN_NODISCARD constexpr bool is_recoverable() const noexcept
+ {
+ return !(m_code == unrecoverable_source_error ||
+ m_code == unrecoverable_internal_error);
+ }
+
+ private:
+ const char* m_msg{nullptr};
+ enum code m_code { good };
+ };
+
+ constexpr inline bool operator==(error a, error b) noexcept
+ {
+ return a.code() == b.code();
+ }
+ constexpr inline bool operator!=(error a, error b) noexcept
+ {
+ return !(a == b);
+ }
+
+ namespace detail {
+ struct error_handler {
+ constexpr error_handler() = default;
+
+ void on_error(error e);
+ void on_error(const char* msg);
+ };
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/detail/file.h b/src/third-party/scnlib/include/scn/detail/file.h
new file mode 100644
index 0000000..03ccff7
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/file.h
@@ -0,0 +1,568 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_FILE_H
+#define SCN_DETAIL_FILE_H
+
+#include <cstdio>
+#include <string>
+
+#include "../util/algorithm.h"
+#include "range.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ struct native_file_handle {
+#if SCN_WINDOWS
+ using handle_type = void*;
+#else
+ using handle_type = int;
+#endif
+
+ static native_file_handle invalid();
+
+ handle_type handle;
+ };
+
+ class byte_mapped_file {
+ public:
+ using iterator = const char*;
+ using sentinel = const char*;
+
+ byte_mapped_file() = default;
+ explicit byte_mapped_file(const char* filename);
+
+ byte_mapped_file(const byte_mapped_file&) = delete;
+ byte_mapped_file& operator=(const byte_mapped_file&) = delete;
+
+ byte_mapped_file(byte_mapped_file&& o) noexcept
+ : m_map(exchange(o.m_map, span<char>{})),
+ m_file(exchange(o.m_file, native_file_handle::invalid()))
+ {
+#if SCN_WINDOWS
+ m_map_handle =
+ exchange(o.m_map_handle, native_file_handle::invalid());
+#endif
+ SCN_ENSURE(!o.valid());
+ SCN_ENSURE(valid());
+ }
+ byte_mapped_file& operator=(byte_mapped_file&& o) noexcept
+ {
+ if (valid()) {
+ _destruct();
+ }
+
+ m_map = exchange(o.m_map, span<char>{});
+ m_file = exchange(o.m_file, native_file_handle::invalid());
+#if SCN_WINDOWS
+ m_map_handle =
+ exchange(o.m_map_handle, native_file_handle::invalid());
+#endif
+
+ SCN_ENSURE(!o.valid());
+ SCN_ENSURE(valid());
+ return *this;
+ }
+
+ ~byte_mapped_file()
+ {
+ if (valid()) {
+ _destruct();
+ }
+ }
+
+ SCN_NODISCARD bool valid() const
+ {
+ return m_file.handle != native_file_handle::invalid().handle;
+ }
+
+ SCN_NODISCARD iterator begin() const
+ {
+ return m_map.begin();
+ }
+ SCN_NODISCARD sentinel end() const
+ {
+ return m_map.end();
+ }
+
+ protected:
+ void _destruct();
+
+ span<char> m_map{};
+ native_file_handle m_file{native_file_handle::invalid().handle};
+#if SCN_WINDOWS
+ native_file_handle m_map_handle{
+ native_file_handle::invalid().handle};
+#endif
+ };
+ } // namespace detail
+
+ /**
+ * Memory-mapped file range.
+ * Manages the lifetime of the mapping itself.
+ */
+ template <typename CharT>
+ class basic_mapped_file : public detail::byte_mapped_file {
+ public:
+ using iterator = const CharT*;
+ using sentinel = const CharT*;
+
+ /// Constructs an empty mapping
+ basic_mapped_file() = default;
+
+ /// Constructs a mapping to a filename
+ explicit basic_mapped_file(const char* f) : detail::byte_mapped_file{f}
+ {
+ }
+
+ SCN_NODISCARD iterator begin() const noexcept
+ {
+ // embrace the UB
+ return reinterpret_cast<iterator>(byte_mapped_file::begin());
+ }
+ SCN_NODISCARD sentinel end() const noexcept
+ {
+ return reinterpret_cast<sentinel>(byte_mapped_file::end());
+ }
+
+ SCN_NODISCARD iterator data() const noexcept
+ {
+ return begin();
+ }
+ SCN_NODISCARD size_t size() const noexcept
+ {
+ return m_map.size() / sizeof(CharT);
+ }
+
+ /// Mapping data
+ span<const CharT> buffer() const
+ {
+ return {data(), size()};
+ }
+
+ detail::range_wrapper<basic_string_view<CharT>> wrap() const noexcept
+ {
+ return basic_string_view<CharT>{data(), size()};
+ }
+ };
+
+ using mapped_file = basic_mapped_file<char>;
+ using mapped_wfile = basic_mapped_file<wchar_t>;
+
+ namespace detail {
+ template <typename CharT>
+ struct basic_file_access;
+ template <typename CharT>
+ struct basic_file_iterator_access;
+ } // namespace detail
+
+ /**
+ * Range mapping to a C FILE*.
+ * Not copyable or reconstructible.
+ */
+ template <typename CharT>
+ class basic_file {
+ friend struct detail::basic_file_access<CharT>;
+ friend struct detail::basic_file_iterator_access<CharT>;
+
+ public:
+ class iterator {
+ friend struct detail::basic_file_iterator_access<CharT>;
+
+ public:
+ using char_type = CharT;
+ using value_type = expected<CharT>;
+ using reference = value_type;
+ using pointer = value_type*;
+ using difference_type = std::ptrdiff_t;
+ using iterator_category = std::bidirectional_iterator_tag;
+ using file_type = basic_file<CharT>;
+
+ iterator() = default;
+
+ expected<CharT> operator*() const;
+
+ iterator& operator++()
+ {
+ SCN_EXPECT(m_file);
+ ++m_current;
+ return *this;
+ }
+ iterator operator++(int)
+ {
+ iterator tmp(*this);
+ operator++();
+ return tmp;
+ }
+
+ iterator& operator--()
+ {
+ SCN_EXPECT(m_file);
+ SCN_EXPECT(m_current > 0);
+
+ m_last_error = error{};
+ --m_current;
+
+ return *this;
+ }
+ iterator operator--(int)
+ {
+ iterator tmp(*this);
+ operator--();
+ return tmp;
+ }
+
+ bool operator==(const iterator& o) const;
+
+ bool operator!=(const iterator& o) const
+ {
+ return !operator==(o);
+ }
+
+ bool operator<(const iterator& o) const
+ {
+ // any valid iterator is before eof and null
+ if (!m_file) {
+ return !o.m_file;
+ }
+ if (!o.m_file) {
+ return !m_file;
+ }
+ SCN_EXPECT(m_file == o.m_file);
+ return m_current < o.m_current;
+ }
+ bool operator>(const iterator& o) const
+ {
+ return o.operator<(*this);
+ }
+ bool operator<=(const iterator& o) const
+ {
+ return !operator>(o);
+ }
+ bool operator>=(const iterator& o) const
+ {
+ return !operator<(o);
+ }
+
+ void reset_begin_iterator() const noexcept
+ {
+ m_current = 0;
+ }
+
+ private:
+ friend class basic_file;
+
+ iterator(const file_type& f, size_t i)
+ : m_file{std::addressof(f)}, m_current{i}
+ {
+ }
+
+ mutable error m_last_error{};
+ const file_type* m_file{nullptr};
+ mutable size_t m_current{0};
+ };
+
+ using sentinel = iterator;
+ using char_type = CharT;
+
+ /**
+ * Construct an empty file.
+ * Reading not possible: valid() is `false`
+ */
+ basic_file() = default;
+ /**
+ * Construct from a FILE*.
+ * Must be a valid handle that can be read from.
+ */
+ basic_file(FILE* f) : m_file{f} {}
+
+ basic_file(const basic_file&) = delete;
+ basic_file& operator=(const basic_file&) = delete;
+
+ basic_file(basic_file&& o) noexcept
+ : m_buffer(detail::exchange(o.m_buffer, {})),
+ m_file(detail::exchange(o.m_file, nullptr))
+ {
+ }
+ basic_file& operator=(basic_file&& o) noexcept
+ {
+ if (valid()) {
+ sync();
+ }
+ m_buffer = detail::exchange(o.m_buffer, {});
+ m_file = detail::exchange(o.m_file, nullptr);
+ return *this;
+ }
+
+ ~basic_file()
+ {
+ if (valid()) {
+ _sync_all();
+ }
+ }
+
+ /**
+ * Get the FILE* for this range.
+ * Only use this handle for reading sync() has been called and no
+ * reading operations have taken place after that.
+ *
+ * \see sync
+ */
+ FILE* handle() const
+ {
+ return m_file;
+ }
+
+ /**
+ * Reset the file handle.
+ * Calls sync(), if necessary, before resetting.
+ * @return The old handle
+ */
+ FILE* set_handle(FILE* f, bool allow_sync = true) noexcept
+ {
+ auto old = m_file;
+ if (old && allow_sync) {
+ sync();
+ }
+ m_file = f;
+ return old;
+ }
+
+ /// Whether the file has been opened
+ constexpr bool valid() const noexcept
+ {
+ return m_file != nullptr;
+ }
+
+ /**
+ * Synchronizes this file with the underlying FILE*.
+ * Invalidates all non-end iterators.
+ * File must be open.
+ *
+ * Necessary for mixing-and-matching scnlib and <cstdio>:
+ * \code{.cpp}
+ * scn::scan(file, ...);
+ * file.sync();
+ * std::fscanf(file.handle(), ...);
+ * \endcode
+ *
+ * Necessary for synchronizing result objects:
+ * \code{.cpp}
+ * auto result = scn::scan(file, ...);
+ * // only result.range() can now be used for scanning
+ * result = scn::scan(result.range(), ...);
+ * // .sync() allows the original file to also be used
+ * file.sync();
+ * result = scn::scan(file, ...);
+ * \endcode
+ */
+ void sync() noexcept
+ {
+ _sync_all();
+ m_buffer.clear();
+ }
+
+ iterator begin() const noexcept
+ {
+ return {*this, 0};
+ }
+ sentinel end() const noexcept
+ {
+ return {};
+ }
+
+ span<const CharT> get_buffer(iterator it,
+ size_t max_size) const noexcept
+ {
+ if (!it.m_file) {
+ return {};
+ }
+ const auto begin =
+ m_buffer.begin() + static_cast<std::ptrdiff_t>(it.m_current);
+ const auto end_diff = detail::min(
+ max_size,
+ static_cast<size_t>(ranges::distance(begin, m_buffer.end())));
+ return {begin, begin + static_cast<std::ptrdiff_t>(end_diff)};
+ }
+
+ private:
+ friend class iterator;
+
+ expected<CharT> _read_single() const;
+
+ void _sync_all() noexcept
+ {
+ _sync_until(m_buffer.size());
+ }
+ void _sync_until(size_t pos) noexcept;
+
+ CharT _get_char_at(size_t i) const
+ {
+ SCN_EXPECT(valid());
+ SCN_EXPECT(i < m_buffer.size());
+ return m_buffer[i];
+ }
+
+ bool _is_at_end(size_t i) const
+ {
+ SCN_EXPECT(valid());
+ return i >= m_buffer.size();
+ }
+
+ mutable std::basic_string<CharT> m_buffer{};
+ FILE* m_file{nullptr};
+ };
+
+ using file = basic_file<char>;
+ using wfile = basic_file<wchar_t>;
+
+ template <>
+ expected<char> file::iterator::operator*() const;
+ template <>
+ expected<wchar_t> wfile::iterator::operator*() const;
+ template <>
+ bool file::iterator::operator==(const file::iterator&) const;
+ template <>
+ bool wfile::iterator::operator==(const wfile::iterator&) const;
+
+ template <>
+ expected<char> file::_read_single() const;
+ template <>
+ expected<wchar_t> wfile::_read_single() const;
+ template <>
+ void file::_sync_until(size_t) noexcept;
+ template <>
+ void wfile::_sync_until(size_t) noexcept;
+
+ /**
+ * A child class for basic_file, handling fopen, fclose, and lifetimes with
+ * RAII.
+ */
+ template <typename CharT>
+ class basic_owning_file : public basic_file<CharT> {
+ public:
+ using char_type = CharT;
+
+ /// Open an empty file
+ basic_owning_file() = default;
+ /// Open a file, with fopen arguments
+ basic_owning_file(const char* f, const char* mode)
+ : basic_file<CharT>(std::fopen(f, mode))
+ {
+ }
+
+ /// Steal ownership of a FILE*
+ explicit basic_owning_file(FILE* f) : basic_file<CharT>(f) {}
+
+ ~basic_owning_file()
+ {
+ if (is_open()) {
+ close();
+ }
+ }
+
+ /// fopen
+ bool open(const char* f, const char* mode)
+ {
+ SCN_EXPECT(!is_open());
+
+ auto h = std::fopen(f, mode);
+ if (!h) {
+ return false;
+ }
+
+ const bool is_wide = sizeof(CharT) > 1;
+ auto ret = std::fwide(h, is_wide ? 1 : -1);
+ if ((is_wide && ret > 0) || (!is_wide && ret < 0) || ret == 0) {
+ this->set_handle(h);
+ return true;
+ }
+ return false;
+ }
+ /// Steal ownership
+ bool open(FILE* f)
+ {
+ SCN_EXPECT(!is_open());
+ if (std::ferror(f) != 0) {
+ return false;
+ }
+ this->set_handle(f);
+ return true;
+ }
+
+ /// Close file
+ void close()
+ {
+ SCN_EXPECT(is_open());
+ this->sync();
+ std::fclose(this->handle());
+ this->set_handle(nullptr, false);
+ }
+
+ /// Is the file open
+ SCN_NODISCARD bool is_open() const
+ {
+ return this->valid();
+ }
+ };
+
+ using owning_file = basic_owning_file<char>;
+ using owning_wfile = basic_owning_file<wchar_t>;
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wexit-time-destructors")
+
+ // Avoid documentation issues: without this, Doxygen will think
+ // SCN_CLANG_PUSH is a part of the stdin_range declaration
+ namespace dummy {
+ }
+
+ /**
+ * Get a reference to the global stdin range
+ */
+ template <typename CharT>
+ basic_file<CharT>& stdin_range()
+ {
+ static auto f = basic_file<CharT>{stdin};
+ return f;
+ }
+ /**
+ * Get a reference to the global `char`-oriented stdin range
+ */
+ inline file& cstdin()
+ {
+ return stdin_range<char>();
+ }
+ /**
+ * Get a reference to the global `wchar_t`-oriented stdin range
+ */
+ inline wfile& wcstdin()
+ {
+ return stdin_range<wchar_t>();
+ }
+ SCN_CLANG_POP
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY && !defined(SCN_FILE_CPP)
+#include "file.cpp"
+#endif
+
+#endif // SCN_DETAIL_FILE_H
diff --git a/src/third-party/scnlib/include/scn/detail/fwd.h b/src/third-party/scnlib/include/scn/detail/fwd.h
new file mode 100644
index 0000000..3dcebf6
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/fwd.h
@@ -0,0 +1,204 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_FWD_H
+#define SCN_DETAIL_FWD_H
+
+#include "config.h"
+
+#include <cstddef>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ // args.h
+
+ template <typename CharT>
+ class basic_arg;
+ template <typename CharT>
+ class basic_args;
+ template <typename CharT, typename... Args>
+ class arg_store;
+
+ template <typename T>
+ struct temporary;
+
+ // error.h
+
+ class error;
+
+ // locale.h
+
+ template <typename CharT>
+ class basic_locale_ref;
+
+ // context.h
+
+ template <typename WrappedRange>
+ class basic_context;
+
+ // parse_context.h
+
+ template <typename CharT>
+ class basic_parse_context;
+ template <typename CharT>
+ class basic_empty_parse_context;
+
+ namespace detail {
+ template <typename T>
+ struct parse_context_template_for_format;
+ }
+
+ // reader/common.h
+
+ template <typename T, typename Enable = void>
+ struct scanner;
+
+ // defined here to avoid including <scn.h> if the user wants to create a
+ // scanner for their own type
+ /**
+ * Base class for all scanners.
+ * User-defined scanner must derive from this type.
+ */
+ struct parser_base {
+ /**
+ * Returns `true` if `skip_range_whitespace()` is to be called before
+ * scanning this value.
+ *
+ * Defaults to `true`. Is `false` for chars, code points and strings
+ * when using set scanning.
+ */
+ static constexpr bool skip_preceding_whitespace()
+ {
+ return true;
+ }
+ /**
+ * Returns `true` if this scanner supports parsing align and fill
+ * specifiers from the format string, and then scanning them.
+ *
+ * Defaults to `false`, `true` for all scnlib-defined scanners.
+ */
+ static constexpr bool support_align_and_fill()
+ {
+ return false;
+ }
+
+ static SCN_CONSTEXPR14 void make_localized() {}
+ };
+
+ struct empty_parser;
+ struct common_parser;
+ struct common_parser_default;
+
+ namespace detail {
+ template <typename T>
+ struct simple_integer_scanner;
+ }
+
+ // visitor.h
+
+ template <typename Context, typename ParseCtx>
+ class basic_visitor;
+
+ // file.h
+
+ template <typename CharT>
+ class basic_mapped_file;
+ template <typename CharT>
+ class basic_file;
+ template <typename CharT>
+ class basic_owning_file;
+
+ // scan.h
+
+ template <typename T>
+ struct span_list_wrapper;
+ template <typename T>
+ struct discard_type;
+
+ // util/array.h
+
+ namespace detail {
+ template <typename T, std::size_t N>
+ struct array;
+ }
+
+ // util/expected.h
+
+ template <typename T, typename Error = ::scn::error, typename Enable = void>
+ class expected;
+
+ // util/memory.h
+
+ namespace detail {
+ template <typename T>
+ struct pointer_traits;
+
+ template <typename T>
+ class erased_storage;
+
+ } // namespace detail
+
+ // util/optional.h
+
+ template <typename T>
+ class optional;
+
+ // util/small_vector.h
+
+ namespace detail {
+ template <typename T, size_t StackN>
+ class small_vector;
+ }
+
+ // util/span.h
+
+ template <typename T>
+ class span;
+
+ // util/string_view.h
+
+ template <typename CharT>
+ class basic_string_view;
+
+ // util/unique_ptr.h
+
+ namespace detail {
+ template <typename T>
+ class unique_ptr;
+ }
+
+ // for SCN_MOVE
+ namespace detail {
+ template <typename T>
+ struct remove_reference {
+ using type = T;
+ };
+ template <typename T>
+ struct remove_reference<T&> {
+ using type = T;
+ };
+ template <typename T>
+ struct remove_reference<T&&> {
+ using type = T;
+ };
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_DETAIL_FWD_H
diff --git a/src/third-party/scnlib/include/scn/detail/locale.h b/src/third-party/scnlib/include/scn/detail/locale.h
new file mode 100644
index 0000000..d4d0f8c
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/locale.h
@@ -0,0 +1,595 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_LOCALE_H
+#define SCN_DETAIL_LOCALE_H
+
+#include "../unicode/unicode.h"
+#include "../util/array.h"
+#include "../util/string_view.h"
+#include "../util/unique_ptr.h"
+
+#include <cwchar>
+#include <string>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ constexpr bool has_zero(uint64_t v)
+ {
+ return (v - UINT64_C(0x0101010101010101)) & ~v &
+ UINT64_C(0x8080808080808080);
+ }
+
+ template <typename CharT>
+ CharT ascii_widen(char ch);
+ template <>
+ constexpr char ascii_widen(char ch)
+ {
+ return ch;
+ }
+ template <>
+ constexpr wchar_t ascii_widen(char ch)
+ {
+ return static_cast<wchar_t>(ch);
+ }
+
+ // Hand write to avoid C locales and thus noticeable performance losses
+ inline bool is_space(char ch) noexcept
+ {
+ static constexpr detail::array<bool, 256> lookup = {
+ {false, false, false, false, false, false, false, false, false,
+ true, true, true, true, true, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, true, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false,
+ false, false, false, false}};
+ return lookup[static_cast<size_t>(static_cast<unsigned char>(ch))];
+ }
+ constexpr inline bool is_space(wchar_t ch) noexcept
+ {
+ return ch == 0x20 || (ch >= 0x09 && ch <= 0x0d);
+ }
+ constexpr inline bool is_space(code_point cp) noexcept
+ {
+ return cp == 0x20 || (cp >= 0x09 && cp <= 0x0d);
+ }
+
+ constexpr inline bool is_digit(char ch) noexcept
+ {
+ return ch >= '0' && ch <= '9';
+ }
+ constexpr inline bool is_digit(wchar_t ch) noexcept
+ {
+ return ch >= L'0' && ch <= L'9';
+ }
+ constexpr inline bool is_digit(code_point cp) noexcept
+ {
+ return cp >= '0' && cp <= '9';
+ }
+
+ template <typename CharT>
+ struct locale_defaults;
+ template <>
+ struct locale_defaults<char> {
+ static constexpr string_view truename()
+ {
+ return {"true"};
+ }
+ static constexpr string_view falsename()
+ {
+ return {"false"};
+ }
+ static constexpr char decimal_point() noexcept
+ {
+ return '.';
+ }
+ static constexpr char thousands_separator() noexcept
+ {
+ return ',';
+ }
+ };
+ template <>
+ struct locale_defaults<wchar_t> {
+ static constexpr wstring_view truename()
+ {
+ return {L"true"};
+ }
+ static constexpr wstring_view falsename()
+ {
+ return {L"false"};
+ }
+ static constexpr wchar_t decimal_point() noexcept
+ {
+ return L'.';
+ }
+ static constexpr wchar_t thousands_separator() noexcept
+ {
+ return L',';
+ }
+ };
+ } // namespace detail
+
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wpadded")
+
+ // scn::scan:
+ // - no L flag -> use hard-coded defaults, akin to "C"
+ // locale_ref.default() -> default_locale_ref
+ // - L flag -> use global C++ locale
+ // locale_ref.localized() -> custom_locale_ref (global C++)
+ // scn::scan_localized:
+ // - no L flag -> use hard-coded defaults, akin to "C"
+ // locale_ref.default() -> default_locale_ref
+ // - L flag -> use given C++ locale
+ // locale_ref.localized() -> custom_locale_ref (given locale)
+
+ namespace detail {
+ // constexpr locale
+ template <typename CharT, typename SV, typename Def>
+ struct basic_static_locale_ref_base {
+ using char_type = CharT;
+ using string_view_type = SV;
+ using defaults = Def;
+
+ static constexpr bool is_static = true;
+
+ constexpr basic_static_locale_ref_base() = default;
+
+ static constexpr bool is_space(char_type ch)
+ {
+ return detail::is_space(ch);
+ }
+ static constexpr bool is_digit(char_type ch)
+ {
+ return detail::is_digit(ch);
+ }
+
+ static SCN_CONSTEXPR14 bool is_space(span<const char_type> ch)
+ {
+ SCN_EXPECT(ch.size() >= 1);
+ return detail::is_space(ch[0]);
+ }
+ static SCN_CONSTEXPR14 bool is_digit(span<const char_type> ch)
+ {
+ SCN_EXPECT(ch.size() >= 1);
+ return detail::is_digit(ch[0]);
+ }
+
+ static constexpr char_type decimal_point()
+ {
+ return defaults::decimal_point();
+ }
+ static constexpr char_type thousands_separator()
+ {
+ return defaults::thousands_separator();
+ }
+
+ static constexpr string_view_type truename()
+ {
+ return defaults::truename();
+ }
+ static constexpr string_view_type falsename()
+ {
+ return defaults::falsename();
+ }
+ };
+ template <typename CharT>
+ struct basic_static_locale_ref
+ : basic_static_locale_ref_base<CharT,
+ basic_string_view<CharT>,
+ locale_defaults<CharT>> {
+ };
+ template <>
+ struct basic_static_locale_ref<code_point>
+ : basic_static_locale_ref_base<code_point,
+ string_view,
+ locale_defaults<char>> {
+ };
+
+ // base class
+ template <typename CharT>
+ class basic_locale_ref_impl_base {
+ public:
+ using char_type = CharT;
+ using string_type = std::basic_string<char_type>;
+ using string_view_type = basic_string_view<char_type>;
+
+ static constexpr bool is_static = false;
+
+ basic_locale_ref_impl_base() = default;
+
+ basic_locale_ref_impl_base(const basic_locale_ref_impl_base&) =
+ default;
+ basic_locale_ref_impl_base(basic_locale_ref_impl_base&&) = default;
+ basic_locale_ref_impl_base& operator=(
+ const basic_locale_ref_impl_base&) = default;
+ basic_locale_ref_impl_base& operator=(
+ basic_locale_ref_impl_base&&) = default;
+
+#define SCN_DEFINE_LOCALE_REF_CTYPE(f) \
+ bool is_##f(char_type ch) const \
+ { \
+ return do_is_##f(ch); \
+ } \
+ bool is_##f(span<const char_type> ch) const \
+ { \
+ return do_is_##f(ch); \
+ }
+
+ SCN_DEFINE_LOCALE_REF_CTYPE(space)
+ SCN_DEFINE_LOCALE_REF_CTYPE(digit)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(alnum)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(alpha)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(blank)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(cntrl)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(graph)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(lower)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(print)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(punct)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(upper)
+ // SCN_DEFINE_LOCALE_REF_CTYPE(xdigit)
+#undef SCN_DEFINE_LOCALE_REF_CTYPE
+
+ char_type decimal_point() const
+ {
+ return do_decimal_point();
+ }
+ char_type thousands_separator() const
+ {
+ return do_thousands_separator();
+ }
+
+ string_view_type truename() const
+ {
+ return do_truename();
+ }
+ string_view_type falsename() const
+ {
+ return do_falsename();
+ }
+
+ protected:
+ ~basic_locale_ref_impl_base() = default;
+
+ private:
+#define SCN_DECLARE_LOCALE_REF_CTYPE_DO(f) \
+ virtual bool do_is_##f(char_type) const = 0; \
+ virtual bool do_is_##f(span<const char_type>) const = 0;
+ SCN_DECLARE_LOCALE_REF_CTYPE_DO(space)
+ SCN_DECLARE_LOCALE_REF_CTYPE_DO(digit)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(alnum)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(alpha)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(blank)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(cntrl)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(graph)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(lower)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(print)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(punct)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(upper)
+ // SCN_DECLARE_LOCALE_REF_CTYPE_DO(xdigit)
+#undef SCN_DECLARE_LOCALE_REF_CTYPE_DO
+
+ virtual char_type do_decimal_point() const = 0;
+ virtual char_type do_thousands_separator() const = 0;
+ virtual string_view_type do_truename() const = 0;
+ virtual string_view_type do_falsename() const = 0;
+ };
+
+ // hardcoded "C", using static_locale_ref
+ template <typename CharT>
+ class basic_default_locale_ref final
+ : public basic_locale_ref_impl_base<CharT> {
+ using base = basic_locale_ref_impl_base<CharT>;
+
+ public:
+ using char_type = typename base::char_type;
+ using string_view_type = typename base::string_view_type;
+
+ basic_default_locale_ref() = default;
+
+ private:
+ using static_type = basic_static_locale_ref<char_type>;
+
+ bool do_is_space(char_type ch) const override
+ {
+ return static_type::is_space(ch);
+ }
+ bool do_is_digit(char_type ch) const override
+ {
+ return static_type::is_digit(ch);
+ }
+
+ bool do_is_space(span<const char_type> ch) const override
+ {
+ return static_type::is_space(ch);
+ }
+ bool do_is_digit(span<const char_type> ch) const override
+ {
+ return static_type::is_digit(ch);
+ }
+
+ char_type do_decimal_point() const override
+ {
+ return static_type::decimal_point();
+ }
+ char_type do_thousands_separator() const override
+ {
+ return static_type::thousands_separator();
+ }
+ string_view_type do_truename() const override
+ {
+ return static_type::truename();
+ }
+ string_view_type do_falsename() const override
+ {
+ return static_type::falsename();
+ }
+ };
+
+ // custom
+ template <typename CharT>
+ class basic_custom_locale_ref final
+ : public basic_locale_ref_impl_base<CharT> {
+ using base = basic_locale_ref_impl_base<CharT>;
+
+ public:
+ using char_type = typename base::char_type;
+ using string_type = typename base::string_type;
+ using string_view_type = typename base::string_view_type;
+
+ basic_custom_locale_ref();
+ basic_custom_locale_ref(const void* locale);
+
+ basic_custom_locale_ref(const basic_custom_locale_ref&) = delete;
+ basic_custom_locale_ref& operator=(const basic_custom_locale_ref&) =
+ delete;
+
+ basic_custom_locale_ref(basic_custom_locale_ref&&);
+ basic_custom_locale_ref& operator=(basic_custom_locale_ref&&);
+
+ ~basic_custom_locale_ref();
+
+ static basic_custom_locale_ref make_classic();
+
+ const void* get_locale() const
+ {
+ return m_locale;
+ }
+
+ void convert_to_global();
+ void convert_to_classic();
+
+ // narrow: locale multibyte -> locale wide
+ // wide: identity
+ error convert_to_wide(const CharT* from_begin,
+ const CharT* from_end,
+ const CharT*& from_next,
+ wchar_t* to_begin,
+ wchar_t* to_end,
+ wchar_t*& to_next) const;
+ expected<wchar_t> convert_to_wide(const CharT* from_begin,
+ const CharT* from_end) const;
+
+#define SCN_DEFINE_CUSTOM_LOCALE_CTYPE(f) \
+ bool is_##f(char_type) const; \
+ bool is_##f(span<const char_type>) const; \
+ bool is_##f(code_point) const;
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(alnum)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(alpha)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(blank)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(cntrl)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(graph)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(lower)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(print)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(punct)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(upper)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(xdigit)
+#undef SCN_DEFINE_CUSTOM_LOCALE_CTYPE
+
+ bool is_space(code_point) const;
+ using base::is_space;
+
+ bool is_digit(code_point) const;
+ using base::is_digit;
+
+ template <typename T>
+ expected<std::ptrdiff_t> read_num(T& val,
+ const string_type& buf,
+ int base) const;
+
+ private:
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ bool do_is_space(char_type ch) const override;
+ bool do_is_digit(char_type ch) const override;
+
+ bool do_is_space(span<const char_type> ch) const override;
+ bool do_is_digit(span<const char_type> ch) const override;
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+
+ char_type do_decimal_point() const override;
+ char_type do_thousands_separator() const override;
+ string_view_type do_truename() const override;
+ string_view_type do_falsename() const override;
+
+ void _initialize();
+
+ const void* m_locale{nullptr};
+ void* m_data{nullptr};
+ };
+ } // namespace detail
+
+ template <typename CharT>
+ class basic_locale_ref {
+ public:
+ using char_type = CharT;
+ using impl_base = detail::basic_locale_ref_impl_base<char_type>;
+ using static_type = detail::basic_static_locale_ref<char_type>;
+ using default_type = detail::basic_default_locale_ref<char_type>;
+ using custom_type = detail::basic_custom_locale_ref<char_type>;
+
+ // default
+ constexpr basic_locale_ref() = default;
+ // nullptr = global
+ constexpr basic_locale_ref(const void* p) : m_payload(p) {}
+
+ basic_locale_ref clone() const
+ {
+ return {m_payload};
+ }
+
+ constexpr bool has_custom() const
+ {
+ return m_payload != nullptr;
+ }
+
+ // hardcoded "C", constexpr, should be preferred whenever possible
+ constexpr static_type get_static() const
+ {
+ return {};
+ }
+
+ // hardcoded "C", not constexpr
+ default_type& get_default()
+ {
+ return m_default;
+ }
+ const default_type& get_default() const
+ {
+ return m_default;
+ }
+
+ // global locale or given locale
+ custom_type& get_localized()
+ {
+ _construct_custom();
+ return *m_custom;
+ }
+ const custom_type& get_localized() const
+ {
+ _construct_custom();
+ return *m_custom;
+ }
+
+ custom_type make_localized_classic() const
+ {
+ return custom_type::make_classic();
+ }
+
+ custom_type* get_localized_unsafe()
+ {
+ return m_custom.get();
+ }
+ const custom_type* get_localized_unsafe() const
+ {
+ return m_custom.get();
+ }
+
+ // virtual interface
+ impl_base& get(bool localized)
+ {
+ if (localized) {
+ return get_localized();
+ }
+ return get_default();
+ }
+ const impl_base& get(bool localized) const
+ {
+ if (localized) {
+ return get_localized();
+ }
+ return get_default();
+ }
+
+ void prepare_localized() const
+ {
+ _construct_custom();
+ }
+ void reset_locale(const void* payload)
+ {
+ m_custom.reset();
+ m_payload = payload;
+ _construct_custom();
+ }
+
+ private:
+ void _construct_custom() const
+ {
+ if (m_custom) {
+ // already constructed
+ return;
+ }
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ m_custom = detail::make_unique<custom_type>(m_payload);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+
+ mutable detail::unique_ptr<custom_type> m_custom{nullptr};
+ const void* m_payload{nullptr};
+ default_type m_default{};
+ };
+
+ template <typename CharT, typename Locale>
+ basic_locale_ref<CharT> make_locale_ref(const Locale& loc)
+ {
+ return {std::addressof(loc)};
+ }
+ template <typename CharT>
+ basic_locale_ref<CharT> make_default_locale_ref()
+ {
+ return {};
+ }
+
+ using locale_ref = basic_locale_ref<char>;
+ using wlocale_ref = basic_locale_ref<wchar_t>;
+
+ SCN_CLANG_POP // -Wpadded
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY && !defined(SCN_LOCALE_CPP)
+#include "locale.cpp"
+#endif
+
+#endif // SCN_DETAIL_LOCALE_H
diff --git a/src/third-party/scnlib/include/scn/detail/parse_context.h b/src/third-party/scnlib/include/scn/detail/parse_context.h
new file mode 100644
index 0000000..91d6687
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/parse_context.h
@@ -0,0 +1,581 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_PARSE_CONTEXT_H
+#define SCN_DETAIL_PARSE_CONTEXT_H
+
+#include "../util/expected.h"
+#include "locale.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ class parse_context_base {
+ public:
+ SCN_CONSTEXPR14 std::ptrdiff_t next_arg_id()
+ {
+ return m_next_arg_id >= 0 ? m_next_arg_id++ : 0;
+ }
+ SCN_CONSTEXPR14 bool check_arg_id(std::ptrdiff_t)
+ {
+ if (m_next_arg_id > 0) {
+ return false;
+ }
+ m_next_arg_id = -1;
+ return true;
+ }
+
+ protected:
+ parse_context_base() = default;
+
+ std::ptrdiff_t m_next_arg_id{0};
+ };
+ } // namespace detail
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wpadded")
+
+ template <typename CharT>
+ class basic_parse_context : public detail::parse_context_base {
+ public:
+ using char_type = CharT;
+ using locale_type = basic_locale_ref<CharT>;
+ using string_view_type = basic_string_view<char_type>;
+ using iterator = typename string_view_type::iterator;
+
+ constexpr basic_parse_context(basic_string_view<char_type> f,
+ locale_type& loc)
+ : m_str(f), m_locale(loc)
+ {
+ }
+
+ /**
+ * Returns `true`, if `next_char()` is a whitespace character according
+ * to the static locale. This means, that `skip_range_whitespace()`
+ * should be called on the source range.
+ */
+ bool should_skip_ws()
+ {
+ bool skip = false;
+ while (*this && m_locale.get_static().is_space(next_char())) {
+ skip = true;
+ advance_char();
+ }
+ return skip;
+ }
+ /**
+ * Returns `true`, if a character equal to `next_char()` should be read
+ * from the source range.
+ *
+ * If `*this` currently points to an escaped
+ * brace character `"{{"` or `"}}"`, skips the first brace, so that
+ * after this function is called, `next_char()` returns the character
+ * that should be read.
+ */
+ bool should_read_literal()
+ {
+ const auto brace = detail::ascii_widen<char_type>('{');
+ if (next_char() != brace) {
+ if (next_char() == detail::ascii_widen<char_type>('}')) {
+ advance_char();
+ }
+ return true;
+ }
+ if (SCN_UNLIKELY(m_str.size() > 1 &&
+ *(m_str.begin() + 1) == brace)) {
+ advance_char();
+ return true;
+ }
+ return false;
+ }
+ /**
+ * Returns `true` if `ch` is equal to `next_char()`
+ */
+ SCN_NODISCARD constexpr bool check_literal(char_type ch) const
+ {
+ return ch == next_char();
+ }
+ /**
+ * Returns `true` if the code units contained in `ch` are equal to the
+ * code units starting from `pctx.begin()`. If `chars_left() <
+ * ch.size()`, returns `false`.
+ */
+ SCN_NODISCARD SCN_CONSTEXPR14 bool check_literal(
+ span<const char_type> ch) const
+ {
+ if (chars_left() < ch.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < ch.size(); ++i) {
+ if (ch[i] != m_str[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ /**
+ * Returns `true` if `cp` is equal to the value returned by `next_cp()`.
+ * If `next_cp()` errored, returns that error
+ * (`error::invalid_encoding`).
+ */
+ SCN_NODISCARD SCN_CONSTEXPR14 expected<bool> check_literal_cp(
+ code_point cp) const
+ {
+ auto next = next_cp();
+ if (!next) {
+ return next.error();
+ }
+ return cp == next.value();
+ }
+
+ /**
+ * Returns `true` if there are characters left in `*this`.
+ */
+ constexpr bool good() const
+ {
+ return !m_str.empty();
+ }
+ constexpr explicit operator bool() const
+ {
+ return good();
+ }
+
+ /**
+ * Returns the next character (= code unit) in `*this`.
+ * `good()` must be `true`.
+ */
+ constexpr char_type next_char() const
+ {
+ return m_str.front();
+ }
+ /**
+ * Returns the next code point in `*this`.
+ * If the code point is encoded incorrectly, returns
+ * `error::invalid_encoding`.
+ */
+ SCN_NODISCARD SCN_CONSTEXPR14 expected<code_point> next_cp() const
+ {
+ code_point cp{};
+ auto it = parse_code_point(m_str.begin(), m_str.end(), cp);
+ if (!it) {
+ return it.error();
+ }
+ return {cp};
+ }
+
+ /**
+ * Returns the number of chars (= code units) left in `*this`.
+ */
+ constexpr std::size_t chars_left() const noexcept
+ {
+ return m_str.size();
+ }
+ /**
+ * Returns the number of code points left in `*this`. If `*this`
+ * contains invalid encoding, returns `error::invalid_encoding`.
+ */
+ SCN_NODISCARD SCN_CONSTEXPR14 expected<std::size_t> cp_left()
+ const noexcept
+ {
+ auto d = code_point_distance(m_str.begin(), m_str.end());
+ if (!d) {
+ return d.error();
+ }
+ return {static_cast<std::size_t>(d.value())};
+ }
+
+ /**
+ * Advances `*this` by `n` characters (= code units). `*this` must have
+ * at least `n` characters left.
+ */
+ SCN_CONSTEXPR14 void advance_char(std::ptrdiff_t n = 1) noexcept
+ {
+ SCN_EXPECT(chars_left() >= static_cast<std::size_t>(n));
+ m_str.remove_prefix(static_cast<std::size_t>(n));
+ }
+ /**
+ * Advances `*this` by a single code point. If the code point is encoded
+ * incorrectly, returns `error::invalid_encoding`.
+ */
+ SCN_NODISCARD SCN_CONSTEXPR14 error advance_cp() noexcept
+ {
+ code_point cp{};
+ auto it = parse_code_point(m_str.begin(), m_str.end(), cp);
+ if (!it) {
+ return it.error();
+ }
+ m_str.remove_prefix(
+ static_cast<size_t>(it.value() - m_str.begin()));
+ return {};
+ }
+
+ /**
+ * Returns `true`, if `*this` has over `n` characters (= code units)
+ * left, so that `peek_char()` with the same `n` parameter can be
+ * called.
+ */
+ constexpr bool can_peek_char(std::size_t n = 1) const noexcept
+ {
+ return chars_left() > n;
+ }
+
+ /**
+ * Returns the character (= code unit) `n` characters past the current
+ * character, so that `peek_char(0)` is equivalent to `next_char()`.
+ * `n <= chars_left()` must be `true`.
+ */
+ SCN_CONSTEXPR14 char_type peek_char(std::size_t n = 1) const noexcept
+ {
+ SCN_EXPECT(n <= chars_left());
+ return m_str[n];
+ }
+ /**
+ * Returns the code point past the current code point (`next_cp()`).
+ *
+ * If there is no code point to peek (the current code point is the last
+ * one in `*this`), returns `error::end_of_range`.
+ * If `*this` contains invalid encoding, returns
+ * `error::invalid_encoding`.
+ */
+ SCN_NODISCARD SCN_CONSTEXPR14 expected<code_point> peek_cp()
+ const noexcept
+ {
+ if (m_str.size() < 2) {
+ return error{error::end_of_range,
+ "End of format string, cannot peek"};
+ }
+
+ code_point cp{};
+ auto it = parse_code_point(m_str.begin(), m_str.end(), cp);
+ if (!it) {
+ return it.error();
+ }
+ if (it.value() == m_str.end()) {
+ return error{error::end_of_range,
+ "End of format string, cannot peek"};
+ }
+
+ it = parse_code_point(it.value(), m_str.end(), cp);
+ if (!it) {
+ return it.error();
+ }
+ return {cp};
+ }
+
+ SCN_CONSTEXPR14 iterator begin() const noexcept
+ {
+ return m_str.begin();
+ }
+ SCN_CONSTEXPR14 iterator end() const noexcept
+ {
+ return m_str.end();
+ }
+
+ /**
+ * Returns `true`, if `next_char() == '{'`
+ */
+ SCN_CONSTEXPR14 bool check_arg_begin() const
+ {
+ SCN_EXPECT(good());
+ return next_char() == detail::ascii_widen<char_type>('{');
+ }
+ /**
+ * Returns `true`, if `next_char() == '}'`
+ */
+ SCN_CONSTEXPR14 bool check_arg_end() const
+ {
+ SCN_EXPECT(good());
+ return next_char() == detail::ascii_widen<char_type>('}');
+ }
+
+ using parse_context_base::check_arg_id;
+ SCN_CONSTEXPR14 void check_arg_id(basic_string_view<CharT>) {}
+
+ SCN_CONSTEXPR14 void arg_begin() const noexcept {}
+ SCN_CONSTEXPR14 void arg_end() const noexcept {}
+
+ SCN_CONSTEXPR14 void arg_handled() const noexcept {}
+
+ const locale_type& locale() const
+ {
+ return m_locale;
+ }
+
+ /**
+ * Parse `*this` using `s`
+ */
+ template <typename Scanner>
+ error parse(Scanner& s)
+ {
+ return s.parse(*this);
+ }
+
+ bool has_arg_id()
+ {
+ SCN_EXPECT(good());
+ if (m_str.size() == 1) {
+ return true;
+ }
+ if (m_str[1] == detail::ascii_widen<char_type>('}')) {
+ advance_char();
+ return false;
+ }
+ if (m_str[1] == detail::ascii_widen<char_type>(':')) {
+ advance_char(2);
+ return false;
+ }
+ return true;
+ }
+ expected<string_view_type> parse_arg_id()
+ {
+ SCN_EXPECT(good());
+ advance_char();
+ if (SCN_UNLIKELY(!good())) {
+ return error(error::invalid_format_string,
+ "Unexpected end of format argument");
+ }
+ auto it = m_str.begin();
+ for (std::ptrdiff_t i = 0; good(); ++i, (void)advance_char()) {
+ if (check_arg_end()) {
+ return string_view_type{
+ it,
+ static_cast<typename string_view_type::size_type>(i)};
+ }
+ if (next_char() == detail::ascii_widen<char_type>(':')) {
+ advance_char();
+ return string_view_type{
+ it,
+ static_cast<typename string_view_type::size_type>(i)};
+ }
+ }
+ return error(error::invalid_format_string,
+ "Unexpected end of format argument");
+ }
+
+ private:
+ string_view_type m_str;
+ locale_type& m_locale;
+ };
+
+ template <typename CharT>
+ class basic_empty_parse_context : public detail::parse_context_base {
+ public:
+ using char_type = CharT;
+ using locale_type = basic_locale_ref<char_type>;
+ using string_view_type = basic_string_view<char_type>;
+
+ constexpr basic_empty_parse_context(int args,
+ locale_type& loc,
+ bool localized = false)
+ : m_locale(loc), m_args_left(args), m_localized(localized)
+ {
+ }
+
+ SCN_CONSTEXPR14 bool should_skip_ws()
+ {
+ if (m_should_skip_ws) {
+ m_should_skip_ws = false;
+ return true;
+ }
+ return false;
+ }
+ constexpr bool should_read_literal() const
+ {
+ return false;
+ }
+ constexpr bool check_literal(char_type) const
+ {
+ return false;
+ }
+ constexpr bool check_literal(span<const char_type>) const
+ {
+ return false;
+ }
+ constexpr bool check_literal_cp(code_point) const
+ {
+ return false;
+ }
+
+ constexpr bool good() const
+ {
+ return m_args_left > 0;
+ }
+ constexpr explicit operator bool() const
+ {
+ return good();
+ }
+
+ SCN_CONSTEXPR14 void advance_char(std::ptrdiff_t = 1) const noexcept {}
+ SCN_CONSTEXPR14 error advance_cp() const noexcept
+ {
+ return {};
+ }
+
+ char_type next_char() const
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+ expected<std::pair<code_point, std::ptrdiff_t>> next_cp() const
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+
+ std::size_t chars_left() const noexcept
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+ std::size_t cp_left() const noexcept
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+
+ constexpr bool can_peek_char() const noexcept
+ {
+ return false;
+ }
+ constexpr bool can_peek_cp() const noexcept
+ {
+ return false;
+ }
+
+ char_type peek_char(std::ptrdiff_t = 1) const noexcept
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+ expected<code_point> peek_cp() const noexcept
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+
+ constexpr bool check_arg_begin() const
+ {
+ return true;
+ }
+ constexpr bool check_arg_end() const
+ {
+ return true;
+ }
+
+ using parse_context_base::check_arg_id;
+ SCN_CONSTEXPR14 void check_arg_id(basic_string_view<CharT>) {}
+
+ SCN_CONSTEXPR14 void arg_begin() const noexcept {}
+ SCN_CONSTEXPR14 void arg_end() const noexcept {}
+
+ SCN_CONSTEXPR14 void arg_handled()
+ {
+ m_should_skip_ws = true;
+ --m_args_left;
+ }
+
+ const locale_type& locale() const
+ {
+ return m_locale;
+ }
+
+ template <typename Scanner>
+ SCN_CONSTEXPR14 error parse(Scanner& s) const
+ {
+ if (m_localized) {
+ s.make_localized();
+ }
+ return {};
+ }
+
+ constexpr bool has_arg_id() const
+ {
+ return false;
+ }
+ SCN_CONSTEXPR14 expected<string_view_type> parse_arg_id() const
+ {
+ SCN_EXPECT(good());
+ return string_view_type{};
+ }
+
+ void reset_args_left(int n)
+ {
+ m_args_left = n;
+ parse_context_base::m_next_arg_id = 0;
+ m_should_skip_ws = false;
+ }
+
+ private:
+ locale_type& m_locale;
+ int m_args_left;
+ bool m_localized;
+ bool m_should_skip_ws{false};
+ };
+
+ namespace detail {
+ template <typename CharT>
+ basic_parse_context<CharT> make_parse_context_impl(
+ basic_string_view<CharT> f,
+ basic_locale_ref<CharT>& loc,
+ bool)
+ {
+ return {f, loc};
+ }
+ template <typename CharT>
+ basic_empty_parse_context<CharT> make_parse_context_impl(
+ int i,
+ basic_locale_ref<CharT>& loc,
+ bool localized)
+ {
+ return {i, loc, localized};
+ }
+
+ template <typename CharT>
+ struct parse_context_template_for_format<basic_string_view<CharT>> {
+ template <typename T>
+ using type = basic_parse_context<T>;
+ };
+ template <>
+ struct parse_context_template_for_format<int> {
+ template <typename CharT>
+ using type = basic_empty_parse_context<CharT>;
+ };
+
+ template <typename F, typename CharT>
+ auto make_parse_context(F f,
+ basic_locale_ref<CharT>& locale,
+ bool localized)
+ -> decltype(make_parse_context_impl(f, locale, localized))
+ {
+ return make_parse_context_impl(f, locale, localized);
+ }
+ } // namespace detail
+
+ template <typename F, typename CharT>
+ auto make_parse_context(F f, basic_locale_ref<CharT>& locale)
+ -> decltype(detail::make_parse_context_impl(f, locale, false))
+ {
+ return detail::make_parse_context_impl(f, locale, false);
+ }
+
+ SCN_CLANG_POP // -Wpadded
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_DETAIL_PARSE_CONTEXT_H
diff --git a/src/third-party/scnlib/include/scn/detail/range.h b/src/third-party/scnlib/include/scn/detail/range.h
new file mode 100644
index 0000000..5b8802f
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/range.h
@@ -0,0 +1,598 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_RANGE_H
+#define SCN_DETAIL_RANGE_H
+
+#include "../ranges/ranges.h"
+#include "../util/algorithm.h"
+#include "../util/memory.h"
+#include "error.h"
+#include "vectored.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ namespace _reset_begin_iterator {
+ struct fn {
+ private:
+ template <typename Iterator>
+ static auto impl(Iterator& it, priority_tag<1>) noexcept(
+ noexcept(it.reset_begin_iterator()))
+ -> decltype(it.reset_begin_iterator())
+ {
+ return it.reset_begin_iterator();
+ }
+
+ template <typename Iterator>
+ static void impl(Iterator&, size_t, priority_tag<0>) noexcept
+ {
+ }
+
+ public:
+ template <typename Iterator>
+ auto operator()(Iterator& it) const
+ noexcept(noexcept(fn::impl(it, priority_tag<1>{})))
+ -> decltype(fn::impl(it, priority_tag<1>{}))
+ {
+ return fn::impl(it, priority_tag<1>{});
+ }
+ };
+ } // namespace _reset_begin_iterator
+ namespace {
+ static constexpr auto& reset_begin_iterator =
+ static_const<detail::_reset_begin_iterator::fn>::value;
+ }
+
+ template <typename Iterator, typename = void>
+ struct extract_char_type;
+ template <typename Iterator>
+ struct extract_char_type<
+ Iterator,
+ typename std::enable_if<std::is_integral<
+ polyfill_2a::iter_value_t<Iterator>>::value>::type> {
+ using type = polyfill_2a::iter_value_t<Iterator>;
+ };
+ template <typename Iterator>
+ struct extract_char_type<
+ Iterator,
+ void_t<
+ typename std::enable_if<!std::is_integral<
+ polyfill_2a::iter_value_t<Iterator>>::value>::type,
+ typename polyfill_2a::iter_value_t<Iterator>::success_type>> {
+ using type =
+ typename polyfill_2a::iter_value_t<Iterator>::success_type;
+ };
+
+ template <typename Range, typename = void>
+ struct is_direct_impl
+ : std::is_integral<ranges::range_value_t<const Range>> {
+ };
+
+ template <typename Range>
+ struct reconstruct_tag {
+ };
+
+ template <
+ typename Range,
+ typename Iterator,
+ typename Sentinel,
+ typename = typename std::enable_if<
+ std::is_constructible<Range, Iterator, Sentinel>::value>::type>
+ Range reconstruct(reconstruct_tag<Range>, Iterator begin, Sentinel end)
+ {
+ return {begin, end};
+ }
+#if SCN_HAS_STRING_VIEW
+ // std::string_view is not reconstructible pre-C++20
+ template <typename CharT,
+ typename Traits,
+ typename Iterator,
+ typename Sentinel>
+ std::basic_string_view<CharT, Traits> reconstruct(
+ reconstruct_tag<std::basic_string_view<CharT, Traits>>,
+ Iterator begin,
+ Sentinel end)
+ {
+ // On MSVC, string_view can't even be constructed from its
+ // iterators!
+ return {::scn::detail::to_address(begin),
+ static_cast<size_t>(ranges::distance(begin, end))};
+ }
+#endif // SCN_HAS_STRING_VIEW
+
+ template <typename T, bool>
+ struct range_wrapper_storage;
+ template <typename T>
+ struct range_wrapper_storage<T, true> {
+ using type = remove_cvref_t<T>;
+ using range_type = const type&;
+
+ const type* value{nullptr};
+
+ range_wrapper_storage() = default;
+ range_wrapper_storage(const type& v, dummy_type)
+ : value(std::addressof(v))
+ {
+ }
+
+ const type& get() const& noexcept
+ {
+ return *value;
+ }
+ type&& get() && noexcept
+ {
+ return *value;
+ }
+ };
+ template <typename T>
+ struct range_wrapper_storage<T, false> {
+ using range_type = T;
+
+ T value{};
+
+ range_wrapper_storage() = default;
+ template <typename U>
+ range_wrapper_storage(U&& v, dummy_type) : value(SCN_FWD(v))
+ {
+ }
+
+ const T& get() const& noexcept
+ {
+ return value;
+ }
+ T&& get() && noexcept
+ {
+ return value;
+ }
+ };
+
+ template <typename T>
+ using _range_wrapper_marker = typename T::range_wrapper_marker;
+
+ template <typename T>
+ struct _has_range_wrapper_marker
+ : custom_ranges::detail::exists<_range_wrapper_marker, T> {
+ };
+
+ /**
+ * Wraps a source range for more consistent behavior
+ */
+ template <typename Range>
+ class range_wrapper {
+ public:
+ using range_type = Range;
+ using range_nocvref_type = remove_cvref_t<Range>;
+ using iterator = ranges::iterator_t<const range_nocvref_type>;
+ using sentinel = ranges::sentinel_t<const range_nocvref_type>;
+ using char_type = typename extract_char_type<iterator>::type;
+ using difference_type =
+ ranges::range_difference_t<const range_nocvref_type>;
+ using storage_type =
+ range_wrapper_storage<Range, std::is_reference<Range>::value>;
+ using storage_range_type = typename storage_type::range_type;
+
+ using range_wrapper_marker = void;
+
+ template <
+ typename R,
+ typename = typename std::enable_if<
+ !_has_range_wrapper_marker<remove_cvref_t<R>>::value>::type>
+ range_wrapper(R&& r)
+ : m_range(SCN_FWD(r), dummy_type{}),
+ m_begin(ranges::cbegin(m_range.get()))
+ {
+ }
+
+ range_wrapper(const range_wrapper& o) : m_range(o.m_range)
+ {
+ const auto n =
+ ranges::distance(o.begin_underlying(), o.m_begin);
+ m_begin = ranges::cbegin(m_range.get());
+ ranges::advance(m_begin, n);
+ m_read = o.m_read;
+ }
+ range_wrapper& operator=(const range_wrapper& o)
+ {
+ const auto n =
+ ranges::distance(o.begin_underlying(), o.m_begin);
+ m_range = o.m_range;
+ m_begin = ranges::cbegin(m_range.get());
+ ranges::advance(m_begin, n);
+ m_read = o.m_read;
+ return *this;
+ }
+
+ range_wrapper(range_wrapper&& o) noexcept
+ {
+ const auto n =
+ ranges::distance(o.begin_underlying(), o.m_begin);
+ m_range = SCN_MOVE(o.m_range);
+ m_begin = ranges::cbegin(m_range.get());
+ ranges::advance(m_begin, n);
+ m_read = exchange(o.m_read, 0);
+ }
+ range_wrapper& operator=(range_wrapper&& o) noexcept
+ {
+ reset_to_rollback_point();
+
+ const auto n =
+ ranges::distance(o.begin_underlying(), o.m_begin);
+ m_range = SCN_MOVE(o.m_range);
+ m_begin = ranges::cbegin(m_range.get());
+ ranges::advance(m_begin, n);
+ m_read = exchange(o.m_read, 0);
+ return *this;
+ }
+
+ ~range_wrapper() = default;
+
+ iterator begin() const noexcept
+ {
+ return m_begin;
+ }
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wnoexcept")
+ sentinel end() const noexcept(
+ noexcept(ranges::end(SCN_DECLVAL(const storage_type&).get())))
+ {
+ return ranges::end(m_range.get());
+ }
+ SCN_GCC_POP
+
+ struct dummy {
+ };
+
+ /**
+ * Returns `true` if `begin() == end()`.
+ */
+ bool empty() const
+ {
+ return begin() == end();
+ }
+
+ /**
+ * Advance the begin iterator by `n` characters.
+ */
+ iterator advance(difference_type n = 1) noexcept
+ {
+ SCN_EXPECT(_advance_check(
+ n, std::integral_constant<bool, is_contiguous>{}));
+ m_read += n;
+ ranges::advance(m_begin, n);
+ return m_begin;
+ }
+
+ /// @{
+ /**
+ * Advance the begin iterator, until it's equal to `it`.
+ * Assumes that `it` is reachable by repeatedly incrementing begin,
+ * will hang otherwise.
+ */
+ template <typename R = range_nocvref_type,
+ typename std::enable_if<SCN_CHECK_CONCEPT(
+ ranges::sized_range<R>)>::type* = nullptr>
+ void advance_to(iterator it) noexcept
+ {
+ const auto diff = ranges::distance(m_begin, it);
+ m_read += diff;
+ m_begin = it;
+ }
+ template <typename R = range_nocvref_type,
+ typename std::enable_if<SCN_CHECK_CONCEPT(
+ !ranges::sized_range<R>)>::type* = nullptr>
+ void advance_to(iterator it) noexcept
+ {
+ while (m_begin != it) {
+ ++m_read;
+ ++m_begin;
+ }
+ }
+ /// @}
+
+ /**
+ * Returns the begin iterator of the underlying source range, is not
+ * necessarily equal to `begin()`.
+ */
+ iterator begin_underlying() const noexcept(noexcept(
+ ranges::cbegin(SCN_DECLVAL(const range_nocvref_type&))))
+ {
+ return ranges::cbegin(m_range.get());
+ }
+
+ /**
+ * Returns the underlying source range.
+ * Note that `range_underlying().begin()` may not be equal to
+ * `begin()`.
+ */
+ const range_type& range_underlying() const noexcept
+ {
+ return m_range.get();
+ }
+
+ /**
+ * Returns a pointer to the beginning of the range.
+ * `*this` must be contiguous.
+ */
+ template <typename R = range_nocvref_type,
+ typename std::enable_if<SCN_CHECK_CONCEPT(
+ ranges::contiguous_range<R>)>::type* = nullptr>
+ auto data() const
+ noexcept(noexcept(*SCN_DECLVAL(ranges::iterator_t<const R>)))
+ -> decltype(std::addressof(
+ *SCN_DECLVAL(ranges::iterator_t<const R>)))
+ {
+ return std::addressof(*m_begin);
+ }
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wnoexcept")
+ /**
+ * Returns `end() - begin()`.
+ * `*this` must be sized.
+ */
+ template <typename R = range_nocvref_type,
+ typename std::enable_if<SCN_CHECK_CONCEPT(
+ ranges::sized_range<R>)>::type* = nullptr>
+ auto size() const noexcept(noexcept(
+ ranges::distance(SCN_DECLVAL(ranges::iterator_t<const R>),
+ SCN_DECLVAL(ranges::sentinel_t<const R>))))
+ -> decltype(ranges::distance(
+ SCN_DECLVAL(ranges::iterator_t<const R>),
+ SCN_DECLVAL(ranges::sentinel_t<const R>)))
+ {
+ return ranges::distance(m_begin, end());
+ }
+ SCN_GCC_POP
+ struct dummy2 {
+ };
+
+ template <typename R = range_nocvref_type,
+ typename std::enable_if<provides_buffer_access_impl<
+ R>::value>::type* = nullptr>
+ span<const char_type> get_buffer_and_advance(
+ size_t max_size = std::numeric_limits<size_t>::max())
+ {
+ auto buf = get_buffer(m_range.get(), begin(), max_size);
+ if (buf.size() == 0) {
+ return buf;
+ }
+ advance(buf.ssize());
+ return buf;
+ }
+
+ /**
+ * Reset `begin()` to the rollback point, as if by repeatedly
+ * calling `operator--()` on the begin iterator.
+ *
+ * Returns `error::unrecoverable_source_error` on failure.
+ *
+ * \see set_rollback_point()
+ */
+ error reset_to_rollback_point()
+ {
+ for (; m_read != 0; --m_read) {
+ --m_begin;
+ if (m_begin == end()) {
+ return {error::unrecoverable_source_error,
+ "Putback failed"};
+ }
+ }
+ return {};
+ }
+ /**
+ * Sets the rollback point equal to the current `begin()` iterator.
+ *
+ * \see reset_to_rollback_point()
+ */
+ void set_rollback_point()
+ {
+ m_read = 0;
+ }
+
+ void reset_begin_iterator()
+ {
+ detail::reset_begin_iterator(m_begin);
+ }
+
+ /**
+ * Construct a new source range from `begin()` and `end()`, and wrap
+ * it in a new `range_wrapper`.
+ */
+ template <typename R>
+ auto reconstruct_and_rewrap() && -> range_wrapper<R>
+ {
+ auto reconstructed =
+ reconstruct(reconstruct_tag<R>{}, begin(), end());
+ return {SCN_MOVE(reconstructed)};
+ }
+
+ /**
+ * `true` if `value_type` is a character type (`char` or `wchar_t`)
+ * `false` if it's an `expected` containing a character
+ */
+ static constexpr bool is_direct =
+ is_direct_impl<range_nocvref_type>::value;
+ // can call .data() and memcpy
+ /**
+ * `true` if `this->data()` can be called, and `memcpy` can be
+ * performed on it.
+ */
+ static constexpr bool is_contiguous =
+ SCN_CHECK_CONCEPT(ranges::contiguous_range<range_nocvref_type>);
+ /**
+ * `true` if the range provides a way to access a contiguous buffer
+ * on it (`detail::get_buffer()`), which may not provide the entire
+ * source data, e.g. a `span` of `span`s (vectored I/O).
+ */
+ static constexpr bool provides_buffer_access =
+ provides_buffer_access_impl<range_nocvref_type>::value;
+
+ private:
+ template <typename R = Range>
+ bool _advance_check(std::ptrdiff_t n, std::true_type)
+ {
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wzero-as-null-pointer-constant")
+ return m_begin + n <= end();
+ SCN_CLANG_POP
+ }
+ template <typename R = Range>
+ bool _advance_check(std::ptrdiff_t, std::false_type)
+ {
+ return true;
+ }
+
+ storage_type m_range;
+ iterator m_begin;
+ mutable difference_type m_read{0};
+ };
+
+ namespace _wrap {
+ struct fn {
+ private:
+ template <typename Range>
+ static range_wrapper<Range> impl(const range_wrapper<Range>& r,
+ priority_tag<4>) noexcept
+ {
+ return r;
+ }
+ template <typename Range>
+ static range_wrapper<Range> impl(range_wrapper<Range>&& r,
+ priority_tag<4>) noexcept
+ {
+ return SCN_MOVE(r);
+ }
+
+ template <typename Range>
+ static auto impl(Range&& r, priority_tag<3>) noexcept(
+ noexcept(SCN_FWD(r).wrap())) -> decltype(SCN_FWD(r).wrap())
+ {
+ return SCN_FWD(r).wrap();
+ }
+
+ template <typename CharT, std::size_t N>
+ static auto impl(CharT (&str)[N], priority_tag<2>) noexcept
+ -> range_wrapper<
+ basic_string_view<typename std::remove_cv<CharT>::type>>
+ {
+ return {
+ basic_string_view<typename std::remove_cv<CharT>::type>(
+ str, str + N - 1)};
+ }
+
+ template <typename CharT, typename Allocator>
+ static auto impl(
+ const std::basic_string<CharT,
+ std::char_traits<CharT>,
+ Allocator>& str,
+ priority_tag<2>) noexcept
+ -> range_wrapper<basic_string_view<CharT>>
+ {
+ return {basic_string_view<CharT>{str.data(), str.size()}};
+ }
+ template <typename CharT, typename Allocator>
+ static auto impl(
+ std::basic_string<CharT,
+ std::char_traits<CharT>,
+ Allocator>&& str,
+ priority_tag<2>) noexcept(std::
+ is_nothrow_move_constructible<
+ decltype(str)>::value)
+ -> range_wrapper<std::basic_string<CharT,
+ std::char_traits<CharT>,
+ Allocator>>
+ {
+ return {SCN_MOVE(str)};
+ }
+
+#if SCN_HAS_STRING_VIEW
+ template <typename CharT>
+ static auto impl(const std::basic_string_view<CharT>& str,
+ priority_tag<1>) noexcept
+ -> range_wrapper<basic_string_view<CharT>>
+ {
+ return {basic_string_view<CharT>{str.data(), str.size()}};
+ }
+#endif
+ template <typename T,
+ typename CharT = typename std::remove_const<T>::type>
+ static auto impl(span<T> s, priority_tag<2>) noexcept
+ -> range_wrapper<basic_string_view<CharT>>
+ {
+ return {basic_string_view<CharT>{s.data(), s.size()}};
+ }
+
+ template <typename Range,
+ typename = typename std::enable_if<
+ SCN_CHECK_CONCEPT(ranges::view<Range>)>::type>
+ static auto impl(Range r, priority_tag<1>) noexcept
+ -> range_wrapper<Range>
+ {
+ return {r};
+ }
+
+ template <typename Range>
+ static auto impl(const Range& r, priority_tag<0>) noexcept
+ -> range_wrapper<Range&>
+ {
+ static_assert(SCN_CHECK_CONCEPT(ranges::range<Range>),
+ "Input needs to be a Range");
+ return {r};
+ }
+ template <typename Range,
+ typename = typename std::enable_if<
+ !std::is_reference<Range>::value>::type>
+ static auto impl(Range&& r, priority_tag<0>) noexcept
+ -> range_wrapper<Range>
+ {
+ static_assert(SCN_CHECK_CONCEPT(ranges::range<Range>),
+ "Input needs to be a Range");
+ return {SCN_MOVE(r)};
+ }
+
+ public:
+ template <typename Range>
+ auto operator()(Range&& r) const
+ noexcept(noexcept(fn::impl(SCN_FWD(r), priority_tag<4>{})))
+ -> decltype(fn::impl(SCN_FWD(r), priority_tag<4>{}))
+ {
+ return fn::impl(SCN_FWD(r), priority_tag<4>{});
+ }
+ };
+ } // namespace _wrap
+ } // namespace detail
+
+ namespace {
+ /**
+ * Create a `range_wrapper` for any supported source range.
+ */
+ static constexpr auto& wrap =
+ detail::static_const<detail::_wrap::fn>::value;
+ } // namespace
+
+ template <typename Range>
+ struct range_wrapper_for {
+ using type = decltype(wrap(SCN_DECLVAL(Range)));
+ };
+ template <typename Range>
+ using range_wrapper_for_t = typename range_wrapper_for<Range>::type;
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_DETAIL_RANGE_H
diff --git a/src/third-party/scnlib/include/scn/detail/result.h b/src/third-party/scnlib/include/scn/detail/result.h
new file mode 100644
index 0000000..6e5170d
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/result.h
@@ -0,0 +1,595 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_RESULT_H
+#define SCN_DETAIL_RESULT_H
+
+#include "../util/expected.h"
+#include "error.h"
+#include "range.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ /**
+ * Base class for the result type returned by most scanning functions
+ * (except for \ref scan_value). \ref scn::detail::scan_result_base inherits
+ * either from this class or \ref expected.
+ */
+ struct wrapped_error {
+ wrapped_error() = default;
+ wrapped_error(::scn::error e) : err(e) {}
+
+ /// Get underlying error
+ SCN_NODISCARD ::scn::error error() const
+ {
+ return err;
+ }
+
+ /// Did the operation succeed -- true means success
+ explicit operator bool() const
+ {
+ return err.operator bool();
+ }
+
+ ::scn::error err{};
+ };
+
+ namespace detail {
+ template <typename Base>
+ class scan_result_base_wrapper : public Base {
+ public:
+ scan_result_base_wrapper(Base&& b) : Base(SCN_MOVE(b)) {}
+
+ protected:
+ void set_base(const Base& b)
+ {
+ static_cast<Base&>(*this) = b;
+ }
+ void set_base(Base&& b)
+ {
+ static_cast<Base&>(*this) = SCN_MOVE(b);
+ }
+ };
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wdocumentation-unknown-command")
+
+ /// @{
+
+ /**
+ * Type returned by scanning functions.
+ * Contains an error (inherits from it: for \ref error, that's \ref
+ * wrapped_error; with \ref scan_value, inherits from \ref expected),
+ * and the leftover range after scanning.
+ *
+ * The leftover range may reference the range given to the scanning
+ * function. Please take the necessary measures to make sure that the
+ * original range outlives the leftover range. Alternatively, if
+ * possible for your specific range type, call the \ref reconstruct()
+ * member function to get a new, independent range.
+ */
+ template <typename WrappedRange, typename Base>
+ class scan_result_base : public scan_result_base_wrapper<Base> {
+ public:
+ using wrapped_range_type = WrappedRange;
+ using base_type = scan_result_base_wrapper<Base>;
+
+ using range_type = typename wrapped_range_type::range_type;
+ using iterator = typename wrapped_range_type::iterator;
+ using sentinel = typename wrapped_range_type::sentinel;
+ using char_type = typename wrapped_range_type::char_type;
+
+ scan_result_base(Base&& b, wrapped_range_type&& r)
+ : base_type(SCN_MOVE(b)), m_range(SCN_MOVE(r))
+ {
+ }
+
+ /// Beginning of the leftover range
+ iterator begin() const noexcept
+ {
+ return m_range.begin();
+ }
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wnoexcept")
+ // Mitigate problem where Doxygen would think that SCN_GCC_PUSH was
+ // a part of the definition of end()
+ public:
+ /// End of the leftover range
+ sentinel end() const
+ noexcept(noexcept(SCN_DECLVAL(wrapped_range_type).end()))
+ {
+ return m_range.end();
+ }
+
+ /// Whether the leftover range is empty
+ bool empty() const
+ noexcept(noexcept(SCN_DECLVAL(wrapped_range_type).end()))
+ {
+ return begin() == end();
+ }
+ SCN_GCC_POP
+ // See above at SCN_GCC_PUSH
+ public:
+ /// A subrange pointing to the leftover range
+ ranges::subrange<iterator, sentinel> subrange() const
+ {
+ return {begin(), end()};
+ }
+
+ /**
+ * Leftover range.
+ * If the leftover range is used to scan a new value, this member
+ * function should be used.
+ *
+ * \see range_wrapper
+ */
+ wrapped_range_type& range() &
+ {
+ return m_range;
+ }
+ /// \copydoc range()
+ const wrapped_range_type& range() const&
+ {
+ return m_range;
+ }
+ /// \copydoc range()
+ wrapped_range_type range() &&
+ {
+ return SCN_MOVE(m_range);
+ }
+
+ /**
+ * \defgroup range_as_range Contiguous leftover range convertors
+ *
+ * These member functions enable more convenient use of the
+ * leftover range for non-scnlib use cases. The range must be
+ * contiguous. The leftover range is not advanced, and can still be
+ * used.
+ *
+ * @{
+ */
+
+ /**
+ * \ingroup range_as_range
+ * Return a view into the leftover range as a \c string_view.
+ * Operations done to the leftover range after a call to this may
+ * cause issues with iterator invalidation. The returned range will
+ * reference to the leftover range, so be wary of
+ * use-after-free-problems.
+ */
+ template <
+ typename R = wrapped_range_type,
+ typename = typename std::enable_if<R::is_contiguous>::type>
+ basic_string_view<char_type> range_as_string_view() const
+ {
+ return {m_range.data(),
+ static_cast<std::size_t>(m_range.size())};
+ }
+ /**
+ * \ingroup range_as_range
+ * Return a view into the leftover range as a \c span.
+ * Operations done to the leftover range after a call to this may
+ * cause issues with iterator invalidation. The returned range will
+ * reference to the leftover range, so be wary of
+ * use-after-free-problems.
+ */
+ template <
+ typename R = wrapped_range_type,
+ typename = typename std::enable_if<R::is_contiguous>::type>
+ span<const char_type> range_as_span() const
+ {
+ return {m_range.data(),
+ static_cast<std::size_t>(m_range.size())};
+ }
+ /**
+ * \ingroup range_as_range
+ * Return the leftover range as a string. The contents are copied
+ * into the string, so using this will not lead to lifetime issues.
+ */
+ template <
+ typename R = wrapped_range_type,
+ typename = typename std::enable_if<R::is_contiguous>::type>
+ std::basic_string<char_type> range_as_string() const
+ {
+ return {m_range.data(),
+ static_cast<std::size_t>(m_range.size())};
+ }
+ /// @}
+
+ protected:
+ wrapped_range_type m_range;
+
+ private:
+ /// \publicsection
+
+ /**
+ * Reconstructs a range of the original type, independent of the
+ * leftover range, beginning from \ref begin and ending in \ref end.
+ *
+ * Compiles only if range is reconstructible.
+ */
+ template <typename R = typename WrappedRange::range_type>
+ R reconstruct() const;
+ };
+
+ template <typename WrappedRange, typename Base>
+ class intermediary_scan_result
+ : public scan_result_base<WrappedRange, Base> {
+ public:
+ using base_type = scan_result_base<WrappedRange, Base>;
+
+ intermediary_scan_result(Base&& b, WrappedRange&& r)
+ : base_type(SCN_MOVE(b), SCN_MOVE(r))
+ {
+ }
+
+ template <typename R = WrappedRange>
+ void reconstruct() const
+ {
+ static_assert(
+ dependent_false<R>::value,
+ "Cannot call .reconstruct() on intermediary_scan_result. "
+ "Assign this value to a previous result value returned by "
+ "a scanning function or make_result (type: "
+ "reconstructed_scan_result or "
+ "non_reconstructed_scan_result) ");
+ }
+ };
+ template <typename WrappedRange, typename Base>
+ class reconstructed_scan_result
+ : public intermediary_scan_result<WrappedRange, Base> {
+ public:
+ using unwrapped_range_type = typename WrappedRange::range_type;
+ using base_type = intermediary_scan_result<WrappedRange, Base>;
+
+ reconstructed_scan_result(Base&& b, WrappedRange&& r)
+ : base_type(SCN_MOVE(b), SCN_MOVE(r))
+ {
+ }
+
+ reconstructed_scan_result& operator=(
+ const intermediary_scan_result<WrappedRange, Base>& other)
+ {
+ this->set_base(other);
+ this->m_range = other.range();
+ return *this;
+ }
+ reconstructed_scan_result& operator=(
+ intermediary_scan_result<WrappedRange, Base>&& other)
+ {
+ this->set_base(other);
+ this->m_range = other.range();
+ return *this;
+ }
+
+ unwrapped_range_type reconstruct() const
+ {
+ return this->range().range_underlying();
+ }
+ };
+ template <typename WrappedRange, typename UnwrappedRange, typename Base>
+ class non_reconstructed_scan_result
+ : public intermediary_scan_result<WrappedRange, Base> {
+ public:
+ using unwrapped_range_type = UnwrappedRange;
+ using base_type = intermediary_scan_result<WrappedRange, Base>;
+
+ non_reconstructed_scan_result(Base&& b, WrappedRange&& r)
+ : base_type(SCN_MOVE(b), SCN_MOVE(r))
+ {
+ }
+
+ non_reconstructed_scan_result& operator=(
+ const intermediary_scan_result<WrappedRange, Base>& other)
+ {
+ this->set_base(other);
+ this->m_range = other.range();
+ return *this;
+ }
+ non_reconstructed_scan_result& operator=(
+ intermediary_scan_result<WrappedRange, Base>&& other)
+ {
+ this->set_base(other);
+ this->m_range = other.range();
+ return *this;
+ }
+
+ template <typename R = unwrapped_range_type>
+ R reconstruct() const
+ {
+ return ::scn::detail::reconstruct(reconstruct_tag<R>{},
+ this->begin(), this->end());
+ }
+ };
+
+ /// @}
+
+ // -Wdocumentation-unknown-command
+ SCN_CLANG_PUSH
+
+ template <typename T>
+ struct range_tag {
+ };
+
+ namespace _wrap_result {
+ struct fn {
+ private:
+ // Range = range_wrapper<ref>&
+ template <typename Error, typename Range>
+ static auto impl(Error e,
+ range_tag<range_wrapper<Range&>&>,
+ range_wrapper<Range&>&& range,
+ priority_tag<5>) noexcept
+ -> intermediary_scan_result<range_wrapper<Range&>, Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)};
+ }
+ // Range = const range_wrapper<ref>&
+ template <typename Error, typename Range>
+ static auto impl(Error e,
+ range_tag<const range_wrapper<Range&>&>,
+ range_wrapper<Range&>&& range,
+ priority_tag<5>) noexcept
+ -> intermediary_scan_result<range_wrapper<Range&>, Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)};
+ }
+ // Range = range_wrapper<ref>&&
+ template <typename Error, typename Range>
+ static auto impl(Error e,
+ range_tag<range_wrapper<Range&>>,
+ range_wrapper<Range&>&& range,
+ priority_tag<5>) noexcept
+ -> intermediary_scan_result<range_wrapper<Range&>, Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)};
+ }
+
+ // Range = range_wrapper<non-ref>&
+ template <typename Error, typename Range>
+ static auto impl(Error e,
+ range_tag<range_wrapper<Range>&>,
+ range_wrapper<Range>&& range,
+ priority_tag<4>) noexcept
+ -> intermediary_scan_result<range_wrapper<Range>, Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)};
+ }
+ // Range = const range_wrapper<non-ref>&
+ template <typename Error, typename Range>
+ static auto impl(Error e,
+ range_tag<const range_wrapper<Range>&>,
+ range_wrapper<Range>&& range,
+ priority_tag<4>) noexcept
+ -> intermediary_scan_result<range_wrapper<Range>, Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)};
+ }
+ // Range = range_wrapper<non-ref>&&
+ template <typename Error, typename Range>
+ static auto impl(Error e,
+ range_tag<range_wrapper<Range>>,
+ range_wrapper<Range>&& range,
+ priority_tag<4>) noexcept
+ -> intermediary_scan_result<range_wrapper<Range>, Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)};
+ }
+
+ // string literals are wonky
+ template <typename Error,
+ typename CharT,
+ size_t N,
+ typename NoCVRef = remove_cvref_t<CharT>>
+ static auto impl(
+ Error e,
+ range_tag<CharT (&)[N]>,
+ range_wrapper<basic_string_view<NoCVRef>>&& range,
+ priority_tag<3>) noexcept
+ -> reconstructed_scan_result<
+ range_wrapper<basic_string_view<NoCVRef>>,
+ Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)
+ .template reconstruct_and_rewrap<
+ basic_string_view<NoCVRef>>()};
+ }
+
+ // (const) InputRange&: View + Reconstructible
+ // wrapped<any>
+ template <typename Error,
+ typename InputRange,
+ typename InnerWrappedRange,
+ typename InputRangeNoConst =
+ typename std::remove_const<InputRange>::type,
+ typename = typename std::enable_if<SCN_CHECK_CONCEPT(
+ ranges::view<InputRangeNoConst>)>::type>
+ static auto impl(Error e,
+ range_tag<InputRange&>,
+ range_wrapper<InnerWrappedRange>&& range,
+ priority_tag<2>) noexcept
+ -> reconstructed_scan_result<
+ decltype(SCN_MOVE(range)
+ .template reconstruct_and_rewrap<
+ InputRangeNoConst>()),
+ Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)
+ .template reconstruct_and_rewrap<
+ InputRangeNoConst>()};
+ }
+
+ // (const) InputRange&: other
+ // wrapped<any>
+ template <typename Error,
+ typename InputRange,
+ typename InnerWrappedRange>
+ static auto impl(Error e,
+ range_tag<InputRange&>,
+ range_wrapper<InnerWrappedRange>&& range,
+ priority_tag<1>) noexcept
+ -> non_reconstructed_scan_result<
+ range_wrapper<InnerWrappedRange>,
+ typename std::remove_const<InputRange>::type,
+ Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)};
+ }
+
+ // InputRange&&: View + Reconstructible
+ // wrapped<non-ref>
+ template <typename Error,
+ typename InputRange,
+ typename InnerWrappedRange,
+ typename InputRangeNoConst =
+ typename std::remove_const<InputRange>::type,
+ typename = typename std::enable_if<SCN_CHECK_CONCEPT(
+ ranges::view<InputRangeNoConst>)>::type>
+ static auto impl(Error e,
+ range_tag<InputRange>,
+ range_wrapper<InnerWrappedRange>&& range,
+ priority_tag<1>) noexcept
+ -> reconstructed_scan_result<
+ decltype(SCN_MOVE(range)
+ .template reconstruct_and_rewrap<
+ InputRangeNoConst>()),
+ Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)
+ .template reconstruct_and_rewrap<
+ InputRangeNoConst>()};
+ }
+
+ // InputRange&&: other
+ // wrapped<non-ref>
+ template <typename Error,
+ typename InputRange,
+ typename InnerWrappedRange>
+ static auto impl(Error e,
+ range_tag<InputRange>,
+ range_wrapper<InnerWrappedRange>&& range,
+ priority_tag<0>) noexcept
+ -> non_reconstructed_scan_result<
+ range_wrapper<InputRange>,
+ typename std::remove_const<InputRange>::type,
+ Error>
+ {
+ return {SCN_MOVE(e), SCN_MOVE(range)};
+ }
+
+#if 0
+ // InputRange&&
+ // wrapped<ref>
+ template <typename Error,
+ typename InputRange,
+ typename InnerWrappedRange,
+ typename NoRef = typename std::remove_reference<
+ InnerWrappedRange>::type>
+ static auto impl(Error e,
+ range_tag<InputRange>,
+ range_wrapper<InnerWrappedRange&>&& range,
+ priority_tag<0>) noexcept
+ -> reconstructed_scan_result<range_wrapper<NoRef>, Error>
+ {
+ return {SCN_MOVE(e),
+ SCN_MOVE(range)
+ .template rewrap_and_reconstruct<NoRef>()};
+ }
+#endif
+
+ public:
+ template <typename Error,
+ typename InputRange,
+ typename InnerWrappedRange>
+ auto operator()(Error e,
+ range_tag<InputRange> tag,
+ range_wrapper<InnerWrappedRange>&& range) const
+ noexcept(noexcept(impl(SCN_MOVE(e),
+ tag,
+ SCN_MOVE(range),
+ priority_tag<5>{})))
+ -> decltype(impl(SCN_MOVE(e),
+ tag,
+ SCN_MOVE(range),
+ priority_tag<5>{}))
+ {
+ static_assert(SCN_CHECK_CONCEPT(ranges::range<InputRange>),
+ "Input needs to be a Range");
+ return impl(SCN_MOVE(e), tag, SCN_MOVE(range),
+ priority_tag<5>{});
+ }
+ };
+ } // namespace _wrap_result
+ namespace {
+ static constexpr auto& wrap_result =
+ static_const<_wrap_result::fn>::value;
+ }
+
+ template <typename Error, typename InputRange, typename WrappedRange>
+ struct result_type_for {
+ using type =
+ decltype(wrap_result(SCN_DECLVAL(Error &&),
+ SCN_DECLVAL(range_tag<InputRange>),
+ SCN_DECLVAL(WrappedRange&&)));
+ };
+ template <typename Error, typename InputRange, typename WrappedRange>
+ using result_type_for_t =
+ typename result_type_for<Error, InputRange, WrappedRange>::type;
+ } // namespace detail
+
+ /**
+ * Create a result object for range \c Range.
+ * Useful if one wishes to scan from the same range in a loop.
+ *
+ * \code{.cpp}
+ * auto source = ...;
+ * auto result = make_result(source);
+ * // scan until failure (no more `int`s, or EOF)
+ * while (result) {
+ * int i;
+ * result = scn::scan(result.range(), "{}", i);
+ * // use i
+ * }
+ * // see result for why we exited the loop
+ * \endcode
+ *
+ * \c Error template parameter can be used to customize the error type for
+ * the result object. By default, it's \ref wrapped_error, which is what
+ * most of the scanning functions use. For \c scan_value, use \c
+ * expected<T>:
+ *
+ * \code{.cpp}
+ * auto result = make_result<scn::expected<int>>(source);
+ * while (result) {
+ * result = scn::scan_value<int>(result.range(), "{}");
+ * // use result.value()
+ * }
+ * \endcode
+ */
+ template <typename Error = wrapped_error, typename Range>
+ auto make_result(Range&& r)
+ -> detail::result_type_for_t<Error, Range, range_wrapper_for_t<Range>>
+ {
+ return detail::wrap_result(Error{}, detail::range_tag<Range>{},
+ wrap(r));
+ }
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_DETAIL_RESULT_H
diff --git a/src/third-party/scnlib/include/scn/detail/vectored.h b/src/third-party/scnlib/include/scn/detail/vectored.h
new file mode 100644
index 0000000..f5c3868
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/vectored.h
@@ -0,0 +1,166 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_VECTORED_H
+#define SCN_DETAIL_VECTORED_H
+
+#include "../ranges/util.h"
+#include "../util/math.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ namespace _get_buffer {
+ struct fn {
+ private:
+ template <typename It>
+ static It _get_end(It begin, It end, size_t max_size)
+ {
+ return begin +
+ min(max_size, static_cast<std::size_t>(
+ ranges::distance(begin, end)));
+ }
+
+ template <typename CharT>
+ static SCN_CONSTEXPR14
+ span<typename std::add_const<CharT>::type>
+ impl(span<span<CharT>> s,
+ typename span<CharT>::iterator begin,
+ size_t max_size,
+ priority_tag<3>) noexcept
+ {
+ auto buf_it = s.begin();
+ for (; buf_it != s.end(); ++buf_it) {
+ if (begin >= buf_it->begin() && begin < buf_it->end()) {
+ break;
+ }
+ if (begin == buf_it->end()) {
+ ++buf_it;
+ begin = buf_it->begin();
+ break;
+ }
+ }
+ if (buf_it == s.end()) {
+ return {};
+ }
+ return {begin, _get_end(begin, buf_it->end(), max_size)};
+ }
+
+ template <
+ typename Range,
+ typename std::enable_if<SCN_CHECK_CONCEPT(
+ ranges::contiguous_range<Range>)>::type* = nullptr>
+ static SCN_CONSTEXPR14 span<typename std::add_const<
+ ranges::range_value_t<const Range>>::type>
+ impl(const Range& s,
+ ranges::iterator_t<const Range> begin,
+ size_t max_size,
+ priority_tag<2>) noexcept
+ {
+ auto b = ranges::begin(s);
+ auto e = ranges::end(s);
+ return {to_address(begin),
+ _get_end(to_address(begin),
+ to_address_safe(e, b, e), max_size)};
+ }
+
+ template <typename Range, typename It>
+ static auto impl(
+ const Range& r,
+ It begin,
+ size_t max_size,
+ priority_tag<1>) noexcept(noexcept(r.get_buffer(begin,
+ max_size)))
+ -> decltype(r.get_buffer(begin, max_size))
+ {
+ return r.get_buffer(begin, max_size);
+ }
+
+ template <typename Range, typename It>
+ static auto impl(
+ const Range& r,
+ It begin,
+ size_t max_size,
+ priority_tag<0>) noexcept(noexcept(get_buffer(r,
+ begin,
+ max_size)))
+ -> decltype(get_buffer(r, begin, max_size))
+ {
+ return get_buffer(r, begin, max_size);
+ }
+
+ public:
+ template <typename Range, typename It>
+ SCN_CONSTEXPR14 auto operator()(const Range& r,
+ It begin,
+ size_t max_size) const
+ noexcept(noexcept(
+ fn::impl(r, begin, max_size, priority_tag<3>{})))
+ -> decltype(fn::impl(r,
+ begin,
+ max_size,
+ priority_tag<3>{}))
+ {
+ return fn::impl(r, begin, max_size, priority_tag<3>{});
+ }
+
+ template <typename Range, typename It>
+ SCN_CONSTEXPR14 auto operator()(const Range& r, It begin) const
+ noexcept(
+ noexcept(fn::impl(r,
+ begin,
+ std::numeric_limits<size_t>::max(),
+ priority_tag<3>{})))
+ -> decltype(fn::impl(r,
+ begin,
+ std::numeric_limits<size_t>::max(),
+ priority_tag<3>{}))
+ {
+ return fn::impl(r, begin,
+ std::numeric_limits<size_t>::max(),
+ priority_tag<3>{});
+ }
+ };
+ } // namespace _get_buffer
+
+ namespace {
+ static constexpr auto& get_buffer =
+ detail::static_const<_get_buffer::fn>::value;
+ } // namespace
+
+ struct provides_buffer_access_concept {
+ template <typename Range, typename Iterator>
+ auto _test_requires(const Range& r, Iterator begin)
+ -> decltype(scn::detail::valid_expr(
+ ::scn::detail::get_buffer(r, begin)));
+ };
+ template <typename Range, typename = void>
+ struct provides_buffer_access_impl
+ : std::integral_constant<
+ bool,
+ ::scn::custom_ranges::detail::_requires<
+ provides_buffer_access_concept,
+ Range,
+ ::scn::ranges::iterator_t<const Range>>::value> {
+ };
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/detail/visitor.h b/src/third-party/scnlib/include/scn/detail/visitor.h
new file mode 100644
index 0000000..e88e2f7
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/detail/visitor.h
@@ -0,0 +1,248 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_DETAIL_VISITOR_H
+#define SCN_DETAIL_VISITOR_H
+
+#include "../reader/reader.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ template <typename Context, typename ParseCtx>
+ class basic_visitor {
+ public:
+ using context_type = Context;
+ using char_type = typename Context::char_type;
+ using arg_type = basic_arg<char_type>;
+
+ basic_visitor(Context& ctx, ParseCtx& pctx)
+ : m_ctx(std::addressof(ctx)), m_pctx(std::addressof(pctx))
+ {
+ }
+
+ template <typename T>
+ auto operator()(T&& val) -> error
+ {
+ return visit(SCN_FWD(val), detail::priority_tag<1>{});
+ }
+
+ private:
+ auto visit(code_point& val, detail::priority_tag<1>) -> error
+ {
+ return detail::visitor_boilerplate<detail::code_point_scanner>(
+ val, *m_ctx, *m_pctx);
+ }
+ auto visit(span<char_type>& val, detail::priority_tag<1>) -> error
+ {
+ return detail::visitor_boilerplate<detail::span_scanner>(
+ val, *m_ctx, *m_pctx);
+ }
+ auto visit(bool& val, detail::priority_tag<1>) -> error
+ {
+ return detail::visitor_boilerplate<detail::bool_scanner>(
+ val, *m_ctx, *m_pctx);
+ }
+
+#define SCN_VISIT_INT(T) \
+ error visit(T& val, detail::priority_tag<0>) \
+ { \
+ return detail::visitor_boilerplate<detail::integer_scanner<T>>( \
+ val, *m_ctx, *m_pctx); \
+ }
+ SCN_VISIT_INT(signed char)
+ SCN_VISIT_INT(short)
+ SCN_VISIT_INT(int)
+ SCN_VISIT_INT(long)
+ SCN_VISIT_INT(long long)
+ SCN_VISIT_INT(unsigned char)
+ SCN_VISIT_INT(unsigned short)
+ SCN_VISIT_INT(unsigned int)
+ SCN_VISIT_INT(unsigned long)
+ SCN_VISIT_INT(unsigned long long)
+ SCN_VISIT_INT(char_type)
+#undef SCN_VISIT_INT
+
+#define SCN_VISIT_FLOAT(T) \
+ error visit(T& val, detail::priority_tag<1>) \
+ { \
+ return detail::visitor_boilerplate<detail::float_scanner<T>>( \
+ val, *m_ctx, *m_pctx); \
+ }
+ SCN_VISIT_FLOAT(float)
+ SCN_VISIT_FLOAT(double)
+ SCN_VISIT_FLOAT(long double)
+#undef SCN_VISIT_FLOAT
+
+ auto visit(std::basic_string<char_type>& val, detail::priority_tag<1>)
+ -> error
+ {
+ return detail::visitor_boilerplate<detail::string_scanner>(
+ val, *m_ctx, *m_pctx);
+ }
+ auto visit(basic_string_view<char_type>& val, detail::priority_tag<1>)
+ -> error
+ {
+ return detail::visitor_boilerplate<detail::string_view_scanner>(
+ val, *m_ctx, *m_pctx);
+ }
+ auto visit(typename arg_type::handle val, detail::priority_tag<1>)
+ -> error
+ {
+ return val.scan(*m_ctx, *m_pctx);
+ }
+ [[noreturn]] auto visit(detail::monostate, detail::priority_tag<0>)
+ -> error
+ {
+ SCN_UNREACHABLE;
+ }
+
+ Context* m_ctx;
+ ParseCtx* m_pctx;
+ };
+
+ template <typename Context, typename ParseCtx>
+ error visit(Context& ctx,
+ ParseCtx& pctx,
+ basic_args<typename Context::char_type> args)
+ {
+ using char_type = typename Context::char_type;
+ using arg_type = basic_arg<char_type>;
+ auto arg = arg_type{};
+
+ while (pctx) {
+ if (pctx.should_skip_ws()) {
+ // Skip whitespace from format string and from stream
+ // EOF is not an error
+ auto ret = skip_range_whitespace(ctx, false);
+ if (SCN_UNLIKELY(!ret)) {
+ if (ret == error::end_of_range) {
+ break;
+ }
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ auto rb = ctx.range().reset_to_rollback_point();
+ if (!rb) {
+ return rb;
+ }
+ return ret;
+ }
+ // Don't advance pctx, should_skip_ws() does it for us
+ continue;
+ }
+
+ // Non-brace character, or
+ // Brace followed by another brace, meaning a literal '{'
+ if (pctx.should_read_literal()) {
+ if (SCN_UNLIKELY(!pctx)) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string"};
+ }
+ // Check for any non-specifier {foo} characters
+ alignas(typename Context::char_type) unsigned char buf[4] = {0};
+ auto ret = read_code_point(ctx.range(), make_span(buf, 4));
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ if (!ret || !pctx.check_literal(ret.value().chars)) {
+ auto rb = ctx.range().reset_to_rollback_point();
+ if (!rb) {
+ // Failed rollback
+ return rb;
+ }
+ if (!ret) {
+ // Failed read
+ return ret.error();
+ }
+
+ // Mismatching characters in scan string and stream
+ return {error::invalid_scanned_value,
+ "Expected character from format string not "
+ "found in the stream"};
+ }
+ // Bump pctx to next char
+ if (!pctx.advance_cp()) {
+ pctx.advance_char();
+ }
+ }
+ else {
+ // Scan argument
+ auto arg_wrapped = [&]() -> expected<arg_type> {
+ if (!pctx.has_arg_id()) {
+ return next_arg(args, pctx);
+ }
+ auto id_wrapped = pctx.parse_arg_id();
+ if (!id_wrapped) {
+ return id_wrapped.error();
+ }
+ auto id = id_wrapped.value();
+ SCN_ENSURE(!id.empty());
+ if (ctx.locale().get_static().is_digit(id.front())) {
+ auto s =
+ detail::simple_integer_scanner<std::ptrdiff_t>{};
+ std::ptrdiff_t i{0};
+ auto span = make_span(id.data(), id.size());
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ auto ret = s.scan(span, i, 10);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ if (!ret || ret.value() != span.end()) {
+ return error(error::invalid_format_string,
+ "Failed to parse argument id from "
+ "format string");
+ }
+ return get_arg(args, pctx, i);
+ }
+ return get_arg(args, pctx, id);
+ }();
+ if (!arg_wrapped) {
+ return arg_wrapped.error();
+ }
+ arg = arg_wrapped.value();
+ SCN_ENSURE(arg);
+ if (!pctx) {
+ return {error::invalid_format_string,
+ "Unexpected end of format argument"};
+ }
+ auto ret = visit_arg<char_type>(
+ basic_visitor<Context, ParseCtx>(ctx, pctx), arg);
+ if (!ret) {
+ auto rb = ctx.range().reset_to_rollback_point();
+ if (!rb) {
+ return rb;
+ }
+ return ret;
+ }
+ // Handle next arg and bump pctx
+ pctx.arg_handled();
+ if (pctx) {
+ auto e = pctx.advance_cp();
+ if (!e) {
+ return e;
+ }
+ }
+ }
+ }
+ if (pctx) {
+ // Format string not exhausted
+ return {error::invalid_format_string,
+ "Format string not exhausted"};
+ }
+ ctx.range().set_rollback_point();
+ return {};
+ }
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_DETAIL_VISITOR_H
diff --git a/src/third-party/scnlib/include/scn/fwd.h b/src/third-party/scnlib/include/scn/fwd.h
new file mode 100644
index 0000000..e91a258
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/fwd.h
@@ -0,0 +1,23 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_FWD_H
+#define SCN_FWD_H
+
+#include "detail/fwd.h"
+
+#endif // SCN_FWD_H
diff --git a/src/third-party/scnlib/include/scn/istream.h b/src/third-party/scnlib/include/scn/istream.h
new file mode 100644
index 0000000..acb2774
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/istream.h
@@ -0,0 +1,23 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_ISTREAM_H
+#define SCN_ISTREAM_H
+
+#include "scan/istream.h"
+
+#endif // SCN_ISTREAM_H
diff --git a/src/third-party/scnlib/include/scn/ranges/custom_impl.h b/src/third-party/scnlib/include/scn/ranges/custom_impl.h
new file mode 100644
index 0000000..86d73d5
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/ranges/custom_impl.h
@@ -0,0 +1,1632 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+//
+// The contents of this file are adapted from NanoRange.
+// https://github.com/tcbrindle/NanoRange
+// Copyright (c) 2018 Tristan Brindle
+// Distributed under the Boost Software License, Version 1.0
+
+#ifndef SCN_RANGES_CUSTOM_IMPL_H
+#define SCN_RANGES_CUSTOM_IMPL_H
+
+#include "util.h"
+
+#include "../util/string_view.h"
+
+#include <iterator>
+#include <utility>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace custom_ranges {
+ // iterator_category is span.h
+
+ template <typename T>
+ struct iterator_category;
+
+ namespace detail {
+ template <typename T, typename = void>
+ struct iterator_category {
+ };
+ template <typename T>
+ struct iterator_category<T*>
+ : std::enable_if<std::is_object<T>::value,
+ contiguous_iterator_tag> {
+ };
+ template <typename T>
+ struct iterator_category<const T> : iterator_category<T> {
+ };
+ template <typename T>
+ struct iterator_category<
+ T,
+ detail::void_t<typename T::iterator_category>> {
+ using type = typename T::iterator_category;
+ };
+ } // namespace detail
+
+ template <typename T>
+ struct iterator_category : detail::iterator_category<T> {
+ };
+ template <typename T>
+ using iterator_category_t = typename iterator_category<T>::type;
+
+ template <typename T>
+ using iter_reference_t = decltype(*SCN_DECLVAL(T&));
+
+ // iter_difference_t
+ template <typename>
+ struct incrementable_traits;
+
+ namespace detail {
+ struct empty {
+ };
+
+ template <typename T>
+ struct with_difference_type {
+ using difference_type = T;
+ };
+ template <typename, typename = void>
+ struct incrementable_traits_helper {
+ };
+
+ template <>
+ struct incrementable_traits_helper<void*> {
+ };
+ template <typename T>
+ struct incrementable_traits_helper<T*>
+ : std::conditional<std::is_object<T>::value,
+ with_difference_type<std::ptrdiff_t>,
+ empty>::type {
+ };
+ template <typename I>
+ struct incrementable_traits_helper<const I>
+ : incrementable_traits<typename std::decay<I>::type> {
+ };
+
+ template <typename, typename = void>
+ struct has_member_difference_type : std::false_type {
+ };
+ template <typename T>
+ struct has_member_difference_type<
+ T,
+ detail::void_t<typename T::difference_type>> : std::true_type {
+ };
+
+ template <typename T>
+ struct incrementable_traits_helper<
+ T,
+ typename std::enable_if<
+ has_member_difference_type<T>::value>::type> {
+ using difference_type = typename T::difference_type;
+ };
+ template <typename T>
+ struct incrementable_traits_helper<
+ T,
+ typename std::enable_if<
+ !std::is_pointer<T>::value &&
+ !has_member_difference_type<T>::value &&
+ std::is_integral<decltype(SCN_DECLVAL(const T&) -
+ SCN_DECLVAL(const T&))>::value>::
+ type>
+ : with_difference_type<typename std::make_signed<
+ decltype(SCN_DECLVAL(T) - SCN_DECLVAL(T))>::type> {
+ };
+ } // namespace detail
+
+ template <typename T>
+ struct incrementable_traits : detail::incrementable_traits_helper<T> {
+ };
+
+ template <typename T>
+ using iter_difference_t =
+ typename incrementable_traits<T>::difference_type;
+
+ // iter_value_t
+ template <typename>
+ struct readable_traits;
+
+ namespace detail {
+ template <typename T>
+ struct with_value_type {
+ using value_type = T;
+ };
+ template <typename, typename = void>
+ struct readable_traits_helper {
+ };
+
+ template <typename T>
+ struct readable_traits_helper<T*>
+ : std::conditional<
+ std::is_object<T>::value,
+ with_value_type<typename std::remove_cv<T>::type>,
+ empty>::type {
+ };
+
+ template <typename I>
+ struct readable_traits_helper<
+ I,
+ typename std::enable_if<std::is_array<I>::value>::type>
+ : readable_traits<typename std::decay<I>::type> {
+ };
+
+ template <typename I>
+ struct readable_traits_helper<
+ const I,
+ typename std::enable_if<!std::is_array<I>::value>::type>
+ : readable_traits<typename std::decay<I>::type> {
+ };
+
+ template <typename T, typename V = typename T::value_type>
+ struct member_value_type
+ : std::conditional<std::is_object<V>::value,
+ with_value_type<V>,
+ empty>::type {
+ };
+
+ template <typename T, typename E = typename T::element_type>
+ struct _member_element_type
+ : std::conditional<
+ std::is_object<E>::value,
+ with_value_type<typename std::remove_cv<E>::type>,
+ empty>::type {
+ };
+
+ template <typename T>
+ using member_value_type_t = typename T::value_type;
+
+ template <typename T>
+ struct has_member_value_type : exists<member_value_type_t, T> {
+ };
+
+ template <typename T>
+ using member_element_type_t = typename T::element_type;
+
+ template <typename T>
+ struct has_member_element_type : exists<member_element_type_t, T> {
+ };
+
+ template <typename T>
+ struct readable_traits_helper<
+ T,
+ typename std::enable_if<
+ has_member_value_type<T>::value &&
+ !has_member_element_type<T>::value>::type>
+ : member_value_type<T> {
+ };
+
+ template <typename T>
+ struct readable_traits_helper<
+ T,
+ typename std::enable_if<has_member_element_type<T>::value &&
+ !has_member_value_type<T>::value>::type>
+ : _member_element_type<T> {
+ };
+
+ template <typename T>
+ struct readable_traits_helper<
+ T,
+ typename std::enable_if<
+ has_member_element_type<T>::value &&
+ has_member_value_type<T>::value>::type> {
+ };
+ } // namespace detail
+
+ template <typename T>
+ struct readable_traits : detail::readable_traits_helper<T> {
+ };
+
+ template <typename T>
+ using iter_value_t = typename readable_traits<T>::value_type;
+
+ // sentinel_for
+ namespace detail {
+ struct sentinel_for_concept {
+ template <typename S, typename I>
+ auto _test_requires(S s, I i)
+ -> decltype(scn::detail::valid_expr(*i, i == s, i != s));
+ };
+ } // namespace detail
+ template <typename S, typename I>
+ struct sentinel_for
+ : std::integral_constant<
+ bool,
+ std::is_default_constructible<S>::value &&
+ std::is_copy_constructible<S>::value &&
+ detail::_requires<detail::sentinel_for_concept, S, I>::
+ value> {
+ };
+
+ // sized_sentinel_for
+ namespace detail {
+ struct sized_sentinel_for_concept {
+ template <typename S, typename I>
+ auto _test_requires(const S& s, const I& i) -> decltype(
+ detail::requires_expr<
+ std::is_same<decltype(s - i),
+ iter_difference_t<I>>::value>{},
+ detail::requires_expr<
+ std::is_same<decltype(i - s),
+ iter_difference_t<I>>::value>{});
+ };
+ } // namespace detail
+ template <typename S, typename I>
+ struct sized_sentinel_for
+ : std::integral_constant<
+ bool,
+ detail::_requires<detail::sized_sentinel_for_concept, S, I>::
+ value &&
+ sentinel_for<S, I>::value> {
+ };
+ template <typename S>
+ struct sized_sentinel_for<S, void*> : std::false_type {
+ };
+ template <typename I>
+ struct sized_sentinel_for<void*, I> : std::false_type {
+ };
+ template <>
+ struct sized_sentinel_for<void*, void*> : std::false_type {
+ };
+
+ // begin
+ namespace _begin {
+ template <typename T>
+ void begin(T&&) = delete;
+ template <typename T>
+ void begin(std::initializer_list<T>&&) = delete;
+
+ struct fn {
+ private:
+ template <typename T, std::size_t N>
+ static SCN_CONSTEXPR14 void impl(T(&&)[N],
+ detail::priority_tag<3>) =
+ delete;
+
+ template <typename T, std::size_t N>
+ static SCN_CONSTEXPR14 auto impl(
+ T (&t)[N],
+ detail::priority_tag<3>) noexcept -> decltype((t) + 0)
+ {
+ return (t) + 0;
+ }
+
+ template <typename C>
+ static SCN_CONSTEXPR14 auto impl(
+ basic_string_view<C> sv,
+ detail::priority_tag<2>) noexcept -> decltype(sv.begin())
+ {
+ return sv.begin();
+ }
+
+ template <typename T>
+ static SCN_CONSTEXPR14 auto
+ impl(T& t, detail::priority_tag<1>) noexcept(noexcept(
+ ::scn::custom_ranges::detail::decay_copy(t.begin())))
+ -> decltype(::scn::custom_ranges::detail::decay_copy(
+ t.begin()))
+ {
+ return ::scn::custom_ranges::detail::decay_copy(t.begin());
+ }
+
+ template <typename T>
+ static SCN_CONSTEXPR14 auto
+ impl(T&& t, detail::priority_tag<0>) noexcept(
+ noexcept(::scn::custom_ranges::detail::decay_copy(
+ begin(SCN_FWD(t)))))
+ -> decltype(::scn::custom_ranges::detail::decay_copy(
+ begin(SCN_FWD(t))))
+ {
+ return ::scn::custom_ranges::detail::decay_copy(
+ begin(SCN_FWD(t)));
+ }
+
+ public:
+ template <typename T>
+ SCN_CONSTEXPR14 auto operator()(T&& t) const noexcept(
+ noexcept(fn::impl(SCN_FWD(t), detail::priority_tag<3>{})))
+ -> decltype(fn::impl(SCN_FWD(t), detail::priority_tag<3>{}))
+ {
+ return fn::impl(SCN_FWD(t), detail::priority_tag<3>{});
+ }
+ };
+ } // namespace _begin
+ namespace {
+ constexpr auto& begin = detail::static_const<_begin::fn>::value;
+ }
+
+ // end
+ namespace _end {
+ template <typename T>
+ void end(T&&) = delete;
+ template <typename T>
+ void end(std::initializer_list<T>&&) = delete;
+
+ struct fn {
+ private:
+ template <typename T, std::size_t N>
+ static constexpr void impl(T(&&)[N],
+ detail::priority_tag<2>) = delete;
+
+ template <typename T, std::size_t N>
+ static constexpr auto impl(T (&t)[N],
+ detail::priority_tag<2>) noexcept
+ -> decltype((t) + N)
+ {
+ return (t) + N;
+ }
+
+ template <typename C>
+ static constexpr auto impl(basic_string_view<C> sv,
+ detail::priority_tag<2>) noexcept
+ -> decltype(sv.end())
+ {
+ return sv.end();
+ }
+
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wnoexcept")
+ template <typename T,
+ typename S =
+ decltype(::scn::custom_ranges::detail::decay_copy(
+ SCN_DECLVAL(T&).end())),
+ typename I = decltype(::scn::custom_ranges::begin(
+ SCN_DECLVAL(T&)))>
+ static constexpr auto
+ impl(T& t, detail::priority_tag<1>) noexcept(
+ noexcept(::scn::custom_ranges::detail::decay_copy(t.end())))
+ -> decltype(::scn::custom_ranges::detail::decay_copy(
+ t.end()))
+ {
+ return ::scn::custom_ranges::detail::decay_copy(t.end());
+ }
+
+ template <typename T,
+ typename S =
+ decltype(::scn::custom_ranges::detail::decay_copy(
+ end(SCN_DECLVAL(T)))),
+ typename I = decltype(::scn::custom_ranges::begin(
+ SCN_DECLVAL(T)))>
+ static constexpr auto
+ impl(T& t, detail::priority_tag<0>) noexcept(noexcept(
+ ::scn::custom_ranges::detail::decay_copy(end(SCN_FWD(t)))))
+ -> S
+ {
+ return ::scn::custom_ranges::detail::decay_copy(
+ end(SCN_FWD(t)));
+ }
+
+ public:
+ template <typename T>
+ constexpr auto operator()(T&& t) const noexcept(
+ noexcept(fn::impl(SCN_FWD(t), detail::priority_tag<2>{})))
+ -> decltype(fn::impl(SCN_FWD(t), detail::priority_tag<2>{}))
+ {
+ return fn::impl(SCN_FWD(t), detail::priority_tag<2>{});
+ }
+ SCN_GCC_POP
+ };
+ } // namespace _end
+ namespace {
+ constexpr auto& end = detail::static_const<_end::fn>::value;
+ }
+
+ // cbegin
+ namespace _cbegin {
+ struct fn {
+ template <typename T>
+ constexpr auto operator()(const T& t) const
+ noexcept(noexcept(::scn::custom_ranges::begin(t)))
+ -> decltype(::scn::custom_ranges::begin(t))
+ {
+ return ::scn::custom_ranges::begin(t);
+ }
+
+ template <typename T>
+ constexpr auto operator()(const T&& t) const noexcept(noexcept(
+ ::scn::custom_ranges::begin(static_cast<const T&&>(t))))
+ -> decltype(::scn::custom_ranges::begin(
+ static_cast<const T&&>(t)))
+ {
+ return ::scn::custom_ranges::begin(
+ static_cast<const T&&>(t));
+ }
+ };
+ } // namespace _cbegin
+ namespace {
+ constexpr auto& cbegin = detail::static_const<_cbegin::fn>::value;
+ }
+
+ // cend
+ namespace _cend {
+ struct fn {
+ template <typename T>
+ constexpr auto operator()(const T& t) const
+ noexcept(noexcept(::scn::custom_ranges::end(t)))
+ -> decltype(::scn::custom_ranges::end(t))
+ {
+ return ::scn::custom_ranges::end(t);
+ }
+
+ template <typename T>
+ constexpr auto operator()(const T&& t) const noexcept(noexcept(
+ ::scn::custom_ranges::end(static_cast<const T&&>(t))))
+ -> decltype(::scn::custom_ranges::end(
+ static_cast<const T&&>(t)))
+ {
+ return ::scn::custom_ranges::end(static_cast<const T&&>(t));
+ }
+ };
+ } // namespace _cend
+ namespace {
+ constexpr auto& cend = detail::static_const<_cend::fn>::value;
+ }
+
+ // range
+ namespace detail {
+ struct range_impl_concept {
+ template <typename T>
+ auto _test_requires(T&& t)
+ -> decltype(::scn::custom_ranges::begin(SCN_FWD(t)),
+ ::scn::custom_ranges::end(SCN_FWD(t)));
+ };
+ template <typename T>
+ struct range_impl : _requires<range_impl_concept, T> {
+ };
+ struct range_concept {
+ template <typename>
+ static auto test(long) -> std::false_type;
+ template <typename T>
+ static auto test(int) ->
+ typename std::enable_if<range_impl<T&>::value,
+ std::true_type>::type;
+ };
+ } // namespace detail
+ template <typename T>
+ struct range : decltype(detail::range_concept::test<T>(0)) {
+ };
+
+ template <typename T>
+ struct forwarding_range
+ : std::integral_constant<bool,
+ range<T>::value &&
+ detail::range_impl<T>::value> {
+ };
+
+ // typedefs
+ template <typename R>
+ using iterator_t =
+ typename std::enable_if<range<R>::value,
+ decltype(::scn::custom_ranges::begin(
+ SCN_DECLVAL(R&)))>::type;
+ template <typename R>
+ using sentinel_t =
+ typename std::enable_if<range<R>::value,
+ decltype(::scn::custom_ranges::end(
+ SCN_DECLVAL(R&)))>::type;
+ template <typename R>
+ using range_difference_t =
+ typename std::enable_if<range<R>::value,
+ iter_difference_t<iterator_t<R>>>::type;
+ template <typename R>
+ using range_value_t =
+ typename std::enable_if<range<R>::value,
+ iter_value_t<iterator_t<R>>>::type;
+ template <typename R>
+ using range_reference_t =
+ typename std::enable_if<range<R>::value,
+ iter_reference_t<iterator_t<R>>>::type;
+
+ // view
+ struct view_base {
+ };
+
+ namespace detail {
+ template <typename>
+ struct is_std_non_view : std::false_type {
+ };
+ template <typename T>
+ struct is_std_non_view<std::initializer_list<T>> : std::true_type {
+ };
+ template <typename T>
+ struct enable_view_helper
+ : std::conditional<
+ std::is_base_of<view_base, T>::value,
+ std::true_type,
+ typename std::conditional<
+ is_std_non_view<T>::value,
+ std::false_type,
+ typename std::conditional<
+ range<T>::value && range<const T>::value,
+ std::is_same<range_reference_t<T>,
+ range_reference_t<const T>>,
+ std::true_type>::type>::type>::type {
+ };
+ template <typename T>
+ struct view_impl
+ : std::integral_constant<
+ bool,
+ std::is_copy_constructible<T>::value &&
+ std::is_default_constructible<T>::value &&
+ detail::enable_view_helper<T>::value> {
+ };
+ } // namespace detail
+ template <typename T>
+ struct view : std::conditional<range<T>::value,
+ detail::view_impl<T>,
+ std::false_type>::type {
+ };
+
+ // data
+ template <typename P>
+ struct _is_object_pointer
+ : std::integral_constant<
+ bool,
+ std::is_pointer<P>::value &&
+ std::is_object<detail::test_t<iter_value_t, P>>::value> {
+ };
+
+ namespace _data {
+ struct fn {
+ private:
+ template <typename CharT, typename Traits, typename Allocator>
+ static constexpr auto impl(
+ std::basic_string<CharT, Traits, Allocator>& str,
+ detail::priority_tag<2>) noexcept -> typename std::
+ basic_string<CharT, Traits, Allocator>::pointer
+ {
+ return std::addressof(*str.begin());
+ }
+ template <typename CharT, typename Traits, typename Allocator>
+ static constexpr auto impl(
+ const std::basic_string<CharT, Traits, Allocator>& str,
+ detail::priority_tag<2>) noexcept -> typename std::
+ basic_string<CharT, Traits, Allocator>::const_pointer
+ {
+ return std::addressof(*str.begin());
+ }
+ template <typename CharT, typename Traits, typename Allocator>
+ static constexpr auto impl(
+ std::basic_string<CharT, Traits, Allocator>&& str,
+ detail::priority_tag<2>) noexcept -> typename std::
+ basic_string<CharT, Traits, Allocator>::pointer
+ {
+ return std::addressof(*str.begin());
+ }
+
+ template <typename T,
+ typename D =
+ decltype(::scn::custom_ranges::detail::decay_copy(
+ SCN_DECLVAL(T&).data()))>
+ static constexpr auto
+ impl(T& t, detail::priority_tag<1>) noexcept(noexcept(
+ ::scn::custom_ranges::detail::decay_copy(t.data()))) ->
+ typename std::enable_if<_is_object_pointer<D>::value,
+ D>::type
+ {
+ return ::scn::custom_ranges::detail::decay_copy(t.data());
+ }
+
+ template <typename T>
+ static constexpr auto
+ impl(T&& t, detail::priority_tag<0>) noexcept(
+ noexcept(::scn::custom_ranges::begin(SCN_FWD(t)))) ->
+ typename std::enable_if<
+ _is_object_pointer<decltype(::scn::custom_ranges::begin(
+ SCN_FWD(t)))>::value,
+ decltype(::scn::custom_ranges::begin(SCN_FWD(t)))>::type
+ {
+ return ::scn::custom_ranges::begin(SCN_FWD(t));
+ }
+
+ public:
+ template <typename T>
+ constexpr auto operator()(T&& t) const noexcept(
+ noexcept(fn::impl(SCN_FWD(t), detail::priority_tag<2>{})))
+ -> decltype(fn::impl(SCN_FWD(t), detail::priority_tag<2>{}))
+ {
+ return fn::impl(SCN_FWD(t), detail::priority_tag<2>{});
+ }
+ };
+ } // namespace _data
+ namespace {
+ constexpr auto& data = detail::static_const<_data::fn>::value;
+ }
+
+ // size
+ template <typename>
+ struct disable_sized_range : std::false_type {
+ };
+
+ namespace _size {
+ template <typename T>
+ void size(T&&) = delete;
+ template <typename T>
+ void size(T&) = delete;
+
+ struct fn {
+ private:
+ template <typename T, std::size_t N>
+ static constexpr std::size_t impl(
+ const T(&&)[N],
+ detail::priority_tag<3>) noexcept
+ {
+ return N;
+ }
+
+ template <typename T, std::size_t N>
+ static constexpr std::size_t impl(
+ const T (&)[N],
+ detail::priority_tag<3>) noexcept
+ {
+ return N;
+ }
+
+ template <typename T,
+ typename I =
+ decltype(::scn::custom_ranges::detail::decay_copy(
+ SCN_DECLVAL(T).size()))>
+ static constexpr auto
+ impl(T&& t, detail::priority_tag<2>) noexcept(
+ noexcept(::scn::custom_ranges::detail::decay_copy(
+ SCN_FWD(t).size()))) ->
+ typename std::enable_if<
+ std::is_integral<I>::value &&
+ !disable_sized_range<
+ detail::remove_cvref_t<T>>::value,
+ I>::type
+ {
+ return ::scn::custom_ranges::detail::decay_copy(
+ SCN_FWD(t).size());
+ }
+
+ template <typename T,
+ typename I =
+ decltype(::scn::custom_ranges::detail::decay_copy(
+ size(SCN_DECLVAL(T))))>
+ static constexpr auto
+ impl(T&& t, detail::priority_tag<1>) noexcept(noexcept(
+ ::scn::custom_ranges::detail::decay_copy(size(SCN_FWD(t)))))
+ -> typename std::enable_if<
+ std::is_integral<I>::value &&
+ !disable_sized_range<
+ detail::remove_cvref_t<T>>::value,
+ I>::type
+ {
+ return ::scn::custom_ranges::detail::decay_copy(
+ size(SCN_FWD(t)));
+ }
+
+ template <typename T,
+ typename I = decltype(::scn::custom_ranges::begin(
+ SCN_DECLVAL(T))),
+ typename S = decltype(::scn::custom_ranges::end(
+ SCN_DECLVAL(T))),
+ typename D =
+ decltype(::scn::custom_ranges::detail::decay_copy(
+ SCN_DECLVAL(S) - SCN_DECLVAL(I)))>
+ static constexpr auto
+ impl(T&& t, detail::priority_tag<0>) noexcept(
+ noexcept(::scn::custom_ranges::detail::decay_copy(
+ ::scn::custom_ranges::end(t) -
+ ::scn::custom_ranges::begin(t)))) ->
+ typename std::enable_if<
+ !std::is_array<detail::remove_cvref_t<T>>::value,
+ D>::type
+ {
+ return ::scn::custom_ranges::detail::decay_copy(
+ ::scn::custom_ranges::end(t) -
+ ::scn::custom_ranges::begin(t));
+ }
+
+ public:
+ template <typename T>
+ constexpr auto operator()(T&& t) const noexcept(
+ noexcept(fn::impl(SCN_FWD(t), detail::priority_tag<3>{})))
+ -> decltype(fn::impl(SCN_FWD(t), detail::priority_tag<3>{}))
+ {
+ return fn::impl(SCN_FWD(t), detail::priority_tag<3>{});
+ }
+ };
+ } // namespace _size
+ namespace {
+ constexpr auto& size = detail::static_const<_size::fn>::value;
+ }
+
+ // empty
+ namespace _empty_ns {
+ struct fn {
+ private:
+ template <typename T>
+ static constexpr auto
+ impl(T&& t, detail::priority_tag<2>) noexcept(
+ noexcept((bool(SCN_FWD(t).empty()))))
+ -> decltype((bool(SCN_FWD(t).empty())))
+ {
+ return bool((SCN_FWD(t).empty()));
+ }
+ template <typename T>
+ static constexpr auto
+ impl(T&& t, detail::priority_tag<1>) noexcept(
+ noexcept(::scn::custom_ranges::size(SCN_FWD(t)) == 0))
+ -> decltype(::scn::custom_ranges::size(SCN_FWD(t)) == 0)
+ {
+ return ::scn::custom_ranges::size(SCN_FWD(t)) == 0;
+ }
+
+ template <typename T,
+ typename I = decltype(::scn::custom_ranges::begin(
+ SCN_DECLVAL(T)))>
+ static constexpr auto
+ impl(T&& t, detail::priority_tag<0>) noexcept(
+ noexcept(::scn::custom_ranges::begin(t) ==
+ ::scn::custom_ranges::end(t)))
+ -> decltype(::scn::custom_ranges::begin(t) ==
+ ::scn::custom_ranges::end(t))
+ {
+ return ::scn::custom_ranges::begin(t) ==
+ ::scn::custom_ranges::end(t);
+ }
+
+ public:
+ template <typename T>
+ constexpr auto operator()(T&& t) const noexcept(
+ noexcept(fn::impl(SCN_FWD(t), detail::priority_tag<2>{})))
+ -> decltype(fn::impl(SCN_FWD(t), detail::priority_tag<2>{}))
+ {
+ return fn::impl(SCN_FWD(t), detail::priority_tag<2>{});
+ }
+ };
+ } // namespace _empty_ns
+ namespace {
+ constexpr auto& empty = detail::static_const<_empty_ns::fn>::value;
+ }
+
+ // sized_range
+ namespace detail {
+ struct sized_range_concept {
+ template <typename T>
+ auto _test_requires(T& t)
+ -> decltype(::scn::custom_ranges::size(t));
+ };
+ } // namespace detail
+ template <typename T>
+ struct sized_range
+ : std::integral_constant<
+ bool,
+ range<T>::value &&
+ !disable_sized_range<detail::remove_cvref_t<T>>::value &&
+ detail::_requires<detail::sized_range_concept,
+ T>::value> {
+ };
+
+ // contiguous_range
+ namespace detail {
+ struct contiguous_range_concept {
+ template <typename>
+ static auto test(long) -> std::false_type;
+ template <typename T>
+ static auto test(int) -> typename std::enable_if<
+ _requires<contiguous_range_concept, T>::value,
+ std::true_type>::type;
+
+ template <typename T>
+ auto _test_requires(T& t)
+ -> decltype(requires_expr<std::is_same<
+ decltype(::scn::custom_ranges::data(t)),
+ typename std::add_pointer<
+ range_reference_t<T>>::type>::value>{});
+ };
+ } // namespace detail
+ template <typename T>
+ struct contiguous_range
+ : decltype(detail::contiguous_range_concept::test<T>(0)) {
+ };
+
+ // subrange
+ template <typename D>
+ class view_interface : public view_base {
+ static_assert(std::is_class<D>::value, "");
+ static_assert(
+ std::is_same<D, typename std::remove_cv<D>::type>::value,
+ "");
+
+ private:
+ SCN_CONSTEXPR14 D& derived() noexcept
+ {
+ return static_cast<D&>(*this);
+ }
+ constexpr D& derived() const noexcept
+ {
+ return static_cast<const D&>(*this);
+ }
+
+ public:
+ SCN_NODISCARD SCN_CONSTEXPR14 bool empty()
+ {
+ return ::scn::custom_ranges::begin(derived()) ==
+ ::scn::custom_ranges::end(derived());
+ }
+ SCN_NODISCARD constexpr bool empty() const
+ {
+ return ::scn::custom_ranges::begin(derived()) ==
+ ::scn::custom_ranges::end(derived());
+ }
+
+ template <typename R = D,
+ typename = decltype(::scn::custom_ranges::empty(
+ SCN_DECLVAL(R&)))>
+ SCN_CONSTEXPR14 explicit operator bool()
+ {
+ return !::scn::custom_ranges::empty(derived());
+ }
+ template <typename R = D,
+ typename = decltype(::scn::custom_ranges::empty(
+ SCN_DECLVAL(const R&)))>
+ constexpr explicit operator bool() const
+ {
+ return !::scn::custom_ranges::empty(derived());
+ }
+
+ template <typename R = D,
+ typename std::enable_if<
+ contiguous_range<R>::value>::type* = nullptr>
+ auto data() -> decltype(std::addressof(
+ *::scn::custom_ranges::begin(static_cast<R&>(*this))))
+ {
+ return ::scn::custom_ranges::empty(derived())
+ ? nullptr
+ : std::addressof(
+ *::scn::custom_ranges::begin(derived()));
+ }
+ template <typename R = D,
+ typename std::enable_if<
+ contiguous_range<const R>::value>::type* = nullptr>
+ auto data() const -> decltype(std::addressof(
+ *::scn::custom_ranges::begin(static_cast<const R&>(*this))))
+ {
+ return ::scn::custom_ranges::empty(derived())
+ ? nullptr
+ : std::addressof(
+ *::scn::custom_ranges::begin(derived()));
+ }
+
+ template <typename R = D,
+ typename std::enable_if<
+ range<R>::value &&
+ sized_sentinel_for<sentinel_t<R>, iterator_t<R>>::
+ value>::type* = nullptr>
+ SCN_CONSTEXPR14 auto size()
+ -> decltype(::scn::custom_ranges::end(static_cast<R&>(*this)) -
+ ::scn::custom_ranges::begin(static_cast<R&>(*this)))
+ {
+ return ::scn::custom_ranges::end(derived()) -
+ ::scn::custom_ranges::begin(derived());
+ }
+
+ template <
+ typename R = D,
+ typename std::enable_if<
+ range<const R>::value &&
+ sized_sentinel_for<sentinel_t<const R>,
+ iterator_t<const R>>::value>::type* =
+ nullptr>
+ constexpr auto size() const
+ -> decltype(::scn::custom_ranges::end(
+ static_cast<const R&>(*this)) -
+ ::scn::custom_ranges::begin(
+ static_cast<const R&>(*this)))
+ {
+ return ::scn::custom_ranges::end(derived()) -
+ ::scn::custom_ranges::begin(derived());
+ }
+ };
+
+ enum class subrange_kind : bool { unsized, sized };
+
+ namespace detail {
+ template <typename I, typename S>
+ struct default_subrange_kind
+ : std::integral_constant<subrange_kind,
+ sized_sentinel_for<S, I>::value
+ ? subrange_kind::sized
+ : subrange_kind::unsized> {
+ };
+ } // namespace detail
+
+ namespace _subrange {
+ template <typename I,
+ typename S = I,
+ subrange_kind = scn::custom_ranges::detail::
+ default_subrange_kind<I, S>::value>
+ class subrange;
+ } // namespace _subrange
+
+ using _subrange::subrange;
+
+ namespace detail {
+ struct pair_like_concept {
+ template <typename>
+ static auto test(long) -> std::false_type;
+ template <typename T,
+ typename = typename std::tuple_size<T>::type>
+ static auto test(int) -> typename std::enable_if<
+ _requires<pair_like_concept, T>::value,
+ std::true_type>::type;
+
+ template <typename T>
+ auto _test_requires(T t) -> decltype(
+ requires_expr<
+ std::is_base_of<std::integral_constant<std::size_t, 2>,
+ std::tuple_size<T>>::value>{},
+ std::declval<std::tuple_element<
+ 0,
+ typename std::remove_const<T>::type>>(),
+ std::declval<std::tuple_element<
+ 1,
+ typename std::remove_const<T>::type>>(),
+ requires_expr<std::is_convertible<
+ decltype(std::get<0>(t)),
+ const std::tuple_element<0, T>&>::value>{},
+ requires_expr<std::is_convertible<
+ decltype(std::get<1>(t)),
+ const std::tuple_element<1, T>&>::value>{});
+ };
+ template <typename T>
+ struct pair_like
+ : std::integral_constant<
+ bool,
+ !std::is_reference<T>::value &&
+ decltype(pair_like_concept::test<T>(0))::value> {
+ };
+
+ struct pair_like_convertible_to_concept {
+ template <typename T, typename U, typename V>
+ auto _test_requires(T&& t) -> decltype(
+ requires_expr<
+ std::is_convertible<decltype(std::get<0>(SCN_FWD(t))),
+ U>::value>{},
+ requires_expr<
+ std::is_convertible<decltype(std::get<1>(SCN_FWD(t))),
+ V>::value>{});
+ };
+ template <typename T, typename U, typename V>
+ struct pair_like_convertible_to
+ : std::integral_constant<
+ bool,
+ !range<T>::value &&
+ pair_like<
+ typename std::remove_reference<T>::type>::value &&
+ _requires<pair_like_convertible_to_concept, T, U, V>::
+ value> {
+ };
+ template <typename T, typename U, typename V>
+ struct pair_like_convertible_from
+ : std::integral_constant<
+ bool,
+ !range<T>::value &&
+ pair_like<
+ typename std::remove_reference<T>::type>::value &&
+ std::is_constructible<T, U, V>::value> {
+ };
+
+ struct iterator_sentinel_pair_concept {
+ template <typename>
+ static auto test(long) -> std::false_type;
+ template <typename T>
+ static auto test(int) -> typename std::enable_if<
+ !range<T>::value && pair_like<T>::value &&
+ sentinel_for<
+ typename std::tuple_element<1, T>::type,
+ typename std::tuple_element<0, T>::type>::value,
+ std::true_type>::type;
+ };
+ template <typename T>
+ struct iterator_sentinel_pair
+ : decltype(iterator_sentinel_pair_concept::test<T>(0)) {
+ };
+
+ template <typename I, typename S, bool StoreSize = false>
+ struct subrange_data {
+ constexpr subrange_data() = default;
+ constexpr subrange_data(I&& b, S&& e)
+ : begin(SCN_MOVE(b)), end(SCN_MOVE(e))
+ {
+ }
+ template <bool Dependent = true>
+ constexpr subrange_data(
+ I&& b,
+ S&& e,
+ typename std::enable_if<Dependent,
+ iter_difference_t<I>>::type)
+ : begin(SCN_MOVE(b)), end(SCN_MOVE(e))
+ {
+ }
+
+ constexpr iter_difference_t<I> get_size() const
+ {
+ return distance(begin, end);
+ }
+
+ I begin{};
+ S end{};
+ };
+
+ template <typename I, typename S>
+ struct subrange_data<I, S, true> {
+ constexpr subrange_data() = default;
+ constexpr subrange_data(I&& b, S&& e, iter_difference_t<I> s)
+ : begin(SCN_MOVE(b)), end(SCN_MOVE(e)), size(s)
+ {
+ }
+
+ constexpr iter_difference_t<I> get_size() const
+ {
+ return size;
+ }
+
+ I begin{};
+ S end{};
+ iter_difference_t<I> size{0};
+ };
+
+ template <typename R, typename I, typename S, subrange_kind K>
+ auto subrange_range_constructor_constraint_helper_fn(long)
+ -> std::false_type;
+
+ template <typename R, typename I, typename S, subrange_kind K>
+ auto subrange_range_constructor_constraint_helper_fn(int) ->
+ typename std::enable_if<
+ forwarding_range<R>::value &&
+ std::is_convertible<iterator_t<R>, I>::value &&
+ std::is_convertible<sentinel_t<R>, S>::value,
+ std::true_type>::type;
+
+ template <typename R, typename I, typename S, subrange_kind K>
+ struct subrange_range_constructor_constraint_helper
+ : decltype(subrange_range_constructor_constraint_helper_fn<R,
+ I,
+ S,
+ K>(
+ 0)) {
+ };
+
+ template <typename R>
+ constexpr subrange_kind subrange_deduction_guide_helper()
+ {
+ return (sized_range<R>::value ||
+ sized_sentinel_for<sentinel_t<R>, iterator_t<R>>::value)
+ ? subrange_kind::sized
+ : subrange_kind::unsized;
+ }
+
+ template <typename T, typename U>
+ struct not_same_as : std::integral_constant<
+ bool,
+ !std::is_same<remove_cvref_t<T>,
+ remove_cvref_t<U>>::value> {
+ };
+ } // namespace detail
+
+ namespace _subrange {
+ template <typename I, typename S, subrange_kind K>
+ class subrange : public view_interface<subrange<I, S, K>> {
+ static_assert(sentinel_for<S, I>::value, "");
+ static_assert(K == subrange_kind::sized ||
+ !sized_sentinel_for<S, I>::value,
+ "");
+
+ static constexpr bool _store_size =
+ K == subrange_kind::sized &&
+ !sized_sentinel_for<S, I>::value;
+
+ public:
+ using iterator = I;
+ using sentinel = S;
+
+ subrange() = default;
+
+ template <bool SS = _store_size,
+ typename std::enable_if<!SS>::type* = nullptr>
+ SCN_CONSTEXPR14 subrange(I i, S s)
+ : m_data{SCN_MOVE(i), SCN_MOVE(s)}
+ {
+ }
+ template <bool Dependent = true,
+ subrange_kind KK = K,
+ typename std::enable_if<
+ KK == subrange_kind::sized>::type* = nullptr>
+ SCN_CONSTEXPR14 subrange(
+ I i,
+ S s,
+ typename std::enable_if<Dependent,
+ iter_difference_t<I>>::type n)
+ : m_data{SCN_MOVE(i), SCN_MOVE(s), n}
+ {
+ }
+
+ constexpr I begin() const noexcept
+ {
+ return m_data.begin;
+ }
+
+ constexpr S end() const noexcept
+ {
+ return m_data.end;
+ }
+
+ SCN_NODISCARD constexpr bool empty() const noexcept
+ {
+ return m_data.begin == m_data.end;
+ }
+
+ template <subrange_kind KK = K,
+ typename std::enable_if<
+ KK == subrange_kind::sized>::type* = nullptr>
+ constexpr iter_difference_t<I> size() const noexcept
+ {
+ return m_data.get_size();
+ }
+
+ private:
+ detail::subrange_data<I, S, _store_size> m_data{};
+ };
+
+ template <typename I, typename S, subrange_kind K>
+ I begin(subrange<I, S, K>&& r) noexcept
+ {
+ return r.begin();
+ }
+ template <typename I, typename S, subrange_kind K>
+ S end(subrange<I, S, K>&& r) noexcept
+ {
+ return r.end();
+ }
+ } // namespace _subrange
+
+ namespace detail {
+ template <std::size_t N>
+ struct subrange_get_impl;
+ template <>
+ struct subrange_get_impl<0> {
+ template <typename I, typename S, subrange_kind K>
+ static auto get(const subrange<I, S, K>& s)
+ -> decltype(s.begin())
+ {
+ return s.begin();
+ }
+ };
+ template <>
+ struct subrange_get_impl<1> {
+ template <typename I, typename S, subrange_kind K>
+ static auto get(const subrange<I, S, K>& s) -> decltype(s.end())
+ {
+ return s.end();
+ }
+ };
+ } // namespace detail
+
+ template <std::size_t N,
+ typename I,
+ typename S,
+ subrange_kind K,
+ typename std::enable_if<(N < 2)>::type* = nullptr>
+ auto get(const subrange<I, S, K>& s)
+ -> decltype(detail::subrange_get_impl<N>::get(s))
+ {
+ return detail::subrange_get_impl<N>::get(s);
+ }
+
+ // reconstructible_range
+ template <typename R>
+ struct pair_reconstructible_range
+ : std::integral_constant<
+ bool,
+ range<R>::value &&
+ forwarding_range<
+ typename std::remove_reference<R>::type>::value &&
+ std::is_constructible<R, iterator_t<R>, sentinel_t<R>>::
+ value> {
+ };
+ template <typename R>
+ struct reconstructible_range
+ : std::integral_constant<
+ bool,
+ range<R>::value &&
+ forwarding_range<
+ typename std::remove_reference<R>::type>::value &&
+ std::is_constructible<
+ R,
+ subrange<iterator_t<R>, sentinel_t<R>>>::value> {
+ };
+ } // namespace custom_ranges
+
+ namespace polyfill_2a {
+ // bidir iterator
+ namespace detail {
+ struct bidirectional_iterator_concept {
+ template <typename I>
+ auto _test_requires(I i)
+ -> decltype(custom_ranges::detail::requires_expr<
+ std::is_same<decltype(i--), I>::value>{});
+ template <typename>
+ static auto test(long) -> std::false_type;
+ template <typename I>
+ static auto test(int) -> typename std::enable_if<
+ std::is_base_of<
+ custom_ranges::bidirectional_iterator_tag,
+ custom_ranges::iterator_category_t<I>>::value &&
+ custom_ranges::detail::
+ _requires<bidirectional_iterator_concept, I>::value,
+ std::true_type>::type;
+ };
+ } // namespace detail
+ template <typename I>
+ struct bidirectional_iterator
+ : decltype(detail::bidirectional_iterator_concept::test<I>(0)) {
+ };
+
+ // random access iterator
+ namespace detail {
+ struct random_access_iterator_concept {
+ template <typename I>
+ auto _test_requires(I i,
+ const I j,
+ const custom_ranges::iter_difference_t<I> n)
+ -> decltype(valid_expr(
+ j + n,
+ custom_ranges::detail::requires_expr<
+ std::is_same<decltype(j + n), I>::value>{},
+ n + j,
+#ifndef _MSC_VER
+ custom_ranges::detail::requires_expr<
+ std::is_same<decltype(n + j), I>::value>{},
+#endif
+ j - n,
+ custom_ranges::detail::requires_expr<
+ std::is_same<decltype(j - n), I>::value>{},
+ j[n],
+ custom_ranges::detail::requires_expr<std::is_same<
+ decltype(j[n]),
+ custom_ranges::iter_reference_t<I>>::value>{},
+ custom_ranges::detail::requires_expr<
+ std::is_convertible<decltype(i < j),
+ bool>::value>{}));
+ template <typename>
+ static auto test(long) -> std::false_type;
+ template <typename I>
+ static auto test(int) -> typename std::enable_if<
+ bidirectional_iterator<I>::value &&
+ std::is_base_of<
+ custom_ranges::random_access_iterator_tag,
+ custom_ranges::iterator_category_t<I>>::value &&
+ custom_ranges::sized_sentinel_for<I, I>::value &&
+ custom_ranges::detail::
+ _requires<random_access_iterator_concept, I>::value,
+ std::true_type>::type;
+ };
+ } // namespace detail
+ template <typename I>
+ struct random_access_iterator
+ : decltype(detail::random_access_iterator_concept::test<I>(0)) {
+ };
+ } // namespace polyfill_2a
+
+ namespace custom_ranges {
+ // advance
+ namespace _advance {
+ struct fn {
+ private:
+ template <typename T>
+ static constexpr T abs(T t)
+ {
+ return t < T{0} ? -t : t;
+ }
+
+ template <
+ typename R,
+ typename std::enable_if<polyfill_2a::random_access_iterator<
+ R>::value>::type* = nullptr>
+ static SCN_CONSTEXPR14 void impl(R& r, iter_difference_t<R> n)
+ {
+ r += n;
+ }
+
+ template <
+ typename I,
+ typename std::enable_if<
+ polyfill_2a::bidirectional_iterator<I>::value &&
+ !polyfill_2a::random_access_iterator<I>::value>::type* =
+ nullptr>
+ static SCN_CONSTEXPR14 void impl(I& i, iter_difference_t<I> n)
+ {
+ constexpr auto zero = iter_difference_t<I>{0};
+
+ if (n > zero) {
+ while (n-- > zero) {
+ ++i;
+ }
+ }
+ else {
+ while (n++ < zero) {
+ --i;
+ }
+ }
+ }
+
+ template <
+ typename I,
+ typename std::enable_if<
+ !polyfill_2a::bidirectional_iterator<I>::value>::type* =
+ nullptr>
+ static SCN_CONSTEXPR14 void impl(I& i, iter_difference_t<I> n)
+ {
+ while (n-- > iter_difference_t<I>{0}) {
+ ++i;
+ }
+ }
+
+ template <
+ typename I,
+ typename S,
+ typename std::enable_if<
+ std::is_assignable<I&, S>::value>::type* = nullptr>
+ static SCN_CONSTEXPR14 void impl(I& i,
+ S bound,
+ detail::priority_tag<2>)
+ {
+ i = SCN_MOVE(bound);
+ }
+
+ template <typename I,
+ typename S,
+ typename std::enable_if<
+ sized_sentinel_for<S, I>::value>::type* = nullptr>
+ static SCN_CONSTEXPR14 void impl(I& i,
+ S bound,
+ detail::priority_tag<1>)
+ {
+ fn::impl(i, bound - i);
+ }
+
+ template <typename I, typename S>
+ static SCN_CONSTEXPR14 void impl(I& i,
+ S bound,
+ detail::priority_tag<0>)
+ {
+ while (i != bound) {
+ ++i;
+ }
+ }
+
+ template <typename I,
+ typename S,
+ typename std::enable_if<
+ sized_sentinel_for<S, I>::value>::type* = nullptr>
+ static SCN_CONSTEXPR14 auto impl(I& i,
+ iter_difference_t<I> n,
+ S bound)
+ -> iter_difference_t<I>
+ {
+ if (fn::abs(n) >= fn::abs(bound - i)) {
+ auto dist = bound - i;
+ fn::impl(i, bound, detail::priority_tag<2>{});
+ return dist;
+ }
+ else {
+ fn::impl(i, n);
+ return n;
+ }
+ }
+
+ template <
+ typename I,
+ typename S,
+ typename std::enable_if<
+ polyfill_2a::bidirectional_iterator<I>::value &&
+ !sized_sentinel_for<S, I>::value>::type* = nullptr>
+ static SCN_CONSTEXPR14 auto impl(I& i,
+ iter_difference_t<I> n,
+ S bound)
+ -> iter_difference_t<I>
+ {
+ constexpr iter_difference_t<I> zero{0};
+ iter_difference_t<I> counter{0};
+
+ if (n < zero) {
+ do {
+ --i;
+ --counter;
+ } while (++n < zero && i != bound);
+ }
+ else {
+ while (n-- > zero && i != bound) {
+ ++i;
+ ++counter;
+ }
+ }
+
+ return counter;
+ }
+
+ template <
+ typename I,
+ typename S,
+ typename std::enable_if<
+ !polyfill_2a::bidirectional_iterator<I>::value &&
+ !sized_sentinel_for<S, I>::value>::type* = nullptr>
+ static SCN_CONSTEXPR14 auto impl(I& i,
+ iter_difference_t<I> n,
+ S bound)
+ -> iter_difference_t<I>
+ {
+ constexpr iter_difference_t<I> zero{0};
+ iter_difference_t<I> counter{0};
+
+ while (n-- > zero && i != bound) {
+ ++i;
+ ++counter;
+ }
+
+ return counter;
+ }
+
+ public:
+ template <typename I>
+ SCN_CONSTEXPR14 void operator()(I& i,
+ iter_difference_t<I> n) const
+ {
+ fn::impl(i, n);
+ }
+
+ template <typename I,
+ typename S,
+ typename std::enable_if<
+ sentinel_for<S, I>::value>::type* = nullptr>
+ SCN_CONSTEXPR14 void operator()(I& i, S bound) const
+ {
+ fn::impl(i, bound, detail::priority_tag<2>{});
+ }
+
+ template <typename I,
+ typename S,
+ typename std::enable_if<
+ sentinel_for<S, I>::value>::type* = nullptr>
+ SCN_CONSTEXPR14 iter_difference_t<I>
+ operator()(I& i, iter_difference_t<I> n, S bound) const
+ {
+ return n - fn::impl(i, n, bound);
+ }
+ };
+ } // namespace _advance
+ namespace {
+ constexpr auto& advance = detail::static_const<_advance::fn>::value;
+ }
+
+ // distance
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wnoexcept")
+ namespace _distance {
+ struct fn {
+ private:
+ template <typename I, typename S>
+ static SCN_CONSTEXPR14 auto impl(I i,
+ S s) noexcept(noexcept(s - i))
+ -> typename std::enable_if<sized_sentinel_for<S, I>::value,
+ iter_difference_t<I>>::type
+ {
+ return s - i;
+ }
+
+ template <typename I, typename S>
+ static SCN_CONSTEXPR14 auto impl(I i, S s) noexcept(
+ noexcept(i != s, ++i, ++SCN_DECLVAL(iter_difference_t<I>&)))
+ -> typename std::enable_if<!sized_sentinel_for<S, I>::value,
+ iter_difference_t<I>>::type
+ {
+ iter_difference_t<I> counter{0};
+ while (i != s) {
+ ++i;
+ ++counter;
+ }
+ return counter;
+ }
+
+ template <typename R>
+ static SCN_CONSTEXPR14 auto impl(R&& r) noexcept(
+ noexcept(::scn::custom_ranges::size(r))) ->
+ typename std::enable_if<
+ sized_range<R>::value,
+ iter_difference_t<iterator_t<R>>>::type
+ {
+ return static_cast<iter_difference_t<iterator_t<R>>>(
+ ::scn::custom_ranges::size(r));
+ }
+
+ template <typename R>
+ static SCN_CONSTEXPR14 auto impl(R&& r) noexcept(
+ noexcept(fn::impl(::scn::custom_ranges::begin(r),
+ ::scn::custom_ranges::end(r)))) ->
+ typename std::enable_if<
+ !sized_range<R>::value,
+ iter_difference_t<iterator_t<R>>>::type
+ {
+ return fn::impl(::scn::custom_ranges::begin(r),
+ ::scn::custom_ranges::end(r));
+ }
+
+ public:
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 auto operator()(I first, S last) const
+ noexcept(noexcept(fn::impl(SCN_MOVE(first),
+ SCN_MOVE(last)))) ->
+ typename std::enable_if<sentinel_for<S, I>::value,
+ iter_difference_t<I>>::type
+ {
+ return fn::impl(SCN_MOVE(first), SCN_MOVE(last));
+ }
+
+ template <typename R>
+ SCN_CONSTEXPR14 auto operator()(R&& r) const
+ noexcept(noexcept(fn::impl(SCN_FWD(r)))) ->
+ typename std::enable_if<
+ range<R>::value,
+ iter_difference_t<iterator_t<R>>>::type
+ {
+ return fn::impl(SCN_FWD(r));
+ }
+ };
+ } // namespace _distance
+ namespace {
+ constexpr auto& distance =
+ detail::static_const<_distance::fn>::value;
+ }
+ SCN_GCC_POP // -Wnoexcept
+ } // namespace custom_ranges
+
+ namespace polyfill_2a {
+ template <typename T>
+ using iter_value_t = ::scn::custom_ranges::iter_value_t<T>;
+ template <typename T>
+ using iter_reference_t = ::scn::custom_ranges::iter_reference_t<T>;
+ template <typename T>
+ using iter_difference_t = ::scn::custom_ranges::iter_difference_t<T>;
+ } // namespace polyfill_2a
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+namespace std {
+ template <typename I, typename S, ::scn::custom_ranges::subrange_kind K>
+ struct tuple_size<::scn::custom_ranges::subrange<I, S, K>>
+ : public integral_constant<size_t, 2> {
+ };
+
+ template <typename I, typename S, ::scn::custom_ranges::subrange_kind K>
+ struct tuple_element<0, ::scn::custom_ranges::subrange<I, S, K>> {
+ using type = I;
+ };
+ template <typename I, typename S, ::scn::custom_ranges::subrange_kind K>
+ struct tuple_element<1, ::scn::custom_ranges::subrange<I, S, K>> {
+ using type = S;
+ };
+
+ using ::scn::custom_ranges::get;
+} // namespace std
+
+#define SCN_CHECK_CONCEPT(C) C::value
+
+#endif // SCN_RANGES_CUSTOM_IMPL_H
diff --git a/src/third-party/scnlib/include/scn/ranges/ranges.h b/src/third-party/scnlib/include/scn/ranges/ranges.h
new file mode 100644
index 0000000..aa70f50
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/ranges/ranges.h
@@ -0,0 +1,49 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_RANGES_RANGES_H
+#define SCN_RANGES_RANGES_H
+
+#include "../detail/config.h"
+
+#ifndef SCN_USE_STD_RANGES
+
+#if SCN_HAS_CONCEPTS && SCN_HAS_RANGES
+#define SCN_USE_STD_RANGES 1
+#else
+#define SCN_USE_STD_RANGES 0
+#endif
+
+#endif // !defined(SCN_USE_STD_RANGES)
+
+#if SCN_USE_STD_RANGES
+#include "std_impl.h"
+#define SCN_RANGES_NAMESPACE ::scn::std_ranges
+#else
+#include "custom_impl.h"
+#define SCN_RANGES_NAMESPACE ::scn::custom_ranges
+#endif
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace ranges = SCN_RANGES_NAMESPACE;
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_RANGES_RANGES_H
diff --git a/src/third-party/scnlib/include/scn/ranges/std_impl.h b/src/third-party/scnlib/include/scn/ranges/std_impl.h
new file mode 100644
index 0000000..abc3422
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/ranges/std_impl.h
@@ -0,0 +1,67 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_RANGES_STD_IMPL_H
+#define SCN_RANGES_STD_IMPL_H
+
+#include "../detail/config.h"
+
+#if SCN_HAS_CONCEPTS && SCN_HAS_RANGES
+
+SCN_GCC_PUSH
+SCN_GCC_IGNORE("-Wnoexcept")
+#include <iterator>
+#include <ranges>
+SCN_GCC_POP
+
+#include "util.h"
+
+#include "../util/string_view.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace std_ranges = ::std::ranges;
+
+ namespace polyfill_2a {
+ template <typename T>
+ using iter_value_t = ::std::iter_value_t<T>;
+ template <typename T>
+ using iter_reference_t = ::std::iter_reference_t<T>;
+ template <typename T>
+ using iter_difference_t = ::std::iter_difference_t<T>;
+
+ template <typename I>
+ concept bidirectional_iterator = std::bidirectional_iterator<I>;
+ template <typename I>
+ concept random_access_iterator = std::random_access_iterator<I>;
+ } // namespace polyfill_2a
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+namespace std::ranges {
+ template <typename CharT>
+ inline constexpr bool enable_view<::scn::basic_string_view<CharT>> = true;
+ template <typename T>
+ inline constexpr bool enable_view<::scn::span<T>> = true;
+} // namespace std
+
+#define SCN_CHECK_CONCEPT(C) C
+#endif
+
+#endif // SCN_RANGES_STD_IMPL_H
diff --git a/src/third-party/scnlib/include/scn/ranges/util.h b/src/third-party/scnlib/include/scn/ranges/util.h
new file mode 100644
index 0000000..d5954d1
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/ranges/util.h
@@ -0,0 +1,419 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+//
+// The contents of this file are adapted from NanoRange.
+// https://github.com/tcbrindle/NanoRange
+// Copyright (c) 2018 Tristan Brindle
+// Distributed under the Boost Software License, Version 1.0
+
+#ifndef SCN_RANGES_UTIL_H
+#define SCN_RANGES_UTIL_H
+
+#include "../util/meta.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace custom_ranges {
+ namespace detail {
+ template <size_t N>
+ using priority_tag = ::scn::detail::priority_tag<N>;
+
+ template <typename... Ts>
+ using void_t = ::scn::detail::void_t<Ts...>;
+
+ template <typename T>
+ using static_const = ::scn::detail::static_const<T>;
+
+ template <typename T>
+ using remove_cvref_t = ::scn::detail::remove_cvref_t<T>;
+
+ template <typename T>
+ constexpr typename std::decay<T>::type decay_copy(T&& t) noexcept(
+ noexcept(static_cast<typename std::decay<T>::type>(SCN_FWD(t))))
+ {
+ return SCN_FWD(t);
+ }
+
+ struct nonesuch {
+ nonesuch() = delete;
+ nonesuch(nonesuch const&) = delete;
+ nonesuch& operator=(const nonesuch&) = delete;
+ ~nonesuch() = delete;
+ };
+
+ template <typename Void,
+ template <class...>
+ class Trait,
+ typename... Args>
+ struct test {
+ using type = nonesuch;
+ };
+
+ template <template <class...> class Trait, typename... Args>
+ struct test<void_t<Trait<Args...>>, Trait, Args...> {
+ using type = Trait<Args...>;
+ };
+
+ template <template <class...> class Trait, typename... Args>
+ using test_t = typename test<void, Trait, Args...>::type;
+
+ template <typename Void,
+ template <class...>
+ class AliasT,
+ typename... Args>
+ struct exists_helper : std::false_type {
+ };
+
+ template <template <class...> class AliasT, typename... Args>
+ struct exists_helper<void_t<AliasT<Args...>>, AliasT, Args...>
+ : std::true_type {
+ };
+
+ template <template <class...> class AliasT, typename... Args>
+ struct exists : exists_helper<void, AliasT, Args...> {
+ };
+
+ template <typename R,
+ typename... Args,
+ typename = decltype(&R::template _test_requires<Args...>)>
+ auto test_requires(R&) -> void;
+
+ template <typename R, typename... Args>
+ using test_requires_t =
+ decltype(test_requires<R, Args...>(SCN_DECLVAL(R&)));
+
+ template <typename R, typename... Args>
+ struct _requires : exists<test_requires_t, R, Args...> {
+ };
+
+ template <bool Expr>
+ using requires_expr = typename std::enable_if<Expr, int>::type;
+
+ template <typename...>
+ struct get_common_type;
+
+ template <typename T, typename U>
+ struct copy_cv {
+ using type = U;
+ };
+ template <typename T, typename U>
+ struct copy_cv<const T, U> {
+ using type = typename std::add_const<U>::type;
+ };
+ template <typename T, typename U>
+ struct copy_cv<volatile T, U> {
+ using type = typename std::add_volatile<U>::type;
+ };
+ template <typename T, typename U>
+ struct copy_cv<const volatile T, U> {
+ using type = typename std::add_cv<U>::type;
+ };
+ template <typename T, typename U>
+ using copy_cv_t = typename copy_cv<T, U>::type;
+
+ template <typename T>
+ using cref_t = typename std::add_lvalue_reference<
+ const typename std::remove_reference<T>::type>::type;
+
+ template <typename T>
+ struct rref_res {
+ using type = T;
+ };
+ template <typename T>
+ struct rref_res<T&> {
+ using type = typename std::remove_reference<T>::type&&;
+ };
+ template <typename T>
+ using rref_res_t = typename rref_res<T>::type;
+
+ template <typename T, typename U>
+ using cond_res_t =
+ decltype(SCN_DECLVAL(bool) ? std::declval<T (&)()>()()
+ : std::declval<U (&)()>()());
+
+ template <typename T, typename U>
+ struct simple_common_reference {
+ };
+
+ template <
+ typename T,
+ typename U,
+ typename C =
+ test_t<cond_res_t, copy_cv_t<T, U>&, copy_cv_t<U, T>&>>
+ struct lvalue_simple_common_reference
+ : std::enable_if<std::is_reference<C>::value, C> {
+ };
+ template <typename T, typename U>
+ using lvalue_scr_t =
+ typename lvalue_simple_common_reference<T, U>::type;
+ template <typename T, typename U>
+ struct simple_common_reference<T&, U&>
+ : lvalue_simple_common_reference<T, U> {
+ };
+
+ template <typename T,
+ typename U,
+ typename LCR = test_t<lvalue_scr_t, T, U>,
+ typename C = rref_res_t<LCR>>
+ struct rvalue_simple_common_reference
+ : std::enable_if<std::is_convertible<T&&, C>::value &&
+ std::is_convertible<U&&, C>::value>::type {
+ };
+ template <typename T, typename U>
+ struct simple_common_reference<T&&, U&&>
+ : rvalue_simple_common_reference<T, U> {
+ };
+
+ template <typename A,
+ typename B,
+ typename C = test_t<lvalue_scr_t, A, const B>>
+ struct mixed_simple_common_reference
+ : std::enable_if<std::is_convertible<B&&, C>::value, C>::type {
+ };
+
+ template <typename A, typename B>
+ struct simple_common_reference<A&, B&&>
+ : mixed_simple_common_reference<A, B> {
+ };
+ template <typename A, typename B>
+ struct simple_common_reference<A&&, B&>
+ : simple_common_reference<B&&, A&> {
+ };
+ template <typename T, typename U>
+ using simple_common_reference_t =
+ typename simple_common_reference<T, U>::type;
+
+ template <typename>
+ struct xref {
+ template <typename U>
+ using type = U;
+ };
+
+ template <typename A>
+ struct xref<A&> {
+ template <typename U>
+ using type = typename std::add_lvalue_reference<
+ typename xref<A>::template type<U>>::type;
+ };
+
+ template <typename A>
+ struct xref<A&&> {
+ template <typename U>
+ using type = typename std::add_rvalue_reference<
+ typename xref<A>::template type<U>>::type;
+ };
+
+ template <typename A>
+ struct xref<const A> {
+ template <typename U>
+ using type = typename std::add_const<
+ typename xref<A>::template type<U>>::type;
+ };
+
+ template <typename A>
+ struct xref<volatile A> {
+ template <typename U>
+ using type = typename std::add_volatile<
+ typename xref<A>::template type<U>>::type;
+ };
+
+ template <typename A>
+ struct xref<const volatile A> {
+ template <typename U>
+ using type = typename std::add_cv<
+ typename xref<A>::template type<U>>::type;
+ };
+
+ template <typename T,
+ typename U,
+ template <class>
+ class TQual,
+ template <class>
+ class UQual>
+ struct basic_common_reference {
+ };
+
+ template <typename...>
+ struct get_common_reference;
+ template <typename... Ts>
+ using get_common_reference_t =
+ typename get_common_reference<Ts...>::type;
+
+ template <>
+ struct get_common_reference<> {
+ };
+ template <typename T0>
+ struct get_common_reference<T0> {
+ using type = T0;
+ };
+
+ template <typename T, typename U>
+ struct has_simple_common_ref
+ : exists<simple_common_reference_t, T, U> {
+ };
+ template <typename T, typename U>
+ using basic_common_ref_t = typename basic_common_reference<
+ ::scn::detail::remove_cvref_t<T>,
+ ::scn::detail::remove_cvref_t<U>,
+ xref<T>::template type,
+ xref<U>::template type>::type;
+
+ template <typename T, typename U>
+ struct has_basic_common_ref : exists<basic_common_ref_t, T, U> {
+ };
+ template <typename T, typename U>
+ struct has_cond_res : exists<cond_res_t, T, U> {
+ };
+
+ template <typename T, typename U, typename = void>
+ struct binary_common_ref : get_common_type<T, U> {
+ };
+ template <typename T, typename U>
+ struct binary_common_ref<
+ T,
+ U,
+ typename std::enable_if<
+ has_simple_common_ref<T, U>::value>::type>
+ : simple_common_reference<T, U> {
+ };
+ template <typename T, typename U>
+ struct binary_common_ref<
+ T,
+ U,
+ typename std::enable_if<
+ has_basic_common_ref<T, U>::value &&
+ !has_simple_common_ref<T, U>::value>::type> {
+ using type = basic_common_ref_t<T, U>;
+ };
+ template <typename T, typename U>
+ struct binary_common_ref<
+ T,
+ U,
+ typename std::enable_if<
+ has_cond_res<T, U>::value &&
+ !has_basic_common_ref<T, U>::value &&
+ !has_simple_common_ref<T, U>::value>::type> {
+ using type = cond_res_t<T, U>;
+ };
+ template <typename T1, typename T2>
+ struct get_common_reference<T1, T2> : binary_common_ref<T1, T2> {
+ };
+
+ template <typename Void, typename T1, typename T2, typename... Rest>
+ struct multiple_common_reference {
+ };
+ template <typename T1, typename T2, typename... Rest>
+ struct multiple_common_reference<
+ void_t<get_common_reference_t<T1, T2>>,
+ T1,
+ T2,
+ Rest...> : get_common_reference<get_common_reference_t<T1, T2>,
+ Rest...> {
+ };
+ template <typename T1, typename T2, typename... Rest>
+ struct get_common_reference<T1, T2, Rest...>
+ : multiple_common_reference<void, T1, T2, Rest...> {
+ };
+
+ template <typename... Ts>
+ using get_common_type_t = typename get_common_type<Ts...>::type;
+
+ template <typename T, typename U>
+ struct _same_decayed
+ : std::integral_constant<
+ bool,
+ std::is_same<T, typename std::decay<T>::type>::value &&
+ std::is_same<U,
+ typename std::decay<U>::type>::value> {
+ };
+
+ template <typename T, typename U>
+ using ternary_return_t =
+ typename std::decay<decltype(false ? SCN_DECLVAL(T)
+ : SCN_DECLVAL(U))>::type;
+
+ template <typename, typename, typename = void>
+ struct binary_common_type {
+ };
+
+ template <typename T, typename U>
+ struct binary_common_type<
+ T,
+ U,
+ typename std::enable_if<!_same_decayed<T, U>::value>::type>
+ : get_common_type<typename std::decay<T>::type,
+ typename std::decay<U>::type> {
+ };
+
+ template <typename T, typename U>
+ struct binary_common_type<
+ T,
+ U,
+ typename std::enable_if<
+ _same_decayed<T, U>::value &&
+ exists<ternary_return_t, T, U>::value>::type> {
+ using type = ternary_return_t<T, U>;
+ };
+
+ template <typename T, typename U>
+ struct binary_common_type<
+ T,
+ U,
+ typename std::enable_if<
+ _same_decayed<T, U>::value &&
+ !exists<ternary_return_t, T, U>::value &&
+ exists<cond_res_t, cref_t<T>, cref_t<U>>::value>::type> {
+ using type =
+ typename std::decay<cond_res_t<cref_t<T>, cref_t<U>>>::type;
+ };
+
+ template <>
+ struct get_common_type<> {
+ };
+
+ template <typename T>
+ struct get_common_type<T> : get_common_type<T, T> {
+ };
+
+ template <typename T, typename U>
+ struct get_common_type<T, U> : binary_common_type<T, U> {
+ };
+
+ template <typename Void, typename...>
+ struct multiple_common_type {
+ };
+
+ template <typename T1, typename T2, typename... R>
+ struct multiple_common_type<void_t<get_common_type_t<T1, T2>>,
+ T1,
+ T2,
+ R...>
+ : get_common_type<get_common_type_t<T1, T2>, R...> {
+ };
+
+ template <typename T1, typename T2, typename... R>
+ struct get_common_type<T1, T2, R...>
+ : multiple_common_type<void, T1, T2, R...> {
+ };
+ } // namespace detail
+ } // namespace custom_ranges
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_RANGES_UTIL_H
diff --git a/src/third-party/scnlib/include/scn/reader/common.h b/src/third-party/scnlib/include/scn/reader/common.h
new file mode 100644
index 0000000..0f2b83b
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/reader/common.h
@@ -0,0 +1,1663 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_READER_COMMON_H
+#define SCN_READER_COMMON_H
+
+#include "../detail/error.h"
+#include "../detail/locale.h"
+#include "../detail/range.h"
+#include "../unicode/unicode.h"
+#include "../util/algorithm.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ // read_code_unit
+
+ namespace detail {
+ template <typename WrappedRange>
+ expected<typename WrappedRange::char_type>
+ read_code_unit_impl(WrappedRange& r, bool advance, std::true_type)
+ {
+ SCN_CLANG_PUSH
+ // clang 10 behaves weirdly
+ SCN_CLANG_IGNORE("-Wzero-as-null-pointer-constant")
+ SCN_EXPECT(r.begin() < r.end());
+ SCN_CLANG_POP
+ auto ch = *r.begin();
+ if (advance) {
+ r.advance();
+ }
+ return {ch};
+ }
+ template <typename WrappedRange>
+ expected<typename WrappedRange::char_type>
+ read_code_unit_impl(WrappedRange& r, bool advance, std::false_type)
+ {
+ SCN_EXPECT(r.begin() != r.end());
+ auto ch = *r.begin();
+ if (advance && ch) {
+ r.advance();
+ }
+ return ch;
+ }
+ } // namespace detail
+
+ /**
+ * Reads a single character (= code unit) from the range.
+ * Dereferences the begin iterator, wrapping it in an `expected` if
+ * necessary.
+ *
+ * Encoding-agnostic, doesn't care about code points, and may leave behind
+ * partial ones.
+ *
+ * \param r Range to read from
+ * \param advance If `true`, and the read was successful, the range is
+ * advanced by a single character, as if by calling `r.advance()`.
+ *
+ * \return The next character in the range, obtained as if by dereferencing
+ * the begin iterator `*r.begin()`.
+ * If `r.begin() == r.end()`, returns EOF.
+ * If `r` is direct, returns `*r.begin()` wrapped in an `expected`.
+ * If `r` is not direct, returns `*r.begin()` as-is, with any errors that
+ * may have been caused by the read.
+ */
+ template <typename WrappedRange>
+ expected<typename WrappedRange::char_type> read_code_unit(
+ WrappedRange& r,
+ bool advance = true)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return detail::read_code_unit_impl(
+ r, advance,
+ std::integral_constant<bool, WrappedRange::is_direct>{});
+ }
+
+ // putback_n
+
+ /// @{
+
+ /**
+ * Puts back `n` characters (= code units) into `r` as if by repeatedly
+ * calling `r.advance(-1)`.
+ *
+ * Encoding-agnostic, may leave behind partial code points.
+ *
+ * \param r Range to roll back
+ * \param n Characters to put back, must be less than or equal to the number
+ * of characters already read from `r`.
+ *
+ * \return If `r` is contiguous, will always return `error::good`.
+ * Otherwise, may return `error::unrecoverable_source_error`, if the putback
+ * fails.
+ */
+ template <
+ typename WrappedRange,
+ typename std::enable_if<WrappedRange::is_contiguous>::type* = nullptr>
+ error putback_n(WrappedRange& r, ranges::range_difference_t<WrappedRange> n)
+ {
+ SCN_EXPECT(n <= ranges::distance(r.begin_underlying(), r.begin()));
+ r.advance(-n);
+ return {};
+ }
+ template <
+ typename WrappedRange,
+ typename std::enable_if<!WrappedRange::is_contiguous>::type* = nullptr>
+ error putback_n(WrappedRange& r, ranges::range_difference_t<WrappedRange> n)
+ {
+ for (ranges::range_difference_t<WrappedRange> i = 0; i < n; ++i) {
+ r.advance(-1);
+ if (r.begin() == r.end()) {
+ return {error::unrecoverable_source_error, "Putback failed"};
+ }
+ }
+ return {};
+ }
+
+ /// @}
+
+ // read_code_point
+
+ /**
+ * Type returned by `read_code_point`
+ * \tparam CharT Character type of the range
+ */
+ template <typename CharT>
+ struct read_code_point_result {
+ /// Code units, may point to `writebuf` given to `read_code_point`
+ span<const CharT> chars;
+ /// Parsed code point
+ code_point cp;
+ };
+
+ namespace detail {
+ // contiguous && direct
+ template <typename CharT, typename WrappedRange>
+ expected<read_code_point_result<CharT>> read_code_point_impl(
+ WrappedRange& r,
+ span<CharT> writebuf,
+ std::true_type)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+
+ auto sbuf = r.get_buffer_and_advance(4 / sizeof(CharT));
+ if (sbuf.size() == 0) {
+ auto ret = read_code_unit(r, true);
+ if (!ret) {
+ return ret.error();
+ }
+ sbuf = writebuf.first(1);
+ writebuf[0] = ret.value();
+ }
+ int len = ::scn::get_sequence_length(sbuf[0]);
+ if (SCN_UNLIKELY(len == 0)) {
+ return error(error::invalid_encoding, "Invalid code point");
+ }
+ if (sbuf.ssize() > len) {
+ auto e = putback_n(r, sbuf.ssize() - len);
+ if (!e) {
+ return e;
+ }
+ sbuf = sbuf.first(static_cast<size_t>(len));
+ }
+ if (len == 1) {
+ // Single-char code point
+ return read_code_point_result<CharT>{sbuf.first(1),
+ make_code_point(sbuf[0])};
+ }
+ while (sbuf.ssize() < len) {
+ auto ret = read_code_unit(r, true);
+ if (!ret) {
+ auto e = putback_n(r, sbuf.ssize());
+ if (!e) {
+ return e;
+ }
+ if (ret.error().code() == error::end_of_range) {
+ return error(error::invalid_encoding,
+ "Invalid code point");
+ }
+ return ret.error();
+ }
+ sbuf = make_span(writebuf.begin(), sbuf.size() + 1);
+ writebuf[sbuf.size() - 1] = ret.value();
+ }
+
+ code_point cp{};
+ auto ret = parse_code_point(sbuf.begin(), sbuf.end(), cp);
+ if (!ret) {
+ return ret.error();
+ }
+ return read_code_point_result<CharT>{sbuf, cp};
+ }
+
+ template <typename CharT, typename WrappedRange>
+ expected<read_code_point_result<CharT>> read_code_point_impl(
+ WrappedRange& r,
+ span<CharT> writebuf,
+ std::false_type)
+ {
+ auto first = read_code_unit(r, false);
+ if (!first) {
+ return first.error();
+ }
+
+ auto len =
+ static_cast<size_t>(::scn::get_sequence_length(first.value()));
+ if (SCN_UNLIKELY(len == 0)) {
+ return error(error::invalid_encoding, "Invalid code point");
+ }
+ r.advance();
+
+ writebuf[0] = first.value();
+ if (len == 1) {
+ // Single-char code point
+ return read_code_point_result<CharT>{
+ make_span(writebuf.data(), 1),
+ make_code_point(first.value())};
+ }
+
+ size_t index = 1;
+
+ auto parse = [&]() -> expected<read_code_point_result<CharT>> {
+ code_point cp{};
+ auto ret = parse_code_point(writebuf.data(),
+ writebuf.data() + len, cp);
+ if (!ret) {
+ auto pb = putback_n(r, static_cast<std::ptrdiff_t>(len));
+ if (!pb) {
+ return pb;
+ }
+ return ret.error();
+ }
+ auto s = make_span(writebuf.data(), len);
+ return read_code_point_result<CharT>{s, cp};
+ };
+ auto advance = [&]() -> error {
+ auto ret = read_code_unit(r, false);
+ if (!ret) {
+ auto pb = putback_n(r, static_cast<std::ptrdiff_t>(index));
+ if (!pb) {
+ return pb;
+ }
+ return ret.error();
+ }
+ writebuf[index] = ret.value();
+ ++index;
+ r.advance();
+ return {};
+ };
+
+ while (index < 4) {
+ auto e = advance();
+ if (!e) {
+ return e;
+ }
+ if (index == len) {
+ return parse();
+ }
+ }
+ SCN_ENSURE(false);
+ SCN_UNREACHABLE;
+ }
+ } // namespace detail
+
+ /**
+ * Read a single Unicode code point from `r` as if by repeatedly calling
+ * `read_code_unit()`.
+ *
+ * Advances the range past the read code point. On error, rolls back the
+ * range into the state it was before calling this function, as if by
+ * calling `putback_n()`.
+ *
+ * \param r Range to read from
+ * \param writebuf Buffer to use for reading into, if necessary. `BufValueT`
+ * can be any trivial type. Must be at least 4 bytes long. May be written
+ * over.
+ *
+ * \return An instance of `read_code_point_result`, wrapped in an
+ * `expected`. `chars` contains the code units read from `r`, which may
+ * point to `writebuf`. `cp` contains the code point parsed.
+ * If `r.begin() == r.end()`, returns EOF.
+ * If `read_code_unit()` or `putback_n()` fails, returns any errors returned
+ * by it.
+ * If the code point was not encoded correctly, returns
+ * `error::invalid_encoding`.
+ */
+ template <typename WrappedRange, typename BufValueT>
+ expected<read_code_point_result<typename WrappedRange::char_type>>
+ read_code_point(WrappedRange& r, span<BufValueT> writebuf)
+ {
+ SCN_EXPECT(writebuf.size() * sizeof(BufValueT) >= 4);
+ using char_type = typename WrappedRange::char_type;
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wcast-align") // taken care of by the caller
+ return detail::read_code_point_impl<char_type>(
+ r,
+ make_span(reinterpret_cast<char_type*>(writebuf.data()),
+ writebuf.size() * sizeof(BufValueT) / sizeof(char_type)),
+ std::integral_constant<bool,
+ WrappedRange::provides_buffer_access>{});
+ SCN_GCC_POP
+ }
+
+ // read_zero_copy
+
+ /// @{
+
+ /**
+ * Reads up to `n` characters (= code units) from `r`, as if by repeatedly
+ * incrementing `r.begin()`, and returns a `span` pointing into `r`.
+ *
+ * Let `count` be `min(r.size(), n)`.
+ * Reads, and advances `r` by `count` characters.
+ * `r.begin()` is in no point dereferenced.
+ * If `r.size()` is not defined, the range is not contiguous, and an empty
+ * span is returned.
+ *
+ * \return A `span` pointing to `r`, starting from `r.begin()` and with a
+ * size of `count`.
+ * If `r.begin() == r.end()`, returns EOF.
+ * If the range does not satisfy `contiguous_range`, returns an empty
+ * `span`.
+ */
+ template <typename WrappedRange,
+ typename std::enable_if<
+ WrappedRange::provides_buffer_access>::type* = nullptr>
+ expected<span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>>
+ read_zero_copy(WrappedRange& r, ranges::range_difference_t<WrappedRange> n)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return r.get_buffer_and_advance(static_cast<size_t>(n));
+ }
+ template <typename WrappedRange,
+ typename std::enable_if<
+ !WrappedRange::provides_buffer_access>::type* = nullptr>
+ expected<span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>>
+ read_zero_copy(WrappedRange& r, ranges::range_difference_t<WrappedRange>)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>{};
+ }
+ /// @}
+
+ // read_all_zero_copy
+
+ /// @{
+ /**
+ * Reads every character from `r`, as if by repeatedly incrementing
+ * `r.begin()`, and returns a `span` pointing into `r`.
+ *
+ * If there's no error, `r` is advanced to the end.
+ * `r.begin()` is in no point dereferenced.
+ * If `r.size()` is not defined, the range is not contiguous, and an empty
+ * span is returned.
+ *
+ * \return A `span` pointing to `r`, starting at `r.begin()` and ending at
+ * `r.end()`.
+ * If `r.begin() == r.end()`, returns EOF.
+ * If the range does not satisfy `contiguous_range`, returns an empty
+ * `span`.
+ */
+ template <
+ typename WrappedRange,
+ typename std::enable_if<WrappedRange::is_contiguous>::type* = nullptr>
+ expected<span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>>
+ read_all_zero_copy(WrappedRange& r)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ auto s = make_span(r.data(), static_cast<size_t>(r.size()));
+ r.advance(r.size());
+ return s;
+ }
+ template <
+ typename WrappedRange,
+ typename std::enable_if<!WrappedRange::is_contiguous>::type* = nullptr>
+ expected<span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>>
+ read_all_zero_copy(WrappedRange& r)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return span<const typename detail::extract_char_type<
+ typename WrappedRange::iterator>::type>{};
+ }
+ /// @}
+
+ // read_into
+
+ namespace detail {
+ template <typename WrappedRange, typename OutputIterator>
+ error read_into_impl(WrappedRange& r,
+ OutputIterator& it,
+ ranges::range_difference_t<WrappedRange> n)
+ {
+ for (; n != 0; --n) {
+ auto ret = read_code_unit(r, false);
+ if (!ret) {
+ return ret.error();
+ }
+ *it = ret.value();
+ r.advance();
+ }
+ return {};
+ }
+ } // namespace detail
+
+ /// @{
+
+ /**
+ * Reads up to `n` characters (= code units) from `r`, as if by repeatedly
+ * calling `read_code_unit()`, and writing the characters into `it`.
+ *
+ * If reading fails at any point, the error is returned.
+ * `r` is advanced by as many characters that were successfully read.
+ *
+ * \param r Range to read
+ * \param it Iterator to write into, e.g. `std::back_insert_iterator`. Must
+ * satisfy `output_iterator`, and be incrementable by `n` times.
+ * \param n Characters to read from `r`
+ *
+ * \return `error::good` if `n` characters were read.
+ * If `r.begin() == r.end()` at any point before `n` characters has been
+ * read, returns EOF.
+ * Any error returned by `read_code_unit()` if one
+ * occurred.
+ */
+ template <typename WrappedRange,
+ typename OutputIterator,
+ typename std::enable_if<
+ WrappedRange::provides_buffer_access>::type* = nullptr>
+ error read_into(WrappedRange& r,
+ OutputIterator& it,
+ ranges::range_difference_t<WrappedRange> n)
+ {
+ while (n != 0) {
+ if (r.begin() == r.end()) {
+ return {error::end_of_range, "EOF"};
+ }
+ auto s = read_zero_copy(r, n);
+ if (!s) {
+ return s.error();
+ }
+ if (s.value().size() == 0) {
+ break;
+ }
+ it = std::copy(s.value().begin(), s.value().end(), it);
+ n -= s.value().ssize();
+ }
+ if (n != 0) {
+ return detail::read_into_impl(r, it, n);
+ }
+ return {};
+ }
+ template <typename WrappedRange,
+ typename OutputIterator,
+ typename std::enable_if<
+ !WrappedRange::provides_buffer_access>::type* = nullptr>
+ error read_into(WrappedRange& r,
+ OutputIterator& it,
+ ranges::range_difference_t<WrappedRange> n)
+ {
+ if (r.begin() == r.end()) {
+ return {error::end_of_range, "EOF"};
+ }
+ return detail::read_into_impl(r, it, n);
+ }
+ /// @}
+
+ namespace detail {
+ template <typename WrappedRange, typename Predicate>
+ expected<span<const typename WrappedRange::char_type>>
+ read_until_pred_contiguous(WrappedRange& r,
+ Predicate&& pred,
+ bool pred_result_to_stop,
+ bool keep_final)
+ {
+ using span_type = span<const typename WrappedRange::char_type>;
+
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+
+ if (!pred.is_multibyte()) {
+ for (auto it = r.begin(); it != r.end(); ++it) {
+ if (pred(make_span(&*it, 1)) == pred_result_to_stop) {
+ auto begin = r.data();
+ auto end = keep_final ? it + 1 : it;
+ r.advance_to(end);
+ return span_type{
+ begin, to_address_safe(end, r.begin(), r.end())};
+ }
+ }
+ }
+ else {
+ for (auto it = r.begin(); it != r.end();) {
+ auto len = ::scn::get_sequence_length(*it);
+ if (len == 0 || ranges::distance(it, r.end()) < len) {
+ return error{error::invalid_encoding,
+ "Invalid code point"};
+ }
+ auto span =
+ make_span(to_address_safe(it, r.begin(), r.end()),
+ static_cast<size_t>(len));
+ code_point cp{};
+ auto i = parse_code_point(span.begin(), span.end(), cp);
+ if (!i) {
+ return i.error();
+ }
+ if (i.value() != span.end()) {
+ return error{error::invalid_encoding,
+ "Invalid code point"};
+ }
+ if (pred(span) == pred_result_to_stop) {
+ auto begin = r.data();
+ auto end = keep_final ? it + len : it;
+ r.advance_to(end);
+ return span_type{
+ begin, to_address_safe(end, r.begin(), r.end())};
+ }
+ it += len;
+ }
+ }
+ auto begin = r.data();
+ auto end = r.data() + r.size();
+ r.advance_to(r.end());
+ return span_type{begin, end};
+ }
+ } // namespace detail
+
+ // read_until_space_zero_copy
+
+ namespace detail {
+ template <typename WrappedRange, typename Predicate>
+ expected<span<const typename WrappedRange::char_type>>
+ read_until_space_zero_copy_impl(WrappedRange& r,
+ Predicate&& is_space,
+ bool keep_final_space,
+ std::true_type)
+ {
+ return detail::read_until_pred_contiguous(r, SCN_FWD(is_space),
+ true, keep_final_space);
+ }
+ template <typename WrappedRange, typename Predicate>
+ expected<span<const typename WrappedRange::char_type>>
+ read_until_space_zero_copy_impl(WrappedRange& r,
+ Predicate&&,
+ bool,
+ std::false_type)
+ {
+ if (r.begin() == r.end()) {
+ return error(error::end_of_range, "EOF");
+ }
+ return span<const typename WrappedRange::char_type>{};
+ }
+ } // namespace detail
+
+ /**
+ * Reads code points from `r`, until a space, as determined by `is_space`,
+ * is found, and returns a `span` pointing to `r`.
+ *
+ * If no error occurs `r` is advanced past the returned span.
+ * On error, `r` is not advanced.
+ *
+ * \param r Range to read from
+ *
+ * \param is_space Predicate taking a span of code units encompassing a code
+ * point, and returning a `bool`, where `true` means that the character is a
+ * space. Additionally, it must have a member function
+ * `is_space.is_multibyte()`, returning a `bool`, where `true` means that a
+ * space character can encompass multiple code units.
+ *
+ * \param keep_final_space If `true`, the space code point found is included
+ * in the returned span, and it is advanced past in `r`. If `false`, it is
+ * not included, and `r.begin()` will point to the space.
+ *
+ * \return Span of code units, pointing to `r`, starting at `r.begin()`, and
+ * ending at the space character, the precise location determined by the
+ * `keep_final_space` parameter.
+ * If `r.begin() == r.end()`, returns EOF.
+ * `r` reaching its end before a space character is found is not considered
+ * an error.
+ * If `r` contains invalid encoding, returns `error::invalid_encoding`.
+ * If the range is not contiguous, returns an empty `span`.
+ */
+ template <typename WrappedRange, typename Predicate>
+ expected<span<const typename WrappedRange::char_type>>
+ read_until_space_zero_copy(WrappedRange& r,
+ Predicate&& is_space,
+ bool keep_final_space)
+ {
+ return detail::read_until_space_zero_copy_impl(
+ r, SCN_FWD(is_space), keep_final_space,
+ std::integral_constant<bool, WrappedRange::is_contiguous>{});
+ }
+
+ // read_until_space
+
+ namespace detail {
+ template <typename WrappedRange,
+ typename Predicate,
+ typename OutputIt,
+ typename OutputItCmp>
+ error read_until_pred_buffer(WrappedRange& r,
+ Predicate&& pred,
+ bool pred_result_to_stop,
+ OutputIt& out,
+ OutputItCmp out_cmp,
+ bool keep_final,
+ bool& done,
+ std::true_type)
+ {
+ if (!pred.is_multibyte()) {
+ while (r.begin() != r.end() && !done) {
+ auto s = r.get_buffer_and_advance();
+ for (auto it = s.begin(); it != s.end() && out_cmp(out);
+ ++it) {
+ if (pred(make_span(&*it, 1)) == pred_result_to_stop) {
+ if (keep_final) {
+ *out = *it;
+ ++out;
+ }
+ auto e =
+ putback_n(r, ranges::distance(it, s.end()));
+ if (!e) {
+ return e;
+ }
+ done = true;
+ break;
+ }
+ *out = *it;
+ ++out;
+ }
+ if (!done && out_cmp(out)) {
+ auto ret = read_code_unit(r, false);
+ if (!ret) {
+ if (ret.error() == error::end_of_range) {
+ return {};
+ }
+ return ret.error();
+ }
+ if (pred(make_span(&ret.value(), 1)) ==
+ pred_result_to_stop) {
+ if (keep_final) {
+ r.advance();
+ *out = ret.value();
+ ++out;
+ }
+ done = true;
+ break;
+ }
+ r.advance();
+ *out = ret.value();
+ ++out;
+ }
+ }
+ }
+ else {
+ while (r.begin() != r.end() && !done) {
+ auto s = r.get_buffer_and_advance();
+ for (auto it = s.begin(); it != s.end() && out_cmp(out);) {
+ auto len = ::scn::get_sequence_length(*it);
+ if (len == 0) {
+ return error{error::invalid_encoding,
+ "Invalid code point"};
+ }
+ if (ranges::distance(it, s.end()) < len) {
+ auto e = putback_n(r, len);
+ if (!e) {
+ return e;
+ }
+ break;
+ }
+ auto cpspan = make_span(it, static_cast<size_t>(len));
+ code_point cp{};
+ auto i =
+ parse_code_point(cpspan.begin(), cpspan.end(), cp);
+ if (!i) {
+ return i.error();
+ }
+ if (i.value() != cpspan.end()) {
+ return error{error::invalid_encoding,
+ "Invalid code point"};
+ }
+ if (pred(cpspan) == pred_result_to_stop) {
+ if (keep_final) {
+ out = std::copy(cpspan.begin(), cpspan.end(),
+ out);
+ }
+ done = true;
+ break;
+ }
+ out = std::copy(cpspan.begin(), cpspan.end(), out);
+ }
+
+ if (!done && out_cmp(out)) {
+ alignas(typename WrappedRange::char_type) unsigned char
+ buf[4] = {0};
+ auto cpret = read_code_point(r, make_span(buf, 4));
+ if (!cpret) {
+ if (cpret.error() == error::end_of_range) {
+ return {};
+ }
+ return cpret.error();
+ }
+ if (pred(cpret.value().chars) == pred_result_to_stop) {
+ if (keep_final) {
+ out = std::copy(cpret.value().chars.begin(),
+ cpret.value().chars.end(), out);
+ }
+ else {
+ return putback_n(r,
+ cpret.value().chars.ssize());
+ }
+ done = true;
+ break;
+ }
+ out = std::copy(cpret.value().chars.begin(),
+ cpret.value().chars.end(), out);
+ }
+ }
+ }
+ return {};
+ }
+ template <typename WrappedRange,
+ typename Predicate,
+ typename OutputIt,
+ typename OutputItCmp>
+ error read_until_pred_buffer(WrappedRange&,
+ Predicate&&,
+ bool,
+ OutputIt&,
+ OutputItCmp,
+ bool,
+ bool& done,
+ std::false_type)
+ {
+ done = false;
+ return {};
+ }
+
+ template <typename WrappedRange,
+ typename Predicate,
+ typename OutputIt,
+ typename OutputItCmp>
+ error read_until_pred_non_contiguous(WrappedRange& r,
+ Predicate&& pred,
+ bool pred_result_to_stop,
+ OutputIt& out,
+ OutputItCmp out_cmp,
+ bool keep_final)
+ {
+ if (r.begin() == r.end()) {
+ return {error::end_of_range, "EOF"};
+ }
+
+ {
+ bool done = false;
+ auto e = read_until_pred_buffer(
+ r, pred, pred_result_to_stop, out, out_cmp, keep_final,
+ done,
+ std::integral_constant<
+ bool, WrappedRange::provides_buffer_access>{});
+ if (!e) {
+ return e;
+ }
+ if (done) {
+ return {};
+ }
+ }
+
+ if (!pred.is_multibyte()) {
+ while (r.begin() != r.end() && out_cmp(out)) {
+ auto cu = read_code_unit(r, false);
+ if (!cu) {
+ return cu.error();
+ }
+ if (pred(make_span(&cu.value(), 1)) ==
+ pred_result_to_stop) {
+ if (keep_final) {
+ r.advance();
+ *out = cu.value();
+ ++out;
+ }
+ return {};
+ }
+ r.advance();
+ *out = cu.value();
+ ++out;
+ }
+ }
+ else {
+ unsigned char buf[4] = {0};
+ while (r.begin() != r.end() && out_cmp(out)) {
+ auto cp = read_code_point(r, make_span(buf, 4));
+ if (!cp) {
+ return cp.error();
+ }
+ if (pred(cp.value().chars) == pred_result_to_stop) {
+ if (keep_final) {
+ out = std::copy(cp.value().chars.begin(),
+ cp.value().chars.end(), out);
+ return {};
+ }
+ else {
+ return putback_n(r, cp.value().chars.ssize());
+ }
+ }
+ out = std::copy(cp.value().chars.begin(),
+ cp.value().chars.end(), out);
+ }
+ }
+ return {};
+ }
+ } // namespace detail
+
+ /// @{
+
+ /**
+ * Reads code points from `r`, until a space, as determined by `is_space`,
+ * is found, and writes them into `out`, a single code unit at a time.
+ *
+ * If no error occurs, `r` is advanced past the last character written into
+ * `out`.
+ *
+ * On error, `r` is advanced an indeterminate amount, as if by calling
+ * `r.advance(n)`, where `n` is a non-negative integer.
+ * It is, however, not advanced past any space characters.
+ *
+ * \param r Range to read from
+ *
+ * \param out Iterator to write read characters into. Must satisfy
+ * `output_iterator`.
+ *
+ * \param is_space Predicate taking a span of code units encompassing a code
+ * point, and returning a `bool`, where `true` means that the character is a
+ * space. Additionally, it must have a member function
+ * `is_space.is_multibyte()`, returning a `bool`, where `true` means that a
+ * space character can encompass multiple code units.
+ *
+ * \param keep_final_space If `true`, the space code point found is written
+ * into `out`, and it is advanced past in `r`. If `false`, it is not
+ * included, and `r.begin()` will point to the space.
+ *
+ * \return `error::good` on success.
+ * If `r.begin() == r.end()`, returns EOF.
+ * `r` reaching its end before a space character is found is not considered
+ * an error.
+ * If `r` contains invalid encoding, returns `error::invalid_encoding`.
+ */
+ template <
+ typename WrappedRange,
+ typename OutputIterator,
+ typename Predicate,
+ typename std::enable_if<WrappedRange::is_contiguous>::type* = nullptr>
+ error read_until_space(WrappedRange& r,
+ OutputIterator& out,
+ Predicate&& is_space,
+ bool keep_final_space)
+ {
+ auto s =
+ read_until_space_zero_copy(r, SCN_FWD(is_space), keep_final_space);
+ if (!s) {
+ return s.error();
+ }
+ out = std::copy(s.value().begin(), s.value().end(), out);
+ return {};
+ }
+ template <
+ typename WrappedRange,
+ typename OutputIterator,
+ typename Predicate,
+ typename std::enable_if<!WrappedRange::is_contiguous>::type* = nullptr>
+ error read_until_space(WrappedRange& r,
+ OutputIterator& out,
+ Predicate&& is_space,
+ bool keep_final_space)
+ {
+ return detail::read_until_pred_non_contiguous(
+ r, SCN_FWD(is_space), true, out,
+ [](const OutputIterator&) { return true; }, keep_final_space);
+ }
+
+ /// @}
+
+ // read_until_space_ranged
+
+ /// @{
+
+ /**
+ * Otherwise equivalent to `read_until_space`, except will also stop reading
+ * if `out == end`.
+ *
+ * \see read_until_space
+ */
+ template <typename WrappedRange,
+ typename OutputIterator,
+ typename Sentinel,
+ typename Predicate>
+ error read_until_space_ranged(WrappedRange& r,
+ OutputIterator& out,
+ Sentinel end,
+ Predicate&& is_space,
+ bool keep_final_space)
+ {
+ return detail::read_until_pred_non_contiguous(
+ r, SCN_FWD(is_space), true, out,
+ [&end](const OutputIterator& it) { return it != end; },
+ keep_final_space);
+ }
+
+ /// @}
+
+ namespace detail {
+ /**
+ * Predicate to pass to read_until_space etc.
+ */
+ template <typename CharT>
+ struct is_space_predicate {
+ using char_type = CharT;
+ using locale_type = basic_locale_ref<char_type>;
+
+ /**
+ * \param l Locale to use, fetched from `ctx.locale()`
+ * \param localized If `true`, use `l.get_custom()`, otherwise use
+ * `l.get_static()`.
+ * \param width If `width != 0`, limit the number of code
+ * units to be read
+ */
+ SCN_CONSTEXPR14 is_space_predicate(const locale_type& l,
+ bool localized,
+ size_t width)
+ : m_locale{nullptr},
+ m_width{width},
+ m_fn{get_fn(localized, width != 0)}
+ {
+ if (localized) {
+ l.prepare_localized();
+ m_locale = l.get_localized_unsafe();
+ }
+ }
+
+ /**
+ * Returns `true` if `ch` is a code point according to the supplied
+ * locale, using either the static or custom locale, depending on
+ * the `localized` parameter given to the constructor.
+ *
+ * Returns also `true` if the maximum width, as determined by the
+ * `width` parameter given to the constructor, was reached.
+ */
+ bool operator()(span<const char_type> ch)
+ {
+ SCN_EXPECT(m_fn);
+ SCN_EXPECT(ch.size() >= 1);
+ return m_fn(m_locale, ch, m_i, m_width);
+ }
+
+ /**
+ * Returns `true`, if `*this` uses the custom locale for classifying
+ * space characters
+ */
+ constexpr bool is_localized() const
+ {
+ return m_locale != nullptr;
+ }
+ /**
+ * Returns `true` if a space character can encompass multiple code
+ * units
+ */
+ constexpr bool is_multibyte() const
+ {
+ return is_localized() && is_multichar_type(CharT{});
+ }
+
+ private:
+ using static_locale_type = typename locale_type::static_type;
+ using custom_locale_type = typename locale_type::custom_type;
+ const custom_locale_type* m_locale;
+ size_t m_width{0}, m_i{0};
+
+ constexpr static bool call(const custom_locale_type*,
+ span<const char_type> ch,
+ size_t&,
+ size_t)
+ {
+ return static_locale_type::is_space(ch);
+ }
+ static bool localized_call(const custom_locale_type* locale,
+ span<const char_type> ch,
+ size_t&,
+ size_t)
+ {
+ SCN_EXPECT(locale != nullptr);
+ return locale->is_space(ch);
+ }
+ SCN_CONSTEXPR14 static bool call_counting(const custom_locale_type*,
+ span<const char_type> ch,
+ size_t& i,
+ size_t max)
+ {
+ SCN_EXPECT(i <= max);
+ if (i == max || i + ch.size() > max) {
+ return true;
+ }
+ i += ch.size();
+ return static_locale_type::is_space(ch);
+ }
+ static bool localized_call_counting(
+ const custom_locale_type* locale,
+ span<const char_type> ch,
+ size_t& i,
+ size_t max)
+ {
+ SCN_EXPECT(locale != nullptr);
+ SCN_EXPECT(i <= max);
+ if (i == max || i + ch.size() > max) {
+ return true;
+ }
+ i += ch.size();
+ return locale->is_space(ch);
+ }
+
+ using fn_type = bool (*)(const custom_locale_type*,
+ span<const char_type>,
+ size_t&,
+ size_t);
+ fn_type m_fn{nullptr};
+
+ static SCN_CONSTEXPR14 fn_type get_fn(bool localized, bool counting)
+ {
+ if (localized) {
+ return counting ? localized_call_counting : localized_call;
+ }
+ return counting ? call_counting : call;
+ }
+ };
+
+ template <typename CharT>
+ is_space_predicate<CharT> make_is_space_predicate(
+ const basic_locale_ref<CharT>& locale,
+ bool localized,
+ size_t width = 0)
+ {
+ return {locale, localized, width};
+ }
+
+ template <typename CharT>
+ struct basic_skipws_iterator {
+ using value_type = void;
+ using reference = void;
+ using pointer = void;
+ using size_type = size_t;
+ using difference_type = std::ptrdiff_t;
+ using iterator_category = std::output_iterator_tag;
+
+ constexpr basic_skipws_iterator() = default;
+
+ basic_skipws_iterator& operator=(CharT)
+ {
+ return *this;
+ }
+ basic_skipws_iterator& operator*()
+ {
+ return *this;
+ }
+ basic_skipws_iterator& operator++()
+ {
+ return *this;
+ }
+ };
+ } // namespace detail
+
+ // skip_range_whitespace
+
+ /// @{
+
+ /**
+ * Reads code points from `ctx.range()`, as if by repeatedly calling
+ * `read_code_point()`, until a non-space character is found, or EOF is
+ * reached. That non-space character is then put back into the range.
+ *
+ * Whether a character is a space, is determined by `ctx.locale()` and the
+ * `localized` parameter.
+ *
+ * \param ctx Context to get the range and locale from.
+ *
+ * \param localized If `true`, `ctx.locale().get_custom()` is used.
+ * Otherwise, `ctx.locale().get_static()` is used.
+ * In practice, means whether locale-specific whitespace characters are
+ * accepted, or just those given by `std::isspace` with the `"C"` locale.
+ *
+ * \return `error::good` on success.
+ * If `ctx.range().begin() == ctx.range().end()`, returns EOF.
+ * If `ctx.range()` contains invalid encoding, returns
+ * `error::invalid_encoding`.
+ */
+ template <typename Context,
+ typename std::enable_if<
+ !Context::range_type::is_contiguous>::type* = nullptr>
+ error skip_range_whitespace(Context& ctx, bool localized) noexcept
+ {
+ auto is_space_pred =
+ detail::make_is_space_predicate(ctx.locale(), localized);
+ auto it = detail::basic_skipws_iterator<typename Context::char_type>{};
+ return detail::read_until_pred_non_contiguous(
+ ctx.range(), is_space_pred, false, it,
+ [](decltype(it)) { return true; }, false);
+ }
+ template <typename Context,
+ typename std::enable_if<
+ Context::range_type::is_contiguous>::type* = nullptr>
+ error skip_range_whitespace(Context& ctx, bool localized) noexcept
+ {
+ auto is_space_pred =
+ detail::make_is_space_predicate(ctx.locale(), localized);
+ return detail::read_until_pred_contiguous(ctx.range(), is_space_pred,
+ false, false)
+ .error();
+ }
+
+ /// @}
+
+ namespace detail {
+ template <typename T>
+ struct simple_integer_scanner {
+ template <typename CharT>
+ static expected<typename span<const CharT>::iterator> scan(
+ span<const CharT> buf,
+ T& val,
+ int base = 10,
+ uint16_t flags = 0);
+
+ template <typename CharT>
+ static expected<typename span<const CharT>::iterator> scan_lower(
+ span<const CharT> buf,
+ T& val,
+ int base = 10,
+ uint16_t flags = 0);
+ };
+ } // namespace detail
+
+ /**
+ * A very simple parser base class, which only accepts empty format string
+ * specifiers, e.g. `{}`, `{:}` or `{1:}`.
+ */
+ struct empty_parser : parser_base {
+ template <typename ParseCtx>
+ error parse(ParseCtx& pctx)
+ {
+ pctx.arg_begin();
+ if (SCN_UNLIKELY(!pctx)) {
+ return {error::invalid_format_string,
+ "Unexpected format string end"};
+ }
+ if (!pctx.check_arg_end()) {
+ return {error::invalid_format_string, "Expected argument end"};
+ }
+ pctx.arg_end();
+ return {};
+ }
+ };
+
+ /**
+ * Provides a framework for building a format string parser.
+ * Does not provide a `parse()` member function, so not a parser on to its
+ * own.
+ */
+ struct common_parser : parser_base {
+ static constexpr bool support_align_and_fill()
+ {
+ return true;
+ }
+
+ protected:
+ /**
+ * Parse the beginning of the argument.
+ * Returns `error::invalid_format_string` if `!pctx` (the format string
+ * ended)
+ */
+ template <typename ParseCtx>
+ error parse_common_begin(ParseCtx& pctx)
+ {
+ pctx.arg_begin();
+ if (SCN_UNLIKELY(!pctx)) {
+ return {error::invalid_format_string,
+ "Unexpected format string end"};
+ }
+ return {};
+ }
+
+ /**
+ * Returns `error::invalid_format_string` if the format string or the
+ * argument has ended.
+ */
+ template <typename ParseCtx>
+ error check_end(ParseCtx& pctx)
+ {
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument"};
+ }
+ return {};
+ }
+
+ /**
+ * Parse alignment, fill, width, and localization flags, and populate
+ * appropriate member variables.
+ *
+ * Returns `error::invalid_format_string` if an error occurred.
+ */
+ template <typename ParseCtx>
+ error parse_common_flags(ParseCtx& pctx)
+ {
+ SCN_EXPECT(check_end(pctx));
+ using char_type = typename ParseCtx::char_type;
+
+ auto ch = pctx.next_char();
+ auto next_char = [&]() -> error {
+ pctx.advance_char();
+ auto e = check_end(pctx);
+ if (!e) {
+ return e;
+ }
+ ch = pctx.next_char();
+ return {};
+ };
+ auto parse_number = [&](size_t& n) -> error {
+ SCN_EXPECT(pctx.locale().get_static().is_digit(ch));
+
+ auto it = pctx.begin();
+ for (; it != pctx.end(); ++it) {
+ if (!pctx.locale().get_static().is_digit(*it)) {
+ break;
+ }
+ }
+ auto buf = make_span(pctx.begin(), it);
+
+ auto s = detail::simple_integer_scanner<size_t>{};
+ auto res = s.scan(buf, n, 10);
+ if (!res) {
+ return res.error();
+ }
+
+ for (it = pctx.begin(); it != res.value();
+ pctx.advance_char(), it = pctx.begin()) {}
+ return {};
+ };
+
+ auto get_align_char = [&](char_type c) -> common_options_type {
+ if (c == detail::ascii_widen<char_type>('<')) {
+ return aligned_left;
+ }
+ if (c == detail::ascii_widen<char_type>('>')) {
+ return aligned_right;
+ }
+ if (c == detail::ascii_widen<char_type>('^')) {
+ return aligned_center;
+ }
+ return common_options_none;
+ };
+ auto parse_align = [&](common_options_type align, char_type fill) {
+ if (align != common_options_none) {
+ common_options |= align;
+ }
+ fill_char = static_cast<char32_t>(fill);
+ };
+
+ // align and fill
+ common_options_type align{};
+ bool align_set = false;
+ if (pctx.chars_left() > 1 &&
+ ch != detail::ascii_widen<char_type>('[')) {
+ const auto peek = pctx.peek_char();
+ align = get_align_char(peek);
+ if (align != common_options_none) {
+ // Arg is like "{:_x}", where _ is some fill character, and
+ // x is an alignment flag
+ // -> we have both alignment and fill
+ parse_align(align, ch);
+
+ auto e = next_char();
+ SCN_ENSURE(e);
+ if (!next_char()) {
+ return {};
+ }
+ align_set = true;
+ }
+ }
+ if (!align_set) {
+ align = get_align_char(ch);
+ if (align != common_options_none) {
+ // Arg is like "{:x}", where x is an alignment flag
+ // -> we have alignment with default fill (space ' ')
+ parse_align(align, detail::ascii_widen<char_type>(' '));
+ if (!next_char()) {
+ return {};
+ }
+ }
+ }
+
+ // digit -> width
+ if (pctx.locale().get_static().is_digit(ch)) {
+ common_options |= width_set;
+
+ size_t w{};
+ auto e = parse_number(w);
+ if (!e) {
+ return e;
+ }
+ field_width = w;
+ return {};
+ }
+ // L -> localized
+ if (ch == detail::ascii_widen<char_type>('L')) {
+ common_options |= localized;
+
+ if (!next_char()) {
+ return {};
+ }
+ }
+
+ return {};
+ }
+
+ /**
+ * Parse argument end.
+ *
+ * Returns `error::invalid_format_string` if argument end was not found.
+ */
+ template <typename ParseCtx>
+ error parse_common_end(ParseCtx& pctx)
+ {
+ if (!pctx || !pctx.check_arg_end()) {
+ return {error::invalid_format_string, "Expected argument end"};
+ }
+
+ pctx.arg_end();
+ return {};
+ }
+
+ /**
+ * A null callback to pass to `parse_common`, doing nothing and
+ * returning `error::good`.
+ */
+ template <typename ParseCtx>
+ static error null_type_cb(ParseCtx&, bool&)
+ {
+ return {};
+ }
+
+ public:
+ /**
+ * Parse a format string argument, using `parse_common_begin`,
+ * `parse_common_flags`, `parse_common_end`, and the supplied type
+ * flags.
+ *
+ * `type_options.size() == type_flags.size()` must be `true`.
+ * `pctx` must be valid, and must start at the format string argument
+ * specifiers, e.g. in the case of `"{1:foo}"` -> `pctx == "foo}"`
+ *
+ * \param pctx Format string to parse
+ * \param type_options A span of characters, where each character
+ * corresponds to a valid type flag. For example, for characters, this
+ * span would be \c ['c']
+ * \param type_flags A span of bools, where the values will be set to
+ * `true`, if a corresponding type flag from `type_options` was found.
+ * Should be initialized to all-`false`, as a `false` value will not be
+ * written.
+ * \param type_cb A callback to call, if none of the `type_options`
+ * matched. Must have the signature `(ParseCtx& pctx, bool& parsed) ->
+ * error`., where `parsed` is set to `true`, if the flag at
+ * `pctx.next_char()` was parsed and advanced past.
+ */
+ template <typename ParseCtx,
+ typename F,
+ typename CharT = typename ParseCtx::char_type>
+ error parse_common(ParseCtx& pctx,
+ span<const CharT> type_options,
+ span<bool> type_flags,
+ F&& type_cb)
+ {
+ SCN_EXPECT(type_options.size() == type_flags.size());
+
+ auto e = parse_common_begin(pctx);
+ if (!e) {
+ return e;
+ }
+
+ if (!pctx) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string"};
+ }
+ if (pctx.check_arg_end()) {
+ return {};
+ }
+
+ e = parse_common_flags(pctx);
+ if (!e) {
+ return e;
+ }
+
+ if (!pctx) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string"};
+ }
+ if (pctx.check_arg_end()) {
+ return {};
+ }
+
+ for (auto ch = pctx.next_char(); pctx && !pctx.check_arg_end();
+ ch = pctx.next_char()) {
+ bool parsed = false;
+ for (std::size_t i = 0; i < type_options.size() && !parsed;
+ ++i) {
+ if (ch == type_options[i]) {
+ if (SCN_UNLIKELY(type_flags[i])) {
+ return {error::invalid_format_string,
+ "Repeat flag in format string"};
+ }
+ type_flags[i] = true;
+ parsed = true;
+ }
+ }
+ if (parsed) {
+ pctx.advance_char();
+ if (!pctx || pctx.check_arg_end()) {
+ break;
+ }
+ continue;
+ }
+
+ e = type_cb(pctx, parsed);
+ if (!e) {
+ return e;
+ }
+ if (parsed) {
+ if (!pctx || pctx.check_arg_end()) {
+ break;
+ }
+ continue;
+ }
+ ch = pctx.next_char();
+
+ if (!parsed) {
+ return {error::invalid_format_string,
+ "Invalid character in format string"};
+ }
+ if (!pctx || pctx.check_arg_end()) {
+ break;
+ }
+ }
+
+ return parse_common_end(pctx);
+ }
+
+ void make_localized()
+ {
+ common_options |= localized;
+ }
+
+ /**
+ * Invoke `parse_common()` with default options (no type flags)
+ */
+ template <typename ParseCtx>
+ error parse_default(ParseCtx& pctx)
+ {
+ return parse_common(pctx, {}, {}, null_type_cb<ParseCtx>);
+ }
+
+ constexpr bool is_aligned_left() const noexcept
+ {
+ return (common_options & aligned_left) != 0 ||
+ (common_options & aligned_center) != 0;
+ }
+ constexpr bool is_aligned_right() const noexcept
+ {
+ return (common_options & aligned_right) != 0 ||
+ (common_options & aligned_center) != 0;
+ }
+ template <typename CharT>
+ constexpr CharT get_fill_char() const noexcept
+ {
+ return static_cast<CharT>(fill_char);
+ }
+
+ size_t field_width{0};
+ char32_t fill_char{0};
+ enum common_options_type : uint8_t {
+ common_options_none = 0,
+ localized = 1, // 'L',
+ aligned_left = 2, // '<'
+ aligned_right = 4, // '>'
+ aligned_center = 8, // '^'
+ width_set = 16, // width
+ common_options_all = 31,
+ };
+ uint8_t common_options{0};
+ };
+
+ /**
+ * Derives from `common_parser`, and implements `parse()` with
+ * `parse_default()`
+ */
+ struct common_parser_default : common_parser {
+ template <typename ParseCtx>
+ error parse(ParseCtx& pctx)
+ {
+ return parse_default(pctx);
+ }
+ };
+
+ namespace detail {
+ template <typename Context,
+ typename std::enable_if<
+ !Context::range_type::is_contiguous>::type* = nullptr>
+ error scan_alignment(Context& ctx,
+ typename Context::char_type fill) noexcept
+ {
+ while (true) {
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+
+ auto ch = read_code_unit(ctx.range());
+ if (SCN_UNLIKELY(!ch)) {
+ return ch.error();
+ }
+ if (ch.value() != fill) {
+ auto pb = putback_n(ctx.range(), 1);
+ if (SCN_UNLIKELY(!pb)) {
+ return pb;
+ }
+ break;
+ }
+
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+ return {};
+ }
+ template <typename Context,
+ typename std::enable_if<
+ Context::range_type::is_contiguous>::type* = nullptr>
+ error scan_alignment(Context& ctx,
+ typename Context::char_type fill) noexcept
+ {
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ const auto end = ctx.range().end();
+ for (auto it = ctx.range().begin(); it != end; ++it) {
+ if (*it != fill) {
+ ctx.range().advance_to(it);
+ return {};
+ }
+ }
+ ctx.range().advance_to(end);
+ return {};
+
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+
+ template <typename Scanner, typename = void>
+ struct scanner_supports_alignment : std::false_type {
+ };
+ template <typename Scanner>
+ struct scanner_supports_alignment<
+ Scanner,
+ typename std::enable_if<Scanner::support_align_and_fill()>::type>
+ : std::true_type {
+ };
+
+ template <typename Context, typename Scanner>
+ error skip_alignment(Context& ctx,
+ Scanner& scanner,
+ bool left,
+ std::true_type)
+ {
+ if (left && !scanner.is_aligned_left()) {
+ return {};
+ }
+ if (!left && !scanner.is_aligned_right()) {
+ return {};
+ }
+ return scan_alignment(
+ ctx,
+ scanner.template get_fill_char<typename Context::char_type>());
+ }
+ template <typename Context, typename Scanner>
+ error skip_alignment(Context&, Scanner&, bool, std::false_type)
+ {
+ return {};
+ }
+
+ /**
+ * Scan argument in `val`, from `ctx`, using `Scanner` and `pctx`.
+ *
+ * Parses `pctx` for `Scanner`, skips whitespace and alignment if
+ * necessary, and scans the argument into `val`.
+ */
+ template <typename Scanner,
+ typename T,
+ typename Context,
+ typename ParseCtx>
+ error visitor_boilerplate(T& val, Context& ctx, ParseCtx& pctx)
+ {
+ Scanner scanner;
+
+ auto err = pctx.parse(scanner);
+ if (!err) {
+ return err;
+ }
+
+ if (scanner.skip_preceding_whitespace()) {
+ err = skip_range_whitespace(ctx, false);
+ if (!err) {
+ return err;
+ }
+ }
+
+ err = skip_alignment(ctx, scanner, false,
+ scanner_supports_alignment<Scanner>{});
+ if (!err) {
+ return err;
+ }
+
+ err = scanner.scan(val, ctx);
+ if (!err) {
+ return err;
+ }
+
+ return skip_alignment(ctx, scanner, true,
+ scanner_supports_alignment<Scanner>{});
+ }
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/reader/float.h b/src/third-party/scnlib/include/scn/reader/float.h
new file mode 100644
index 0000000..24265a1
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/reader/float.h
@@ -0,0 +1,246 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_READER_FLOAT_H
+#define SCN_READER_FLOAT_H
+
+#include "../util/small_vector.h"
+#include "common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+ namespace detail {
+ template <typename T>
+ struct float_scanner_access;
+
+ template <typename T>
+ struct float_scanner : common_parser {
+ static_assert(std::is_floating_point<T>::value,
+ "float_scanner requires a floating point type");
+
+ friend struct float_scanner_access<T>;
+
+ template <typename ParseCtx>
+ error parse(ParseCtx& pctx)
+ {
+ using char_type = typename ParseCtx::char_type;
+
+ array<char_type, 10> options{
+ {// hex
+ ascii_widen<char_type>('a'), ascii_widen<char_type>('A'),
+ // scientific
+ ascii_widen<char_type>('e'), ascii_widen<char_type>('E'),
+ // fixed
+ ascii_widen<char_type>('f'), ascii_widen<char_type>('F'),
+ // general
+ ascii_widen<char_type>('g'), ascii_widen<char_type>('G'),
+ // localized digits
+ ascii_widen<char_type>('n'),
+ // thsep
+ ascii_widen<char_type>('\'')}};
+ bool flags[10] = {false};
+
+ auto e = parse_common(
+ pctx, span<const char_type>{options.begin(), options.end()},
+ span<bool>{flags, 10}, null_type_cb<ParseCtx>);
+ if (!e) {
+ return e;
+ }
+
+ if (flags[0] && flags[1]) {
+ return {error::invalid_format_string,
+ "Can't have both 'a' and 'A' flags with floats"};
+ }
+ if (flags[2] && flags[3]) {
+ return {error::invalid_format_string,
+ "Can't have both 'e' and 'E' flags with floats"};
+ }
+ if (flags[4] && flags[5]) {
+ return {error::invalid_format_string,
+ "Can't have both 'f' and 'F' flags with floats"};
+ }
+ if (flags[6] && flags[7]) {
+ return {error::invalid_format_string,
+ "Can't have both 'g' and 'G' flags with floats"};
+ }
+
+ bool set_hex = flags[0] || flags[1];
+ bool set_scientific = flags[2] || flags[3];
+ bool set_fixed = flags[4] || flags[5];
+ bool set_general = flags[6] || flags[7];
+ if (set_general && set_fixed) {
+ return {error::invalid_format_string,
+ "General float already implies fixed"};
+ }
+ if (set_general && set_scientific) {
+ return {error::invalid_format_string,
+ "General float already implies scientific"};
+ }
+
+ format_options = 0;
+ if (set_hex) {
+ format_options |= allow_hex;
+ }
+ if (set_scientific) {
+ format_options |= allow_scientific;
+ }
+ if (set_fixed) {
+ format_options |= allow_fixed;
+ }
+ if (set_general) {
+ format_options |= allow_fixed | allow_scientific;
+ }
+ if (format_options == 0) {
+ format_options |=
+ allow_fixed | allow_scientific | allow_hex;
+ }
+
+ // 'n'
+ if (flags[8]) {
+ common_options |= localized;
+ format_options |= localized_digits;
+ }
+
+ // thsep
+ if (flags[9]) {
+ format_options |= allow_thsep;
+ }
+
+ return {};
+ }
+
+ template <typename Context>
+ error scan(T& val, Context& ctx)
+ {
+ using char_type = typename Context::char_type;
+
+ auto do_parse_float = [&](span<const char_type> s) -> error {
+ T tmp = 0;
+ expected<std::ptrdiff_t> ret{0};
+ if (SCN_UNLIKELY((format_options & localized_digits) != 0 ||
+ ((common_options & localized) != 0 &&
+ (format_options & allow_hex) != 0))) {
+ // 'n' OR ('L' AND 'a')
+ // because none of our parsers support BOTH hexfloats
+ // and custom (localized) decimal points,
+ // so we have to fall back on iostreams
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ std::basic_string<char_type> str(s.data(), s.size());
+ ret =
+ ctx.locale().get_localized().read_num(tmp, str, 0);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+ else {
+ ret = _read_float(
+ tmp, s,
+ ctx.locale()
+ .get((common_options & localized) != 0)
+ .decimal_point());
+ }
+
+ if (!ret) {
+ return ret.error();
+ }
+ if (ret.value() != s.ssize()) {
+ auto pb =
+ putback_n(ctx.range(), s.ssize() - ret.value());
+ if (!pb) {
+ return pb;
+ }
+ }
+ val = tmp;
+ return {};
+ };
+
+ auto is_space_pred = make_is_space_predicate(
+ ctx.locale(), (common_options & localized) != 0,
+ field_width);
+
+ if (Context::range_type::is_contiguous) {
+ auto s = read_until_space_zero_copy(ctx.range(),
+ is_space_pred, false);
+ if (!s) {
+ return s.error();
+ }
+ return do_parse_float(s.value());
+ }
+
+ small_vector<char_type, 32> buf;
+ auto outputit = std::back_inserter(buf);
+ auto e = read_until_space(ctx.range(), outputit, is_space_pred,
+ false);
+ if (!e && buf.empty()) {
+ return e;
+ }
+
+ return do_parse_float(make_span(buf));
+ }
+
+ enum format_options_type {
+ allow_hex = 1,
+ allow_scientific = 2,
+ allow_fixed = 4,
+ localized_digits = 8,
+ allow_thsep = 16
+ };
+ uint8_t format_options{allow_hex | allow_scientific | allow_fixed};
+
+ private:
+ template <typename CharT>
+ expected<std::ptrdiff_t> _read_float(T& val,
+ span<const CharT> s,
+ CharT locale_decimal_point)
+ {
+ size_t chars{};
+ std::basic_string<CharT> str(s.data(), s.size());
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ auto ret =
+ _read_float_impl(str.data(), chars, locale_decimal_point);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ if (!ret) {
+ return ret.error();
+ }
+ val = ret.value();
+ return static_cast<std::ptrdiff_t>(chars);
+ }
+
+ template <typename CharT>
+ expected<T> _read_float_impl(const CharT* str,
+ size_t& chars,
+ CharT locale_decimal_point);
+ };
+
+ // instantiate
+ template struct float_scanner<float>;
+ template struct float_scanner<double>;
+ template struct float_scanner<long double>;
+
+ template <typename T>
+ struct float_scanner_access : public float_scanner<T> {
+ using float_scanner<T>::_read_float;
+ using float_scanner<T>::_read_float_impl;
+ };
+ } // namespace detail
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY && \
+ !defined(SCN_READER_FLOAT_CPP)
+#include "reader_float.cpp"
+#endif
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/reader/int.h b/src/third-party/scnlib/include/scn/reader/int.h
new file mode 100644
index 0000000..19bac44
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/reader/int.h
@@ -0,0 +1,537 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_READER_INT_H
+#define SCN_READER_INT_H
+
+#include "../util/math.h"
+#include "common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename T>
+ struct integer_scanner : common_parser {
+ static_assert(std::is_integral<T>::value,
+ "integer_scanner requires an integral type");
+
+ friend struct simple_integer_scanner<T>;
+
+ bool skip_preceding_whitespace()
+ {
+ // if format_options == single_code_unit,
+ // then we're scanning a char -> don't skip
+ return format_options != single_code_unit;
+ }
+
+ template <typename ParseCtx>
+ error parse(ParseCtx& pctx)
+ {
+ using char_type = typename ParseCtx::char_type;
+
+ format_options = 0;
+
+ int custom_base = 0;
+ auto each = [&](ParseCtx& p, bool& parsed) -> error {
+ parsed = false;
+ auto ch = pctx.next_char();
+
+ if (ch == detail::ascii_widen<char_type>('B')) {
+ // Custom base
+ p.advance_char();
+ if (SCN_UNLIKELY(!p)) {
+ return {error::invalid_format_string,
+ "Unexpected format string end"};
+ }
+ if (SCN_UNLIKELY(p.check_arg_end())) {
+ return {error::invalid_format_string,
+ "Unexpected argument end"};
+ }
+ ch = p.next_char();
+
+ const auto zero = detail::ascii_widen<char_type>('0'),
+ nine = detail::ascii_widen<char_type>('9');
+ integer_type_for_char<char_type> tmp = 0;
+ if (ch < zero || ch > nine) {
+ return {error::invalid_format_string,
+ "Invalid character after 'B', "
+ "expected digit"};
+ }
+ tmp = static_cast<integer_type_for_char<char_type>>(
+ p.next_char() - zero);
+ if (tmp < 1) {
+ return {error::invalid_format_string,
+ "Invalid base, must be between 2 and 36"};
+ }
+
+ p.advance_char();
+ if (!p) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string"};
+ }
+ if (p.check_arg_end()) {
+ custom_base = static_cast<uint8_t>(tmp);
+ parsed = true;
+ return {};
+ }
+ ch = p.next_char();
+
+ if (ch < zero || ch > nine) {
+ return {error::invalid_format_string,
+ "Invalid character after 'B', "
+ "expected digit"};
+ }
+ tmp *= 10;
+ tmp += static_cast<integer_type_for_char<char_type>>(
+ ch - zero);
+ if (tmp < 2 || tmp > 36) {
+ return {error::invalid_format_string,
+ "Invalid base, must be between 2 and 36"};
+ }
+ custom_base = static_cast<uint8_t>(tmp);
+ parsed = true;
+ pctx.advance_char();
+ return {};
+ }
+
+ return {};
+ };
+
+ array<char_type, 9> options{{// decimal
+ ascii_widen<char_type>('d'),
+ // binary
+ ascii_widen<char_type>('b'),
+ // octal
+ ascii_widen<char_type>('o'),
+ // hex
+ ascii_widen<char_type>('x'),
+ // detect base
+ ascii_widen<char_type>('i'),
+ // unsigned decimal
+ ascii_widen<char_type>('u'),
+ // code unit
+ ascii_widen<char_type>('c'),
+ // localized digits
+ ascii_widen<char_type>('n'),
+ // thsep
+ ascii_widen<char_type>('\'')}};
+ bool flags[9] = {false};
+
+ auto e = parse_common(
+ pctx, span<const char_type>{options.begin(), options.end()},
+ span<bool>{flags, 9}, each);
+ if (!e) {
+ return e;
+ }
+
+ int base_flags_set = int(flags[0]) + int(flags[1]) +
+ int(flags[2]) + int(flags[3]) +
+ int(flags[4]) + int(flags[5]) +
+ int(custom_base != 0);
+ if (SCN_UNLIKELY(base_flags_set > 1)) {
+ return {error::invalid_format_string,
+ "Up to one base flags ('d', 'i', 'u', 'b', 'o', "
+ "'x', 'B') allowed"};
+ }
+ else if (base_flags_set == 0) {
+ // Default:
+ // 'c' for CharT
+ // 'd' otherwise
+ if (std::is_same<T, typename ParseCtx::char_type>::value) {
+ format_options = single_code_unit;
+ }
+ else {
+ base = 10;
+ }
+ }
+ else if (custom_base != 0) {
+ // B__
+ base = static_cast<uint8_t>(custom_base);
+ }
+ else if (flags[0]) {
+ // 'd' flag
+ base = 10;
+ }
+ else if (flags[1]) {
+ // 'b' flag
+ base = 2;
+ format_options |= allow_base_prefix;
+ }
+ else if (flags[2]) {
+ // 'o' flag
+ base = 8;
+ format_options |= allow_base_prefix;
+ }
+ else if (flags[3]) {
+ // 'x' flag
+ base = 16;
+ format_options |= allow_base_prefix;
+ }
+ else if (flags[4]) {
+ // 'i' flag
+ base = 0;
+ }
+ else if (flags[5]) {
+ // 'u' flag
+ base = 10;
+ format_options |= only_unsigned;
+ }
+
+ // n set, implies L
+ if (flags[7]) {
+ common_options |= localized;
+ format_options |= localized_digits;
+ }
+ if ((format_options & localized_digits) != 0 &&
+ (base != 0 && base != 10 && base != 8 && base != 16)) {
+ return {error::invalid_format_string,
+ "Localized integers can only be scanned in "
+ "bases 8, 10 and 16"};
+ }
+
+ // thsep flag
+ if (flags[8]) {
+ format_options |= allow_thsep;
+ }
+
+ // 'c' flag -> no other options allowed
+ if (flags[6]) {
+ if (!(format_options == 0 ||
+ format_options == single_code_unit) ||
+ base_flags_set != 0) {
+ return {error::invalid_format_string,
+ "'c' flag cannot be used in conjunction with "
+ "any other flags"};
+ }
+ format_options = single_code_unit;
+ }
+
+ return {};
+ }
+
+ template <typename Context>
+ error scan(T& val, Context& ctx)
+ {
+ using char_type = typename Context::char_type;
+ auto do_parse_int = [&](span<const char_type> s) -> error {
+ T tmp = 0;
+ expected<std::ptrdiff_t> ret{0};
+ if (SCN_UNLIKELY((format_options & localized_digits) !=
+ 0)) {
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ int b{base};
+ auto r = parse_base_prefix<char_type>(s, b);
+ if (!r) {
+ return r.error();
+ }
+ if (b == -1) {
+ // -1 means we read a '0'
+ tmp = 0;
+ return {};
+ }
+ if (b != 10 && base != b && base != 0) {
+ return {error::invalid_scanned_value,
+ "Invalid base prefix"};
+ }
+ if (base == 0) {
+ base = static_cast<uint8_t>(b);
+ }
+ if (base != 8 && base != 10 && base != 16) {
+ return {error::invalid_scanned_value,
+ "Localized values have to be in base "
+ "8, 10 or 16"};
+ }
+
+ auto it = r.value();
+ std::basic_string<char_type> str(to_address(it),
+ s.size());
+ ret = ctx.locale().get_localized().read_num(
+ tmp, str, static_cast<int>(base));
+
+ if (tmp < T{0} &&
+ (format_options & only_unsigned) != 0) {
+ return {error::invalid_scanned_value,
+ "Parsed negative value when type was 'u'"};
+ }
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+ else {
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ ret = _parse_int(tmp, s);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+
+ if (!ret) {
+ return ret.error();
+ }
+ if (ret.value() != s.ssize()) {
+ auto pb =
+ putback_n(ctx.range(), s.ssize() - ret.value());
+ if (!pb) {
+ return pb;
+ }
+ }
+ val = tmp;
+ return {};
+ };
+
+ if (format_options == single_code_unit) {
+ SCN_MSVC_PUSH
+ SCN_MSVC_IGNORE(4127) // conditional expression is constant
+ if (sizeof(T) < sizeof(char_type)) {
+ // sizeof(char_type) > 1 -> wide range
+ // Code unit might not fit
+ return error{error::invalid_operation,
+ "Cannot read this type as a code unit "
+ "from a wide range"};
+ }
+ SCN_MSVC_POP
+ auto ch = read_code_unit(ctx.range());
+ if (!ch) {
+ return ch.error();
+ }
+ val = static_cast<T>(ch.value());
+ return {};
+ }
+
+ SCN_MSVC_PUSH
+ SCN_MSVC_IGNORE(4127) // conditional expression is constant
+ if ((std::is_same<T, char>::value ||
+ std::is_same<T, wchar_t>::value) &&
+ !std::is_same<T, char_type>::value) {
+ // T is a character type, but not char_type:
+ // Trying to read a char from a wide range, or wchar_t from
+ // a narrow one
+ // Reading a code unit is allowed, however
+ return error{error::invalid_operation,
+ "Cannot read a char from a wide range, or a "
+ "wchar_t from a narrow one"};
+ }
+ SCN_MSVC_POP
+
+ std::basic_string<char_type> buf{};
+ span<const char_type> bufspan{};
+ auto e = _read_source(
+ ctx, buf, bufspan,
+ std::integral_constant<
+ bool, Context::range_type::is_contiguous>{});
+ if (!e) {
+ return e;
+ }
+
+ return do_parse_int(bufspan);
+ }
+
+ enum format_options_type : uint8_t {
+ // "n" option -> localized digits and digit grouping
+ localized_digits = 1,
+ // "'" option -> accept thsep
+ // if "L" use locale, default=','
+ allow_thsep = 2,
+ // "u" option -> don't allow sign
+ only_unsigned = 4,
+ // Allow base prefix (e.g. 0B and 0x)
+ allow_base_prefix = 8,
+ // "c" option -> scan a code unit
+ single_code_unit = 16,
+ };
+ uint8_t format_options{default_format_options()};
+
+ // 0 = detect base
+ // Otherwise [2,36]
+ uint8_t base{0};
+
+ private:
+ static SCN_CONSTEXPR14 uint8_t default_format_options()
+ {
+ SCN_MSVC_PUSH
+ SCN_MSVC_IGNORE(4127) // conditional expression is constant
+ if (std::is_same<T, char>::value ||
+ std::is_same<T, wchar_t>::value) {
+ return single_code_unit;
+ }
+ return 0;
+ SCN_MSVC_POP
+ }
+
+ template <typename Context, typename Buf, typename CharT>
+ error _read_source(Context& ctx,
+ Buf& buf,
+ span<const CharT>& s,
+ std::false_type)
+ {
+ auto do_read = [&](Buf& b) -> error {
+ auto outputit = std::back_inserter(b);
+ auto is_space_pred = make_is_space_predicate(
+ ctx.locale(), (common_options & localized) != 0,
+ field_width);
+ auto e = read_until_space(ctx.range(), outputit,
+ is_space_pred, false);
+ if (!e && b.empty()) {
+ return e;
+ }
+
+ return {};
+ };
+
+ if (SCN_LIKELY((format_options & allow_thsep) == 0)) {
+ auto e = do_read(buf);
+ if (!e) {
+ return e;
+ }
+ s = make_span(buf.data(), buf.size());
+ return {};
+ }
+
+ Buf tmp;
+ auto e = do_read(tmp);
+ if (!e) {
+ return e;
+ }
+ auto thsep = ctx.locale()
+ .get((common_options & localized) != 0)
+ .thousands_separator();
+
+ auto it = tmp.begin();
+ for (; it != tmp.end(); ++it) {
+ if (*it == thsep) {
+ for (auto it2 = it; ++it2 != tmp.end();) {
+ *it++ = SCN_MOVE(*it2);
+ }
+ break;
+ }
+ }
+
+ auto n =
+ static_cast<std::size_t>(std::distance(tmp.begin(), it));
+ if (n == 0) {
+ return {error::invalid_scanned_value,
+ "Only a thousands separator found"};
+ }
+
+ buf = SCN_MOVE(tmp);
+ s = make_span(buf.data(), n);
+ return {};
+ }
+
+ template <typename Context, typename Buf, typename CharT>
+ error _read_source(Context& ctx,
+ Buf& buf,
+ span<const CharT>& s,
+ std::true_type)
+ {
+ if (SCN_UNLIKELY((format_options & allow_thsep) != 0)) {
+ return _read_source(ctx, buf, s, std::false_type{});
+ }
+ auto ret = read_zero_copy(
+ ctx.range(), field_width != 0
+ ? static_cast<std::ptrdiff_t>(field_width)
+ : ctx.range().size());
+ if (!ret) {
+ return ret.error();
+ }
+ s = ret.value();
+ return {};
+ }
+
+ template <typename CharT>
+ expected<typename span<const CharT>::iterator> parse_base_prefix(
+ span<const CharT> s,
+ int& b) const;
+
+ template <typename CharT>
+ expected<std::ptrdiff_t> _parse_int(T& val, span<const CharT> s);
+
+ template <typename CharT>
+ expected<typename span<const CharT>::iterator> _parse_int_impl(
+ T& val,
+ bool minus_sign,
+ span<const CharT> buf) const;
+ };
+
+ // instantiate
+ template struct integer_scanner<signed char>;
+ template struct integer_scanner<short>;
+ template struct integer_scanner<int>;
+ template struct integer_scanner<long>;
+ template struct integer_scanner<long long>;
+ template struct integer_scanner<unsigned char>;
+ template struct integer_scanner<unsigned short>;
+ template struct integer_scanner<unsigned int>;
+ template struct integer_scanner<unsigned long>;
+ template struct integer_scanner<unsigned long long>;
+ template struct integer_scanner<char>;
+ template struct integer_scanner<wchar_t>;
+
+ template <typename T>
+ template <typename CharT>
+ expected<typename span<const CharT>::iterator>
+ simple_integer_scanner<T>::scan(span<const CharT> buf,
+ T& val,
+ int base,
+ uint16_t flags)
+ {
+ SCN_EXPECT(buf.size() != 0);
+
+ integer_scanner<T> s{};
+ s.base = static_cast<uint8_t>(base);
+ s.format_options = flags & 0xffu;
+ s.common_options = static_cast<uint8_t>(flags >> 8u);
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ auto n = s._parse_int(val, buf);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ if (!n) {
+ return n.error();
+ }
+ return buf.begin() + n.value();
+ }
+ template <typename T>
+ template <typename CharT>
+ expected<typename span<const CharT>::iterator>
+ simple_integer_scanner<T>::scan_lower(span<const CharT> buf,
+ T& val,
+ int base,
+ uint16_t flags)
+ {
+ SCN_EXPECT(buf.size() != 0);
+ SCN_EXPECT(base > 0);
+
+ integer_scanner<T> s{};
+ s.base = static_cast<uint8_t>(base);
+ s.format_options = flags & 0xffu;
+ s.common_options = static_cast<uint8_t>(flags >> 8u);
+
+ bool minus_sign = false;
+ if (buf[0] == ascii_widen<CharT>('-')) {
+ buf = buf.subspan(1);
+ minus_sign = true;
+ }
+
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ return s._parse_int_impl(val, minus_sign, buf);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+ } // namespace detail
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY && !defined(SCN_READER_INT_CPP)
+#include "reader_int.cpp"
+#endif
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/reader/reader.h b/src/third-party/scnlib/include/scn/reader/reader.h
new file mode 100644
index 0000000..cd955ce
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/reader/reader.h
@@ -0,0 +1,111 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_READER_READER_H
+#define SCN_READER_READER_H
+
+#include "common.h"
+#include "float.h"
+#include "int.h"
+#include "string.h"
+#include "types.h"
+
+#include "../detail/args.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ template <>
+ struct scanner<code_point> : public detail::code_point_scanner {
+ };
+ template <>
+ struct scanner<bool> : public detail::bool_scanner {
+ };
+ template <>
+ struct scanner<char> : public detail::integer_scanner<char> {
+ };
+ template <>
+ struct scanner<wchar_t> : public detail::integer_scanner<wchar_t> {
+ };
+ template <>
+ struct scanner<signed char> : public detail::integer_scanner<signed char> {
+ };
+ template <>
+ struct scanner<short> : public detail::integer_scanner<short> {
+ };
+ template <>
+ struct scanner<int> : public detail::integer_scanner<int> {
+ };
+ template <>
+ struct scanner<long> : public detail::integer_scanner<long> {
+ };
+ template <>
+ struct scanner<long long> : public detail::integer_scanner<long long> {
+ };
+ template <>
+ struct scanner<unsigned char>
+ : public detail::integer_scanner<unsigned char> {
+ };
+ template <>
+ struct scanner<unsigned short>
+ : public detail::integer_scanner<unsigned short> {
+ };
+ template <>
+ struct scanner<unsigned int>
+ : public detail::integer_scanner<unsigned int> {
+ };
+ template <>
+ struct scanner<unsigned long>
+ : public detail::integer_scanner<unsigned long> {
+ };
+ template <>
+ struct scanner<unsigned long long>
+ : public detail::integer_scanner<unsigned long long> {
+ };
+ template <>
+ struct scanner<float> : public detail::float_scanner<float> {
+ };
+ template <>
+ struct scanner<double> : public detail::float_scanner<double> {
+ };
+ template <>
+ struct scanner<long double> : public detail::float_scanner<long double> {
+ };
+ template <typename CharT, typename Allocator>
+ struct scanner<std::basic_string<CharT, std::char_traits<CharT>, Allocator>>
+ : public detail::string_scanner {
+ };
+ template <typename CharT>
+ struct scanner<span<CharT>> : public detail::span_scanner {
+ };
+ template <typename CharT>
+ struct scanner<basic_string_view<CharT>>
+ : public detail::string_view_scanner {
+ };
+#if SCN_HAS_STRING_VIEW
+ template <typename CharT>
+ struct scanner<std::basic_string_view<CharT>>
+ : public detail::std_string_view_scanner {
+ };
+#endif
+ template <>
+ struct scanner<detail::monostate>;
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/reader/string.h b/src/third-party/scnlib/include/scn/reader/string.h
new file mode 100644
index 0000000..19727ee
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/reader/string.h
@@ -0,0 +1,1336 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_READER_STRING_H
+#define SCN_READER_STRING_H
+
+#include "../util/small_vector.h"
+#include "common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+ namespace detail {
+ class set_parser_type {
+ public:
+ constexpr set_parser_type() = default;
+
+ template <typename ParseCtx>
+ error parse_set(ParseCtx& pctx, bool& parsed)
+ {
+ using char_type = typename ParseCtx::char_type;
+ SCN_EXPECT(pctx.next_char() == ascii_widen<char_type>('['));
+
+ pctx.advance_char();
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument"};
+ }
+
+ get_option(flag::enabled) = true;
+ parsed = true;
+
+ if (pctx.next_char() == ascii_widen<char_type>('^')) {
+ // inverted
+ get_option(flag::inverted) = true;
+ pctx.advance_char();
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument"};
+ }
+ }
+
+ if (pctx.next_char() == ascii_widen<char_type>(']')) {
+ // end of range
+ get_option(flag::accept_all) = true;
+ pctx.advance_char();
+ return {};
+ }
+
+ while (true) {
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument"};
+ }
+
+ const auto ch = pctx.next_char();
+ if (ch == ascii_widen<char_type>(']')) {
+ break;
+ }
+
+ auto err = parse_next_char(pctx, true);
+ if (!err) {
+ return err;
+ }
+
+ err = pctx.advance_cp();
+ if (!err) {
+ pctx.advance_char();
+ }
+ }
+ auto err = pctx.advance_cp();
+ if (!err) {
+ pctx.advance_char();
+ }
+
+ return {};
+ }
+
+ error sanitize(bool localized)
+ {
+ // specifiers -> chars, if not localized
+ if (get_option(flag::use_specifiers)) {
+ if ((get_option(specifier::letters) ||
+ get_option(specifier::alpha)) &&
+ get_option(specifier::inverted_letters)) {
+ get_option(flag::accept_all) = true;
+ }
+ if (get_option(specifier::alnum_underscore) &&
+ get_option(specifier::inverted_alnum_underscore)) {
+ get_option(flag::accept_all) = true;
+ }
+ if ((get_option(specifier::whitespace) ||
+ get_option(specifier::space)) &&
+ get_option(specifier::inverted_whitespace)) {
+ get_option(flag::accept_all) = true;
+ }
+ if ((get_option(specifier::numbers) ||
+ get_option(specifier::digit)) &&
+ get_option(specifier::inverted_numbers)) {
+ get_option(flag::accept_all) = true;
+ }
+ }
+
+ if (get_option(flag::use_specifiers) &&
+ !get_option(flag::accept_all)) {
+ if (localized) {
+ if (get_option(specifier::letters)) {
+ get_option(specifier::letters) = false;
+ get_option(specifier::alpha) = true;
+ }
+ if (get_option(specifier::alnum_underscore)) {
+ get_option(specifier::alnum_underscore) = false;
+ get_option(specifier::alnum) = true;
+ get_option('_') = true;
+ }
+ if (get_option(specifier::whitespace)) {
+ get_option(specifier::whitespace) = false;
+ get_option(specifier::space) = true;
+ }
+ if (get_option(specifier::numbers)) {
+ get_option(specifier::numbers) = false;
+ get_option(specifier::digit) = true;
+ }
+ }
+ else {
+ auto do_range = [&](char a, char b) {
+ for (; a < b; ++a) {
+ get_option(a) = true;
+ }
+ get_option(b) = true;
+ };
+ auto do_lower = [&]() {
+ // a-z
+ do_range(0x61, 0x7a);
+ };
+ auto do_upper = [&]() {
+ // A-Z
+ do_range(0x41, 0x5a);
+ };
+ auto do_digit = [&]() {
+ // 0-9
+ do_range(0x30, 0x39);
+ };
+
+ if (get_option(specifier::alnum)) {
+ do_lower();
+ do_upper();
+ do_digit();
+ get_option(specifier::alnum) = false;
+ }
+ if (get_option(specifier::alpha)) {
+ do_lower();
+ do_upper();
+ get_option(specifier::alpha) = false;
+ }
+ if (get_option(specifier::blank)) {
+ get_option(' ') = true;
+ get_option('\t') = true;
+ get_option(specifier::blank) = false;
+ }
+ if (get_option(specifier::cntrl)) {
+ do_range(0, 0x1f);
+ get_option(0x7f) = true;
+ get_option(specifier::cntrl) = false;
+ }
+ if (get_option(specifier::digit)) {
+ do_digit();
+ get_option(specifier::digit) = false;
+ }
+ if (get_option(specifier::graph)) {
+ do_range(0x21, 0x7e);
+ get_option(specifier::graph) = false;
+ }
+ if (get_option(specifier::lower)) {
+ do_lower();
+ get_option(specifier::lower) = false;
+ }
+ if (get_option(specifier::print)) {
+ do_range(0x20, 0x7e);
+ get_option(specifier::print) = false;
+ }
+ if (get_option(specifier::punct)) {
+ do_range(0x21, 0x2f);
+ do_range(0x3a, 0x40);
+ do_range(0x5b, 0x60);
+ do_range(0x7b, 0x7e);
+ get_option(specifier::punct) = false;
+ }
+ if (get_option(specifier::space)) {
+ do_range(0x9, 0xd);
+ get_option(' ') = true;
+ get_option(specifier::space) = false;
+ }
+ if (get_option(specifier::upper)) {
+ do_upper();
+ get_option(specifier::upper) = false;
+ }
+ if (get_option(specifier::xdigit)) {
+ do_digit();
+ do_range(0x41, 0x46);
+ do_range(0x61, 0x66);
+ get_option(specifier::xdigit) = false;
+ }
+ if (get_option(specifier::letters)) {
+ do_upper();
+ do_lower();
+ get_option(specifier::letters) = false;
+ }
+ if (get_option(specifier::inverted_letters)) {
+ do_range(0x0, 0x2f);
+ do_range(0x3a, 0x40);
+ do_range(0x5b, 0x60);
+ do_range(0x7b, 0x7f);
+ get_option(specifier::inverted_letters) = false;
+ }
+ if (get_option(specifier::alnum_underscore)) {
+ do_digit();
+ do_upper();
+ do_lower();
+ get_option('_') = true;
+ get_option(specifier::alnum_underscore) = false;
+ }
+ if (get_option(specifier::inverted_alnum_underscore)) {
+ bool underscore = get_option('_');
+ do_range(0x0, 0x2f);
+ do_range(0x3a, 0x40);
+ do_range(0x5b, 0x60);
+ do_range(0x7b, 0x7f);
+ get_option('_') = underscore; // reset back
+ get_option(specifier::inverted_alnum_underscore) =
+ false;
+ }
+ if (get_option(specifier::whitespace)) {
+ do_range(0x9, 0xd);
+ get_option(' ') = true;
+ get_option(specifier::whitespace) = false;
+ }
+ if (get_option(specifier::inverted_whitespace)) {
+ do_range(0, 0x8);
+ do_range(0xe, 0x1f);
+ do_range(0x21, 0x7f);
+ get_option(specifier::inverted_whitespace) = false;
+ }
+ if (get_option(specifier::numbers)) {
+ do_digit();
+ get_option(specifier::numbers) = false;
+ }
+ if (get_option(specifier::inverted_numbers)) {
+ do_range(0, 0x2f);
+ do_range(0x3a, 0x7f);
+ get_option(specifier::inverted_numbers) = false;
+ }
+
+ {
+ bool first = get_option(0);
+ char i = 1;
+ for (; i < 0x7f; ++i) {
+ if (first != get_option(i)) {
+ break;
+ }
+ }
+ if (i == 0x7f && first == get_option(0x7f)) {
+ get_option(flag::accept_all) = true;
+ if (!first) {
+ get_option(flag::inverted) = true;
+ }
+ }
+ }
+
+ get_option(flag::use_specifiers) = false;
+ get_option(flag::use_chars) = true;
+ }
+ }
+
+ return {};
+ }
+
+ // true = char accepted
+ template <typename CharT, typename Locale>
+ bool check_character(CharT ch, bool localized, const Locale& loc)
+ {
+ SCN_EXPECT(get_option(flag::enabled));
+
+ const bool not_inverted = !get_option(flag::inverted);
+ if (get_option(flag::accept_all)) {
+ return not_inverted;
+ }
+
+ if (get_option(flag::use_specifiers)) {
+ SCN_EXPECT(localized); // ensured by sanitize()
+ SCN_UNUSED(localized);
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ if (get_option(specifier::alnum) &&
+ loc.get_localized().is_alnum(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::alpha) &&
+ loc.get_localized().is_alpha(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::blank) &&
+ loc.get_localized().is_blank(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::cntrl) &&
+ loc.get_localized().is_cntrl(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::digit) &&
+ loc.get_localized().is_digit(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::graph) &&
+ loc.get_localized().is_graph(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::lower) &&
+ loc.get_localized().is_lower(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::print) &&
+ loc.get_localized().is_print(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::punct) &&
+ loc.get_localized().is_punct(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::space) &&
+ loc.get_localized().is_space(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::upper) &&
+ loc.get_localized().is_upper(ch)) {
+ return not_inverted;
+ }
+ if (get_option(specifier::xdigit) &&
+ loc.get_localized().is_xdigit(ch)) {
+ return not_inverted;
+ }
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+ if (get_option(flag::use_chars) && (ch >= 0 && ch <= 0x7f)) {
+ if (get_option(static_cast<char>(ch))) {
+ return not_inverted;
+ }
+ }
+ if (get_option(flag::use_ranges)) {
+ const auto c = static_cast<uint32_t>(ch);
+ for (const auto& e : set_extra_ranges) {
+ if (c >= e.begin && c <= e.end) {
+ return not_inverted;
+ }
+ }
+ }
+ return !not_inverted;
+ }
+
+ enum class specifier : size_t {
+ alnum = 0x80,
+ alpha,
+ blank,
+ cntrl,
+ digit,
+ graph,
+ lower,
+ print,
+ punct,
+ space,
+ upper,
+ xdigit,
+ letters = 0x90, // \l
+ inverted_letters, // \L
+ alnum_underscore, // \w
+ inverted_alnum_underscore, // \W
+ whitespace, // \s
+ inverted_whitespace, // \S
+ numbers, // \d
+ inverted_numbers, // \D
+ last = 0x9f
+ };
+ enum class flag : size_t {
+ enabled = 0xa0, // using [set]
+ accept_all, // empty [set]
+ inverted, // ^ flag
+ // 0x00 - 0x7f
+ use_chars,
+ // 0x80 - 0x8f
+ use_specifiers,
+ // set_extra_ranges
+ use_ranges,
+ last = 0xaf
+ };
+
+ bool& get_option(char ch)
+ {
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wtype-limits")
+ SCN_EXPECT(ch >= 0 && ch <= 0x7f);
+ SCN_GCC_POP
+ return set_options[static_cast<size_t>(ch)];
+ }
+ SCN_NODISCARD bool get_option(char ch) const
+ {
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wtype-limits")
+ SCN_EXPECT(ch >= 0 && ch <= 0x7f);
+ SCN_GCC_POP
+ return set_options[static_cast<size_t>(ch)];
+ }
+
+ bool& get_option(specifier s)
+ {
+ return set_options[static_cast<size_t>(s)];
+ }
+ SCN_NODISCARD bool get_option(specifier s) const
+ {
+ return set_options[static_cast<size_t>(s)];
+ }
+
+ bool& get_option(flag f)
+ {
+ return set_options[static_cast<size_t>(f)];
+ }
+ SCN_NODISCARD bool get_option(flag f) const
+ {
+ return set_options[static_cast<size_t>(f)];
+ }
+
+ SCN_NODISCARD bool enabled() const
+ {
+ return get_option(flag::enabled);
+ }
+
+ private:
+ void accept_char(char ch)
+ {
+ get_option(ch) = true;
+ get_option(flag::use_chars) = true;
+ }
+ void accept_char(code_point cp)
+ {
+ if (cp >= 0 && cp <= 0x7f) {
+ return accept_char(static_cast<char>(cp));
+ }
+ set_extra_ranges.push_back(set_range::single(cp));
+ get_option(flag::use_ranges) = true;
+ }
+ void accept_char(wchar_t ch)
+ {
+ SCN_GCC_COMPAT_PUSH
+ SCN_GCC_COMPAT_IGNORE("-Wtype-limits")
+ if (ch >= 0 && ch <= 0x7f) {
+ return accept_char(static_cast<char>(ch));
+ }
+ SCN_GCC_COMPAT_POP
+ set_extra_ranges.push_back(set_range::single(ch));
+ get_option(flag::use_ranges) = true;
+ }
+
+ void accept_char_range(char first, char last)
+ {
+ SCN_EXPECT(first >= 0);
+ SCN_EXPECT(last >= 0);
+ SCN_EXPECT(first <= last);
+ get_option(flag::use_chars) = true;
+ for (; first != last; ++first) {
+ get_option(first) = true;
+ }
+ SCN_ENSURE(first == last);
+ get_option(last) = true;
+ }
+ void accept_char_range(code_point first, code_point last)
+ {
+ SCN_EXPECT(first <= last);
+ if (first >= 0 && last <= 0x7f) {
+ return accept_char_range(static_cast<char>(first),
+ static_cast<char>(last));
+ }
+ set_extra_ranges.push_back(set_range::range(first, last));
+ get_option(flag::use_ranges) = true;
+ }
+ void accept_char_range(wchar_t first, wchar_t last)
+ {
+ SCN_EXPECT(first <= last);
+ SCN_GCC_COMPAT_PUSH
+ SCN_GCC_COMPAT_IGNORE("-Wtype-limits")
+ if (first >= 0 && last <= 0x7f) {
+ return accept_char_range(static_cast<char>(first),
+ static_cast<char>(last));
+ }
+ SCN_GCC_COMPAT_POP
+ set_extra_ranges.push_back(set_range::range(first, last));
+ get_option(flag::use_ranges) = true;
+ }
+
+ template <typename ParseCtx>
+ error parse_range(ParseCtx& pctx, code_point begin)
+ {
+ using char_type = typename ParseCtx::char_type;
+ SCN_EXPECT(pctx.next_char() == ascii_widen<char_type>('-'));
+ if (pctx.can_peek_char() &&
+ pctx.peek_char() == ascii_widen<char_type>(']')) {
+ // Just a '-'
+ accept_char(begin);
+ accept_char(ascii_widen<char_type>('-'));
+ return {};
+ }
+ pctx.advance_char();
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument"};
+ }
+ return parse_next_char(pctx, false, begin);
+ }
+ template <typename ParseCtx>
+ error parse_literal(ParseCtx& pctx,
+ bool allow_range,
+ code_point begin = make_code_point(0))
+ {
+ using char_type = typename ParseCtx::char_type;
+ if (allow_range) {
+ auto e = pctx.peek_cp();
+ if (!e && e.error().code() != error::end_of_range) {
+ return e.error();
+ }
+ if (e && e.value() == ascii_widen<char_type>('-')) {
+ const auto cp = pctx.next_cp();
+ if (!cp) {
+ return cp.error();
+ }
+ auto err = pctx.advance_cp();
+ if (!err) {
+ return err;
+ }
+ return parse_range(pctx, cp.value());
+ }
+ }
+ const auto cp = pctx.next_cp();
+ if (!cp) {
+ return cp.error();
+ }
+ if (cp.value() >= 0 && cp.value() <= 0x7f) {
+ if (!allow_range) {
+ if (static_cast<
+ typename std::make_unsigned<char_type>::type>(
+ cp.value()) <
+ static_cast<
+ typename std::make_unsigned<char_type>::type>(
+ begin)) {
+ return {error::invalid_format_string,
+ "Last char in [set] range is less than the "
+ "first"};
+ }
+ accept_char_range(begin, cp.value());
+ }
+ else {
+ accept_char(cp.value());
+ }
+ }
+ else {
+ if (!allow_range) {
+ if (static_cast<
+ typename std::make_unsigned<char_type>::type>(
+ cp.value()) <
+ static_cast<
+ typename std::make_unsigned<char_type>::type>(
+ begin)) {
+ return {error::invalid_format_string,
+ "Last char in [set] range is less than the "
+ "first"};
+ }
+ set_extra_ranges.push_back(
+ set_range::range(begin, cp.value()));
+ }
+ else {
+ set_extra_ranges.push_back(
+ set_range::single(cp.value()));
+ }
+ get_option(flag::use_ranges) = true;
+ }
+ return {};
+ }
+ template <typename ParseCtx>
+ error parse_colon_specifier(ParseCtx& pctx)
+ {
+ using char_type = typename ParseCtx::char_type;
+ SCN_EXPECT(pctx.next_char() == ascii_widen<char_type>(':'));
+ pctx.advance_char();
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument"};
+ }
+ if (pctx.next_char() == ascii_widen<char_type>(']')) {
+ return {
+ error::invalid_format_string,
+ "Unexpected end of [set] in format string after ':'"};
+ }
+
+ std::basic_string<char_type> buf;
+ while (true) {
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument"};
+ }
+ auto ch = pctx.next_char();
+ if (ch == ascii_widen<char_type>(':')) {
+ break;
+ }
+ if (ch == ascii_widen<char_type>(']')) {
+ return {error::invalid_format_string,
+ "Unexpected end of [set] :specifier:, did you "
+ "forget a terminating colon?"};
+ }
+ buf.push_back(ch);
+ pctx.advance_char();
+ }
+
+ auto ch = pctx.next_char();
+ if (buf == all_str(ch)) {
+ get_option(flag::accept_all) = true;
+ return {};
+ }
+ if (buf == alnum_str(ch)) {
+ get_option(specifier::alnum) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == alpha_str(ch)) {
+ get_option(specifier::alpha) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == blank_str(ch)) {
+ get_option(specifier::blank) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == cntrl_str(ch)) {
+ get_option(specifier::cntrl) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == digit_str(ch)) {
+ get_option(specifier::digit) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == graph_str(ch)) {
+ get_option(specifier::graph) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == lower_str(ch)) {
+ get_option(specifier::lower) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == print_str(ch)) {
+ get_option(specifier::print) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == punct_str(ch)) {
+ get_option(specifier::punct) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == space_str(ch)) {
+ get_option(specifier::space) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == upper_str(ch)) {
+ get_option(specifier::upper) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (buf == xdigit_str(ch)) {
+ get_option(specifier::xdigit) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+
+ return {error::invalid_format_string,
+ "Invalid :specifier: in [set]"};
+ }
+ template <typename ParseCtx>
+ error parse_backslash_hex(ParseCtx& pctx,
+ bool allow_range,
+ code_point begin = make_code_point(0))
+ {
+ using char_type = typename ParseCtx::char_type;
+ SCN_EXPECT(pctx.next_char() == ascii_widen<char_type>('x') ||
+ pctx.next_char() == ascii_widen<char_type>('u') ||
+ pctx.next_char() == ascii_widen<char_type>('U'));
+
+ const char_type flag_char = pctx.next_char();
+ const int chars = [flag_char]() {
+ auto ch = static_cast<char>(flag_char);
+ if (ch == 'x') {
+ return 2;
+ }
+ if (ch == 'u') {
+ return 4;
+ }
+ if (ch == 'U') {
+ return 8;
+ }
+ SCN_ENSURE(false);
+ SCN_UNREACHABLE;
+ }();
+
+ char_type str[8] = {0};
+ for (int i = 0; i < chars; ++i) {
+ pctx.advance_char();
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument "
+ "after '\\x', '\\u', or '\\U'"};
+ }
+ if (pctx.next_char() == ascii_widen<char_type>(']')) {
+ return {error::invalid_format_string,
+ "Unexpected end of [set] in format string "
+ "after '\\x', '\\u', or '\\U'"};
+ }
+ str[i] = pctx.next_char();
+ }
+
+ auto scanner = simple_integer_scanner<uint64_t>{};
+ uint64_t i;
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ auto res = scanner.scan(
+ scn::make_span(str, static_cast<size_t>(chars)).as_const(),
+ i, 16);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ if (!res) {
+ return {error::invalid_format_string,
+ "Failed to parse '\\x', '\\u', or '\\U' flag in "
+ "format string"};
+ }
+ const uint64_t min = 0;
+ const uint64_t max = [chars]() {
+ if (chars == 2) {
+ // \x
+ return uint64_t{0x7f};
+ }
+ if (chars == 4) {
+ return uint64_t{0xffff};
+ }
+ if (chars == 8) {
+ return uint64_t{0xffffffff};
+ }
+ SCN_ENSURE(false);
+ SCN_UNREACHABLE;
+ }();
+ if (i < min || i > max) {
+ return {error::invalid_format_string,
+ "'\\x', '\\u', or '\\U' option in format string "
+ "out of range"};
+ }
+
+ if (allow_range && pctx.can_peek_char() &&
+ pctx.peek_char() == ascii_widen<char_type>('-')) {
+ pctx.advance_char();
+ return parse_range(pctx, make_code_point(i));
+ }
+ if (!allow_range) {
+ accept_char_range(begin, make_code_point(i));
+ }
+ else {
+ accept_char(make_code_point(i));
+ }
+ return {};
+ }
+ template <typename ParseCtx>
+ error parse_backslash_specifier(
+ ParseCtx& pctx,
+ bool allow_range,
+ code_point begin = make_code_point(0))
+ {
+ using char_type = typename ParseCtx::char_type;
+ SCN_EXPECT(pctx.next_char() == ascii_widen<char_type>('\\'));
+ pctx.advance_char();
+
+ if (!pctx || pctx.check_arg_end()) {
+ return {error::invalid_format_string,
+ "Unexpected end of format string argument"};
+ }
+ if (pctx.next_char() == ascii_widen<char_type>(']') &&
+ pctx.can_peek_char() &&
+ pctx.peek_char() == ascii_widen<char_type>('}')) {
+ return {error::invalid_format_string,
+ "Unexpected end of [set] in format string"};
+ }
+
+ if (pctx.next_char() == ascii_widen<char_type>('\\')) {
+ // Literal "\\"
+ accept_char(pctx.next_char());
+ return {};
+ }
+
+ // specifiers
+ if (pctx.next_char() == ascii_widen<char_type>('l')) {
+ // \l
+ get_option(specifier::letters) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (pctx.next_char() == ascii_widen<char_type>('L')) {
+ // \L
+ get_option(specifier::inverted_letters) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+
+ if (pctx.next_char() == ascii_widen<char_type>('w')) {
+ // \w
+ get_option(specifier::alnum_underscore) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (pctx.next_char() == ascii_widen<char_type>('W')) {
+ // \W
+ get_option(specifier::inverted_alnum_underscore) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+
+ if (pctx.next_char() == ascii_widen<char_type>('s')) {
+ // \s
+ get_option(specifier::whitespace) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (pctx.next_char() == ascii_widen<char_type>('S')) {
+ // \S
+ get_option(specifier::inverted_whitespace) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+
+ if (pctx.next_char() == ascii_widen<char_type>('d')) {
+ // \d
+ get_option(specifier::numbers) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+ if (pctx.next_char() == ascii_widen<char_type>('D')) {
+ // \D
+ get_option(specifier::inverted_numbers) = true;
+ get_option(flag::use_specifiers) = true;
+ return {};
+ }
+
+ if (pctx.next_char() == ascii_widen<char_type>('x') ||
+ pctx.next_char() == ascii_widen<char_type>('u') ||
+ pctx.next_char() == ascii_widen<char_type>('U')) {
+ // \x__, \u____, or \U________
+ return parse_backslash_hex(pctx, allow_range, begin);
+ }
+
+ // Literal, e.g. \: -> :
+ return parse_literal(pctx, true);
+ }
+ template <typename ParseCtx>
+ error parse_next_char(ParseCtx& pctx,
+ bool allow_range,
+ code_point begin = make_code_point(0))
+ {
+ using char_type = typename ParseCtx::char_type;
+ const auto ch = pctx.next_char();
+ if (ch == ascii_widen<char_type>('\\')) {
+ return parse_backslash_specifier(pctx, allow_range, begin);
+ }
+ if (allow_range && ch == ascii_widen<char_type>(':')) {
+ return parse_colon_specifier(pctx);
+ }
+ return parse_literal(pctx, allow_range, begin);
+ }
+
+ SCN_NODISCARD static constexpr const char* all_str(char)
+ {
+ return "all";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* all_str(wchar_t)
+ {
+ return L"all";
+ }
+ SCN_NODISCARD static constexpr const char* alnum_str(char)
+ {
+ return "alnum";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* alnum_str(wchar_t)
+ {
+ return L"alnum";
+ }
+ SCN_NODISCARD static constexpr const char* alpha_str(char)
+ {
+ return "alpha";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* alpha_str(wchar_t)
+ {
+ return L"alpha";
+ }
+ SCN_NODISCARD static constexpr const char* blank_str(char)
+ {
+ return "blank";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* blank_str(wchar_t)
+ {
+ return L"blank";
+ }
+ SCN_NODISCARD static constexpr const char* cntrl_str(char)
+ {
+ return "cntrl";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* cntrl_str(wchar_t)
+ {
+ return L"cntrl";
+ }
+ SCN_NODISCARD static constexpr const char* digit_str(char)
+ {
+ return "digit";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* digit_str(wchar_t)
+ {
+ return L"digit";
+ }
+ SCN_NODISCARD static constexpr const char* graph_str(char)
+ {
+ return "graph";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* graph_str(wchar_t)
+ {
+ return L"graph";
+ }
+ SCN_NODISCARD static constexpr const char* lower_str(char)
+ {
+ return "lower";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* lower_str(wchar_t)
+ {
+ return L"lower";
+ }
+ SCN_NODISCARD static constexpr const char* print_str(char)
+ {
+ return "print";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* print_str(wchar_t)
+ {
+ return L"print";
+ }
+ SCN_NODISCARD static constexpr const char* punct_str(char)
+ {
+ return "punct";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* punct_str(wchar_t)
+ {
+ return L"punct";
+ }
+ SCN_NODISCARD static constexpr const char* space_str(char)
+ {
+ return "space";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* space_str(wchar_t)
+ {
+ return L"space";
+ }
+ SCN_NODISCARD static constexpr const char* upper_str(char)
+ {
+ return "upper";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* upper_str(wchar_t)
+ {
+ return L"upper";
+ }
+ SCN_NODISCARD static constexpr const char* xdigit_str(char)
+ {
+ return "xdigit";
+ }
+ SCN_NODISCARD static constexpr const wchar_t* xdigit_str(wchar_t)
+ {
+ return L"xdigit";
+ }
+
+ // 0x00 - 0x7f, individual chars, true = accept
+ // 0x80 - 0x9f, specifiers, true = accept (if use_specifiers = true)
+ // 0xa0 - 0xaf, flags
+ array<bool, 0xb0> set_options{{false}};
+
+ struct set_range {
+ constexpr set_range(uint32_t b, uint32_t e) : begin(b), end(e)
+ {
+ }
+
+ uint32_t begin{};
+ uint32_t end{}; // inclusive
+
+ static set_range single(code_point cp)
+ {
+ return {static_cast<uint32_t>(cp),
+ static_cast<uint32_t>(cp)};
+ }
+ static set_range single(wchar_t ch)
+ {
+ return {static_cast<uint32_t>(ch),
+ static_cast<uint32_t>(ch)};
+ }
+
+ static set_range range(code_point begin, code_point end)
+ {
+ SCN_EXPECT(begin <= end);
+ return {static_cast<uint32_t>(begin),
+ static_cast<uint32_t>(end)};
+ }
+ static set_range range(wchar_t begin, wchar_t end)
+ {
+ SCN_EXPECT(begin <= end);
+ return {static_cast<uint32_t>(begin),
+ static_cast<uint32_t>(end)};
+ }
+ };
+ // Used if set_options[use_ranges] = true
+ small_vector<set_range, 1> set_extra_ranges{};
+ };
+
+ struct string_scanner : common_parser {
+ static constexpr bool skip_preceding_whitespace()
+ {
+ return false;
+ }
+
+ template <typename ParseCtx>
+ error parse(ParseCtx& pctx)
+ {
+ using char_type = typename ParseCtx::char_type;
+
+ auto s_flag = detail::ascii_widen<char_type>('s');
+ bool s_set{};
+
+ auto each = [&](ParseCtx& p, bool& parsed) -> error {
+ if (p.next_char() == ascii_widen<char_type>('[')) {
+ if (set_parser.get_option(
+ set_parser_type::flag::enabled)) {
+ return {error::invalid_format_string,
+ "[set] already specified for this argument "
+ "in format string"};
+ }
+ return set_parser.parse_set(p, parsed);
+ }
+ return {};
+ };
+ auto e = parse_common(pctx, span<const char_type>{&s_flag, 1},
+ span<bool>{&s_set, 1}, each);
+ if (!e) {
+ return e;
+ }
+ if (set_parser.enabled()) {
+ bool loc = (common_options & localized) != 0;
+ return set_parser.sanitize(loc);
+ }
+ return {};
+ }
+
+ template <typename Context, typename Allocator>
+ error scan(
+ std::basic_string<typename Context::char_type,
+ std::char_traits<typename Context::char_type>,
+ Allocator>& val,
+ Context& ctx)
+ {
+ if (set_parser.enabled()) {
+ bool loc = (common_options & localized) != 0;
+ bool mb = (loc || set_parser.get_option(
+ set_parser_type::flag::use_ranges)) &&
+ is_multichar_type(typename Context::char_type{});
+ return do_scan(ctx, val,
+ pred<Context>{ctx, set_parser, loc, mb});
+ }
+
+ auto e = skip_range_whitespace(ctx, false);
+ if (!e) {
+ return e;
+ }
+
+ auto is_space_pred = make_is_space_predicate(
+ ctx.locale(), (common_options & localized) != 0,
+ field_width);
+ return do_scan(ctx, val, is_space_pred);
+ }
+
+ set_parser_type set_parser;
+
+ protected:
+ template <typename Context, typename Allocator, typename Pred>
+ error do_scan(
+ Context& ctx,
+ std::basic_string<typename Context::char_type,
+ std::char_traits<typename Context::char_type>,
+ Allocator>& val,
+ Pred&& predicate)
+ {
+ using string_type = std::basic_string<
+ typename Context::char_type,
+ std::char_traits<typename Context::char_type>, Allocator>;
+
+ if (Context::range_type::is_contiguous) {
+ auto s = read_until_space_zero_copy(
+ ctx.range(), SCN_FWD(predicate), false);
+ if (!s) {
+ return s.error();
+ }
+ if (s.value().size() == 0) {
+ return {error::invalid_scanned_value,
+ "Empty string parsed"};
+ }
+ val.assign(s.value().data(), s.value().size());
+ return {};
+ }
+
+ string_type tmp(val.get_allocator());
+ auto outputit = std::back_inserter(tmp);
+ auto ret = read_until_space(ctx.range(), outputit,
+ SCN_FWD(predicate), false);
+ if (SCN_UNLIKELY(!ret)) {
+ return ret;
+ }
+ if (SCN_UNLIKELY(tmp.empty())) {
+ return {error::invalid_scanned_value,
+ "Empty string parsed"};
+ }
+ val = SCN_MOVE(tmp);
+
+ return {};
+ }
+
+ template <typename Context>
+ struct pred {
+ Context& ctx;
+ set_parser_type& set_parser;
+ bool localized;
+ bool multibyte;
+
+ bool operator()(span<const char> ch) const
+ {
+ SCN_EXPECT(ch.size() >= 1);
+ code_point cp{};
+ auto it = parse_code_point(ch.begin(), ch.end(), cp);
+ if (!it) {
+ // todo: is this really a good idea
+ return !set_parser.check_character(ch[0], localized,
+ ctx.locale());
+ }
+ return !set_parser.check_character(cp, localized,
+ ctx.locale());
+ }
+ bool operator()(span<const wchar_t> ch) const
+ {
+ SCN_EXPECT(ch.size() == 1);
+ return !set_parser.check_character(ch[0], localized,
+ ctx.locale());
+ }
+ constexpr bool is_localized() const
+ {
+ return localized;
+ }
+ constexpr bool is_multibyte() const
+ {
+ return multibyte;
+ }
+ };
+ };
+
+ struct span_scanner : public string_scanner {
+ template <typename Context>
+ error scan(span<typename Context::char_type>& val, Context& ctx)
+ {
+ if (val.size() == 0) {
+ return {error::invalid_scanned_value,
+ "Cannot scan into an empty span"};
+ }
+
+ if (set_parser.enabled()) {
+ bool loc = (common_options & localized) != 0;
+ bool mb = (loc || set_parser.get_option(
+ set_parser_type::flag::use_ranges)) &&
+ is_multichar_type(typename Context::char_type{});
+ return do_scan(ctx, val,
+ string_scanner::pred<Context>{
+ ctx, set_parser, loc, mb});
+ }
+
+ auto e = skip_range_whitespace(ctx, false);
+ if (!e) {
+ return e;
+ }
+
+ auto is_space_pred = make_is_space_predicate(
+ ctx.locale(), (common_options & localized) != 0,
+ field_width != 0 ? min(field_width, val.size())
+ : val.size());
+ return do_scan(ctx, val, is_space_pred);
+ }
+
+ protected:
+ template <typename Context, typename Pred>
+ error do_scan(Context& ctx,
+ span<typename Context::char_type>& val,
+ Pred&& predicate)
+ {
+ if (Context::range_type::is_contiguous) {
+ auto s = read_until_space_zero_copy(
+ ctx.range(), SCN_FWD(predicate), false);
+ if (!s) {
+ return s.error();
+ }
+ if (s.value().size() == 0) {
+ return {error::invalid_scanned_value,
+ "Empty string parsed"};
+ }
+ std::copy(s.value().begin(), s.value().end(), val.begin());
+ val = val.first(s.value().size());
+ return {};
+ }
+
+ std::basic_string<typename Context::char_type> tmp;
+ auto outputit = std::back_inserter(tmp);
+ auto ret = read_until_space(ctx.range(), outputit,
+ SCN_FWD(predicate), false);
+ if (SCN_UNLIKELY(!ret)) {
+ return ret;
+ }
+ if (SCN_UNLIKELY(tmp.empty())) {
+ return {error::invalid_scanned_value,
+ "Empty string parsed"};
+ }
+ std::copy(tmp.begin(), tmp.end(), val.begin());
+ val = val.first(tmp.size());
+
+ return {};
+ }
+ };
+
+ struct string_view_scanner : string_scanner {
+ public:
+ template <typename Context>
+ error scan(basic_string_view<typename Context::char_type>& val,
+ Context& ctx)
+ {
+ if (!Context::range_type::is_contiguous) {
+ return {error::invalid_operation,
+ "Cannot read a string_view from a "
+ "non-contiguous_range"};
+ }
+
+ if (set_parser.enabled()) {
+ bool loc = (common_options & localized) != 0;
+ bool mb = (loc || set_parser.get_option(
+ set_parser_type::flag::use_ranges)) &&
+ is_multichar_type(typename Context::char_type{});
+ return do_scan(ctx, val,
+ string_scanner::pred<Context>{
+ ctx, set_parser, loc, mb});
+ }
+
+ auto e = skip_range_whitespace(ctx, false);
+ if (!e) {
+ return e;
+ }
+
+ auto is_space_pred = make_is_space_predicate(
+ ctx.locale(), (common_options & localized) != 0,
+ field_width);
+ return do_scan(ctx, val, is_space_pred);
+ }
+
+ protected:
+ template <typename Context, typename Pred>
+ error do_scan(Context& ctx,
+ basic_string_view<typename Context::char_type>& val,
+ Pred&& predicate)
+ {
+ SCN_EXPECT(Context::range_type::is_contiguous);
+
+ auto s = read_until_space_zero_copy(ctx.range(),
+ SCN_FWD(predicate), false);
+ if (!s) {
+ return s.error();
+ }
+ if (s.value().size() == 0) {
+ return {error::invalid_scanned_value,
+ "Empty string parsed"};
+ }
+ val = basic_string_view<typename Context::char_type>(
+ s.value().data(), s.value().size());
+ return {};
+ }
+ };
+
+#if SCN_HAS_STRING_VIEW
+ struct std_string_view_scanner : string_view_scanner {
+ template <typename Context>
+ error scan(std::basic_string_view<typename Context::char_type>& val,
+ Context& ctx)
+ {
+ using char_type = typename Context::char_type;
+ auto sv =
+ ::scn::basic_string_view<char_type>(val.data(), val.size());
+ auto e = string_view_scanner::scan(sv, ctx);
+ if (e) {
+ val =
+ std::basic_string_view<char_type>(sv.data(), sv.size());
+ }
+ return e;
+ }
+ };
+#endif
+ } // namespace detail
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/reader/types.h b/src/third-party/scnlib/include/scn/reader/types.h
new file mode 100644
index 0000000..047d10d
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/reader/types.h
@@ -0,0 +1,220 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_READER_TYPES_H
+#define SCN_READER_TYPES_H
+
+#include "int.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+ namespace detail {
+ struct code_point_scanner : common_parser {
+ static constexpr bool skip_preceding_whitespace()
+ {
+ return false;
+ }
+
+ template <typename ParseCtx>
+ error parse(ParseCtx& pctx)
+ {
+ using char_type = typename ParseCtx::char_type;
+
+ auto c_flag = detail::ascii_widen<char_type>('c');
+ bool c_set{};
+ return parse_common(pctx, span<const char_type>{&c_flag, 1},
+ span<bool>{&c_set, 1},
+ null_type_cb<ParseCtx>);
+ }
+
+ template <typename Context>
+ error scan(code_point& val, Context& ctx)
+ {
+ unsigned char buf[4] = {0};
+ auto cp = read_code_point(ctx.range(), make_span(buf, 4));
+ if (!cp) {
+ return cp.error();
+ }
+ val = cp.value().cp;
+ return {};
+ }
+ };
+
+ struct bool_scanner : common_parser {
+ template <typename ParseCtx>
+ error parse(ParseCtx& pctx)
+ {
+ using char_type = typename ParseCtx::char_type;
+
+ array<char_type, 3> options{{
+ // Only strings
+ ascii_widen<char_type>('s'),
+ // Only ints
+ ascii_widen<char_type>('i'),
+ // Localized digits
+ ascii_widen<char_type>('n'),
+ }};
+ bool flags[3] = {false};
+ auto e = parse_common(
+ pctx, span<const char_type>{options.begin(), options.end()},
+ span<bool>{flags, 3}, null_type_cb<ParseCtx>);
+
+ if (!e) {
+ return e;
+ }
+
+ format_options = 0;
+ // default ('s' + 'i')
+ if (!flags[0] && !flags[1]) {
+ format_options |= allow_string | allow_int;
+ }
+ // 's'
+ if (flags[0]) {
+ format_options |= allow_string;
+ }
+ // 'i'
+ if (flags[1]) {
+ format_options |= allow_int;
+ }
+ // 'n'
+ if (flags[2]) {
+ format_options |= localized_digits;
+ // 'n' implies 'L'
+ common_options |= localized;
+ }
+ return {};
+ }
+
+ template <typename Context>
+ error scan(bool& val, Context& ctx)
+ {
+ using char_type = typename Context::char_type;
+
+ if ((format_options & allow_string) != 0) {
+ auto truename = ctx.locale().get_static().truename();
+ auto falsename = ctx.locale().get_static().falsename();
+ if ((common_options & localized) != 0) {
+ truename = ctx.locale().get_localized().truename();
+ falsename = ctx.locale().get_localized().falsename();
+ }
+ const auto max_len =
+ detail::max(truename.size(), falsename.size());
+ std::basic_string<char_type> buf;
+ buf.reserve(max_len);
+
+ auto tmp_it = std::back_inserter(buf);
+ auto is_space_pred = make_is_space_predicate(
+ ctx.locale(), (common_options & localized) != 0,
+ field_width);
+ auto e = read_until_space(ctx.range(), tmp_it,
+ is_space_pred, false);
+ if (!e) {
+ return e;
+ }
+
+ bool found = false;
+ if (buf.size() >= falsename.size()) {
+ if (std::equal(falsename.begin(), falsename.end(),
+ buf.begin())) {
+ val = false;
+ found = true;
+ }
+ }
+ if (!found && buf.size() >= truename.size()) {
+ if (std::equal(truename.begin(), truename.end(),
+ buf.begin())) {
+ val = true;
+ found = true;
+ }
+ }
+ if (found) {
+ return {};
+ }
+ else {
+ auto pb =
+ putback_n(ctx.range(),
+ static_cast<std::ptrdiff_t>(buf.size()));
+ if (!pb) {
+ return pb;
+ }
+ }
+ }
+
+ if ((format_options & allow_int) != 0) {
+ if ((format_options & localized_digits) != 0) {
+ int i{};
+ auto s = integer_scanner<int>{};
+ s.common_options = integer_scanner<int>::localized;
+ s.format_options =
+ integer_scanner<int>::only_unsigned |
+ integer_scanner<int>::localized_digits;
+ auto e = s.scan(i, ctx);
+ if (!e) {
+ return e;
+ }
+ if (SCN_UNLIKELY(i != 0 && i != 1)) {
+ return {
+ error::invalid_scanned_value,
+ "Scanned integral boolean not equal to 0 or 1"};
+ }
+ else if (i == 0) {
+ val = false;
+ }
+ else {
+ val = true;
+ }
+ return {};
+ }
+
+ unsigned char buf[4] = {0};
+ auto cp = read_code_point(ctx.range(), make_span(buf, 4));
+ if (!cp) {
+ return cp.error();
+ }
+ if (cp.value().cp == detail::ascii_widen<char_type>('0')) {
+ val = false;
+ return {};
+ }
+ if (cp.value().cp == detail::ascii_widen<char_type>('1')) {
+ val = true;
+ return {};
+ }
+ auto pb = putback_n(ctx.range(), cp.value().chars.ssize());
+ if (!pb) {
+ return pb;
+ }
+ }
+
+ return {error::invalid_scanned_value, "Couldn't scan bool"};
+ }
+
+ enum format_options_type {
+ // 's' option
+ allow_string = 1,
+ // 'i' option
+ allow_int = 2,
+ // 'n' option
+ localized_digits = 4
+ };
+ uint8_t format_options{allow_string | allow_int};
+ };
+
+ } // namespace detail
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/scan/common.h b/src/third-party/scnlib/include/scn/scan/common.h
new file mode 100644
index 0000000..ccdd825
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/scan/common.h
@@ -0,0 +1,131 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_SCAN_COMMON_H
+#define SCN_SCAN_COMMON_H
+
+#include "../detail/locale.h"
+#include "../detail/result.h"
+#include "../unicode/common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename CharT>
+ constexpr int to_format(int i)
+ {
+ return i;
+ }
+ template <typename T>
+ constexpr auto to_format(T&& f) -> decltype(string_view{SCN_FWD(f)})
+ {
+ return {SCN_FWD(f)};
+ }
+ template <typename T>
+ constexpr auto to_format(T&& f) -> decltype(wstring_view{SCN_FWD(f)})
+ {
+ return {SCN_FWD(f)};
+ }
+ template <typename CharT>
+ basic_string_view<CharT> to_format(const std::basic_string<CharT>& str)
+ {
+ return {str.data(), str.size()};
+ }
+
+ template <typename CharT>
+ struct until_pred {
+ array<CharT, 4> until;
+ size_t size;
+
+ constexpr until_pred(CharT ch) : until({{ch}}), size(1) {}
+ until_pred(code_point cp)
+ {
+ auto ret = encode_code_point(until.begin(), until.end(), cp);
+ SCN_ENSURE(ret);
+ size = ret.value() - until.begin();
+ }
+
+ SCN_CONSTEXPR14 bool operator()(span<const CharT> ch) const
+ {
+ if (ch.size() != size) {
+ return false;
+ }
+ for (size_t i = 0; i < ch.size(); ++i) {
+ if (ch[i] != until[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ static constexpr bool is_localized()
+ {
+ return false;
+ }
+ constexpr bool is_multibyte() const
+ {
+ return size != 1;
+ }
+ };
+
+ template <typename Error, typename Range>
+ using generic_scan_result_for_range = decltype(detail::wrap_result(
+ SCN_DECLVAL(Error),
+ SCN_DECLVAL(detail::range_tag<Range>),
+ SCN_DECLVAL(range_wrapper_for_t<Range>)));
+ template <typename Range>
+ using scan_result_for_range =
+ generic_scan_result_for_range<wrapped_error, Range>;
+ } // namespace detail
+
+ template <typename T>
+ struct discard_type {
+ discard_type() = default;
+ };
+
+ /**
+ * Scans an instance of `T`, but doesn't store it anywhere.
+ * Uses `scn::temp` internally, so the user doesn't have to bother.
+ *
+ * \code{.cpp}
+ * int i{};
+ * // 123 is discarded, 456 is read into `i`
+ * auto result = scn::scan("123 456", "{} {}", scn::discard<T>(), i);
+ * // result == true
+ * // i == 456
+ * \endcode
+ */
+ template <typename T>
+ discard_type<T>& discard()
+ {
+ return temp(discard_type<T>{})();
+ }
+
+ template <typename T>
+ struct scanner<discard_type<T>> : public scanner<T> {
+ template <typename Context>
+ error scan(discard_type<T>&, Context& ctx)
+ {
+ T tmp;
+ return scanner<T>::scan(tmp, ctx);
+ }
+ };
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/scan/getline.h b/src/third-party/scnlib/include/scn/scan/getline.h
new file mode 100644
index 0000000..c330969
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/scan/getline.h
@@ -0,0 +1,186 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_SCAN_GETLINE_H
+#define SCN_SCAN_GETLINE_H
+
+#include "common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename WrappedRange,
+ typename String,
+ typename Until,
+ typename CharT = typename WrappedRange::char_type>
+ error getline_impl(WrappedRange& r, String& str, Until until)
+ {
+ auto pred = until_pred<CharT>{until};
+ auto s = read_until_space_zero_copy(r, pred, true);
+ if (!s) {
+ return s.error();
+ }
+ if (s.value().size() != 0) {
+ auto size = s.value().size();
+ if (pred(s.value().last(1))) {
+ --size;
+ }
+ str.clear();
+ str.resize(size);
+ std::copy(s.value().begin(), s.value().begin() + size,
+ str.begin());
+ return {};
+ }
+
+ String tmp;
+ auto out = std::back_inserter(tmp);
+ auto e = read_until_space(r, out, pred, true);
+ if (!e) {
+ return e;
+ }
+ if (pred(span<const CharT>(&*(tmp.end() - 1), 1))) {
+ tmp.pop_back();
+ }
+ str = SCN_MOVE(tmp);
+ return {};
+ }
+ template <typename WrappedRange,
+ typename Until,
+ typename CharT = typename WrappedRange::char_type>
+ error getline_impl(WrappedRange& r,
+ basic_string_view<CharT>& str,
+ Until until)
+ {
+ static_assert(
+ WrappedRange::is_contiguous,
+ "Cannot getline a string_view from a non-contiguous range");
+ auto pred = until_pred<CharT>{until};
+ auto s = read_until_space_zero_copy(r, pred, true);
+ if (!s) {
+ return s.error();
+ }
+ SCN_ASSERT(s.value().size(), "");
+ auto size = s.value().size();
+ if (pred(s.value().last(1))) {
+ --size;
+ }
+ str = basic_string_view<CharT>{s.value().data(), size};
+ return {};
+ }
+#if SCN_HAS_STRING_VIEW
+ template <typename WrappedRange,
+ typename Until,
+ typename CharT = typename WrappedRange::char_type>
+ auto getline_impl(WrappedRange& r,
+ std::basic_string_view<CharT>& str,
+ Until until) -> error
+ {
+ auto sv = ::scn::basic_string_view<CharT>{};
+ auto ret = getline_impl(r, sv, until);
+ str = ::std::basic_string_view<CharT>{sv.data(), sv.size()};
+ return ret;
+ }
+#endif
+ } // namespace detail
+
+ /**
+ * Read the range in \c r into \c str until \c until is found.
+ * \c until will be skipped in parsing: it will not be pushed into \c
+ * str, and the returned range will go past it.
+ *
+ * If `str` is convertible to a `basic_string_view`:
+ * - And if `r` is a `contiguous_range`:
+ * - `str` is set to point inside `r` with the appropriate length
+ * - if not, returns an error
+ *
+ * Otherwise, clears `str` by calling `str.clear()`, and then reads the
+ * range into `str` as if by repeatedly calling \c str.push_back.
+ * `str.reserve()` is also required to be present.
+ *
+ * `Until` can either be the same as `r` character type (`char` or
+ * `wchar_t`), or `code_point`.
+ *
+ * \code{.cpp}
+ * auto source = "hello\nworld"
+ * std::string line;
+ * auto result = scn::getline(source, line, '\n');
+ * // line == "hello"
+ * // result.range() == "world"
+ *
+ * // Using the other overload
+ * result = scn::getline(result.range(), line);
+ * // line == "world"
+ * // result.empty() == true
+ * \endcode
+ */
+#if SCN_DOXYGEN
+ template <typename Range, typename String, typename Until>
+ auto getline(Range&& r, String& str, Until until)
+ -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Range, typename String, typename Until>
+ SCN_NODISCARD auto getline(Range&& r, String& str, Until until)
+ -> detail::scan_result_for_range<Range>
+ {
+ auto wrapped = wrap(SCN_FWD(r));
+ auto err = getline_impl(wrapped, str, until);
+ if (!err) {
+ auto e = wrapped.reset_to_rollback_point();
+ if (!e) {
+ err = e;
+ }
+ }
+ else {
+ wrapped.set_rollback_point();
+ }
+ return detail::wrap_result(
+ wrapped_error{err}, detail::range_tag<Range>{}, SCN_MOVE(wrapped));
+ }
+#endif
+
+ /**
+ * Equivalent to \ref getline with the last parameter set to
+ * <tt>'\\n'</tt> with the appropriate character type.
+ *
+ * In other words, reads `r` into `str` until <tt>'\\n'</tt> is found.
+ *
+ * The character type is determined by `r`.
+ */
+#if SCN_DOXYGEN
+ template <typename Range,
+ typename String,
+ typename CharT = typename detail::extract_char_type<
+ ranges::iterator_t<range_wrapper_for_t<Range>>>::type>
+ auto getline(Range&& r, String& str)
+ -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Range,
+ typename String,
+ typename CharT = typename detail::extract_char_type<
+ ranges::iterator_t<range_wrapper_for_t<Range>>>::type>
+ SCN_NODISCARD auto getline(Range&& r, String& str)
+ -> detail::scan_result_for_range<Range>
+ {
+ return getline(SCN_FWD(r), str, detail::ascii_widen<CharT>('\n'));
+ }
+#endif
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/scan/ignore.h b/src/third-party/scnlib/include/scn/scan/ignore.h
new file mode 100644
index 0000000..415a877
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/scan/ignore.h
@@ -0,0 +1,189 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_SCAN_IGNORE_H
+#define SCN_SCAN_IGNORE_H
+
+#include "common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename CharT>
+ struct ignore_iterator {
+ using value_type = CharT;
+ using pointer = value_type*;
+ using reference = value_type&;
+ using difference_type = std::ptrdiff_t;
+ using iterator_category = std::output_iterator_tag;
+
+ constexpr ignore_iterator() = default;
+
+ SCN_CONSTEXPR14 ignore_iterator& operator=(CharT) noexcept
+ {
+ return *this;
+ }
+ constexpr const ignore_iterator& operator=(CharT) const noexcept
+ {
+ return *this;
+ }
+
+ SCN_CONSTEXPR14 ignore_iterator& operator*() noexcept
+ {
+ return *this;
+ }
+ constexpr const ignore_iterator& operator*() const noexcept
+ {
+ return *this;
+ }
+
+ SCN_CONSTEXPR14 ignore_iterator& operator++() noexcept
+ {
+ return *this;
+ }
+ constexpr const ignore_iterator& operator++() const noexcept
+ {
+ return *this;
+ }
+ };
+
+ template <typename CharT>
+ struct ignore_iterator_n {
+ using value_type = CharT;
+ using pointer = value_type*;
+ using reference = value_type&;
+ using difference_type = std::ptrdiff_t;
+ using iterator_category = std::output_iterator_tag;
+
+ ignore_iterator_n() = default;
+ ignore_iterator_n(difference_type n) : i(n) {}
+
+ constexpr const ignore_iterator_n& operator=(CharT) const noexcept
+ {
+ return *this;
+ }
+
+ constexpr const ignore_iterator_n& operator*() const noexcept
+ {
+ return *this;
+ }
+
+ SCN_CONSTEXPR14 ignore_iterator_n& operator++() noexcept
+ {
+ ++i;
+ return *this;
+ }
+
+ constexpr bool operator==(const ignore_iterator_n& o) const noexcept
+ {
+ return i == o.i;
+ }
+ constexpr bool operator!=(const ignore_iterator_n& o) const noexcept
+ {
+ return !(*this == o);
+ }
+
+ difference_type i{0};
+ };
+
+ template <typename WrappedRange,
+ typename Until,
+ typename CharT = typename WrappedRange::char_type>
+ error ignore_until_impl(WrappedRange& r, Until until)
+ {
+ ignore_iterator<CharT> it{};
+ return read_until_space(r, it, until_pred<CharT>{until}, false);
+ }
+
+ template <typename WrappedRange,
+ typename Until,
+ typename CharT = typename WrappedRange::char_type>
+ error ignore_until_n_impl(WrappedRange& r,
+ ranges::range_difference_t<WrappedRange> n,
+ Until until)
+ {
+ ignore_iterator_n<CharT> begin{}, end{n};
+ return read_until_space_ranged(r, begin, end,
+ until_pred<CharT>{until}, false);
+ }
+ } // namespace detail
+
+ /**
+ * Advances the beginning of \c r until \c until is found.
+ */
+#if SCN_DOXYGEN
+ template <typename Range, typename Until>
+ auto ignore_until(Range&& r, Until until)
+ -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Range, typename Until>
+ SCN_NODISCARD auto ignore_until(Range&& r, Until until)
+ -> detail::scan_result_for_range<Range>
+ {
+ auto wrapped = wrap(SCN_FWD(r));
+ auto err = detail::ignore_until_impl(wrapped, until);
+ if (!err) {
+ auto e = wrapped.reset_to_rollback_point();
+ if (!e) {
+ err = e;
+ }
+ }
+ else {
+ wrapped.set_rollback_point();
+ }
+ return detail::wrap_result(
+ wrapped_error{err}, detail::range_tag<Range>{}, SCN_MOVE(wrapped));
+ }
+#endif
+
+ /**
+ * Advances the beginning of \c r until \c until is found, or the
+ * beginning has been advanced \c n times.
+ *
+ * `Until` can be the `r` character type (`char` or `wchar_t`), or
+ * `code_point`.
+ */
+#if SCN_DOXYGEN
+ template <typename Range, typename Until>
+ auto ignore_until_n(Range&& r,
+ ranges::range_difference_t<Range> n,
+ Until until) -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Range, typename Until>
+ SCN_NODISCARD auto ignore_until_n(Range&& r,
+ ranges::range_difference_t<Range> n,
+ Until until)
+ -> detail::scan_result_for_range<Range>
+ {
+ auto wrapped = wrap(SCN_FWD(r));
+ auto err = detail::ignore_until_n_impl(wrapped, n, until);
+ if (!err) {
+ auto e = wrapped.reset_to_rollback_point();
+ if (!e) {
+ err = e;
+ }
+ }
+ return detail::wrap_result(
+ wrapped_error{err}, detail::range_tag<Range>{}, SCN_MOVE(wrapped));
+ }
+#endif
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/scan/istream.h b/src/third-party/scnlib/include/scn/scan/istream.h
new file mode 100644
index 0000000..a9aef86
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/scan/istream.h
@@ -0,0 +1,147 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_SCAN_ISTREAM_H
+#define SCN_SCAN_ISTREAM_H
+
+#include "../reader/common.h"
+#include "../detail/result.h"
+
+#include <istream>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename WrappedRange>
+ class range_streambuf
+ : public std::basic_streambuf<typename WrappedRange::char_type> {
+ using base = std::basic_streambuf<typename WrappedRange::char_type>;
+
+ public:
+ using range_type = WrappedRange;
+ using char_type = typename WrappedRange::char_type;
+ using traits_type = typename base::traits_type;
+ using int_type = typename base::int_type;
+
+ explicit range_streambuf(range_type& r) : m_range(std::addressof(r))
+ {
+ }
+
+ private:
+ int_type underflow() override
+ {
+ // already read
+ if (!traits_type::eq_int_type(m_ch, traits_type::eof())) {
+ return m_ch;
+ }
+
+ auto ret = read_code_unit(*m_range);
+ if (!ret) {
+ // error
+ // m_ch is already eof
+ return traits_type::eof();
+ }
+ m_ch = traits_type::to_int_type(ret.value());
+ return m_ch;
+ }
+ int_type uflow() override
+ {
+ auto ret = underflow();
+ if (ret != traits_type::eof()) {
+ m_ch = traits_type::eof();
+ }
+ return ret;
+ }
+ std::streamsize showmanyc() override
+ {
+ return traits_type::eq_int_type(m_ch, traits_type::eof()) ? 0
+ : 1;
+ }
+ int_type pbackfail(int_type) override
+ {
+ auto e = putback_n(*m_range, 1);
+ if (!e) {
+ return traits_type::eof();
+ }
+ return traits_type::to_int_type(0);
+ }
+
+ range_type* m_range;
+ int_type m_ch{traits_type::eof()};
+ };
+
+ // Trick stolen from {fmt}
+ template <typename CharT>
+ struct test_std_stream : std::basic_istream<CharT> {
+ private:
+ struct null;
+ // Hide all operator>> from std::basic_istream<CharT>
+ void operator>>(null);
+ };
+
+ // Check for user-defined operator>>
+ template <typename CharT, typename T, typename = void>
+ struct is_std_streamable : std::false_type {
+ };
+
+ template <typename CharT, typename T>
+ struct is_std_streamable<
+ CharT,
+ T,
+ void_t<decltype(SCN_DECLVAL(test_std_stream<CharT>&) >>
+ SCN_DECLVAL(T&))>> : std::true_type {
+ };
+ } // namespace detail
+
+ template <typename T>
+ struct scanner<T,
+ typename std::enable_if<
+ detail::is_std_streamable<char, T>::value ||
+ detail::is_std_streamable<wchar_t, T>::value>::type>
+ : public empty_parser {
+ template <typename Context>
+ error scan(T& val, Context& ctx)
+ {
+ static_assert(detail::is_std_streamable<typename Context::char_type,
+ T>::value,
+ "Type can not be read from a basic_istream of this "
+ "character type");
+ detail::range_streambuf<typename Context::range_type> streambuf(
+ ctx.range());
+ std::basic_istream<typename Context::char_type> stream(
+ std::addressof(streambuf));
+
+ if (!(stream >> val)) {
+ if (stream.eof()) {
+ return {error::end_of_range, "EOF"};
+ }
+ if (stream.bad()) {
+ return {error::unrecoverable_source_error,
+ "Bad std::istream after reading"};
+ }
+ return {error::invalid_scanned_value,
+ "Failed to read with std::istream"};
+ }
+ return {};
+ }
+ };
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_DETAIL_ISTREAM_H
diff --git a/src/third-party/scnlib/include/scn/scan/list.h b/src/third-party/scnlib/include/scn/scan/list.h
new file mode 100644
index 0000000..a5eeaf5
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/scan/list.h
@@ -0,0 +1,450 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_SCAN_LIST_H
+#define SCN_SCAN_LIST_H
+
+#include "common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ /**
+ * Adapts a `span` into a type that can be read into using \ref
+ * scan_list. This way, potentially unnecessary dynamic memory
+ * allocations can be avoided. To use as a parameter to \ref scan_list,
+ * use \ref make_span_list_wrapper.
+ *
+ * \code{.cpp}
+ * std::vector<int> buffer(8, 0);
+ * scn::span<int> s = scn::make_span(buffer);
+ *
+ * auto wrapper = scn::span_list_wrapper<int>(s);
+ * scn::scan_list("123 456", wrapper);
+ * // s[0] == buffer[0] == 123
+ * // s[1] == buffer[1] == 456
+ * \endcode
+ *
+ * \see scan_list
+ * \see make_span_list_wrapper
+ */
+ template <typename T>
+ struct span_list_wrapper {
+ using value_type = T;
+
+ span_list_wrapper(span<T> s) : m_span(s) {}
+
+ void push_back(T val)
+ {
+ SCN_EXPECT(n < max_size());
+ m_span[n] = SCN_MOVE(val);
+ ++n;
+ }
+
+ SCN_NODISCARD constexpr std::size_t size() const noexcept
+ {
+ return n;
+ }
+ SCN_NODISCARD constexpr std::size_t max_size() const noexcept
+ {
+ return m_span.size();
+ }
+
+ span<T> m_span;
+ std::size_t n{0};
+ };
+
+ namespace detail {
+ template <typename T>
+ using span_list_wrapper_for =
+ span_list_wrapper<typename decltype(make_span(
+ SCN_DECLVAL(T&)))::value_type>;
+ }
+
+ /**
+ * Adapts a contiguous buffer into a type containing a `span` that can
+ * be read into using \ref scn::scan_list.
+ *
+ * Example adapted from \ref span_list_wrapper:
+ * \code{.cpp}
+ * std::vector<int> buffer(8, 0);
+ * scn::scan_list("123 456", scn::make_span_list_wrapper(buffer));
+ * // s[0] == buffer[0] == 123
+ * // s[1] == buffer[1] == 456
+ * \endcode
+ *
+ * \see scan_list
+ * \see span_list_wrapper
+ */
+ template <typename T>
+ auto make_span_list_wrapper(T& s)
+ -> temporary<detail::span_list_wrapper_for<T>>
+ {
+ auto _s = make_span(s);
+ return temp(span_list_wrapper<typename decltype(_s)::value_type>(_s));
+ }
+
+ /**
+ * Used to customize `scan_list_ex()`.
+ *
+ * \tparam CharT Can be a code unit type (`char` or `wchar_t`, depending on
+ * the source range), or `code_point`.
+ *
+ * `list_separator`, `list_until` and `list_separator_and_until` can be used
+ * to create a value of this type, taking advantage of template argument
+ * deduction (no need to hand-specify `CharT`).
+ */
+ template <typename CharT>
+ struct scan_list_options {
+ /**
+ * If set, up to one separator character can be accepted between values,
+ * which may be surrounded by whitespace.
+ */
+ optional<CharT> separator{};
+ /**
+ * If set, reading the list is stopped if this character is found
+ * between values.
+ *
+ * In that case, it is advanced over, and no error is returned.
+ */
+ optional<CharT> until{};
+
+ scan_list_options() = default;
+ scan_list_options(optional<CharT> s, optional<CharT> u)
+ : separator(SCN_MOVE(s)), until(SCN_MOVE(u))
+ {
+ }
+ };
+
+ /**
+ * Create a `scan_list_options` for `scan_list_ex`, by using `ch` as the
+ * separator character.
+ */
+ template <typename CharT>
+ scan_list_options<CharT> list_separator(CharT ch)
+ {
+ return {optional<CharT>{ch}, nullopt};
+ }
+ /**
+ * Create a `scan_list_options` for `scan_list_ex`, by using `ch` as the
+ * until-character.
+ */
+ template <typename CharT>
+ scan_list_options<CharT> list_until(CharT ch)
+ {
+ return {nullopt, optional<CharT>{ch}};
+ }
+ /**
+ * Create a `scan_list_options` for `scan_list_ex`, by using `sep` as the
+ * separator, and `until` as the until-character.
+ */
+ template <typename CharT>
+ scan_list_options<CharT> list_separator_and_until(CharT sep, CharT until)
+ {
+ return {optional<CharT>{sep}, optional<CharT>{until}};
+ }
+
+ namespace detail {
+ template <typename WrappedRange, typename CharT>
+ expected<CharT> check_separator(WrappedRange& r, size_t& n, CharT)
+ {
+ auto ret = read_code_unit(r);
+ if (!ret) {
+ return ret.error();
+ }
+ n = 1;
+ return ret.value();
+ }
+ template <typename WrappedRange>
+ expected<code_point> check_separator(WrappedRange& r,
+ size_t& n,
+ code_point)
+ {
+ unsigned char buf[4] = {0};
+ auto ret = read_code_point(r, make_span(buf, 4));
+ if (!ret) {
+ return ret.error();
+ }
+ n = ret.value().chars.size();
+ return ret.value().cp;
+ }
+
+ template <typename Context, typename Container, typename Separator>
+ auto scan_list_impl(Context& ctx,
+ bool localized,
+ Container& c,
+ scan_list_options<Separator> options) -> error
+ {
+ using char_type = typename Context::char_type;
+ using value_type = typename Container::value_type;
+ value_type value;
+
+ auto args = make_args_for(ctx.range(), 1, value);
+
+ bool scanning = true;
+ while (scanning) {
+ if (c.size() == c.max_size()) {
+ break;
+ }
+
+ // read value
+ auto pctx = make_parse_context(1, ctx.locale(), localized);
+ auto err = visit(ctx, pctx, basic_args<char_type>{args});
+ if (!err) {
+ if (err == error::end_of_range) {
+ break;
+ }
+ return err;
+ }
+ c.push_back(SCN_MOVE(value));
+
+ auto next = static_cast<Separator>(0);
+ size_t n{0};
+
+ auto read_next = [&]() -> error {
+ auto ret = check_separator(ctx.range(), n,
+ static_cast<Separator>(0));
+ if (!ret) {
+ if (ret.error() == error::end_of_range) {
+ scanning = false;
+ return {};
+ }
+ return ret.error();
+ }
+ next = ret.value();
+
+ err =
+ putback_n(ctx.range(), static_cast<std::ptrdiff_t>(n));
+ if (!err) {
+ return err;
+ }
+
+ return {};
+ };
+
+ bool sep_found = false;
+ while (true) {
+ // read until
+ if (options.until) {
+ err = read_next();
+ if (!err) {
+ return err;
+ }
+ if (!scanning) {
+ break;
+ }
+
+ if (next == options.until.get()) {
+ scanning = false;
+ break;
+ }
+ }
+
+ // read sep
+ if (options.separator && !sep_found) {
+ err = read_next();
+ if (!err) {
+ return err;
+ }
+ if (!scanning) {
+ break;
+ }
+
+ if (next == options.separator.get()) {
+ // skip to next char
+ ctx.range().advance(static_cast<std::ptrdiff_t>(n));
+ continue;
+ }
+ }
+
+ err = read_next();
+ if (!err) {
+ return err;
+ }
+ if (!scanning) {
+ break;
+ }
+
+ if (ctx.locale().get_static().is_space(next)) {
+ // skip ws
+ ctx.range().advance(static_cast<std::ptrdiff_t>(n));
+ }
+ else {
+ break;
+ }
+ }
+ }
+
+ return {};
+ }
+ } // namespace detail
+
+ /**
+ * Reads values repeatedly from `r` and writes them into `c`.
+ *
+ * The values read are of type `Container::value_type`, and they are
+ * written into `c` using `c.push_back`.
+ * The values are separated by whitespace.
+ *
+ * The range is read, until:
+ * - `c.max_size()` is reached, or
+ * - range `EOF` is reached
+ *
+ * In these cases, an error will not be returned, and the beginning
+ * of the returned range will point to the first character after the
+ * scanned list.
+ *
+ * If an invalid value is scanned, `error::invalid_scanned_value` is
+ * returned, but the values already in `vec` will remain there. The range is
+ * put back to the state it was before reading the invalid value.
+ *
+ * To scan into `span`, use \ref span_list_wrapper.
+ * \ref make_span_list_wrapper
+ *
+ * \code{.cpp}
+ * std::vector<int> vec{};
+ * auto result = scn::scan_list("123 456", vec);
+ * // vec == [123, 456]
+ * // result.empty() == true
+ *
+ * vec.clear();
+ * result = scn::scan_list("123 456 abc", vec);
+ * // vec == [123, 456]
+ * // result.error() == invalid_scanned_value
+ * // result.range() == " abc"
+ * \endcode
+ *
+ * \param r Range to read from
+ * \param c Container to write values to, using `c.push_back()`.
+ * `Container::value_type` will be used to determine the type of the values
+ * to read.
+ */
+#if SCN_DOXYGEN
+ template <typename Range, typename Container>
+ auto scan_list(Range&& r, Container& c)
+ -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Range, typename Container>
+ SCN_NODISCARD auto scan_list(Range&& r, Container& c)
+ -> detail::scan_result_for_range<Range>
+ {
+ auto range = wrap(SCN_FWD(r));
+ auto ctx = make_context(SCN_MOVE(range));
+ using char_type = typename decltype(ctx)::char_type;
+
+ auto err = detail::scan_list_impl(ctx, false, c,
+ scan_list_options<char_type>{});
+
+ return detail::wrap_result(wrapped_error{err},
+ detail::range_tag<Range>{},
+ SCN_MOVE(ctx.range()));
+ }
+#endif
+
+ /**
+ * Otherwise equivalent to `scan_list()`, except can react to additional
+ * characters, based on `options`.
+ *
+ * See `scan_list_options` for more information.
+ *
+ * \param r Range to scan from
+ * \param c Container to write read values into
+ * \param options Options to use
+ *
+ * \code{.cpp}
+ * std::vector<int> vec{};
+ * auto result = scn::scan_list_ex("123, 456", vec,
+ * scn::list_separator(','));
+ * // vec == [123, 456]
+ * // result.empty() == true
+ * \endcode
+ *
+ * \see scan_list
+ * \see scan_list_options
+ */
+#if SCN_DOXYGEN
+ template <typename Range, typename Container, typename CharT>
+ auto scan_list_ex(Range&& r, Container& c, scan_list_options<CharT> options)
+ -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Range, typename Container, typename CharT>
+ SCN_NODISCARD auto scan_list_ex(Range&& r,
+ Container& c,
+ scan_list_options<CharT> options)
+ -> detail::scan_result_for_range<Range>
+ {
+ auto range = wrap(SCN_FWD(r));
+ auto ctx = make_context(SCN_MOVE(range));
+
+ auto err = detail::scan_list_impl(ctx, false, c, options);
+
+ return detail::wrap_result(wrapped_error{err},
+ detail::range_tag<Range>{},
+ SCN_MOVE(ctx.range()));
+ }
+#endif
+
+ /**
+ * Otherwise equivalent to `scan_list_ex()`, except uses `loc` to scan the
+ * values.
+ *
+ * \param loc Locale to use for scanning. Must be a `std::locale`.
+ * \param r Range to scan from
+ * \param c Container to write read values into
+ * \param options Options to use
+ *
+ * \see scan_list_ex()
+ * \see scan_localized()
+ */
+#if SCN_DOXYGEN
+ template <typename Locale,
+ typename Range,
+ typename Container,
+ typename CharT>
+ auto scan_list_localized(const Locale& loc,
+ Range&& r,
+ Container& c,
+ scan_list_options<CharT> options)
+ -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Locale,
+ typename Range,
+ typename Container,
+ typename CharT>
+ SCN_NODISCARD auto scan_list_localized(const Locale& loc,
+ Range&& r,
+ Container& c,
+ scan_list_options<CharT> options)
+ -> detail::scan_result_for_range<Range>
+ {
+ auto range = wrap(SCN_FWD(r));
+ using char_type = typename decltype(range)::char_type;
+ auto locale = make_locale_ref<char_type>(loc);
+ auto ctx = make_context(SCN_MOVE(range), SCN_MOVE(locale));
+
+ auto err = detail::scan_list_impl(ctx, true, c, options);
+
+ return detail::wrap_result(wrapped_error{err},
+ detail::range_tag<Range>{},
+ SCN_MOVE(ctx.range()));
+ }
+#endif
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/scan/scan.h b/src/third-party/scnlib/include/scn/scan/scan.h
new file mode 100644
index 0000000..20f4cd1
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/scan/scan.h
@@ -0,0 +1,444 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_SCAN_SCAN_H
+#define SCN_SCAN_SCAN_H
+
+#include "../util/optional.h"
+#include "common.h"
+#include "vscan.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace dummy {
+ }
+
+ /**
+ * \tparam OriginalRange The type of the range passed to the scanning
+ * function \param result Return value of `vscan` \return Result object
+ *
+ * \code{.cpp}
+ * template <typename Range, typename... Args>
+ * auto scan(Range&& r, string_view f, Args&... a) {
+ * auto range = scn::wrap(std::forward<Range>(r));
+ * auto args = scn::make_args_for(range, f, a...);
+ * auto ret = scn::vscan(std::move(range), f, {args});
+ * return scn::make_scan_result<Range>(std::move(ret));
+ * }
+ * \endcode
+ */
+ template <typename OriginalRange,
+ typename Error = wrapped_error,
+ typename WrappedRange>
+ auto make_scan_result(vscan_result<WrappedRange> result)
+ -> detail::scan_result_for_range<OriginalRange>
+ {
+ return detail::wrap_result(Error{result.err},
+ detail::range_tag<OriginalRange>{},
+ SCN_MOVE(result.range));
+ }
+
+ namespace detail {
+ template <typename Range, typename Format, typename... Args>
+ auto scan_boilerplate(Range&& r, const Format& f, Args&... a)
+ -> detail::scan_result_for_range<Range>
+ {
+ static_assert(sizeof...(Args) > 0,
+ "Have to scan at least a single argument");
+ static_assert(SCN_CHECK_CONCEPT(ranges::range<Range>),
+ "Input needs to be a Range");
+
+ auto range = wrap(SCN_FWD(r));
+ auto format = detail::to_format(f);
+ auto args = make_args_for(range, format, a...);
+ auto ret = vscan(SCN_MOVE(range), format, {args});
+ return make_scan_result<Range>(SCN_MOVE(ret));
+ }
+
+ template <typename Range, typename... Args>
+ auto scan_boilerplate_default(Range&& r, Args&... a)
+ -> detail::scan_result_for_range<Range>
+ {
+ static_assert(sizeof...(Args) > 0,
+ "Have to scan at least a single argument");
+ static_assert(SCN_CHECK_CONCEPT(ranges::range<Range>),
+ "Input needs to be a Range");
+
+ auto range = wrap(SCN_FWD(r));
+ auto format = static_cast<int>(sizeof...(Args));
+ auto args = make_args_for(range, format, a...);
+ auto ret = vscan_default(SCN_MOVE(range), format, {args});
+ return make_scan_result<Range>(SCN_MOVE(ret));
+ }
+
+ template <typename Locale,
+ typename Range,
+ typename Format,
+ typename... Args>
+ auto scan_boilerplate_localized(const Locale& loc,
+ Range&& r,
+ const Format& f,
+ Args&... a)
+ -> detail::scan_result_for_range<Range>
+ {
+ static_assert(sizeof...(Args) > 0,
+ "Have to scan at least a single argument");
+ static_assert(SCN_CHECK_CONCEPT(ranges::range<Range>),
+ "Input needs to be a Range");
+
+ auto range = wrap(SCN_FWD(r));
+ auto format = detail::to_format(f);
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ auto locale =
+ make_locale_ref<typename decltype(range)::char_type>(loc);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+
+ auto args = make_args_for(range, format, a...);
+ auto ret = vscan_localized(SCN_MOVE(range), SCN_MOVE(locale),
+ format, {args});
+ return make_scan_result<Range>(SCN_MOVE(ret));
+ }
+
+ } // namespace detail
+
+ // scan
+
+ // For some reason, Doxygen dislikes SCN_NODISCARD
+
+ /**
+ * The most fundamental part of the scanning API.
+ * Reads from the range in \c r according to the format string \c f.
+ *
+ * \code{.cpp}
+ * int i;
+ * scn::scan("123", "{}", i);
+ * // i == 123
+ * \endcode
+ */
+#if SCN_DOXYGEN
+ template <typename Range, typename Format, typename... Args>
+ auto scan(Range&& r, const Format& f, Args&... a)
+ -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Range, typename Format, typename... Args>
+ SCN_NODISCARD auto scan(Range&& r, const Format& f, Args&... a)
+ -> detail::scan_result_for_range<Range>
+ {
+ return detail::scan_boilerplate(SCN_FWD(r), f, a...);
+ }
+#endif
+
+ // default format
+
+ /**
+ * Equivalent to \ref scan, but with a
+ * format string with the appropriate amount of space-separated `"{}"`s for
+ * the number of arguments. Because this function doesn't have to parse the
+ * format string, performance is improved.
+ *
+ * Adapted from the example for \ref scan
+ * \code{.cpp}
+ * int i;
+ * scn::scan_default("123", i);
+ * // i == 123
+ * \endcode
+ *
+ * \see scan
+ */
+#if SCN_DOXYGEN
+ template <typename Range, typename... Args>
+ auto scan_default(Range&& r, Args&... a)
+ -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Range, typename... Args>
+ SCN_NODISCARD auto scan_default(Range&& r, Args&... a)
+ -> detail::scan_result_for_range<Range>
+ {
+ return detail::scan_boilerplate_default(std::forward<Range>(r), a...);
+ }
+#endif
+
+ // scan localized
+
+ /**
+ * Read from the range in \c r using the locale in \c loc.
+ * \c loc must be a \c std::locale. The parameter is a template to avoid
+ * inclusion of `<locale>`.
+ *
+ * Use of this function is discouraged, due to the overhead involved
+ * with locales. Note, that the other functions are completely
+ * locale-agnostic, and aren't affected by changes to the global C
+ * locale.
+ *
+ * \code{.cpp}
+ * double d;
+ * scn::scan_localized(std::locale{"fi_FI"}, "3,14", "{}", d);
+ * // d == 3.14
+ * \endcode
+ *
+ * \see scan
+ */
+#if SCN_DOXYGEN
+ template <typename Locale,
+ typename Range,
+ typename Format,
+ typename... Args>
+ auto scan_localized(const Locale& loc,
+ Range&& r,
+ const Format& f,
+ Args&... a) -> detail::scan_result_for_range<Range>;
+#else
+ template <typename Locale,
+ typename Range,
+ typename Format,
+ typename... Args>
+ SCN_NODISCARD auto scan_localized(const Locale& loc,
+ Range&& r,
+ const Format& f,
+ Args&... a)
+ -> detail::scan_result_for_range<Range>
+ {
+ return detail::scan_boilerplate_localized(loc, std::forward<Range>(r),
+ f, a...);
+ }
+#endif
+
+ // value
+
+ /**
+ * Scans a single value with the default options, returning it instead of
+ * using an output parameter.
+ *
+ * The parsed value is in `ret.value()`, if `ret == true`.
+ * The return type of this function is otherwise similar to other scanning
+ * functions.
+ *
+ * \code{.cpp}
+ * auto ret = scn::scan_value<int>("42");
+ * if (ret) {
+ * // ret.value() == 42
+ * }
+ * \endcode
+ */
+#if SCN_DOXYGEN
+ template <typename T, typename Range>
+ auto scan_value(Range&& r)
+ -> detail::generic_scan_result_for_range<expected<T>, Range>;
+#else
+ template <typename T, typename Range>
+ SCN_NODISCARD auto scan_value(Range&& r)
+ -> detail::generic_scan_result_for_range<expected<T>, Range>
+ {
+ T value;
+ auto range = wrap(SCN_FWD(r));
+ auto args = make_args_for(range, 1, value);
+ auto ret = vscan_default(SCN_MOVE(range), 1, {args});
+ if (ret.err) {
+ return detail::wrap_result(expected<T>{value},
+ detail::range_tag<Range>{},
+ SCN_MOVE(ret.range));
+ }
+ return detail::wrap_result(expected<T>{ret.err},
+ detail::range_tag<Range>{},
+ SCN_MOVE(ret.range));
+ }
+#endif
+
+ // input
+
+ /**
+ * Otherwise equivalent to \ref scan, expect reads from `stdin`.
+ * Character type is determined by the format string.
+ * Syncs with `<cstdio>`.
+ */
+ template <typename Format,
+ typename... Args,
+ typename CharT = ranges::range_value_t<Format>>
+#if SCN_DOXYGEN
+ auto input(const Format& f, Args&... a)
+ -> detail::scan_result_for_range<basic_file<CharT>&>;
+#else
+ SCN_NODISCARD auto input(const Format& f, Args&... a)
+ -> detail::scan_result_for_range<basic_file<CharT>&>
+ {
+ auto& range = stdin_range<CharT>();
+ auto ret = detail::scan_boilerplate(range, f, a...);
+ range.sync();
+ ret.range().reset_begin_iterator();
+ return ret;
+ }
+#endif
+
+ // prompt
+
+ namespace detail {
+ inline void put_stdout(const char* str)
+ {
+ std::fputs(str, stdout);
+ }
+ inline void put_stdout(const wchar_t* str)
+ {
+ std::fputws(str, stdout);
+ }
+ } // namespace detail
+
+ /**
+ * Equivalent to \ref input, except writes what's in `p` to `stdout`.
+ *
+ * \code{.cpp}
+ * int i{};
+ * scn::prompt("What's your favorite number? ", "{}", i);
+ * // Equivalent to:
+ * // std::fputs("What's your favorite number? ", stdout);
+ * // scn::input("{}", i);
+ * \endcode
+ */
+#if SCN_DOXYGEN
+ template <typename CharT, typename Format, typename... Args>
+ auto prompt(const CharT* p, const Format& f, Args&... a)
+ -> detail::scan_result_for_range<basic_file<CharT>&>;
+#else
+ template <typename CharT, typename Format, typename... Args>
+ SCN_NODISCARD auto prompt(const CharT* p, const Format& f, Args&... a)
+ -> detail::scan_result_for_range<basic_file<CharT>&>
+ {
+ SCN_EXPECT(p != nullptr);
+ detail::put_stdout(p);
+
+ return input(f, a...);
+ }
+#endif
+
+ // parse_integer
+
+ /**
+ * Parses an integer into \c val in base \c base from \c str.
+ * Returns a pointer past the last character read, or an error.
+ *
+ * @param str source, can't be empty, cannot have:
+ * - preceding whitespace
+ * - preceding \c "0x" or \c "0" (base is determined by the \c base
+ * parameter)
+ * - \c '+' sign (\c '-' is fine)
+ * @param val parsed integer, must be value-constructed
+ * @param base between [2,36]
+ */
+#if SCN_DOXYGEN
+ template <typename T, typename CharT>
+ expected<const CharT*> parse_integer(basic_string_view<CharT> str,
+ T& val,
+ int base = 10);
+#else
+ template <typename T, typename CharT>
+ SCN_NODISCARD expected<const CharT*>
+ parse_integer(basic_string_view<CharT> str, T& val, int base = 10)
+ {
+ SCN_EXPECT(!str.empty());
+ auto s = detail::simple_integer_scanner<T>{};
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ auto ret =
+ s.scan_lower(span<const CharT>(str.data(), str.size()), val, base);
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ if (!ret) {
+ return ret.error();
+ }
+ return {ret.value()};
+ }
+#endif
+
+ /**
+ * Parses float into \c val from \c str.
+ * Returns a pointer past the last character read, or an error.
+ *
+ * @param str source, can't be empty
+ * @param val parsed float, must be value-constructed
+ */
+#if SCN_DOXYGEN
+ template <typename T, typename CharT>
+ expected<const CharT*> parse_float(basic_string_view<CharT> str, T& val);
+#else
+ template <typename T, typename CharT>
+ SCN_NODISCARD expected<const CharT*> parse_float(
+ basic_string_view<CharT> str,
+ T& val)
+ {
+ SCN_EXPECT(!str.empty());
+ auto s = detail::float_scanner_access<T>{};
+ auto ret = s._read_float(val, make_span(str.data(), str.size()),
+ detail::ascii_widen<CharT>('.'));
+ if (!ret) {
+ return ret.error();
+ }
+ return {str.data() + ret.value()};
+ }
+#endif
+
+ /**
+ * A convenience function for creating scanners for user-provided types.
+ *
+ * Wraps \ref vscan_usertype
+ *
+ * Example use:
+ *
+ * \code{.cpp}
+ * // Type has two integers, and its textual representation is
+ * // "[val1, val2]"
+ * struct user_type {
+ * int val1;
+ * int val2;
+ * };
+ *
+ * template <>
+ * struct scn::scanner<user_type> : public scn::empty_parser {
+ * template <typename Context>
+ * error scan(user_type& val, Context& ctx)
+ * {
+ * return scan_usertype(ctx, "[{}, {}]", val.val1, val.val2);
+ * }
+ * };
+ * \endcode
+ *
+ * \param ctx Context given to the scanning function
+ * \param f Format string to parse
+ * \param a Member types (etc) to parse
+ */
+#if SCN_DOXYGEN
+ template <typename WrappedRange, typename Format, typename... Args>
+ error scan_usertype(basic_context<WrappedRange>& ctx,
+ const Format& f,
+ Args&... a);
+#else
+ template <typename WrappedRange, typename Format, typename... Args>
+ SCN_NODISCARD error scan_usertype(basic_context<WrappedRange>& ctx,
+ const Format& f,
+ Args&... a)
+ {
+ static_assert(sizeof...(Args) > 0,
+ "Have to scan at least a single argument");
+
+ using char_type = typename WrappedRange::char_type;
+ auto args = make_args<basic_context<WrappedRange>,
+ basic_parse_context<char_type>>(a...);
+ return vscan_usertype(ctx, basic_string_view<char_type>(f), {args});
+ }
+#endif
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_DETAIL_SCAN_H
diff --git a/src/third-party/scnlib/include/scn/scan/vscan.h b/src/third-party/scnlib/include/scn/scan/vscan.h
new file mode 100644
index 0000000..86bf23e
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/scan/vscan.h
@@ -0,0 +1,208 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_SCAN_VSCAN_H
+#define SCN_SCAN_VSCAN_H
+
+#include "../detail/context.h"
+#include "../detail/file.h"
+#include "../detail/parse_context.h"
+#include "../detail/visitor.h"
+#include "common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ // Avoid documentation issues: without this, Doxygen will think
+ // SCN_BEGIN_NAMESPACE is a part of the vscan declaration
+ namespace dummy {
+ }
+
+ /**
+ * Type returned by `vscan` and others
+ */
+ template <typename WrappedRange>
+ struct vscan_result {
+ error err;
+ WrappedRange range;
+ };
+
+ namespace detail {
+ template <typename WrappedRange,
+ typename CharT = typename WrappedRange::char_type>
+ vscan_result<WrappedRange> vscan_boilerplate(
+ WrappedRange&& r,
+ basic_string_view<CharT> fmt,
+ basic_args<CharT> args)
+ {
+ auto ctx = make_context(SCN_MOVE(r));
+ auto pctx = make_parse_context(fmt, ctx.locale());
+ auto err = visit(ctx, pctx, SCN_MOVE(args));
+ return {err, SCN_MOVE(ctx.range())};
+ }
+
+ template <typename WrappedRange,
+ typename CharT = typename WrappedRange::char_type>
+ vscan_result<WrappedRange> vscan_boilerplate_default(
+ WrappedRange&& r,
+ int n_args,
+ basic_args<CharT> args)
+ {
+ auto ctx = make_context(SCN_MOVE(r));
+ auto pctx = make_parse_context(n_args, ctx.locale());
+ auto err = visit(ctx, pctx, SCN_MOVE(args));
+ return {err, SCN_MOVE(ctx.range())};
+ }
+
+ template <typename WrappedRange,
+ typename Format,
+ typename CharT = typename WrappedRange::char_type>
+ vscan_result<WrappedRange> vscan_boilerplate_localized(
+ WrappedRange&& r,
+ basic_locale_ref<CharT>&& loc,
+ const Format& fmt,
+ basic_args<CharT> args)
+ {
+ auto ctx = make_context(SCN_MOVE(r), SCN_MOVE(loc));
+ auto pctx = make_parse_context(fmt, ctx.locale());
+ auto err = visit(ctx, pctx, SCN_MOVE(args));
+ return {err, SCN_MOVE(ctx.range())};
+ }
+ } // namespace detail
+
+ /**
+ * In the spirit of {fmt}/`std::format` and `vformat`, `vscan` behaves
+ * similarly to \ref scan, except instead of taking a variadic argument
+ * pack, it takes an object of type `basic_args`, which type-erases the
+ * arguments to scan. This, in effect, will decrease generated code size and
+ * compile times dramatically.
+ *
+ * \param range Source range that has been wrapped with `detail::wrap`, and
+ * passed in as an rvalue.
+ * \param fmt Format string to use
+ * \param args Type-erased values to read
+ */
+ template <typename WrappedRange,
+ typename CharT = typename WrappedRange::char_type>
+ vscan_result<WrappedRange> vscan(WrappedRange range,
+ basic_string_view<CharT> fmt,
+ basic_args<CharT>&& args)
+ {
+ return detail::vscan_boilerplate(SCN_MOVE(range), fmt, SCN_MOVE(args));
+ }
+
+ /**
+ * To be used with `scan_default`
+ *
+ * \param range Source range that has been wrapped with `detail::wrap`, and
+ * passed in as an rvalue.
+ * \param n_args Number of arguments in args
+ * \param args Type-erased values to read
+ *
+ * \see vscan
+ */
+ template <typename WrappedRange,
+ typename CharT = typename WrappedRange::char_type>
+ vscan_result<WrappedRange> vscan_default(WrappedRange range,
+ int n_args,
+ basic_args<CharT>&& args)
+ {
+ return detail::vscan_boilerplate_default(SCN_MOVE(range), n_args,
+ SCN_MOVE(args));
+ }
+
+ /**
+ * To be used with `scan_localized`
+ *
+ * \param loc Locale to use
+ * \param range Source range that has been wrapped with `detail::wrap`, and
+ * passed in as an rvalue.
+ * \param fmt Format string to use
+ * \param args Type-erased values to read
+ *
+ * \see vscan
+ */
+ template <typename WrappedRange,
+ typename CharT = typename WrappedRange::char_type>
+ vscan_result<WrappedRange> vscan_localized(WrappedRange range,
+ basic_locale_ref<CharT>&& loc,
+ basic_string_view<CharT> fmt,
+ basic_args<CharT>&& args)
+ {
+ return detail::vscan_boilerplate_localized(
+ SCN_MOVE(range), SCN_MOVE(loc), fmt, SCN_MOVE(args));
+ }
+
+ /**
+ * \see scan_usertype
+ * \see vscan
+ */
+ template <typename WrappedRange,
+ typename CharT = typename WrappedRange::char_type>
+ error vscan_usertype(basic_context<WrappedRange>& ctx,
+ basic_string_view<CharT> f,
+ basic_args<CharT>&& args)
+ {
+ auto pctx = make_parse_context(f, ctx.locale());
+ return visit(ctx, pctx, SCN_MOVE(args));
+ }
+
+#if !defined(SCN_HEADER_ONLY) || !SCN_HEADER_ONLY
+
+#define SCN_VSCAN_DECLARE(Range, WrappedAlias, CharAlias) \
+ namespace detail { \
+ namespace vscan_macro { \
+ using WrappedAlias = range_wrapper_for_t<Range>; \
+ using CharAlias = typename WrappedAlias::char_type; \
+ } \
+ } \
+ vscan_result<detail::vscan_macro::WrappedAlias> vscan( \
+ detail::vscan_macro::WrappedAlias&&, \
+ basic_string_view<detail::vscan_macro::CharAlias>, \
+ basic_args<detail::vscan_macro::CharAlias>&&); \
+ \
+ vscan_result<detail::vscan_macro::WrappedAlias> vscan_default( \
+ detail::vscan_macro::WrappedAlias&&, int, \
+ basic_args<detail::vscan_macro::CharAlias>&&); \
+ \
+ vscan_result<detail::vscan_macro::WrappedAlias> vscan_localized( \
+ detail::vscan_macro::WrappedAlias&&, \
+ basic_locale_ref<detail::vscan_macro::CharAlias>&&, \
+ basic_string_view<detail::vscan_macro::CharAlias>, \
+ basic_args<detail::vscan_macro::CharAlias>&&); \
+ \
+ error vscan_usertype(basic_context<detail::vscan_macro::WrappedAlias>&, \
+ basic_string_view<detail::vscan_macro::CharAlias>, \
+ basic_args<detail::vscan_macro::CharAlias>&&)
+
+ SCN_VSCAN_DECLARE(string_view, string_view_wrapped, string_view_char);
+ SCN_VSCAN_DECLARE(wstring_view, wstring_view_wrapped, wstring_view_char);
+ SCN_VSCAN_DECLARE(std::string, string_wrapped, string_char);
+ SCN_VSCAN_DECLARE(std::wstring, wstring_wrapped, wstring_char);
+ SCN_VSCAN_DECLARE(file&, file_ref_wrapped, file_ref_char);
+ SCN_VSCAN_DECLARE(wfile&, wfile_ref_wrapped, wfile_ref_char);
+
+#endif // !SCN_HEADER_ONLY
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY && !defined(SCN_VSCAN_CPP)
+#include "vscan.cpp"
+#endif
+
+#endif // SCN_SCAN_VSCAN_H
diff --git a/src/third-party/scnlib/include/scn/scn.h b/src/third-party/scnlib/include/scn/scn.h
new file mode 100644
index 0000000..ad4e062
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/scn.h
@@ -0,0 +1,26 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_SCN_H
+#define SCN_SCN_H
+
+#include "scan/scan.h"
+#include "scan/getline.h"
+#include "scan/ignore.h"
+#include "scan/list.h"
+
+#endif // SCN_SCN_H
diff --git a/src/third-party/scnlib/include/scn/tuple_return.h b/src/third-party/scnlib/include/scn/tuple_return.h
new file mode 100644
index 0000000..fc5ecb0
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/tuple_return.h
@@ -0,0 +1,23 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_TUPLE_RETURN_H
+#define SCN_TUPLE_RETURN_H
+
+#include "tuple_return/tuple_return.h"
+
+#endif // SCN_TUPLE_RETURN_H
diff --git a/src/third-party/scnlib/include/scn/tuple_return/tuple_return.h b/src/third-party/scnlib/include/scn/tuple_return/tuple_return.h
new file mode 100644
index 0000000..91d6254
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/tuple_return/tuple_return.h
@@ -0,0 +1,123 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_TUPLE_RETURN_TUPLE_RETURN_H
+#define SCN_TUPLE_RETURN_TUPLE_RETURN_H
+
+#include "../scan/vscan.h"
+#include "util.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace dummy {
+ }
+
+/**
+ * Alternative interface for scanning, returning values as a tuple, instead
+ * of taking them by reference.
+ *
+ * It's highly recommended to use this interface only with C++17 or later,
+ * as structured bindings make it way more ergonomic.
+ *
+ * Compared to the regular scan interface, the performance of this interface
+ * is the same (generated code is virtually identical with optimizations
+ * enabled), but the compile time is slower.
+ *
+ * Values scanned by this function still need to be default-constructible.
+ * To scan a non-default-constructible value, use \c scn::optional
+ *
+ * \param r Input range
+ * \param f Format string to use
+ *
+ * \return Tuple, where the first element is the scan result, and the
+ * remaining elements are the scanned values.
+ */
+#if SCN_DOXYGEN
+ template <typename... Args, typename Range, typename Format>
+ auto scan_tuple(Range&& r, Format f)
+ -> std::tuple<detail::scan_result_for_range<Range>, Args...>;
+#else
+ template <typename... Args, typename Range, typename Format>
+ SCN_NODISCARD auto scan_tuple(Range&& r, Format f)
+ -> std::tuple<detail::scan_result_for_range<Range>, Args...>
+ {
+ using result = detail::scan_result_for_range<Range>;
+ using range_type = typename result::wrapped_range_type;
+
+ using context_type = basic_context<range_type>;
+ using parse_context_type =
+ basic_parse_context<typename context_type::locale_type>;
+ using char_type = typename range_type::char_type;
+
+ auto range = wrap(SCN_FWD(r));
+ auto scanfn = [&range, &f](Args&... a) {
+ auto args = make_args<context_type, parse_context_type>(a...);
+ return vscan(SCN_MOVE(range), basic_string_view<char_type>(f),
+ {args});
+ };
+
+ std::tuple<Args...> values{Args{}...};
+ auto ret = detail::apply(scanfn, values);
+ return std::tuple_cat(
+ std::tuple<result>{detail::wrap_result(wrapped_error{ret.err},
+ detail::range_tag<Range>{},
+ SCN_MOVE(ret.range))},
+ SCN_MOVE(values));
+ }
+#endif
+
+ /**
+ * Equivalent to `scan_tuple`, except uses `vscan_default` under the hood.
+ */
+#if SCN_DOXYGEN
+ template <typename... Args, typename Range>
+ auto scan_tuple_default(Range&& r)
+ -> std::tuple<detail::scan_result_for_range<Range>, Args...>;
+#else
+ template <typename... Args, typename Range>
+ SCN_NODISCARD auto scan_tuple_default(Range&& r)
+ -> std::tuple<detail::scan_result_for_range<Range>, Args...>
+ {
+ using result = detail::scan_result_for_range<Range>;
+ using range_type = typename result::wrapped_range_type;
+
+ using context_type = basic_context<range_type>;
+ using parse_context_type =
+ basic_empty_parse_context<typename context_type::locale_type>;
+
+ auto range = wrap(SCN_FWD(r));
+ auto scanfn = [&range](Args&... a) {
+ auto args = make_args<context_type, parse_context_type>(a...);
+ return vscan_default(SCN_MOVE(range),
+ static_cast<int>(sizeof...(Args)), {args});
+ };
+
+ std::tuple<Args...> values{Args{}...};
+ auto ret = detail::apply(scanfn, values);
+ return std::tuple_cat(
+ std::tuple<result>{detail::wrap_result(wrapped_error{ret.err},
+ detail::range_tag<Range>{},
+ SCN_MOVE(ret.range))},
+ SCN_MOVE(values));
+ }
+#endif
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/tuple_return/util.h b/src/third-party/scnlib/include/scn/tuple_return/util.h
new file mode 100644
index 0000000..be0e2ab
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/tuple_return/util.h
@@ -0,0 +1,176 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_TUPLE_RETURN_UTIL_H
+#define SCN_TUPLE_RETURN_UTIL_H
+
+#include "../util/meta.h"
+
+#include <functional>
+#include <tuple>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ // From cppreference
+ template <typename Fn,
+ typename... Args,
+ typename std::enable_if<std::is_member_pointer<
+ typename std::decay<Fn>::type>::value>::type* = nullptr,
+ int = 0>
+ constexpr auto invoke(Fn&& f, Args&&... args) noexcept(
+ noexcept(std::mem_fn(f)(std::forward<Args>(args)...)))
+ -> decltype(std::mem_fn(f)(std::forward<Args>(args)...))
+ {
+ return std::mem_fn(f)(std::forward<Args>(args)...);
+ }
+
+ template <typename Fn,
+ typename... Args,
+ typename std::enable_if<!std::is_member_pointer<
+ typename std::decay<Fn>::type>::value>::type* = nullptr>
+ constexpr auto invoke(Fn&& f, Args&&... args) noexcept(
+ noexcept(std::forward<Fn>(f)(std::forward<Args>(args)...)))
+ -> decltype(std::forward<Fn>(f)(std::forward<Args>(args)...))
+ {
+ return std::forward<Fn>(f)(std::forward<Args>(args)...);
+ }
+
+ // From Boost.mp11
+ template <typename T, T... I>
+ struct integer_sequence {
+ };
+
+ // iseq_if_c
+ template <bool C, typename T, typename E>
+ struct iseq_if_c_impl;
+
+ template <typename T, typename E>
+ struct iseq_if_c_impl<true, T, E> {
+ using type = T;
+ };
+
+ template <typename T, typename E>
+ struct iseq_if_c_impl<false, T, E> {
+ using type = E;
+ };
+
+ template <bool C, typename T, typename E>
+ using iseq_if_c = typename iseq_if_c_impl<C, T, E>::type;
+
+ // iseq_identity
+ template <typename T>
+ struct iseq_identity {
+ using type = T;
+ };
+
+ template <typename S1, typename S2>
+ struct append_integer_sequence;
+
+ template <typename T, T... I, T... J>
+ struct append_integer_sequence<integer_sequence<T, I...>,
+ integer_sequence<T, J...>> {
+ using type = integer_sequence<T, I..., (J + sizeof...(I))...>;
+ };
+
+ template <typename T, T N>
+ struct make_integer_sequence_impl;
+
+ template <typename T, T N>
+ struct make_integer_sequence_impl_ {
+ private:
+ static_assert(
+ N >= 0,
+ "make_integer_sequence<T, N>: N must not be negative");
+
+ static T const M = N / 2;
+ static T const R = N % 2;
+
+ using S1 = typename make_integer_sequence_impl<T, M>::type;
+ using S2 = typename append_integer_sequence<S1, S1>::type;
+ using S3 = typename make_integer_sequence_impl<T, R>::type;
+ using S4 = typename append_integer_sequence<S2, S3>::type;
+
+ public:
+ using type = S4;
+ };
+
+ template <typename T, T N>
+ struct make_integer_sequence_impl
+ : iseq_if_c<N == 0,
+ iseq_identity<integer_sequence<T>>,
+ iseq_if_c<N == 1,
+ iseq_identity<integer_sequence<T, 0>>,
+ make_integer_sequence_impl_<T, N>>> {
+ };
+
+ // make_integer_sequence
+ template <typename T, T N>
+ using make_integer_sequence =
+ typename detail::make_integer_sequence_impl<T, N>::type;
+
+ // index_sequence
+ template <std::size_t... I>
+ using index_sequence = integer_sequence<std::size_t, I...>;
+
+ // make_index_sequence
+ template <std::size_t N>
+ using make_index_sequence = make_integer_sequence<std::size_t, N>;
+
+ // index_sequence_for
+ template <typename... T>
+ using index_sequence_for =
+ make_integer_sequence<std::size_t, sizeof...(T)>;
+
+ // From cppreference
+ template <class F, class Tuple, std::size_t... I>
+ constexpr auto
+ apply_impl(F&& f, Tuple&& t, index_sequence<I...>) noexcept(
+ noexcept(detail::invoke(std::forward<F>(f),
+ std::get<I>(std::forward<Tuple>(t))...)))
+ -> decltype(detail::invoke(std::forward<F>(f),
+ std::get<I>(std::forward<Tuple>(t))...))
+ {
+ return detail::invoke(std::forward<F>(f),
+ std::get<I>(std::forward<Tuple>(t))...);
+ } // namespace detail
+
+ template <class F, class Tuple>
+ constexpr auto apply(F&& f, Tuple&& t) noexcept(
+ noexcept(detail::apply_impl(
+ std::forward<F>(f),
+ std::forward<Tuple>(t),
+ make_index_sequence<std::tuple_size<
+ typename std::remove_reference<Tuple>::type>::value>{})))
+ -> decltype(detail::apply_impl(
+ std::forward<F>(f),
+ std::forward<Tuple>(t),
+ make_index_sequence<std::tuple_size<
+ typename std::remove_reference<Tuple>::type>::value>{}))
+ {
+ return detail::apply_impl(
+ std::forward<F>(f), std::forward<Tuple>(t),
+ make_index_sequence<std::tuple_size<
+ typename std::remove_reference<Tuple>::type>::value>{});
+ }
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/unicode/common.h b/src/third-party/scnlib/include/scn/unicode/common.h
new file mode 100644
index 0000000..3807793
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/unicode/common.h
@@ -0,0 +1,139 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+//
+// The contents of this file are based on utfcpp:
+// https://github.com/nemtrif/utfcpp
+// Copyright (c) 2006 Nemanja Trifunovic
+// Distributed under the Boost Software License, version 1.0
+
+#ifndef SCN_UNICODE_COMMON_H
+#define SCN_UNICODE_COMMON_H
+
+#include "../detail/fwd.h"
+
+#include <cstdint>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ /**
+ * A Unicode code point
+ */
+ enum class code_point : uint32_t {};
+
+ template <typename T>
+ constexpr bool operator==(code_point a, T b)
+ {
+ return static_cast<uint32_t>(a) == static_cast<uint32_t>(b);
+ }
+ template <typename T>
+ constexpr bool operator!=(code_point a, T b)
+ {
+ return static_cast<uint32_t>(a) != static_cast<uint32_t>(b);
+ }
+ template <typename T>
+ constexpr bool operator<(code_point a, T b)
+ {
+ return static_cast<uint32_t>(a) < static_cast<uint32_t>(b);
+ }
+ template <typename T>
+ constexpr bool operator>(code_point a, T b)
+ {
+ return static_cast<uint32_t>(a) > static_cast<uint32_t>(b);
+ }
+ template <typename T>
+ constexpr bool operator<=(code_point a, T b)
+ {
+ return static_cast<uint32_t>(a) <= static_cast<uint32_t>(b);
+ }
+ template <typename T>
+ constexpr bool operator>=(code_point a, T b)
+ {
+ return static_cast<uint32_t>(a) >= static_cast<uint32_t>(b);
+ }
+
+ namespace detail {
+ static constexpr const uint16_t lead_surrogate_min = 0xd800;
+ static constexpr const uint16_t lead_surrogate_max = 0xdbff;
+ static constexpr const uint16_t trail_surrogate_min = 0xdc00;
+ static constexpr const uint16_t trail_surrogate_max = 0xdfff;
+ static constexpr const uint16_t lead_offset =
+ lead_surrogate_min - (0x10000u >> 10);
+ static constexpr const uint32_t surrogate_offset =
+ 0x10000u - (lead_surrogate_min << 10) - trail_surrogate_min;
+ static constexpr const uint32_t code_point_max = 0x10ffff;
+
+ template <typename Octet>
+ constexpr uint8_t mask8(Octet o)
+ {
+ return static_cast<uint8_t>(0xff & o);
+ }
+ template <typename U16>
+ constexpr uint16_t mask16(U16 v)
+ {
+ return static_cast<uint16_t>(0xffff & v);
+ }
+ template <typename U16>
+ constexpr bool is_lead_surrogate(U16 cp)
+ {
+ return cp >= lead_surrogate_min && cp <= lead_surrogate_max;
+ }
+ template <typename U16>
+ constexpr bool is_trail_surrogate(U16 cp)
+ {
+ return cp >= trail_surrogate_min && cp <= trail_surrogate_max;
+ }
+ template <typename U16>
+ constexpr bool is_surrogate(U16 cp)
+ {
+ return cp >= lead_surrogate_min && cp <= trail_surrogate_max;
+ }
+
+ constexpr inline bool is_code_point_valid(code_point cp)
+ {
+ return cp <= code_point_max && !is_surrogate(cp);
+ }
+ } // namespace detail
+
+ template <typename T>
+ constexpr code_point make_code_point(T ch)
+ {
+ return static_cast<code_point>(ch);
+ }
+
+ /**
+ * Returns `true`, if `cp` is valid, e.g. is less than or equal to the
+ * maximum value for a code point (U+10FFFF), and is not a surrogate (U+D800
+ * to U+DFFF).
+ */
+ constexpr inline bool is_valid_code_point(code_point cp)
+ {
+ return detail::is_code_point_valid(cp);
+ }
+ /**
+ * Returns `true` if `cp` can be encoded in ASCII as-is (is between U+0 and
+ * U+7F)
+ */
+ constexpr inline bool is_ascii_code_point(code_point cp)
+ {
+ return cp <= 0x7f;
+ }
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/unicode/unicode.h b/src/third-party/scnlib/include/scn/unicode/unicode.h
new file mode 100644
index 0000000..011b0b9
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/unicode/unicode.h
@@ -0,0 +1,243 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+//
+// The contents of this file are based on utfcpp:
+// https://github.com/nemtrif/utfcpp
+// Copyright (c) 2006 Nemanja Trifunovic
+// Distributed under the Boost Software License, version 1.0
+
+#ifndef SCN_UNICODE_UNICODE_H
+#define SCN_UNICODE_UNICODE_H
+
+#include "utf16.h"
+#include "utf8.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ inline constexpr bool is_wide_multichar()
+ {
+ return sizeof(wchar_t) == 2;
+ }
+
+ inline constexpr bool is_multichar_type(char)
+ {
+ return true;
+ }
+ inline constexpr bool is_multichar_type(wchar_t)
+ {
+ return is_wide_multichar();
+ }
+
+ using utf8_tag = std::integral_constant<size_t, 1>;
+ using utf16_tag = std::integral_constant<size_t, 2>;
+ using utf32_tag = std::integral_constant<size_t, 4>;
+
+#define SCN_MAKE_UTF_TAG(CharT) \
+ std::integral_constant<size_t, sizeof(CharT)> {}
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> parse_code_point(I begin,
+ S end,
+ code_point& cp,
+ utf8_tag)
+ {
+ return utf8::parse_code_point(begin, end, cp);
+ }
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> parse_code_point(I begin,
+ S end,
+ code_point& cp,
+ utf16_tag)
+ {
+ return utf16::parse_code_point(begin, end, cp);
+ }
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> parse_code_point(I begin,
+ S end,
+ code_point& cp,
+ utf32_tag)
+ {
+ SCN_EXPECT(begin != end);
+ cp = make_code_point(*begin);
+ return {++begin};
+ }
+ } // namespace detail
+
+ /**
+ * Parses a Unicode code point from the range at `[begin, end)`, and writes
+ * it into `cp`.
+ *
+ * The encoding is determined by the size of the value type of the range.
+ * Let `n = sizeof(typename std::iterator_traits<I>::value_type)`.
+ * If `n == 1` -> UTF-8. If `n == 2` -> UTF-16. If `n == 4` -> UTF-32.
+ *
+ * `begin != end` must be `true`.
+ *
+ * On error, `cp` is not written into.
+ *
+ * \return On success, returns an iterator one-past the last code unit used
+ * to parse `cp`. If the code point is encoded incorrectly, returns
+ * `error::invalid_encoding`.
+ */
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> parse_code_point(I begin, S end, code_point& cp)
+ {
+ return detail::parse_code_point(
+ begin, end, cp,
+ SCN_MAKE_UTF_TAG(typename std::iterator_traits<I>::value_type));
+ }
+
+ namespace detail {
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> encode_code_point(I begin,
+ S end,
+ code_point cp,
+ utf8_tag)
+ {
+ return utf8::encode_code_point(begin, end, cp);
+ }
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> encode_code_point(I begin,
+ S end,
+ code_point cp,
+ utf16_tag)
+ {
+ return utf16::encode_code_point(begin, end, cp);
+ }
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> encode_code_point(I begin,
+ S end,
+ code_point cp,
+ utf32_tag)
+ {
+ SCN_EXPECT(begin + 1 >= end);
+ *begin++ = static_cast<uint32_t>(cp);
+ return {begin};
+ }
+ } // namespace detail
+
+ /**
+ * Writes the code point `cp` into `begin`, using the encoding determined by
+ * the type of `begin`.
+ *
+ * For more information on how the encoding is determined, see \ref
+ * parse_code_point().
+ *
+ * `end` must be reachable from `begin`, and must have enough room to encode
+ * the code point (4 code units for UTF-8, 2 for UTF-16, and 1 for UTF-32).
+ *
+ * \param begin Beginning of the range to write the result to
+ * \param end End of the range to write the result to
+ * \param cp Code point to encode
+ * \return On success, one-past the last code unit written.
+ * If `cp` was not a valid code point, returns `error::invalid_encoding`.
+ */
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> encode_code_point(I begin, S end, code_point cp)
+ {
+ return detail::encode_code_point(
+ begin, end, cp,
+ SCN_MAKE_UTF_TAG(typename std::iterator_traits<I>::value_type));
+ }
+
+ namespace detail {
+ template <typename T>
+ SCN_CONSTEXPR14 int get_sequence_length(T a, utf8_tag)
+ {
+ return utf8::get_sequence_length(a);
+ }
+ template <typename T>
+ SCN_CONSTEXPR14 int get_sequence_length(T a, utf16_tag)
+ {
+ return utf16::get_sequence_length(a);
+ }
+ template <typename T>
+ SCN_CONSTEXPR14 int get_sequence_length(T, utf32_tag)
+ {
+ return 1;
+ }
+ } // namespace detail
+
+ /**
+ * Returns the length of the code point starting from code unit `a` in code
+ * units.
+ *
+ * For information on how the encoding is determined, see \ref
+ * parse_code_point().
+ *
+ * \param a The first code unit in a code point.
+ *
+ * \return Length of the code point starting from `a`, in code units
+ * If the code point is encoded incorrectly, or this code unit is not the
+ * first code unit in a code point, returns 0.
+ */
+ template <typename T>
+ SCN_CONSTEXPR14 int get_sequence_length(T a)
+ {
+ return detail::get_sequence_length(a, SCN_MAKE_UTF_TAG(T));
+ }
+
+ namespace detail {
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<std::ptrdiff_t> code_point_distance(I begin,
+ S end,
+ utf8_tag)
+ {
+ return utf8::code_point_distance(begin, end);
+ }
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<std::ptrdiff_t> code_point_distance(I begin,
+ S end,
+ utf16_tag)
+ {
+ return utf16::code_point_distance(begin, end);
+ }
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<std::ptrdiff_t> code_point_distance(I begin,
+ S end,
+ utf32_tag)
+ {
+ return {end - begin};
+ }
+ } // namespace detail
+
+ /**
+ * Get the distance between two code points, in code points.
+ *
+ * `end >= begin` must be `true`.
+ * `begin` and `end` must both point to the first code units in a code
+ * point.
+ *
+ * \return The distance between `begin` and `end`, in code points. If the
+ * string was encoded incorrectly, returns `error::invalid_encoding`.
+ */
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<std::ptrdiff_t> code_point_distance(I begin, S end)
+ {
+ return detail::code_point_distance(
+ begin, end,
+ SCN_MAKE_UTF_TAG(typename std::iterator_traits<I>::value_type));
+ }
+
+#undef SCN_MAKE_UTF_TAG
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/unicode/utf16.h b/src/third-party/scnlib/include/scn/unicode/utf16.h
new file mode 100644
index 0000000..8d8a400
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/unicode/utf16.h
@@ -0,0 +1,139 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+//
+// The contents of this file are based on utfcpp:
+// https://github.com/nemtrif/utfcpp
+// Copyright (c) 2006 Nemanja Trifunovic
+// Distributed under the Boost Software License, version 1.0
+
+#ifndef SCN_UNICODE_UTF16_H
+#define SCN_UNICODE_UTF16_H
+
+#include "../detail/error.h"
+#include "../util/expected.h"
+#include "common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ namespace utf16 {
+ template <typename U16>
+ SCN_CONSTEXPR14 int get_sequence_length(U16 ch)
+ {
+ uint16_t lead = mask16(ch);
+ if (is_lead_surrogate(lead)) {
+ return 2;
+ }
+ if (SCN_UNLIKELY(is_trail_surrogate(lead))) {
+ return 0;
+ }
+ return 1;
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 error validate_next(I& it, S end, code_point& cp)
+ {
+ SCN_EXPECT(it != end);
+
+ uint16_t lead = mask16(*it);
+ if (is_lead_surrogate(lead)) {
+ ++it;
+ if (it == end) {
+ return {error::invalid_encoding,
+ "Lone utf16 lead surrogate"};
+ }
+ uint16_t trail = mask16(*it);
+ if (!is_trail_surrogate(trail)) {
+ return {error::invalid_encoding,
+ "Invalid utf16 trail surrogate"};
+ }
+ ++it;
+ cp = static_cast<code_point>(
+ static_cast<uint32_t>(lead << 10u) + trail +
+ surrogate_offset);
+ return {};
+ }
+ if (is_trail_surrogate(lead)) {
+ return {error::invalid_encoding,
+ "Lone utf16 trail surrogate"};
+ }
+
+ cp = static_cast<code_point>(*it);
+ ++it;
+ return {};
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> parse_code_point(I begin,
+ S end,
+ code_point& cp)
+ {
+ auto e = validate_next(begin, end, cp);
+ if (!e) {
+ return e;
+ }
+ return {begin};
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> encode_code_point(I begin,
+ S end,
+ code_point cp)
+ {
+ SCN_EXPECT(begin + 2 <= end);
+
+ if (!is_valid_code_point(cp)) {
+ return error(error::invalid_encoding,
+ "Invalid code point, cannot encode in UTF-16");
+ }
+
+ if (cp > 0xffffu) {
+ *begin++ = static_cast<uint16_t>(
+ (static_cast<uint32_t>(cp) >> 10u) + lead_offset);
+ *begin++ = static_cast<uint16_t>(
+ (static_cast<uint32_t>(cp) & 0x3ffu) +
+ trail_surrogate_min);
+ }
+ else {
+ *begin++ = static_cast<uint16_t>(cp);
+ }
+ return {begin};
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<std::ptrdiff_t> code_point_distance(
+ I begin,
+ S end)
+ {
+ std::ptrdiff_t dist{};
+ code_point cp{};
+ for (; begin < end; ++dist) {
+ auto e = validate_next(begin, end, cp);
+ if (!e) {
+ return e;
+ }
+ }
+ return {dist};
+ }
+ } // namespace utf16
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/unicode/utf8.h b/src/third-party/scnlib/include/scn/unicode/utf8.h
new file mode 100644
index 0000000..d2ee54d
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/unicode/utf8.h
@@ -0,0 +1,297 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+//
+// The contents of this file are based on utfcpp:
+// https://github.com/nemtrif/utfcpp
+// Copyright (c) 2006 Nemanja Trifunovic
+// Distributed under the Boost Software License, version 1.0
+
+#ifndef SCN_UNICODE_UTF8_H
+#define SCN_UNICODE_UTF8_H
+
+#include "../detail/error.h"
+#include "../util/expected.h"
+#include "common.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ namespace utf8 {
+ template <typename Octet>
+ constexpr bool is_trail(Octet o)
+ {
+ return (mask8(o) >> 6) == 2;
+ }
+
+ template <typename Octet>
+ SCN_CONSTEXPR14 int get_sequence_length(Octet ch)
+ {
+ uint8_t lead = detail::mask8(ch);
+ if (lead < 0x80) {
+ return 1;
+ }
+ else if ((lead >> 5) == 6) {
+ return 2;
+ }
+ else if ((lead >> 4) == 0xe) {
+ return 3;
+ }
+ else if ((lead >> 3) == 0x1e) {
+ return 4;
+ }
+ return 0;
+ }
+
+ SCN_CONSTEXPR14 bool is_overlong_sequence(code_point cp,
+ std::ptrdiff_t len)
+ {
+ if (cp < 0x80) {
+ if (len != 1) {
+ return true;
+ }
+ }
+ else if (cp < 0x800) {
+ if (len != 2) {
+ return true;
+ }
+ }
+ else if (cp < 0x10000) {
+ if (len != 3) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 error increase_safely(I& it, S end)
+ {
+ if (++it == end) {
+ return {error::invalid_encoding,
+ "Unexpected end of range when decoding utf8 "
+ "(partial codepoint)"};
+ }
+ if (!is_trail(*it)) {
+ return {error::invalid_encoding,
+ "Invalid utf8 codepoint parsed"};
+ }
+ return {};
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 error get_sequence_1(I& it, S end, code_point& cp)
+ {
+ SCN_EXPECT(it != end);
+ cp = make_code_point(mask8(*it));
+ return {};
+ }
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 error get_sequence_2(I& it, S end, code_point& cp)
+ {
+ SCN_EXPECT(it != end);
+ uint32_t c = mask8(*it);
+
+ auto e = increase_safely(it, end);
+ if (!e) {
+ return e;
+ }
+
+ c = static_cast<uint32_t>((c << 6u) & 0x7ffu) +
+ (static_cast<uint32_t>(*it) & 0x3fu);
+ cp = make_code_point(c);
+
+ return {};
+ }
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 error get_sequence_3(I& it, S end, code_point& cp)
+ {
+ SCN_EXPECT(it != end);
+ uint32_t c = mask8(*it);
+
+ auto e = increase_safely(it, end);
+ if (!e) {
+ return e;
+ }
+
+ c = static_cast<uint32_t>((c << 12u) & 0xffffu) +
+ (static_cast<uint32_t>(mask8(*it) << 6u) & 0xfffu);
+
+ e = increase_safely(it, end);
+ if (!e) {
+ return e;
+ }
+
+ c += static_cast<uint32_t>(*it) & 0x3fu;
+ cp = make_code_point(c);
+
+ return {};
+ }
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 error get_sequence_4(I& it, S end, code_point& cp)
+ {
+ SCN_EXPECT(it != end);
+ uint32_t c = mask8(*it);
+
+ auto e = increase_safely(it, end);
+ if (!e) {
+ return e;
+ }
+
+ c = ((c << 18u) & 0x1fffffu) +
+ (static_cast<uint32_t>(mask8(*it) << 12u) & 0x3ffffu);
+
+ e = increase_safely(it, end);
+ if (!e) {
+ return e;
+ }
+
+ c += static_cast<uint32_t>(mask8(*it) << 6u) & 0xfffu;
+
+ e = increase_safely(it, end);
+ if (!e) {
+ return e;
+ }
+
+ c += static_cast<uint32_t>(*it) & 0x3fu;
+ cp = make_code_point(c);
+
+ return {};
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 error validate_next(I& it, S end, code_point& cp)
+ {
+ SCN_EXPECT(it != end);
+
+ int len = get_sequence_length(*it);
+ error e{};
+ switch (len) {
+ case 1:
+ e = get_sequence_1(it, end, cp);
+ break;
+ case 2:
+ e = get_sequence_2(it, end, cp);
+ break;
+ case 3:
+ e = get_sequence_3(it, end, cp);
+ break;
+ case 4:
+ e = get_sequence_4(it, end, cp);
+ break;
+ default:
+ return {error::invalid_encoding,
+ "Invalid lead byte for utf8"};
+ }
+
+ if (!e) {
+ return e;
+ }
+ if (!is_valid_code_point(cp) || is_overlong_sequence(cp, len)) {
+ return {error::invalid_encoding, "Invalid utf8 code point"};
+ }
+ ++it;
+ return {};
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> parse_code_point(I begin,
+ S end,
+ code_point& cp)
+ {
+ code_point c{};
+ auto e = validate_next(begin, end, c);
+ if (e) {
+ cp = c;
+ return {begin};
+ }
+ return e;
+ }
+
+ template <typename I>
+ I append(code_point cp, I it)
+ {
+ SCN_EXPECT(is_code_point_valid(cp));
+
+ if (cp < 0x80) {
+ *(it++) = static_cast<uint8_t>(cp);
+ }
+ else if (cp < 0x800) {
+ *(it++) = static_cast<uint8_t>(
+ (static_cast<uint32_t>(cp) >> 6u) | 0xc0u);
+ *(it++) = static_cast<uint8_t>(
+ (static_cast<uint32_t>(cp) & 0x3fu) | 0x80u);
+ }
+ else if (cp < 0x10000) {
+ *(it++) = static_cast<uint8_t>(
+ (static_cast<uint32_t>(cp) >> 12u) | 0xe0u);
+ *(it++) = static_cast<uint8_t>(
+ ((static_cast<uint32_t>(cp) >> 6u) & 0x3fu) | 0x80u);
+ *(it++) = static_cast<uint8_t>(
+ (static_cast<uint32_t>(cp) & 0x3fu) | 0x80u);
+ }
+ else {
+ *(it++) = static_cast<uint8_t>(
+ (static_cast<uint32_t>(cp) >> 18u) | 0xf0u);
+ *(it++) = static_cast<uint8_t>(
+ ((static_cast<uint32_t>(cp) >> 12u) & 0x3fu) | 0x80u);
+ *(it++) = static_cast<uint8_t>(
+ ((static_cast<uint32_t>(cp) >> 6u) & 0x3fu) | 0x80u);
+ *(it++) = static_cast<uint8_t>(
+ (static_cast<uint32_t>(cp) & 0x3fu) | 0x80u);
+ }
+ return it;
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<I> encode_code_point(I begin,
+ S end,
+ code_point cp)
+ {
+ SCN_EXPECT(begin + 4 <= end);
+
+ if (!is_code_point_valid(cp)) {
+ return error(error::invalid_encoding,
+ "Invalid code point, cannot encode in UTF-8");
+ }
+ return {append(cp, begin)};
+ }
+
+ template <typename I, typename S>
+ SCN_CONSTEXPR14 expected<std::ptrdiff_t> code_point_distance(
+ I begin,
+ S end)
+ {
+ std::ptrdiff_t dist{};
+ code_point cp{};
+ for (; begin < end; ++dist) {
+ auto e = validate_next(begin, end, cp);
+ if (!e) {
+ return e;
+ }
+ }
+ return {dist};
+ }
+
+ } // namespace utf8
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/util/algorithm.h b/src/third-party/scnlib/include/scn/util/algorithm.h
new file mode 100644
index 0000000..a17b6b6
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/algorithm.h
@@ -0,0 +1,80 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_ALGORITHM_H
+#define SCN_UTIL_ALGORITHM_H
+
+#include "../detail/fwd.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ /**
+ * Implementation of `std::exchange` for C++11
+ */
+ template <typename T, typename U = T>
+ SCN_CONSTEXPR14 T exchange(T& obj, U&& new_value)
+ {
+ T old_value = SCN_MOVE(obj);
+ obj = SCN_FWD(new_value);
+ return old_value;
+ }
+
+ /**
+ * Implementation of `std::max` without including `<algorithm>`
+ */
+ template <typename T>
+ constexpr T max(T a, T b) noexcept
+ {
+ return (a < b) ? b : a;
+ }
+
+ /**
+ * Implementation of `std::min_element` without including `<algorithm>`
+ */
+ template <typename It>
+ SCN_CONSTEXPR14 It min_element(It first, It last)
+ {
+ if (first == last) {
+ return last;
+ }
+
+ It smallest = first;
+ ++first;
+ for (; first != last; ++first) {
+ if (*first < *smallest) {
+ smallest = first;
+ }
+ }
+ return smallest;
+ }
+
+ /**
+ * Implementation of `std::min` without including `<algorithm>`
+ */
+ template <typename T>
+ constexpr T min(T a, T b) noexcept
+ {
+ return (b < a) ? b : a;
+ }
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/util/array.h b/src/third-party/scnlib/include/scn/util/array.h
new file mode 100644
index 0000000..6c86488
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/array.h
@@ -0,0 +1,105 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_ARRAY_H
+#define SCN_UTIL_ARRAY_H
+
+#include "../detail/fwd.h"
+
+#include <cstdint>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ /**
+ * Implementation of `std::array` without including `<array>` (can be
+ * heavy-ish)
+ */
+ template <typename T, std::size_t N>
+ struct array {
+ static_assert(N > 0, "zero-sized array not supported");
+
+ using value_type = T;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using reference = T&;
+ using const_reference = const T&;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using iterator = pointer;
+ using const_iterator = const_pointer;
+
+ SCN_CONSTEXPR14 reference operator[](size_type i)
+ {
+ SCN_EXPECT(i < size());
+ return m_data[i];
+ }
+ SCN_CONSTEXPR14 const_reference operator[](size_type i) const
+ {
+ SCN_EXPECT(i < size());
+ return m_data[i];
+ }
+
+ SCN_CONSTEXPR14 iterator begin() noexcept
+ {
+ return m_data;
+ }
+ constexpr const_iterator begin() const noexcept
+ {
+ return m_data;
+ }
+ constexpr const_iterator cbegin() const noexcept
+ {
+ return m_data;
+ }
+
+ SCN_CONSTEXPR14 iterator end() noexcept
+ {
+ return m_data + N;
+ }
+ constexpr const_iterator end() const noexcept
+ {
+ return m_data + N;
+ }
+ constexpr const_iterator cend() const noexcept
+ {
+ return m_data + N;
+ }
+
+ SCN_CONSTEXPR14 pointer data() noexcept
+ {
+ return m_data;
+ }
+ constexpr const_pointer data() const noexcept
+ {
+ return m_data;
+ }
+
+ SCN_NODISCARD constexpr size_type size() const noexcept
+ {
+ return N;
+ }
+
+ T m_data[N];
+ };
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/util/expected.h b/src/third-party/scnlib/include/scn/util/expected.h
new file mode 100644
index 0000000..f7c9a82
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/expected.h
@@ -0,0 +1,158 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_EXPECTED_H
+#define SCN_UTIL_EXPECTED_H
+
+#include "memory.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ /**
+ * expected-like type.
+ * For situations where there can be a value in case of success or an error
+ * code.
+ */
+ template <typename T, typename Error, typename Enable>
+ class expected;
+
+ /**
+ * expected-like type for default-constructible success values.
+ * Not optimized for space-efficiency (both members are stored
+ * simultaneously).
+ * `error` is used as the error value and discriminant flag.
+ */
+ template <typename T, typename Error>
+ class expected<T,
+ Error,
+ typename std::enable_if<
+ std::is_default_constructible<T>::value>::type> {
+ public:
+ using success_type = T;
+ using error_type = Error;
+
+ constexpr expected() = default;
+ constexpr expected(success_type s) : m_s(s) {}
+ constexpr expected(error_type e) : m_e(e) {}
+
+ SCN_NODISCARD constexpr bool has_value() const noexcept
+ {
+ return m_e == Error{};
+ }
+ constexpr explicit operator bool() const noexcept
+ {
+ return has_value();
+ }
+ constexpr bool operator!() const noexcept
+ {
+ return !operator bool();
+ }
+
+ SCN_CONSTEXPR14 success_type& value() & noexcept
+ {
+ return m_s;
+ }
+ constexpr success_type value() const& noexcept
+ {
+ return m_s;
+ }
+ SCN_CONSTEXPR14 success_type value() && noexcept
+ {
+ return SCN_MOVE(m_s);
+ }
+
+ SCN_CONSTEXPR14 error_type& error() noexcept
+ {
+ return m_e;
+ }
+ constexpr error_type error() const noexcept
+ {
+ return m_e;
+ }
+
+ private:
+ success_type m_s{};
+ error_type m_e{error_type::success_tag()};
+ };
+
+ /**
+ * expected-like type for non-default-constructible success values.
+ * Not optimized for space-efficiency.
+ * `error` is used as the error value and discriminant flag.
+ */
+ template <typename T, typename Error>
+ class expected<T,
+ Error,
+ typename std::enable_if<
+ !std::is_default_constructible<T>::value>::type> {
+ public:
+ using success_type = T;
+ using success_storage = detail::erased_storage<T>;
+ using error_type = Error;
+
+ expected(success_type s) : m_s(SCN_MOVE(s)) {}
+ constexpr expected(error_type e) : m_e(e) {}
+
+ SCN_NODISCARD constexpr bool has_value() const noexcept
+ {
+ return m_e == Error{};
+ }
+ constexpr explicit operator bool() const noexcept
+ {
+ return has_value();
+ }
+ constexpr bool operator!() const noexcept
+ {
+ return !operator bool();
+ }
+
+ SCN_CONSTEXPR14 success_type& value() noexcept
+ {
+ return *m_s;
+ }
+ constexpr const success_type& value() const noexcept
+ {
+ return *m_s;
+ }
+
+ SCN_CONSTEXPR14 error_type& error() noexcept
+ {
+ return m_e;
+ }
+ constexpr error_type error() const noexcept
+ {
+ return m_e;
+ }
+
+ private:
+ success_storage m_s{};
+ error_type m_e{error_type::success_tag()};
+ };
+
+ template <typename T,
+ typename U = typename std::remove_cv<
+ typename std::remove_reference<T>::type>::type>
+ expected<U> make_expected(T&& val)
+ {
+ return expected<U>(std::forward<T>(val));
+ }
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/util/math.h b/src/third-party/scnlib/include/scn/util/math.h
new file mode 100644
index 0000000..4cca941
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/math.h
@@ -0,0 +1,121 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_MATH_H
+#define SCN_UTIL_MATH_H
+
+#include "../detail/fwd.h"
+
+#include <cmath>
+#include <limits>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename Integral>
+ SCN_CONSTEXPR14 int _max_digits(int base) noexcept
+ {
+ using lim = std::numeric_limits<Integral>;
+
+ char base8_digits[8] = {3, 5, 0, 11, 0, 0, 0, 21};
+
+ if (base == 10) {
+ return lim::digits10;
+ }
+ if (base == 8) {
+ return static_cast<int>(base8_digits[sizeof(Integral) - 1]);
+ }
+ if (base == lim::radix) {
+ return lim::digits;
+ }
+
+ auto i = lim::max();
+
+ Integral digits = 0;
+ while (i) {
+ i /= static_cast<Integral>(base);
+ digits++;
+ }
+ return static_cast<int>(digits);
+ }
+
+ /**
+ * Returns the maximum number of digits that an integer in base `base`
+ * can have, including the sign.
+ *
+ * If `base == 0`, uses `2` (longest), and adds 2 to the result, to
+ * accommodate for a base prefix (e.g. `0x`)
+ */
+ template <typename Integral>
+ SCN_CONSTEXPR14 int max_digits(int base) noexcept
+ {
+ auto b = base == 0 ? 2 : base;
+ auto d = _max_digits<Integral>(b) +
+ (std::is_signed<Integral>::value ? 1 : 0);
+ if (base == 0) {
+ return d + 2; // accommodate for 0x/0o
+ }
+ return d;
+ }
+
+ /**
+ * Implementation of `std::div`, which is constexpr pre-C++23
+ */
+ template <typename T>
+ constexpr std::pair<T, T> div(T l, T r) noexcept
+ {
+ return {l / r, l % r};
+ }
+
+ template <typename T>
+ struct zero_value;
+ template <>
+ struct zero_value<float> {
+ static constexpr float value = 0.0f;
+ };
+ template <>
+ struct zero_value<double> {
+ static constexpr double value = 0.0;
+ };
+ template <>
+ struct zero_value<long double> {
+ static constexpr long double value = 0.0l;
+ };
+
+ /**
+ * Returns `true` if `ch` is a digit for an integer in base `base`.
+ */
+ template <typename CharT>
+ bool is_base_digit(CharT ch, int base)
+ {
+ if (base <= 10) {
+ return ch >= static_cast<CharT>('0') &&
+ ch <= static_cast<CharT>('0') + base - 1;
+ }
+ return is_base_digit(ch, 10) ||
+ (ch >= static_cast<CharT>('a') &&
+ ch <= static_cast<CharT>('a') + base - 1) ||
+ (ch >= static_cast<CharT>('A') &&
+ ch <= static_cast<CharT>('A') + base - 1);
+ }
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/util/memory.h b/src/third-party/scnlib/include/scn/util/memory.h
new file mode 100644
index 0000000..9dfb970
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/memory.h
@@ -0,0 +1,404 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_MEMORY_H
+#define SCN_UTIL_MEMORY_H
+
+#include "meta.h"
+
+#include <cstring>
+#include <new>
+
+SCN_GCC_PUSH
+SCN_GCC_IGNORE("-Wnoexcept")
+#include <iterator>
+SCN_GCC_POP
+
+#if SCN_MSVC && SCN_HAS_STRING_VIEW
+#include <string_view>
+#endif
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename T>
+ struct pointer_traits;
+
+ template <typename T>
+ struct pointer_traits<T*> {
+ using pointer = T*;
+ using element_type = T;
+ using difference_type = std::ptrdiff_t;
+
+ template <typename U>
+ using rebind = U*;
+
+ template <typename U = T,
+ typename std::enable_if<!std::is_void<U>::value>::type* =
+ nullptr>
+ static constexpr pointer pointer_to(U& r) noexcept
+ {
+ return &r;
+ }
+ };
+
+ template <typename T>
+ constexpr T* to_address_impl(T* p, priority_tag<2>) noexcept
+ {
+ return p;
+ }
+ template <typename Ptr>
+ SCN_CONSTEXPR14 auto to_address_impl(const Ptr& p,
+ priority_tag<1>) noexcept
+ -> decltype(::scn::detail::pointer_traits<Ptr>::to_address(p))
+ {
+ return ::scn::detail::pointer_traits<Ptr>::to_address(p);
+ }
+ template <typename Ptr>
+ constexpr auto to_address_impl(const Ptr& p, priority_tag<0>) noexcept
+ -> decltype(::scn::detail::to_address_impl(p.operator->(),
+ priority_tag<2>{}))
+ {
+ return ::scn::detail::to_address_impl(p.operator->(),
+ priority_tag<2>{});
+ }
+
+ template <typename Ptr>
+ constexpr auto to_address(Ptr&& p) noexcept
+ -> decltype(::scn::detail::to_address_impl(SCN_FWD(p),
+ priority_tag<2>{}))
+ {
+ return ::scn::detail::to_address_impl(SCN_FWD(p),
+ priority_tag<2>{});
+ }
+
+#if SCN_WINDOWS
+ template <typename I, typename B, typename E>
+ SCN_CONSTEXPR14 auto to_address_safe(I&& p, B begin, E end) noexcept
+ -> decltype(to_address(SCN_FWD(p)))
+ {
+ if (p >= begin && p < end) {
+ return to_address(SCN_FWD(p));
+ }
+ if (begin == end) {
+ return to_address(SCN_FWD(p));
+ }
+ if (p == end) {
+ return to_address(SCN_FWD(p) - 1) + 1;
+ }
+ SCN_ENSURE(false);
+ SCN_UNREACHABLE;
+ }
+#else
+ template <typename I, typename B, typename E>
+ SCN_CONSTEXPR14 auto to_address_safe(I&& p, B, E) noexcept
+ -> decltype(to_address(SCN_FWD(p)))
+ {
+ return to_address(SCN_FWD(p));
+ }
+#endif
+
+ // Workaround for MSVC _String_view_iterator
+#if SCN_MSVC && SCN_HAS_STRING_VIEW
+ template <typename Traits>
+ struct pointer_traits<std::_String_view_iterator<Traits>> {
+ using iterator = std::_String_view_iterator<Traits>;
+ using pointer = typename iterator::pointer;
+ using element_type = typename iterator::value_type;
+ using difference_type = typename iterator::difference_type;
+
+ static constexpr pointer to_address(const iterator& it) noexcept
+ {
+ // operator-> of _String_view_iterator
+ // is checked for past-the-end dereference,
+ // even though operator-> isn't dereferencing anything :)))
+ return it._Unwrapped();
+ }
+ };
+#endif
+
+ template <typename T>
+ constexpr T* launder(T* p) noexcept
+ {
+#if SCN_HAS_LAUNDER
+ return std::launder(p);
+#else
+ return p;
+#endif
+ }
+
+ template <typename ForwardIt, typename T>
+ void uninitialized_fill(ForwardIt first,
+ ForwardIt last,
+ const T& value,
+ std::true_type) noexcept
+ {
+ using value_type =
+ typename std::iterator_traits<ForwardIt>::value_type;
+ const auto dist = static_cast<size_t>(std::distance(first, last)) *
+ sizeof(value_type);
+ std::memset(&*first, static_cast<unsigned char>(value), dist);
+ }
+ template <typename ForwardIt, typename T>
+ void uninitialized_fill(ForwardIt first,
+ ForwardIt last,
+ const T& value,
+ std::false_type) noexcept
+ {
+ using value_type =
+ typename std::iterator_traits<ForwardIt>::value_type;
+ ForwardIt current = first;
+ for (; current != last; ++current) {
+ ::new (static_cast<void*>(std::addressof(*current)))
+ value_type(value);
+ }
+ }
+ template <typename ForwardIt, typename T>
+ void uninitialized_fill(ForwardIt first,
+ ForwardIt last,
+ const T& value) noexcept
+ {
+ constexpr bool B = std::is_trivially_copyable<T>::value &&
+ std::is_pointer<ForwardIt>::value &&
+ sizeof(T) == 1;
+ return uninitialized_fill(first, last, value,
+ std::integral_constant<bool, B>{});
+ }
+
+ template <typename ForwardIt>
+ void uninitialized_fill_default_construct(ForwardIt first,
+ ForwardIt last) noexcept
+ {
+ using value_type =
+ typename std::iterator_traits<ForwardIt>::value_type;
+ ForwardIt current = first;
+ for (; current != last; ++current) {
+ ::new (static_cast<void*>(std::addressof(*current))) value_type;
+ }
+ }
+ template <typename ForwardIt>
+ void uninitialized_fill_value_init(ForwardIt first,
+ ForwardIt last) noexcept
+ {
+ using value_type =
+ typename std::iterator_traits<ForwardIt>::value_type;
+ ForwardIt current = first;
+ for (; current != last; ++current) {
+ ::new (static_cast<void*>(std::addressof(*current)))
+ value_type();
+ }
+ }
+
+ template <typename InputIt,
+ typename ForwardIt,
+ typename std::enable_if<
+ !std::is_trivially_copyable<typename std::iterator_traits<
+ ForwardIt>::value_type>::value>::type* = nullptr>
+ ForwardIt uninitialized_copy(InputIt first,
+ InputIt last,
+ ForwardIt d_first) noexcept
+ {
+ using value_type =
+ typename std::iterator_traits<ForwardIt>::value_type;
+ ForwardIt current = d_first;
+ for (; first != last; ++first, (void)++current) {
+ ::new (static_cast<void*>(std::addressof(*current)))
+ value_type(*first);
+ }
+ return current;
+ }
+ template <typename InputIt,
+ typename ForwardIt,
+ typename std::enable_if<
+ std::is_trivially_copyable<typename std::iterator_traits<
+ ForwardIt>::value_type>::value>::type* = nullptr>
+ ForwardIt uninitialized_copy(InputIt first,
+ InputIt last,
+ ForwardIt d_first) noexcept
+ {
+ using value_type =
+ typename std::iterator_traits<ForwardIt>::value_type;
+ using pointer = typename std::iterator_traits<ForwardIt>::pointer;
+ auto ptr =
+ std::memcpy(std::addressof(*d_first), std::addressof(*first),
+ static_cast<size_t>(std::distance(first, last)) *
+ sizeof(value_type));
+ return ForwardIt{static_cast<pointer>(ptr)};
+ }
+
+ template <typename InputIt,
+ typename ForwardIt,
+ typename std::enable_if<
+ !std::is_trivially_copyable<typename std::iterator_traits<
+ ForwardIt>::value_type>::value>::type* = nullptr>
+ ForwardIt uninitialized_move(InputIt first,
+ InputIt last,
+ ForwardIt d_first) noexcept
+ {
+ using value_type =
+ typename std::iterator_traits<ForwardIt>::value_type;
+ ForwardIt current = d_first;
+ for (; first != last; ++first, (void)++current) {
+ ::new (static_cast<void*>(std::addressof(*current)))
+ value_type(std::move(*first));
+ }
+ return current;
+ }
+ template <typename InputIt,
+ typename ForwardIt,
+ typename std::enable_if<
+ std::is_trivially_copyable<typename std::iterator_traits<
+ ForwardIt>::value_type>::value>::type* = nullptr>
+ ForwardIt uninitialized_move(InputIt first,
+ InputIt last,
+ ForwardIt d_first) noexcept
+ {
+ using value_type =
+ typename std::iterator_traits<ForwardIt>::value_type;
+ using pointer = typename std::iterator_traits<ForwardIt>::pointer;
+ auto ptr =
+ std::memcpy(std::addressof(*d_first), std::addressof(*first),
+ static_cast<size_t>(std::distance(first, last)) *
+ sizeof(value_type));
+ return ForwardIt(static_cast<pointer>(ptr));
+ }
+
+ template <typename T>
+ class SCN_TRIVIAL_ABI erased_storage {
+ public:
+ using value_type = T;
+ using pointer = T*;
+ using storage_type = unsigned char[sizeof(T)];
+
+ constexpr erased_storage() noexcept = default;
+
+ erased_storage(T val) noexcept(
+ std::is_nothrow_move_constructible<T>::value)
+ : m_ptr(::new (static_cast<void*>(&m_data)) T(SCN_MOVE(val)))
+ {
+ }
+
+ erased_storage(const erased_storage& other)
+ : m_ptr(other ? ::new (static_cast<void*>(&m_data))
+ T(other.get())
+ : nullptr)
+ {
+ }
+ erased_storage& operator=(const erased_storage& other)
+ {
+ _destruct();
+ if (other) {
+ m_ptr = ::new (static_cast<void*>(&m_data)) T(other.get());
+ }
+ return *this;
+ }
+
+ erased_storage(erased_storage&& other) noexcept
+ : m_ptr(other ? ::new (static_cast<void*>(&m_data))
+ T(SCN_MOVE(other.get()))
+ : nullptr)
+ {
+ other.m_ptr = nullptr;
+ }
+ erased_storage& operator=(erased_storage&& other) noexcept
+ {
+ _destruct();
+ if (other) {
+ m_ptr = ::new (static_cast<void*>(&m_data))
+ T(SCN_MOVE(other.get()));
+ other.m_ptr = nullptr;
+ }
+ return *this;
+ }
+
+ ~erased_storage() noexcept
+ {
+ _destruct();
+ }
+
+ SCN_NODISCARD constexpr bool has_value() const noexcept
+ {
+ return m_ptr != nullptr;
+ }
+ constexpr explicit operator bool() const noexcept
+ {
+ return has_value();
+ }
+
+ SCN_CONSTEXPR14 T& get() noexcept
+ {
+ SCN_EXPECT(has_value());
+ return _get();
+ }
+ SCN_CONSTEXPR14 const T& get() const noexcept
+ {
+ SCN_EXPECT(has_value());
+ return _get();
+ }
+
+ SCN_CONSTEXPR14 T& operator*() noexcept
+ {
+ SCN_EXPECT(has_value());
+ return _get();
+ }
+ SCN_CONSTEXPR14 const T& operator*() const noexcept
+ {
+ SCN_EXPECT(has_value());
+ return _get();
+ }
+
+ SCN_CONSTEXPR14 T* operator->() noexcept
+ {
+ return m_ptr;
+ }
+ SCN_CONSTEXPR14 const T* operator->() const noexcept
+ {
+ return m_ptr;
+ }
+
+ private:
+ void _destruct()
+ {
+ if (m_ptr) {
+ _get().~T();
+ }
+ m_ptr = nullptr;
+ }
+ static pointer _toptr(storage_type& data)
+ {
+ return ::scn::detail::launder(
+ reinterpret_cast<T*>(reinterpret_cast<void*>(data.data())));
+ }
+ SCN_CONSTEXPR14 T& _get() noexcept
+ {
+ return *m_ptr;
+ }
+ SCN_CONSTEXPR14 const T& _get() const noexcept
+ {
+ return *m_ptr;
+ }
+
+ alignas(T) storage_type m_data{};
+ pointer m_ptr{nullptr};
+ };
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/util/meta.h b/src/third-party/scnlib/include/scn/util/meta.h
new file mode 100644
index 0000000..83b738c
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/meta.h
@@ -0,0 +1,77 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_META_H
+#define SCN_UTIL_META_H
+
+#include "../detail/fwd.h"
+
+#include <type_traits>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename... Ts>
+ struct make_void {
+ using type = void;
+ };
+ template <typename... Ts>
+ using void_t = typename make_void<Ts...>::type;
+
+ template <typename... T>
+ void valid_expr(T&&...);
+
+ template <typename T>
+ struct remove_cvref {
+ using type = typename std::remove_cv<
+ typename std::remove_reference<T>::type>::type;
+ };
+ template <typename T>
+ using remove_cvref_t = typename remove_cvref<T>::type;
+
+ // Stolen from range-v3
+ template <typename T>
+ struct static_const {
+ static constexpr T value{};
+ };
+ template <typename T>
+ constexpr T static_const<T>::value;
+
+ template <std::size_t I>
+ struct priority_tag : priority_tag<I - 1> {
+ };
+ template <>
+ struct priority_tag<0> {
+ };
+
+ struct dummy_type {
+ };
+
+ template <typename T>
+ struct dependent_false : std::false_type {
+ };
+
+ template <typename T>
+ using integer_type_for_char = typename std::
+ conditional<std::is_signed<T>::value, int, unsigned>::type;
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/util/optional.h b/src/third-party/scnlib/include/scn/util/optional.h
new file mode 100644
index 0000000..9c0c808
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/optional.h
@@ -0,0 +1,105 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_OPTIONAL_H
+#define SCN_UTIL_OPTIONAL_H
+
+#include "memory.h"
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ struct nullopt_t {
+ };
+ namespace {
+ static constexpr auto& nullopt = detail::static_const<nullopt_t>::value;
+ }
+
+ /**
+ * A very lackluster optional implementation.
+ * Useful when scanning non-default-constructible types, especially with
+ * <tuple_return.h>:
+ *
+ * \code{.cpp}
+ * // implement scn::scanner for optional<mytype>
+ * optional<mytype> val;
+ * scn::scan(source, "{}", val);
+ *
+ * // with tuple_return:
+ * auto [result, val] = scn::scan_tuple<optional<mytype>>(source, "{}");
+ * \endcode
+ */
+ template <typename T>
+ class optional {
+ public:
+ using value_type = T;
+ using storage_type = detail::erased_storage<T>;
+
+ optional() = default;
+ optional(nullopt_t) : m_storage{} {}
+
+ optional(value_type val) : m_storage(SCN_MOVE(val)) {}
+ optional& operator=(value_type val)
+ {
+ m_storage = storage_type(SCN_MOVE(val));
+ return *this;
+ }
+
+ SCN_NODISCARD constexpr bool has_value() const noexcept
+ {
+ return m_storage.operator bool();
+ }
+ constexpr explicit operator bool() const noexcept
+ {
+ return has_value();
+ }
+
+ SCN_CONSTEXPR14 T& get() noexcept
+ {
+ return m_storage.get();
+ }
+ SCN_CONSTEXPR14 const T& get() const noexcept
+ {
+ return m_storage.get();
+ }
+
+ SCN_CONSTEXPR14 T& operator*() noexcept
+ {
+ return get();
+ }
+ SCN_CONSTEXPR14 const T& operator*() const noexcept
+ {
+ return get();
+ }
+
+ SCN_CONSTEXPR14 T* operator->() noexcept
+ {
+ return m_storage.operator->();
+ }
+ SCN_CONSTEXPR14 const T* operator->() const noexcept
+ {
+ return m_storage.operator->();
+ }
+
+ private:
+ storage_type m_storage;
+ };
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/include/scn/util/small_vector.h b/src/third-party/scnlib/include/scn/util/small_vector.h
new file mode 100644
index 0000000..93a5514
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/small_vector.h
@@ -0,0 +1,788 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_SMALL_VECTOR_H
+#define SCN_UTIL_SMALL_VECTOR_H
+
+#include "math.h"
+#include "memory.h"
+
+#include <cstdint>
+#include <cstring>
+
+SCN_GCC_PUSH
+SCN_GCC_IGNORE("-Wnoexcept")
+#include <iterator>
+SCN_GCC_POP
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename Iter>
+ std::reverse_iterator<Iter> make_reverse_iterator(Iter i)
+ {
+ return std::reverse_iterator<Iter>(i);
+ }
+
+ class small_vector_base {
+ static SCN_CONSTEXPR14 uint64_t _next_pow2_64(uint64_t x) noexcept
+ {
+ --x;
+ x |= (x >> 1);
+ x |= (x >> 2);
+ x |= (x >> 4);
+ x |= (x >> 8);
+ x |= (x >> 16);
+ x |= (x >> 32);
+ return x + 1;
+ }
+ static SCN_CONSTEXPR14 uint32_t _next_pow2_32(uint32_t x) noexcept
+ {
+ --x;
+ x |= (x >> 1);
+ x |= (x >> 2);
+ x |= (x >> 4);
+ x |= (x >> 8);
+ x |= (x >> 16);
+ return x + 1;
+ }
+
+ protected:
+ size_t next_pow2(size_t x)
+ {
+ SCN_MSVC_PUSH
+ SCN_MSVC_IGNORE(4127) // conditional expression is constant
+ if (sizeof(size_t) == sizeof(uint64_t)) {
+ return static_cast<size_t>(
+ _next_pow2_64(static_cast<uint64_t>(x)));
+ }
+ SCN_MSVC_POP
+ return static_cast<size_t>(
+ _next_pow2_32(static_cast<uint32_t>(x)));
+ }
+ };
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wpadded")
+
+ template <typename T, size_t N>
+ struct basic_stack_storage {
+ alignas(T) unsigned char data[N * sizeof(T)];
+
+ T* reinterpret_data()
+ {
+ return ::scn::detail::launder(reinterpret_unconstructed_data());
+ }
+ const T* reinterpret_data() const
+ {
+ return ::scn::detail::launder(reinterpret_unconstructed_data());
+ }
+
+ SCN_NODISCARD T* reinterpret_unconstructed_data()
+ {
+ return static_cast<T*>(static_cast<void*>(data));
+ }
+ SCN_NODISCARD const T* reinterpret_unconstructed_data() const
+ {
+ return static_cast<const T*>(static_cast<const void*>(data));
+ }
+
+ SCN_NODISCARD SCN_CONSTEXPR14 unsigned char*
+ get_unconstructed_data()
+ {
+ return data;
+ }
+ SCN_NODISCARD constexpr const unsigned char*
+ get_unconstructed_data() const
+ {
+ return data;
+ }
+ };
+
+ // -Wpadded
+ SCN_CLANG_POP
+
+ template <typename T>
+ constexpr T constexpr_max(T val)
+ {
+ return val;
+ }
+ template <typename T, typename... Ts>
+ constexpr T constexpr_max(T val, Ts... a)
+ {
+ return val > constexpr_max(a...) ? val : constexpr_max(a...);
+ }
+
+ template <typename T>
+ struct alignas(constexpr_max(alignof(T),
+ alignof(T*))) basic_stack_storage<T, 0> {
+ T* reinterpret_data()
+ {
+ return nullptr;
+ }
+ const T* reinterpret_data() const
+ {
+ return nullptr;
+ }
+
+ T* reinterpret_unconstructed_data()
+ {
+ return nullptr;
+ }
+ const T* reinterpret_unconstructed_data() const
+ {
+ return nullptr;
+ }
+
+ unsigned char* get_unconstructed_data()
+ {
+ return nullptr;
+ }
+ const unsigned char* get_unconstructed_data() const
+ {
+ return nullptr;
+ }
+ };
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wpadded")
+
+ /**
+ * A contiguous container, that stores its values in the stack, if
+ * `size() <= StackN`
+ */
+ template <typename T, size_t StackN>
+ class small_vector : protected small_vector_base {
+ public:
+ using value_type = T;
+ using size_type = size_t;
+ using difference_type = std::ptrdiff_t;
+ using reference = T&;
+ using const_reference = const T&;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using iterator = pointer;
+ using const_iterator = const_pointer;
+ using reverse_iterator = std::reverse_iterator<pointer>;
+ using const_reverse_iterator = std::reverse_iterator<const_pointer>;
+
+ struct stack_storage : basic_stack_storage<T, StackN> {
+ };
+ struct heap_storage {
+ size_type cap{0};
+ };
+
+ small_vector() noexcept
+ : m_ptr(_construct_stack_storage()
+ .reinterpret_unconstructed_data())
+ {
+ SCN_MSVC_PUSH
+ SCN_MSVC_IGNORE(4127) // conditional expression is constant
+
+ if (StackN == 0) {
+ _destruct_stack_storage();
+ _construct_heap_storage();
+ }
+
+ SCN_MSVC_POP
+
+ SCN_ENSURE(size() == 0);
+ }
+
+ explicit small_vector(size_type count, const T& value)
+ {
+ if (!can_be_small(count)) {
+ auto& heap = _construct_heap_storage();
+ auto cap = next_pow2(count);
+ auto storage_ptr = new unsigned char[count * sizeof(T)];
+ auto ptr =
+ static_cast<pointer>(static_cast<void*>(storage_ptr));
+ uninitialized_fill(ptr, ptr + count, value);
+
+ heap.cap = cap;
+ m_size = count;
+ m_ptr = ::scn::detail::launder(ptr);
+ }
+ else {
+ auto& stack = _construct_stack_storage();
+ uninitialized_fill(
+ stack.reinterpret_unconstructed_data(),
+ stack.reinterpret_unconstructed_data() + StackN, value);
+ m_size = count;
+ m_ptr = stack.reinterpret_data();
+ }
+
+ SCN_ENSURE(data());
+ SCN_ENSURE(size() == count);
+ SCN_ENSURE(capacity() >= size());
+ }
+
+ explicit small_vector(size_type count)
+ {
+ if (!can_be_small(count)) {
+ auto& heap = _construct_heap_storage();
+ auto cap = next_pow2(count);
+ auto storage_ptr = new unsigned char[count * sizeof(T)];
+ auto ptr =
+ static_cast<pointer>(static_cast<void*>(storage_ptr));
+ uninitialized_fill_value_init(ptr, ptr + count);
+ heap.cap = cap;
+ m_size = count;
+ m_ptr = ::scn::detail::launder(ptr);
+ }
+ else {
+ auto& stack = _construct_stack_storage();
+ uninitialized_fill_value_init(
+ stack.reinterpret_unconstructed_data(),
+ stack.reinterpret_unconstructed_data() + count);
+ m_size = count;
+ m_ptr = stack.reinterpret_data();
+ }
+
+ SCN_ENSURE(data());
+ SCN_ENSURE(size() == count);
+ SCN_ENSURE(capacity() >= size());
+ }
+
+ small_vector(const small_vector& other)
+ {
+ if (other.empty()) {
+ auto& stack = _construct_stack_storage();
+ m_ptr = stack.reinterpret_unconstructed_data();
+ return;
+ }
+
+ auto s = other.size();
+ if (!other.is_small()) {
+ auto& heap = _construct_heap_storage();
+ auto cap = other.capacity();
+ auto optr = other.data();
+
+ auto storage_ptr = new unsigned char[cap * sizeof(T)];
+ auto ptr =
+ static_cast<pointer>(static_cast<void*>(storage_ptr));
+ uninitialized_copy(optr, optr + s, ptr);
+
+ m_ptr = ::scn::detail::launder(ptr);
+ m_size = s;
+ heap.cap = cap;
+ }
+ else {
+ auto& stack = _construct_stack_storage();
+ auto optr = other.data();
+ uninitialized_copy(optr, optr + s,
+ stack.reinterpret_unconstructed_data());
+ m_size = s;
+ m_ptr = stack.reinterpret_data();
+ }
+
+ SCN_ENSURE(data());
+ SCN_ENSURE(other.data());
+ SCN_ENSURE(other.size() == size());
+ SCN_ENSURE(other.capacity() == capacity());
+ }
+ small_vector(small_vector&& other) noexcept
+ {
+ if (other.empty()) {
+ auto& stack = _construct_stack_storage();
+ m_ptr = stack.reinterpret_unconstructed_data();
+ return;
+ }
+
+ auto s = other.size();
+ if (!other.is_small()) {
+ auto& heap = _construct_heap_storage();
+ m_ptr = other.data();
+
+ m_size = s;
+ heap.cap = other.capacity();
+ }
+ else {
+ auto& stack = _construct_stack_storage();
+ auto optr = other.data();
+ uninitialized_move(optr, optr + s,
+ stack.reinterpret_unconstructed_data());
+
+ m_size = s;
+ other._destruct_elements();
+ }
+ other.m_ptr = nullptr;
+
+ SCN_ENSURE(data());
+ }
+
+ small_vector& operator=(const small_vector& other)
+ {
+ _destruct_elements();
+
+ if (other.empty()) {
+ return *this;
+ }
+
+ SCN_ASSERT(size() == 0, "");
+
+ // this other
+ // s s false || true
+ // s h false || false second
+ // h s true || true
+ // h h true || false
+ if (!is_small() || other.is_small()) {
+ uninitialized_copy(other.data(),
+ other.data() + other.size(), data());
+ m_ptr = ::scn::detail::launder(data());
+ m_size = other.size();
+ if (!other.is_small()) {
+ _get_heap().cap = other.capacity();
+ }
+ }
+ else {
+ _destruct_stack_storage();
+ auto& heap = _construct_heap_storage();
+
+ auto cap = next_pow2(other.size());
+ auto storage_ptr = new unsigned char[cap * sizeof(T)];
+ auto ptr =
+ static_cast<pointer>(static_cast<void*>(storage_ptr));
+ uninitialized_copy(other.data(),
+ other.data() + other.size(), ptr);
+ m_ptr = ::scn::detail::launder(ptr);
+ m_size = other.size();
+ heap.cap = cap;
+ }
+ return *this;
+ }
+
+ small_vector& operator=(small_vector&& other) noexcept
+ {
+ _destruct_elements();
+
+ if (other.empty()) {
+ return *this;
+ }
+
+ SCN_ASSERT(size() == 0, "");
+
+ if (!is_small() && !other.is_small()) {
+ if (!is_small()) {
+ if (capacity() != 0) {
+ delete[] ::scn::detail::launder(
+ static_cast<unsigned char*>(
+ static_cast<void*>(m_ptr)));
+ }
+ }
+
+ m_ptr = other.data();
+ m_size = other.size();
+ _get_heap().cap = other.capacity();
+ }
+ else if (!is_small() || other.is_small()) {
+ uninitialized_move(other.data(),
+ other.data() + other.size(), data());
+ m_size = other.size();
+ other._destruct_elements();
+ }
+ else {
+ _destruct_stack_storage();
+ auto& heap = _construct_heap_storage();
+
+ m_ptr = other.data();
+ m_size = other.size();
+ heap.cap = other.capacity();
+ }
+
+ other.m_ptr = nullptr;
+
+ return *this;
+ }
+
+ ~small_vector()
+ {
+ _destruct();
+ }
+
+ SCN_NODISCARD SCN_CONSTEXPR14 pointer data() noexcept
+ {
+ return m_ptr;
+ }
+ SCN_NODISCARD constexpr const_pointer data() const noexcept
+ {
+ return m_ptr;
+ }
+ SCN_NODISCARD constexpr size_type size() const noexcept
+ {
+ return m_size;
+ }
+ SCN_NODISCARD size_type capacity() const noexcept
+ {
+ if (SCN_LIKELY(is_small())) {
+ return StackN;
+ }
+ return _get_heap().cap;
+ }
+
+ SCN_NODISCARD constexpr bool empty() const noexcept
+ {
+ return size() == 0;
+ }
+
+ SCN_NODISCARD bool is_small() const noexcept
+ {
+ // oh so very ub
+ return m_ptr == reinterpret_cast<const_pointer>(
+ std::addressof(m_stack_storage));
+ }
+ constexpr static bool can_be_small(size_type n) noexcept
+ {
+ return n <= StackN;
+ }
+
+ SCN_CONSTEXPR14 reference operator[](size_type pos)
+ {
+ SCN_EXPECT(pos < size());
+ return *(begin() + pos);
+ }
+ SCN_CONSTEXPR14 const_reference operator[](size_type pos) const
+ {
+ SCN_EXPECT(pos < size());
+ return *(begin() + pos);
+ }
+
+ SCN_CONSTEXPR14 reference front()
+ {
+ SCN_EXPECT(!empty());
+ return *begin();
+ }
+ SCN_CONSTEXPR14 const_reference front() const
+ {
+ SCN_EXPECT(!empty());
+ return *begin();
+ }
+
+ SCN_CONSTEXPR14 reference back()
+ {
+ SCN_EXPECT(!empty());
+ return *(end() - 1);
+ }
+ SCN_CONSTEXPR14 const_reference back() const
+ {
+ SCN_EXPECT(!empty());
+ return *(end() - 1);
+ }
+
+ SCN_CONSTEXPR14 iterator begin() noexcept
+ {
+ return data();
+ }
+ constexpr const_iterator begin() const noexcept
+ {
+ return data();
+ }
+ constexpr const_iterator cbegin() const noexcept
+ {
+ return begin();
+ }
+
+ SCN_CONSTEXPR14 iterator end() noexcept
+ {
+ return begin() + size();
+ }
+ constexpr const_iterator end() const noexcept
+ {
+ return begin() + size();
+ }
+ constexpr const_iterator cend() const noexcept
+ {
+ return end();
+ }
+
+ SCN_CONSTEXPR14 reverse_iterator rbegin() noexcept
+ {
+ return make_reverse_iterator(end());
+ }
+ constexpr const_reverse_iterator rbegin() const noexcept
+ {
+ return make_reverse_iterator(end());
+ }
+ constexpr const_reverse_iterator crbegin() const noexcept
+ {
+ return rbegin();
+ }
+
+ SCN_CONSTEXPR14 reverse_iterator rend() noexcept
+ {
+ return make_reverse_iterator(begin());
+ }
+ constexpr const_reverse_iterator rend() const noexcept
+ {
+ return make_reverse_iterator(begin());
+ }
+ constexpr const_reverse_iterator crend() const noexcept
+ {
+ return rend();
+ }
+
+ SCN_NODISCARD
+ constexpr size_type max_size() const noexcept
+ {
+ return std::numeric_limits<size_type>::max();
+ }
+
+ void make_small() noexcept
+ {
+ if (is_small() || !can_be_small(size())) {
+ return;
+ }
+
+ stack_storage s;
+ uninitialized_move(begin(), end(),
+ s.reinterpret_unconstructed_data());
+ auto tmp_size = size();
+
+ _destruct();
+ auto& stack = _construct_stack_storage();
+ uninitialized_move(s.reinterpret_data(),
+ s.reinterpret_data() + tmp_size,
+ stack.reinterpret_unconstructed_data());
+ m_size = tmp_size;
+ }
+
+ void reserve(size_type new_cap)
+ {
+ if (new_cap <= capacity()) {
+ return;
+ }
+ _realloc(next_pow2(new_cap));
+ }
+
+ void shrink_to_fit()
+ {
+ if (is_small()) {
+ return;
+ }
+ if (!can_be_small(size())) {
+ _realloc(size());
+ }
+ else {
+ make_small();
+ }
+ }
+
+ void clear() noexcept
+ {
+ _destruct_elements();
+ }
+
+ iterator erase(iterator pos)
+ {
+ if (pos == end()) {
+ pos->~T();
+ m_size = size() - 1;
+ return end();
+ }
+ else {
+ for (auto it = pos; it != end(); ++it) {
+ it->~T();
+ ::new (static_cast<void*>(it)) T(std::move(*(it + 1)));
+ }
+ (end() - 1)->~T();
+ m_size = size() - 1;
+ return pos;
+ }
+ }
+
+ iterator erase(iterator b, iterator e)
+ {
+ if (begin() == end()) {
+ return b;
+ }
+ if (e == end()) {
+ auto n = static_cast<size_t>(std::distance(b, e));
+ for (auto it = b; it != e; ++it) {
+ it->~T();
+ }
+ m_size = size() - n;
+ return end();
+ }
+ SCN_ENSURE(false);
+ SCN_UNREACHABLE;
+ }
+
+ void push_back(const T& value)
+ {
+ ::new (_prepare_push_back()) T(value);
+ m_size = size() + 1;
+ }
+ void push_back(T&& value)
+ {
+ ::new (_prepare_push_back()) T(std::move(value));
+ m_size = size() + 1;
+ }
+
+ template <typename... Args>
+ reference emplace_back(Args&&... args)
+ {
+ ::new (_prepare_push_back()) T(SCN_FWD(args)...);
+ m_size = size() + 1;
+ return back();
+ }
+
+ void pop_back()
+ {
+ back().~T();
+ m_size = size() - 1;
+ }
+
+ void resize(size_type count)
+ {
+ if (count > size()) {
+ if (count > capacity()) {
+ _realloc(next_pow2(capacity()));
+ }
+ uninitialized_fill_value_init(begin() + size(),
+ begin() + count);
+ }
+ else {
+ for (auto it = begin() + count; it != end(); ++it) {
+ it->~T();
+ }
+ }
+ m_size = count;
+ }
+
+ SCN_CONSTEXPR14 void swap(small_vector& other) noexcept
+ {
+ small_vector tmp{SCN_MOVE(other)};
+ other = std::move(*this);
+ *this = std::move(tmp);
+ }
+
+ private:
+ stack_storage& _construct_stack_storage() noexcept
+ {
+ ::new (std::addressof(m_stack_storage)) stack_storage;
+ m_ptr = m_stack_storage.reinterpret_unconstructed_data();
+ return m_stack_storage;
+ }
+ heap_storage& _construct_heap_storage() noexcept
+ {
+ ::new (std::addressof(m_heap_storage)) heap_storage;
+ m_ptr = nullptr;
+ return m_heap_storage;
+ }
+
+ void _destruct_stack_storage() noexcept
+ {
+ _get_stack().~stack_storage();
+ }
+ void _destruct_heap_storage() noexcept
+ {
+ if (capacity() != 0) {
+ delete[] static_cast<unsigned char*>(
+ static_cast<void*>(m_ptr));
+ }
+ _get_heap().~heap_storage();
+ }
+
+ void _destruct_elements() noexcept
+ {
+ const auto s = size();
+ for (size_type i = 0; i != s; ++i) {
+ m_ptr[i].~T();
+ }
+ m_size = 0;
+ }
+
+ void _destruct() noexcept
+ {
+ _destruct_elements();
+ if (SCN_UNLIKELY(!is_small())) {
+ _destruct_heap_storage();
+ }
+ else {
+ _destruct_stack_storage();
+ }
+ }
+
+ void _realloc(size_type new_cap)
+ {
+ auto storage_ptr = new unsigned char[new_cap * sizeof(T)];
+ auto ptr =
+ static_cast<pointer>(static_cast<void*>(storage_ptr));
+ auto n = size();
+ uninitialized_move(begin(), end(), ptr);
+ _destruct();
+ auto& heap = [this]() -> heap_storage& {
+ if (is_small()) {
+ return _construct_heap_storage();
+ }
+ return _get_heap();
+ }();
+ m_ptr = ptr;
+ m_size = n;
+ heap.cap = new_cap;
+ }
+
+ void* _prepare_push_back()
+ {
+ if (SCN_UNLIKELY(size() == capacity())) {
+ _realloc(next_pow2(size() + 1));
+ }
+ return m_ptr + size();
+ }
+
+ stack_storage& _get_stack() noexcept
+ {
+ return m_stack_storage;
+ }
+ const stack_storage& _get_stack() const noexcept
+ {
+ return m_stack_storage;
+ }
+
+ heap_storage& _get_heap() noexcept
+ {
+ return m_heap_storage;
+ }
+ const heap_storage& _get_heap() const noexcept
+ {
+ return m_heap_storage;
+ }
+
+ pointer m_ptr{nullptr};
+ size_type m_size{0};
+ union {
+ stack_storage m_stack_storage;
+ heap_storage m_heap_storage;
+ };
+ };
+
+ template <typename T, size_t N>
+ SCN_CONSTEXPR14 void swap(
+ small_vector<T, N>& l,
+ small_vector<T, N>& r) noexcept(noexcept(l.swap(r)))
+ {
+ l.swap(r);
+ }
+
+ SCN_CLANG_POP // -Wpadded
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_SMALL_VECTOR_H
diff --git a/src/third-party/scnlib/include/scn/util/span.h b/src/third-party/scnlib/include/scn/util/span.h
new file mode 100644
index 0000000..1abdd6c
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/span.h
@@ -0,0 +1,240 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_SPAN_H
+#define SCN_UTIL_SPAN_H
+
+#include "memory.h"
+
+SCN_GCC_PUSH
+SCN_GCC_IGNORE("-Wnoexcept")
+#include <iterator>
+SCN_GCC_POP
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace custom_ranges {
+ // iterator_category
+ using std::bidirectional_iterator_tag;
+ using std::forward_iterator_tag;
+ using std::input_iterator_tag;
+ using std::output_iterator_tag;
+ using std::random_access_iterator_tag;
+ struct contiguous_iterator_tag : random_access_iterator_tag {
+ };
+ } // namespace custom_ranges
+
+ /**
+ * A view over a contiguous range.
+ * Stripped-down version of `std::span`.
+ */
+ template <typename T>
+ class span {
+ public:
+ using element_type = T;
+ using value_type = typename std::remove_cv<T>::type;
+ using index_type = std::size_t;
+ using ssize_type = std::ptrdiff_t;
+ using difference_type = std::ptrdiff_t;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = const T&;
+
+ using iterator = pointer;
+ using const_iterator = const_pointer;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ constexpr span() noexcept = default;
+
+ template <typename I,
+ typename = decltype(detail::to_address(SCN_DECLVAL(I)))>
+ SCN_CONSTEXPR14 span(I begin, index_type count) noexcept
+ : m_ptr(detail::to_address(begin)),
+ m_end(detail::to_address(begin) + count)
+ {
+ }
+
+ template <typename I,
+ typename S,
+ typename = decltype(detail::to_address(SCN_DECLVAL(I)),
+ detail::to_address(SCN_DECLVAL(S)))>
+ SCN_CONSTEXPR14 span(I first, S last) noexcept
+ : m_ptr(detail::to_address(first)),
+ m_end(detail::to_address_safe(last, first, last))
+ {
+ }
+
+ template <typename U = typename std::add_const<T>::type,
+ typename E = element_type,
+ typename = typename std::enable_if<
+ std::is_same<E, value_type>::value>::type>
+ constexpr span(span<U> other) : m_ptr(other.m_ptr), m_end(other.m_end)
+ {
+ }
+
+ template <size_t N>
+ constexpr span(element_type (&arr)[N]) noexcept
+ : m_ptr(&arr), m_end(&arr + N)
+ {
+ }
+
+ SCN_CONSTEXPR14 iterator begin() noexcept
+ {
+ return m_ptr;
+ }
+ SCN_CONSTEXPR14 iterator end() noexcept
+ {
+ return m_end;
+ }
+ SCN_CONSTEXPR14 reverse_iterator rbegin() noexcept
+ {
+ return reverse_iterator{end()};
+ }
+ SCN_CONSTEXPR14 reverse_iterator rend() noexcept
+ {
+ return reverse_iterator{begin()};
+ }
+
+ constexpr const_iterator begin() const noexcept
+ {
+ return m_ptr;
+ }
+ constexpr const_iterator end() const noexcept
+ {
+ return m_end;
+ }
+ constexpr const_reverse_iterator rbegin() const noexcept
+ {
+ return reverse_iterator{end()};
+ }
+ constexpr const_reverse_iterator rend() const noexcept
+ {
+ return reverse_iterator{begin()};
+ }
+
+ constexpr const_iterator cbegin() const noexcept
+ {
+ return m_ptr;
+ }
+ constexpr const_iterator cend() const noexcept
+ {
+ return m_end;
+ }
+ constexpr const_reverse_iterator crbegin() const noexcept
+ {
+ return reverse_iterator{cend()};
+ }
+ constexpr const_reverse_iterator crend() const noexcept
+ {
+ return reverse_iterator{cbegin()};
+ }
+
+ SCN_CONSTEXPR14 reference operator[](index_type i) const noexcept
+ {
+ SCN_EXPECT(size() > i);
+ return *(m_ptr + i);
+ }
+
+ constexpr pointer data() const noexcept
+ {
+ return m_ptr;
+ }
+ SCN_NODISCARD constexpr index_type size() const noexcept
+ {
+ return static_cast<index_type>(m_end - m_ptr);
+ }
+ SCN_NODISCARD constexpr ssize_type ssize() const noexcept
+ {
+ return m_end - m_ptr;
+ }
+
+ SCN_CONSTEXPR14 span<T> first(index_type n) const
+ {
+ SCN_EXPECT(size() >= n);
+ return span<T>(data(), data() + n);
+ }
+ SCN_CONSTEXPR14 span<T> last(index_type n) const
+ {
+ SCN_EXPECT(size() >= n);
+ return span<T>(data() + size() - n, data() + size());
+ }
+ SCN_CONSTEXPR14 span<T> subspan(index_type off) const
+ {
+ SCN_EXPECT(size() >= off);
+ return span<T>(data() + off, size() - off);
+ }
+ SCN_CONSTEXPR14 span<T> subspan(index_type off,
+ difference_type count) const
+ {
+ SCN_EXPECT(size() > off + count);
+ SCN_EXPECT(count > 0);
+ return span<T>(data() + off, count);
+ }
+
+ constexpr operator span<typename std::add_const<T>::type>() const
+ {
+ return {m_ptr, m_end};
+ }
+ constexpr span<typename std::add_const<T>::type> as_const() const
+ {
+ return {m_ptr, m_end};
+ }
+
+ private:
+ pointer m_ptr{nullptr};
+ pointer m_end{nullptr};
+ };
+
+ template <typename I,
+ typename S,
+ typename Ptr = decltype(detail::to_address(SCN_DECLVAL(I))),
+ typename SPtr = decltype(detail::to_address(SCN_DECLVAL(S))),
+ typename ValueT = typename detail::remove_reference<
+ typename std::remove_pointer<Ptr>::type>::type>
+ SCN_CONSTEXPR14 auto make_span(I first, S last) noexcept -> span<ValueT>
+ {
+ return {first, last};
+ }
+ template <typename I,
+ typename Ptr = decltype(detail::to_address(SCN_DECLVAL(I))),
+ typename ValueT = typename detail::remove_reference<
+ typename std::remove_pointer<Ptr>::type>::type>
+ SCN_CONSTEXPR14 auto make_span(I first, std::size_t len) noexcept
+ -> span<ValueT>
+ {
+ return {first, len};
+ }
+
+ template <typename T>
+ SCN_CONSTEXPR14 span<typename T::value_type> make_span(
+ T& container) noexcept
+ {
+ using std::begin;
+ using std::end;
+ return span<typename T::value_type>(
+ detail::to_address(begin(container)),
+ detail::to_address_safe(end(container), begin(container),
+ end(container)));
+ }
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_SPAN_H
diff --git a/src/third-party/scnlib/include/scn/util/string_view.h b/src/third-party/scnlib/include/scn/util/string_view.h
new file mode 100644
index 0000000..576b93e
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/string_view.h
@@ -0,0 +1,270 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_STRING_VIEW_H
+#define SCN_UTIL_STRING_VIEW_H
+
+#include "algorithm.h"
+#include "span.h"
+
+#include <cstdint>
+#include <cstring>
+#include <cwchar>
+
+#if SCN_HAS_STRING_VIEW
+#include <string_view>
+#endif
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ inline size_t strlen(const char* s) noexcept
+ {
+ return ::std::strlen(s);
+ }
+ inline size_t strlen(const wchar_t* s) noexcept
+ {
+ return ::std::wcslen(s);
+ }
+ inline size_t strlen(const char16_t* s) noexcept
+ {
+ SCN_EXPECT(s);
+ auto end = s;
+ for (; *end != u'\0'; ++end)
+ ;
+ return static_cast<size_t>(end - s);
+ }
+ inline size_t strlen(const char32_t* s) noexcept
+ {
+ SCN_EXPECT(s);
+ auto end = s;
+ for (; *end != U'\0'; ++end)
+ ;
+ return static_cast<size_t>(end - s);
+ }
+#if SCN_HAS_CHAR8
+ inline size_t strlen(const char8_t* s) noexcept
+ {
+ return std::strlen(reinterpret_cast<const char*>(s));
+ }
+#endif
+ } // namespace detail
+
+ /**
+ * A view over a (sub)string.
+ * Used even when std::string_view is available to avoid compatibility
+ * issues.
+ */
+ template <typename CharT>
+ class basic_string_view {
+ public:
+ using value_type = CharT;
+ using span_type = span<const value_type>;
+
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using iterator = typename span_type::const_iterator;
+ using const_iterator = iterator;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = reverse_iterator;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using span_index_type = typename span_type::index_type;
+
+ static constexpr const size_type npos = size_type(-1);
+
+ constexpr basic_string_view() noexcept = default;
+ constexpr basic_string_view(const_pointer s, size_type c)
+ : m_data(s, static_cast<span_index_type>(c))
+ {
+ }
+ constexpr basic_string_view(const_pointer s)
+ : m_data(s, static_cast<span_index_type>(detail::strlen(s)))
+ {
+ }
+ template <size_t N>
+ constexpr basic_string_view(const CharT (&s)[N]) : m_data(s, N)
+ {
+ }
+ constexpr basic_string_view(const_pointer first, const_pointer last)
+ : m_data(first, last)
+ {
+ }
+#if SCN_HAS_STRING_VIEW
+ constexpr basic_string_view(std::basic_string_view<value_type> str)
+ : m_data(str.data(), str.size())
+ {
+ }
+#endif
+
+ template <typename T = char16_t,
+ typename std::enable_if<std::is_same<T, char16_t>::value &&
+ std::is_same<CharT, wchar_t>::value &&
+ sizeof(char16_t) ==
+ sizeof(wchar_t)>::type* = nullptr>
+ constexpr basic_string_view(const T* s)
+ : basic_string_view(reinterpret_cast<const wchar_t*>(s))
+ {
+ }
+ template <typename T = char32_t,
+ typename std::enable_if<std::is_same<T, char32_t>::value &&
+ std::is_same<CharT, wchar_t>::value &&
+ sizeof(char32_t) ==
+ sizeof(wchar_t)>::type* = nullptr>
+ constexpr basic_string_view(const T* s)
+ : basic_string_view(reinterpret_cast<const wchar_t*>(s))
+ {
+ }
+#if SCN_HAS_CHAR8
+ template <typename T = char8_t,
+ typename std::enable_if<
+ std::is_same<T, char8_t>::value &&
+ std::is_same<CharT, char>::value>::type* = nullptr>
+ constexpr basic_string_view(const T* s)
+ : basic_string_view(reinterpret_cast<const char*>(s))
+ {
+ }
+#endif
+
+ constexpr const_iterator begin() const noexcept
+ {
+ return cbegin();
+ }
+ constexpr const_iterator cbegin() const noexcept
+ {
+ return m_data.cbegin();
+ }
+ constexpr const_iterator end() const noexcept
+ {
+ return cend();
+ }
+ constexpr const_iterator cend() const noexcept
+ {
+ return m_data.cend();
+ }
+
+ constexpr const_reverse_iterator rbegin() const noexcept
+ {
+ return crbegin();
+ }
+ constexpr const_reverse_iterator crbegin() const noexcept
+ {
+ return m_data.crbegin();
+ }
+ constexpr const_reverse_iterator rend() const noexcept
+ {
+ return crend();
+ }
+ constexpr const_reverse_iterator crend() const noexcept
+ {
+ return m_data.crend();
+ }
+
+ constexpr const_reference operator[](size_type pos) const
+ {
+ return m_data[static_cast<typename span_type::index_type>(pos)];
+ }
+ SCN_CONSTEXPR14 const_reference at(size_type pos) const
+ {
+ SCN_EXPECT(pos < size());
+ return m_data.at(static_cast<typename span_type::index_type>(pos));
+ }
+
+ constexpr const_reference front() const
+ {
+ return operator[](0);
+ }
+ constexpr const_reference back() const
+ {
+ return operator[](size() - 1);
+ }
+ constexpr const_pointer data() const noexcept
+ {
+ return m_data.data();
+ }
+
+ SCN_NODISCARD constexpr size_type size() const noexcept
+ {
+ return static_cast<size_type>(m_data.size());
+ }
+ SCN_NODISCARD constexpr size_type length() const noexcept
+ {
+ return size();
+ }
+ SCN_NODISCARD constexpr size_type max_size() const noexcept
+ {
+ return SIZE_MAX - 1;
+ }
+ SCN_NODISCARD constexpr bool empty() const noexcept
+ {
+ return size() == 0;
+ }
+
+ SCN_CONSTEXPR14 void remove_prefix(size_type n)
+ {
+ SCN_EXPECT(n <= size());
+ m_data = m_data.subspan(n);
+ }
+ SCN_CONSTEXPR14 void remove_suffix(size_type n)
+ {
+ SCN_EXPECT(n <= size());
+ m_data = m_data.first(size() - n);
+ }
+
+ SCN_CONSTEXPR14 void swap(basic_string_view& v) noexcept
+ {
+ using std::swap;
+ swap(m_data, v.m_data);
+ }
+
+ size_type copy(pointer dest, size_type count, size_type pos = 0) const
+ {
+ SCN_EXPECT(pos <= size());
+ auto n = detail::min(count, size() - pos);
+ /* std::copy(data() + pos, n, dest); */
+ std::memcpy(dest, begin() + pos, n * sizeof(value_type));
+ return n;
+ }
+ SCN_CONSTEXPR14 basic_string_view substr(size_type pos = 0,
+ size_type count = npos) const
+ {
+ SCN_EXPECT(pos <= size());
+ auto n = detail::min(count, size() - pos);
+ return m_data.subspan(pos, n);
+ }
+
+#if SCN_HAS_STRING_VIEW
+ operator std::basic_string_view<value_type>() const noexcept
+ {
+ return {m_data.data(), m_data.size()};
+ }
+#endif
+
+ private:
+ span_type m_data{};
+ };
+
+ using string_view = basic_string_view<char>;
+ using wstring_view = basic_string_view<wchar_t>;
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif // SCN_STRING_VIEW_H
diff --git a/src/third-party/scnlib/include/scn/util/unique_ptr.h b/src/third-party/scnlib/include/scn/util/unique_ptr.h
new file mode 100644
index 0000000..4723de8
--- /dev/null
+++ b/src/third-party/scnlib/include/scn/util/unique_ptr.h
@@ -0,0 +1,118 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#ifndef SCN_UTIL_UNIQUE_PTR_H
+#define SCN_UTIL_UNIQUE_PTR_H
+
+#include "../detail/fwd.h"
+
+#include <type_traits>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ /**
+ * `std::unique_ptr` implementation with [[clang::trivial_abi]], without
+ * including `<memory>`
+ */
+ template <typename T>
+ class SCN_TRIVIAL_ABI unique_ptr {
+ public:
+ using element_type = T;
+ using pointer = T*;
+
+ constexpr unique_ptr() noexcept = default;
+ constexpr unique_ptr(std::nullptr_t) noexcept {}
+
+ constexpr explicit unique_ptr(pointer p) noexcept : m_ptr(p) {}
+
+ template <
+ typename U,
+ typename std::enable_if<
+ std::is_convertible<U*, pointer>::value>::type* = nullptr>
+ SCN_CONSTEXPR14 unique_ptr(unique_ptr<U>&& u) noexcept
+ : m_ptr(SCN_MOVE(u.get()))
+ {
+ u.reset();
+ }
+
+ unique_ptr(const unique_ptr&) = delete;
+ unique_ptr& operator=(const unique_ptr&) = delete;
+
+ SCN_CONSTEXPR14 unique_ptr(unique_ptr&& p) noexcept
+ : m_ptr(SCN_MOVE(p.m_ptr))
+ {
+ p.m_ptr = nullptr;
+ }
+ unique_ptr& operator=(unique_ptr&& p) noexcept
+ {
+ delete m_ptr;
+ m_ptr = p.m_ptr;
+ p.m_ptr = nullptr;
+ return *this;
+ }
+
+ ~unique_ptr() noexcept
+ {
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ delete m_ptr;
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+
+ constexpr explicit operator bool() const noexcept
+ {
+ return get() != nullptr;
+ }
+
+ constexpr pointer get() const noexcept
+ {
+ return m_ptr;
+ }
+
+ constexpr pointer operator->() const noexcept
+ {
+ return m_ptr;
+ }
+ constexpr typename std::add_lvalue_reference<T>::type operator*()
+ const
+ {
+ return *m_ptr;
+ }
+
+ SCN_CONSTEXPR14 void reset()
+ {
+ m_ptr = nullptr;
+ }
+
+ private:
+ pointer m_ptr{nullptr};
+ };
+
+ template <typename T, typename... Args>
+ unique_ptr<T> make_unique(Args&&... a)
+ {
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ return unique_ptr<T>(new T(SCN_FWD(a)...));
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ }
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
+
+#endif
diff --git a/src/third-party/scnlib/licenses/README.md b/src/third-party/scnlib/licenses/README.md
new file mode 100644
index 0000000..cd5e2d4
--- /dev/null
+++ b/src/third-party/scnlib/licenses/README.md
@@ -0,0 +1,25 @@
+# Third-party Software Licenses
+
+## {fmt}
+
+{fmt} is licensed under the MIT license.
+Copyright (c) 2012 - present, Victor Zverovich
+See `fmt.rst` for more
+
+## NanoRange
+
+NanoRange is licensed under the Boost Software License, version 1.0.
+Copyright (c) 2018 Tristan Brindle (tcbrindle at gmail dot com)
+See `nanorange.txt` for more
+
+## fast_float
+
+fast_float is licensed under either of Apache License, version 2.0 or MIT license.
+Copyright (c) 2021 The fast_float authors
+See `fast_float-apache.txt` and `fast_float-mit.txt` for more
+
+## utfcpp
+
+utfcpp is licensed under the Boost Software License, version 1.0.
+Copyright (c) 2006 Nemanja Trifunovic
+See `utfcpp.txt` for more
diff --git a/src/third-party/scnlib/licenses/fast_float-apache.txt b/src/third-party/scnlib/licenses/fast_float-apache.txt
new file mode 100644
index 0000000..26f4398
--- /dev/null
+++ b/src/third-party/scnlib/licenses/fast_float-apache.txt
@@ -0,0 +1,190 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2021 The fast_float authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/src/third-party/scnlib/licenses/fast_float-mit.txt b/src/third-party/scnlib/licenses/fast_float-mit.txt
new file mode 100644
index 0000000..2fb2a37
--- /dev/null
+++ b/src/third-party/scnlib/licenses/fast_float-mit.txt
@@ -0,0 +1,27 @@
+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.
diff --git a/src/third-party/scnlib/licenses/fmt.rst b/src/third-party/scnlib/licenses/fmt.rst
new file mode 100644
index 0000000..f0ec3db
--- /dev/null
+++ b/src/third-party/scnlib/licenses/fmt.rst
@@ -0,0 +1,27 @@
+Copyright (c) 2012 - present, Victor Zverovich
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+--- Optional exception to the license ---
+
+As an exception, if, as a result of your compiling your source code, portions
+of this Software are embedded into a machine-executable object form of such
+source code, you may redistribute such embedded portions in such object form
+without including the above copyright and permission notices.
diff --git a/src/third-party/scnlib/licenses/nanorange.txt b/src/third-party/scnlib/licenses/nanorange.txt
new file mode 100644
index 0000000..36b7cd9
--- /dev/null
+++ b/src/third-party/scnlib/licenses/nanorange.txt
@@ -0,0 +1,23 @@
+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.
diff --git a/src/third-party/scnlib/licenses/utfcpp.txt b/src/third-party/scnlib/licenses/utfcpp.txt
new file mode 100644
index 0000000..36b7cd9
--- /dev/null
+++ b/src/third-party/scnlib/licenses/utfcpp.txt
@@ -0,0 +1,23 @@
+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.
diff --git a/src/third-party/scnlib/src/Makefile.am b/src/third-party/scnlib/src/Makefile.am
new file mode 100644
index 0000000..7e5c4ae
--- /dev/null
+++ b/src/third-party/scnlib/src/Makefile.am
@@ -0,0 +1,65 @@
+
+noinst_HEADERS = \
+ ../include/scn/reader/reader.h \
+ ../include/scn/reader/float.h \
+ ../include/scn/reader/types.h \
+ ../include/scn/reader/int.h \
+ ../include/scn/reader/common.h \
+ ../include/scn/reader/string.h \
+ ../include/scn/ranges/custom_impl.h \
+ ../include/scn/ranges/std_impl.h \
+ ../include/scn/ranges/ranges.h \
+ ../include/scn/ranges/util.h \
+ ../include/scn/fwd.h \
+ ../include/scn/util/algorithm.h \
+ ../include/scn/util/small_vector.h \
+ ../include/scn/util/optional.h \
+ ../include/scn/util/expected.h \
+ ../include/scn/util/array.h \
+ ../include/scn/util/unique_ptr.h \
+ ../include/scn/util/math.h \
+ ../include/scn/util/memory.h \
+ ../include/scn/util/span.h \
+ ../include/scn/util/meta.h \
+ ../include/scn/util/string_view.h \
+ ../include/scn/unicode/unicode.h \
+ ../include/scn/unicode/common.h \
+ ../include/scn/unicode/utf16.h \
+ ../include/scn/unicode/utf8.h \
+ ../include/scn/all.h \
+ ../include/scn/tuple_return/tuple_return.h \
+ ../include/scn/tuple_return/util.h \
+ ../include/scn/scan/ignore.h \
+ ../include/scn/scan/getline.h \
+ ../include/scn/scan/list.h \
+ ../include/scn/scan/common.h \
+ ../include/scn/scan/istream.h \
+ ../include/scn/scan/vscan.h \
+ ../include/scn/scan/scan.h \
+ ../include/scn/tuple_return.h \
+ ../include/scn/detail/error.h \
+ ../include/scn/detail/fwd.h \
+ ../include/scn/detail/range.h \
+ ../include/scn/detail/locale.h \
+ ../include/scn/detail/config.h \
+ ../include/scn/detail/file.h \
+ ../include/scn/detail/context.h \
+ ../include/scn/detail/result.h \
+ ../include/scn/detail/visitor.h \
+ ../include/scn/detail/args.h \
+ ../include/scn/detail/parse_context.h \
+ ../include/scn/detail/vectored.h \
+ ../include/scn/scn.h \
+ ../include/scn/istream.h \
+ deps/fast_float/single_include/fast_float/fast_float.h
+
+noinst_LIBRARIES = libscnlib.a
+
+AM_CPPFLAGS = -I$(srcdir)/../include -I$(srcdir)/deps/fast_float/single_include
+
+libscnlib_a_SOURCES = \
+ reader_float.cpp \
+ locale.cpp \
+ reader_int.cpp \
+ file.cpp \
+ vscan.cpp
diff --git a/src/third-party/scnlib/src/deps/fast_float/single_include/fast_float/fast_float.h b/src/third-party/scnlib/src/deps/fast_float/single_include/fast_float/fast_float.h
new file mode 100644
index 0000000..3f94372
--- /dev/null
+++ b/src/third-party/scnlib/src/deps/fast_float/single_include/fast_float/fast_float.h
@@ -0,0 +1,2981 @@
+// fast_float v3.4.0
+
+// 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
+//
+// Licensed under the Apache License, Version 2.0, or the
+// MIT License at your option. This file may not be copied,
+// modified, or distributed except according to those terms.
+//
+// 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.
+//
+// Apache License (Version 2.0) Notice
+//
+// Copyright 2021 The fast_float authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+//
+
+#ifndef FASTFLOAT_FAST_FLOAT_H
+#define FASTFLOAT_FAST_FLOAT_H
+
+#include <system_error>
+
+namespace fast_float {
+ enum chars_format {
+ scientific = 1<<0,
+ fixed = 1<<2,
+ hex = 1<<3,
+ general = fixed | scientific
+ };
+
+
+ struct from_chars_result {
+ const char *ptr;
+ std::errc ec;
+ };
+
+ struct parse_options {
+ constexpr explicit parse_options(chars_format fmt = chars_format::general,
+ char dot = '.')
+ : format(fmt), decimal_point(dot) {}
+
+ /** Which number formats are accepted */
+ chars_format format;
+ /** The character used as decimal point */
+ char decimal_point;
+ };
+
+ /**
+ * 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 allowe the fixed point and scientific notation respectively.
+ * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`.
+ */
+ template<typename T>
+ from_chars_result from_chars(const char *first, const char *last,
+ T &value, chars_format fmt = chars_format::general) noexcept;
+
+ /**
+ * Like from_chars, but accepts an `options` argument to govern number parsing.
+ */
+ template<typename T>
+ from_chars_result from_chars_advanced(const char *first, const char *last,
+ T &value, parse_options options) noexcept;
+
+}
+#endif // FASTFLOAT_FAST_FLOAT_H
+
+#ifndef FASTFLOAT_FLOAT_COMMON_H
+#define FASTFLOAT_FLOAT_COMMON_H
+
+#include <cfloat>
+#include <cstdint>
+#include <cassert>
+#include <cstring>
+#include <type_traits>
+
+#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(__EMSCRIPTEN__))
+#define FASTFLOAT_64BIT
+#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \
+ || defined(__arm__) || defined(_M_ARM) \
+ || defined(__MINGW32__))
+#define FASTFLOAT_32BIT
+#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
+#elif SIZE_MAX == 0xffffffffffffffff
+#define FASTFLOAT_64BIT
+#else
+#error Unknown platform (not 32-bit, not 64-bit?)
+#endif
+#endif
+
+#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__))
+#include <intrin.h>
+#endif
+
+#if defined(_MSC_VER) && !defined(__clang__)
+#define FASTFLOAT_VISUAL_STUDIO 1
+#endif
+
+#ifdef _WIN32
+#define FASTFLOAT_IS_BIG_ENDIAN 0
+#else
+#if defined(__APPLE__) || defined(__FreeBSD__)
+#include <machine/endian.h>
+#elif defined(sun) || defined(__sun)
+#include <sys/byteorder.h>
+#else
+#include <endian.h>
+#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
+
+#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) { if (!(x)) abort(); }
+#endif
+
+#ifndef FASTFLOAT_DEBUG_ASSERT
+#include <cassert>
+#define FASTFLOAT_DEBUG_ASSERT(x) assert(x)
+#endif
+
+// rust style `try!()` macro, or `?` operator
+#define FASTFLOAT_TRY(x) { if (!(x)) return false; }
+
+namespace fast_float {
+
+ // Compares two ASCII strings in a case insensitive manner.
+ inline bool fastfloat_strncasecmp(const char *input1, const char *input2,
+ size_t length) {
+ char running_diff{0};
+ for (size_t i = 0; i < length; i++) {
+ running_diff |= (input1[i] ^ input2[i]);
+ }
+ return (running_diff == 0) || (running_diff == 32);
+ }
+
+#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 <typename T>
+ struct span {
+ const T* ptr;
+ size_t length;
+ span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {}
+ span() : ptr(nullptr), length(0) {}
+
+ constexpr size_t len() const noexcept {
+ return length;
+ }
+
+ const T& operator[](size_t index) const noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ return ptr[index];
+ }
+ };
+
+ struct value128 {
+ uint64_t low;
+ uint64_t high;
+ value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {}
+ value128() : low(0), high(0) {}
+ };
+
+ /* result might be undefined when input_num is zero */
+ fastfloat_really_inline int leading_zeroes(uint64_t input_num) {
+ assert(input_num > 0);
+#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
+ 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;
+#endif
+#else
+ return __builtin_clzll(input_num);
+#endif
+ }
+
+#ifdef FASTFLOAT_32BIT
+
+ // slow emulation routine for 32-bit
+ fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) {
+ return x * (uint64_t)y;
+ }
+
+// slow emulation routine for 32-bit
+#if !defined(__MINGW64__)
+ fastfloat_really_inline uint64_t _umul128(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 = !!(adbc < ad);
+ uint64_t lo = bd + (adbc << 32);
+ *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) +
+ (adbc_carry << 32) + !!(lo < bd);
+ return lo;
+ }
+#endif // !__MINGW64__
+
+#endif // FASTFLOAT_32BIT
+
+
+ // compute 64-bit a*b
+ fastfloat_really_inline value128 full_multiplication(uint64_t a,
+ uint64_t b) {
+ value128 answer;
+#ifdef _M_ARM64
+ // ARM64 has native support for 64-bit multiplications, no need to emulate
+ answer.high = __umulh(a, b);
+ answer.low = a * b;
+#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__))
+ answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64
+#elif defined(FASTFLOAT_64BIT)
+ __uint128_t r = ((__uint128_t)a) * b;
+ answer.low = uint64_t(r);
+ answer.high = uint64_t(r >> 64);
+#else
+#error Not implemented
+#endif
+ return answer;
+ }
+
+ struct adjusted_mantissa {
+ uint64_t mantissa{0};
+ int32_t power2{0}; // a negative value indicates an invalid result
+ adjusted_mantissa() = default;
+ bool operator==(const adjusted_mantissa &o) const {
+ return mantissa == o.mantissa && power2 == o.power2;
+ }
+ bool operator!=(const adjusted_mantissa &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;
+
+ constexpr static double powers_of_ten_double[] = {
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
+ 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
+ constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5,
+ 1e6, 1e7, 1e8, 1e9, 1e10};
+
+ template <typename T> struct binary_format {
+ using equiv_uint = typename std::conditional<sizeof(T) == 4, uint32_t, uint64_t>::type;
+
+ static inline constexpr int mantissa_explicit_bits();
+ static inline constexpr int minimum_exponent();
+ static inline constexpr int infinite_power();
+ static inline constexpr int sign_index();
+ static inline constexpr int min_exponent_fast_path();
+ static inline constexpr int max_exponent_fast_path();
+ static inline constexpr int max_exponent_round_to_even();
+ static inline constexpr int min_exponent_round_to_even();
+ static inline constexpr uint64_t max_mantissa_fast_path();
+ static inline constexpr int largest_power_of_ten();
+ static inline constexpr int smallest_power_of_ten();
+ static inline constexpr T exact_power_of_ten(int64_t power);
+ static inline constexpr size_t max_digits();
+ static inline constexpr equiv_uint exponent_mask();
+ static inline constexpr equiv_uint mantissa_mask();
+ static inline constexpr equiv_uint hidden_bit_mask();
+ };
+
+ template <> inline constexpr int binary_format<double>::mantissa_explicit_bits() {
+ return 52;
+ }
+ template <> inline constexpr int binary_format<float>::mantissa_explicit_bits() {
+ return 23;
+ }
+
+ template <> inline constexpr int binary_format<double>::max_exponent_round_to_even() {
+ return 23;
+ }
+
+ template <> inline constexpr int binary_format<float>::max_exponent_round_to_even() {
+ return 10;
+ }
+
+ template <> inline constexpr int binary_format<double>::min_exponent_round_to_even() {
+ return -4;
+ }
+
+ template <> inline constexpr int binary_format<float>::min_exponent_round_to_even() {
+ return -17;
+ }
+
+ template <> inline constexpr int binary_format<double>::minimum_exponent() {
+ return -1023;
+ }
+ template <> inline constexpr int binary_format<float>::minimum_exponent() {
+ return -127;
+ }
+
+ template <> inline constexpr int binary_format<double>::infinite_power() {
+ return 0x7FF;
+ }
+ template <> inline constexpr int binary_format<float>::infinite_power() {
+ return 0xFF;
+ }
+
+ template <> inline constexpr int binary_format<double>::sign_index() { return 63; }
+ template <> inline constexpr int binary_format<float>::sign_index() { return 31; }
+
+ template <> inline constexpr int binary_format<double>::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<float>::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<double>::max_exponent_fast_path() {
+ return 22;
+ }
+ template <> inline constexpr int binary_format<float>::max_exponent_fast_path() {
+ return 10;
+ }
+
+ template <> inline constexpr uint64_t binary_format<double>::max_mantissa_fast_path() {
+ return uint64_t(2) << mantissa_explicit_bits();
+ }
+ template <> inline constexpr uint64_t binary_format<float>::max_mantissa_fast_path() {
+ return uint64_t(2) << mantissa_explicit_bits();
+ }
+
+ template <>
+ inline constexpr double binary_format<double>::exact_power_of_ten(int64_t power) {
+ return powers_of_ten_double[power];
+ }
+ template <>
+ inline constexpr float binary_format<float>::exact_power_of_ten(int64_t power) {
+
+ return powers_of_ten_float[power];
+ }
+
+
+ template <>
+ inline constexpr int binary_format<double>::largest_power_of_ten() {
+ return 308;
+ }
+ template <>
+ inline constexpr int binary_format<float>::largest_power_of_ten() {
+ return 38;
+ }
+
+ template <>
+ inline constexpr int binary_format<double>::smallest_power_of_ten() {
+ return -342;
+ }
+ template <>
+ inline constexpr int binary_format<float>::smallest_power_of_ten() {
+ return -65;
+ }
+
+ template <> inline constexpr size_t binary_format<double>::max_digits() {
+ return 769;
+ }
+ template <> inline constexpr size_t binary_format<float>::max_digits() {
+ return 114;
+ }
+
+ template <> inline constexpr binary_format<float>::equiv_uint
+ binary_format<float>::exponent_mask() {
+ return 0x7F800000;
+ }
+ template <> inline constexpr binary_format<double>::equiv_uint
+ binary_format<double>::exponent_mask() {
+ return 0x7FF0000000000000;
+ }
+
+ template <> inline constexpr binary_format<float>::equiv_uint
+ binary_format<float>::mantissa_mask() {
+ return 0x007FFFFF;
+ }
+ template <> inline constexpr binary_format<double>::equiv_uint
+ binary_format<double>::mantissa_mask() {
+ return 0x000FFFFFFFFFFFFF;
+ }
+
+ template <> inline constexpr binary_format<float>::equiv_uint
+ binary_format<float>::hidden_bit_mask() {
+ return 0x00800000;
+ }
+ template <> inline constexpr binary_format<double>::equiv_uint
+ binary_format<double>::hidden_bit_mask() {
+ return 0x0010000000000000;
+ }
+
+ template<typename T>
+ fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) {
+ uint64_t word = am.mantissa;
+ word |= uint64_t(am.power2) << binary_format<T>::mantissa_explicit_bits();
+ word = negative
+ ? word | (uint64_t(1) << binary_format<T>::sign_index()) : word;
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ if (std::is_same<T, float>::value) {
+ ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian
+ } else {
+ ::memcpy(&value, &word, sizeof(T));
+ }
+#else
+ // For little-endian systems:
+ ::memcpy(&value, &word, sizeof(T));
+#endif
+ }
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_ASCII_NUMBER_H
+#define FASTFLOAT_ASCII_NUMBER_H
+
+#include <cctype>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+
+
+namespace fast_float {
+
+ // Next function can be micro-optimized, but compilers are entirely
+ // able to optimize it well.
+ fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; }
+
+ fastfloat_really_inline 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;
+ }
+
+ fastfloat_really_inline uint64_t read_u64(const char *chars) {
+ 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;
+ }
+
+ fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) {
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ // Need to read as-if the number was in little-endian order.
+ val = byteswap(val);
+#endif
+ ::memcpy(chars, &val, sizeof(uint64_t));
+ }
+
+ // credit @aqrit
+ fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) {
+ const uint64_t mask = 0x000000FF000000FF;
+ const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
+ const uint64_t 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);
+ }
+
+ fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept {
+ return parse_eight_digits_unrolled(read_u64(chars));
+ }
+
+ // credit @aqrit
+ fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept {
+ return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
+ 0x8080808080808080));
+ }
+
+ fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept {
+ return is_made_of_eight_digits_fast(read_u64(chars));
+ }
+
+ typedef span<const char> byte_span;
+
+ struct parsed_number_string {
+ int64_t exponent{0};
+ uint64_t mantissa{0};
+ const char *lastmatch{nullptr};
+ bool negative{false};
+ bool valid{false};
+ bool too_many_digits{false};
+ // contains the range of the significant digits
+ byte_span integer{}; // non-nullable
+ byte_span fraction{}; // nullable
+ };
+
+ // Assuming that you use no more than 19 digits, this will
+ // parse an ASCII string.
+ fastfloat_really_inline
+ parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept {
+ const chars_format fmt = options.format;
+ const char decimal_point = options.decimal_point;
+
+ parsed_number_string answer;
+ answer.valid = false;
+ answer.too_many_digits = false;
+ answer.negative = (*p == '-');
+ if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+ ++p;
+ if (p == pend) {
+ return answer;
+ }
+ if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot
+ return answer;
+ }
+ }
+ const char *const start_digits = p;
+
+ uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
+
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ // a multiplication by 10 is cheaper than an arbitrary integer
+ // multiplication
+ i = 10 * i +
+ uint64_t(*p - '0'); // might overflow, we will handle the overflow later
+ ++p;
+ }
+ const char *const end_of_integer_part = p;
+ int64_t digit_count = int64_t(end_of_integer_part - start_digits);
+ answer.integer = byte_span(start_digits, size_t(digit_count));
+ int64_t exponent = 0;
+ if ((p != pend) && (*p == decimal_point)) {
+ ++p;
+ const char* 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.
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '0');
+ ++p;
+ i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
+ }
+ exponent = before - p;
+ answer.fraction = byte_span(before, size_t(p - before));
+ digit_count -= exponent;
+ }
+ // we must have encountered at least one integer!
+ if (digit_count == 0) {
+ return answer;
+ }
+ int64_t exp_number = 0; // explicit exponential part
+ if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) {
+ const char * location_of_e = p;
+ ++p;
+ bool neg_exp = false;
+ if ((p != pend) && ('-' == *p)) {
+ neg_exp = true;
+ ++p;
+ } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
+ ++p;
+ }
+ if ((p == pend) || !is_integer(*p)) {
+ if(!(fmt & chars_format::fixed)) {
+ // We are in error.
+ return answer;
+ }
+ // Otherwise, we will be ignoring the 'e'.
+ p = location_of_e;
+ } else {
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '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((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; }
+ }
+ 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.
+ const char *start = start_digits;
+ while ((start != pend) && (*start == '0' || *start == decimal_point)) {
+ if(*start == '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;
+ const char* int_end = p + answer.integer.len();
+ const uint64_t minimal_nineteen_digit_integer{1000000000000000000};
+ while((i < minimal_nineteen_digit_integer) && (p != int_end)) {
+ i = i * 10 + uint64_t(*p - '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;
+ const char* frac_end = p + answer.fraction.len();
+ while((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
+ i = i * 10 + uint64_t(*p - '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;
+ }
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_FAST_TABLE_H
+#define FASTFLOAT_FAST_TABLE_H
+
+#include <cstdint>
+
+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 <class unused = void>
+ struct powers_template {
+
+ constexpr static int smallest_power_of_five = binary_format<double>::smallest_power_of_ten();
+ constexpr static int largest_power_of_five = binary_format<double>::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.
+ static const uint64_t power_of_five_128[number_of_entries];
+ };
+
+ template <class unused>
+ const uint64_t powers_template<unused>::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,};
+ using powers = powers_template<>;
+
+}
+
+#endif
+
+#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H
+#define FASTFLOAT_DECIMAL_TO_BINARY_H
+
+#include <cfloat>
+#include <cinttypes>
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+
+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 <int bit_precision>
+ fastfloat_really_inline
+ value128 compute_product_approximation(int64_t q, uint64_t w) {
+ const int 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 <typename binary>
+ fastfloat_really_inline
+ 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 <typename binary>
+ fastfloat_really_inline
+ adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept {
+ int lz = leading_zeroes(w);
+ w <<= lz;
+ value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
+ return compute_error_scaled<binary>(q, product.high, lz);
+ }
+
+ // w * 10 ** q
+ // The returned value should be a valid ieee64 number that simply need 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 <typename binary>
+ fastfloat_really_inline
+ 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<binary::mantissa_explicit_bits() + 3>(q, w);
+ if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further
+ // In some very rare cases, this could happen, in which case we might need a more accurate
+ // computation that what we can provide cheaply. This is very, very unlikely.
+ //
+ const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0,
+ // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation.
+ if(!inside_safe_exponent) {
+ return compute_error_scaled<binary>(q, product.high, lz);
+ }
+ }
+ // 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);
+
+ answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3);
+
+ 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.
+ 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 << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == 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 <algorithm>
+#include <cstdint>
+#include <climits>
+#include <cstring>
+
+
+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
+ 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> 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 <uint16_t size>
+ struct stackvec {
+ limb data[size];
+ // we never need more than 150 limbs
+ uint16_t length{0};
+
+ stackvec() = default;
+ stackvec(const stackvec &) = delete;
+ stackvec &operator=(const stackvec &) = delete;
+ stackvec(stackvec &&) = delete;
+ stackvec &operator=(stackvec &&other) = delete;
+
+ // create stack vector from existing limb span.
+ stackvec(limb_span s) {
+ FASTFLOAT_ASSERT(try_extend(s));
+ }
+
+ limb& operator[](size_t index) noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ return data[index];
+ }
+ const limb& operator[](size_t index) const noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ return data[index];
+ }
+ // index from the end of the container
+ 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.
+ 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
+ void push_unchecked(limb value) noexcept {
+ data[length] = value;
+ length++;
+ }
+ // append item to vector, returning if item was added
+ 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
+ void extend_unchecked(limb_span s) noexcept {
+ limb* ptr = data + length;
+ ::memcpy((void*)ptr, (const void*)s.ptr, sizeof(limb) * s.len());
+ set_len(len() + s.len());
+ }
+ // try to add items to the vector, returning if items were added
+ 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.
+ 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.
+ 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.
+ 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.
+ void normalize() noexcept {
+ while (len() > 0 && rindex(0) == 0) {
+ length--;
+ }
+ }
+ };
+
+ fastfloat_really_inline
+ uint64_t empty_hi64(bool& truncated) noexcept {
+ truncated = false;
+ return 0;
+ }
+
+ fastfloat_really_inline
+ uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept {
+ truncated = false;
+ int shl = leading_zeroes(r0);
+ return r0 << shl;
+ }
+
+ fastfloat_really_inline
+ 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
+ uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept {
+ return uint64_hi64(r0, truncated);
+ }
+
+ fastfloat_really_inline
+ 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
+ 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
+ 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)
+ 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
+ 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 <uint16_t size>
+ inline bool small_add_from(stackvec<size>& 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 <uint16_t size>
+ fastfloat_really_inline bool small_add(stackvec<size>& vec, limb y) noexcept {
+ return small_add_from(vec, y, 0);
+ }
+
+ // multiply bigint by scalar value.
+ template <uint16_t size>
+ inline bool small_mul(stackvec<size>& 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 <uint16_t size>
+ bool large_add_from(stackvec<size>& 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 <uint16_t size>
+ fastfloat_really_inline bool large_add_from(stackvec<size>& x, limb_span y) noexcept {
+ return large_add_from(x, y, 0);
+ }
+
+ // grade-school multiplication algorithm
+ template <uint16_t size>
+ bool long_mul(stackvec<size>& x, limb_span y) noexcept {
+ limb_span xs = limb_span(x.data, x.len());
+ stackvec<size> 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<size> 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 <uint16_t size>
+ bool large_mul(stackvec<size>& 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;
+ }
+
+ // 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 {
+ // storage of the limbs, in little-endian order.
+ stackvec<bigint_limbs> vec;
+
+ bigint(): vec() {}
+ bigint(const bigint &) = delete;
+ bigint &operator=(const bigint &) = delete;
+ bigint(bigint &&) = delete;
+ bigint &operator=(bigint &&other) = delete;
+
+ 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.
+ 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.
+ int compare(const bigint& 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.
+ 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.
+ 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;
+ const limb* src = vec.data;
+ ::memmove(dst, src, sizeof(limb) * 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.
+ 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.
+ 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.
+ int bit_length() const noexcept {
+ int lz = ctlz();
+ return int(limb_bits * vec.len()) - lz;
+ }
+
+ bool mul(limb y) noexcept {
+ return small_mul(vec, y);
+ }
+
+ bool add(limb y) noexcept {
+ return small_add(vec, y);
+ }
+
+ // multiply as if by 2 raised to a power.
+ bool pow2(uint32_t exp) noexcept {
+ return shl(exp);
+ }
+
+ // multiply as if by 5 raised to a power.
+ bool pow5(uint32_t exp) noexcept {
+ // multiply by a power of 5
+ 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
+ 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) {
+ FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp])));
+ }
+
+ return true;
+ }
+
+ // multiply as if by 10 raised to a power.
+ bool pow10(uint32_t exp) noexcept {
+ FASTFLOAT_TRY(pow5(exp));
+ return pow2(exp);
+ }
+ };
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_ASCII_NUMBER_H
+#define FASTFLOAT_ASCII_NUMBER_H
+
+#include <cctype>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+
+
+namespace fast_float {
+
+ // Next function can be micro-optimized, but compilers are entirely
+ // able to optimize it well.
+ fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; }
+
+ fastfloat_really_inline 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;
+ }
+
+ fastfloat_really_inline uint64_t read_u64(const char *chars) {
+ 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;
+ }
+
+ fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) {
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ // Need to read as-if the number was in little-endian order.
+ val = byteswap(val);
+#endif
+ ::memcpy(chars, &val, sizeof(uint64_t));
+ }
+
+ // credit @aqrit
+ fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) {
+ const uint64_t mask = 0x000000FF000000FF;
+ const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
+ const uint64_t 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);
+ }
+
+ fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept {
+ return parse_eight_digits_unrolled(read_u64(chars));
+ }
+
+ // credit @aqrit
+ fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept {
+ return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
+ 0x8080808080808080));
+ }
+
+ fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept {
+ return is_made_of_eight_digits_fast(read_u64(chars));
+ }
+
+ typedef span<const char> byte_span;
+
+ struct parsed_number_string {
+ int64_t exponent{0};
+ uint64_t mantissa{0};
+ const char *lastmatch{nullptr};
+ bool negative{false};
+ bool valid{false};
+ bool too_many_digits{false};
+ // contains the range of the significant digits
+ byte_span integer{}; // non-nullable
+ byte_span fraction{}; // nullable
+ };
+
+ // Assuming that you use no more than 19 digits, this will
+ // parse an ASCII string.
+ fastfloat_really_inline
+ parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept {
+ const chars_format fmt = options.format;
+ const char decimal_point = options.decimal_point;
+
+ parsed_number_string answer;
+ answer.valid = false;
+ answer.too_many_digits = false;
+ answer.negative = (*p == '-');
+ if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+ ++p;
+ if (p == pend) {
+ return answer;
+ }
+ if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot
+ return answer;
+ }
+ }
+ const char *const start_digits = p;
+
+ uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
+
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ // a multiplication by 10 is cheaper than an arbitrary integer
+ // multiplication
+ i = 10 * i +
+ uint64_t(*p - '0'); // might overflow, we will handle the overflow later
+ ++p;
+ }
+ const char *const end_of_integer_part = p;
+ int64_t digit_count = int64_t(end_of_integer_part - start_digits);
+ answer.integer = byte_span(start_digits, size_t(digit_count));
+ int64_t exponent = 0;
+ if ((p != pend) && (*p == decimal_point)) {
+ ++p;
+ const char* 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.
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '0');
+ ++p;
+ i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
+ }
+ exponent = before - p;
+ answer.fraction = byte_span(before, size_t(p - before));
+ digit_count -= exponent;
+ }
+ // we must have encountered at least one integer!
+ if (digit_count == 0) {
+ return answer;
+ }
+ int64_t exp_number = 0; // explicit exponential part
+ if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) {
+ const char * location_of_e = p;
+ ++p;
+ bool neg_exp = false;
+ if ((p != pend) && ('-' == *p)) {
+ neg_exp = true;
+ ++p;
+ } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
+ ++p;
+ }
+ if ((p == pend) || !is_integer(*p)) {
+ if(!(fmt & chars_format::fixed)) {
+ // We are in error.
+ return answer;
+ }
+ // Otherwise, we will be ignoring the 'e'.
+ p = location_of_e;
+ } else {
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '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((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; }
+ }
+ 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.
+ const char *start = start_digits;
+ while ((start != pend) && (*start == '0' || *start == decimal_point)) {
+ if(*start == '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;
+ const char* int_end = p + answer.integer.len();
+ const uint64_t minimal_nineteen_digit_integer{1000000000000000000};
+ while((i < minimal_nineteen_digit_integer) && (p != int_end)) {
+ i = i * 10 + uint64_t(*p - '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;
+ const char* frac_end = p + answer.fraction.len();
+ while((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
+ i = i * 10 + uint64_t(*p - '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;
+ }
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_DIGIT_COMPARISON_H
+#define FASTFLOAT_DIGIT_COMPARISON_H
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+
+
+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.
+ fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& 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 <typename T>
+ fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept {
+ using equiv_uint = typename binary_format<T>::equiv_uint;
+ constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask();
+ constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask();
+ constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask();
+
+ adjusted_mantissa am;
+ int32_t bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent();
+ equiv_uint bits;
+ ::memcpy(&bits, &value, sizeof(T));
+ 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<T>::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 <typename T>
+ fastfloat_really_inline 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 <typename T, typename callback>
+ fastfloat_really_inline void round(adjusted_mantissa& am, callback cb) noexcept {
+ int32_t mantissa_shift = 64 - binary_format<T>::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<T>::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<T>::mantissa_explicit_bits())) {
+ am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
+ am.power2++;
+ }
+
+ // check for infinite: we could have carried to an infinite power
+ am.mantissa &= ~(uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
+ if (am.power2 >= binary_format<T>::infinite_power()) {
+ am.power2 = binary_format<T>::infinite_power();
+ am.mantissa = 0;
+ }
+ }
+
+ template <typename callback>
+ fastfloat_really_inline
+ void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept {
+ uint64_t mask;
+ uint64_t halfway;
+ if (shift == 64) {
+ mask = UINT64_MAX;
+ } else {
+ mask = (uint64_t(1) << shift) - 1;
+ }
+ if (shift == 0) {
+ halfway = 0;
+ } else {
+ halfway = uint64_t(1) << (shift - 1);
+ }
+ uint64_t truncated_bits = am.mantissa & mask;
+ uint64_t is_above = truncated_bits > halfway;
+ uint64_t 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 void round_down(adjusted_mantissa& am, int32_t shift) noexcept {
+ if (shift == 64) {
+ am.mantissa = 0;
+ } else {
+ am.mantissa >>= shift;
+ }
+ am.power2 += shift;
+ }
+
+ fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept {
+ uint64_t val;
+ while (std::distance(first, last) >= 8) {
+ ::memcpy(&val, first, sizeof(uint64_t));
+ if (val != 0x3030303030303030) {
+ break;
+ }
+ first += 8;
+ }
+ while (first != last) {
+ if (*first != '0') {
+ break;
+ }
+ first++;
+ }
+ }
+
+ // determine if any non-zero digits were truncated.
+ // all characters must be valid digits.
+ fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept {
+ // do 8-bit optimizations, can just compare to 8 literal 0s.
+ uint64_t val;
+ while (std::distance(first, last) >= 8) {
+ ::memcpy(&val, first, sizeof(uint64_t));
+ if (val != 0x3030303030303030) {
+ return true;
+ }
+ first += 8;
+ }
+ while (first != last) {
+ if (*first != '0') {
+ return true;
+ }
+ first++;
+ }
+ return false;
+ }
+
+ fastfloat_really_inline bool is_truncated(byte_span s) noexcept {
+ return is_truncated(s.ptr, s.ptr + s.len());
+ }
+
+ fastfloat_really_inline
+ void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept {
+ value = value * 100000000 + parse_eight_digits_unrolled(p);
+ p += 8;
+ counter += 8;
+ count += 8;
+ }
+
+ fastfloat_really_inline
+ void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count) noexcept {
+ value = value * 10 + limb(*p - '0');
+ p++;
+ counter++;
+ count++;
+ }
+
+ fastfloat_really_inline
+ void add_native(bigint& big, limb power, limb value) noexcept {
+ big.mul(power);
+ big.add(value);
+ }
+
+ fastfloat_really_inline 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
+ inline void parse_mantissa(bigint& result, parsed_number_string& 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.
+ const char* p = num.integer.ptr;
+ const char* 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 <typename T>
+ inline 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<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent();
+ answer.power2 = bigmant.bit_length() - 64 + bias;
+
+ round<T>(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 <typename T>
+ inline 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<T>(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<T>(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 <typename T>
+ inline adjusted_mantissa digit_comp(parsed_number_string& 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<T>::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<T>(bigmant, exponent);
+ } else {
+ return negative_digit_comp<T>(bigmant, am, exponent);
+ }
+ }
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_PARSE_NUMBER_H
+#define FASTFLOAT_PARSE_NUMBER_H
+
+
+#include <cmath>
+#include <cstring>
+#include <limits>
+#include <system_error>
+
+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 <typename T>
+ from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept {
+ from_chars_result answer;
+ answer.ptr = first;
+ answer.ec = std::errc(); // be optimistic
+ bool minusSign = false;
+ if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here
+ minusSign = true;
+ ++first;
+ }
+ if (last - first >= 3) {
+ if (fastfloat_strncasecmp(first, "nan", 3)) {
+ answer.ptr = (first += 3);
+ value = minusSign ? -std::numeric_limits<T>::quiet_NaN() : std::numeric_limits<T>::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 == '(') {
+ for(const char* ptr = first + 1; ptr != last; ++ptr) {
+ if (*ptr == ')') {
+ answer.ptr = ptr + 1; // valid nan(n-char-seq-opt)
+ break;
+ }
+ else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_'))
+ break; // forbidden char, not nan(n-char-seq-opt)
+ }
+ }
+ return answer;
+ }
+ if (fastfloat_strncasecmp(first, "inf", 3)) {
+ if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) {
+ answer.ptr = first + 8;
+ } else {
+ answer.ptr = first + 3;
+ }
+ value = minusSign ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity();
+ return answer;
+ }
+ }
+ answer.ec = std::errc::invalid_argument;
+ return answer;
+ }
+
+ } // namespace detail
+
+ template<typename T>
+ from_chars_result from_chars(const char *first, const char *last,
+ T &value, chars_format fmt /*= chars_format::general*/) noexcept {
+ return from_chars_advanced(first, last, value, parse_options{fmt});
+ }
+
+ template<typename T>
+ from_chars_result from_chars_advanced(const char *first, const char *last,
+ T &value, parse_options options) noexcept {
+
+ static_assert (std::is_same<T, double>::value || std::is_same<T, float>::value, "only float and double are supported");
+
+
+ from_chars_result answer;
+ if (first == last) {
+ answer.ec = std::errc::invalid_argument;
+ answer.ptr = first;
+ return answer;
+ }
+ parsed_number_string pns = parse_number_string(first, last, options);
+ if (!pns.valid) {
+ return detail::parse_infnan(first, last, value);
+ }
+ answer.ec = std::errc(); // be optimistic
+ answer.ptr = pns.lastmatch;
+ // Next is Clinger's fast path.
+ if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && pns.mantissa <=binary_format<T>::max_mantissa_fast_path() && !pns.too_many_digits) {
+ value = T(pns.mantissa);
+ if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); }
+ else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); }
+ if (pns.negative) { value = -value; }
+ return answer;
+ }
+ adjusted_mantissa am = compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
+ if(pns.too_many_digits && am.power2 >= 0) {
+ if(am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) {
+ am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa);
+ }
+ }
+ // If we called compute_float<binary_format<T>>(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<T>(pns, am); }
+ to_float(pns.negative, am, value);
+ return answer;
+ }
+
+} // namespace fast_float
+
+#endif
+
diff --git a/src/third-party/scnlib/src/file.cpp b/src/third-party/scnlib/src/file.cpp
new file mode 100644
index 0000000..ec53145
--- /dev/null
+++ b/src/third-party/scnlib/src/file.cpp
@@ -0,0 +1,311 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY
+#define SCN_FILE_CPP
+#endif
+
+#include <scn/detail/error.h>
+#include <scn/detail/file.h>
+#include <scn/util/expected.h>
+
+#include <cstdio>
+
+#if SCN_POSIX
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#elif SCN_WINDOWS
+
+#ifdef WIN32_LEAN_AND_MEAN
+#define SCN_WIN32_LEAN_DEFINED 1
+#else
+#define WIN32_LEAN_AND_MEAN
+#define SCN_WIN32_LEAN_DEFINED 0
+#endif
+
+#ifdef NOMINMAX
+#define SCN_NOMINMAX_DEFINED 1
+#else
+#define NOMINMAX
+#define SCN_NOMINMAX_DEFINED 0
+#endif
+
+#include <Windows.h>
+
+#if !SCN_NOMINMAX_DEFINED
+#undef NOMINMAX
+#endif
+
+#if !SCN_WIN32_LEAN_DEFINED
+#undef WIN32_LEAN_AND_MEAN
+#endif
+
+#endif
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ SCN_FUNC native_file_handle native_file_handle::invalid()
+ {
+#if SCN_WINDOWS
+ return {INVALID_HANDLE_VALUE};
+#else
+ return {-1};
+#endif
+ }
+
+ SCN_FUNC byte_mapped_file::byte_mapped_file(const char* filename)
+ {
+#if SCN_POSIX
+ int fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ return;
+ }
+
+ struct stat s {
+ };
+ int status = fstat(fd, &s);
+ if (status == -1) {
+ close(fd);
+ return;
+ }
+ auto size = s.st_size;
+
+ auto ptr =
+ static_cast<char*>(mmap(nullptr, static_cast<size_t>(size),
+ PROT_READ, MAP_PRIVATE, fd, 0));
+ if (ptr == MAP_FAILED) {
+ close(fd);
+ return;
+ }
+
+ m_file.handle = fd;
+ m_map = span<char>{ptr, static_cast<size_t>(size)};
+#elif SCN_WINDOWS
+ auto f = ::CreateFileA(
+ filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (f == INVALID_HANDLE_VALUE) {
+ return;
+ }
+
+ LARGE_INTEGER _size;
+ if (::GetFileSizeEx(f, &_size) == 0) {
+ ::CloseHandle(f);
+ return;
+ }
+ auto size = static_cast<size_t>(_size.QuadPart);
+
+ auto h = ::CreateFileMappingA(
+ f, nullptr, PAGE_READONLY,
+#ifdef _WIN64
+ static_cast<DWORD>(size >> 32ull),
+#else
+ DWORD{0},
+#endif
+ static_cast<DWORD>(size & 0xffffffffull), nullptr);
+ if (h == INVALID_HANDLE_VALUE || h == nullptr) {
+ ::CloseHandle(f);
+ return;
+ }
+
+ auto start = ::MapViewOfFile(h, FILE_MAP_READ, 0, 0, size);
+ if (!start) {
+ ::CloseHandle(h);
+ ::CloseHandle(f);
+ return;
+ }
+
+ m_file.handle = f;
+ m_map_handle.handle = h;
+ m_map = span<char>{static_cast<char*>(start), size};
+#else
+ SCN_UNUSED(filename);
+#endif
+ }
+
+ SCN_FUNC void byte_mapped_file::_destruct()
+ {
+#if SCN_POSIX
+ munmap(m_map.data(), m_map.size());
+ close(m_file.handle);
+#elif SCN_WINDOWS
+ ::CloseHandle(m_map_handle.handle);
+ ::CloseHandle(m_file.handle);
+ m_map_handle = native_file_handle::invalid();
+#endif
+
+ m_file = native_file_handle::invalid();
+ m_map = span<char>{};
+
+ SCN_ENSURE(!valid());
+ }
+
+ } // namespace detail
+
+ namespace detail {
+ template <typename CharT>
+ struct basic_file_iterator_access {
+ using iterator = typename basic_file<CharT>::iterator;
+
+ basic_file_iterator_access(const iterator& it) : self(it) {}
+
+ SCN_NODISCARD expected<CharT> deref() const
+ {
+ SCN_EXPECT(self.m_file);
+
+ if (self.m_file->m_buffer.empty()) {
+ // no chars have been read
+ return self.m_file->_read_single();
+ }
+ if (!self.m_last_error) {
+ // last read failed
+ return self.m_last_error;
+ }
+ return self.m_file->_get_char_at(self.m_current);
+ }
+
+ SCN_NODISCARD bool eq(const iterator& o) const
+ {
+ if (self.m_file && (self.m_file == o.m_file || !o.m_file)) {
+ if (self.m_file->_is_at_end(self.m_current) &&
+ self.m_last_error.code() != error::end_of_range &&
+ !o.m_file) {
+ self.m_last_error = error{};
+ auto r = self.m_file->_read_single();
+ if (!r) {
+ self.m_last_error = r.error();
+ return !o.m_file || self.m_current == o.m_current ||
+ o.m_last_error.code() == error::end_of_range;
+ }
+ }
+ }
+
+ // null file == null file
+ if (!self.m_file && !o.m_file) {
+ return true;
+ }
+ // null file == eof file
+ if (!self.m_file && o.m_file) {
+ // lhs null, rhs potentially eof
+ return o.m_last_error.code() == error::end_of_range;
+ }
+ // eof file == null file
+ if (self.m_file && !o.m_file) {
+ // rhs null, lhs potentially eof
+ return self.m_last_error.code() == error::end_of_range;
+ }
+ // eof file == eof file
+ if (self.m_last_error == o.m_last_error &&
+ self.m_last_error.code() == error::end_of_range) {
+ return true;
+ }
+
+ return self.m_file == o.m_file && self.m_current == o.m_current;
+ }
+
+ const iterator& self;
+ };
+ } // namespace detail
+
+ template <>
+ SCN_FUNC expected<char> basic_file<char>::iterator::operator*() const
+ {
+ return detail::basic_file_iterator_access<char>(*this).deref();
+ }
+ template <>
+ SCN_FUNC expected<wchar_t> basic_file<wchar_t>::iterator::operator*() const
+ {
+ return detail::basic_file_iterator_access<wchar_t>(*this).deref();
+ }
+
+ template <>
+ SCN_FUNC bool basic_file<char>::iterator::operator==(
+ const basic_file<char>::iterator& o) const
+ {
+ return detail::basic_file_iterator_access<char>(*this).eq(o);
+ }
+ template <>
+ SCN_FUNC bool basic_file<wchar_t>::iterator::operator==(
+ const basic_file<wchar_t>::iterator& o) const
+ {
+ return detail::basic_file_iterator_access<wchar_t>(*this).eq(o);
+ }
+
+ template <>
+ SCN_FUNC expected<char> file::_read_single() const
+ {
+ SCN_EXPECT(valid());
+ int tmp = std::fgetc(m_file);
+ if (tmp == EOF) {
+ if (std::feof(m_file) != 0) {
+ return error(error::end_of_range, "EOF");
+ }
+ if (std::ferror(m_file) != 0) {
+ return error(error::source_error, "fgetc error");
+ }
+ return error(error::unrecoverable_source_error,
+ "Unknown fgetc error");
+ }
+ auto ch = static_cast<char>(tmp);
+ m_buffer.push_back(ch);
+ return ch;
+ }
+ template <>
+ SCN_FUNC expected<wchar_t> wfile::_read_single() const
+ {
+ SCN_EXPECT(valid());
+ wint_t tmp = std::fgetwc(m_file);
+ if (tmp == WEOF) {
+ if (std::feof(m_file) != 0) {
+ return error(error::end_of_range, "EOF");
+ }
+ if (std::ferror(m_file) != 0) {
+ return error(error::source_error, "fgetc error");
+ }
+ return error(error::unrecoverable_source_error,
+ "Unknown fgetc error");
+ }
+ auto ch = static_cast<wchar_t>(tmp);
+ m_buffer.push_back(ch);
+ return ch;
+ }
+
+ template <>
+ SCN_FUNC void file::_sync_until(std::size_t pos) noexcept
+ {
+ for (auto it = m_buffer.rbegin();
+ it != m_buffer.rend() - static_cast<std::ptrdiff_t>(pos); ++it) {
+ std::ungetc(static_cast<unsigned char>(*it), m_file);
+ }
+ }
+ template <>
+ SCN_FUNC void wfile::_sync_until(std::size_t pos) noexcept
+ {
+ for (auto it = m_buffer.rbegin();
+ it != m_buffer.rend() - static_cast<std::ptrdiff_t>(pos); ++it) {
+ std::ungetwc(static_cast<wint_t>(*it), m_file);
+ }
+ }
+
+ SCN_END_NAMESPACE
+} // namespace scn
diff --git a/src/third-party/scnlib/src/locale.cpp b/src/third-party/scnlib/src/locale.cpp
new file mode 100644
index 0000000..bc628e0
--- /dev/null
+++ b/src/third-party/scnlib/src/locale.cpp
@@ -0,0 +1,668 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY
+#define SCN_LOCALE_CPP
+#endif
+
+#include <scn/detail/locale.h>
+#include <scn/util/math.h>
+
+#include <cctype>
+#include <cmath>
+#include <cwchar>
+#include <iomanip>
+#include <locale>
+#include <sstream>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ template <typename CharT>
+ struct locale_data {
+ using char_type = CharT;
+ using string_type = std::basic_string<char_type>;
+
+ std::locale global_locale{std::locale()};
+ std::locale classic_locale{std::locale::classic()};
+
+ string_type truename{};
+ string_type falsename{};
+ char_type decimal_point{};
+ char_type thousands_separator{};
+ };
+
+ template <typename CharT>
+ const std::locale& to_locale(const basic_custom_locale_ref<CharT>& l)
+ {
+ SCN_EXPECT(l.get_locale());
+ return *static_cast<const std::locale*>(l.get_locale());
+ }
+
+ // Buggy on gcc 5 and 6
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wmaybe-uninitialized")
+
+ template <typename CharT>
+ basic_custom_locale_ref<CharT>::basic_custom_locale_ref()
+ {
+ auto data = new locale_data<CharT>{};
+ m_data = data;
+ m_locale = &data->global_locale;
+ _initialize();
+ }
+ template <typename CharT>
+ basic_custom_locale_ref<CharT>::basic_custom_locale_ref(
+ const void* locale)
+ : m_locale(locale)
+ {
+ auto data = new locale_data<CharT>{};
+ m_data = data;
+ if (!locale) {
+ m_locale = &data->global_locale;
+ }
+ _initialize();
+ }
+
+ SCN_GCC_POP
+
+ template <typename CharT>
+ void basic_custom_locale_ref<CharT>::_initialize()
+ {
+ const auto& facet =
+ std::use_facet<std::numpunct<CharT>>(to_locale(*this));
+
+ auto& data = *static_cast<locale_data<CharT>*>(m_data);
+ data.truename = facet.truename();
+ data.falsename = facet.falsename();
+ data.decimal_point = facet.decimal_point();
+ data.thousands_separator = facet.thousands_sep();
+ }
+
+ template <typename CharT>
+ basic_custom_locale_ref<CharT>::basic_custom_locale_ref(
+ basic_custom_locale_ref&& o)
+ {
+ m_data = o.m_data;
+ m_locale = o.m_locale;
+
+ o.m_data = nullptr;
+ o.m_locale = nullptr;
+
+ _initialize();
+ }
+ template <typename CharT>
+ auto basic_custom_locale_ref<CharT>::operator=(
+ basic_custom_locale_ref&& o) -> basic_custom_locale_ref&
+ {
+ delete static_cast<locale_data<CharT>*>(m_data);
+
+ m_data = o.m_data;
+ m_locale = o.m_locale;
+
+ o.m_data = nullptr;
+ o.m_locale = nullptr;
+
+ _initialize();
+
+ return *this;
+ }
+
+ template <typename CharT>
+ basic_custom_locale_ref<CharT>::~basic_custom_locale_ref()
+ {
+ delete static_cast<locale_data<CharT>*>(m_data);
+ }
+
+ template <typename CharT>
+ auto basic_custom_locale_ref<CharT>::make_classic()
+ -> basic_custom_locale_ref
+ {
+ basic_custom_locale_ref loc{};
+ loc.convert_to_classic();
+ return loc;
+ }
+
+ template <typename CharT>
+ void basic_custom_locale_ref<CharT>::convert_to_classic()
+ {
+ m_locale =
+ &static_cast<locale_data<CharT>*>(m_data)->classic_locale;
+ }
+ template <typename CharT>
+ void basic_custom_locale_ref<CharT>::convert_to_global()
+ {
+ SCN_EXPECT(m_data);
+ m_locale = &static_cast<locale_data<CharT>*>(m_data)->global_locale;
+ }
+
+ template <typename CharT>
+ bool basic_custom_locale_ref<CharT>::do_is_space(char_type ch) const
+ {
+ return std::isspace(ch, to_locale(*this));
+ }
+ template <typename CharT>
+ bool basic_custom_locale_ref<CharT>::do_is_digit(char_type ch) const
+ {
+ return std::isdigit(ch, to_locale(*this));
+ }
+
+ template <typename CharT>
+ auto basic_custom_locale_ref<CharT>::do_decimal_point() const
+ -> char_type
+ {
+ return static_cast<locale_data<CharT>*>(m_data)->decimal_point;
+ }
+ template <typename CharT>
+ auto basic_custom_locale_ref<CharT>::do_thousands_separator() const
+ -> char_type
+ {
+ return static_cast<locale_data<CharT>*>(m_data)
+ ->thousands_separator;
+ }
+ template <typename CharT>
+ auto basic_custom_locale_ref<CharT>::do_truename() const
+ -> string_view_type
+ {
+ const auto& str =
+ static_cast<locale_data<CharT>*>(m_data)->truename;
+ return {str.data(), str.size()};
+ }
+ template <typename CharT>
+ auto basic_custom_locale_ref<CharT>::do_falsename() const
+ -> string_view_type
+ {
+ const auto& str =
+ static_cast<locale_data<CharT>*>(m_data)->falsename;
+ return {str.data(), str.size()};
+ }
+
+ static inline error convert_to_wide_impl(const std::locale&,
+ const char*,
+ const char*,
+ const char*&,
+ wchar_t*,
+ wchar_t*,
+ wchar_t*&)
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+ static inline error convert_to_wide_impl(const std::locale&,
+ const wchar_t*,
+ const wchar_t*,
+ const wchar_t*&,
+ wchar_t*,
+ wchar_t*,
+ wchar_t*&)
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+
+ template <typename CharT>
+ error basic_custom_locale_ref<CharT>::convert_to_wide(
+ const CharT* from_begin,
+ const CharT* from_end,
+ const CharT*& from_next,
+ wchar_t* to_begin,
+ wchar_t* to_end,
+ wchar_t*& to_next) const
+ {
+ return convert_to_wide_impl(to_locale(*this), from_begin, from_end,
+ from_next, to_begin, to_end, to_next);
+ }
+
+ static inline expected<wchar_t> convert_to_wide_impl(const std::locale&,
+ const char*,
+ const char*)
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+ static inline expected<wchar_t> convert_to_wide_impl(const std::locale&,
+ const wchar_t*,
+ const wchar_t*)
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+
+ template <typename CharT>
+ expected<wchar_t> basic_custom_locale_ref<CharT>::convert_to_wide(
+ const CharT* from_begin,
+ const CharT* from_end) const
+ {
+ return convert_to_wide_impl(to_locale(*this), from_begin, from_end);
+ }
+
+ template <typename CharT>
+ bool basic_custom_locale_ref<CharT>::do_is_space(
+ span<const char_type> ch) const
+ {
+ const auto& locale = to_locale(*this);
+ if (sizeof(CharT) == 1) {
+ SCN_EXPECT(ch.size() >= 1);
+ code_point cp{};
+ auto it = parse_code_point(ch.begin(), ch.end(), cp);
+ SCN_EXPECT(it);
+ return is_space(cp);
+ }
+ SCN_EXPECT(ch.size() == 1);
+ return std::isspace(ch[0], locale);
+ }
+ template <typename CharT>
+ bool basic_custom_locale_ref<CharT>::do_is_digit(
+ span<const char_type> ch) const
+ {
+ const auto& locale = to_locale(*this);
+ if (sizeof(CharT) == 1) {
+ SCN_EXPECT(ch.size() >= 1);
+ code_point cp{};
+ auto it = parse_code_point(ch.begin(), ch.end(), cp);
+ SCN_EXPECT(it);
+ return is_digit(cp);
+ }
+ SCN_EXPECT(ch.size() == 1);
+ return std::isdigit(ch[0], locale);
+ }
+
+#define SCN_DEFINE_CUSTOM_LOCALE_CTYPE(f) \
+ template <typename CharT> \
+ bool basic_custom_locale_ref<CharT>::is_##f(char_type ch) const \
+ { \
+ return std::is##f(ch, to_locale(*this)); \
+ } \
+ template <typename CharT> \
+ bool basic_custom_locale_ref<CharT>::is_##f(code_point cp) const \
+ { \
+ return std::is##f(static_cast<wchar_t>(cp), to_locale(*this)); \
+ } \
+ template <typename CharT> \
+ bool basic_custom_locale_ref<CharT>::is_##f(span<const char_type> ch) \
+ const \
+ { \
+ const auto& locale = to_locale(*this); \
+ if (sizeof(CharT) == 1) { \
+ SCN_EXPECT(ch.size() >= 1); \
+ code_point cp{}; \
+ auto it = parse_code_point(ch.begin(), ch.end(), cp); \
+ SCN_EXPECT(it); \
+ return is_##f(cp); \
+ } \
+ SCN_EXPECT(ch.size() == 1); \
+ return std::is##f(ch[0], locale); \
+ }
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(alnum)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(alpha)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(cntrl)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(graph)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(lower)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(print)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(punct)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(upper)
+ SCN_DEFINE_CUSTOM_LOCALE_CTYPE(xdigit)
+#undef SCN_DEFINE_CUSTOM_LOCALE_CTYPE
+
+ template <typename CharT>
+ bool basic_custom_locale_ref<CharT>::is_space(code_point cp) const
+ {
+ return std::isspace(static_cast<wchar_t>(cp), to_locale(*this));
+ }
+ template <typename CharT>
+ bool basic_custom_locale_ref<CharT>::is_digit(code_point cp) const
+ {
+ return std::isdigit(static_cast<wchar_t>(cp), to_locale(*this));
+ }
+
+ // For some reason, there's no isblank in libc++
+ template <typename CharT>
+ bool basic_custom_locale_ref<CharT>::is_blank(char_type ch) const
+ {
+ return std::use_facet<std::ctype<CharT>>(to_locale(*this))
+ .is(std::ctype_base::blank, ch);
+ }
+ template <typename CharT>
+ bool basic_custom_locale_ref<CharT>::is_blank(code_point ch) const
+ {
+ return std::use_facet<std::ctype<wchar_t>>(to_locale(*this))
+ .is(std::ctype_base::blank, static_cast<wchar_t>(ch));
+ }
+ template <typename CharT>
+ bool basic_custom_locale_ref<CharT>::is_blank(
+ span<const char_type> ch) const
+ {
+ const auto& locale = to_locale(*this);
+ if (sizeof(CharT) == 1) {
+ SCN_EXPECT(ch.size() >= 1);
+ code_point cp{};
+ auto it = parse_code_point(ch.begin(), ch.end(), cp);
+ SCN_EXPECT(it);
+ return is_blank(cp);
+ }
+ SCN_EXPECT(ch.size() == 1);
+ return std::use_facet<std::ctype<CharT>>(locale).is(
+ std::ctype_base::blank, ch[0]);
+ }
+
+ template <typename T>
+ auto read_num_check_range(T val) ->
+ typename std::enable_if<std::is_integral<T>::value, error>::type
+ {
+ if (val == std::numeric_limits<T>::max()) {
+ return error(error::value_out_of_range,
+ "Scanned number out of range: overflow");
+ }
+ if (val == std::numeric_limits<T>::min()) {
+ return error(error::value_out_of_range,
+ "Scanned number out of range: underflow");
+ }
+ return error(error::invalid_scanned_value,
+ "Localized number read failed");
+ }
+ template <typename T>
+ auto read_num_check_range(T val) ->
+ typename std::enable_if<std::is_floating_point<T>::value,
+ error>::type
+ {
+ SCN_GCC_COMPAT_PUSH
+ SCN_GCC_COMPAT_IGNORE("-Wfloat-equal")
+ if (val == std::numeric_limits<T>::max() ||
+ val == -std::numeric_limits<T>::max()) {
+ return error(error::value_out_of_range,
+ "Scanned number out of range: overflow");
+ }
+ if (val == zero_value<T>::value) {
+ return error(error::value_out_of_range,
+ "Scanned number out of range: underflow");
+ }
+ SCN_GCC_COMPAT_POP
+ return error(error::invalid_scanned_value,
+ "Localized number read failed");
+ }
+
+ template <typename T, typename CharT>
+ error do_read_num_impl(T& val, std::basic_istringstream<CharT>& ss)
+ {
+ ss >> val;
+ return {};
+ }
+ template <typename CharT>
+ error do_read_num_impl(CharT& val, std::basic_istringstream<CharT>& ss)
+ {
+ long long tmp;
+ if (!(ss >> tmp)) {
+ return {};
+ }
+ if (tmp > std::numeric_limits<CharT>::max()) {
+ return {error::value_out_of_range,
+ "Scanned number out of range: overflow"};
+ }
+ if (tmp < std::numeric_limits<CharT>::min()) {
+ return {error::value_out_of_range,
+ "Scanned number out of range: underflow"};
+ }
+ val = static_cast<CharT>(tmp);
+ return {};
+ }
+ template <typename CharT>
+ error do_read_num_impl(signed char& val,
+ std::basic_istringstream<CharT>& ss)
+ {
+ int tmp;
+ if (!(ss >> tmp)) {
+ return {};
+ }
+ if (tmp > std::numeric_limits<signed char>::max()) {
+ return {error::value_out_of_range,
+ "Scanned number out of range: overflow"};
+ }
+ if (tmp < std::numeric_limits<signed char>::min()) {
+ return {error::value_out_of_range,
+ "Scanned number out of range: underflow"};
+ }
+ val = static_cast<signed char>(tmp);
+ return {};
+ }
+ template <typename CharT>
+ error do_read_num_impl(unsigned char& val,
+ std::basic_istringstream<CharT>& ss)
+ {
+ int tmp;
+ if (!(ss >> tmp)) {
+ return {};
+ }
+ if (tmp > std::numeric_limits<unsigned char>::max()) {
+ return {error::value_out_of_range,
+ "Scanned number out of range: overflow"};
+ }
+ if (tmp < 0) {
+ return {error::value_out_of_range,
+ "Scanned number out of range: underflow"};
+ }
+ val = static_cast<unsigned char>(tmp);
+ return {};
+ }
+
+ template <typename T, typename CharT>
+ expected<std::ptrdiff_t> do_read_num(
+ T& val,
+ const std::locale& loc,
+ const std::basic_string<CharT>& buf,
+ int base)
+ {
+#if SCN_HAS_EXCEPTIONS
+ std::basic_istringstream<CharT> ss(buf);
+ ss.imbue(loc);
+ ss >> std::setbase(base);
+
+ try {
+ T tmp;
+ auto e = do_read_num_impl(tmp, ss);
+ if (ss.bad()) {
+ return error(error::unrecoverable_internal_error,
+ "Localized stringstream is bad");
+ }
+ if (!e) {
+ return e;
+ }
+ if (ss.fail()) {
+ return read_num_check_range(tmp);
+ }
+ val = tmp;
+ }
+ catch (const std::ios_base::failure& f) {
+ return error(error::invalid_scanned_value, f.what());
+ }
+ return ss.eof() ? static_cast<std::ptrdiff_t>(buf.size())
+ : static_cast<std::ptrdiff_t>(ss.tellg());
+#else
+ SCN_UNUSED(val);
+ SCN_UNUSED(loc);
+ SCN_UNUSED(buf);
+ return error(error::exceptions_required,
+ "Localized number reading is only supported with "
+ "exceptions enabled");
+#endif
+ }
+
+ template <>
+ expected<std::ptrdiff_t> do_read_num(wchar_t&,
+ const std::locale&,
+ const std::string&,
+ int)
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+ template <>
+ expected<std::ptrdiff_t> do_read_num(char&,
+ const std::locale&,
+ const std::wstring&,
+ int)
+ {
+ SCN_EXPECT(false);
+ SCN_UNREACHABLE;
+ }
+
+ template <typename CharT>
+ template <typename T>
+ expected<std::ptrdiff_t> basic_custom_locale_ref<CharT>::read_num(
+ T& val,
+ const string_type& buf,
+ int b) const
+ {
+ return do_read_num<T, CharT>(val, to_locale(*this), buf, b);
+ }
+
+#if SCN_INCLUDE_SOURCE_DEFINITIONS
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wpadded")
+ SCN_CLANG_IGNORE("-Wweak-template-vtables")
+ template class basic_custom_locale_ref<char>;
+ template class basic_custom_locale_ref<wchar_t>;
+ SCN_CLANG_POP
+
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<signed char>(signed char&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<short>(short&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<int>(int&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<long>(long&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<long long>(long long&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ char>::read_num<unsigned char>(unsigned char&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ char>::read_num<unsigned short>(unsigned short&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ char>::read_num<unsigned int>(unsigned int&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ char>::read_num<unsigned long>(unsigned long&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ char>::read_num<unsigned long long>(unsigned long long&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<char>(char&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<wchar_t>(wchar_t&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<float>(float&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<double>(double&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<char>::read_num<long double>(long double&,
+ const string_type&,
+ int) const;
+
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ wchar_t>::read_num<signed char>(signed char&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<wchar_t>::read_num<short>(short&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<wchar_t>::read_num<int>(int&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<wchar_t>::read_num<long>(long&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ wchar_t>::read_num<long long>(long long&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ wchar_t>::read_num<unsigned char>(unsigned char&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ wchar_t>::read_num<unsigned short>(unsigned short&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ wchar_t>::read_num<unsigned int>(unsigned int&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ wchar_t>::read_num<unsigned long>(unsigned long&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ wchar_t>::read_num<unsigned long long>(unsigned long long&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<wchar_t>::read_num<float>(float&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<wchar_t>::read_num<double>(double&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t> basic_custom_locale_ref<
+ wchar_t>::read_num<long double>(long double&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<wchar_t>::read_num<char>(char&,
+ const string_type&,
+ int) const;
+ template expected<std::ptrdiff_t>
+ basic_custom_locale_ref<wchar_t>::read_num<wchar_t>(wchar_t&,
+ const string_type&,
+ int) const;
+#endif
+
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
diff --git a/src/third-party/scnlib/src/reader_float.cpp b/src/third-party/scnlib/src/reader_float.cpp
new file mode 100644
index 0000000..77c66a5
--- /dev/null
+++ b/src/third-party/scnlib/src/reader_float.cpp
@@ -0,0 +1,433 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY
+#define SCN_READER_FLOAT_CPP
+#endif
+
+#include <scn/detail/args.h>
+#include <scn/reader/float.h>
+
+#include <cerrno>
+#include <clocale>
+
+#if SCN_HAS_FLOAT_CHARCONV
+#include <charconv>
+#endif
+
+SCN_GCC_PUSH
+SCN_GCC_IGNORE("-Wold-style-cast")
+SCN_GCC_IGNORE("-Wnoexcept")
+SCN_GCC_IGNORE("-Wshift-count-overflow")
+SCN_GCC_IGNORE("-Wsign-conversion")
+
+SCN_CLANG_PUSH
+SCN_CLANG_IGNORE("-Wold-style-cast")
+
+#if SCN_CLANG >= SCN_COMPILER(13, 0, 0)
+SCN_CLANG_IGNORE("-Wreserved-identifier")
+#endif
+
+#if SCN_CLANG >= SCN_COMPILER(10, 0, 0)
+SCN_CLANG_IGNORE("-Wextra-semi-stmt")
+#endif
+
+#include <fast_float/fast_float.h>
+
+SCN_CLANG_POP
+SCN_GCC_POP
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace read_float {
+ static bool is_hexfloat(const char* str, std::size_t len) noexcept
+ {
+ if (len < 3) {
+ return false;
+ }
+ return str[0] == '0' && (str[1] == 'x' || str[1] == 'X');
+ }
+ static bool is_hexfloat(const wchar_t* str, std::size_t len) noexcept
+ {
+ if (len < 3) {
+ return false;
+ }
+ return str[0] == L'0' && (str[1] == L'x' || str[1] == L'X');
+ }
+
+ namespace cstd {
+#if SCN_GCC >= SCN_COMPILER(7, 0, 0)
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wnoexcept-type")
+#endif
+ template <typename T, typename CharT, typename F>
+ expected<T> impl(F&& f_strtod,
+ T huge_value,
+ const CharT* str,
+ size_t& chars,
+ uint8_t options)
+ {
+ // Get current C locale
+ const auto loc = std::setlocale(LC_NUMERIC, nullptr);
+ // For whatever reason, this cannot be stored in the heap if
+ // setlocale hasn't been called before, or msan errors with
+ // 'use-of-unitialized-value' when resetting the locale back.
+ // POSIX specifies that the content of loc may not be static, so
+ // we need to save it ourselves
+ char locbuf[64] = {0};
+ std::strcpy(locbuf, loc);
+
+ std::setlocale(LC_NUMERIC, "C");
+
+ CharT* end{};
+ errno = 0;
+ T f = f_strtod(str, &end);
+ chars = static_cast<size_t>(end - str);
+ auto err = errno;
+ // Reset locale
+ std::setlocale(LC_NUMERIC, locbuf);
+ errno = 0;
+
+ SCN_GCC_COMPAT_PUSH
+ SCN_GCC_COMPAT_IGNORE("-Wfloat-equal")
+ // No conversion
+ if (f == detail::zero_value<T>::value && chars == 0) {
+ return error(error::invalid_scanned_value, "strtod");
+ }
+ // Range error
+ if (err == ERANGE) {
+ // Underflow
+ if (f == detail::zero_value<T>::value) {
+ return error(
+ error::value_out_of_range,
+ "Floating-point value out of range: underflow");
+ }
+ // Overflow
+ if (f == huge_value || f == -huge_value) {
+ return error(
+ error::value_out_of_range,
+ "Floating-point value out of range: overflow");
+ }
+ // Subnormals cause ERANGE but a value is still returned
+ }
+
+ if (is_hexfloat(str, detail::strlen(str)) &&
+ (options & detail::float_scanner<T>::allow_hex) == 0) {
+ return error(error::invalid_scanned_value,
+ "Hexfloats not allowed by the format string");
+ }
+
+ SCN_GCC_COMPAT_POP
+ return f;
+ }
+#if SCN_GCC >= SCN_COMPILER(7, 0, 0)
+ SCN_GCC_POP
+#endif
+
+ template <typename CharT, typename T>
+ struct read;
+
+ template <>
+ struct read<char, float> {
+ static expected<float> get(const char* str,
+ size_t& chars,
+ uint8_t options)
+ {
+ return impl<float>(strtof, HUGE_VALF, str, chars, options);
+ }
+ };
+
+ template <>
+ struct read<char, double> {
+ static expected<double> get(const char* str,
+ size_t& chars,
+ uint8_t options)
+ {
+ return impl<double>(strtod, HUGE_VAL, str, chars, options);
+ }
+ };
+
+ template <>
+ struct read<char, long double> {
+ static expected<long double> get(const char* str,
+ size_t& chars,
+ uint8_t options)
+ {
+ return impl<long double>(strtold, HUGE_VALL, str, chars,
+ options);
+ }
+ };
+
+ template <>
+ struct read<wchar_t, float> {
+ static expected<float> get(const wchar_t* str,
+ size_t& chars,
+ uint8_t options)
+ {
+ return impl<float>(wcstof, HUGE_VALF, str, chars, options);
+ }
+ };
+ template <>
+ struct read<wchar_t, double> {
+ static expected<double> get(const wchar_t* str,
+ size_t& chars,
+ uint8_t options)
+ {
+ return impl<double>(wcstod, HUGE_VAL, str, chars, options);
+ }
+ };
+ template <>
+ struct read<wchar_t, long double> {
+ static expected<long double> get(const wchar_t* str,
+ size_t& chars,
+ uint8_t options)
+ {
+ return impl<long double>(wcstold, HUGE_VALL, str, chars,
+ options);
+ }
+ };
+ } // namespace cstd
+
+ namespace from_chars {
+#if SCN_HAS_FLOAT_CHARCONV
+ template <typename T>
+ struct read {
+ static expected<T> get(const char* str,
+ size_t& chars,
+ uint8_t options)
+ {
+ const auto len = std::strlen(str);
+ std::chars_format flags{};
+ if (((options & detail::float_scanner<T>::allow_hex) !=
+ 0) &&
+ is_hexfloat(str, len)) {
+ str += 2;
+ flags = std::chars_format::hex;
+ }
+ else {
+ if ((options & detail::float_scanner<T>::allow_fixed) !=
+ 0) {
+ flags |= std::chars_format::fixed;
+ }
+ if ((options &
+ detail::float_scanner<T>::allow_scientific) != 0) {
+ flags |= std::chars_format::scientific;
+ }
+ }
+ if (flags == static_cast<std::chars_format>(0)) {
+ return error{error::invalid_scanned_value,
+ "Expected a hexfloat"};
+ }
+
+ T value{};
+ const auto result =
+ std::from_chars(str, str + len, value, flags);
+ if (result.ec == std::errc::invalid_argument) {
+ return error(error::invalid_scanned_value,
+ "from_chars");
+ }
+ if (result.ec == std::errc::result_out_of_range) {
+ // Out of range, may be subnormal -> fall back to strtod
+ // On gcc std::from_chars doesn't parse subnormals
+ return cstd::read<char, T>::get(str, chars, options);
+ }
+ chars = static_cast<size_t>(result.ptr - str);
+ return value;
+ }
+ };
+#else
+ template <typename T>
+ struct read {
+ static expected<T> get(const char* str,
+ size_t& chars,
+ uint8_t options)
+ {
+ // Fall straight back to strtod
+ return cstd::read<char, T>::get(str, chars, options);
+ }
+ };
+#endif
+ } // namespace from_chars
+
+ namespace fast_float {
+ template <typename T>
+ expected<T> impl(const char* str,
+ size_t& chars,
+ uint8_t options,
+ char locale_decimal_point)
+ {
+ const auto len = std::strlen(str);
+ if (((options & detail::float_scanner<T>::allow_hex) != 0) &&
+ is_hexfloat(str, len)) {
+ // fast_float doesn't support hexfloats
+ return from_chars::read<T>::get(str, chars, options);
+ }
+
+ T value{};
+ ::fast_float::parse_options flags{};
+ if ((options & detail::float_scanner<T>::allow_fixed) != 0) {
+ flags.format = ::fast_float::fixed;
+ }
+ if ((options & detail::float_scanner<T>::allow_scientific) !=
+ 0) {
+ flags.format = static_cast<::fast_float::chars_format>(
+ flags.format | ::fast_float::scientific);
+ }
+ if ((options & detail::float_scanner<T>::localized) != 0) {
+ flags.decimal_point = locale_decimal_point;
+ }
+
+ const auto result = ::fast_float::from_chars_advanced(
+ str, str + len, value, flags);
+ if (result.ec == std::errc::invalid_argument) {
+ return error(error::invalid_scanned_value, "fast_float");
+ }
+ if (result.ec == std::errc::result_out_of_range) {
+ return error(error::value_out_of_range, "fast_float");
+ }
+ if (std::isinf(value)) {
+ // fast_float represents very large or small values as inf
+ // But, it also parses "inf", which from_chars does not
+ if (!(len >= 3 && (str[0] == 'i' || str[0] == 'I'))) {
+ // Input was not actually infinity ->
+ // invalid result, fall back to from_chars
+ return from_chars::read<T>::get(str, chars, options);
+ }
+ }
+ chars = static_cast<size_t>(result.ptr - str);
+ return value;
+ }
+
+ template <typename T>
+ struct read;
+
+ template <>
+ struct read<float> {
+ static expected<float> get(const char* str,
+ size_t& chars,
+ uint8_t options,
+ char locale_decimal_point)
+ {
+ return impl<float>(str, chars, options,
+ locale_decimal_point);
+ }
+ };
+ template <>
+ struct read<double> {
+ static expected<double> get(const char* str,
+ size_t& chars,
+ uint8_t options,
+ char locale_decimal_points)
+ {
+ return impl<double>(str, chars, options,
+ locale_decimal_points);
+ }
+ };
+ template <>
+ struct read<long double> {
+ static expected<long double> get(const char* str,
+ size_t& chars,
+ uint8_t options,
+ char)
+ {
+ // Fallback to strtod
+ // fast_float doesn't support long double
+ return cstd::read<char, long double>::get(str, chars,
+ options);
+ }
+ };
+ } // namespace fast_float
+
+ template <typename CharT, typename T>
+ struct read;
+
+ template <typename T>
+ struct read<char, T> {
+ static expected<T> get(const char* str,
+ size_t& chars,
+ uint8_t options,
+ char locale_decimal_points)
+ {
+ // char -> default to fast_float,
+ // fallback to strtod if necessary
+ return read_float::fast_float::read<T>::get(
+ str, chars, options, locale_decimal_points);
+ }
+ };
+ template <typename T>
+ struct read<wchar_t, T> {
+ static expected<T> get(const wchar_t* str,
+ size_t& chars,
+ uint8_t options,
+ wchar_t)
+ {
+ // wchar_t -> straight to strtod
+ return read_float::cstd::read<wchar_t, T>::get(str, chars,
+ options);
+ }
+ };
+ } // namespace read_float
+
+ namespace detail {
+ template <typename T>
+ template <typename CharT>
+ expected<T> float_scanner<T>::_read_float_impl(
+ const CharT* str,
+ size_t& chars,
+ CharT locale_decimal_point)
+ {
+ // Parsing algorithm to use:
+ // If CharT == wchar_t -> strtod
+ // If CharT == char:
+ // 1. fast_float
+ // fallback if a hex float, or incorrectly parsed an inf
+ // (very large or small value)
+ // 2. std::from_chars
+ // fallback if not available (C++17) or float is subnormal
+ // 3. std::strtod
+ return read_float::read<CharT, T>::get(str, chars, format_options,
+ locale_decimal_point);
+ }
+
+#if SCN_INCLUDE_SOURCE_DEFINITIONS
+
+ template expected<float>
+ float_scanner<float>::_read_float_impl(const char*, size_t&, char);
+ template expected<double>
+ float_scanner<double>::_read_float_impl(const char*, size_t&, char);
+ template expected<long double>
+ float_scanner<long double>::_read_float_impl(const char*,
+ size_t&,
+ char);
+ template expected<float> float_scanner<float>::_read_float_impl(
+ const wchar_t*,
+ size_t&,
+ wchar_t);
+ template expected<double> float_scanner<double>::_read_float_impl(
+ const wchar_t*,
+ size_t&,
+ wchar_t);
+ template expected<long double>
+ float_scanner<long double>::_read_float_impl(const wchar_t*,
+ size_t&,
+ wchar_t);
+#endif
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
diff --git a/src/third-party/scnlib/src/reader_int.cpp b/src/third-party/scnlib/src/reader_int.cpp
new file mode 100644
index 0000000..9b953d2
--- /dev/null
+++ b/src/third-party/scnlib/src/reader_int.cpp
@@ -0,0 +1,372 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY
+#define SCN_READER_INT_CPP
+#endif
+
+#include <scn/detail/args.h>
+#include <scn/reader/int.h>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+ namespace detail {
+ SCN_NODISCARD static unsigned char _char_to_int(char ch)
+ {
+ static constexpr unsigned char digits_arr[] = {
+ 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};
+ return digits_arr[static_cast<unsigned char>(ch)];
+ }
+ SCN_NODISCARD static unsigned char _char_to_int(wchar_t ch)
+ {
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wconversion")
+ if (ch >= std::numeric_limits<char>::min() &&
+ ch <= std::numeric_limits<char>::max()) {
+ return _char_to_int(static_cast<char>(ch));
+ }
+ return 255;
+ SCN_GCC_POP
+ }
+
+ template <typename T>
+ template <typename CharT>
+ expected<typename span<const CharT>::iterator>
+ integer_scanner<T>::parse_base_prefix(span<const CharT> s, int& b) const
+ {
+ auto it = s.begin();
+ // If base can be detected, it should start with '0'
+ if (*it == ascii_widen<CharT>('0')) {
+ ++it;
+ if (it == s.end()) {
+ // It's really just 0
+ // Return it to begininng, where '0' is
+ b = -1;
+ return it;
+ }
+ if (*it == ascii_widen<CharT>('x') ||
+ *it == ascii_widen<CharT>('X')) {
+ // Hex: 0x or 0X
+ ++it;
+ if (SCN_UNLIKELY(it == s.end())) {
+ // 0x/0X not a valid number
+ b = -1;
+ return --it;
+ }
+ if (b == 0) {
+ // Detect base
+ b = 16;
+ }
+ else if (b != 16) {
+ // Invalid prefix for base
+ return error(error::invalid_scanned_value,
+ "Invalid base prefix");
+ }
+ }
+ else if (*it == ascii_widen<CharT>('b') ||
+ *it == ascii_widen<CharT>('B')) {
+ // Binary: 0b or 0b
+ ++it;
+ if (SCN_UNLIKELY(it == s.end())) {
+ // 0b/0B not a valid number
+ b = -1;
+ return --it;
+ }
+ if (b == 0) {
+ // Detect base
+ b = 2;
+ }
+ else if (b != 2) {
+ // Invalid prefix for base
+ return error(error::invalid_scanned_value,
+ "Invalid base prefix");
+ }
+ }
+ else if (*it == ascii_widen<CharT>('o') ||
+ *it == ascii_widen<CharT>('O')) {
+ // Octal: 0o or 0O
+ ++it;
+ if (SCN_UNLIKELY(it == s.end())) {
+ // 0o/0O not a valid number
+ b = -1;
+ return --it;
+ }
+ if (b == 0) {
+ // Detect base
+ b = 8;
+ }
+ else if (b != 8) {
+ // Invalid prefix for base
+ return error(error::invalid_scanned_value,
+ "Invalid base prefix");
+ }
+ }
+ else if (b == 0) {
+ // Starting with only 0 -> octal
+ b = 8;
+ }
+ }
+ if (b == 0) {
+ // None detected, default to 10
+ b = 10;
+ }
+ return it;
+ }
+
+ template <typename T>
+ template <typename CharT>
+ expected<std::ptrdiff_t> integer_scanner<T>::_parse_int(
+ T& val,
+ span<const CharT> s)
+ {
+ SCN_EXPECT(s.size() > 0);
+
+ SCN_MSVC_PUSH
+ SCN_MSVC_IGNORE(4244)
+ SCN_MSVC_IGNORE(4127) // conditional expression is constant
+
+ if (std::is_unsigned<T>::value) {
+ if (s[0] == detail::ascii_widen<CharT>('-')) {
+ return error(error::invalid_scanned_value,
+ "Unexpected sign '-' when scanning an "
+ "unsigned integer");
+ }
+ }
+
+ SCN_MSVC_POP
+
+ T tmp = 0;
+ bool minus_sign = false;
+ auto it = s.begin();
+
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wsign-conversion")
+ if (s[0] == ascii_widen<CharT>('-')) {
+ if (SCN_UNLIKELY((format_options & only_unsigned) != 0)) {
+ // 'u' option -> negative values disallowed
+ return error(error::invalid_scanned_value,
+ "Parsed negative value when type was 'u'");
+ }
+ minus_sign = true;
+ ++it;
+ }
+ else if (s[0] == ascii_widen<CharT>('+')) {
+ ++it;
+ }
+ SCN_GCC_POP
+ if (SCN_UNLIKELY(it == s.end())) {
+ return error(error::invalid_scanned_value,
+ "Expected number after sign");
+ }
+
+ // Format string was 'i' or empty -> detect base
+ // or
+ // allow_base_prefix (skip 0x etc.)
+ if (SCN_UNLIKELY(base == 0 ||
+ (format_options & allow_base_prefix) != 0)) {
+ int b{base};
+ auto r = parse_base_prefix<CharT>({it, s.end()}, b);
+ if (!r) {
+ return r.error();
+ }
+ if (b == -1) {
+ // -1 means we read a '0'
+ val = 0;
+ return ranges::distance(s.begin(), r.value());
+ }
+ if (b != 10 && base != b && base != 0) {
+ return error(error::invalid_scanned_value,
+ "Invalid base prefix");
+ }
+ if (base == 0) {
+ base = static_cast<uint8_t>(b);
+ }
+ it = r.value();
+ }
+
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wconversion")
+ SCN_GCC_IGNORE("-Wsign-conversion")
+ SCN_GCC_IGNORE("-Wsign-compare")
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wconversion")
+ SCN_CLANG_IGNORE("-Wsign-conversion")
+ SCN_CLANG_IGNORE("-Wsign-compare")
+
+ SCN_ASSUME(base > 0);
+
+ SCN_CLANG_PUSH_IGNORE_UNDEFINED_TEMPLATE
+ auto r = _parse_int_impl(tmp, minus_sign, make_span(it, s.end()));
+ SCN_CLANG_POP_IGNORE_UNDEFINED_TEMPLATE
+ if (!r) {
+ return r.error();
+ }
+ it = r.value();
+ if (s.begin() == it) {
+ return error(error::invalid_scanned_value, "custom::read_int");
+ }
+ val = tmp;
+ return ranges::distance(s.begin(), it);
+
+ SCN_CLANG_POP
+ SCN_GCC_POP
+ }
+
+ template <typename T>
+ template <typename CharT>
+ expected<typename span<const CharT>::iterator>
+ integer_scanner<T>::_parse_int_impl(T& val,
+ bool minus_sign,
+ span<const CharT> buf) const
+ {
+ SCN_GCC_PUSH
+ SCN_GCC_IGNORE("-Wconversion")
+ SCN_GCC_IGNORE("-Wsign-conversion")
+ SCN_GCC_IGNORE("-Wsign-compare")
+
+ SCN_CLANG_PUSH
+ SCN_CLANG_IGNORE("-Wconversion")
+ SCN_CLANG_IGNORE("-Wsign-conversion")
+ SCN_CLANG_IGNORE("-Wsign-compare")
+
+ SCN_MSVC_PUSH
+ SCN_MSVC_IGNORE(4018) // > signed/unsigned mismatch
+ SCN_MSVC_IGNORE(4389) // == signed/unsigned mismatch
+ SCN_MSVC_IGNORE(4244) // lossy conversion
+
+ using utype = typename std::make_unsigned<T>::type;
+
+ const auto ubase = static_cast<utype>(base);
+ SCN_ASSUME(ubase > 0);
+
+ constexpr auto uint_max = static_cast<utype>(-1);
+ constexpr auto int_max = static_cast<utype>(uint_max >> 1);
+ constexpr auto abs_int_min = static_cast<utype>(int_max + 1);
+
+ const auto cut = div(
+ [&]() -> utype {
+ if (std::is_signed<T>::value) {
+ if (minus_sign) {
+ return abs_int_min;
+ }
+ return int_max;
+ }
+ return uint_max;
+ }(),
+ ubase);
+ const auto cutoff = cut.first;
+ const auto cutlim = cut.second;
+
+ auto it = buf.begin();
+ const auto end = buf.end();
+ utype tmp = 0;
+ for (; it != end; ++it) {
+ const auto digit = _char_to_int(*it);
+ if (digit >= ubase) {
+ break;
+ }
+ if (SCN_UNLIKELY(tmp > cutoff ||
+ (tmp == cutoff && digit > cutlim))) {
+ if (!minus_sign) {
+ return error(error::value_out_of_range,
+ "Out of range: integer overflow");
+ }
+ return error(error::value_out_of_range,
+ "Out of range: integer underflow");
+ }
+ tmp = tmp * ubase + digit;
+ }
+ if (minus_sign) {
+ // special case: signed int minimum's absolute value can't
+ // be represented with the same type
+ //
+ // For example, short int -- range is [-32768, 32767], 32768
+ // can't be represented
+ //
+ // In that case, -static_cast<T>(tmp) would trigger UB
+ if (SCN_UNLIKELY(tmp == abs_int_min)) {
+ val = std::numeric_limits<T>::min();
+ }
+ else {
+ val = -static_cast<T>(tmp);
+ }
+ }
+ else {
+ val = static_cast<T>(tmp);
+ }
+ return it;
+
+ SCN_MSVC_POP
+ SCN_CLANG_POP
+ SCN_GCC_POP
+ }
+
+#if SCN_INCLUDE_SOURCE_DEFINITIONS
+
+#define SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(CharT, T) \
+ template expected<std::ptrdiff_t> integer_scanner<T>::_parse_int( \
+ T& val, span<const CharT> s); \
+ template expected<typename span<const CharT>::iterator> \
+ integer_scanner<T>::_parse_int_impl(T& val, bool minus_sign, \
+ span<const CharT> buf) const; \
+ template expected<typename span<const CharT>::iterator> \
+ integer_scanner<T>::parse_base_prefix(span<const CharT>, int&) const;
+
+#define SCN_DEFINE_INTEGER_SCANNER_MEMBERS(Char) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, signed char) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, short) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, int) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, long) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, long long) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, unsigned char) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, unsigned short) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, unsigned int) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, unsigned long) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, unsigned long long) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, char) \
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS_IMPL(Char, wchar_t)
+
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS(char)
+ SCN_DEFINE_INTEGER_SCANNER_MEMBERS(wchar_t)
+
+#endif
+
+ } // namespace detail
+
+ SCN_END_NAMESPACE
+} // namespace scn
diff --git a/src/third-party/scnlib/src/vscan.cpp b/src/third-party/scnlib/src/vscan.cpp
new file mode 100644
index 0000000..7365773
--- /dev/null
+++ b/src/third-party/scnlib/src/vscan.cpp
@@ -0,0 +1,80 @@
+// Copyright 2017 Elias Kosunen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// This file is a part of scnlib:
+// https://github.com/eliaskosunen/scnlib
+
+#if defined(SCN_HEADER_ONLY) && SCN_HEADER_ONLY
+#define SCN_VSCAN_CPP
+#endif
+
+#include <scn/scan/vscan.h>
+
+#include <scn/detail/context.h>
+#include <scn/detail/parse_context.h>
+#include <scn/detail/visitor.h>
+
+namespace scn {
+ SCN_BEGIN_NAMESPACE
+
+#if SCN_INCLUDE_SOURCE_DEFINITIONS
+
+#define SCN_VSCAN_DEFINE(Range, WrappedAlias, CharAlias) \
+ vscan_result<detail::vscan_macro::WrappedAlias> vscan( \
+ detail::vscan_macro::WrappedAlias&& range, \
+ basic_string_view<detail::vscan_macro::CharAlias> fmt, \
+ basic_args<detail::vscan_macro::CharAlias>&& args) \
+ { \
+ return detail::vscan_boilerplate(SCN_MOVE(range), fmt, \
+ SCN_MOVE(args)); \
+ } \
+ \
+ vscan_result<detail::vscan_macro::WrappedAlias> vscan_default( \
+ detail::vscan_macro::WrappedAlias&& range, int n_args, \
+ basic_args<detail::vscan_macro::CharAlias>&& args) \
+ { \
+ return detail::vscan_boilerplate_default(SCN_MOVE(range), n_args, \
+ SCN_MOVE(args)); \
+ } \
+ \
+ vscan_result<detail::vscan_macro::WrappedAlias> vscan_localized( \
+ detail::vscan_macro::WrappedAlias&& range, \
+ basic_locale_ref<detail::vscan_macro::CharAlias>&& loc, \
+ basic_string_view<detail::vscan_macro::CharAlias> fmt, \
+ basic_args<detail::vscan_macro::CharAlias>&& args) \
+ { \
+ return detail::vscan_boilerplate_localized( \
+ SCN_MOVE(range), SCN_MOVE(loc), fmt, SCN_MOVE(args)); \
+ } \
+ \
+ error vscan_usertype( \
+ basic_context<detail::vscan_macro::WrappedAlias>& ctx, \
+ basic_string_view<detail::vscan_macro::CharAlias> f, \
+ basic_args<detail::vscan_macro::CharAlias>&& args) \
+ { \
+ auto pctx = make_parse_context(f, ctx.locale()); \
+ return visit(ctx, pctx, SCN_MOVE(args)); \
+ }
+
+ SCN_VSCAN_DEFINE(string_view, string_view_wrapped, string_view_char)
+ SCN_VSCAN_DEFINE(wstring_view, wstring_view_wrapped, wstring_view_char)
+ SCN_VSCAN_DEFINE(std::string, string_wrapped, string_char)
+ SCN_VSCAN_DEFINE(std::wstring, wstring_wrapped, wstring_char)
+ SCN_VSCAN_DEFINE(file&, file_ref_wrapped, file_ref_char)
+ SCN_VSCAN_DEFINE(wfile&, wfile_ref_wrapped, wfile_ref_char)
+
+#endif
+
+ SCN_END_NAMESPACE
+} // namespace scn
diff --git a/src/third-party/sqlite/ext/dbdump.c b/src/third-party/sqlite/ext/dbdump.c
new file mode 100644
index 0000000..9d0764d
--- /dev/null
+++ b/src/third-party/sqlite/ext/dbdump.c
@@ -0,0 +1,730 @@
+/*
+** 2016-03-13
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file implements a C-language subroutine that converts the content
+** of an SQLite database into UTF-8 text SQL statements that can be used
+** to exactly recreate the original database. ROWID values are preserved.
+**
+** A prototype of the implemented subroutine is this:
+**
+** int sqlite3_db_dump(
+** sqlite3 *db,
+** const char *zSchema,
+** const char *zTable,
+** void (*xCallback)(void*, const char*),
+** void *pArg
+** );
+**
+** The db parameter is the database connection. zSchema is the schema within
+** that database which is to be dumped. Usually the zSchema is "main" but
+** can also be "temp" or any ATTACH-ed database. If zTable is not NULL, then
+** only the content of that one table is dumped. If zTable is NULL, then all
+** tables are dumped.
+**
+** The generate text is passed to xCallback() in multiple calls. The second
+** argument to xCallback() is a copy of the pArg parameter. The first
+** argument is some of the output text that this routine generates. The
+** signature to xCallback() is designed to make it compatible with fputs().
+**
+** The sqlite3_db_dump() subroutine returns SQLITE_OK on success or some error
+** code if it encounters a problem.
+**
+** If this file is compiled with -DDBDUMP_STANDALONE then a "main()" routine
+** is included so that this routine becomes a command-line utility. The
+** command-line utility takes two or three arguments which are the name
+** of the database file, the schema, and optionally the table, forming the
+** first three arguments of a single call to the library routine.
+*/
+#include "sqlite3.h"
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+/*
+** The state of the dump process.
+*/
+typedef struct DState DState;
+struct DState {
+ sqlite3 *db; /* The database connection */
+ int nErr; /* Number of errors seen so far */
+ int rc; /* Error code */
+ int writableSchema; /* True if in writable_schema mode */
+ int (*xCallback)(const char*,void*); /* Send output here */
+ void *pArg; /* Argument to xCallback() */
+};
+
+/*
+** A variable length string to which one can append text.
+*/
+typedef struct DText DText;
+struct DText {
+ char *z; /* The text */
+ int n; /* Number of bytes of content in z[] */
+ int nAlloc; /* Number of bytes allocated to z[] */
+};
+
+/*
+** Initialize and destroy a DText object
+*/
+static void initText(DText *p){
+ memset(p, 0, sizeof(*p));
+}
+static void freeText(DText *p){
+ sqlite3_free(p->z);
+ initText(p);
+}
+
+/* zIn is either a pointer to a NULL-terminated string in memory obtained
+** from malloc(), or a NULL pointer. The string pointed to by zAppend is
+** added to zIn, and the result returned in memory obtained from malloc().
+** zIn, if it was not NULL, is freed.
+**
+** If the third argument, quote, is not '\0', then it is used as a
+** quote character for zAppend.
+*/
+static void appendText(DText *p, char const *zAppend, char quote){
+ int len;
+ int i;
+ int nAppend = (int)(strlen(zAppend) & 0x3fffffff);
+
+ len = nAppend+p->n+1;
+ if( quote ){
+ len += 2;
+ for(i=0; i<nAppend; i++){
+ if( zAppend[i]==quote ) len++;
+ }
+ }
+
+ if( p->n+len>=p->nAlloc ){
+ char *zNew;
+ p->nAlloc = p->nAlloc*2 + len + 20;
+ zNew = sqlite3_realloc(p->z, p->nAlloc);
+ if( zNew==0 ){
+ freeText(p);
+ return;
+ }
+ p->z = zNew;
+ }
+
+ if( quote ){
+ char *zCsr = p->z+p->n;
+ *zCsr++ = quote;
+ for(i=0; i<nAppend; i++){
+ *zCsr++ = zAppend[i];
+ if( zAppend[i]==quote ) *zCsr++ = quote;
+ }
+ *zCsr++ = quote;
+ p->n = (int)(zCsr - p->z);
+ *zCsr = '\0';
+ }else{
+ memcpy(p->z+p->n, zAppend, nAppend);
+ p->n += nAppend;
+ p->z[p->n] = '\0';
+ }
+}
+
+/*
+** Attempt to determine if identifier zName needs to be quoted, either
+** because it contains non-alphanumeric characters, or because it is an
+** SQLite keyword. Be conservative in this estimate: When in doubt assume
+** that quoting is required.
+**
+** Return '"' if quoting is required. Return 0 if no quoting is required.
+*/
+static char quoteChar(const char *zName){
+ int i;
+ if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"';
+ for(i=0; zName[i]; i++){
+ if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"';
+ }
+#if 0
+ return sqlite3_keyword_check(zName, i) ? '"' : 0;
+#else
+ return 0;
+#endif
+}
+
+
+/*
+** Release memory previously allocated by tableColumnList().
+*/
+static void freeColumnList(char **azCol){
+ int i;
+ for(i=1; azCol[i]; i++){
+ sqlite3_free(azCol[i]);
+ }
+ /* azCol[0] is a static string */
+ sqlite3_free(azCol);
+}
+
+/*
+** Return a list of pointers to strings which are the names of all
+** columns in table zTab. The memory to hold the names is dynamically
+** allocated and must be released by the caller using a subsequent call
+** to freeColumnList().
+**
+** The azCol[0] entry is usually NULL. However, if zTab contains a rowid
+** value that needs to be preserved, then azCol[0] is filled in with the
+** name of the rowid column.
+**
+** The first regular column in the table is azCol[1]. The list is terminated
+** by an entry with azCol[i]==0.
+*/
+static char **tableColumnList(DState *p, const char *zTab){
+ char **azCol = 0;
+ sqlite3_stmt *pStmt = 0;
+ char *zSql;
+ int nCol = 0;
+ int nAlloc = 0;
+ int nPK = 0; /* Number of PRIMARY KEY columns seen */
+ int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */
+ int preserveRowid = 1;
+ int rc;
+
+ zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab);
+ if( zSql==0 ) return 0;
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ if( rc ) return 0;
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ if( nCol>=nAlloc-2 ){
+ char **azNew;
+ nAlloc = nAlloc*2 + nCol + 10;
+ azNew = sqlite3_realloc64(azCol, nAlloc*sizeof(azCol[0]));
+ if( azNew==0 ) goto col_oom;
+ azCol = azNew;
+ azCol[0] = 0;
+ }
+ azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1));
+ if( azCol[nCol]==0 ) goto col_oom;
+ if( sqlite3_column_int(pStmt, 5) ){
+ nPK++;
+ if( nPK==1
+ && sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,2),
+ "INTEGER")==0
+ ){
+ isIPK = 1;
+ }else{
+ isIPK = 0;
+ }
+ }
+ }
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ azCol[nCol+1] = 0;
+
+ /* The decision of whether or not a rowid really needs to be preserved
+ ** is tricky. We never need to preserve a rowid for a WITHOUT ROWID table
+ ** or a table with an INTEGER PRIMARY KEY. We are unable to preserve
+ ** rowids on tables where the rowid is inaccessible because there are other
+ ** columns in the table named "rowid", "_rowid_", and "oid".
+ */
+ if( isIPK ){
+ /* If a single PRIMARY KEY column with type INTEGER was seen, then it
+ ** might be an alise for the ROWID. But it might also be a WITHOUT ROWID
+ ** table or a INTEGER PRIMARY KEY DESC column, neither of which are
+ ** ROWID aliases. To distinguish these cases, check to see if
+ ** there is a "pk" entry in "PRAGMA index_list". There will be
+ ** no "pk" index if the PRIMARY KEY really is an alias for the ROWID.
+ */
+ zSql = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)"
+ " WHERE origin='pk'", zTab);
+ if( zSql==0 ) goto col_oom;
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ if( rc ){
+ freeColumnList(azCol);
+ return 0;
+ }
+ rc = sqlite3_step(pStmt);
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ preserveRowid = rc==SQLITE_ROW;
+ }
+ if( preserveRowid ){
+ /* Only preserve the rowid if we can find a name to use for the
+ ** rowid */
+ static char *azRowid[] = { "rowid", "_rowid_", "oid" };
+ int i, j;
+ for(j=0; j<3; j++){
+ for(i=1; i<=nCol; i++){
+ if( sqlite3_stricmp(azRowid[j],azCol[i])==0 ) break;
+ }
+ if( i>nCol ){
+ /* At this point, we know that azRowid[j] is not the name of any
+ ** ordinary column in the table. Verify that azRowid[j] is a valid
+ ** name for the rowid before adding it to azCol[0]. WITHOUT ROWID
+ ** tables will fail this last check */
+ rc = sqlite3_table_column_metadata(p->db,0,zTab,azRowid[j],0,0,0,0,0);
+ if( rc==SQLITE_OK ) azCol[0] = azRowid[j];
+ break;
+ }
+ }
+ }
+ return azCol;
+
+col_oom:
+ sqlite3_finalize(pStmt);
+ freeColumnList(azCol);
+ p->nErr++;
+ p->rc = SQLITE_NOMEM;
+ return 0;
+}
+
+/*
+** Send mprintf-formatted content to the output callback.
+*/
+static void output_formatted(DState *p, const char *zFormat, ...){
+ va_list ap;
+ char *z;
+ va_start(ap, zFormat);
+ z = sqlite3_vmprintf(zFormat, ap);
+ va_end(ap);
+ p->xCallback(z, p->pArg);
+ sqlite3_free(z);
+}
+
+/*
+** Find a string that is not found anywhere in z[]. Return a pointer
+** to that string.
+**
+** Try to use zA and zB first. If both of those are already found in z[]
+** then make up some string and store it in the buffer zBuf.
+*/
+static const char *unused_string(
+ const char *z, /* Result must not appear anywhere in z */
+ const char *zA, const char *zB, /* Try these first */
+ char *zBuf /* Space to store a generated string */
+){
+ unsigned i = 0;
+ if( strstr(z, zA)==0 ) return zA;
+ if( strstr(z, zB)==0 ) return zB;
+ do{
+ sqlite3_snprintf(20,zBuf,"(%s%u)", zA, i++);
+ }while( strstr(z,zBuf)!=0 );
+ return zBuf;
+}
+
+/*
+** Output the given string as a quoted string using SQL quoting conventions.
+** Additionallly , escape the "\n" and "\r" characters so that they do not
+** get corrupted by end-of-line translation facilities in some operating
+** systems.
+*/
+static void output_quoted_escaped_string(DState *p, const char *z){
+ int i;
+ char c;
+ for(i=0; (c = z[i])!=0 && c!='\'' && c!='\n' && c!='\r'; i++){}
+ if( c==0 ){
+ output_formatted(p,"'%s'",z);
+ }else{
+ const char *zNL = 0;
+ const char *zCR = 0;
+ int nNL = 0;
+ int nCR = 0;
+ char zBuf1[20], zBuf2[20];
+ for(i=0; z[i]; i++){
+ if( z[i]=='\n' ) nNL++;
+ if( z[i]=='\r' ) nCR++;
+ }
+ if( nNL ){
+ p->xCallback("replace(", p->pArg);
+ zNL = unused_string(z, "\\n", "\\012", zBuf1);
+ }
+ if( nCR ){
+ p->xCallback("replace(", p->pArg);
+ zCR = unused_string(z, "\\r", "\\015", zBuf2);
+ }
+ p->xCallback("'", p->pArg);
+ while( *z ){
+ for(i=0; (c = z[i])!=0 && c!='\n' && c!='\r' && c!='\''; i++){}
+ if( c=='\'' ) i++;
+ if( i ){
+ output_formatted(p, "%.*s", i, z);
+ z += i;
+ }
+ if( c=='\'' ){
+ p->xCallback("'", p->pArg);
+ continue;
+ }
+ if( c==0 ){
+ break;
+ }
+ z++;
+ if( c=='\n' ){
+ p->xCallback(zNL, p->pArg);
+ continue;
+ }
+ p->xCallback(zCR, p->pArg);
+ }
+ p->xCallback("'", p->pArg);
+ if( nCR ){
+ output_formatted(p, ",'%s',char(13))", zCR);
+ }
+ if( nNL ){
+ output_formatted(p, ",'%s',char(10))", zNL);
+ }
+ }
+}
+
+/*
+** This is an sqlite3_exec callback routine used for dumping the database.
+** Each row received by this callback consists of a table name,
+** the table type ("index" or "table") and SQL to create the table.
+** This routine should print text sufficient to recreate the table.
+*/
+static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
+ int rc;
+ const char *zTable;
+ const char *zType;
+ const char *zSql;
+ DState *p = (DState*)pArg;
+ sqlite3_stmt *pStmt;
+
+ (void)azCol;
+ if( nArg!=3 ) return 1;
+ zTable = azArg[0];
+ zType = azArg[1];
+ zSql = azArg[2];
+
+ if( strcmp(zTable, "sqlite_sequence")==0 ){
+ p->xCallback("DELETE FROM sqlite_sequence;\n", p->pArg);
+ }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 ){
+ p->xCallback("ANALYZE sqlite_schema;\n", p->pArg);
+ }else if( strncmp(zTable, "sqlite_", 7)==0 ){
+ return 0;
+ }else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
+#if 0
+ if( !p->writableSchema ){
+ p->xCallback("PRAGMA writable_schema=ON;\n", p->pArg);
+ p->writableSchema = 1;
+ }
+ output_formatted(p,
+ "INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)"
+ "VALUES('table','%q','%q',0,'%q');",
+ zTable, zTable, zSql);
+ return 0;
+#endif
+ }else{
+ if( sqlite3_strglob("CREATE TABLE ['\"]*", zSql)==0 ){
+ p->xCallback("CREATE TABLE IF NOT EXISTS ", p->pArg);
+ p->xCallback(zSql+13, p->pArg);
+ }else{
+ p->xCallback(zSql, p->pArg);
+ }
+ p->xCallback(";\n", p->pArg);
+ }
+
+ if( strcmp(zType, "table")==0 ){
+ DText sSelect;
+ DText sTable;
+ char **azTCol;
+ int i;
+ int nCol;
+
+ azTCol = tableColumnList(p, zTable);
+ if( azTCol==0 ) return 0;
+
+ initText(&sTable);
+ appendText(&sTable, "INSERT INTO ", 0);
+
+ /* Always quote the table name, even if it appears to be pure ascii,
+ ** in case it is a keyword. Ex: INSERT INTO "table" ... */
+ appendText(&sTable, zTable, quoteChar(zTable));
+
+ /* If preserving the rowid, add a column list after the table name.
+ ** In other words: "INSERT INTO tab(rowid,a,b,c,...) VALUES(...)"
+ ** instead of the usual "INSERT INTO tab VALUES(...)".
+ */
+ if( azTCol[0] ){
+ appendText(&sTable, "(", 0);
+ appendText(&sTable, azTCol[0], 0);
+ for(i=1; azTCol[i]; i++){
+ appendText(&sTable, ",", 0);
+ appendText(&sTable, azTCol[i], quoteChar(azTCol[i]));
+ }
+ appendText(&sTable, ")", 0);
+ }
+ appendText(&sTable, " VALUES(", 0);
+
+ /* Build an appropriate SELECT statement */
+ initText(&sSelect);
+ appendText(&sSelect, "SELECT ", 0);
+ if( azTCol[0] ){
+ appendText(&sSelect, azTCol[0], 0);
+ appendText(&sSelect, ",", 0);
+ }
+ for(i=1; azTCol[i]; i++){
+ appendText(&sSelect, azTCol[i], quoteChar(azTCol[i]));
+ if( azTCol[i+1] ){
+ appendText(&sSelect, ",", 0);
+ }
+ }
+ nCol = i;
+ if( azTCol[0]==0 ) nCol--;
+ freeColumnList(azTCol);
+ appendText(&sSelect, " FROM ", 0);
+ appendText(&sSelect, zTable, quoteChar(zTable));
+
+ rc = sqlite3_prepare_v2(p->db, sSelect.z, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
+ p->nErr++;
+ if( p->rc==SQLITE_OK ) p->rc = rc;
+ }else{
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ p->xCallback(sTable.z, p->pArg);
+ for(i=0; i<nCol; i++){
+ if( i ) p->xCallback(",", p->pArg);
+ switch( sqlite3_column_type(pStmt,i) ){
+ case SQLITE_INTEGER: {
+ output_formatted(p, "%lld", sqlite3_column_int64(pStmt,i));
+ break;
+ }
+ case SQLITE_FLOAT: {
+ double r = sqlite3_column_double(pStmt,i);
+ sqlite3_uint64 ur;
+ memcpy(&ur,&r,sizeof(r));
+ if( ur==0x7ff0000000000000LL ){
+ p->xCallback("1e999", p->pArg);
+ }else if( ur==0xfff0000000000000LL ){
+ p->xCallback("-1e999", p->pArg);
+ }else{
+ output_formatted(p, "%!.20g", r);
+ }
+ break;
+ }
+ case SQLITE_NULL: {
+ p->xCallback("NULL", p->pArg);
+ break;
+ }
+ case SQLITE_TEXT: {
+ output_quoted_escaped_string(p,
+ (const char*)sqlite3_column_text(pStmt,i));
+ break;
+ }
+ case SQLITE_BLOB: {
+ int nByte = sqlite3_column_bytes(pStmt,i);
+ unsigned char *a = (unsigned char*)sqlite3_column_blob(pStmt,i);
+ int j;
+ p->xCallback("x'", p->pArg);
+ for(j=0; j<nByte; j++){
+ char zWord[3];
+ zWord[0] = "0123456789abcdef"[(a[j]>>4)&15];
+ zWord[1] = "0123456789abcdef"[a[j]&15];
+ zWord[2] = 0;
+ p->xCallback(zWord, p->pArg);
+ }
+ p->xCallback("'", p->pArg);
+ break;
+ }
+ }
+ }
+ p->xCallback(");\n", p->pArg);
+ }
+ }
+ sqlite3_finalize(pStmt);
+ freeText(&sTable);
+ freeText(&sSelect);
+ }
+ return 0;
+}
+
+
+/*
+** Execute a query statement that will generate SQL output. Print
+** the result columns, comma-separated, on a line and then add a
+** semicolon terminator to the end of that line.
+**
+** If the number of columns is 1 and that column contains text "--"
+** then write the semicolon on a separate line. That way, if a
+** "--" comment occurs at the end of the statement, the comment
+** won't consume the semicolon terminator.
+*/
+static void output_sql_from_query(
+ DState *p, /* Query context */
+ const char *zSelect, /* SELECT statement to extract content */
+ ...
+){
+ sqlite3_stmt *pSelect;
+ int rc;
+ int nResult;
+ int i;
+ const char *z;
+ char *zSql;
+ va_list ap;
+ va_start(ap, zSelect);
+ zSql = sqlite3_vmprintf(zSelect, ap);
+ va_end(ap);
+ if( zSql==0 ){
+ p->rc = SQLITE_NOMEM;
+ p->nErr++;
+ return;
+ }
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pSelect, 0);
+ sqlite3_free(zSql);
+ if( rc!=SQLITE_OK || !pSelect ){
+ output_formatted(p, "/**** ERROR: (%d) %s *****/\n", rc,
+ sqlite3_errmsg(p->db));
+ p->nErr++;
+ return;
+ }
+ rc = sqlite3_step(pSelect);
+ nResult = sqlite3_column_count(pSelect);
+ while( rc==SQLITE_ROW ){
+ z = (const char*)sqlite3_column_text(pSelect, 0);
+ p->xCallback(z, p->pArg);
+ for(i=1; i<nResult; i++){
+ p->xCallback(",", p->pArg);
+ p->xCallback((const char*)sqlite3_column_text(pSelect,i), p->pArg);
+ }
+ if( z==0 ) z = "";
+ while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
+ if( z[0] ){
+ p->xCallback("\n;\n", p->pArg);
+ }else{
+ p->xCallback(";\n", p->pArg);
+ }
+ rc = sqlite3_step(pSelect);
+ }
+ rc = sqlite3_finalize(pSelect);
+ if( rc!=SQLITE_OK ){
+ output_formatted(p, "/**** ERROR: (%d) %s *****/\n", rc,
+ sqlite3_errmsg(p->db));
+ if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
+ }
+}
+
+/*
+** Run zQuery. Use dump_callback() as the callback routine so that
+** the contents of the query are output as SQL statements.
+**
+** If we get a SQLITE_CORRUPT error, rerun the query after appending
+** "ORDER BY rowid DESC" to the end.
+*/
+static void run_schema_dump_query(
+ DState *p,
+ const char *zQuery,
+ ...
+){
+ char *zErr = 0;
+ char *z;
+ va_list ap;
+ va_start(ap, zQuery);
+ z = sqlite3_vmprintf(zQuery, ap);
+ va_end(ap);
+ sqlite3_exec(p->db, z, dump_callback, p, &zErr);
+ sqlite3_free(z);
+ if( zErr ){
+ output_formatted(p, "/****** %s ******/\n", zErr);
+ sqlite3_free(zErr);
+ p->nErr++;
+ zErr = 0;
+ }
+}
+
+/*
+** Convert an SQLite database into SQL statements that will recreate that
+** database.
+*/
+int sqlite3_db_dump(
+ sqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which schema to dump. Usually "main". */
+ const char *zTable, /* Which table to dump. NULL means everything. */
+ int (*xCallback)(const char*,void*), /* Output sent to this callback */
+ void *pArg /* Second argument of the callback */
+){
+ DState x;
+ memset(&x, 0, sizeof(x));
+ x.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0);
+ if( x.rc ) return x.rc;
+ x.db = db;
+ x.xCallback = xCallback;
+ x.pArg = pArg;
+ xCallback("PRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\n", pArg);
+ if( zTable==0 ){
+ run_schema_dump_query(&x,
+ "SELECT name, type, sql FROM \"%w\".sqlite_schema "
+ "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'",
+ zSchema
+ );
+ run_schema_dump_query(&x,
+ "SELECT name, type, sql FROM \"%w\".sqlite_schema "
+ "WHERE name=='sqlite_sequence'", zSchema
+ );
+ output_sql_from_query(&x,
+ "SELECT sql FROM sqlite_schema "
+ "WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0
+ );
+ }else{
+ run_schema_dump_query(&x,
+ "SELECT name, type, sql FROM \"%w\".sqlite_schema "
+ "WHERE tbl_name=%Q COLLATE nocase AND type=='table'"
+ " AND sql NOT NULL",
+ zSchema, zTable
+ );
+ output_sql_from_query(&x,
+ "SELECT sql FROM \"%w\".sqlite_schema "
+ "WHERE sql NOT NULL"
+ " AND type IN ('index','trigger','view')"
+ " AND tbl_name=%Q COLLATE nocase",
+ zSchema, zTable
+ );
+ }
+ if( x.writableSchema ){
+ xCallback("PRAGMA writable_schema=OFF;\n", pArg);
+ }
+ xCallback(x.nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n", pArg);
+ sqlite3_exec(db, "COMMIT", 0, 0, 0);
+ return x.rc;
+}
+
+
+
+/* The generic subroutine is above. The code the follows implements
+** the command-line interface.
+*/
+#ifdef DBDUMP_STANDALONE
+#include <stdio.h>
+
+/*
+** Command-line interface
+*/
+int main(int argc, char **argv){
+ sqlite3 *db;
+ const char *zDb;
+ const char *zSchema;
+ const char *zTable = 0;
+ int rc;
+
+ if( argc<2 || argc>4 ){
+ fprintf(stderr, "Usage: %s DATABASE ?SCHEMA? ?TABLE?\n", argv[0]);
+ return 1;
+ }
+ zDb = argv[1];
+ zSchema = argc>=3 ? argv[2] : "main";
+ zTable = argc==4 ? argv[3] : 0;
+
+ rc = sqlite3_open(zDb, &db);
+ if( rc ){
+ fprintf(stderr, "Cannot open \"%s\": %s\n", zDb, sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return 1;
+ }
+ rc = sqlite3_db_dump(db, zSchema, zTable,
+ (int(*)(const char*,void*))fputs, (void*)stdout);
+ if( rc ){
+ fprintf(stderr, "Error: sqlite3_db_dump() returns %d\n", rc);
+ }
+ sqlite3_close(db);
+ return rc!=SQLITE_OK;
+}
+#endif /* DBDUMP_STANDALONE */
diff --git a/src/third-party/sqlite/ext/series.c b/src/third-party/sqlite/ext/series.c
new file mode 100644
index 0000000..4b34f84
--- /dev/null
+++ b/src/third-party/sqlite/ext/series.c
@@ -0,0 +1,448 @@
+/*
+** 2015-08-18
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file demonstrates how to create a table-valued-function using
+** a virtual table. This demo implements the generate_series() function
+** which gives similar results to the eponymous function in PostgreSQL.
+** Examples:
+**
+** SELECT * FROM generate_series(0,100,5);
+**
+** The query above returns integers from 0 through 100 counting by steps
+** of 5.
+**
+** SELECT * FROM generate_series(0,100);
+**
+** Integers from 0 through 100 with a step size of 1.
+**
+** SELECT * FROM generate_series(20) LIMIT 10;
+**
+** Integers 20 through 29.
+**
+** HOW IT WORKS
+**
+** The generate_series "function" is really a virtual table with the
+** following schema:
+**
+** CREATE TABLE generate_series(
+** value,
+** start HIDDEN,
+** stop HIDDEN,
+** step HIDDEN
+** );
+**
+** Function arguments in queries against this virtual table are translated
+** into equality constraints against successive hidden columns. In other
+** words, the following pairs of queries are equivalent to each other:
+**
+** SELECT * FROM generate_series(0,100,5);
+** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5;
+**
+** SELECT * FROM generate_series(0,100);
+** SELECT * FROM generate_series WHERE start=0 AND stop=100;
+**
+** SELECT * FROM generate_series(20) LIMIT 10;
+** SELECT * FROM generate_series WHERE start=20 LIMIT 10;
+**
+** The generate_series virtual table implementation leaves the xCreate method
+** set to NULL. This means that it is not possible to do a CREATE VIRTUAL
+** TABLE command with "generate_series" as the USING argument. Instead, there
+** is a single generate_series virtual table that is always available without
+** having to be created first.
+**
+** The xBestIndex method looks for equality constraints against the hidden
+** start, stop, and step columns, and if present, it uses those constraints
+** to bound the sequence of generated values. If the equality constraints
+** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step.
+** xBestIndex returns a small cost when both start and stop are available,
+** and a very large cost if either start or stop are unavailable. This
+** encourages the query planner to order joins such that the bounds of the
+** series are well-defined.
+*/
+#include "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+#include <assert.h>
+#include <string.h>
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+
+/* series_cursor is a subclass of sqlite3_vtab_cursor which will
+** serve as the underlying representation of a cursor that scans
+** over rows of the result
+*/
+typedef struct series_cursor series_cursor;
+struct series_cursor {
+ sqlite3_vtab_cursor base; /* Base class - must be first */
+ int isDesc; /* True to count down rather than up */
+ sqlite3_int64 iRowid; /* The rowid */
+ sqlite3_int64 iValue; /* Current value ("value") */
+ sqlite3_int64 mnValue; /* Mimimum value ("start") */
+ sqlite3_int64 mxValue; /* Maximum value ("stop") */
+ sqlite3_int64 iStep; /* Increment ("step") */
+};
+
+/*
+** The seriesConnect() method is invoked to create a new
+** series_vtab that describes the generate_series virtual table.
+**
+** Think of this routine as the constructor for series_vtab objects.
+**
+** All this routine needs to do is:
+**
+** (1) Allocate the series_vtab object and initialize all fields.
+**
+** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
+** result set of queries against generate_series will look like.
+*/
+static int seriesConnect(
+ sqlite3 *db,
+ void *pUnused,
+ int argcUnused, const char *const*argvUnused,
+ sqlite3_vtab **ppVtab,
+ char **pzErrUnused
+){
+ sqlite3_vtab *pNew;
+ int rc;
+
+/* Column numbers */
+#define SERIES_COLUMN_VALUE 0
+#define SERIES_COLUMN_START 1
+#define SERIES_COLUMN_STOP 2
+#define SERIES_COLUMN_STEP 3
+
+ (void)pUnused;
+ (void)argcUnused;
+ (void)argvUnused;
+ (void)pzErrUnused;
+ rc = sqlite3_declare_vtab(db,
+ "CREATE TABLE x(value,start hidden,stop hidden,step hidden)");
+ if( rc==SQLITE_OK ){
+ pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, sizeof(*pNew));
+#ifdef SQLITE_VTAB_INNOCUOUS
+ sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+#endif
+ }
+ return rc;
+}
+
+/*
+** This method is the destructor for series_cursor objects.
+*/
+static int seriesDisconnect(sqlite3_vtab *pVtab){
+ sqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new series_cursor object.
+*/
+static int seriesOpen(sqlite3_vtab *pUnused, sqlite3_vtab_cursor **ppCursor){
+ series_cursor *pCur;
+ (void)pUnused;
+ pCur = sqlite3_malloc( sizeof(*pCur) );
+ if( pCur==0 ) return SQLITE_NOMEM;
+ memset(pCur, 0, sizeof(*pCur));
+ *ppCursor = &pCur->base;
+ return SQLITE_OK;
+}
+
+/*
+** Destructor for a series_cursor.
+*/
+static int seriesClose(sqlite3_vtab_cursor *cur){
+ sqlite3_free(cur);
+ return SQLITE_OK;
+}
+
+
+/*
+** Advance a series_cursor to its next row of output.
+*/
+static int seriesNext(sqlite3_vtab_cursor *cur){
+ series_cursor *pCur = (series_cursor*)cur;
+ if( pCur->isDesc ){
+ pCur->iValue -= pCur->iStep;
+ }else{
+ pCur->iValue += pCur->iStep;
+ }
+ pCur->iRowid++;
+ return SQLITE_OK;
+}
+
+/*
+** Return values of columns for the row at which the series_cursor
+** is currently pointing.
+*/
+static int seriesColumn(
+ sqlite3_vtab_cursor *cur, /* The cursor */
+ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
+ int i /* Which column to return */
+){
+ series_cursor *pCur = (series_cursor*)cur;
+ sqlite3_int64 x = 0;
+ switch( i ){
+ case SERIES_COLUMN_START: x = pCur->mnValue; break;
+ case SERIES_COLUMN_STOP: x = pCur->mxValue; break;
+ case SERIES_COLUMN_STEP: x = pCur->iStep; break;
+ default: x = pCur->iValue; break;
+ }
+ sqlite3_result_int64(ctx, x);
+ return SQLITE_OK;
+}
+
+/*
+** Return the rowid for the current row. In this implementation, the
+** first row returned is assigned rowid value 1, and each subsequent
+** row a value 1 more than that of the previous.
+*/
+static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ series_cursor *pCur = (series_cursor*)cur;
+ *pRowid = pCur->iRowid;
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int seriesEof(sqlite3_vtab_cursor *cur){
+ series_cursor *pCur = (series_cursor*)cur;
+ if( pCur->isDesc ){
+ return pCur->iValue < pCur->mnValue;
+ }else{
+ return pCur->iValue > pCur->mxValue;
+ }
+}
+
+/* True to cause run-time checking of the start=, stop=, and/or step=
+** parameters. The only reason to do this is for testing the
+** constraint checking logic for virtual tables in the SQLite core.
+*/
+#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY
+# define SQLITE_SERIES_CONSTRAINT_VERIFY 0
+#endif
+
+/*
+** This method is called to "rewind" the series_cursor object back
+** to the first row of output. This method is always called at least
+** once prior to any call to seriesColumn() or seriesRowid() or
+** seriesEof().
+**
+** The query plan selected by seriesBestIndex is passed in the idxNum
+** parameter. (idxStr is not used in this implementation.) idxNum
+** is a bitmask showing which constraints are available:
+**
+** 1: start=VALUE
+** 2: stop=VALUE
+** 4: step=VALUE
+**
+** Also, if bit 8 is set, that means that the series should be output
+** in descending order rather than in ascending order. If bit 16 is
+** set, then output must appear in ascending order.
+**
+** This routine should initialize the cursor and position it so that it
+** is pointing at the first row, or pointing off the end of the table
+** (so that seriesEof() will return true) if the table is empty.
+*/
+static int seriesFilter(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStrUnused,
+ int argc, sqlite3_value **argv
+){
+ series_cursor *pCur = (series_cursor *)pVtabCursor;
+ int i = 0;
+ (void)idxStrUnused;
+ if( idxNum & 1 ){
+ pCur->mnValue = sqlite3_value_int64(argv[i++]);
+ }else{
+ pCur->mnValue = 0;
+ }
+ if( idxNum & 2 ){
+ pCur->mxValue = sqlite3_value_int64(argv[i++]);
+ }else{
+ pCur->mxValue = 0xffffffff;
+ }
+ if( idxNum & 4 ){
+ pCur->iStep = sqlite3_value_int64(argv[i++]);
+ if( pCur->iStep==0 ){
+ pCur->iStep = 1;
+ }else if( pCur->iStep<0 ){
+ pCur->iStep = -pCur->iStep;
+ if( (idxNum & 16)==0 ) idxNum |= 8;
+ }
+ }else{
+ pCur->iStep = 1;
+ }
+ for(i=0; i<argc; i++){
+ if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
+ /* If any of the constraints have a NULL value, then return no rows.
+ ** See ticket https://www.sqlite.org/src/info/fac496b61722daf2 */
+ pCur->mnValue = 1;
+ pCur->mxValue = 0;
+ break;
+ }
+ }
+ if( idxNum & 8 ){
+ pCur->isDesc = 1;
+ pCur->iValue = pCur->mxValue;
+ if( pCur->iStep>0 ){
+ pCur->iValue -= (pCur->mxValue - pCur->mnValue)%pCur->iStep;
+ }
+ }else{
+ pCur->isDesc = 0;
+ pCur->iValue = pCur->mnValue;
+ }
+ pCur->iRowid = 1;
+ return SQLITE_OK;
+}
+
+/*
+** SQLite will invoke this method one or more times while planning a query
+** that uses the generate_series virtual table. This routine needs to create
+** a query plan for each invocation and compute an estimated cost for that
+** plan.
+**
+** In this implementation idxNum is used to represent the
+** query plan. idxStr is unused.
+**
+** The query plan is represented by bits in idxNum:
+**
+** (1) start = $value -- constraint exists
+** (2) stop = $value -- constraint exists
+** (4) step = $value -- constraint exists
+** (8) output in descending order
+*/
+static int seriesBestIndex(
+ sqlite3_vtab *vtab,
+ sqlite3_index_info *pIdxInfo
+){
+ int i, j; /* Loop over constraints */
+ int idxNum = 0; /* The query plan bitmask */
+ int unusableMask = 0; /* Mask of unusable constraints */
+ int nArg = 0; /* Number of arguments that seriesFilter() expects */
+ int aIdx[3]; /* Constraints on start, stop, and step */
+ const struct sqlite3_index_constraint *pConstraint;
+
+ /* This implementation assumes that the start, stop, and step columns
+ ** are the last three columns in the virtual table. */
+ assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 );
+ assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 );
+ aIdx[0] = aIdx[1] = aIdx[2] = -1;
+ pConstraint = pIdxInfo->aConstraint;
+ for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
+ int iCol; /* 0 for start, 1 for stop, 2 for step */
+ int iMask; /* bitmask for those column */
+ if( pConstraint->iColumn<SERIES_COLUMN_START ) continue;
+ iCol = pConstraint->iColumn - SERIES_COLUMN_START;
+ assert( iCol>=0 && iCol<=2 );
+ iMask = 1 << iCol;
+ if( pConstraint->usable==0 ){
+ unusableMask |= iMask;
+ continue;
+ }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ idxNum |= iMask;
+ aIdx[iCol] = i;
+ }
+ }
+ for(i=0; i<3; i++){
+ if( (j = aIdx[i])>=0 ){
+ pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg;
+ pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY;
+ }
+ }
+ if( (unusableMask & ~idxNum)!=0 ){
+ /* The start, stop, and step columns are inputs. Therefore if there
+ ** are unusable constraints on any of start, stop, or step then
+ ** this plan is unusable */
+ return SQLITE_CONSTRAINT;
+ }
+ if( (idxNum & 3)==3 ){
+ /* Both start= and stop= boundaries are available. This is the
+ ** the preferred case */
+ pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0));
+ pIdxInfo->estimatedRows = 1000;
+ if( pIdxInfo->nOrderBy==1 ){
+ if( pIdxInfo->aOrderBy[0].desc ){
+ idxNum |= 8;
+ }else{
+ idxNum |= 16;
+ }
+ pIdxInfo->orderByConsumed = 1;
+ }
+ }else{
+ if (!(idxNum & 1)) {
+ vtab->zErrMsg = sqlite3_mprintf("the start parameter is required");
+ } else {
+ vtab->zErrMsg = sqlite3_mprintf("the stop parameter is required");
+ }
+ return SQLITE_ERROR;
+ }
+ pIdxInfo->idxNum = idxNum;
+ return SQLITE_OK;
+}
+
+/*
+** This following structure defines all the methods for the
+** generate_series virtual table.
+*/
+static sqlite3_module seriesModule = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ seriesConnect, /* xConnect */
+ seriesBestIndex, /* xBestIndex */
+ seriesDisconnect, /* xDisconnect */
+ 0, /* xDestroy */
+ seriesOpen, /* xOpen - open a cursor */
+ seriesClose, /* xClose - close a cursor */
+ seriesFilter, /* xFilter - configure scan constraints */
+ seriesNext, /* xNext - advance a cursor */
+ seriesEof, /* xEof - check for end of scan */
+ seriesColumn, /* xColumn - read data */
+ seriesRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
+};
+
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_series_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ int rc = SQLITE_OK;
+ SQLITE_EXTENSION_INIT2(pApi);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( sqlite3_libversion_number()<3008012 ){
+ *pzErrMsg = sqlite3_mprintf(
+ "generate_series() requires SQLite 3.8.12 or later");
+ return SQLITE_ERROR;
+ }
+ rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0);
+#endif
+ return rc;
+}
diff --git a/src/third-party/xxHash/xxh_x86dispatch.c b/src/third-party/xxHash/xxh_x86dispatch.c
new file mode 100644
index 0000000..bec93bf
--- /dev/null
+++ b/src/third-party/xxHash/xxh_x86dispatch.c
@@ -0,0 +1,770 @@
+/*
+ * xxHash - Extremely Fast Hash algorithm
+ * Copyright (C) 2020-2021 Yann Collet
+ *
+ * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You can contact the author at:
+ * - xxHash homepage: https://www.xxhash.com
+ * - xxHash source repository: https://github.com/Cyan4973/xxHash
+ */
+
+
+/*!
+ * @file xxh_x86dispatch.c
+ *
+ * Automatic dispatcher code for the @ref XXH3_family on x86-based targets.
+ *
+ * Optional add-on.
+ *
+ * **Compile this file with the default flags for your target.** Do not compile
+ * with flags like `-mavx*`, `-march=native`, or `/arch:AVX*`, there will be
+ * an error. See @ref XXH_X86DISPATCH_ALLOW_AVX for details.
+ *
+ * @defgroup dispatch x86 Dispatcher
+ * @{
+ */
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+#if !(defined(__x86_64__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64))
+# error "Dispatching is currently only supported on x86 and x86_64."
+#endif
+
+/*!
+ * @def XXH_X86DISPATCH_ALLOW_AVX
+ * @brief Disables the AVX sanity check.
+ *
+ * Don't compile xxh_x86dispatch.c with options like `-mavx*`, `-march=native`,
+ * or `/arch:AVX*`. It is intended to be compiled for the minimum target, and
+ * it selectively enables SSE2, AVX2, and AVX512 when it is needed.
+ *
+ * Using this option _globally_ allows this feature, and therefore makes it
+ * undefined behavior to execute on any CPU without said feature.
+ *
+ * Even if the source code isn't directly using AVX intrinsics in a function,
+ * the compiler can still generate AVX code from autovectorization and by
+ * "upgrading" SSE2 intrinsics to use the VEX prefixes (a.k.a. AVX128).
+ *
+ * Use the same flags that you use to compile the rest of the program; this
+ * file will safely generate SSE2, AVX2, and AVX512 without these flags.
+ *
+ * Define XXH_X86DISPATCH_ALLOW_AVX to ignore this check, and feel free to open
+ * an issue if there is a target in the future where AVX is a default feature.
+ */
+#ifdef XXH_DOXYGEN
+# define XXH_X86DISPATCH_ALLOW_AVX
+#endif
+
+#if defined(__AVX__) && !defined(XXH_X86DISPATCH_ALLOW_AVX)
+# error "Do not compile xxh_x86dispatch.c with AVX enabled! See the comment above."
+#endif
+
+#ifdef __has_include
+# define XXH_HAS_INCLUDE(header) __has_include(header)
+#else
+# define XXH_HAS_INCLUDE(header) 0
+#endif
+
+/*!
+ * @def XXH_DISPATCH_SCALAR
+ * @brief Enables/dispatching the scalar code path.
+ *
+ * If this is defined to 0, SSE2 support is assumed. This reduces code size
+ * when the scalar path is not needed.
+ *
+ * This is automatically defined to 0 when...
+ * - SSE2 support is enabled in the compiler
+ * - Targeting x86_64
+ * - Targeting Android x86
+ * - Targeting macOS
+ */
+#ifndef XXH_DISPATCH_SCALAR
+# if defined(__SSE2__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) /* SSE2 on by default */ \
+ || defined(__x86_64__) || defined(_M_X64) /* x86_64 */ \
+ || defined(__ANDROID__) || defined(__APPLEv__) /* Android or macOS */
+# define XXH_DISPATCH_SCALAR 0 /* disable */
+# else
+# define XXH_DISPATCH_SCALAR 1
+# endif
+#endif
+/*!
+ * @def XXH_DISPATCH_AVX2
+ * @brief Enables/disables dispatching for AVX2.
+ *
+ * This is automatically detected if it is not defined.
+ * - GCC 4.7 and later are known to support AVX2, but >4.9 is required for
+ * to get the AVX2 intrinsics and typedefs without -mavx -mavx2.
+ * - Visual Studio 2013 Update 2 and later are known to support AVX2.
+ * - The GCC/Clang internal header `<avx2intrin.h>` is detected. While this is
+ * not allowed to be included directly, it still appears in the builtin
+ * include path and is detectable with `__has_include`.
+ *
+ * @see XXH_AVX2
+ */
+#ifndef XXH_DISPATCH_AVX2
+# if (defined(__GNUC__) && (__GNUC__ > 4)) /* GCC 5.0+ */ \
+ || (defined(_MSC_VER) && _MSC_VER >= 1900) /* VS 2015+ */ \
+ || (defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 180030501) /* VS 2013 Update 2 */ \
+ || XXH_HAS_INCLUDE(<avx2intrin.h>) /* GCC/Clang internal header */
+# define XXH_DISPATCH_AVX2 1 /* enable dispatch towards AVX2 */
+# else
+# define XXH_DISPATCH_AVX2 0
+# endif
+#endif /* XXH_DISPATCH_AVX2 */
+
+/*!
+ * @def XXH_DISPATCH_AVX512
+ * @brief Enables/disables dispatching for AVX512.
+ *
+ * Automatically detected if one of the following conditions is met:
+ * - GCC 4.9 and later are known to support AVX512.
+ * - Visual Studio 2017 and later are known to support AVX2.
+ * - The GCC/Clang internal header `<avx512fintrin.h>` is detected. While this
+ * is not allowed to be included directly, it still appears in the builtin
+ * include path and is detectable with `__has_include`.
+ *
+ * @see XXH_AVX512
+ */
+#ifndef XXH_DISPATCH_AVX512
+# if (defined(__GNUC__) \
+ && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))) /* GCC 4.9+ */ \
+ || (defined(_MSC_VER) && _MSC_VER >= 1910) /* VS 2017+ */ \
+ || XXH_HAS_INCLUDE(<avx512fintrin.h>) /* GCC/Clang internal header */
+# define XXH_DISPATCH_AVX512 1 /* enable dispatch towards AVX512 */
+# else
+# define XXH_DISPATCH_AVX512 0
+# endif
+#endif /* XXH_DISPATCH_AVX512 */
+
+/*!
+ * @def XXH_TARGET_SSE2
+ * @brief Allows a function to be compiled with SSE2 intrinsics.
+ *
+ * Uses `__attribute__((__target__("sse2")))` on GCC to allow SSE2 to be used
+ * even with `-mno-sse2`.
+ *
+ * @def XXH_TARGET_AVX2
+ * @brief Like @ref XXH_TARGET_SSE2, but for AVX2.
+ *
+ * @def XXH_TARGET_AVX512
+ * @brief Like @ref XXH_TARGET_SSE2, but for AVX512.
+ */
+#if defined(__GNUC__)
+# include <emmintrin.h> /* SSE2 */
+# if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
+# include <immintrin.h> /* AVX2, AVX512F */
+# endif
+# define XXH_TARGET_SSE2 __attribute__((__target__("sse2")))
+# define XXH_TARGET_AVX2 __attribute__((__target__("avx2")))
+# define XXH_TARGET_AVX512 __attribute__((__target__("avx512f")))
+#elif defined(_MSC_VER)
+# include <intrin.h>
+# define XXH_TARGET_SSE2
+# define XXH_TARGET_AVX2
+# define XXH_TARGET_AVX512
+#else
+# error "Dispatching is currently not supported for your compiler."
+#endif
+
+#ifdef XXH_DISPATCH_DEBUG
+/* debug logging */
+# include <stdio.h>
+# define XXH_debugPrint(str) { fprintf(stderr, "DEBUG: xxHash dispatch: %s \n", str); fflush(NULL); }
+#else
+# define XXH_debugPrint(str) ((void)0)
+# undef NDEBUG /* avoid redefinition */
+# define NDEBUG
+#endif
+#include <assert.h>
+
+#define XXH_INLINE_ALL
+#define XXH_X86DISPATCH
+#include "xxhash.h"
+
+/*
+ * Support both AT&T and Intel dialects
+ *
+ * GCC doesn't convert AT&T syntax to Intel syntax, and will error out if
+ * compiled with -masm=intel. Instead, it supports dialect switching with
+ * curly braces: { AT&T syntax | Intel syntax }
+ *
+ * Clang's integrated assembler automatically converts AT&T syntax to Intel if
+ * needed, making the dialect switching useless (it isn't even supported).
+ *
+ * Note: Comments are written in the inline assembly itself.
+ */
+#ifdef __clang__
+# define XXH_I_ATT(intel, att) att "\n\t"
+#else
+# define XXH_I_ATT(intel, att) "{" att "|" intel "}\n\t"
+#endif
+
+/*!
+ * @internal
+ * @brief Runs CPUID.
+ *
+ * @param eax , ecx The parameters to pass to CPUID, %eax and %ecx respectively.
+ * @param abcd The array to store the result in, `{ eax, ebx, ecx, edx }`
+ */
+static void XXH_cpuid(xxh_u32 eax, xxh_u32 ecx, xxh_u32* abcd)
+{
+#if defined(_MSC_VER)
+ __cpuidex(abcd, eax, ecx);
+#else
+ xxh_u32 ebx, edx;
+# if defined(__i386__) && defined(__PIC__)
+ __asm__(
+ "# Call CPUID\n\t"
+ "#\n\t"
+ "# On 32-bit x86 with PIC enabled, we are not allowed to overwrite\n\t"
+ "# EBX, so we use EDI instead.\n\t"
+ XXH_I_ATT("mov edi, ebx", "movl %%ebx, %%edi")
+ XXH_I_ATT("cpuid", "cpuid" )
+ XXH_I_ATT("xchg edi, ebx", "xchgl %%ebx, %%edi")
+ : "=D" (ebx),
+# else
+ __asm__(
+ "# Call CPUID\n\t"
+ XXH_I_ATT("cpuid", "cpuid")
+ : "=b" (ebx),
+# endif
+ "+a" (eax), "+c" (ecx), "=d" (edx));
+ abcd[0] = eax;
+ abcd[1] = ebx;
+ abcd[2] = ecx;
+ abcd[3] = edx;
+#endif
+}
+
+/*
+ * Modified version of Intel's guide
+ * https://software.intel.com/en-us/articles/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family
+ */
+
+#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
+/*!
+ * @internal
+ * @brief Runs `XGETBV`.
+ *
+ * While the CPU may support AVX2, the operating system might not properly save
+ * the full YMM/ZMM registers.
+ *
+ * xgetbv is used for detecting this: Any compliant operating system will define
+ * a set of flags in the xcr0 register indicating how it saves the AVX registers.
+ *
+ * You can manually disable this flag on Windows by running, as admin:
+ *
+ * bcdedit.exe /set xsavedisable 1
+ *
+ * and rebooting. Run the same command with 0 to re-enable it.
+ */
+static xxh_u64 XXH_xgetbv(void)
+{
+#if defined(_MSC_VER)
+ return _xgetbv(0); /* min VS2010 SP1 compiler is required */
+#else
+ xxh_u32 xcr0_lo, xcr0_hi;
+ __asm__(
+ "# Call XGETBV\n\t"
+ "#\n\t"
+ "# Older assemblers (e.g. macOS's ancient GAS version) don't support\n\t"
+ "# the XGETBV opcode, so we encode it by hand instead.\n\t"
+ "# See <https://github.com/asmjit/asmjit/issues/78> for details.\n\t"
+ ".byte 0x0f, 0x01, 0xd0\n\t"
+ : "=a" (xcr0_lo), "=d" (xcr0_hi) : "c" (0));
+ return xcr0_lo | ((xxh_u64)xcr0_hi << 32);
+#endif
+}
+#endif
+
+#define XXH_SSE2_CPUID_MASK (1 << 26)
+#define XXH_OSXSAVE_CPUID_MASK ((1 << 26) | (1 << 27))
+#define XXH_AVX2_CPUID_MASK (1 << 5)
+#define XXH_AVX2_XGETBV_MASK ((1 << 2) | (1 << 1))
+#define XXH_AVX512F_CPUID_MASK (1 << 16)
+#define XXH_AVX512F_XGETBV_MASK ((7 << 5) | (1 << 2) | (1 << 1))
+
+/*!
+ * @internal
+ * @brief Returns the best XXH3 implementation.
+ *
+ * Runs various CPUID/XGETBV tests to try and determine the best implementation.
+ *
+ * @return The best @ref XXH_VECTOR implementation.
+ * @see XXH_VECTOR_TYPES
+ */
+static int XXH_featureTest(void)
+{
+ xxh_u32 abcd[4];
+ xxh_u32 max_leaves;
+ int best = XXH_SCALAR;
+#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
+ xxh_u64 xgetbv_val;
+#endif
+#if defined(__GNUC__) && defined(__i386__)
+ xxh_u32 cpuid_supported;
+ __asm__(
+ "# For the sake of ruthless backwards compatibility, check if CPUID\n\t"
+ "# is supported in the EFLAGS on i386.\n\t"
+ "# This is not necessary on x86_64 - CPUID is mandatory.\n\t"
+ "# The ID flag (bit 21) in the EFLAGS register indicates support\n\t"
+ "# for the CPUID instruction. If a software procedure can set and\n\t"
+ "# clear this flag, the processor executing the procedure supports\n\t"
+ "# the CPUID instruction.\n\t"
+ "# <https://c9x.me/x86/html/file_module_x86_id_45.html>\n\t"
+ "#\n\t"
+ "# Routine is from <https://wiki.osdev.org/CPUID>.\n\t"
+
+ "# Save EFLAGS\n\t"
+ XXH_I_ATT("pushfd", "pushfl" )
+ "# Store EFLAGS\n\t"
+ XXH_I_ATT("pushfd", "pushfl" )
+ "# Invert the ID bit in stored EFLAGS\n\t"
+ XXH_I_ATT("xor dword ptr[esp], 0x200000", "xorl $0x200000, (%%esp)")
+ "# Load stored EFLAGS (with ID bit inverted)\n\t"
+ XXH_I_ATT("popfd", "popfl" )
+ "# Store EFLAGS again (ID bit may or not be inverted)\n\t"
+ XXH_I_ATT("pushfd", "pushfl" )
+ "# eax = modified EFLAGS (ID bit may or may not be inverted)\n\t"
+ XXH_I_ATT("pop eax", "popl %%eax" )
+ "# eax = whichever bits were changed\n\t"
+ XXH_I_ATT("xor eax, dword ptr[esp]", "xorl (%%esp), %%eax" )
+ "# Restore original EFLAGS\n\t"
+ XXH_I_ATT("popfd", "popfl" )
+ "# eax = zero if ID bit can't be changed, else non-zero\n\t"
+ XXH_I_ATT("and eax, 0x200000", "andl $0x200000, %%eax" )
+ : "=a" (cpuid_supported) :: "cc");
+
+ if (XXH_unlikely(!cpuid_supported)) {
+ XXH_debugPrint("CPUID support is not detected!");
+ return best;
+ }
+
+#endif
+ /* Check how many CPUID pages we have */
+ XXH_cpuid(0, 0, abcd);
+ max_leaves = abcd[0];
+
+ /* Shouldn't happen on hardware, but happens on some QEMU configs. */
+ if (XXH_unlikely(max_leaves == 0)) {
+ XXH_debugPrint("Max CPUID leaves == 0!");
+ return best;
+ }
+
+ /* Check for SSE2, OSXSAVE and xgetbv */
+ XXH_cpuid(1, 0, abcd);
+
+ /*
+ * Test for SSE2. The check is redundant on x86_64, but it doesn't hurt.
+ */
+ if (XXH_unlikely((abcd[3] & XXH_SSE2_CPUID_MASK) != XXH_SSE2_CPUID_MASK))
+ return best;
+
+ XXH_debugPrint("SSE2 support detected.");
+
+ best = XXH_SSE2;
+#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
+ /* Make sure we have enough leaves */
+ if (XXH_unlikely(max_leaves < 7))
+ return best;
+
+ /* Test for OSXSAVE and XGETBV */
+ if ((abcd[2] & XXH_OSXSAVE_CPUID_MASK) != XXH_OSXSAVE_CPUID_MASK)
+ return best;
+
+ /* CPUID check for AVX features */
+ XXH_cpuid(7, 0, abcd);
+
+ xgetbv_val = XXH_xgetbv();
+#if XXH_DISPATCH_AVX2
+ /* Validate that AVX2 is supported by the CPU */
+ if ((abcd[1] & XXH_AVX2_CPUID_MASK) != XXH_AVX2_CPUID_MASK)
+ return best;
+
+ /* Validate that the OS supports YMM registers */
+ if ((xgetbv_val & XXH_AVX2_XGETBV_MASK) != XXH_AVX2_XGETBV_MASK) {
+ XXH_debugPrint("AVX2 supported by the CPU, but not the OS.");
+ return best;
+ }
+
+ /* AVX2 supported */
+ XXH_debugPrint("AVX2 support detected.");
+ best = XXH_AVX2;
+#endif
+#if XXH_DISPATCH_AVX512
+ /* Check if AVX512F is supported by the CPU */
+ if ((abcd[1] & XXH_AVX512F_CPUID_MASK) != XXH_AVX512F_CPUID_MASK) {
+ XXH_debugPrint("AVX512F not supported by CPU");
+ return best;
+ }
+
+ /* Validate that the OS supports ZMM registers */
+ if ((xgetbv_val & XXH_AVX512F_XGETBV_MASK) != XXH_AVX512F_XGETBV_MASK) {
+ XXH_debugPrint("AVX512F supported by the CPU, but not the OS.");
+ return best;
+ }
+
+ /* AVX512F supported */
+ XXH_debugPrint("AVX512F support detected.");
+ best = XXH_AVX512;
+#endif
+#endif
+ return best;
+}
+
+
+/* === Vector implementations === */
+
+/*!
+ * @internal
+ * @brief Defines the various dispatch functions.
+ *
+ * TODO: Consolidate?
+ *
+ * @param suffix The suffix for the functions, e.g. sse2 or scalar
+ * @param target XXH_TARGET_* or empty.
+ */
+#define XXH_DEFINE_DISPATCH_FUNCS(suffix, target) \
+ \
+/* === XXH3, default variants === */ \
+ \
+XXH_NO_INLINE target XXH64_hash_t \
+XXHL64_default_##suffix(const void* XXH_RESTRICT input, size_t len) \
+{ \
+ return XXH3_hashLong_64b_internal( \
+ input, len, XXH3_kSecret, sizeof(XXH3_kSecret), \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix \
+ ); \
+} \
+ \
+/* === XXH3, Seeded variants === */ \
+ \
+XXH_NO_INLINE target XXH64_hash_t \
+XXHL64_seed_##suffix(const void* XXH_RESTRICT input, size_t len, \
+ XXH64_hash_t seed) \
+{ \
+ return XXH3_hashLong_64b_withSeed_internal( \
+ input, len, seed, XXH3_accumulate_512_##suffix, \
+ XXH3_scrambleAcc_##suffix, XXH3_initCustomSecret_##suffix \
+ ); \
+} \
+ \
+/* === XXH3, Secret variants === */ \
+ \
+XXH_NO_INLINE target XXH64_hash_t \
+XXHL64_secret_##suffix(const void* XXH_RESTRICT input, size_t len, \
+ const void* secret, size_t secretLen) \
+{ \
+ return XXH3_hashLong_64b_internal( \
+ input, len, secret, secretLen, \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix \
+ ); \
+} \
+ \
+/* === XXH3 update variants === */ \
+ \
+XXH_NO_INLINE target XXH_errorcode \
+XXH3_update_##suffix(XXH3_state_t* state, const void* input, size_t len) \
+{ \
+ return XXH3_update(state, (const xxh_u8*)input, len, \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix); \
+} \
+ \
+/* === XXH128 default variants === */ \
+ \
+XXH_NO_INLINE target XXH128_hash_t \
+XXHL128_default_##suffix(const void* XXH_RESTRICT input, size_t len) \
+{ \
+ return XXH3_hashLong_128b_internal( \
+ input, len, XXH3_kSecret, sizeof(XXH3_kSecret), \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix \
+ ); \
+} \
+ \
+/* === XXH128 Secret variants === */ \
+ \
+XXH_NO_INLINE target XXH128_hash_t \
+XXHL128_secret_##suffix(const void* XXH_RESTRICT input, size_t len, \
+ const void* XXH_RESTRICT secret, size_t secretLen) \
+{ \
+ return XXH3_hashLong_128b_internal( \
+ input, len, (const xxh_u8*)secret, secretLen, \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix); \
+} \
+ \
+/* === XXH128 Seeded variants === */ \
+ \
+XXH_NO_INLINE target XXH128_hash_t \
+XXHL128_seed_##suffix(const void* XXH_RESTRICT input, size_t len, \
+ XXH64_hash_t seed) \
+{ \
+ return XXH3_hashLong_128b_withSeed_internal(input, len, seed, \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix, \
+ XXH3_initCustomSecret_##suffix); \
+}
+
+/* End XXH_DEFINE_DISPATCH_FUNCS */
+
+#if XXH_DISPATCH_SCALAR
+XXH_DEFINE_DISPATCH_FUNCS(scalar, /* nothing */)
+#endif
+XXH_DEFINE_DISPATCH_FUNCS(sse2, XXH_TARGET_SSE2)
+#if XXH_DISPATCH_AVX2
+XXH_DEFINE_DISPATCH_FUNCS(avx2, XXH_TARGET_AVX2)
+#endif
+#if XXH_DISPATCH_AVX512
+XXH_DEFINE_DISPATCH_FUNCS(avx512, XXH_TARGET_AVX512)
+#endif
+#undef XXH_DEFINE_DISPATCH_FUNCS
+
+/* ==== Dispatchers ==== */
+
+typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_default)(const void* XXH_RESTRICT, size_t);
+
+typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSeed)(const void* XXH_RESTRICT, size_t, XXH64_hash_t);
+
+typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSecret)(const void* XXH_RESTRICT, size_t, const void* XXH_RESTRICT, size_t);
+
+typedef XXH_errorcode (*XXH3_dispatchx86_update)(XXH3_state_t*, const void*, size_t);
+
+typedef struct {
+ XXH3_dispatchx86_hashLong64_default hashLong64_default;
+ XXH3_dispatchx86_hashLong64_withSeed hashLong64_seed;
+ XXH3_dispatchx86_hashLong64_withSecret hashLong64_secret;
+ XXH3_dispatchx86_update update;
+} XXH_dispatchFunctions_s;
+
+#define XXH_NB_DISPATCHES 4
+
+/*!
+ * @internal
+ * @brief Table of dispatchers for @ref XXH3_64bits().
+ *
+ * @pre The indices must match @ref XXH_VECTOR_TYPE.
+ */
+static const XXH_dispatchFunctions_s XXH_kDispatch[XXH_NB_DISPATCHES] = {
+#if XXH_DISPATCH_SCALAR
+ /* Scalar */ { XXHL64_default_scalar, XXHL64_seed_scalar, XXHL64_secret_scalar, XXH3_update_scalar },
+#else
+ /* Scalar */ { NULL, NULL, NULL, NULL },
+#endif
+ /* SSE2 */ { XXHL64_default_sse2, XXHL64_seed_sse2, XXHL64_secret_sse2, XXH3_update_sse2 },
+#if XXH_DISPATCH_AVX2
+ /* AVX2 */ { XXHL64_default_avx2, XXHL64_seed_avx2, XXHL64_secret_avx2, XXH3_update_avx2 },
+#else
+ /* AVX2 */ { NULL, NULL, NULL, NULL },
+#endif
+#if XXH_DISPATCH_AVX512
+ /* AVX512 */ { XXHL64_default_avx512, XXHL64_seed_avx512, XXHL64_secret_avx512, XXH3_update_avx512 }
+#else
+ /* AVX512 */ { NULL, NULL, NULL, NULL }
+#endif
+};
+/*!
+ * @internal
+ * @brief The selected dispatch table for @ref XXH3_64bits().
+ */
+static XXH_dispatchFunctions_s XXH_g_dispatch = { NULL, NULL, NULL, NULL };
+
+
+typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_default)(const void* XXH_RESTRICT, size_t);
+
+typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSeed)(const void* XXH_RESTRICT, size_t, XXH64_hash_t);
+
+typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSecret)(const void* XXH_RESTRICT, size_t, const void* XXH_RESTRICT, size_t);
+
+typedef struct {
+ XXH3_dispatchx86_hashLong128_default hashLong128_default;
+ XXH3_dispatchx86_hashLong128_withSeed hashLong128_seed;
+ XXH3_dispatchx86_hashLong128_withSecret hashLong128_secret;
+ XXH3_dispatchx86_update update;
+} XXH_dispatch128Functions_s;
+
+
+/*!
+ * @internal
+ * @brief Table of dispatchers for @ref XXH3_128bits().
+ *
+ * @pre The indices must match @ref XXH_VECTOR_TYPE.
+ */
+static const XXH_dispatch128Functions_s XXH_kDispatch128[XXH_NB_DISPATCHES] = {
+#if XXH_DISPATCH_SCALAR
+ /* Scalar */ { XXHL128_default_scalar, XXHL128_seed_scalar, XXHL128_secret_scalar, XXH3_update_scalar },
+#else
+ /* Scalar */ { NULL, NULL, NULL, NULL },
+#endif
+ /* SSE2 */ { XXHL128_default_sse2, XXHL128_seed_sse2, XXHL128_secret_sse2, XXH3_update_sse2 },
+#if XXH_DISPATCH_AVX2
+ /* AVX2 */ { XXHL128_default_avx2, XXHL128_seed_avx2, XXHL128_secret_avx2, XXH3_update_avx2 },
+#else
+ /* AVX2 */ { NULL, NULL, NULL, NULL },
+#endif
+#if XXH_DISPATCH_AVX512
+ /* AVX512 */ { XXHL128_default_avx512, XXHL128_seed_avx512, XXHL128_secret_avx512, XXH3_update_avx512 }
+#else
+ /* AVX512 */ { NULL, NULL, NULL, NULL }
+#endif
+};
+
+/*!
+ * @internal
+ * @brief The selected dispatch table for @ref XXH3_64bits().
+ */
+static XXH_dispatch128Functions_s XXH_g_dispatch128 = { NULL, NULL, NULL, NULL };
+
+/*!
+ * @internal
+ * @brief Runs a CPUID check and sets the correct dispatch tables.
+ */
+static void XXH_setDispatch(void)
+{
+ int vecID = XXH_featureTest();
+ XXH_STATIC_ASSERT(XXH_AVX512 == XXH_NB_DISPATCHES-1);
+ assert(XXH_SCALAR <= vecID && vecID <= XXH_AVX512);
+#if !XXH_DISPATCH_SCALAR
+ assert(vecID != XXH_SCALAR);
+#endif
+#if !XXH_DISPATCH_AVX512
+ assert(vecID != XXH_AVX512);
+#endif
+#if !XXH_DISPATCH_AVX2
+ assert(vecID != XXH_AVX2);
+#endif
+ XXH_g_dispatch = XXH_kDispatch[vecID];
+ XXH_g_dispatch128 = XXH_kDispatch128[vecID];
+}
+
+
+/* ==== XXH3 public functions ==== */
+
+static XXH64_hash_t
+XXH3_hashLong_64b_defaultSecret_selection(const void* input, size_t len,
+ XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
+{
+ (void)seed64; (void)secret; (void)secretLen;
+ if (XXH_g_dispatch.hashLong64_default == NULL) XXH_setDispatch();
+ return XXH_g_dispatch.hashLong64_default(input, len);
+}
+
+XXH64_hash_t XXH3_64bits_dispatch(const void* input, size_t len)
+{
+ return XXH3_64bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_defaultSecret_selection);
+}
+
+static XXH64_hash_t
+XXH3_hashLong_64b_withSeed_selection(const void* input, size_t len,
+ XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
+{
+ (void)secret; (void)secretLen;
+ if (XXH_g_dispatch.hashLong64_seed == NULL) XXH_setDispatch();
+ return XXH_g_dispatch.hashLong64_seed(input, len, seed64);
+}
+
+XXH64_hash_t XXH3_64bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed)
+{
+ return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed_selection);
+}
+
+static XXH64_hash_t
+XXH3_hashLong_64b_withSecret_selection(const void* input, size_t len,
+ XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
+{
+ (void)seed64;
+ if (XXH_g_dispatch.hashLong64_secret == NULL) XXH_setDispatch();
+ return XXH_g_dispatch.hashLong64_secret(input, len, secret, secretLen);
+}
+
+XXH64_hash_t XXH3_64bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen)
+{
+ return XXH3_64bits_internal(input, len, 0, secret, secretLen, XXH3_hashLong_64b_withSecret_selection);
+}
+
+XXH_errorcode
+XXH3_64bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len)
+{
+ if (XXH_g_dispatch.update == NULL) XXH_setDispatch();
+ return XXH_g_dispatch.update(state, (const xxh_u8*)input, len);
+}
+
+
+/* ==== XXH128 public functions ==== */
+
+static XXH128_hash_t
+XXH3_hashLong_128b_defaultSecret_selection(const void* input, size_t len,
+ XXH64_hash_t seed64, const void* secret, size_t secretLen)
+{
+ (void)seed64; (void)secret; (void)secretLen;
+ if (XXH_g_dispatch128.hashLong128_default == NULL) XXH_setDispatch();
+ return XXH_g_dispatch128.hashLong128_default(input, len);
+}
+
+XXH128_hash_t XXH3_128bits_dispatch(const void* input, size_t len)
+{
+ return XXH3_128bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_defaultSecret_selection);
+}
+
+static XXH128_hash_t
+XXH3_hashLong_128b_withSeed_selection(const void* input, size_t len,
+ XXH64_hash_t seed64, const void* secret, size_t secretLen)
+{
+ (void)secret; (void)secretLen;
+ if (XXH_g_dispatch128.hashLong128_seed == NULL) XXH_setDispatch();
+ return XXH_g_dispatch128.hashLong128_seed(input, len, seed64);
+}
+
+XXH128_hash_t XXH3_128bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed)
+{
+ return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_withSeed_selection);
+}
+
+static XXH128_hash_t
+XXH3_hashLong_128b_withSecret_selection(const void* input, size_t len,
+ XXH64_hash_t seed64, const void* secret, size_t secretLen)
+{
+ (void)seed64;
+ if (XXH_g_dispatch128.hashLong128_secret == NULL) XXH_setDispatch();
+ return XXH_g_dispatch128.hashLong128_secret(input, len, secret, secretLen);
+}
+
+XXH128_hash_t XXH3_128bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen)
+{
+ return XXH3_128bits_internal(input, len, 0, secret, secretLen, XXH3_hashLong_128b_withSecret_selection);
+}
+
+XXH_errorcode
+XXH3_128bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len)
+{
+ if (XXH_g_dispatch128.update == NULL) XXH_setDispatch();
+ return XXH_g_dispatch128.update(state, (const xxh_u8*)input, len);
+}
+
+#if defined (__cplusplus)
+}
+#endif
+/*! @} */
diff --git a/src/third-party/xxHash/xxh_x86dispatch.h b/src/third-party/xxHash/xxh_x86dispatch.h
new file mode 100644
index 0000000..417ef08
--- /dev/null
+++ b/src/third-party/xxHash/xxh_x86dispatch.h
@@ -0,0 +1,85 @@
+/*
+ * xxHash - XXH3 Dispatcher for x86-based targets
+ * Copyright (C) 2020-2021 Yann Collet
+ *
+ * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You can contact the author at:
+ * - xxHash homepage: https://www.xxhash.com
+ * - xxHash source repository: https://github.com/Cyan4973/xxHash
+ */
+
+#ifndef XXH_X86DISPATCH_H_13563687684
+#define XXH_X86DISPATCH_H_13563687684
+
+#include "xxhash.h" /* XXH64_hash_t, XXH3_state_t */
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_dispatch(const void* input, size_t len);
+XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen);
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len);
+
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_dispatch(const void* input, size_t len);
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen);
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len);
+
+#if defined (__cplusplus)
+}
+#endif
+
+
+/* automatic replacement of XXH3 functions.
+ * can be disabled by setting XXH_DISPATCH_DISABLE_REPLACE */
+#ifndef XXH_DISPATCH_DISABLE_REPLACE
+
+# undef XXH3_64bits
+# define XXH3_64bits XXH3_64bits_dispatch
+# undef XXH3_64bits_withSeed
+# define XXH3_64bits_withSeed XXH3_64bits_withSeed_dispatch
+# undef XXH3_64bits_withSecret
+# define XXH3_64bits_withSecret XXH3_64bits_withSecret_dispatch
+# undef XXH3_64bits_update
+# define XXH3_64bits_update XXH3_64bits_update_dispatch
+
+# undef XXH128
+# define XXH128 XXH3_128bits_withSeed_dispatch
+# undef XXH3_128bits
+# define XXH3_128bits XXH3_128bits_dispatch
+# undef XXH3_128bits_withSeed
+# define XXH3_128bits_withSeed XXH3_128bits_withSeed_dispatch
+# undef XXH3_128bits_withSecret
+# define XXH3_128bits_withSecret XXH3_128bits_withSecret_dispatch
+# undef XXH3_128bits_update
+# define XXH3_128bits_update XXH3_128bits_update_dispatch
+
+#endif /* XXH_DISPATCH_DISABLE_REPLACE */
+
+#endif /* XXH_X86DISPATCH_H_13563687684 */
diff --git a/src/third-party/xxHash/xxhash.c b/src/third-party/xxHash/xxhash.c
new file mode 100644
index 0000000..083b039
--- /dev/null
+++ b/src/third-party/xxHash/xxhash.c
@@ -0,0 +1,43 @@
+/*
+ * xxHash - Extremely Fast Hash algorithm
+ * Copyright (C) 2012-2021 Yann Collet
+ *
+ * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You can contact the author at:
+ * - xxHash homepage: https://www.xxhash.com
+ * - xxHash source repository: https://github.com/Cyan4973/xxHash
+ */
+
+
+/*
+ * xxhash.c instantiates functions defined in xxhash.h
+ */
+
+#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */
+#define XXH_IMPLEMENTATION /* access definitions */
+
+#include "xxhash.h"
diff --git a/src/third-party/xxHash/xxhash.h b/src/third-party/xxHash/xxhash.h
new file mode 100644
index 0000000..331f0ae
--- /dev/null
+++ b/src/third-party/xxHash/xxhash.h
@@ -0,0 +1,6075 @@
+/*
+ * xxHash - Extremely Fast Hash algorithm
+ * Header File
+ * Copyright (C) 2012-2021 Yann Collet
+ *
+ * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You can contact the author at:
+ * - xxHash homepage: https://www.xxhash.com
+ * - xxHash source repository: https://github.com/Cyan4973/xxHash
+ */
+
+/*!
+ * @mainpage xxHash
+ *
+ * xxHash is an extremely fast non-cryptographic hash algorithm, working at RAM speed
+ * limits.
+ *
+ * It is proposed in four flavors, in three families:
+ * 1. @ref XXH32_family
+ * - Classic 32-bit hash function. Simple, compact, and runs on almost all
+ * 32-bit and 64-bit systems.
+ * 2. @ref XXH64_family
+ * - Classic 64-bit adaptation of XXH32. Just as simple, and runs well on most
+ * 64-bit systems (but _not_ 32-bit systems).
+ * 3. @ref XXH3_family
+ * - Modern 64-bit and 128-bit hash function family which features improved
+ * strength and performance across the board, especially on smaller data.
+ * It benefits greatly from SIMD and 64-bit without requiring it.
+ *
+ * Benchmarks
+ * ---
+ * The reference system uses an Intel i7-9700K CPU, and runs Ubuntu x64 20.04.
+ * The open source benchmark program is compiled with clang v10.0 using -O3 flag.
+ *
+ * | Hash Name | ISA ext | Width | Large Data Speed | Small Data Velocity |
+ * | -------------------- | ------- | ----: | ---------------: | ------------------: |
+ * | XXH3_64bits() | @b AVX2 | 64 | 59.4 GB/s | 133.1 |
+ * | MeowHash | AES-NI | 128 | 58.2 GB/s | 52.5 |
+ * | XXH3_128bits() | @b AVX2 | 128 | 57.9 GB/s | 118.1 |
+ * | CLHash | PCLMUL | 64 | 37.1 GB/s | 58.1 |
+ * | XXH3_64bits() | @b SSE2 | 64 | 31.5 GB/s | 133.1 |
+ * | XXH3_128bits() | @b SSE2 | 128 | 29.6 GB/s | 118.1 |
+ * | RAM sequential read | | N/A | 28.0 GB/s | N/A |
+ * | ahash | AES-NI | 64 | 22.5 GB/s | 107.2 |
+ * | City64 | | 64 | 22.0 GB/s | 76.6 |
+ * | T1ha2 | | 64 | 22.0 GB/s | 99.0 |
+ * | City128 | | 128 | 21.7 GB/s | 57.7 |
+ * | FarmHash | AES-NI | 64 | 21.3 GB/s | 71.9 |
+ * | XXH64() | | 64 | 19.4 GB/s | 71.0 |
+ * | SpookyHash | | 64 | 19.3 GB/s | 53.2 |
+ * | Mum | | 64 | 18.0 GB/s | 67.0 |
+ * | CRC32C | SSE4.2 | 32 | 13.0 GB/s | 57.9 |
+ * | XXH32() | | 32 | 9.7 GB/s | 71.9 |
+ * | City32 | | 32 | 9.1 GB/s | 66.0 |
+ * | Blake3* | @b AVX2 | 256 | 4.4 GB/s | 8.1 |
+ * | Murmur3 | | 32 | 3.9 GB/s | 56.1 |
+ * | SipHash* | | 64 | 3.0 GB/s | 43.2 |
+ * | Blake3* | @b SSE2 | 256 | 2.4 GB/s | 8.1 |
+ * | HighwayHash | | 64 | 1.4 GB/s | 6.0 |
+ * | FNV64 | | 64 | 1.2 GB/s | 62.7 |
+ * | Blake2* | | 256 | 1.1 GB/s | 5.1 |
+ * | SHA1* | | 160 | 0.8 GB/s | 5.6 |
+ * | MD5* | | 128 | 0.6 GB/s | 7.8 |
+ * @note
+ * - Hashes which require a specific ISA extension are noted. SSE2 is also noted,
+ * even though it is mandatory on x64.
+ * - Hashes with an asterisk are cryptographic. Note that MD5 is non-cryptographic
+ * by modern standards.
+ * - Small data velocity is a rough average of algorithm's efficiency for small
+ * data. For more accurate information, see the wiki.
+ * - More benchmarks and strength tests are found on the wiki:
+ * https://github.com/Cyan4973/xxHash/wiki
+ *
+ * Usage
+ * ------
+ * All xxHash variants use a similar API. Changing the algorithm is a trivial
+ * substitution.
+ *
+ * @pre
+ * For functions which take an input and length parameter, the following
+ * requirements are assumed:
+ * - The range from [`input`, `input + length`) is valid, readable memory.
+ * - The only exception is if the `length` is `0`, `input` may be `NULL`.
+ * - For C++, the objects must have the *TriviallyCopyable* property, as the
+ * functions access bytes directly as if it was an array of `unsigned char`.
+ *
+ * @anchor single_shot_example
+ * **Single Shot**
+ *
+ * These functions are stateless functions which hash a contiguous block of memory,
+ * immediately returning the result. They are the easiest and usually the fastest
+ * option.
+ *
+ * XXH32(), XXH64(), XXH3_64bits(), XXH3_128bits()
+ *
+ * @code{.c}
+ * #include <string.h>
+ * #include "xxhash.h"
+ *
+ * // Example for a function which hashes a null terminated string with XXH32().
+ * XXH32_hash_t hash_string(const char* string, XXH32_hash_t seed)
+ * {
+ * // NULL pointers are only valid if the length is zero
+ * size_t length = (string == NULL) ? 0 : strlen(string);
+ * return XXH32(string, length, seed);
+ * }
+ * @endcode
+ *
+ * @anchor streaming_example
+ * **Streaming**
+ *
+ * These groups of functions allow incremental hashing of unknown size, even
+ * more than what would fit in a size_t.
+ *
+ * XXH32_reset(), XXH64_reset(), XXH3_64bits_reset(), XXH3_128bits_reset()
+ *
+ * @code{.c}
+ * #include <stdio.h>
+ * #include <assert.h>
+ * #include "xxhash.h"
+ * // Example for a function which hashes a FILE incrementally with XXH3_64bits().
+ * XXH64_hash_t hashFile(FILE* f)
+ * {
+ * // Allocate a state struct. Do not just use malloc() or new.
+ * XXH3_state_t* state = XXH3_createState();
+ * assert(state != NULL && "Out of memory!");
+ * // Reset the state to start a new hashing session.
+ * XXH3_64bits_reset(state);
+ * char buffer[4096];
+ * size_t count;
+ * // Read the file in chunks
+ * while ((count = fread(buffer, 1, sizeof(buffer), f)) != 0) {
+ * // Run update() as many times as necessary to process the data
+ * XXH3_64bits_update(state, buffer, count);
+ * }
+ * // Retrieve the finalized hash. This will not change the state.
+ * XXH64_hash_t result = XXH3_64bits_digest(state);
+ * // Free the state. Do not use free().
+ * XXH3_freeState(state);
+ * return result;
+ * }
+ * @endcode
+ *
+ * @file xxhash.h
+ * xxHash prototypes and implementation
+ */
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+/* ****************************
+ * INLINE mode
+ ******************************/
+/*!
+ * @defgroup public Public API
+ * Contains details on the public xxHash functions.
+ * @{
+ */
+#ifdef XXH_DOXYGEN
+/*!
+ * @brief Exposes the implementation and marks all functions as `inline`.
+ *
+ * Use these build macros to inline xxhash into the target unit.
+ * Inlining improves performance on small inputs, especially when the length is
+ * expressed as a compile-time constant:
+ *
+ * https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html
+ *
+ * It also keeps xxHash symbols private to the unit, so they are not exported.
+ *
+ * Usage:
+ * @code{.c}
+ * #define XXH_INLINE_ALL
+ * #include "xxhash.h"
+ * @endcode
+ * Do not compile and link xxhash.o as a separate object, as it is not useful.
+ */
+# define XXH_INLINE_ALL
+# undef XXH_INLINE_ALL
+/*!
+ * @brief Exposes the implementation without marking functions as inline.
+ */
+# define XXH_PRIVATE_API
+# undef XXH_PRIVATE_API
+/*!
+ * @brief Emulate a namespace by transparently prefixing all symbols.
+ *
+ * If you want to include _and expose_ xxHash functions from within your own
+ * library, but also want to avoid symbol collisions with other libraries which
+ * may also include xxHash, you can use @ref XXH_NAMESPACE to automatically prefix
+ * any public symbol from xxhash library with the value of @ref XXH_NAMESPACE
+ * (therefore, avoid empty or numeric values).
+ *
+ * Note that no change is required within the calling program as long as it
+ * includes `xxhash.h`: Regular symbol names will be automatically translated
+ * by this header.
+ */
+# define XXH_NAMESPACE /* YOUR NAME HERE */
+# undef XXH_NAMESPACE
+#endif
+
+#if (defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)) \
+ && !defined(XXH_INLINE_ALL_31684351384)
+ /* this section should be traversed only once */
+# define XXH_INLINE_ALL_31684351384
+ /* give access to the advanced API, required to compile implementations */
+# undef XXH_STATIC_LINKING_ONLY /* avoid macro redef */
+# define XXH_STATIC_LINKING_ONLY
+ /* make all functions private */
+# undef XXH_PUBLIC_API
+# if defined(__GNUC__)
+# define XXH_PUBLIC_API static __inline __attribute__((unused))
+# elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
+# define XXH_PUBLIC_API static inline
+# elif defined(_MSC_VER)
+# define XXH_PUBLIC_API static __inline
+# else
+ /* note: this version may generate warnings for unused static functions */
+# define XXH_PUBLIC_API static
+# endif
+
+ /*
+ * This part deals with the special case where a unit wants to inline xxHash,
+ * but "xxhash.h" has previously been included without XXH_INLINE_ALL,
+ * such as part of some previously included *.h header file.
+ * Without further action, the new include would just be ignored,
+ * and functions would effectively _not_ be inlined (silent failure).
+ * The following macros solve this situation by prefixing all inlined names,
+ * avoiding naming collision with previous inclusions.
+ */
+ /* Before that, we unconditionally #undef all symbols,
+ * in case they were already defined with XXH_NAMESPACE.
+ * They will then be redefined for XXH_INLINE_ALL
+ */
+# undef XXH_versionNumber
+ /* XXH32 */
+# undef XXH32
+# undef XXH32_createState
+# undef XXH32_freeState
+# undef XXH32_reset
+# undef XXH32_update
+# undef XXH32_digest
+# undef XXH32_copyState
+# undef XXH32_canonicalFromHash
+# undef XXH32_hashFromCanonical
+ /* XXH64 */
+# undef XXH64
+# undef XXH64_createState
+# undef XXH64_freeState
+# undef XXH64_reset
+# undef XXH64_update
+# undef XXH64_digest
+# undef XXH64_copyState
+# undef XXH64_canonicalFromHash
+# undef XXH64_hashFromCanonical
+ /* XXH3_64bits */
+# undef XXH3_64bits
+# undef XXH3_64bits_withSecret
+# undef XXH3_64bits_withSeed
+# undef XXH3_64bits_withSecretandSeed
+# undef XXH3_createState
+# undef XXH3_freeState
+# undef XXH3_copyState
+# undef XXH3_64bits_reset
+# undef XXH3_64bits_reset_withSeed
+# undef XXH3_64bits_reset_withSecret
+# undef XXH3_64bits_update
+# undef XXH3_64bits_digest
+# undef XXH3_generateSecret
+ /* XXH3_128bits */
+# undef XXH128
+# undef XXH3_128bits
+# undef XXH3_128bits_withSeed
+# undef XXH3_128bits_withSecret
+# undef XXH3_128bits_reset
+# undef XXH3_128bits_reset_withSeed
+# undef XXH3_128bits_reset_withSecret
+# undef XXH3_128bits_reset_withSecretandSeed
+# undef XXH3_128bits_update
+# undef XXH3_128bits_digest
+# undef XXH128_isEqual
+# undef XXH128_cmp
+# undef XXH128_canonicalFromHash
+# undef XXH128_hashFromCanonical
+ /* Finally, free the namespace itself */
+# undef XXH_NAMESPACE
+
+ /* employ the namespace for XXH_INLINE_ALL */
+# define XXH_NAMESPACE XXH_INLINE_
+ /*
+ * Some identifiers (enums, type names) are not symbols,
+ * but they must nonetheless be renamed to avoid redeclaration.
+ * Alternative solution: do not redeclare them.
+ * However, this requires some #ifdefs, and has a more dispersed impact.
+ * Meanwhile, renaming can be achieved in a single place.
+ */
+# define XXH_IPREF(Id) XXH_NAMESPACE ## Id
+# define XXH_OK XXH_IPREF(XXH_OK)
+# define XXH_ERROR XXH_IPREF(XXH_ERROR)
+# define XXH_errorcode XXH_IPREF(XXH_errorcode)
+# define XXH32_canonical_t XXH_IPREF(XXH32_canonical_t)
+# define XXH64_canonical_t XXH_IPREF(XXH64_canonical_t)
+# define XXH128_canonical_t XXH_IPREF(XXH128_canonical_t)
+# define XXH32_state_s XXH_IPREF(XXH32_state_s)
+# define XXH32_state_t XXH_IPREF(XXH32_state_t)
+# define XXH64_state_s XXH_IPREF(XXH64_state_s)
+# define XXH64_state_t XXH_IPREF(XXH64_state_t)
+# define XXH3_state_s XXH_IPREF(XXH3_state_s)
+# define XXH3_state_t XXH_IPREF(XXH3_state_t)
+# define XXH128_hash_t XXH_IPREF(XXH128_hash_t)
+ /* Ensure the header is parsed again, even if it was previously included */
+# undef XXHASH_H_5627135585666179
+# undef XXHASH_H_STATIC_13879238742
+#endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */
+
+/* ****************************************************************
+ * Stable API
+ *****************************************************************/
+#ifndef XXHASH_H_5627135585666179
+#define XXHASH_H_5627135585666179 1
+
+/*! @brief Marks a global symbol. */
+#if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API)
+# if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT))
+# ifdef XXH_EXPORT
+# define XXH_PUBLIC_API __declspec(dllexport)
+# elif XXH_IMPORT
+# define XXH_PUBLIC_API __declspec(dllimport)
+# endif
+# else
+# define XXH_PUBLIC_API /* do nothing */
+# endif
+#endif
+
+#ifdef XXH_NAMESPACE
+# define XXH_CAT(A,B) A##B
+# define XXH_NAME2(A,B) XXH_CAT(A,B)
+# define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber)
+/* XXH32 */
+# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32)
+# define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState)
+# define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState)
+# define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset)
+# define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update)
+# define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest)
+# define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState)
+# define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash)
+# define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical)
+/* XXH64 */
+# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64)
+# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState)
+# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState)
+# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset)
+# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update)
+# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest)
+# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState)
+# define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash)
+# define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical)
+/* XXH3_64bits */
+# define XXH3_64bits XXH_NAME2(XXH_NAMESPACE, XXH3_64bits)
+# define XXH3_64bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSecret)
+# define XXH3_64bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSeed)
+# define XXH3_64bits_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSecretandSeed)
+# define XXH3_createState XXH_NAME2(XXH_NAMESPACE, XXH3_createState)
+# define XXH3_freeState XXH_NAME2(XXH_NAMESPACE, XXH3_freeState)
+# define XXH3_copyState XXH_NAME2(XXH_NAMESPACE, XXH3_copyState)
+# define XXH3_64bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset)
+# define XXH3_64bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSeed)
+# define XXH3_64bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSecret)
+# define XXH3_64bits_reset_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSecretandSeed)
+# define XXH3_64bits_update XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_update)
+# define XXH3_64bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_digest)
+# define XXH3_generateSecret XXH_NAME2(XXH_NAMESPACE, XXH3_generateSecret)
+# define XXH3_generateSecret_fromSeed XXH_NAME2(XXH_NAMESPACE, XXH3_generateSecret_fromSeed)
+/* XXH3_128bits */
+# define XXH128 XXH_NAME2(XXH_NAMESPACE, XXH128)
+# define XXH3_128bits XXH_NAME2(XXH_NAMESPACE, XXH3_128bits)
+# define XXH3_128bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSeed)
+# define XXH3_128bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSecret)
+# define XXH3_128bits_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSecretandSeed)
+# define XXH3_128bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset)
+# define XXH3_128bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSeed)
+# define XXH3_128bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSecret)
+# define XXH3_128bits_reset_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSecretandSeed)
+# define XXH3_128bits_update XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_update)
+# define XXH3_128bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_digest)
+# define XXH128_isEqual XXH_NAME2(XXH_NAMESPACE, XXH128_isEqual)
+# define XXH128_cmp XXH_NAME2(XXH_NAMESPACE, XXH128_cmp)
+# define XXH128_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH128_canonicalFromHash)
+# define XXH128_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH128_hashFromCanonical)
+#endif
+
+
+/* *************************************
+* Compiler specifics
+***************************************/
+
+/* specific declaration modes for Windows */
+#if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API)
+# if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT))
+# ifdef XXH_EXPORT
+# define XXH_PUBLIC_API __declspec(dllexport)
+# elif XXH_IMPORT
+# define XXH_PUBLIC_API __declspec(dllimport)
+# endif
+# else
+# define XXH_PUBLIC_API /* do nothing */
+# endif
+#endif
+
+#if defined (__GNUC__)
+# define XXH_CONSTF __attribute__((const))
+# define XXH_PUREF __attribute__((pure))
+# define XXH_MALLOCF __attribute__((malloc))
+#else
+# define XXH_CONSTF /* disable */
+# define XXH_PUREF
+# define XXH_MALLOCF
+#endif
+
+/* *************************************
+* Version
+***************************************/
+#define XXH_VERSION_MAJOR 0
+#define XXH_VERSION_MINOR 8
+#define XXH_VERSION_RELEASE 1
+/*! @brief Version number, encoded as two digits each */
+#define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE)
+
+/*!
+ * @brief Obtains the xxHash version.
+ *
+ * This is mostly useful when xxHash is compiled as a shared library,
+ * since the returned value comes from the library, as opposed to header file.
+ *
+ * @return @ref XXH_VERSION_NUMBER of the invoked library.
+ */
+XXH_PUBLIC_API XXH_CONSTF unsigned XXH_versionNumber (void);
+
+
+/* ****************************
+* Common basic types
+******************************/
+#include <stddef.h> /* size_t */
+/*!
+ * @brief Exit code for the streaming API.
+ */
+typedef enum {
+ XXH_OK = 0, /*!< OK */
+ XXH_ERROR /*!< Error */
+} XXH_errorcode;
+
+
+/*-**********************************************************************
+* 32-bit hash
+************************************************************************/
+#if defined(XXH_DOXYGEN) /* Don't show <stdint.h> include */
+/*!
+ * @brief An unsigned 32-bit integer.
+ *
+ * Not necessarily defined to `uint32_t` but functionally equivalent.
+ */
+typedef uint32_t XXH32_hash_t;
+
+#elif !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# include <stdint.h>
+ typedef uint32_t XXH32_hash_t;
+
+#else
+# include <limits.h>
+# if UINT_MAX == 0xFFFFFFFFUL
+ typedef unsigned int XXH32_hash_t;
+# elif ULONG_MAX == 0xFFFFFFFFUL
+ typedef unsigned long XXH32_hash_t;
+# else
+# error "unsupported platform: need a 32-bit type"
+# endif
+#endif
+
+/*!
+ * @}
+ *
+ * @defgroup XXH32_family XXH32 family
+ * @ingroup public
+ * Contains functions used in the classic 32-bit xxHash algorithm.
+ *
+ * @note
+ * XXH32 is useful for older platforms, with no or poor 64-bit performance.
+ * Note that the @ref XXH3_family provides competitive speed for both 32-bit
+ * and 64-bit systems, and offers true 64/128 bit hash results.
+ *
+ * @see @ref XXH64_family, @ref XXH3_family : Other xxHash families
+ * @see @ref XXH32_impl for implementation details
+ * @{
+ */
+
+/*!
+ * @brief Calculates the 32-bit hash of @p input using xxHash32.
+ *
+ * Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark): 5.4 GB/s
+ *
+ * See @ref single_shot_example "Single Shot Example" for an example.
+ *
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ * @param seed The 32-bit seed to alter the hash's output predictably.
+ *
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return The calculated 32-bit hash value.
+ *
+ * @see
+ * XXH64(), XXH3_64bits_withSeed(), XXH3_128bits_withSeed(), XXH128():
+ * Direct equivalents for the other variants of xxHash.
+ * @see
+ * XXH32_createState(), XXH32_update(), XXH32_digest(): Streaming version.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed);
+
+#ifndef XXH_NO_STREAM
+/*!
+ * Streaming functions generate the xxHash value from an incremental input.
+ * This method is slower than single-call functions, due to state management.
+ * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized.
+ *
+ * An XXH state must first be allocated using `XXH*_createState()`.
+ *
+ * Start a new hash by initializing the state with a seed using `XXH*_reset()`.
+ *
+ * Then, feed the hash state by calling `XXH*_update()` as many times as necessary.
+ *
+ * The function returns an error code, with 0 meaning OK, and any other value
+ * meaning there is an error.
+ *
+ * Finally, a hash value can be produced anytime, by using `XXH*_digest()`.
+ * This function returns the nn-bits hash as an int or long long.
+ *
+ * It's still possible to continue inserting input into the hash state after a
+ * digest, and generate new hash values later on by invoking `XXH*_digest()`.
+ *
+ * When done, release the state using `XXH*_freeState()`.
+ *
+ * @see streaming_example at the top of @ref xxhash.h for an example.
+ */
+
+/*!
+ * @typedef struct XXH32_state_s XXH32_state_t
+ * @brief The opaque state struct for the XXH32 streaming API.
+ *
+ * @see XXH32_state_s for details.
+ */
+typedef struct XXH32_state_s XXH32_state_t;
+
+/*!
+ * @brief Allocates an @ref XXH32_state_t.
+ *
+ * Must be freed with XXH32_freeState().
+ * @return An allocated XXH32_state_t on success, `NULL` on failure.
+ */
+XXH_PUBLIC_API XXH_MALLOCF XXH32_state_t* XXH32_createState(void);
+/*!
+ * @brief Frees an @ref XXH32_state_t.
+ *
+ * Must be allocated with XXH32_createState().
+ * @param statePtr A pointer to an @ref XXH32_state_t allocated with @ref XXH32_createState().
+ * @return XXH_OK.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr);
+/*!
+ * @brief Copies one @ref XXH32_state_t to another.
+ *
+ * @param dst_state The state to copy to.
+ * @param src_state The state to copy from.
+ * @pre
+ * @p dst_state and @p src_state must not be `NULL` and must not overlap.
+ */
+XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state);
+
+/*!
+ * @brief Resets an @ref XXH32_state_t to begin a new hash.
+ *
+ * This function resets and seeds a state. Call it before @ref XXH32_update().
+ *
+ * @param statePtr The state struct to reset.
+ * @param seed The 32-bit seed to alter the hash result predictably.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, XXH32_hash_t seed);
+
+/*!
+ * @brief Consumes a block of @p input to an @ref XXH32_state_t.
+ *
+ * Call this to incrementally consume blocks of data.
+ *
+ * @param statePtr The state struct to update.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length);
+
+/*!
+ * @brief Returns the calculated hash value from an @ref XXH32_state_t.
+ *
+ * @note
+ * Calling XXH32_digest() will not affect @p statePtr, so you can update,
+ * digest, and update again.
+ *
+ * @param statePtr The state struct to calculate the hash from.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return The calculated xxHash32 value from that state.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
+
+/******* Canonical representation *******/
+
+/*
+ * The default return values from XXH functions are unsigned 32 and 64 bit
+ * integers.
+ * This the simplest and fastest format for further post-processing.
+ *
+ * However, this leaves open the question of what is the order on the byte level,
+ * since little and big endian conventions will store the same number differently.
+ *
+ * The canonical representation settles this issue by mandating big-endian
+ * convention, the same convention as human-readable numbers (large digits first).
+ *
+ * When writing hash values to storage, sending them over a network, or printing
+ * them, it's highly recommended to use the canonical representation to ensure
+ * portability across a wider range of systems, present and future.
+ *
+ * The following functions allow transformation of hash values to and from
+ * canonical format.
+ */
+
+/*!
+ * @brief Canonical (big endian) representation of @ref XXH32_hash_t.
+ */
+typedef struct {
+ unsigned char digest[4]; /*!< Hash bytes, big endian */
+} XXH32_canonical_t;
+
+/*!
+ * @brief Converts an @ref XXH32_hash_t to a big endian @ref XXH32_canonical_t.
+ *
+ * @param dst The @ref XXH32_canonical_t pointer to be stored to.
+ * @param hash The @ref XXH32_hash_t to be converted.
+ *
+ * @pre
+ * @p dst must not be `NULL`.
+ */
+XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash);
+
+/*!
+ * @brief Converts an @ref XXH32_canonical_t to a native @ref XXH32_hash_t.
+ *
+ * @param src The @ref XXH32_canonical_t to convert.
+ *
+ * @pre
+ * @p src must not be `NULL`.
+ *
+ * @return The converted hash.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src);
+
+
+#ifdef __has_attribute
+# define XXH_HAS_ATTRIBUTE(x) __has_attribute(x)
+#else
+# define XXH_HAS_ATTRIBUTE(x) 0
+#endif
+
+/* C-language Attributes are added in C23. */
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ > 201710L) && defined(__has_c_attribute)
+# define XXH_HAS_C_ATTRIBUTE(x) __has_c_attribute(x)
+#else
+# define XXH_HAS_C_ATTRIBUTE(x) 0
+#endif
+
+#if defined(__cplusplus) && defined(__has_cpp_attribute)
+# define XXH_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
+#else
+# define XXH_HAS_CPP_ATTRIBUTE(x) 0
+#endif
+
+/*
+ * Define XXH_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute
+ * introduced in CPP17 and C23.
+ * CPP17 : https://en.cppreference.com/w/cpp/language/attributes/fallthrough
+ * C23 : https://en.cppreference.com/w/c/language/attributes/fallthrough
+ */
+#if XXH_HAS_C_ATTRIBUTE(fallthrough) || XXH_HAS_CPP_ATTRIBUTE(fallthrough)
+# define XXH_FALLTHROUGH [[fallthrough]]
+#elif XXH_HAS_ATTRIBUTE(__fallthrough__)
+# define XXH_FALLTHROUGH __attribute__ ((__fallthrough__))
+#else
+# define XXH_FALLTHROUGH /* fallthrough */
+#endif
+
+/*!
+ * @}
+ * @ingroup public
+ * @{
+ */
+
+#ifndef XXH_NO_LONG_LONG
+/*-**********************************************************************
+* 64-bit hash
+************************************************************************/
+#if defined(XXH_DOXYGEN) /* don't include <stdint.h> */
+/*!
+ * @brief An unsigned 64-bit integer.
+ *
+ * Not necessarily defined to `uint64_t` but functionally equivalent.
+ */
+typedef uint64_t XXH64_hash_t;
+#elif !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# include <stdint.h>
+ typedef uint64_t XXH64_hash_t;
+#else
+# include <limits.h>
+# if defined(__LP64__) && ULONG_MAX == 0xFFFFFFFFFFFFFFFFULL
+ /* LP64 ABI says uint64_t is unsigned long */
+ typedef unsigned long XXH64_hash_t;
+# else
+ /* the following type must have a width of 64-bit */
+ typedef unsigned long long XXH64_hash_t;
+# endif
+#endif
+
+/*!
+ * @}
+ *
+ * @defgroup XXH64_family XXH64 family
+ * @ingroup public
+ * @{
+ * Contains functions used in the classic 64-bit xxHash algorithm.
+ *
+ * @note
+ * XXH3 provides competitive speed for both 32-bit and 64-bit systems,
+ * and offers true 64/128 bit hash results.
+ * It provides better speed for systems with vector processing capabilities.
+ */
+
+/*!
+ * @brief Calculates the 64-bit hash of @p input using xxHash64.
+ *
+ * This function usually runs faster on 64-bit systems, but slower on 32-bit
+ * systems (see benchmark).
+ *
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ * @param seed The 64-bit seed to alter the hash's output predictably.
+ *
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return The calculated 64-bit hash.
+ *
+ * @see
+ * XXH32(), XXH3_64bits_withSeed(), XXH3_128bits_withSeed(), XXH128():
+ * Direct equivalents for the other variants of xxHash.
+ * @see
+ * XXH64_createState(), XXH64_update(), XXH64_digest(): Streaming version.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64(const void* input, size_t length, XXH64_hash_t seed);
+
+/******* Streaming *******/
+#ifndef XXH_NO_STREAM
+/*!
+ * @brief The opaque state struct for the XXH64 streaming API.
+ *
+ * @see XXH64_state_s for details.
+ */
+typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */
+XXH_PUBLIC_API XXH_MALLOCF XXH64_state_t* XXH64_createState(void);
+XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr);
+XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state);
+
+XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length);
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
+/******* Canonical representation *******/
+typedef struct { unsigned char digest[sizeof(XXH64_hash_t)]; } XXH64_canonical_t;
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash);
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src);
+
+#ifndef XXH_NO_XXH3
+
+/*!
+ * @}
+ * ************************************************************************
+ * @defgroup XXH3_family XXH3 family
+ * @ingroup public
+ * @{
+ *
+ * XXH3 is a more recent hash algorithm featuring:
+ * - Improved speed for both small and large inputs
+ * - True 64-bit and 128-bit outputs
+ * - SIMD acceleration
+ * - Improved 32-bit viability
+ *
+ * Speed analysis methodology is explained here:
+ *
+ * https://fastcompression.blogspot.com/2019/03/presenting-xxh3.html
+ *
+ * Compared to XXH64, expect XXH3 to run approximately
+ * ~2x faster on large inputs and >3x faster on small ones,
+ * exact differences vary depending on platform.
+ *
+ * XXH3's speed benefits greatly from SIMD and 64-bit arithmetic,
+ * but does not require it.
+ * Most 32-bit and 64-bit targets that can run XXH32 smoothly can run XXH3
+ * at competitive speeds, even without vector support. Further details are
+ * explained in the implementation.
+ *
+ * Optimized implementations are provided for AVX512, AVX2, SSE2, NEON, POWER8,
+ * ZVector and scalar targets. This can be controlled via the @ref XXH_VECTOR
+ * macro. For the x86 family, an automatic dispatcher is included separately
+ * in @ref xxh_x86dispatch.c.
+ *
+ * XXH3 implementation is portable:
+ * it has a generic C90 formulation that can be compiled on any platform,
+ * all implementations generage exactly the same hash value on all platforms.
+ * Starting from v0.8.0, it's also labelled "stable", meaning that
+ * any future version will also generate the same hash value.
+ *
+ * XXH3 offers 2 variants, _64bits and _128bits.
+ *
+ * When only 64 bits are needed, prefer invoking the _64bits variant, as it
+ * reduces the amount of mixing, resulting in faster speed on small inputs.
+ * It's also generally simpler to manipulate a scalar return type than a struct.
+ *
+ * The API supports one-shot hashing, streaming mode, and custom secrets.
+ */
+/*-**********************************************************************
+* XXH3 64-bit variant
+************************************************************************/
+
+/*!
+ * @brief 64-bit unseeded variant of XXH3.
+ *
+ * This is equivalent to @ref XXH3_64bits_withSeed() with a seed of 0, however
+ * it may have slightly better performance due to constant propagation of the
+ * defaults.
+ *
+ * @see
+ * XXH32(), XXH64(), XXH3_128bits(): equivalent for the other xxHash algorithms
+ * @see
+ * XXH3_64bits_withSeed(), XXH3_64bits_withSecret(): other seeding variants
+ * @see
+ * XXH3_64bits_reset(), XXH3_64bits_update(), XXH3_64bits_digest(): Streaming version.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits(const void* input, size_t length);
+
+/*!
+ * @brief 64-bit seeded variant of XXH3
+ *
+ * This variant generates a custom secret on the fly based on default secret
+ * altered using the `seed` value.
+ *
+ * While this operation is decently fast, note that it's not completely free.
+ *
+ * @note
+ * seed == 0 produces the same results as @ref XXH3_64bits().
+ *
+ * @param input The data to hash
+ * @param length The length
+ * @param seed The 64-bit seed to alter the state.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSeed(const void* input, size_t length, XXH64_hash_t seed);
+
+/*!
+ * The bare minimum size for a custom secret.
+ *
+ * @see
+ * XXH3_64bits_withSecret(), XXH3_64bits_reset_withSecret(),
+ * XXH3_128bits_withSecret(), XXH3_128bits_reset_withSecret().
+ */
+#define XXH3_SECRET_SIZE_MIN 136
+
+/*!
+ * @brief 64-bit variant of XXH3 with a custom "secret".
+ *
+ * It's possible to provide any blob of bytes as a "secret" to generate the hash.
+ * This makes it more difficult for an external actor to prepare an intentional collision.
+ * The main condition is that secretSize *must* be large enough (>= XXH3_SECRET_SIZE_MIN).
+ * However, the quality of the secret impacts the dispersion of the hash algorithm.
+ * Therefore, the secret _must_ look like a bunch of random bytes.
+ * Avoid "trivial" or structured data such as repeated sequences or a text document.
+ * Whenever in doubt about the "randomness" of the blob of bytes,
+ * consider employing "XXH3_generateSecret()" instead (see below).
+ * It will generate a proper high entropy secret derived from the blob of bytes.
+ * Another advantage of using XXH3_generateSecret() is that
+ * it guarantees that all bits within the initial blob of bytes
+ * will impact every bit of the output.
+ * This is not necessarily the case when using the blob of bytes directly
+ * because, when hashing _small_ inputs, only a portion of the secret is employed.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize);
+
+
+/******* Streaming *******/
+#ifndef XXH_NO_STREAM
+/*
+ * Streaming requires state maintenance.
+ * This operation costs memory and CPU.
+ * As a consequence, streaming is slower than one-shot hashing.
+ * For better performance, prefer one-shot functions whenever applicable.
+ */
+
+/*!
+ * @brief The state struct for the XXH3 streaming API.
+ *
+ * @see XXH3_state_s for details.
+ */
+typedef struct XXH3_state_s XXH3_state_t;
+XXH_PUBLIC_API XXH_MALLOCF XXH3_state_t* XXH3_createState(void);
+XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr);
+XXH_PUBLIC_API void XXH3_copyState(XXH3_state_t* dst_state, const XXH3_state_t* src_state);
+
+/*
+ * XXH3_64bits_reset():
+ * Initialize with default parameters.
+ * digest will be equivalent to `XXH3_64bits()`.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset(XXH3_state_t* statePtr);
+/*
+ * XXH3_64bits_reset_withSeed():
+ * Generate a custom secret from `seed`, and store it into `statePtr`.
+ * digest will be equivalent to `XXH3_64bits_withSeed()`.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed);
+/*!
+ * XXH3_64bits_reset_withSecret():
+ * `secret` is referenced, it _must outlive_ the hash streaming session.
+ * Similar to one-shot API, `secretSize` must be >= `XXH3_SECRET_SIZE_MIN`,
+ * and the quality of produced hash values depends on secret's entropy
+ * (secret's content should look like a bunch of random bytes).
+ * When in doubt about the randomness of a candidate `secret`,
+ * consider employing `XXH3_generateSecret()` instead (see below).
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize);
+
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update (XXH3_state_t* statePtr, const void* input, size_t length);
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
+
+/* note : canonical representation of XXH3 is the same as XXH64
+ * since they both produce XXH64_hash_t values */
+
+
+/*-**********************************************************************
+* XXH3 128-bit variant
+************************************************************************/
+
+/*!
+ * @brief The return value from 128-bit hashes.
+ *
+ * Stored in little endian order, although the fields themselves are in native
+ * endianness.
+ */
+typedef struct {
+ XXH64_hash_t low64; /*!< `value & 0xFFFFFFFFFFFFFFFF` */
+ XXH64_hash_t high64; /*!< `value >> 64` */
+} XXH128_hash_t;
+
+/*!
+ * @brief Unseeded 128-bit variant of XXH3
+ *
+ * The 128-bit variant of XXH3 has more strength, but it has a bit of overhead
+ * for shorter inputs.
+ *
+ * This is equivalent to @ref XXH3_128bits_withSeed() with a seed of 0, however
+ * it may have slightly better performance due to constant propagation of the
+ * defaults.
+ *
+ * @see
+ * XXH32(), XXH64(), XXH3_64bits(): equivalent for the other xxHash algorithms
+ * @see
+ * XXH3_128bits_withSeed(), XXH3_128bits_withSecret(): other seeding variants
+ * @see
+ * XXH3_128bits_reset(), XXH3_128bits_update(), XXH3_128bits_digest(): Streaming version.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits(const void* data, size_t len);
+/*! @brief Seeded 128-bit variant of XXH3. @see XXH3_64bits_withSeed(). */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSeed(const void* data, size_t len, XXH64_hash_t seed);
+/*! @brief Custom secret 128-bit variant of XXH3. @see XXH3_64bits_withSecret(). */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize);
+
+/******* Streaming *******/
+#ifndef XXH_NO_STREAM
+/*
+ * Streaming requires state maintenance.
+ * This operation costs memory and CPU.
+ * As a consequence, streaming is slower than one-shot hashing.
+ * For better performance, prefer one-shot functions whenever applicable.
+ *
+ * XXH3_128bits uses the same XXH3_state_t as XXH3_64bits().
+ * Use already declared XXH3_createState() and XXH3_freeState().
+ *
+ * All reset and streaming functions have same meaning as their 64-bit counterpart.
+ */
+
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH3_state_t* statePtr);
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize);
+
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update (XXH3_state_t* statePtr, const void* input, size_t length);
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
+
+/* Following helper functions make it possible to compare XXH128_hast_t values.
+ * Since XXH128_hash_t is a structure, this capability is not offered by the language.
+ * Note: For better performance, these functions can be inlined using XXH_INLINE_ALL */
+
+/*!
+ * XXH128_isEqual():
+ * Return: 1 if `h1` and `h2` are equal, 0 if they are not.
+ */
+XXH_PUBLIC_API XXH_PUREF int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2);
+
+/*!
+ * @brief Compares two @ref XXH128_hash_t
+ * This comparator is compatible with stdlib's `qsort()`/`bsearch()`.
+ *
+ * @return: >0 if *h128_1 > *h128_2
+ * =0 if *h128_1 == *h128_2
+ * <0 if *h128_1 < *h128_2
+ */
+XXH_PUBLIC_API XXH_PUREF int XXH128_cmp(const void* h128_1, const void* h128_2);
+
+
+/******* Canonical representation *******/
+typedef struct { unsigned char digest[sizeof(XXH128_hash_t)]; } XXH128_canonical_t;
+XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash);
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128_hashFromCanonical(const XXH128_canonical_t* src);
+
+
+#endif /* !XXH_NO_XXH3 */
+#endif /* XXH_NO_LONG_LONG */
+
+/*!
+ * @}
+ */
+#endif /* XXHASH_H_5627135585666179 */
+
+
+
+#if defined(XXH_STATIC_LINKING_ONLY) && !defined(XXHASH_H_STATIC_13879238742)
+#define XXHASH_H_STATIC_13879238742
+/* ****************************************************************************
+ * This section contains declarations which are not guaranteed to remain stable.
+ * They may change in future versions, becoming incompatible with a different
+ * version of the library.
+ * These declarations should only be used with static linking.
+ * Never use them in association with dynamic linking!
+ ***************************************************************************** */
+
+/*
+ * These definitions are only present to allow static allocation
+ * of XXH states, on stack or in a struct, for example.
+ * Never **ever** access their members directly.
+ */
+
+/*!
+ * @internal
+ * @brief Structure for XXH32 streaming API.
+ *
+ * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY,
+ * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is
+ * an opaque type. This allows fields to safely be changed.
+ *
+ * Typedef'd to @ref XXH32_state_t.
+ * Do not access the members of this struct directly.
+ * @see XXH64_state_s, XXH3_state_s
+ */
+struct XXH32_state_s {
+ XXH32_hash_t total_len_32; /*!< Total length hashed, modulo 2^32 */
+ XXH32_hash_t large_len; /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */
+ XXH32_hash_t v[4]; /*!< Accumulator lanes */
+ XXH32_hash_t mem32[4]; /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */
+ XXH32_hash_t memsize; /*!< Amount of data in @ref mem32 */
+ XXH32_hash_t reserved; /*!< Reserved field. Do not read nor write to it. */
+}; /* typedef'd to XXH32_state_t */
+
+
+#ifndef XXH_NO_LONG_LONG /* defined when there is no 64-bit support */
+
+/*!
+ * @internal
+ * @brief Structure for XXH64 streaming API.
+ *
+ * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY,
+ * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is
+ * an opaque type. This allows fields to safely be changed.
+ *
+ * Typedef'd to @ref XXH64_state_t.
+ * Do not access the members of this struct directly.
+ * @see XXH32_state_s, XXH3_state_s
+ */
+struct XXH64_state_s {
+ XXH64_hash_t total_len; /*!< Total length hashed. This is always 64-bit. */
+ XXH64_hash_t v[4]; /*!< Accumulator lanes */
+ XXH64_hash_t mem64[4]; /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */
+ XXH32_hash_t memsize; /*!< Amount of data in @ref mem64 */
+ XXH32_hash_t reserved32; /*!< Reserved field, needed for padding anyways*/
+ XXH64_hash_t reserved64; /*!< Reserved field. Do not read or write to it. */
+}; /* typedef'd to XXH64_state_t */
+
+#ifndef XXH_NO_XXH3
+
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* >= C11 */
+# include <stdalign.h>
+# define XXH_ALIGN(n) alignas(n)
+#elif defined(__cplusplus) && (__cplusplus >= 201103L) /* >= C++11 */
+/* In C++ alignas() is a keyword */
+# define XXH_ALIGN(n) alignas(n)
+#elif defined(__GNUC__)
+# define XXH_ALIGN(n) __attribute__ ((aligned(n)))
+#elif defined(_MSC_VER)
+# define XXH_ALIGN(n) __declspec(align(n))
+#else
+# define XXH_ALIGN(n) /* disabled */
+#endif
+
+/* Old GCC versions only accept the attribute after the type in structures. */
+#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) /* C11+ */ \
+ && ! (defined(__cplusplus) && (__cplusplus >= 201103L)) /* >= C++11 */ \
+ && defined(__GNUC__)
+# define XXH_ALIGN_MEMBER(align, type) type XXH_ALIGN(align)
+#else
+# define XXH_ALIGN_MEMBER(align, type) XXH_ALIGN(align) type
+#endif
+
+/*!
+ * @brief The size of the internal XXH3 buffer.
+ *
+ * This is the optimal update size for incremental hashing.
+ *
+ * @see XXH3_64b_update(), XXH3_128b_update().
+ */
+#define XXH3_INTERNALBUFFER_SIZE 256
+
+/*!
+ * @brief Default size of the secret buffer (and @ref XXH3_kSecret).
+ *
+ * This is the size used in @ref XXH3_kSecret and the seeded functions.
+ *
+ * Not to be confused with @ref XXH3_SECRET_SIZE_MIN.
+ */
+#define XXH3_SECRET_DEFAULT_SIZE 192
+
+/*!
+ * @internal
+ * @brief Structure for XXH3 streaming API.
+ *
+ * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY,
+ * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined.
+ * Otherwise it is an opaque type.
+ * Never use this definition in combination with dynamic library.
+ * This allows fields to safely be changed in the future.
+ *
+ * @note ** This structure has a strict alignment requirement of 64 bytes!! **
+ * Do not allocate this with `malloc()` or `new`,
+ * it will not be sufficiently aligned.
+ * Use @ref XXH3_createState() and @ref XXH3_freeState(), or stack allocation.
+ *
+ * Typedef'd to @ref XXH3_state_t.
+ * Do never access the members of this struct directly.
+ *
+ * @see XXH3_INITSTATE() for stack initialization.
+ * @see XXH3_createState(), XXH3_freeState().
+ * @see XXH32_state_s, XXH64_state_s
+ */
+struct XXH3_state_s {
+ XXH_ALIGN_MEMBER(64, XXH64_hash_t acc[8]);
+ /*!< The 8 accumulators. See @ref XXH32_state_s::v and @ref XXH64_state_s::v */
+ XXH_ALIGN_MEMBER(64, unsigned char customSecret[XXH3_SECRET_DEFAULT_SIZE]);
+ /*!< Used to store a custom secret generated from a seed. */
+ XXH_ALIGN_MEMBER(64, unsigned char buffer[XXH3_INTERNALBUFFER_SIZE]);
+ /*!< The internal buffer. @see XXH32_state_s::mem32 */
+ XXH32_hash_t bufferedSize;
+ /*!< The amount of memory in @ref buffer, @see XXH32_state_s::memsize */
+ XXH32_hash_t useSeed;
+ /*!< Reserved field. Needed for padding on 64-bit. */
+ size_t nbStripesSoFar;
+ /*!< Number or stripes processed. */
+ XXH64_hash_t totalLen;
+ /*!< Total length hashed. 64-bit even on 32-bit targets. */
+ size_t nbStripesPerBlock;
+ /*!< Number of stripes per block. */
+ size_t secretLimit;
+ /*!< Size of @ref customSecret or @ref extSecret */
+ XXH64_hash_t seed;
+ /*!< Seed for _withSeed variants. Must be zero otherwise, @see XXH3_INITSTATE() */
+ XXH64_hash_t reserved64;
+ /*!< Reserved field. */
+ const unsigned char* extSecret;
+ /*!< Reference to an external secret for the _withSecret variants, NULL
+ * for other variants. */
+ /* note: there may be some padding at the end due to alignment on 64 bytes */
+}; /* typedef'd to XXH3_state_t */
+
+#undef XXH_ALIGN_MEMBER
+
+/*!
+ * @brief Initializes a stack-allocated `XXH3_state_s`.
+ *
+ * When the @ref XXH3_state_t structure is merely emplaced on stack,
+ * it should be initialized with XXH3_INITSTATE() or a memset()
+ * in case its first reset uses XXH3_NNbits_reset_withSeed().
+ * This init can be omitted if the first reset uses default or _withSecret mode.
+ * This operation isn't necessary when the state is created with XXH3_createState().
+ * Note that this doesn't prepare the state for a streaming operation,
+ * it's still necessary to use XXH3_NNbits_reset*() afterwards.
+ */
+#define XXH3_INITSTATE(XXH3_state_ptr) { (XXH3_state_ptr)->seed = 0; }
+
+
+/*!
+ * simple alias to pre-selected XXH3_128bits variant
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128(const void* data, size_t len, XXH64_hash_t seed);
+
+
+/* === Experimental API === */
+/* Symbols defined below must be considered tied to a specific library version. */
+
+/*!
+ * XXH3_generateSecret():
+ *
+ * Derive a high-entropy secret from any user-defined content, named customSeed.
+ * The generated secret can be used in combination with `*_withSecret()` functions.
+ * The `_withSecret()` variants are useful to provide a higher level of protection
+ * than 64-bit seed, as it becomes much more difficult for an external actor to
+ * guess how to impact the calculation logic.
+ *
+ * The function accepts as input a custom seed of any length and any content,
+ * and derives from it a high-entropy secret of length @p secretSize into an
+ * already allocated buffer @p secretBuffer.
+ *
+ * The generated secret can then be used with any `*_withSecret()` variant.
+ * The functions @ref XXH3_128bits_withSecret(), @ref XXH3_64bits_withSecret(),
+ * @ref XXH3_128bits_reset_withSecret() and @ref XXH3_64bits_reset_withSecret()
+ * are part of this list. They all accept a `secret` parameter
+ * which must be large enough for implementation reasons (>= @ref XXH3_SECRET_SIZE_MIN)
+ * _and_ feature very high entropy (consist of random-looking bytes).
+ * These conditions can be a high bar to meet, so @ref XXH3_generateSecret() can
+ * be employed to ensure proper quality.
+ *
+ * @p customSeed can be anything. It can have any size, even small ones,
+ * and its content can be anything, even "poor entropy" sources such as a bunch
+ * of zeroes. The resulting `secret` will nonetheless provide all required qualities.
+ *
+ * @pre
+ * - @p secretSize must be >= @ref XXH3_SECRET_SIZE_MIN
+ * - When @p customSeedSize > 0, supplying NULL as customSeed is undefined behavior.
+ *
+ * Example code:
+ * @code{.c}
+ * #include <stdio.h>
+ * #include <stdlib.h>
+ * #include <string.h>
+ * #define XXH_STATIC_LINKING_ONLY // expose unstable API
+ * #include "xxhash.h"
+ * // Hashes argv[2] using the entropy from argv[1].
+ * int main(int argc, char* argv[])
+ * {
+ * char secret[XXH3_SECRET_SIZE_MIN];
+ * if (argv != 3) { return 1; }
+ * XXH3_generateSecret(secret, sizeof(secret), argv[1], strlen(argv[1]));
+ * XXH64_hash_t h = XXH3_64bits_withSecret(
+ * argv[2], strlen(argv[2]),
+ * secret, sizeof(secret)
+ * );
+ * printf("%016llx\n", (unsigned long long) h);
+ * }
+ * @endcode
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize);
+
+/*!
+ * @brief Generate the same secret as the _withSeed() variants.
+ *
+ * The generated secret can be used in combination with
+ *`*_withSecret()` and `_withSecretandSeed()` variants.
+ *
+ * Example C++ `std::string` hash class:
+ * @code{.cpp}
+ * #include <string>
+ * #define XXH_STATIC_LINKING_ONLY // expose unstable API
+ * #include "xxhash.h"
+ * // Slow, seeds each time
+ * class HashSlow {
+ * XXH64_hash_t seed;
+ * public:
+ * HashSlow(XXH64_hash_t s) : seed{s} {}
+ * size_t operator()(const std::string& x) const {
+ * return size_t{XXH3_64bits_withSeed(x.c_str(), x.length(), seed)};
+ * }
+ * };
+ * // Fast, caches the seeded secret for future uses.
+ * class HashFast {
+ * unsigned char secret[XXH3_SECRET_SIZE_MIN];
+ * public:
+ * HashFast(XXH64_hash_t s) {
+ * XXH3_generateSecret_fromSeed(secret, seed);
+ * }
+ * size_t operator()(const std::string& x) const {
+ * return size_t{
+ * XXH3_64bits_withSecret(x.c_str(), x.length(), secret, sizeof(secret))
+ * };
+ * }
+ * };
+ * @endcode
+ * @param secretBuffer A writable buffer of @ref XXH3_SECRET_SIZE_MIN bytes
+ * @param seed The seed to seed the state.
+ */
+XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed);
+
+/*!
+ * These variants generate hash values using either
+ * @p seed for "short" keys (< XXH3_MIDSIZE_MAX = 240 bytes)
+ * or @p secret for "large" keys (>= XXH3_MIDSIZE_MAX).
+ *
+ * This generally benefits speed, compared to `_withSeed()` or `_withSecret()`.
+ * `_withSeed()` has to generate the secret on the fly for "large" keys.
+ * It's fast, but can be perceptible for "not so large" keys (< 1 KB).
+ * `_withSecret()` has to generate the masks on the fly for "small" keys,
+ * which requires more instructions than _withSeed() variants.
+ * Therefore, _withSecretandSeed variant combines the best of both worlds.
+ *
+ * When @p secret has been generated by XXH3_generateSecret_fromSeed(),
+ * this variant produces *exactly* the same results as `_withSeed()` variant,
+ * hence offering only a pure speed benefit on "large" input,
+ * by skipping the need to regenerate the secret for every large input.
+ *
+ * Another usage scenario is to hash the secret to a 64-bit hash value,
+ * for example with XXH3_64bits(), which then becomes the seed,
+ * and then employ both the seed and the secret in _withSecretandSeed().
+ * On top of speed, an added benefit is that each bit in the secret
+ * has a 50% chance to swap each bit in the output, via its impact to the seed.
+ *
+ * This is not guaranteed when using the secret directly in "small data" scenarios,
+ * because only portions of the secret are employed for small data.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t
+XXH3_64bits_withSecretandSeed(const void* data, size_t len,
+ const void* secret, size_t secretSize,
+ XXH64_hash_t seed);
+/*! @copydoc XXH3_64bits_withSecretandSeed() */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t
+XXH3_128bits_withSecretandSeed(const void* input, size_t length,
+ const void* secret, size_t secretSize,
+ XXH64_hash_t seed64);
+#ifndef XXH_NO_STREAM
+/*! @copydoc XXH3_64bits_withSecretandSeed() */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
+ const void* secret, size_t secretSize,
+ XXH64_hash_t seed64);
+/*! @copydoc XXH3_64bits_withSecretandSeed() */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
+ const void* secret, size_t secretSize,
+ XXH64_hash_t seed64);
+#endif /* !XXH_NO_STREAM */
+
+#endif /* !XXH_NO_XXH3 */
+#endif /* XXH_NO_LONG_LONG */
+#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)
+# define XXH_IMPLEMENTATION
+#endif
+
+#endif /* defined(XXH_STATIC_LINKING_ONLY) && !defined(XXHASH_H_STATIC_13879238742) */
+
+
+/* ======================================================================== */
+/* ======================================================================== */
+/* ======================================================================== */
+
+
+/*-**********************************************************************
+ * xxHash implementation
+ *-**********************************************************************
+ * xxHash's implementation used to be hosted inside xxhash.c.
+ *
+ * However, inlining requires implementation to be visible to the compiler,
+ * hence be included alongside the header.
+ * Previously, implementation was hosted inside xxhash.c,
+ * which was then #included when inlining was activated.
+ * This construction created issues with a few build and install systems,
+ * as it required xxhash.c to be stored in /include directory.
+ *
+ * xxHash implementation is now directly integrated within xxhash.h.
+ * As a consequence, xxhash.c is no longer needed in /include.
+ *
+ * xxhash.c is still available and is still useful.
+ * In a "normal" setup, when xxhash is not inlined,
+ * xxhash.h only exposes the prototypes and public symbols,
+ * while xxhash.c can be built into an object file xxhash.o
+ * which can then be linked into the final binary.
+ ************************************************************************/
+
+#if ( defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) \
+ || defined(XXH_IMPLEMENTATION) ) && !defined(XXH_IMPLEM_13a8737387)
+# define XXH_IMPLEM_13a8737387
+
+/* *************************************
+* Tuning parameters
+***************************************/
+
+/*!
+ * @defgroup tuning Tuning parameters
+ * @{
+ *
+ * Various macros to control xxHash's behavior.
+ */
+#ifdef XXH_DOXYGEN
+/*!
+ * @brief Define this to disable 64-bit code.
+ *
+ * Useful if only using the @ref XXH32_family and you have a strict C90 compiler.
+ */
+# define XXH_NO_LONG_LONG
+# undef XXH_NO_LONG_LONG /* don't actually */
+/*!
+ * @brief Controls how unaligned memory is accessed.
+ *
+ * By default, access to unaligned memory is controlled by `memcpy()`, which is
+ * safe and portable.
+ *
+ * Unfortunately, on some target/compiler combinations, the generated assembly
+ * is sub-optimal.
+ *
+ * The below switch allow selection of a different access method
+ * in the search for improved performance.
+ *
+ * @par Possible options:
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=0` (default): `memcpy`
+ * @par
+ * Use `memcpy()`. Safe and portable. Note that most modern compilers will
+ * eliminate the function call and treat it as an unaligned access.
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=1`: `__attribute__((aligned(1)))`
+ * @par
+ * Depends on compiler extensions and is therefore not portable.
+ * This method is safe _if_ your compiler supports it,
+ * and *generally* as fast or faster than `memcpy`.
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=2`: Direct cast
+ * @par
+ * Casts directly and dereferences. This method doesn't depend on the
+ * compiler, but it violates the C standard as it directly dereferences an
+ * unaligned pointer. It can generate buggy code on targets which do not
+ * support unaligned memory accesses, but in some circumstances, it's the
+ * only known way to get the most performance.
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=3`: Byteshift
+ * @par
+ * Also portable. This can generate the best code on old compilers which don't
+ * inline small `memcpy()` calls, and it might also be faster on big-endian
+ * systems which lack a native byteswap instruction. However, some compilers
+ * will emit literal byteshifts even if the target supports unaligned access.
+ * .
+ *
+ * @warning
+ * Methods 1 and 2 rely on implementation-defined behavior. Use these with
+ * care, as what works on one compiler/platform/optimization level may cause
+ * another to read garbage data or even crash.
+ *
+ * See http://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html for details.
+ *
+ * Prefer these methods in priority order (0 > 3 > 1 > 2)
+ */
+# define XXH_FORCE_MEMORY_ACCESS 0
+
+/*!
+ * @def XXH_SIZE_OPT
+ * @brief Controls how much xxHash optimizes for size.
+ *
+ * xxHash, when compiled, tends to result in a rather large binary size. This
+ * is mostly due to heavy usage to forced inlining and constant folding of the
+ * @ref XXH3_family to increase performance.
+ *
+ * However, some developers prefer size over speed. This option can
+ * significantly reduce the size of the generated code. When using the `-Os`
+ * or `-Oz` options on GCC or Clang, this is defined to 1 by default,
+ * otherwise it is defined to 0.
+ *
+ * Most of these size optimizations can be controlled manually.
+ *
+ * This is a number from 0-2.
+ * - `XXH_SIZE_OPT` == 0: Default. xxHash makes no size optimizations. Speed
+ * comes first.
+ * - `XXH_SIZE_OPT` == 1: Default for `-Os` and `-Oz`. xxHash is more
+ * conservative and disables hacks that increase code size. It implies the
+ * options @ref XXH_NO_INLINE_HINTS == 1, @ref XXH_FORCE_ALIGN_CHECK == 0,
+ * and @ref XXH3_NEON_LANES == 8 if they are not already defined.
+ * - `XXH_SIZE_OPT` == 2: xxHash tries to make itself as small as possible.
+ * Performance may cry. For example, the single shot functions just use the
+ * streaming API.
+ */
+# define XXH_SIZE_OPT 0
+
+/*!
+ * @def XXH_FORCE_ALIGN_CHECK
+ * @brief If defined to non-zero, adds a special path for aligned inputs (XXH32()
+ * and XXH64() only).
+ *
+ * This is an important performance trick for architectures without decent
+ * unaligned memory access performance.
+ *
+ * It checks for input alignment, and when conditions are met, uses a "fast
+ * path" employing direct 32-bit/64-bit reads, resulting in _dramatically
+ * faster_ read speed.
+ *
+ * The check costs one initial branch per hash, which is generally negligible,
+ * but not zero.
+ *
+ * Moreover, it's not useful to generate an additional code path if memory
+ * access uses the same instruction for both aligned and unaligned
+ * addresses (e.g. x86 and aarch64).
+ *
+ * In these cases, the alignment check can be removed by setting this macro to 0.
+ * Then the code will always use unaligned memory access.
+ * Align check is automatically disabled on x86, x64, ARM64, and some ARM chips
+ * which are platforms known to offer good unaligned memory accesses performance.
+ *
+ * It is also disabled by default when @ref XXH_SIZE_OPT >= 1.
+ *
+ * This option does not affect XXH3 (only XXH32 and XXH64).
+ */
+# define XXH_FORCE_ALIGN_CHECK 0
+
+/*!
+ * @def XXH_NO_INLINE_HINTS
+ * @brief When non-zero, sets all functions to `static`.
+ *
+ * By default, xxHash tries to force the compiler to inline almost all internal
+ * functions.
+ *
+ * This can usually improve performance due to reduced jumping and improved
+ * constant folding, but significantly increases the size of the binary which
+ * might not be favorable.
+ *
+ * Additionally, sometimes the forced inlining can be detrimental to performance,
+ * depending on the architecture.
+ *
+ * XXH_NO_INLINE_HINTS marks all internal functions as static, giving the
+ * compiler full control on whether to inline or not.
+ *
+ * When not optimizing (-O0), using `-fno-inline` with GCC or Clang, or if
+ * @ref XXH_SIZE_OPT >= 1, this will automatically be defined.
+ */
+# define XXH_NO_INLINE_HINTS 0
+
+/*!
+ * @def XXH32_ENDJMP
+ * @brief Whether to use a jump for `XXH32_finalize`.
+ *
+ * For performance, `XXH32_finalize` uses multiple branches in the finalizer.
+ * This is generally preferable for performance,
+ * but depending on exact architecture, a jmp may be preferable.
+ *
+ * This setting is only possibly making a difference for very small inputs.
+ */
+# define XXH32_ENDJMP 0
+
+/*!
+ * @internal
+ * @brief Redefines old internal names.
+ *
+ * For compatibility with code that uses xxHash's internals before the names
+ * were changed to improve namespacing. There is no other reason to use this.
+ */
+# define XXH_OLD_NAMES
+# undef XXH_OLD_NAMES /* don't actually use, it is ugly. */
+
+/*!
+ * @def XXH_NO_STREAM
+ * @brief Disables the streaming API.
+ *
+ * When xxHash is not inlined and the streaming functions are not used, disabling
+ * the streaming functions can improve code size significantly, especially with
+ * the @ref XXH3_family which tends to make constant folded copies of itself.
+ */
+# define XXH_NO_STREAM
+# undef XXH_NO_STREAM /* don't actually */
+#endif /* XXH_DOXYGEN */
+/*!
+ * @}
+ */
+
+#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */
+ /* prefer __packed__ structures (method 1) for GCC
+ * < ARMv7 with unaligned access (e.g. Raspbian armhf) still uses byte shifting, so we use memcpy
+ * which for some reason does unaligned loads. */
+# if defined(__GNUC__) && !(defined(__ARM_ARCH) && __ARM_ARCH < 7 && defined(__ARM_FEATURE_UNALIGNED))
+# define XXH_FORCE_MEMORY_ACCESS 1
+# endif
+#endif
+
+#ifndef XXH_SIZE_OPT
+ /* default to 1 for -Os or -Oz */
+# if (defined(__GNUC__) || defined(__clang__)) && defined(__OPTIMIZE_SIZE__)
+# define XXH_SIZE_OPT 1
+# else
+# define XXH_SIZE_OPT 0
+# endif
+#endif
+
+#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */
+ /* don't check on sizeopt, x86, aarch64, or arm when unaligned access is available */
+# if XXH_SIZE_OPT >= 1 || \
+ defined(__i386) || defined(__x86_64__) || defined(__aarch64__) || defined(__ARM_FEATURE_UNALIGNED) \
+ || defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64) || defined(_M_ARM) /* visual */
+# define XXH_FORCE_ALIGN_CHECK 0
+# else
+# define XXH_FORCE_ALIGN_CHECK 1
+# endif
+#endif
+
+#ifndef XXH_NO_INLINE_HINTS
+# if XXH_SIZE_OPT >= 1 || defined(__NO_INLINE__) /* -O0, -fno-inline */
+# define XXH_NO_INLINE_HINTS 1
+# else
+# define XXH_NO_INLINE_HINTS 0
+# endif
+#endif
+
+#ifndef XXH32_ENDJMP
+/* generally preferable for performance */
+# define XXH32_ENDJMP 0
+#endif
+
+/*!
+ * @defgroup impl Implementation
+ * @{
+ */
+
+
+/* *************************************
+* Includes & Memory related functions
+***************************************/
+#if defined(XXH_NO_STREAM)
+/* nothing */
+#elif defined(XXH_NO_STDLIB)
+
+/* When requesting to disable any mention of stdlib,
+ * the library loses the ability to invoked malloc / free.
+ * In practice, it means that functions like `XXH*_createState()`
+ * will always fail, and return NULL.
+ * This flag is useful in situations where
+ * xxhash.h is integrated into some kernel, embedded or limited environment
+ * without access to dynamic allocation.
+ */
+
+static XXH_CONSTF void* XXH_malloc(size_t s) { (void)s; return NULL; }
+static void XXH_free(void* p) { (void)p; }
+
+#else
+
+/*
+ * Modify the local functions below should you wish to use
+ * different memory routines for malloc() and free()
+ */
+#include <stdlib.h>
+
+/*!
+ * @internal
+ * @brief Modify this function to use a different routine than malloc().
+ */
+static XXH_MALLOCF void* XXH_malloc(size_t s) { return malloc(s); }
+
+/*!
+ * @internal
+ * @brief Modify this function to use a different routine than free().
+ */
+static void XXH_free(void* p) { free(p); }
+
+#endif /* XXH_NO_STDLIB */
+
+#include <string.h>
+
+/*!
+ * @internal
+ * @brief Modify this function to use a different routine than memcpy().
+ */
+static void* XXH_memcpy(void* dest, const void* src, size_t size)
+{
+ return memcpy(dest,src,size);
+}
+
+#include <limits.h> /* ULLONG_MAX */
+
+
+/* *************************************
+* Compiler Specific Options
+***************************************/
+#ifdef _MSC_VER /* Visual Studio warning fix */
+# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
+#endif
+
+#if XXH_NO_INLINE_HINTS /* disable inlining hints */
+# if defined(__GNUC__) || defined(__clang__)
+# define XXH_FORCE_INLINE static __attribute__((unused))
+# else
+# define XXH_FORCE_INLINE static
+# endif
+# define XXH_NO_INLINE static
+/* enable inlining hints */
+#elif defined(__GNUC__) || defined(__clang__)
+# define XXH_FORCE_INLINE static __inline__ __attribute__((always_inline, unused))
+# define XXH_NO_INLINE static __attribute__((noinline))
+#elif defined(_MSC_VER) /* Visual Studio */
+# define XXH_FORCE_INLINE static __forceinline
+# define XXH_NO_INLINE static __declspec(noinline)
+#elif defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) /* C99 */
+# define XXH_FORCE_INLINE static inline
+# define XXH_NO_INLINE static
+#else
+# define XXH_FORCE_INLINE static
+# define XXH_NO_INLINE static
+#endif
+
+
+
+/* *************************************
+* Debug
+***************************************/
+/*!
+ * @ingroup tuning
+ * @def XXH_DEBUGLEVEL
+ * @brief Sets the debugging level.
+ *
+ * XXH_DEBUGLEVEL is expected to be defined externally, typically via the
+ * compiler's command line options. The value must be a number.
+ */
+#ifndef XXH_DEBUGLEVEL
+# ifdef DEBUGLEVEL /* backwards compat */
+# define XXH_DEBUGLEVEL DEBUGLEVEL
+# else
+# define XXH_DEBUGLEVEL 0
+# endif
+#endif
+
+#if (XXH_DEBUGLEVEL>=1)
+# include <assert.h> /* note: can still be disabled with NDEBUG */
+# define XXH_ASSERT(c) assert(c)
+#else
+# define XXH_ASSERT(c) ((void)0)
+#endif
+
+/* note: use after variable declarations */
+#ifndef XXH_STATIC_ASSERT
+# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */
+# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { _Static_assert((c),m); } while(0)
+# elif defined(__cplusplus) && (__cplusplus >= 201103L) /* C++11 */
+# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0)
+# else
+# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { struct xxh_sa { char x[(c) ? 1 : -1]; }; } while(0)
+# endif
+# define XXH_STATIC_ASSERT(c) XXH_STATIC_ASSERT_WITH_MESSAGE((c),#c)
+#endif
+
+/*!
+ * @internal
+ * @def XXH_COMPILER_GUARD(var)
+ * @brief Used to prevent unwanted optimizations for @p var.
+ *
+ * It uses an empty GCC inline assembly statement with a register constraint
+ * which forces @p var into a general purpose register (eg eax, ebx, ecx
+ * on x86) and marks it as modified.
+ *
+ * This is used in a few places to avoid unwanted autovectorization (e.g.
+ * XXH32_round()). All vectorization we want is explicit via intrinsics,
+ * and _usually_ isn't wanted elsewhere.
+ *
+ * We also use it to prevent unwanted constant folding for AArch64 in
+ * XXH3_initCustomSecret_scalar().
+ */
+#if defined(__GNUC__) || defined(__clang__)
+# define XXH_COMPILER_GUARD(var) __asm__ __volatile__("" : "+r" (var))
+#else
+# define XXH_COMPILER_GUARD(var) ((void)0)
+#endif
+
+/* *************************************
+* Basic Types
+***************************************/
+#if !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# include <stdint.h>
+ typedef uint8_t xxh_u8;
+#else
+ typedef unsigned char xxh_u8;
+#endif
+typedef XXH32_hash_t xxh_u32;
+
+#ifdef XXH_OLD_NAMES
+# define BYTE xxh_u8
+# define U8 xxh_u8
+# define U32 xxh_u32
+#endif
+
+/* *** Memory access *** */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_read32(const void* ptr)
+ * @brief Reads an unaligned 32-bit integer from @p ptr in native endianness.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ *
+ * @param ptr The pointer to read from.
+ * @return The 32-bit native endian integer from the bytes at @p ptr.
+ */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_readLE32(const void* ptr)
+ * @brief Reads an unaligned 32-bit little endian integer from @p ptr.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ *
+ * @param ptr The pointer to read from.
+ * @return The 32-bit little endian integer from the bytes at @p ptr.
+ */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_readBE32(const void* ptr)
+ * @brief Reads an unaligned 32-bit big endian integer from @p ptr.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ *
+ * @param ptr The pointer to read from.
+ * @return The 32-bit big endian integer from the bytes at @p ptr.
+ */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_readLE32_align(const void* ptr, XXH_alignment align)
+ * @brief Like @ref XXH_readLE32(), but has an option for aligned reads.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ * Note that when @ref XXH_FORCE_ALIGN_CHECK == 0, the @p align parameter is
+ * always @ref XXH_alignment::XXH_unaligned.
+ *
+ * @param ptr The pointer to read from.
+ * @param align Whether @p ptr is aligned.
+ * @pre
+ * If @p align == @ref XXH_alignment::XXH_aligned, @p ptr must be 4 byte
+ * aligned.
+ * @return The 32-bit little endian integer from the bytes at @p ptr.
+ */
+
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
+/*
+ * Manual byteshift. Best for old compilers which don't inline memcpy.
+ * We actually directly use XXH_readLE32 and XXH_readBE32.
+ */
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))
+
+/*
+ * Force direct memory access. Only works on CPU which support unaligned memory
+ * access in hardware.
+ */
+static xxh_u32 XXH_read32(const void* memPtr) { return *(const xxh_u32*) memPtr; }
+
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
+
+/*
+ * __attribute__((aligned(1))) is supported by gcc and clang. Originally the
+ * documentation claimed that it only increased the alignment, but actually it
+ * can decrease it on gcc, clang, and icc:
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502,
+ * https://gcc.godbolt.org/z/xYez1j67Y.
+ */
+#ifdef XXH_OLD_NAMES
+typedef union { xxh_u32 u32; } __attribute__((packed)) unalign;
+#endif
+static xxh_u32 XXH_read32(const void* ptr)
+{
+ typedef __attribute__((aligned(1))) xxh_u32 xxh_unalign32;
+ return *((const xxh_unalign32*)ptr);
+}
+
+#else
+
+/*
+ * Portable and safe solution. Generally efficient.
+ * see: http://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
+ */
+static xxh_u32 XXH_read32(const void* memPtr)
+{
+ xxh_u32 val;
+ XXH_memcpy(&val, memPtr, sizeof(val));
+ return val;
+}
+
+#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */
+
+
+/* *** Endianness *** */
+
+/*!
+ * @ingroup tuning
+ * @def XXH_CPU_LITTLE_ENDIAN
+ * @brief Whether the target is little endian.
+ *
+ * Defined to 1 if the target is little endian, or 0 if it is big endian.
+ * It can be defined externally, for example on the compiler command line.
+ *
+ * If it is not defined,
+ * a runtime check (which is usually constant folded) is used instead.
+ *
+ * @note
+ * This is not necessarily defined to an integer constant.
+ *
+ * @see XXH_isLittleEndian() for the runtime check.
+ */
+#ifndef XXH_CPU_LITTLE_ENDIAN
+/*
+ * Try to detect endianness automatically, to avoid the nonstandard behavior
+ * in `XXH_isLittleEndian()`
+ */
+# if defined(_WIN32) /* Windows is always little endian */ \
+ || defined(__LITTLE_ENDIAN__) \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+# define XXH_CPU_LITTLE_ENDIAN 1
+# elif defined(__BIG_ENDIAN__) \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+# define XXH_CPU_LITTLE_ENDIAN 0
+# else
+/*!
+ * @internal
+ * @brief Runtime check for @ref XXH_CPU_LITTLE_ENDIAN.
+ *
+ * Most compilers will constant fold this.
+ */
+static int XXH_isLittleEndian(void)
+{
+ /*
+ * Portable and well-defined behavior.
+ * Don't use static: it is detrimental to performance.
+ */
+ const union { xxh_u32 u; xxh_u8 c[4]; } one = { 1 };
+ return one.c[0];
+}
+# define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian()
+# endif
+#endif
+
+
+
+
+/* ****************************************
+* Compiler-specific Functions and Macros
+******************************************/
+#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
+
+#ifdef __has_builtin
+# define XXH_HAS_BUILTIN(x) __has_builtin(x)
+#else
+# define XXH_HAS_BUILTIN(x) 0
+#endif
+
+/*!
+ * @internal
+ * @def XXH_rotl32(x,r)
+ * @brief 32-bit rotate left.
+ *
+ * @param x The 32-bit integer to be rotated.
+ * @param r The number of bits to rotate.
+ * @pre
+ * @p r > 0 && @p r < 32
+ * @note
+ * @p x and @p r may be evaluated multiple times.
+ * @return The rotated result.
+ */
+#if !defined(NO_CLANG_BUILTIN) && XXH_HAS_BUILTIN(__builtin_rotateleft32) \
+ && XXH_HAS_BUILTIN(__builtin_rotateleft64)
+# define XXH_rotl32 __builtin_rotateleft32
+# define XXH_rotl64 __builtin_rotateleft64
+/* Note: although _rotl exists for minGW (GCC under windows), performance seems poor */
+#elif defined(_MSC_VER)
+# define XXH_rotl32(x,r) _rotl(x,r)
+# define XXH_rotl64(x,r) _rotl64(x,r)
+#else
+# define XXH_rotl32(x,r) (((x) << (r)) | ((x) >> (32 - (r))))
+# define XXH_rotl64(x,r) (((x) << (r)) | ((x) >> (64 - (r))))
+#endif
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_swap32(xxh_u32 x)
+ * @brief A 32-bit byteswap.
+ *
+ * @param x The 32-bit integer to byteswap.
+ * @return @p x, byteswapped.
+ */
+#if defined(_MSC_VER) /* Visual Studio */
+# define XXH_swap32 _byteswap_ulong
+#elif XXH_GCC_VERSION >= 403
+# define XXH_swap32 __builtin_bswap32
+#else
+static xxh_u32 XXH_swap32 (xxh_u32 x)
+{
+ return ((x << 24) & 0xff000000 ) |
+ ((x << 8) & 0x00ff0000 ) |
+ ((x >> 8) & 0x0000ff00 ) |
+ ((x >> 24) & 0x000000ff );
+}
+#endif
+
+
+/* ***************************
+* Memory reads
+*****************************/
+
+/*!
+ * @internal
+ * @brief Enum to indicate whether a pointer is aligned.
+ */
+typedef enum {
+ XXH_aligned, /*!< Aligned */
+ XXH_unaligned /*!< Possibly unaligned */
+} XXH_alignment;
+
+/*
+ * XXH_FORCE_MEMORY_ACCESS==3 is an endian-independent byteshift load.
+ *
+ * This is ideal for older compilers which don't inline memcpy.
+ */
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
+
+XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* memPtr)
+{
+ const xxh_u8* bytePtr = (const xxh_u8 *)memPtr;
+ return bytePtr[0]
+ | ((xxh_u32)bytePtr[1] << 8)
+ | ((xxh_u32)bytePtr[2] << 16)
+ | ((xxh_u32)bytePtr[3] << 24);
+}
+
+XXH_FORCE_INLINE xxh_u32 XXH_readBE32(const void* memPtr)
+{
+ const xxh_u8* bytePtr = (const xxh_u8 *)memPtr;
+ return bytePtr[3]
+ | ((xxh_u32)bytePtr[2] << 8)
+ | ((xxh_u32)bytePtr[1] << 16)
+ | ((xxh_u32)bytePtr[0] << 24);
+}
+
+#else
+XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr));
+}
+
+static xxh_u32 XXH_readBE32(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr);
+}
+#endif
+
+XXH_FORCE_INLINE xxh_u32
+XXH_readLE32_align(const void* ptr, XXH_alignment align)
+{
+ if (align==XXH_unaligned) {
+ return XXH_readLE32(ptr);
+ } else {
+ return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u32*)ptr : XXH_swap32(*(const xxh_u32*)ptr);
+ }
+}
+
+
+/* *************************************
+* Misc
+***************************************/
+/*! @ingroup public */
+XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; }
+
+
+/* *******************************************************************
+* 32-bit hash functions
+*********************************************************************/
+/*!
+ * @}
+ * @defgroup XXH32_impl XXH32 implementation
+ * @ingroup impl
+ *
+ * Details on the XXH32 implementation.
+ * @{
+ */
+ /* #define instead of static const, to be used as initializers */
+#define XXH_PRIME32_1 0x9E3779B1U /*!< 0b10011110001101110111100110110001 */
+#define XXH_PRIME32_2 0x85EBCA77U /*!< 0b10000101111010111100101001110111 */
+#define XXH_PRIME32_3 0xC2B2AE3DU /*!< 0b11000010101100101010111000111101 */
+#define XXH_PRIME32_4 0x27D4EB2FU /*!< 0b00100111110101001110101100101111 */
+#define XXH_PRIME32_5 0x165667B1U /*!< 0b00010110010101100110011110110001 */
+
+#ifdef XXH_OLD_NAMES
+# define PRIME32_1 XXH_PRIME32_1
+# define PRIME32_2 XXH_PRIME32_2
+# define PRIME32_3 XXH_PRIME32_3
+# define PRIME32_4 XXH_PRIME32_4
+# define PRIME32_5 XXH_PRIME32_5
+#endif
+
+/*!
+ * @internal
+ * @brief Normal stripe processing routine.
+ *
+ * This shuffles the bits so that any bit from @p input impacts several bits in
+ * @p acc.
+ *
+ * @param acc The accumulator lane.
+ * @param input The stripe of input to mix.
+ * @return The mixed accumulator lane.
+ */
+static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input)
+{
+ acc += input * XXH_PRIME32_2;
+ acc = XXH_rotl32(acc, 13);
+ acc *= XXH_PRIME32_1;
+#if (defined(__SSE4_1__) || defined(__aarch64__)) && !defined(XXH_ENABLE_AUTOVECTORIZE)
+ /*
+ * UGLY HACK:
+ * A compiler fence is the only thing that prevents GCC and Clang from
+ * autovectorizing the XXH32 loop (pragmas and attributes don't work for some
+ * reason) without globally disabling SSE4.1.
+ *
+ * The reason we want to avoid vectorization is because despite working on
+ * 4 integers at a time, there are multiple factors slowing XXH32 down on
+ * SSE4:
+ * - There's a ridiculous amount of lag from pmulld (10 cycles of latency on
+ * newer chips!) making it slightly slower to multiply four integers at
+ * once compared to four integers independently. Even when pmulld was
+ * fastest, Sandy/Ivy Bridge, it is still not worth it to go into SSE
+ * just to multiply unless doing a long operation.
+ *
+ * - Four instructions are required to rotate,
+ * movqda tmp, v // not required with VEX encoding
+ * pslld tmp, 13 // tmp <<= 13
+ * psrld v, 19 // x >>= 19
+ * por v, tmp // x |= tmp
+ * compared to one for scalar:
+ * roll v, 13 // reliably fast across the board
+ * shldl v, v, 13 // Sandy Bridge and later prefer this for some reason
+ *
+ * - Instruction level parallelism is actually more beneficial here because
+ * the SIMD actually serializes this operation: While v1 is rotating, v2
+ * can load data, while v3 can multiply. SSE forces them to operate
+ * together.
+ *
+ * This is also enabled on AArch64, as Clang autovectorizes it incorrectly
+ * and it is pointless writing a NEON implementation that is basically the
+ * same speed as scalar for XXH32.
+ */
+ XXH_COMPILER_GUARD(acc);
+#endif
+ return acc;
+}
+
+/*!
+ * @internal
+ * @brief Mixes all bits to finalize the hash.
+ *
+ * The final mix ensures that all input bits have a chance to impact any bit in
+ * the output digest, resulting in an unbiased distribution.
+ *
+ * @param hash The hash to avalanche.
+ * @return The avalanched hash.
+ */
+static xxh_u32 XXH32_avalanche(xxh_u32 hash)
+{
+ hash ^= hash >> 15;
+ hash *= XXH_PRIME32_2;
+ hash ^= hash >> 13;
+ hash *= XXH_PRIME32_3;
+ hash ^= hash >> 16;
+ return hash;
+}
+
+#define XXH_get32bits(p) XXH_readLE32_align(p, align)
+
+/*!
+ * @internal
+ * @brief Processes the last 0-15 bytes of @p ptr.
+ *
+ * There may be up to 15 bytes remaining to consume from the input.
+ * This final stage will digest them to ensure that all input bytes are present
+ * in the final mix.
+ *
+ * @param hash The hash to finalize.
+ * @param ptr The pointer to the remaining input.
+ * @param len The remaining length, modulo 16.
+ * @param align Whether @p ptr is aligned.
+ * @return The finalized hash.
+ * @see XXH64_finalize().
+ */
+static XXH_PUREF xxh_u32
+XXH32_finalize(xxh_u32 hash, const xxh_u8* ptr, size_t len, XXH_alignment align)
+{
+#define XXH_PROCESS1 do { \
+ hash += (*ptr++) * XXH_PRIME32_5; \
+ hash = XXH_rotl32(hash, 11) * XXH_PRIME32_1; \
+} while (0)
+
+#define XXH_PROCESS4 do { \
+ hash += XXH_get32bits(ptr) * XXH_PRIME32_3; \
+ ptr += 4; \
+ hash = XXH_rotl32(hash, 17) * XXH_PRIME32_4; \
+} while (0)
+
+ if (ptr==NULL) XXH_ASSERT(len == 0);
+
+ /* Compact rerolled version; generally faster */
+ if (!XXH32_ENDJMP) {
+ len &= 15;
+ while (len >= 4) {
+ XXH_PROCESS4;
+ len -= 4;
+ }
+ while (len > 0) {
+ XXH_PROCESS1;
+ --len;
+ }
+ return XXH32_avalanche(hash);
+ } else {
+ switch(len&15) /* or switch(bEnd - p) */ {
+ case 12: XXH_PROCESS4;
+ XXH_FALLTHROUGH;
+ case 8: XXH_PROCESS4;
+ XXH_FALLTHROUGH;
+ case 4: XXH_PROCESS4;
+ return XXH32_avalanche(hash);
+
+ case 13: XXH_PROCESS4;
+ XXH_FALLTHROUGH;
+ case 9: XXH_PROCESS4;
+ XXH_FALLTHROUGH;
+ case 5: XXH_PROCESS4;
+ XXH_PROCESS1;
+ return XXH32_avalanche(hash);
+
+ case 14: XXH_PROCESS4;
+ XXH_FALLTHROUGH;
+ case 10: XXH_PROCESS4;
+ XXH_FALLTHROUGH;
+ case 6: XXH_PROCESS4;
+ XXH_PROCESS1;
+ XXH_PROCESS1;
+ return XXH32_avalanche(hash);
+
+ case 15: XXH_PROCESS4;
+ XXH_FALLTHROUGH;
+ case 11: XXH_PROCESS4;
+ XXH_FALLTHROUGH;
+ case 7: XXH_PROCESS4;
+ XXH_FALLTHROUGH;
+ case 3: XXH_PROCESS1;
+ XXH_FALLTHROUGH;
+ case 2: XXH_PROCESS1;
+ XXH_FALLTHROUGH;
+ case 1: XXH_PROCESS1;
+ XXH_FALLTHROUGH;
+ case 0: return XXH32_avalanche(hash);
+ }
+ XXH_ASSERT(0);
+ return hash; /* reaching this point is deemed impossible */
+ }
+}
+
+#ifdef XXH_OLD_NAMES
+# define PROCESS1 XXH_PROCESS1
+# define PROCESS4 XXH_PROCESS4
+#else
+# undef XXH_PROCESS1
+# undef XXH_PROCESS4
+#endif
+
+/*!
+ * @internal
+ * @brief The implementation for @ref XXH32().
+ *
+ * @param input , len , seed Directly passed from @ref XXH32().
+ * @param align Whether @p input is aligned.
+ * @return The calculated hash.
+ */
+XXH_FORCE_INLINE XXH_PUREF xxh_u32
+XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment align)
+{
+ xxh_u32 h32;
+
+ if (input==NULL) XXH_ASSERT(len == 0);
+
+ if (len>=16) {
+ const xxh_u8* const bEnd = input + len;
+ const xxh_u8* const limit = bEnd - 15;
+ xxh_u32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
+ xxh_u32 v2 = seed + XXH_PRIME32_2;
+ xxh_u32 v3 = seed + 0;
+ xxh_u32 v4 = seed - XXH_PRIME32_1;
+
+ do {
+ v1 = XXH32_round(v1, XXH_get32bits(input)); input += 4;
+ v2 = XXH32_round(v2, XXH_get32bits(input)); input += 4;
+ v3 = XXH32_round(v3, XXH_get32bits(input)); input += 4;
+ v4 = XXH32_round(v4, XXH_get32bits(input)); input += 4;
+ } while (input < limit);
+
+ h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7)
+ + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18);
+ } else {
+ h32 = seed + XXH_PRIME32_5;
+ }
+
+ h32 += (xxh_u32)len;
+
+ return XXH32_finalize(h32, input, len&15, align);
+}
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t seed)
+{
+#if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2
+ /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
+ XXH32_state_t state;
+ XXH32_reset(&state, seed);
+ XXH32_update(&state, (const xxh_u8*)input, len);
+ return XXH32_digest(&state);
+#else
+ if (XXH_FORCE_ALIGN_CHECK) {
+ if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */
+ return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_aligned);
+ } }
+
+ return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_unaligned);
+#endif
+}
+
+
+
+/******* Hash streaming *******/
+#ifndef XXH_NO_STREAM
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void)
+{
+ return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t));
+}
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr)
+{
+ XXH_free(statePtr);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState)
+{
+ XXH_memcpy(dstState, srcState, sizeof(*dstState));
+}
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, XXH32_hash_t seed)
+{
+ XXH_ASSERT(statePtr != NULL);
+ memset(statePtr, 0, sizeof(*statePtr));
+ statePtr->v[0] = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
+ statePtr->v[1] = seed + XXH_PRIME32_2;
+ statePtr->v[2] = seed + 0;
+ statePtr->v[3] = seed - XXH_PRIME32_1;
+ return XXH_OK;
+}
+
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH32_update(XXH32_state_t* state, const void* input, size_t len)
+{
+ if (input==NULL) {
+ XXH_ASSERT(len == 0);
+ return XXH_OK;
+ }
+
+ { const xxh_u8* p = (const xxh_u8*)input;
+ const xxh_u8* const bEnd = p + len;
+
+ state->total_len_32 += (XXH32_hash_t)len;
+ state->large_len |= (XXH32_hash_t)((len>=16) | (state->total_len_32>=16));
+
+ if (state->memsize + len < 16) { /* fill in tmp buffer */
+ XXH_memcpy((xxh_u8*)(state->mem32) + state->memsize, input, len);
+ state->memsize += (XXH32_hash_t)len;
+ return XXH_OK;
+ }
+
+ if (state->memsize) { /* some data left from previous update */
+ XXH_memcpy((xxh_u8*)(state->mem32) + state->memsize, input, 16-state->memsize);
+ { const xxh_u32* p32 = state->mem32;
+ state->v[0] = XXH32_round(state->v[0], XXH_readLE32(p32)); p32++;
+ state->v[1] = XXH32_round(state->v[1], XXH_readLE32(p32)); p32++;
+ state->v[2] = XXH32_round(state->v[2], XXH_readLE32(p32)); p32++;
+ state->v[3] = XXH32_round(state->v[3], XXH_readLE32(p32));
+ }
+ p += 16-state->memsize;
+ state->memsize = 0;
+ }
+
+ if (p <= bEnd-16) {
+ const xxh_u8* const limit = bEnd - 16;
+
+ do {
+ state->v[0] = XXH32_round(state->v[0], XXH_readLE32(p)); p+=4;
+ state->v[1] = XXH32_round(state->v[1], XXH_readLE32(p)); p+=4;
+ state->v[2] = XXH32_round(state->v[2], XXH_readLE32(p)); p+=4;
+ state->v[3] = XXH32_round(state->v[3], XXH_readLE32(p)); p+=4;
+ } while (p<=limit);
+
+ }
+
+ if (p < bEnd) {
+ XXH_memcpy(state->mem32, p, (size_t)(bEnd-p));
+ state->memsize = (unsigned)(bEnd-p);
+ }
+ }
+
+ return XXH_OK;
+}
+
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* state)
+{
+ xxh_u32 h32;
+
+ if (state->large_len) {
+ h32 = XXH_rotl32(state->v[0], 1)
+ + XXH_rotl32(state->v[1], 7)
+ + XXH_rotl32(state->v[2], 12)
+ + XXH_rotl32(state->v[3], 18);
+ } else {
+ h32 = state->v[2] /* == seed */ + XXH_PRIME32_5;
+ }
+
+ h32 += state->total_len_32;
+
+ return XXH32_finalize(h32, (const xxh_u8*)state->mem32, state->memsize, XXH_aligned);
+}
+#endif /* !XXH_NO_STREAM */
+
+/******* Canonical representation *******/
+
+/*!
+ * @ingroup XXH32_family
+ * The default return values from XXH functions are unsigned 32 and 64 bit
+ * integers.
+ *
+ * The canonical representation uses big endian convention, the same convention
+ * as human-readable numbers (large digits first).
+ *
+ * This way, hash values can be written into a file or buffer, remaining
+ * comparable across different systems.
+ *
+ * The following functions allow transformation of hash values to and from their
+ * canonical format.
+ */
+XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash)
+{
+ XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t));
+ if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash);
+ XXH_memcpy(dst, &hash, sizeof(*dst));
+}
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src)
+{
+ return XXH_readBE32(src);
+}
+
+
+#ifndef XXH_NO_LONG_LONG
+
+/* *******************************************************************
+* 64-bit hash functions
+*********************************************************************/
+/*!
+ * @}
+ * @ingroup impl
+ * @{
+ */
+/******* Memory access *******/
+
+typedef XXH64_hash_t xxh_u64;
+
+#ifdef XXH_OLD_NAMES
+# define U64 xxh_u64
+#endif
+
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
+/*
+ * Manual byteshift. Best for old compilers which don't inline memcpy.
+ * We actually directly use XXH_readLE64 and XXH_readBE64.
+ */
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))
+
+/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */
+static xxh_u64 XXH_read64(const void* memPtr)
+{
+ return *(const xxh_u64*) memPtr;
+}
+
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
+
+/*
+ * __attribute__((aligned(1))) is supported by gcc and clang. Originally the
+ * documentation claimed that it only increased the alignment, but actually it
+ * can decrease it on gcc, clang, and icc:
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502,
+ * https://gcc.godbolt.org/z/xYez1j67Y.
+ */
+#ifdef XXH_OLD_NAMES
+typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((packed)) unalign64;
+#endif
+static xxh_u64 XXH_read64(const void* ptr)
+{
+ typedef __attribute__((aligned(1))) xxh_u64 xxh_unalign64;
+ return *((const xxh_unalign64*)ptr);
+}
+
+#else
+
+/*
+ * Portable and safe solution. Generally efficient.
+ * see: http://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
+ */
+static xxh_u64 XXH_read64(const void* memPtr)
+{
+ xxh_u64 val;
+ XXH_memcpy(&val, memPtr, sizeof(val));
+ return val;
+}
+
+#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */
+
+#if defined(_MSC_VER) /* Visual Studio */
+# define XXH_swap64 _byteswap_uint64
+#elif XXH_GCC_VERSION >= 403
+# define XXH_swap64 __builtin_bswap64
+#else
+static xxh_u64 XXH_swap64(xxh_u64 x)
+{
+ return ((x << 56) & 0xff00000000000000ULL) |
+ ((x << 40) & 0x00ff000000000000ULL) |
+ ((x << 24) & 0x0000ff0000000000ULL) |
+ ((x << 8) & 0x000000ff00000000ULL) |
+ ((x >> 8) & 0x00000000ff000000ULL) |
+ ((x >> 24) & 0x0000000000ff0000ULL) |
+ ((x >> 40) & 0x000000000000ff00ULL) |
+ ((x >> 56) & 0x00000000000000ffULL);
+}
+#endif
+
+
+/* XXH_FORCE_MEMORY_ACCESS==3 is an endian-independent byteshift load. */
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
+
+XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* memPtr)
+{
+ const xxh_u8* bytePtr = (const xxh_u8 *)memPtr;
+ return bytePtr[0]
+ | ((xxh_u64)bytePtr[1] << 8)
+ | ((xxh_u64)bytePtr[2] << 16)
+ | ((xxh_u64)bytePtr[3] << 24)
+ | ((xxh_u64)bytePtr[4] << 32)
+ | ((xxh_u64)bytePtr[5] << 40)
+ | ((xxh_u64)bytePtr[6] << 48)
+ | ((xxh_u64)bytePtr[7] << 56);
+}
+
+XXH_FORCE_INLINE xxh_u64 XXH_readBE64(const void* memPtr)
+{
+ const xxh_u8* bytePtr = (const xxh_u8 *)memPtr;
+ return bytePtr[7]
+ | ((xxh_u64)bytePtr[6] << 8)
+ | ((xxh_u64)bytePtr[5] << 16)
+ | ((xxh_u64)bytePtr[4] << 24)
+ | ((xxh_u64)bytePtr[3] << 32)
+ | ((xxh_u64)bytePtr[2] << 40)
+ | ((xxh_u64)bytePtr[1] << 48)
+ | ((xxh_u64)bytePtr[0] << 56);
+}
+
+#else
+XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr));
+}
+
+static xxh_u64 XXH_readBE64(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr);
+}
+#endif
+
+XXH_FORCE_INLINE xxh_u64
+XXH_readLE64_align(const void* ptr, XXH_alignment align)
+{
+ if (align==XXH_unaligned)
+ return XXH_readLE64(ptr);
+ else
+ return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u64*)ptr : XXH_swap64(*(const xxh_u64*)ptr);
+}
+
+
+/******* xxh64 *******/
+/*!
+ * @}
+ * @defgroup XXH64_impl XXH64 implementation
+ * @ingroup impl
+ *
+ * Details on the XXH64 implementation.
+ * @{
+ */
+/* #define rather that static const, to be used as initializers */
+#define XXH_PRIME64_1 0x9E3779B185EBCA87ULL /*!< 0b1001111000110111011110011011000110000101111010111100101010000111 */
+#define XXH_PRIME64_2 0xC2B2AE3D27D4EB4FULL /*!< 0b1100001010110010101011100011110100100111110101001110101101001111 */
+#define XXH_PRIME64_3 0x165667B19E3779F9ULL /*!< 0b0001011001010110011001111011000110011110001101110111100111111001 */
+#define XXH_PRIME64_4 0x85EBCA77C2B2AE63ULL /*!< 0b1000010111101011110010100111011111000010101100101010111001100011 */
+#define XXH_PRIME64_5 0x27D4EB2F165667C5ULL /*!< 0b0010011111010100111010110010111100010110010101100110011111000101 */
+
+#ifdef XXH_OLD_NAMES
+# define PRIME64_1 XXH_PRIME64_1
+# define PRIME64_2 XXH_PRIME64_2
+# define PRIME64_3 XXH_PRIME64_3
+# define PRIME64_4 XXH_PRIME64_4
+# define PRIME64_5 XXH_PRIME64_5
+#endif
+
+/*! @copydoc XXH32_round */
+static xxh_u64 XXH64_round(xxh_u64 acc, xxh_u64 input)
+{
+ acc += input * XXH_PRIME64_2;
+ acc = XXH_rotl64(acc, 31);
+ acc *= XXH_PRIME64_1;
+ return acc;
+}
+
+static xxh_u64 XXH64_mergeRound(xxh_u64 acc, xxh_u64 val)
+{
+ val = XXH64_round(0, val);
+ acc ^= val;
+ acc = acc * XXH_PRIME64_1 + XXH_PRIME64_4;
+ return acc;
+}
+
+/*! @copydoc XXH32_avalanche */
+static xxh_u64 XXH64_avalanche(xxh_u64 hash)
+{
+ hash ^= hash >> 33;
+ hash *= XXH_PRIME64_2;
+ hash ^= hash >> 29;
+ hash *= XXH_PRIME64_3;
+ hash ^= hash >> 32;
+ return hash;
+}
+
+
+#define XXH_get64bits(p) XXH_readLE64_align(p, align)
+
+/*!
+ * @internal
+ * @brief Processes the last 0-31 bytes of @p ptr.
+ *
+ * There may be up to 31 bytes remaining to consume from the input.
+ * This final stage will digest them to ensure that all input bytes are present
+ * in the final mix.
+ *
+ * @param hash The hash to finalize.
+ * @param ptr The pointer to the remaining input.
+ * @param len The remaining length, modulo 32.
+ * @param align Whether @p ptr is aligned.
+ * @return The finalized hash
+ * @see XXH32_finalize().
+ */
+static XXH_PUREF xxh_u64
+XXH64_finalize(xxh_u64 hash, const xxh_u8* ptr, size_t len, XXH_alignment align)
+{
+ if (ptr==NULL) XXH_ASSERT(len == 0);
+ len &= 31;
+ while (len >= 8) {
+ xxh_u64 const k1 = XXH64_round(0, XXH_get64bits(ptr));
+ ptr += 8;
+ hash ^= k1;
+ hash = XXH_rotl64(hash,27) * XXH_PRIME64_1 + XXH_PRIME64_4;
+ len -= 8;
+ }
+ if (len >= 4) {
+ hash ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1;
+ ptr += 4;
+ hash = XXH_rotl64(hash, 23) * XXH_PRIME64_2 + XXH_PRIME64_3;
+ len -= 4;
+ }
+ while (len > 0) {
+ hash ^= (*ptr++) * XXH_PRIME64_5;
+ hash = XXH_rotl64(hash, 11) * XXH_PRIME64_1;
+ --len;
+ }
+ return XXH64_avalanche(hash);
+}
+
+#ifdef XXH_OLD_NAMES
+# define PROCESS1_64 XXH_PROCESS1_64
+# define PROCESS4_64 XXH_PROCESS4_64
+# define PROCESS8_64 XXH_PROCESS8_64
+#else
+# undef XXH_PROCESS1_64
+# undef XXH_PROCESS4_64
+# undef XXH_PROCESS8_64
+#endif
+
+/*!
+ * @internal
+ * @brief The implementation for @ref XXH64().
+ *
+ * @param input , len , seed Directly passed from @ref XXH64().
+ * @param align Whether @p input is aligned.
+ * @return The calculated hash.
+ */
+XXH_FORCE_INLINE XXH_PUREF xxh_u64
+XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment align)
+{
+ xxh_u64 h64;
+ if (input==NULL) XXH_ASSERT(len == 0);
+
+ if (len>=32) {
+ const xxh_u8* const bEnd = input + len;
+ const xxh_u8* const limit = bEnd - 31;
+ xxh_u64 v1 = seed + XXH_PRIME64_1 + XXH_PRIME64_2;
+ xxh_u64 v2 = seed + XXH_PRIME64_2;
+ xxh_u64 v3 = seed + 0;
+ xxh_u64 v4 = seed - XXH_PRIME64_1;
+
+ do {
+ v1 = XXH64_round(v1, XXH_get64bits(input)); input+=8;
+ v2 = XXH64_round(v2, XXH_get64bits(input)); input+=8;
+ v3 = XXH64_round(v3, XXH_get64bits(input)); input+=8;
+ v4 = XXH64_round(v4, XXH_get64bits(input)); input+=8;
+ } while (input<limit);
+
+ h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);
+ h64 = XXH64_mergeRound(h64, v1);
+ h64 = XXH64_mergeRound(h64, v2);
+ h64 = XXH64_mergeRound(h64, v3);
+ h64 = XXH64_mergeRound(h64, v4);
+
+ } else {
+ h64 = seed + XXH_PRIME64_5;
+ }
+
+ h64 += (xxh_u64) len;
+
+ return XXH64_finalize(h64, input, len, align);
+}
+
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t len, XXH64_hash_t seed)
+{
+#if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2
+ /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
+ XXH64_state_t state;
+ XXH64_reset(&state, seed);
+ XXH64_update(&state, (const xxh_u8*)input, len);
+ return XXH64_digest(&state);
+#else
+ if (XXH_FORCE_ALIGN_CHECK) {
+ if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */
+ return XXH64_endian_align((const xxh_u8*)input, len, seed, XXH_aligned);
+ } }
+
+ return XXH64_endian_align((const xxh_u8*)input, len, seed, XXH_unaligned);
+
+#endif
+}
+
+/******* Hash Streaming *******/
+#ifndef XXH_NO_STREAM
+/*! @ingroup XXH64_family*/
+XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void)
+{
+ return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t));
+}
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr)
+{
+ XXH_free(statePtr);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState)
+{
+ XXH_memcpy(dstState, srcState, sizeof(*dstState));
+}
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, XXH64_hash_t seed)
+{
+ XXH_ASSERT(statePtr != NULL);
+ memset(statePtr, 0, sizeof(*statePtr));
+ statePtr->v[0] = seed + XXH_PRIME64_1 + XXH_PRIME64_2;
+ statePtr->v[1] = seed + XXH_PRIME64_2;
+ statePtr->v[2] = seed + 0;
+ statePtr->v[3] = seed - XXH_PRIME64_1;
+ return XXH_OK;
+}
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH64_update (XXH64_state_t* state, const void* input, size_t len)
+{
+ if (input==NULL) {
+ XXH_ASSERT(len == 0);
+ return XXH_OK;
+ }
+
+ { const xxh_u8* p = (const xxh_u8*)input;
+ const xxh_u8* const bEnd = p + len;
+
+ state->total_len += len;
+
+ if (state->memsize + len < 32) { /* fill in tmp buffer */
+ XXH_memcpy(((xxh_u8*)state->mem64) + state->memsize, input, len);
+ state->memsize += (xxh_u32)len;
+ return XXH_OK;
+ }
+
+ if (state->memsize) { /* tmp buffer is full */
+ XXH_memcpy(((xxh_u8*)state->mem64) + state->memsize, input, 32-state->memsize);
+ state->v[0] = XXH64_round(state->v[0], XXH_readLE64(state->mem64+0));
+ state->v[1] = XXH64_round(state->v[1], XXH_readLE64(state->mem64+1));
+ state->v[2] = XXH64_round(state->v[2], XXH_readLE64(state->mem64+2));
+ state->v[3] = XXH64_round(state->v[3], XXH_readLE64(state->mem64+3));
+ p += 32 - state->memsize;
+ state->memsize = 0;
+ }
+
+ if (p+32 <= bEnd) {
+ const xxh_u8* const limit = bEnd - 32;
+
+ do {
+ state->v[0] = XXH64_round(state->v[0], XXH_readLE64(p)); p+=8;
+ state->v[1] = XXH64_round(state->v[1], XXH_readLE64(p)); p+=8;
+ state->v[2] = XXH64_round(state->v[2], XXH_readLE64(p)); p+=8;
+ state->v[3] = XXH64_round(state->v[3], XXH_readLE64(p)); p+=8;
+ } while (p<=limit);
+
+ }
+
+ if (p < bEnd) {
+ XXH_memcpy(state->mem64, p, (size_t)(bEnd-p));
+ state->memsize = (unsigned)(bEnd-p);
+ }
+ }
+
+ return XXH_OK;
+}
+
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64_digest(const XXH64_state_t* state)
+{
+ xxh_u64 h64;
+
+ if (state->total_len >= 32) {
+ h64 = XXH_rotl64(state->v[0], 1) + XXH_rotl64(state->v[1], 7) + XXH_rotl64(state->v[2], 12) + XXH_rotl64(state->v[3], 18);
+ h64 = XXH64_mergeRound(h64, state->v[0]);
+ h64 = XXH64_mergeRound(h64, state->v[1]);
+ h64 = XXH64_mergeRound(h64, state->v[2]);
+ h64 = XXH64_mergeRound(h64, state->v[3]);
+ } else {
+ h64 = state->v[2] /*seed*/ + XXH_PRIME64_5;
+ }
+
+ h64 += (xxh_u64) state->total_len;
+
+ return XXH64_finalize(h64, (const xxh_u8*)state->mem64, (size_t)state->total_len, XXH_aligned);
+}
+#endif /* !XXH_NO_STREAM */
+
+/******* Canonical representation *******/
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash)
+{
+ XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t));
+ if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash);
+ XXH_memcpy(dst, &hash, sizeof(*dst));
+}
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src)
+{
+ return XXH_readBE64(src);
+}
+
+#ifndef XXH_NO_XXH3
+
+/* *********************************************************************
+* XXH3
+* New generation hash designed for speed on small keys and vectorization
+************************************************************************ */
+/*!
+ * @}
+ * @defgroup XXH3_impl XXH3 implementation
+ * @ingroup impl
+ * @{
+ */
+
+/* === Compiler specifics === */
+
+#if ((defined(sun) || defined(__sun)) && __cplusplus) /* Solaris includes __STDC_VERSION__ with C++. Tested with GCC 5.5 */
+# define XXH_RESTRICT /* disable */
+#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* >= C99 */
+# define XXH_RESTRICT restrict
+#else
+/* Note: it might be useful to define __restrict or __restrict__ for some C++ compilers */
+# define XXH_RESTRICT /* disable */
+#endif
+
+#if (defined(__GNUC__) && (__GNUC__ >= 3)) \
+ || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) \
+ || defined(__clang__)
+# define XXH_likely(x) __builtin_expect(x, 1)
+# define XXH_unlikely(x) __builtin_expect(x, 0)
+#else
+# define XXH_likely(x) (x)
+# define XXH_unlikely(x) (x)
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+# if defined(__ARM_NEON__) || defined(__ARM_NEON) \
+ || defined(__aarch64__) || defined(_M_ARM) \
+ || defined(_M_ARM64) || defined(_M_ARM64EC)
+# define inline __inline__ /* circumvent a clang bug */
+# include <arm_neon.h>
+# undef inline
+# elif defined(__AVX2__)
+# include <immintrin.h>
+# elif defined(__SSE2__)
+# include <emmintrin.h>
+# endif
+#endif
+
+#if defined(_MSC_VER)
+# include <intrin.h>
+#endif
+
+/*
+ * One goal of XXH3 is to make it fast on both 32-bit and 64-bit, while
+ * remaining a true 64-bit/128-bit hash function.
+ *
+ * This is done by prioritizing a subset of 64-bit operations that can be
+ * emulated without too many steps on the average 32-bit machine.
+ *
+ * For example, these two lines seem similar, and run equally fast on 64-bit:
+ *
+ * xxh_u64 x;
+ * x ^= (x >> 47); // good
+ * x ^= (x >> 13); // bad
+ *
+ * However, to a 32-bit machine, there is a major difference.
+ *
+ * x ^= (x >> 47) looks like this:
+ *
+ * x.lo ^= (x.hi >> (47 - 32));
+ *
+ * while x ^= (x >> 13) looks like this:
+ *
+ * // note: funnel shifts are not usually cheap.
+ * x.lo ^= (x.lo >> 13) | (x.hi << (32 - 13));
+ * x.hi ^= (x.hi >> 13);
+ *
+ * The first one is significantly faster than the second, simply because the
+ * shift is larger than 32. This means:
+ * - All the bits we need are in the upper 32 bits, so we can ignore the lower
+ * 32 bits in the shift.
+ * - The shift result will always fit in the lower 32 bits, and therefore,
+ * we can ignore the upper 32 bits in the xor.
+ *
+ * Thanks to this optimization, XXH3 only requires these features to be efficient:
+ *
+ * - Usable unaligned access
+ * - A 32-bit or 64-bit ALU
+ * - If 32-bit, a decent ADC instruction
+ * - A 32 or 64-bit multiply with a 64-bit result
+ * - For the 128-bit variant, a decent byteswap helps short inputs.
+ *
+ * The first two are already required by XXH32, and almost all 32-bit and 64-bit
+ * platforms which can run XXH32 can run XXH3 efficiently.
+ *
+ * Thumb-1, the classic 16-bit only subset of ARM's instruction set, is one
+ * notable exception.
+ *
+ * First of all, Thumb-1 lacks support for the UMULL instruction which
+ * performs the important long multiply. This means numerous __aeabi_lmul
+ * calls.
+ *
+ * Second of all, the 8 functional registers are just not enough.
+ * Setup for __aeabi_lmul, byteshift loads, pointers, and all arithmetic need
+ * Lo registers, and this shuffling results in thousands more MOVs than A32.
+ *
+ * A32 and T32 don't have this limitation. They can access all 14 registers,
+ * do a 32->64 multiply with UMULL, and the flexible operand allowing free
+ * shifts is helpful, too.
+ *
+ * Therefore, we do a quick sanity check.
+ *
+ * If compiling Thumb-1 for a target which supports ARM instructions, we will
+ * emit a warning, as it is not a "sane" platform to compile for.
+ *
+ * Usually, if this happens, it is because of an accident and you probably need
+ * to specify -march, as you likely meant to compile for a newer architecture.
+ *
+ * Credit: large sections of the vectorial and asm source code paths
+ * have been contributed by @easyaspi314
+ */
+#if defined(__thumb__) && !defined(__thumb2__) && defined(__ARM_ARCH_ISA_ARM)
+# warning "XXH3 is highly inefficient without ARM or Thumb-2."
+#endif
+
+/* ==========================================
+ * Vectorization detection
+ * ========================================== */
+
+#ifdef XXH_DOXYGEN
+/*!
+ * @ingroup tuning
+ * @brief Overrides the vectorization implementation chosen for XXH3.
+ *
+ * Can be defined to 0 to disable SIMD or any of the values mentioned in
+ * @ref XXH_VECTOR_TYPE.
+ *
+ * If this is not defined, it uses predefined macros to determine the best
+ * implementation.
+ */
+# define XXH_VECTOR XXH_SCALAR
+/*!
+ * @ingroup tuning
+ * @brief Possible values for @ref XXH_VECTOR.
+ *
+ * Note that these are actually implemented as macros.
+ *
+ * If this is not defined, it is detected automatically.
+ * @ref XXH_X86DISPATCH overrides this.
+ */
+enum XXH_VECTOR_TYPE /* fake enum */ {
+ XXH_SCALAR = 0, /*!< Portable scalar version */
+ XXH_SSE2 = 1, /*!<
+ * SSE2 for Pentium 4, Opteron, all x86_64.
+ *
+ * @note SSE2 is also guaranteed on Windows 10, macOS, and
+ * Android x86.
+ */
+ XXH_AVX2 = 2, /*!< AVX2 for Haswell and Bulldozer */
+ XXH_AVX512 = 3, /*!< AVX512 for Skylake and Icelake */
+ XXH_NEON = 4, /*!< NEON for most ARMv7-A and all AArch64 */
+ XXH_VSX = 5, /*!< VSX and ZVector for POWER8/z13 (64-bit) */
+};
+/*!
+ * @ingroup tuning
+ * @brief Selects the minimum alignment for XXH3's accumulators.
+ *
+ * When using SIMD, this should match the alignment reqired for said vector
+ * type, so, for example, 32 for AVX2.
+ *
+ * Default: Auto detected.
+ */
+# define XXH_ACC_ALIGN 8
+#endif
+
+/* Actual definition */
+#ifndef XXH_DOXYGEN
+# define XXH_SCALAR 0
+# define XXH_SSE2 1
+# define XXH_AVX2 2
+# define XXH_AVX512 3
+# define XXH_NEON 4
+# define XXH_VSX 5
+#endif
+
+#ifndef XXH_VECTOR /* can be defined on command line */
+# if ( \
+ defined(__ARM_NEON__) || defined(__ARM_NEON) /* gcc */ \
+ || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) /* msvc */ \
+ ) && ( \
+ defined(_WIN32) || defined(__LITTLE_ENDIAN__) /* little endian only */ \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) \
+ )
+# define XXH_VECTOR XXH_NEON
+# elif defined(__AVX512F__)
+# define XXH_VECTOR XXH_AVX512
+# elif defined(__AVX2__)
+# define XXH_VECTOR XXH_AVX2
+# elif defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP == 2))
+# define XXH_VECTOR XXH_SSE2
+# elif (defined(__PPC64__) && defined(__POWER8_VECTOR__)) \
+ || (defined(__s390x__) && defined(__VEC__)) \
+ && defined(__GNUC__) /* TODO: IBM XL */
+# define XXH_VECTOR XXH_VSX
+# else
+# define XXH_VECTOR XXH_SCALAR
+# endif
+#endif
+
+/*
+ * Controls the alignment of the accumulator,
+ * for compatibility with aligned vector loads, which are usually faster.
+ */
+#ifndef XXH_ACC_ALIGN
+# if defined(XXH_X86DISPATCH)
+# define XXH_ACC_ALIGN 64 /* for compatibility with avx512 */
+# elif XXH_VECTOR == XXH_SCALAR /* scalar */
+# define XXH_ACC_ALIGN 8
+# elif XXH_VECTOR == XXH_SSE2 /* sse2 */
+# define XXH_ACC_ALIGN 16
+# elif XXH_VECTOR == XXH_AVX2 /* avx2 */
+# define XXH_ACC_ALIGN 32
+# elif XXH_VECTOR == XXH_NEON /* neon */
+# define XXH_ACC_ALIGN 16
+# elif XXH_VECTOR == XXH_VSX /* vsx */
+# define XXH_ACC_ALIGN 16
+# elif XXH_VECTOR == XXH_AVX512 /* avx512 */
+# define XXH_ACC_ALIGN 64
+# endif
+#endif
+
+#if defined(XXH_X86DISPATCH) || XXH_VECTOR == XXH_SSE2 \
+ || XXH_VECTOR == XXH_AVX2 || XXH_VECTOR == XXH_AVX512
+# define XXH_SEC_ALIGN XXH_ACC_ALIGN
+#else
+# define XXH_SEC_ALIGN 8
+#endif
+
+/*
+ * UGLY HACK:
+ * GCC usually generates the best code with -O3 for xxHash.
+ *
+ * However, when targeting AVX2, it is overzealous in its unrolling resulting
+ * in code roughly 3/4 the speed of Clang.
+ *
+ * There are other issues, such as GCC splitting _mm256_loadu_si256 into
+ * _mm_loadu_si128 + _mm256_inserti128_si256. This is an optimization which
+ * only applies to Sandy and Ivy Bridge... which don't even support AVX2.
+ *
+ * That is why when compiling the AVX2 version, it is recommended to use either
+ * -O2 -mavx2 -march=haswell
+ * or
+ * -O2 -mavx2 -mno-avx256-split-unaligned-load
+ * for decent performance, or to use Clang instead.
+ *
+ * Fortunately, we can control the first one with a pragma that forces GCC into
+ * -O2, but the other one we can't control without "failed to inline always
+ * inline function due to target mismatch" warnings.
+ */
+#if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \
+ && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \
+ && defined(__OPTIMIZE__) && XXH_SIZE_OPT <= 0 /* respect -O0 and -Os */
+# pragma GCC push_options
+# pragma GCC optimize("-O2")
+#endif
+
+
+#if XXH_VECTOR == XXH_NEON
+/*
+ * NEON's setup for vmlal_u32 is a little more complicated than it is on
+ * SSE2, AVX2, and VSX.
+ *
+ * While PMULUDQ and VMULEUW both perform a mask, VMLAL.U32 performs an upcast.
+ *
+ * To do the same operation, the 128-bit 'Q' register needs to be split into
+ * two 64-bit 'D' registers, performing this operation::
+ *
+ * [ a | b ]
+ * | '---------. .--------' |
+ * | x |
+ * | .---------' '--------. |
+ * [ a & 0xFFFFFFFF | b & 0xFFFFFFFF ],[ a >> 32 | b >> 32 ]
+ *
+ * Due to significant changes in aarch64, the fastest method for aarch64 is
+ * completely different than the fastest method for ARMv7-A.
+ *
+ * ARMv7-A treats D registers as unions overlaying Q registers, so modifying
+ * D11 will modify the high half of Q5. This is similar to how modifying AH
+ * will only affect bits 8-15 of AX on x86.
+ *
+ * VZIP takes two registers, and puts even lanes in one register and odd lanes
+ * in the other.
+ *
+ * On ARMv7-A, this strangely modifies both parameters in place instead of
+ * taking the usual 3-operand form.
+ *
+ * Therefore, if we want to do this, we can simply use a D-form VZIP.32 on the
+ * lower and upper halves of the Q register to end up with the high and low
+ * halves where we want - all in one instruction.
+ *
+ * vzip.32 d10, d11 @ d10 = { d10[0], d11[0] }; d11 = { d10[1], d11[1] }
+ *
+ * Unfortunately we need inline assembly for this: Instructions modifying two
+ * registers at once is not possible in GCC or Clang's IR, and they have to
+ * create a copy.
+ *
+ * aarch64 requires a different approach.
+ *
+ * In order to make it easier to write a decent compiler for aarch64, many
+ * quirks were removed, such as conditional execution.
+ *
+ * NEON was also affected by this.
+ *
+ * aarch64 cannot access the high bits of a Q-form register, and writes to a
+ * D-form register zero the high bits, similar to how writes to W-form scalar
+ * registers (or DWORD registers on x86_64) work.
+ *
+ * The formerly free vget_high intrinsics now require a vext (with a few
+ * exceptions)
+ *
+ * Additionally, VZIP was replaced by ZIP1 and ZIP2, which are the equivalent
+ * of PUNPCKL* and PUNPCKH* in SSE, respectively, in order to only modify one
+ * operand.
+ *
+ * The equivalent of the VZIP.32 on the lower and upper halves would be this
+ * mess:
+ *
+ * ext v2.4s, v0.4s, v0.4s, #2 // v2 = { v0[2], v0[3], v0[0], v0[1] }
+ * zip1 v1.2s, v0.2s, v2.2s // v1 = { v0[0], v2[0] }
+ * zip2 v0.2s, v0.2s, v1.2s // v0 = { v0[1], v2[1] }
+ *
+ * Instead, we use a literal downcast, vmovn_u64 (XTN), and vshrn_n_u64 (SHRN):
+ *
+ * shrn v1.2s, v0.2d, #32 // v1 = (uint32x2_t)(v0 >> 32);
+ * xtn v0.2s, v0.2d // v0 = (uint32x2_t)(v0 & 0xFFFFFFFF);
+ *
+ * This is available on ARMv7-A, but is less efficient than a single VZIP.32.
+ */
+
+/*!
+ * Function-like macro:
+ * void XXH_SPLIT_IN_PLACE(uint64x2_t &in, uint32x2_t &outLo, uint32x2_t &outHi)
+ * {
+ * outLo = (uint32x2_t)(in & 0xFFFFFFFF);
+ * outHi = (uint32x2_t)(in >> 32);
+ * in = UNDEFINED;
+ * }
+ */
+# if !defined(XXH_NO_VZIP_HACK) /* define to disable */ \
+ && (defined(__GNUC__) || defined(__clang__)) \
+ && (defined(__arm__) || defined(__thumb__) || defined(_M_ARM))
+# define XXH_SPLIT_IN_PLACE(in, outLo, outHi) \
+ do { \
+ /* Undocumented GCC/Clang operand modifier: %e0 = lower D half, %f0 = upper D half */ \
+ /* https://github.com/gcc-mirror/gcc/blob/38cf91e5/gcc/config/arm/arm.c#L22486 */ \
+ /* https://github.com/llvm-mirror/llvm/blob/2c4ca683/lib/Target/ARM/ARMAsmPrinter.cpp#L399 */ \
+ __asm__("vzip.32 %e0, %f0" : "+w" (in)); \
+ (outLo) = vget_low_u32 (vreinterpretq_u32_u64(in)); \
+ (outHi) = vget_high_u32(vreinterpretq_u32_u64(in)); \
+ } while (0)
+# else
+# define XXH_SPLIT_IN_PLACE(in, outLo, outHi) \
+ do { \
+ (outLo) = vmovn_u64 (in); \
+ (outHi) = vshrn_n_u64 ((in), 32); \
+ } while (0)
+# endif
+
+/*!
+ * @internal
+ * @brief `vld1q_u64` but faster and alignment-safe.
+ *
+ * On AArch64, unaligned access is always safe, but on ARMv7-a, it is only
+ * *conditionally* safe (`vld1` has an alignment bit like `movdq[ua]` in x86).
+ *
+ * GCC for AArch64 sees `vld1q_u8` as an intrinsic instead of a load, so it
+ * prohibits load-store optimizations. Therefore, a direct dereference is used.
+ *
+ * Otherwise, `vld1q_u8` is used with `vreinterpretq_u8_u64` to do a safe
+ * unaligned load.
+ */
+#if defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)
+XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr) /* silence -Wcast-align */
+{
+ return *(uint64x2_t const*)ptr;
+}
+#else
+XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr)
+{
+ return vreinterpretq_u64_u8(vld1q_u8((uint8_t const*)ptr));
+}
+#endif
+/*!
+ * @ingroup tuning
+ * @brief Controls the NEON to scalar ratio for XXH3
+ *
+ * On AArch64 when not optimizing for size, XXH3 will run 6 lanes using NEON and
+ * 2 lanes on scalar by default.
+ *
+ * This can be set to 2, 4, 6, or 8. ARMv7 will default to all 8 NEON lanes, as the
+ * emulated 64-bit arithmetic is too slow.
+ *
+ * Modern ARM CPUs are _very_ sensitive to how their pipelines are used.
+ *
+ * For example, the Cortex-A73 can dispatch 3 micro-ops per cycle, but it can't
+ * have more than 2 NEON (F0/F1) micro-ops. If you are only using NEON instructions,
+ * you are only using 2/3 of the CPU bandwidth.
+ *
+ * This is even more noticable on the more advanced cores like the A76 which
+ * can dispatch 8 micro-ops per cycle, but still only 2 NEON micro-ops at once.
+ *
+ * Therefore, @ref XXH3_NEON_LANES lanes will be processed using NEON, and the
+ * remaining lanes will use scalar instructions. This improves the bandwidth
+ * and also gives the integer pipelines something to do besides twiddling loop
+ * counters and pointers.
+ *
+ * This change benefits CPUs with large micro-op buffers without negatively affecting
+ * other CPUs:
+ *
+ * | Chipset | Dispatch type | NEON only | 6:2 hybrid | Diff. |
+ * |:----------------------|:--------------------|----------:|-----------:|------:|
+ * | Snapdragon 730 (A76) | 2 NEON/8 micro-ops | 8.8 GB/s | 10.1 GB/s | ~16% |
+ * | Snapdragon 835 (A73) | 2 NEON/3 micro-ops | 5.1 GB/s | 5.3 GB/s | ~5% |
+ * | Marvell PXA1928 (A53) | In-order dual-issue | 1.9 GB/s | 1.9 GB/s | 0% |
+ *
+ * It also seems to fix some bad codegen on GCC, making it almost as fast as clang.
+ *
+ * @see XXH3_accumulate_512_neon()
+ */
+# ifndef XXH3_NEON_LANES
+# if (defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) \
+ && XXH_SIZE_OPT <= 0
+# define XXH3_NEON_LANES 6
+# else
+# define XXH3_NEON_LANES XXH_ACC_NB
+# endif
+# endif
+#endif /* XXH_VECTOR == XXH_NEON */
+
+/*
+ * VSX and Z Vector helpers.
+ *
+ * This is very messy, and any pull requests to clean this up are welcome.
+ *
+ * There are a lot of problems with supporting VSX and s390x, due to
+ * inconsistent intrinsics, spotty coverage, and multiple endiannesses.
+ */
+#if XXH_VECTOR == XXH_VSX
+/* Annoyingly, these headers _may_ define three macros: `bool`, `vector`,
+ * and `pixel`. This is a problem for obvious reasons.
+ *
+ * These keywords are unnecessary; the spec literally says they are
+ * equivalent to `__bool`, `__vector`, and `__pixel` and may be undef'd
+ * after including the header.
+ *
+ * We use pragma push_macro/pop_macro to keep the namespace clean. */
+# pragma push_macro("bool")
+# pragma push_macro("vector")
+# pragma push_macro("pixel")
+/* silence potential macro redefined warnings */
+# undef bool
+# undef vector
+# undef pixel
+
+# if defined(__s390x__)
+# include <s390intrin.h>
+# else
+# include <altivec.h>
+# endif
+
+/* Restore the original macro values, if applicable. */
+# pragma pop_macro("pixel")
+# pragma pop_macro("vector")
+# pragma pop_macro("bool")
+
+typedef __vector unsigned long long xxh_u64x2;
+typedef __vector unsigned char xxh_u8x16;
+typedef __vector unsigned xxh_u32x4;
+
+# ifndef XXH_VSX_BE
+# if defined(__BIG_ENDIAN__) \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+# define XXH_VSX_BE 1
+# elif defined(__VEC_ELEMENT_REG_ORDER__) && __VEC_ELEMENT_REG_ORDER__ == __ORDER_BIG_ENDIAN__
+# warning "-maltivec=be is not recommended. Please use native endianness."
+# define XXH_VSX_BE 1
+# else
+# define XXH_VSX_BE 0
+# endif
+# endif /* !defined(XXH_VSX_BE) */
+
+# if XXH_VSX_BE
+# if defined(__POWER9_VECTOR__) || (defined(__clang__) && defined(__s390x__))
+# define XXH_vec_revb vec_revb
+# else
+/*!
+ * A polyfill for POWER9's vec_revb().
+ */
+XXH_FORCE_INLINE xxh_u64x2 XXH_vec_revb(xxh_u64x2 val)
+{
+ xxh_u8x16 const vByteSwap = { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
+ 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08 };
+ return vec_perm(val, val, vByteSwap);
+}
+# endif
+# endif /* XXH_VSX_BE */
+
+/*!
+ * Performs an unaligned vector load and byte swaps it on big endian.
+ */
+XXH_FORCE_INLINE xxh_u64x2 XXH_vec_loadu(const void *ptr)
+{
+ xxh_u64x2 ret;
+ XXH_memcpy(&ret, ptr, sizeof(xxh_u64x2));
+# if XXH_VSX_BE
+ ret = XXH_vec_revb(ret);
+# endif
+ return ret;
+}
+
+/*
+ * vec_mulo and vec_mule are very problematic intrinsics on PowerPC
+ *
+ * These intrinsics weren't added until GCC 8, despite existing for a while,
+ * and they are endian dependent. Also, their meaning swap depending on version.
+ * */
+# if defined(__s390x__)
+ /* s390x is always big endian, no issue on this platform */
+# define XXH_vec_mulo vec_mulo
+# define XXH_vec_mule vec_mule
+# elif defined(__clang__) && XXH_HAS_BUILTIN(__builtin_altivec_vmuleuw) && !defined(__ibmxl__)
+/* Clang has a better way to control this, we can just use the builtin which doesn't swap. */
+ /* The IBM XL Compiler (which defined __clang__) only implements the vec_* operations */
+# define XXH_vec_mulo __builtin_altivec_vmulouw
+# define XXH_vec_mule __builtin_altivec_vmuleuw
+# else
+/* gcc needs inline assembly */
+/* Adapted from https://github.com/google/highwayhash/blob/master/highwayhash/hh_vsx.h. */
+XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mulo(xxh_u32x4 a, xxh_u32x4 b)
+{
+ xxh_u64x2 result;
+ __asm__("vmulouw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b));
+ return result;
+}
+XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mule(xxh_u32x4 a, xxh_u32x4 b)
+{
+ xxh_u64x2 result;
+ __asm__("vmuleuw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b));
+ return result;
+}
+# endif /* XXH_vec_mulo, XXH_vec_mule */
+#endif /* XXH_VECTOR == XXH_VSX */
+
+
+/* prefetch
+ * can be disabled, by declaring XXH_NO_PREFETCH build macro */
+#if defined(XXH_NO_PREFETCH)
+# define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */
+#else
+# if XXH_SIZE_OPT >= 1
+# define XXH_PREFETCH(ptr) (void)(ptr)
+# elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) /* _mm_prefetch() not defined outside of x86/x64 */
+# include <mmintrin.h> /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */
+# define XXH_PREFETCH(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0)
+# elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) )
+# define XXH_PREFETCH(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 3 /* locality */)
+# else
+# define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */
+# endif
+#endif /* XXH_NO_PREFETCH */
+
+
+/* ==========================================
+ * XXH3 default settings
+ * ========================================== */
+
+#define XXH_SECRET_DEFAULT_SIZE 192 /* minimum XXH3_SECRET_SIZE_MIN */
+
+#if (XXH_SECRET_DEFAULT_SIZE < XXH3_SECRET_SIZE_MIN)
+# error "default keyset is not large enough"
+#endif
+
+/*! Pseudorandom secret taken directly from FARSH. */
+XXH_ALIGN(64) static const xxh_u8 XXH3_kSecret[XXH_SECRET_DEFAULT_SIZE] = {
+ 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
+ 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
+ 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
+ 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
+ 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
+ 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
+ 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
+ 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
+ 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
+ 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
+ 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
+ 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
+};
+
+
+#ifdef XXH_OLD_NAMES
+# define kSecret XXH3_kSecret
+#endif
+
+#ifdef XXH_DOXYGEN
+/*!
+ * @brief Calculates a 32-bit to 64-bit long multiply.
+ *
+ * Implemented as a macro.
+ *
+ * Wraps `__emulu` on MSVC x86 because it tends to call `__allmul` when it doesn't
+ * need to (but it shouldn't need to anyways, it is about 7 instructions to do
+ * a 64x64 multiply...). Since we know that this will _always_ emit `MULL`, we
+ * use that instead of the normal method.
+ *
+ * If you are compiling for platforms like Thumb-1 and don't have a better option,
+ * you may also want to write your own long multiply routine here.
+ *
+ * @param x, y Numbers to be multiplied
+ * @return 64-bit product of the low 32 bits of @p x and @p y.
+ */
+XXH_FORCE_INLINE xxh_u64
+XXH_mult32to64(xxh_u64 x, xxh_u64 y)
+{
+ return (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF);
+}
+#elif defined(_MSC_VER) && defined(_M_IX86)
+# define XXH_mult32to64(x, y) __emulu((unsigned)(x), (unsigned)(y))
+#else
+/*
+ * Downcast + upcast is usually better than masking on older compilers like
+ * GCC 4.2 (especially 32-bit ones), all without affecting newer compilers.
+ *
+ * The other method, (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF), will AND both operands
+ * and perform a full 64x64 multiply -- entirely redundant on 32-bit.
+ */
+# define XXH_mult32to64(x, y) ((xxh_u64)(xxh_u32)(x) * (xxh_u64)(xxh_u32)(y))
+#endif
+
+/*!
+ * @brief Calculates a 64->128-bit long multiply.
+ *
+ * Uses `__uint128_t` and `_umul128` if available, otherwise uses a scalar
+ * version.
+ *
+ * @param lhs , rhs The 64-bit integers to be multiplied
+ * @return The 128-bit result represented in an @ref XXH128_hash_t.
+ */
+static XXH128_hash_t
+XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs)
+{
+ /*
+ * GCC/Clang __uint128_t method.
+ *
+ * On most 64-bit targets, GCC and Clang define a __uint128_t type.
+ * This is usually the best way as it usually uses a native long 64-bit
+ * multiply, such as MULQ on x86_64 or MUL + UMULH on aarch64.
+ *
+ * Usually.
+ *
+ * Despite being a 32-bit platform, Clang (and emscripten) define this type
+ * despite not having the arithmetic for it. This results in a laggy
+ * compiler builtin call which calculates a full 128-bit multiply.
+ * In that case it is best to use the portable one.
+ * https://github.com/Cyan4973/xxHash/issues/211#issuecomment-515575677
+ */
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(__wasm__) \
+ && defined(__SIZEOF_INT128__) \
+ || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128)
+
+ __uint128_t const product = (__uint128_t)lhs * (__uint128_t)rhs;
+ XXH128_hash_t r128;
+ r128.low64 = (xxh_u64)(product);
+ r128.high64 = (xxh_u64)(product >> 64);
+ return r128;
+
+ /*
+ * MSVC for x64's _umul128 method.
+ *
+ * xxh_u64 _umul128(xxh_u64 Multiplier, xxh_u64 Multiplicand, xxh_u64 *HighProduct);
+ *
+ * This compiles to single operand MUL on x64.
+ */
+#elif (defined(_M_X64) || defined(_M_IA64)) && !defined(_M_ARM64EC)
+
+#ifndef _MSC_VER
+# pragma intrinsic(_umul128)
+#endif
+ xxh_u64 product_high;
+ xxh_u64 const product_low = _umul128(lhs, rhs, &product_high);
+ XXH128_hash_t r128;
+ r128.low64 = product_low;
+ r128.high64 = product_high;
+ return r128;
+
+ /*
+ * MSVC for ARM64's __umulh method.
+ *
+ * This compiles to the same MUL + UMULH as GCC/Clang's __uint128_t method.
+ */
+#elif defined(_M_ARM64) || defined(_M_ARM64EC)
+
+#ifndef _MSC_VER
+# pragma intrinsic(__umulh)
+#endif
+ XXH128_hash_t r128;
+ r128.low64 = lhs * rhs;
+ r128.high64 = __umulh(lhs, rhs);
+ return r128;
+
+#else
+ /*
+ * Portable scalar method. Optimized for 32-bit and 64-bit ALUs.
+ *
+ * This is a fast and simple grade school multiply, which is shown below
+ * with base 10 arithmetic instead of base 0x100000000.
+ *
+ * 9 3 // D2 lhs = 93
+ * x 7 5 // D2 rhs = 75
+ * ----------
+ * 1 5 // D2 lo_lo = (93 % 10) * (75 % 10) = 15
+ * 4 5 | // D2 hi_lo = (93 / 10) * (75 % 10) = 45
+ * 2 1 | // D2 lo_hi = (93 % 10) * (75 / 10) = 21
+ * + 6 3 | | // D2 hi_hi = (93 / 10) * (75 / 10) = 63
+ * ---------
+ * 2 7 | // D2 cross = (15 / 10) + (45 % 10) + 21 = 27
+ * + 6 7 | | // D2 upper = (27 / 10) + (45 / 10) + 63 = 67
+ * ---------
+ * 6 9 7 5 // D4 res = (27 * 10) + (15 % 10) + (67 * 100) = 6975
+ *
+ * The reasons for adding the products like this are:
+ * 1. It avoids manual carry tracking. Just like how
+ * (9 * 9) + 9 + 9 = 99, the same applies with this for UINT64_MAX.
+ * This avoids a lot of complexity.
+ *
+ * 2. It hints for, and on Clang, compiles to, the powerful UMAAL
+ * instruction available in ARM's Digital Signal Processing extension
+ * in 32-bit ARMv6 and later, which is shown below:
+ *
+ * void UMAAL(xxh_u32 *RdLo, xxh_u32 *RdHi, xxh_u32 Rn, xxh_u32 Rm)
+ * {
+ * xxh_u64 product = (xxh_u64)*RdLo * (xxh_u64)*RdHi + Rn + Rm;
+ * *RdLo = (xxh_u32)(product & 0xFFFFFFFF);
+ * *RdHi = (xxh_u32)(product >> 32);
+ * }
+ *
+ * This instruction was designed for efficient long multiplication, and
+ * allows this to be calculated in only 4 instructions at speeds
+ * comparable to some 64-bit ALUs.
+ *
+ * 3. It isn't terrible on other platforms. Usually this will be a couple
+ * of 32-bit ADD/ADCs.
+ */
+
+ /* First calculate all of the cross products. */
+ xxh_u64 const lo_lo = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs & 0xFFFFFFFF);
+ xxh_u64 const hi_lo = XXH_mult32to64(lhs >> 32, rhs & 0xFFFFFFFF);
+ xxh_u64 const lo_hi = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs >> 32);
+ xxh_u64 const hi_hi = XXH_mult32to64(lhs >> 32, rhs >> 32);
+
+ /* Now add the products together. These will never overflow. */
+ xxh_u64 const cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi;
+ xxh_u64 const upper = (hi_lo >> 32) + (cross >> 32) + hi_hi;
+ xxh_u64 const lower = (cross << 32) | (lo_lo & 0xFFFFFFFF);
+
+ XXH128_hash_t r128;
+ r128.low64 = lower;
+ r128.high64 = upper;
+ return r128;
+#endif
+}
+
+/*!
+ * @brief Calculates a 64-bit to 128-bit multiply, then XOR folds it.
+ *
+ * The reason for the separate function is to prevent passing too many structs
+ * around by value. This will hopefully inline the multiply, but we don't force it.
+ *
+ * @param lhs , rhs The 64-bit integers to multiply
+ * @return The low 64 bits of the product XOR'd by the high 64 bits.
+ * @see XXH_mult64to128()
+ */
+static xxh_u64
+XXH3_mul128_fold64(xxh_u64 lhs, xxh_u64 rhs)
+{
+ XXH128_hash_t product = XXH_mult64to128(lhs, rhs);
+ return product.low64 ^ product.high64;
+}
+
+/*! Seems to produce slightly better code on GCC for some reason. */
+XXH_FORCE_INLINE XXH_CONSTF xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift)
+{
+ XXH_ASSERT(0 <= shift && shift < 64);
+ return v64 ^ (v64 >> shift);
+}
+
+/*
+ * This is a fast avalanche stage,
+ * suitable when input bits are already partially mixed
+ */
+static XXH64_hash_t XXH3_avalanche(xxh_u64 h64)
+{
+ h64 = XXH_xorshift64(h64, 37);
+ h64 *= 0x165667919E3779F9ULL;
+ h64 = XXH_xorshift64(h64, 32);
+ return h64;
+}
+
+/*
+ * This is a stronger avalanche,
+ * inspired by Pelle Evensen's rrmxmx
+ * preferable when input has not been previously mixed
+ */
+static XXH64_hash_t XXH3_rrmxmx(xxh_u64 h64, xxh_u64 len)
+{
+ /* this mix is inspired by Pelle Evensen's rrmxmx */
+ h64 ^= XXH_rotl64(h64, 49) ^ XXH_rotl64(h64, 24);
+ h64 *= 0x9FB21C651E98DF25ULL;
+ h64 ^= (h64 >> 35) + len ;
+ h64 *= 0x9FB21C651E98DF25ULL;
+ return XXH_xorshift64(h64, 28);
+}
+
+
+/* ==========================================
+ * Short keys
+ * ==========================================
+ * One of the shortcomings of XXH32 and XXH64 was that their performance was
+ * sub-optimal on short lengths. It used an iterative algorithm which strongly
+ * favored lengths that were a multiple of 4 or 8.
+ *
+ * Instead of iterating over individual inputs, we use a set of single shot
+ * functions which piece together a range of lengths and operate in constant time.
+ *
+ * Additionally, the number of multiplies has been significantly reduced. This
+ * reduces latency, especially when emulating 64-bit multiplies on 32-bit.
+ *
+ * Depending on the platform, this may or may not be faster than XXH32, but it
+ * is almost guaranteed to be faster than XXH64.
+ */
+
+/*
+ * At very short lengths, there isn't enough input to fully hide secrets, or use
+ * the entire secret.
+ *
+ * There is also only a limited amount of mixing we can do before significantly
+ * impacting performance.
+ *
+ * Therefore, we use different sections of the secret and always mix two secret
+ * samples with an XOR. This should have no effect on performance on the
+ * seedless or withSeed variants because everything _should_ be constant folded
+ * by modern compilers.
+ *
+ * The XOR mixing hides individual parts of the secret and increases entropy.
+ *
+ * This adds an extra layer of strength for custom secrets.
+ */
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_1to3_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(1 <= len && len <= 3);
+ XXH_ASSERT(secret != NULL);
+ /*
+ * len = 1: combined = { input[0], 0x01, input[0], input[0] }
+ * len = 2: combined = { input[1], 0x02, input[0], input[1] }
+ * len = 3: combined = { input[2], 0x03, input[0], input[1] }
+ */
+ { xxh_u8 const c1 = input[0];
+ xxh_u8 const c2 = input[len >> 1];
+ xxh_u8 const c3 = input[len - 1];
+ xxh_u32 const combined = ((xxh_u32)c1 << 16) | ((xxh_u32)c2 << 24)
+ | ((xxh_u32)c3 << 0) | ((xxh_u32)len << 8);
+ xxh_u64 const bitflip = (XXH_readLE32(secret) ^ XXH_readLE32(secret+4)) + seed;
+ xxh_u64 const keyed = (xxh_u64)combined ^ bitflip;
+ return XXH64_avalanche(keyed);
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_4to8_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(4 <= len && len <= 8);
+ seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32;
+ { xxh_u32 const input1 = XXH_readLE32(input);
+ xxh_u32 const input2 = XXH_readLE32(input + len - 4);
+ xxh_u64 const bitflip = (XXH_readLE64(secret+8) ^ XXH_readLE64(secret+16)) - seed;
+ xxh_u64 const input64 = input2 + (((xxh_u64)input1) << 32);
+ xxh_u64 const keyed = input64 ^ bitflip;
+ return XXH3_rrmxmx(keyed, len);
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_9to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(9 <= len && len <= 16);
+ { xxh_u64 const bitflip1 = (XXH_readLE64(secret+24) ^ XXH_readLE64(secret+32)) + seed;
+ xxh_u64 const bitflip2 = (XXH_readLE64(secret+40) ^ XXH_readLE64(secret+48)) - seed;
+ xxh_u64 const input_lo = XXH_readLE64(input) ^ bitflip1;
+ xxh_u64 const input_hi = XXH_readLE64(input + len - 8) ^ bitflip2;
+ xxh_u64 const acc = len
+ + XXH_swap64(input_lo) + input_hi
+ + XXH3_mul128_fold64(input_lo, input_hi);
+ return XXH3_avalanche(acc);
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_0to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(len <= 16);
+ { if (XXH_likely(len > 8)) return XXH3_len_9to16_64b(input, len, secret, seed);
+ if (XXH_likely(len >= 4)) return XXH3_len_4to8_64b(input, len, secret, seed);
+ if (len) return XXH3_len_1to3_64b(input, len, secret, seed);
+ return XXH64_avalanche(seed ^ (XXH_readLE64(secret+56) ^ XXH_readLE64(secret+64)));
+ }
+}
+
+/*
+ * DISCLAIMER: There are known *seed-dependent* multicollisions here due to
+ * multiplication by zero, affecting hashes of lengths 17 to 240.
+ *
+ * However, they are very unlikely.
+ *
+ * Keep this in mind when using the unseeded XXH3_64bits() variant: As with all
+ * unseeded non-cryptographic hashes, it does not attempt to defend itself
+ * against specially crafted inputs, only random inputs.
+ *
+ * Compared to classic UMAC where a 1 in 2^31 chance of 4 consecutive bytes
+ * cancelling out the secret is taken an arbitrary number of times (addressed
+ * in XXH3_accumulate_512), this collision is very unlikely with random inputs
+ * and/or proper seeding:
+ *
+ * This only has a 1 in 2^63 chance of 8 consecutive bytes cancelling out, in a
+ * function that is only called up to 16 times per hash with up to 240 bytes of
+ * input.
+ *
+ * This is not too bad for a non-cryptographic hash function, especially with
+ * only 64 bit outputs.
+ *
+ * The 128-bit variant (which trades some speed for strength) is NOT affected
+ * by this, although it is always a good idea to use a proper seed if you care
+ * about strength.
+ */
+XXH_FORCE_INLINE xxh_u64 XXH3_mix16B(const xxh_u8* XXH_RESTRICT input,
+ const xxh_u8* XXH_RESTRICT secret, xxh_u64 seed64)
+{
+#if defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \
+ && defined(__i386__) && defined(__SSE2__) /* x86 + SSE2 */ \
+ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable like XXH32 hack */
+ /*
+ * UGLY HACK:
+ * GCC for x86 tends to autovectorize the 128-bit multiply, resulting in
+ * slower code.
+ *
+ * By forcing seed64 into a register, we disrupt the cost model and
+ * cause it to scalarize. See `XXH32_round()`
+ *
+ * FIXME: Clang's output is still _much_ faster -- On an AMD Ryzen 3600,
+ * XXH3_64bits @ len=240 runs at 4.6 GB/s with Clang 9, but 3.3 GB/s on
+ * GCC 9.2, despite both emitting scalar code.
+ *
+ * GCC generates much better scalar code than Clang for the rest of XXH3,
+ * which is why finding a more optimal codepath is an interest.
+ */
+ XXH_COMPILER_GUARD(seed64);
+#endif
+ { xxh_u64 const input_lo = XXH_readLE64(input);
+ xxh_u64 const input_hi = XXH_readLE64(input+8);
+ return XXH3_mul128_fold64(
+ input_lo ^ (XXH_readLE64(secret) + seed64),
+ input_hi ^ (XXH_readLE64(secret+8) - seed64)
+ );
+ }
+}
+
+/* For mid range keys, XXH3 uses a Mum-hash variant. */
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(16 < len && len <= 128);
+
+ { xxh_u64 acc = len * XXH_PRIME64_1;
+#if XXH_SIZE_OPT >= 1
+ /* Smaller and cleaner, but slightly slower. */
+ size_t i = (len - 1) / 32;
+ do {
+ acc += XXH3_mix16B(input+16 * i, secret+32*i, seed);
+ acc += XXH3_mix16B(input+len-16*(i+1), secret+32*i+16, seed);
+ } while (i-- != 0);
+#else
+ if (len > 32) {
+ if (len > 64) {
+ if (len > 96) {
+ acc += XXH3_mix16B(input+48, secret+96, seed);
+ acc += XXH3_mix16B(input+len-64, secret+112, seed);
+ }
+ acc += XXH3_mix16B(input+32, secret+64, seed);
+ acc += XXH3_mix16B(input+len-48, secret+80, seed);
+ }
+ acc += XXH3_mix16B(input+16, secret+32, seed);
+ acc += XXH3_mix16B(input+len-32, secret+48, seed);
+ }
+ acc += XXH3_mix16B(input+0, secret+0, seed);
+ acc += XXH3_mix16B(input+len-16, secret+16, seed);
+#endif
+ return XXH3_avalanche(acc);
+ }
+}
+
+#define XXH3_MIDSIZE_MAX 240
+
+XXH_NO_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX);
+
+ #define XXH3_MIDSIZE_STARTOFFSET 3
+ #define XXH3_MIDSIZE_LASTOFFSET 17
+
+ { xxh_u64 acc = len * XXH_PRIME64_1;
+ int const nbRounds = (int)len / 16;
+ int i;
+ for (i=0; i<8; i++) {
+ acc += XXH3_mix16B(input+(16*i), secret+(16*i), seed);
+ }
+ acc = XXH3_avalanche(acc);
+ XXH_ASSERT(nbRounds >= 8);
+#if defined(__clang__) /* Clang */ \
+ && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \
+ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable */
+ /*
+ * UGLY HACK:
+ * Clang for ARMv7-A tries to vectorize this loop, similar to GCC x86.
+ * In everywhere else, it uses scalar code.
+ *
+ * For 64->128-bit multiplies, even if the NEON was 100% optimal, it
+ * would still be slower than UMAAL (see XXH_mult64to128).
+ *
+ * Unfortunately, Clang doesn't handle the long multiplies properly and
+ * converts them to the nonexistent "vmulq_u64" intrinsic, which is then
+ * scalarized into an ugly mess of VMOV.32 instructions.
+ *
+ * This mess is difficult to avoid without turning autovectorization
+ * off completely, but they are usually relatively minor and/or not
+ * worth it to fix.
+ *
+ * This loop is the easiest to fix, as unlike XXH32, this pragma
+ * _actually works_ because it is a loop vectorization instead of an
+ * SLP vectorization.
+ */
+ #pragma clang loop vectorize(disable)
+#endif
+ for (i=8 ; i < nbRounds; i++) {
+ acc += XXH3_mix16B(input+(16*i), secret+(16*(i-8)) + XXH3_MIDSIZE_STARTOFFSET, seed);
+ }
+ /* last bytes */
+ acc += XXH3_mix16B(input + len - 16, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET, seed);
+ return XXH3_avalanche(acc);
+ }
+}
+
+
+/* ======= Long Keys ======= */
+
+#define XXH_STRIPE_LEN 64
+#define XXH_SECRET_CONSUME_RATE 8 /* nb of secret bytes consumed at each accumulation */
+#define XXH_ACC_NB (XXH_STRIPE_LEN / sizeof(xxh_u64))
+
+#ifdef XXH_OLD_NAMES
+# define STRIPE_LEN XXH_STRIPE_LEN
+# define ACC_NB XXH_ACC_NB
+#endif
+
+XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64)
+{
+ if (!XXH_CPU_LITTLE_ENDIAN) v64 = XXH_swap64(v64);
+ XXH_memcpy(dst, &v64, sizeof(v64));
+}
+
+/* Several intrinsic functions below are supposed to accept __int64 as argument,
+ * as documented in https://software.intel.com/sites/landingpage/IntrinsicsGuide/ .
+ * However, several environments do not define __int64 type,
+ * requiring a workaround.
+ */
+#if !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+ typedef int64_t xxh_i64;
+#else
+ /* the following type must have a width of 64-bit */
+ typedef long long xxh_i64;
+#endif
+
+
+/*
+ * XXH3_accumulate_512 is the tightest loop for long inputs, and it is the most optimized.
+ *
+ * It is a hardened version of UMAC, based off of FARSH's implementation.
+ *
+ * This was chosen because it adapts quite well to 32-bit, 64-bit, and SIMD
+ * implementations, and it is ridiculously fast.
+ *
+ * We harden it by mixing the original input to the accumulators as well as the product.
+ *
+ * This means that in the (relatively likely) case of a multiply by zero, the
+ * original input is preserved.
+ *
+ * On 128-bit inputs, we swap 64-bit pairs when we add the input to improve
+ * cross-pollination, as otherwise the upper and lower halves would be
+ * essentially independent.
+ *
+ * This doesn't matter on 64-bit hashes since they all get merged together in
+ * the end, so we skip the extra step.
+ *
+ * Both XXH3_64bits and XXH3_128bits use this subroutine.
+ */
+
+#if (XXH_VECTOR == XXH_AVX512) \
+ || (defined(XXH_DISPATCH_AVX512) && XXH_DISPATCH_AVX512 != 0)
+
+#ifndef XXH_TARGET_AVX512
+# define XXH_TARGET_AVX512 /* disable attribute target */
+#endif
+
+XXH_FORCE_INLINE XXH_TARGET_AVX512 void
+XXH3_accumulate_512_avx512(void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ __m512i* const xacc = (__m512i *) acc;
+ XXH_ASSERT((((size_t)acc) & 63) == 0);
+ XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i));
+
+ {
+ /* data_vec = input[0]; */
+ __m512i const data_vec = _mm512_loadu_si512 (input);
+ /* key_vec = secret[0]; */
+ __m512i const key_vec = _mm512_loadu_si512 (secret);
+ /* data_key = data_vec ^ key_vec; */
+ __m512i const data_key = _mm512_xor_si512 (data_vec, key_vec);
+ /* data_key_lo = data_key >> 32; */
+ __m512i const data_key_lo = _mm512_shuffle_epi32 (data_key, (_MM_PERM_ENUM)_MM_SHUFFLE(0, 3, 0, 1));
+ /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */
+ __m512i const product = _mm512_mul_epu32 (data_key, data_key_lo);
+ /* xacc[0] += swap(data_vec); */
+ __m512i const data_swap = _mm512_shuffle_epi32(data_vec, (_MM_PERM_ENUM)_MM_SHUFFLE(1, 0, 3, 2));
+ __m512i const sum = _mm512_add_epi64(*xacc, data_swap);
+ /* xacc[0] += product; */
+ *xacc = _mm512_add_epi64(product, sum);
+ }
+}
+
+/*
+ * XXH3_scrambleAcc: Scrambles the accumulators to improve mixing.
+ *
+ * Multiplication isn't perfect, as explained by Google in HighwayHash:
+ *
+ * // Multiplication mixes/scrambles bytes 0-7 of the 64-bit result to
+ * // varying degrees. In descending order of goodness, bytes
+ * // 3 4 2 5 1 6 0 7 have quality 228 224 164 160 100 96 36 32.
+ * // As expected, the upper and lower bytes are much worse.
+ *
+ * Source: https://github.com/google/highwayhash/blob/0aaf66b/highwayhash/hh_avx2.h#L291
+ *
+ * Since our algorithm uses a pseudorandom secret to add some variance into the
+ * mix, we don't need to (or want to) mix as often or as much as HighwayHash does.
+ *
+ * This isn't as tight as XXH3_accumulate, but still written in SIMD to avoid
+ * extraction.
+ *
+ * Both XXH3_64bits and XXH3_128bits use this subroutine.
+ */
+
+XXH_FORCE_INLINE XXH_TARGET_AVX512 void
+XXH3_scrambleAcc_avx512(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 63) == 0);
+ XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i));
+ { __m512i* const xacc = (__m512i*) acc;
+ const __m512i prime32 = _mm512_set1_epi32((int)XXH_PRIME32_1);
+
+ /* xacc[0] ^= (xacc[0] >> 47) */
+ __m512i const acc_vec = *xacc;
+ __m512i const shifted = _mm512_srli_epi64 (acc_vec, 47);
+ __m512i const data_vec = _mm512_xor_si512 (acc_vec, shifted);
+ /* xacc[0] ^= secret; */
+ __m512i const key_vec = _mm512_loadu_si512 (secret);
+ __m512i const data_key = _mm512_xor_si512 (data_vec, key_vec);
+
+ /* xacc[0] *= XXH_PRIME32_1; */
+ __m512i const data_key_hi = _mm512_shuffle_epi32 (data_key, (_MM_PERM_ENUM)_MM_SHUFFLE(0, 3, 0, 1));
+ __m512i const prod_lo = _mm512_mul_epu32 (data_key, prime32);
+ __m512i const prod_hi = _mm512_mul_epu32 (data_key_hi, prime32);
+ *xacc = _mm512_add_epi64(prod_lo, _mm512_slli_epi64(prod_hi, 32));
+ }
+}
+
+XXH_FORCE_INLINE XXH_TARGET_AVX512 void
+XXH3_initCustomSecret_avx512(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
+{
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 63) == 0);
+ XXH_STATIC_ASSERT(XXH_SEC_ALIGN == 64);
+ XXH_ASSERT(((size_t)customSecret & 63) == 0);
+ (void)(&XXH_writeLE64);
+ { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m512i);
+ __m512i const seed = _mm512_mask_set1_epi64(_mm512_set1_epi64((xxh_i64)seed64), 0xAA, (xxh_i64)(0U - seed64));
+
+ const __m512i* const src = (const __m512i*) ((const void*) XXH3_kSecret);
+ __m512i* const dest = ( __m512i*) customSecret;
+ int i;
+ XXH_ASSERT(((size_t)src & 63) == 0); /* control alignment */
+ XXH_ASSERT(((size_t)dest & 63) == 0);
+ for (i=0; i < nbRounds; ++i) {
+ /* GCC has a bug, _mm512_stream_load_si512 accepts 'void*', not 'void const*',
+ * this will warn "discards 'const' qualifier". */
+ union {
+ const __m512i* cp;
+ void* p;
+ } remote_const_void;
+ remote_const_void.cp = src + i;
+ dest[i] = _mm512_add_epi64(_mm512_stream_load_si512(remote_const_void.p), seed);
+ } }
+}
+
+#endif
+
+#if (XXH_VECTOR == XXH_AVX2) \
+ || (defined(XXH_DISPATCH_AVX2) && XXH_DISPATCH_AVX2 != 0)
+
+#ifndef XXH_TARGET_AVX2
+# define XXH_TARGET_AVX2 /* disable attribute target */
+#endif
+
+XXH_FORCE_INLINE XXH_TARGET_AVX2 void
+XXH3_accumulate_512_avx2( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 31) == 0);
+ { __m256i* const xacc = (__m256i *) acc;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */
+ const __m256i* const xinput = (const __m256i *) input;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */
+ const __m256i* const xsecret = (const __m256i *) secret;
+
+ size_t i;
+ for (i=0; i < XXH_STRIPE_LEN/sizeof(__m256i); i++) {
+ /* data_vec = xinput[i]; */
+ __m256i const data_vec = _mm256_loadu_si256 (xinput+i);
+ /* key_vec = xsecret[i]; */
+ __m256i const key_vec = _mm256_loadu_si256 (xsecret+i);
+ /* data_key = data_vec ^ key_vec; */
+ __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec);
+ /* data_key_lo = data_key >> 32; */
+ __m256i const data_key_lo = _mm256_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1));
+ /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */
+ __m256i const product = _mm256_mul_epu32 (data_key, data_key_lo);
+ /* xacc[i] += swap(data_vec); */
+ __m256i const data_swap = _mm256_shuffle_epi32(data_vec, _MM_SHUFFLE(1, 0, 3, 2));
+ __m256i const sum = _mm256_add_epi64(xacc[i], data_swap);
+ /* xacc[i] += product; */
+ xacc[i] = _mm256_add_epi64(product, sum);
+ } }
+}
+
+XXH_FORCE_INLINE XXH_TARGET_AVX2 void
+XXH3_scrambleAcc_avx2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 31) == 0);
+ { __m256i* const xacc = (__m256i*) acc;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */
+ const __m256i* const xsecret = (const __m256i *) secret;
+ const __m256i prime32 = _mm256_set1_epi32((int)XXH_PRIME32_1);
+
+ size_t i;
+ for (i=0; i < XXH_STRIPE_LEN/sizeof(__m256i); i++) {
+ /* xacc[i] ^= (xacc[i] >> 47) */
+ __m256i const acc_vec = xacc[i];
+ __m256i const shifted = _mm256_srli_epi64 (acc_vec, 47);
+ __m256i const data_vec = _mm256_xor_si256 (acc_vec, shifted);
+ /* xacc[i] ^= xsecret; */
+ __m256i const key_vec = _mm256_loadu_si256 (xsecret+i);
+ __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec);
+
+ /* xacc[i] *= XXH_PRIME32_1; */
+ __m256i const data_key_hi = _mm256_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1));
+ __m256i const prod_lo = _mm256_mul_epu32 (data_key, prime32);
+ __m256i const prod_hi = _mm256_mul_epu32 (data_key_hi, prime32);
+ xacc[i] = _mm256_add_epi64(prod_lo, _mm256_slli_epi64(prod_hi, 32));
+ }
+ }
+}
+
+XXH_FORCE_INLINE XXH_TARGET_AVX2 void XXH3_initCustomSecret_avx2(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
+{
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 31) == 0);
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE / sizeof(__m256i)) == 6);
+ XXH_STATIC_ASSERT(XXH_SEC_ALIGN <= 64);
+ (void)(&XXH_writeLE64);
+ XXH_PREFETCH(customSecret);
+ { __m256i const seed = _mm256_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64, (xxh_i64)(0U - seed64), (xxh_i64)seed64);
+
+ const __m256i* const src = (const __m256i*) ((const void*) XXH3_kSecret);
+ __m256i* dest = ( __m256i*) customSecret;
+
+# if defined(__GNUC__) || defined(__clang__)
+ /*
+ * On GCC & Clang, marking 'dest' as modified will cause the compiler:
+ * - do not extract the secret from sse registers in the internal loop
+ * - use less common registers, and avoid pushing these reg into stack
+ */
+ XXH_COMPILER_GUARD(dest);
+# endif
+ XXH_ASSERT(((size_t)src & 31) == 0); /* control alignment */
+ XXH_ASSERT(((size_t)dest & 31) == 0);
+
+ /* GCC -O2 need unroll loop manually */
+ dest[0] = _mm256_add_epi64(_mm256_stream_load_si256(src+0), seed);
+ dest[1] = _mm256_add_epi64(_mm256_stream_load_si256(src+1), seed);
+ dest[2] = _mm256_add_epi64(_mm256_stream_load_si256(src+2), seed);
+ dest[3] = _mm256_add_epi64(_mm256_stream_load_si256(src+3), seed);
+ dest[4] = _mm256_add_epi64(_mm256_stream_load_si256(src+4), seed);
+ dest[5] = _mm256_add_epi64(_mm256_stream_load_si256(src+5), seed);
+ }
+}
+
+#endif
+
+/* x86dispatch always generates SSE2 */
+#if (XXH_VECTOR == XXH_SSE2) || defined(XXH_X86DISPATCH)
+
+#ifndef XXH_TARGET_SSE2
+# define XXH_TARGET_SSE2 /* disable attribute target */
+#endif
+
+XXH_FORCE_INLINE XXH_TARGET_SSE2 void
+XXH3_accumulate_512_sse2( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ /* SSE2 is just a half-scale version of the AVX2 version. */
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+ { __m128i* const xacc = (__m128i *) acc;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */
+ const __m128i* const xinput = (const __m128i *) input;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */
+ const __m128i* const xsecret = (const __m128i *) secret;
+
+ size_t i;
+ for (i=0; i < XXH_STRIPE_LEN/sizeof(__m128i); i++) {
+ /* data_vec = xinput[i]; */
+ __m128i const data_vec = _mm_loadu_si128 (xinput+i);
+ /* key_vec = xsecret[i]; */
+ __m128i const key_vec = _mm_loadu_si128 (xsecret+i);
+ /* data_key = data_vec ^ key_vec; */
+ __m128i const data_key = _mm_xor_si128 (data_vec, key_vec);
+ /* data_key_lo = data_key >> 32; */
+ __m128i const data_key_lo = _mm_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1));
+ /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */
+ __m128i const product = _mm_mul_epu32 (data_key, data_key_lo);
+ /* xacc[i] += swap(data_vec); */
+ __m128i const data_swap = _mm_shuffle_epi32(data_vec, _MM_SHUFFLE(1,0,3,2));
+ __m128i const sum = _mm_add_epi64(xacc[i], data_swap);
+ /* xacc[i] += product; */
+ xacc[i] = _mm_add_epi64(product, sum);
+ } }
+}
+
+XXH_FORCE_INLINE XXH_TARGET_SSE2 void
+XXH3_scrambleAcc_sse2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+ { __m128i* const xacc = (__m128i*) acc;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */
+ const __m128i* const xsecret = (const __m128i *) secret;
+ const __m128i prime32 = _mm_set1_epi32((int)XXH_PRIME32_1);
+
+ size_t i;
+ for (i=0; i < XXH_STRIPE_LEN/sizeof(__m128i); i++) {
+ /* xacc[i] ^= (xacc[i] >> 47) */
+ __m128i const acc_vec = xacc[i];
+ __m128i const shifted = _mm_srli_epi64 (acc_vec, 47);
+ __m128i const data_vec = _mm_xor_si128 (acc_vec, shifted);
+ /* xacc[i] ^= xsecret[i]; */
+ __m128i const key_vec = _mm_loadu_si128 (xsecret+i);
+ __m128i const data_key = _mm_xor_si128 (data_vec, key_vec);
+
+ /* xacc[i] *= XXH_PRIME32_1; */
+ __m128i const data_key_hi = _mm_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1));
+ __m128i const prod_lo = _mm_mul_epu32 (data_key, prime32);
+ __m128i const prod_hi = _mm_mul_epu32 (data_key_hi, prime32);
+ xacc[i] = _mm_add_epi64(prod_lo, _mm_slli_epi64(prod_hi, 32));
+ }
+ }
+}
+
+XXH_FORCE_INLINE XXH_TARGET_SSE2 void XXH3_initCustomSecret_sse2(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
+{
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0);
+ (void)(&XXH_writeLE64);
+ { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m128i);
+
+# if defined(_MSC_VER) && defined(_M_IX86) && _MSC_VER < 1900
+ /* MSVC 32bit mode does not support _mm_set_epi64x before 2015 */
+ XXH_ALIGN(16) const xxh_i64 seed64x2[2] = { (xxh_i64)seed64, (xxh_i64)(0U - seed64) };
+ __m128i const seed = _mm_load_si128((__m128i const*)seed64x2);
+# else
+ __m128i const seed = _mm_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64);
+# endif
+ int i;
+
+ const void* const src16 = XXH3_kSecret;
+ __m128i* dst16 = (__m128i*) customSecret;
+# if defined(__GNUC__) || defined(__clang__)
+ /*
+ * On GCC & Clang, marking 'dest' as modified will cause the compiler:
+ * - do not extract the secret from sse registers in the internal loop
+ * - use less common registers, and avoid pushing these reg into stack
+ */
+ XXH_COMPILER_GUARD(dst16);
+# endif
+ XXH_ASSERT(((size_t)src16 & 15) == 0); /* control alignment */
+ XXH_ASSERT(((size_t)dst16 & 15) == 0);
+
+ for (i=0; i < nbRounds; ++i) {
+ dst16[i] = _mm_add_epi64(_mm_load_si128((const __m128i *)src16+i), seed);
+ } }
+}
+
+#endif
+
+#if (XXH_VECTOR == XXH_NEON)
+
+/* forward declarations for the scalar routines */
+XXH_FORCE_INLINE void
+XXH3_scalarRound(void* XXH_RESTRICT acc, void const* XXH_RESTRICT input,
+ void const* XXH_RESTRICT secret, size_t lane);
+
+XXH_FORCE_INLINE void
+XXH3_scalarScrambleRound(void* XXH_RESTRICT acc,
+ void const* XXH_RESTRICT secret, size_t lane);
+
+/*!
+ * @internal
+ * @brief The bulk processing loop for NEON.
+ *
+ * The NEON code path is actually partially scalar when running on AArch64. This
+ * is to optimize the pipelining and can have up to 15% speedup depending on the
+ * CPU, and it also mitigates some GCC codegen issues.
+ *
+ * @see XXH3_NEON_LANES for configuring this and details about this optimization.
+ */
+XXH_FORCE_INLINE void
+XXH3_accumulate_512_neon( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+ XXH_STATIC_ASSERT(XXH3_NEON_LANES > 0 && XXH3_NEON_LANES <= XXH_ACC_NB && XXH3_NEON_LANES % 2 == 0);
+ {
+ uint64x2_t* const xacc = (uint64x2_t *) acc;
+ /* We don't use a uint32x4_t pointer because it causes bus errors on ARMv7. */
+ uint8_t const* const xinput = (const uint8_t *) input;
+ uint8_t const* const xsecret = (const uint8_t *) secret;
+
+ size_t i;
+ /* AArch64 uses both scalar and neon at the same time */
+ for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) {
+ XXH3_scalarRound(acc, input, secret, i);
+ }
+ for (i=0; i < XXH3_NEON_LANES / 2; i++) {
+ uint64x2_t acc_vec = xacc[i];
+ /* data_vec = xinput[i]; */
+ uint64x2_t data_vec = XXH_vld1q_u64(xinput + (i * 16));
+ /* key_vec = xsecret[i]; */
+ uint64x2_t key_vec = XXH_vld1q_u64(xsecret + (i * 16));
+ uint64x2_t data_key;
+ uint32x2_t data_key_lo, data_key_hi;
+ /* acc_vec_2 = swap(data_vec) */
+ uint64x2_t acc_vec_2 = vextq_u64(data_vec, data_vec, 1);
+ /* data_key = data_vec ^ key_vec; */
+ data_key = veorq_u64(data_vec, key_vec);
+ /* data_key_lo = (uint32x2_t) (data_key & 0xFFFFFFFF);
+ * data_key_hi = (uint32x2_t) (data_key >> 32);
+ * data_key = UNDEFINED; */
+ XXH_SPLIT_IN_PLACE(data_key, data_key_lo, data_key_hi);
+ /* acc_vec_2 += (uint64x2_t) data_key_lo * (uint64x2_t) data_key_hi; */
+ acc_vec_2 = vmlal_u32 (acc_vec_2, data_key_lo, data_key_hi);
+ /* xacc[i] += acc_vec_2; */
+ acc_vec = vaddq_u64 (acc_vec, acc_vec_2);
+ xacc[i] = acc_vec;
+ }
+
+ }
+}
+
+XXH_FORCE_INLINE void
+XXH3_scrambleAcc_neon(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+
+ { uint64x2_t* xacc = (uint64x2_t*) acc;
+ uint8_t const* xsecret = (uint8_t const*) secret;
+ uint32x2_t prime = vdup_n_u32 (XXH_PRIME32_1);
+
+ size_t i;
+ /* AArch64 uses both scalar and neon at the same time */
+ for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) {
+ XXH3_scalarScrambleRound(acc, secret, i);
+ }
+ for (i=0; i < XXH3_NEON_LANES / 2; i++) {
+ /* xacc[i] ^= (xacc[i] >> 47); */
+ uint64x2_t acc_vec = xacc[i];
+ uint64x2_t shifted = vshrq_n_u64 (acc_vec, 47);
+ uint64x2_t data_vec = veorq_u64 (acc_vec, shifted);
+
+ /* xacc[i] ^= xsecret[i]; */
+ uint64x2_t key_vec = XXH_vld1q_u64 (xsecret + (i * 16));
+ uint64x2_t data_key = veorq_u64 (data_vec, key_vec);
+
+ /* xacc[i] *= XXH_PRIME32_1 */
+ uint32x2_t data_key_lo, data_key_hi;
+ /* data_key_lo = (uint32x2_t) (xacc[i] & 0xFFFFFFFF);
+ * data_key_hi = (uint32x2_t) (xacc[i] >> 32);
+ * xacc[i] = UNDEFINED; */
+ XXH_SPLIT_IN_PLACE(data_key, data_key_lo, data_key_hi);
+ { /*
+ * prod_hi = (data_key >> 32) * XXH_PRIME32_1;
+ *
+ * Avoid vmul_u32 + vshll_n_u32 since Clang 6 and 7 will
+ * incorrectly "optimize" this:
+ * tmp = vmul_u32(vmovn_u64(a), vmovn_u64(b));
+ * shifted = vshll_n_u32(tmp, 32);
+ * to this:
+ * tmp = "vmulq_u64"(a, b); // no such thing!
+ * shifted = vshlq_n_u64(tmp, 32);
+ *
+ * However, unlike SSE, Clang lacks a 64-bit multiply routine
+ * for NEON, and it scalarizes two 64-bit multiplies instead.
+ *
+ * vmull_u32 has the same timing as vmul_u32, and it avoids
+ * this bug completely.
+ * See https://bugs.llvm.org/show_bug.cgi?id=39967
+ */
+ uint64x2_t prod_hi = vmull_u32 (data_key_hi, prime);
+ /* xacc[i] = prod_hi << 32; */
+ prod_hi = vshlq_n_u64(prod_hi, 32);
+ /* xacc[i] += (prod_hi & 0xFFFFFFFF) * XXH_PRIME32_1; */
+ xacc[i] = vmlal_u32(prod_hi, data_key_lo, prime);
+ }
+ }
+ }
+}
+
+#endif
+
+#if (XXH_VECTOR == XXH_VSX)
+
+XXH_FORCE_INLINE void
+XXH3_accumulate_512_vsx( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ /* presumed aligned */
+ unsigned int* const xacc = (unsigned int*) acc;
+ xxh_u64x2 const* const xinput = (xxh_u64x2 const*) input; /* no alignment restriction */
+ xxh_u64x2 const* const xsecret = (xxh_u64x2 const*) secret; /* no alignment restriction */
+ xxh_u64x2 const v32 = { 32, 32 };
+ size_t i;
+ for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) {
+ /* data_vec = xinput[i]; */
+ xxh_u64x2 const data_vec = XXH_vec_loadu(xinput + i);
+ /* key_vec = xsecret[i]; */
+ xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + i);
+ xxh_u64x2 const data_key = data_vec ^ key_vec;
+ /* shuffled = (data_key << 32) | (data_key >> 32); */
+ xxh_u32x4 const shuffled = (xxh_u32x4)vec_rl(data_key, v32);
+ /* product = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)shuffled & 0xFFFFFFFF); */
+ xxh_u64x2 const product = XXH_vec_mulo((xxh_u32x4)data_key, shuffled);
+ /* acc_vec = xacc[i]; */
+ xxh_u64x2 acc_vec = (xxh_u64x2)vec_xl(0, xacc + 4 * i);
+ acc_vec += product;
+
+ /* swap high and low halves */
+#ifdef __s390x__
+ acc_vec += vec_permi(data_vec, data_vec, 2);
+#else
+ acc_vec += vec_xxpermdi(data_vec, data_vec, 2);
+#endif
+ /* xacc[i] = acc_vec; */
+ vec_xst((xxh_u32x4)acc_vec, 0, xacc + 4 * i);
+ }
+}
+
+XXH_FORCE_INLINE void
+XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+
+ { xxh_u64x2* const xacc = (xxh_u64x2*) acc;
+ const xxh_u64x2* const xsecret = (const xxh_u64x2*) secret;
+ /* constants */
+ xxh_u64x2 const v32 = { 32, 32 };
+ xxh_u64x2 const v47 = { 47, 47 };
+ xxh_u32x4 const prime = { XXH_PRIME32_1, XXH_PRIME32_1, XXH_PRIME32_1, XXH_PRIME32_1 };
+ size_t i;
+ for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) {
+ /* xacc[i] ^= (xacc[i] >> 47); */
+ xxh_u64x2 const acc_vec = xacc[i];
+ xxh_u64x2 const data_vec = acc_vec ^ (acc_vec >> v47);
+
+ /* xacc[i] ^= xsecret[i]; */
+ xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + i);
+ xxh_u64x2 const data_key = data_vec ^ key_vec;
+
+ /* xacc[i] *= XXH_PRIME32_1 */
+ /* prod_lo = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)prime & 0xFFFFFFFF); */
+ xxh_u64x2 const prod_even = XXH_vec_mule((xxh_u32x4)data_key, prime);
+ /* prod_hi = ((xxh_u64x2)data_key >> 32) * ((xxh_u64x2)prime >> 32); */
+ xxh_u64x2 const prod_odd = XXH_vec_mulo((xxh_u32x4)data_key, prime);
+ xacc[i] = prod_odd + (prod_even << v32);
+ } }
+}
+
+#endif
+
+/* scalar variants - universal */
+
+/*!
+ * @internal
+ * @brief Scalar round for @ref XXH3_accumulate_512_scalar().
+ *
+ * This is extracted to its own function because the NEON path uses a combination
+ * of NEON and scalar.
+ */
+XXH_FORCE_INLINE void
+XXH3_scalarRound(void* XXH_RESTRICT acc,
+ void const* XXH_RESTRICT input,
+ void const* XXH_RESTRICT secret,
+ size_t lane)
+{
+ xxh_u64* xacc = (xxh_u64*) acc;
+ xxh_u8 const* xinput = (xxh_u8 const*) input;
+ xxh_u8 const* xsecret = (xxh_u8 const*) secret;
+ XXH_ASSERT(lane < XXH_ACC_NB);
+ XXH_ASSERT(((size_t)acc & (XXH_ACC_ALIGN-1)) == 0);
+ {
+ xxh_u64 const data_val = XXH_readLE64(xinput + lane * 8);
+ xxh_u64 const data_key = data_val ^ XXH_readLE64(xsecret + lane * 8);
+ xacc[lane ^ 1] += data_val; /* swap adjacent lanes */
+ xacc[lane] += XXH_mult32to64(data_key & 0xFFFFFFFF, data_key >> 32);
+ }
+}
+
+/*!
+ * @internal
+ * @brief Processes a 64 byte block of data using the scalar path.
+ */
+XXH_FORCE_INLINE void
+XXH3_accumulate_512_scalar(void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ size_t i;
+ /* ARM GCC refuses to unroll this loop, resulting in a 24% slowdown on ARMv6. */
+#if defined(__GNUC__) && !defined(__clang__) \
+ && (defined(__arm__) || defined(__thumb2__)) \
+ && defined(__ARM_FEATURE_UNALIGNED) /* no unaligned access just wastes bytes */ \
+ && XXH_SIZE_OPT <= 0
+# pragma GCC unroll 8
+#endif
+ for (i=0; i < XXH_ACC_NB; i++) {
+ XXH3_scalarRound(acc, input, secret, i);
+ }
+}
+
+/*!
+ * @internal
+ * @brief Scalar scramble step for @ref XXH3_scrambleAcc_scalar().
+ *
+ * This is extracted to its own function because the NEON path uses a combination
+ * of NEON and scalar.
+ */
+XXH_FORCE_INLINE void
+XXH3_scalarScrambleRound(void* XXH_RESTRICT acc,
+ void const* XXH_RESTRICT secret,
+ size_t lane)
+{
+ xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned */
+ const xxh_u8* const xsecret = (const xxh_u8*) secret; /* no alignment restriction */
+ XXH_ASSERT((((size_t)acc) & (XXH_ACC_ALIGN-1)) == 0);
+ XXH_ASSERT(lane < XXH_ACC_NB);
+ {
+ xxh_u64 const key64 = XXH_readLE64(xsecret + lane * 8);
+ xxh_u64 acc64 = xacc[lane];
+ acc64 = XXH_xorshift64(acc64, 47);
+ acc64 ^= key64;
+ acc64 *= XXH_PRIME32_1;
+ xacc[lane] = acc64;
+ }
+}
+
+/*!
+ * @internal
+ * @brief Scrambles the accumulators after a large chunk has been read
+ */
+XXH_FORCE_INLINE void
+XXH3_scrambleAcc_scalar(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ size_t i;
+ for (i=0; i < XXH_ACC_NB; i++) {
+ XXH3_scalarScrambleRound(acc, secret, i);
+ }
+}
+
+XXH_FORCE_INLINE void
+XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
+{
+ /*
+ * We need a separate pointer for the hack below,
+ * which requires a non-const pointer.
+ * Any decent compiler will optimize this out otherwise.
+ */
+ const xxh_u8* kSecretPtr = XXH3_kSecret;
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0);
+
+#if defined(__clang__) && defined(__aarch64__)
+ /*
+ * UGLY HACK:
+ * Clang generates a bunch of MOV/MOVK pairs for aarch64, and they are
+ * placed sequentially, in order, at the top of the unrolled loop.
+ *
+ * While MOVK is great for generating constants (2 cycles for a 64-bit
+ * constant compared to 4 cycles for LDR), it fights for bandwidth with
+ * the arithmetic instructions.
+ *
+ * I L S
+ * MOVK
+ * MOVK
+ * MOVK
+ * MOVK
+ * ADD
+ * SUB STR
+ * STR
+ * By forcing loads from memory (as the asm line causes Clang to assume
+ * that XXH3_kSecretPtr has been changed), the pipelines are used more
+ * efficiently:
+ * I L S
+ * LDR
+ * ADD LDR
+ * SUB STR
+ * STR
+ *
+ * See XXH3_NEON_LANES for details on the pipsline.
+ *
+ * XXH3_64bits_withSeed, len == 256, Snapdragon 835
+ * without hack: 2654.4 MB/s
+ * with hack: 3202.9 MB/s
+ */
+ XXH_COMPILER_GUARD(kSecretPtr);
+#endif
+ /*
+ * Note: in debug mode, this overrides the asm optimization
+ * and Clang will emit MOVK chains again.
+ */
+ XXH_ASSERT(kSecretPtr == XXH3_kSecret);
+
+ { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / 16;
+ int i;
+ for (i=0; i < nbRounds; i++) {
+ /*
+ * The asm hack causes Clang to assume that kSecretPtr aliases with
+ * customSecret, and on aarch64, this prevented LDP from merging two
+ * loads together for free. Putting the loads together before the stores
+ * properly generates LDP.
+ */
+ xxh_u64 lo = XXH_readLE64(kSecretPtr + 16*i) + seed64;
+ xxh_u64 hi = XXH_readLE64(kSecretPtr + 16*i + 8) - seed64;
+ XXH_writeLE64((xxh_u8*)customSecret + 16*i, lo);
+ XXH_writeLE64((xxh_u8*)customSecret + 16*i + 8, hi);
+ } }
+}
+
+
+typedef void (*XXH3_f_accumulate_512)(void* XXH_RESTRICT, const void*, const void*);
+typedef void (*XXH3_f_scrambleAcc)(void* XXH_RESTRICT, const void*);
+typedef void (*XXH3_f_initCustomSecret)(void* XXH_RESTRICT, xxh_u64);
+
+
+#if (XXH_VECTOR == XXH_AVX512)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_avx512
+#define XXH3_scrambleAcc XXH3_scrambleAcc_avx512
+#define XXH3_initCustomSecret XXH3_initCustomSecret_avx512
+
+#elif (XXH_VECTOR == XXH_AVX2)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_avx2
+#define XXH3_scrambleAcc XXH3_scrambleAcc_avx2
+#define XXH3_initCustomSecret XXH3_initCustomSecret_avx2
+
+#elif (XXH_VECTOR == XXH_SSE2)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_sse2
+#define XXH3_scrambleAcc XXH3_scrambleAcc_sse2
+#define XXH3_initCustomSecret XXH3_initCustomSecret_sse2
+
+#elif (XXH_VECTOR == XXH_NEON)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_neon
+#define XXH3_scrambleAcc XXH3_scrambleAcc_neon
+#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+
+#elif (XXH_VECTOR == XXH_VSX)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_vsx
+#define XXH3_scrambleAcc XXH3_scrambleAcc_vsx
+#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+
+#else /* scalar */
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_scalar
+#define XXH3_scrambleAcc XXH3_scrambleAcc_scalar
+#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+
+#endif
+
+#if XXH_SIZE_OPT >= 1 /* don't do SIMD for initialization */
+# undef XXH3_initCustomSecret
+# define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+#endif
+
+#ifndef XXH_PREFETCH_DIST
+# ifdef __clang__
+# define XXH_PREFETCH_DIST 320
+# else
+# if (XXH_VECTOR == XXH_AVX512)
+# define XXH_PREFETCH_DIST 512
+# else
+# define XXH_PREFETCH_DIST 384
+# endif
+# endif /* __clang__ */
+#endif /* XXH_PREFETCH_DIST */
+
+/*
+ * XXH3_accumulate()
+ * Loops over XXH3_accumulate_512().
+ * Assumption: nbStripes will not overflow the secret size
+ */
+XXH_FORCE_INLINE void
+XXH3_accumulate( xxh_u64* XXH_RESTRICT acc,
+ const xxh_u8* XXH_RESTRICT input,
+ const xxh_u8* XXH_RESTRICT secret,
+ size_t nbStripes,
+ XXH3_f_accumulate_512 f_acc512)
+{
+ size_t n;
+ for (n = 0; n < nbStripes; n++ ) {
+ const xxh_u8* const in = input + n*XXH_STRIPE_LEN;
+ XXH_PREFETCH(in + XXH_PREFETCH_DIST);
+ f_acc512(acc,
+ in,
+ secret + n*XXH_SECRET_CONSUME_RATE);
+ }
+}
+
+XXH_FORCE_INLINE void
+XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc,
+ const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH3_f_accumulate_512 f_acc512,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ size_t const nbStripesPerBlock = (secretSize - XXH_STRIPE_LEN) / XXH_SECRET_CONSUME_RATE;
+ size_t const block_len = XXH_STRIPE_LEN * nbStripesPerBlock;
+ size_t const nb_blocks = (len - 1) / block_len;
+
+ size_t n;
+
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
+
+ for (n = 0; n < nb_blocks; n++) {
+ XXH3_accumulate(acc, input + n*block_len, secret, nbStripesPerBlock, f_acc512);
+ f_scramble(acc, secret + secretSize - XXH_STRIPE_LEN);
+ }
+
+ /* last partial block */
+ XXH_ASSERT(len > XXH_STRIPE_LEN);
+ { size_t const nbStripes = ((len - 1) - (block_len * nb_blocks)) / XXH_STRIPE_LEN;
+ XXH_ASSERT(nbStripes <= (secretSize / XXH_SECRET_CONSUME_RATE));
+ XXH3_accumulate(acc, input + nb_blocks*block_len, secret, nbStripes, f_acc512);
+
+ /* last stripe */
+ { const xxh_u8* const p = input + len - XXH_STRIPE_LEN;
+#define XXH_SECRET_LASTACC_START 7 /* not aligned on 8, last secret is different from acc & scrambler */
+ f_acc512(acc, p, secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_LASTACC_START);
+ } }
+}
+
+XXH_FORCE_INLINE xxh_u64
+XXH3_mix2Accs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret)
+{
+ return XXH3_mul128_fold64(
+ acc[0] ^ XXH_readLE64(secret),
+ acc[1] ^ XXH_readLE64(secret+8) );
+}
+
+static XXH64_hash_t
+XXH3_mergeAccs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret, xxh_u64 start)
+{
+ xxh_u64 result64 = start;
+ size_t i = 0;
+
+ for (i = 0; i < 4; i++) {
+ result64 += XXH3_mix2Accs(acc+2*i, secret + 16*i);
+#if defined(__clang__) /* Clang */ \
+ && (defined(__arm__) || defined(__thumb__)) /* ARMv7 */ \
+ && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \
+ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable */
+ /*
+ * UGLY HACK:
+ * Prevent autovectorization on Clang ARMv7-a. Exact same problem as
+ * the one in XXH3_len_129to240_64b. Speeds up shorter keys > 240b.
+ * XXH3_64bits, len == 256, Snapdragon 835:
+ * without hack: 2063.7 MB/s
+ * with hack: 2560.7 MB/s
+ */
+ XXH_COMPILER_GUARD(result64);
+#endif
+ }
+
+ return XXH3_avalanche(result64);
+}
+
+#define XXH3_INIT_ACC { XXH_PRIME32_3, XXH_PRIME64_1, XXH_PRIME64_2, XXH_PRIME64_3, \
+ XXH_PRIME64_4, XXH_PRIME32_2, XXH_PRIME64_5, XXH_PRIME32_1 }
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3_hashLong_64b_internal(const void* XXH_RESTRICT input, size_t len,
+ const void* XXH_RESTRICT secret, size_t secretSize,
+ XXH3_f_accumulate_512 f_acc512,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC;
+
+ XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, f_acc512, f_scramble);
+
+ /* converge into final hash */
+ XXH_STATIC_ASSERT(sizeof(acc) == 64);
+ /* do not align on 8, so that the secret is different from the accumulator */
+#define XXH_SECRET_MERGEACCS_START 11
+ XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
+ return XXH3_mergeAccs(acc, (const xxh_u8*)secret + XXH_SECRET_MERGEACCS_START, (xxh_u64)len * XXH_PRIME64_1);
+}
+
+/*
+ * It's important for performance to transmit secret's size (when it's static)
+ * so that the compiler can properly optimize the vectorized loop.
+ * This makes a big performance difference for "medium" keys (<1 KB) when using AVX instruction set.
+ */
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)seed64;
+ return XXH3_hashLong_64b_internal(input, len, secret, secretLen, XXH3_accumulate_512, XXH3_scrambleAcc);
+}
+
+/*
+ * It's preferable for performance that XXH3_hashLong is not inlined,
+ * as it results in a smaller function for small data, easier to the instruction cache.
+ * Note that inside this no_inline function, we do inline the internal loop,
+ * and provide a statically defined secret size to allow optimization of vector loop.
+ */
+XXH_NO_INLINE XXH_PUREF XXH64_hash_t
+XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)seed64; (void)secret; (void)secretLen;
+ return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512, XXH3_scrambleAcc);
+}
+
+/*
+ * XXH3_hashLong_64b_withSeed():
+ * Generate a custom key based on alteration of default XXH3_kSecret with the seed,
+ * and then use this key for long mode hashing.
+ *
+ * This operation is decently fast but nonetheless costs a little bit of time.
+ * Try to avoid it whenever possible (typically when seed==0).
+ *
+ * It's important for performance that XXH3_hashLong is not inlined. Not sure
+ * why (uop cache maybe?), but the difference is large and easily measurable.
+ */
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3_hashLong_64b_withSeed_internal(const void* input, size_t len,
+ XXH64_hash_t seed,
+ XXH3_f_accumulate_512 f_acc512,
+ XXH3_f_scrambleAcc f_scramble,
+ XXH3_f_initCustomSecret f_initSec)
+{
+#if XXH_SIZE_OPT <= 0
+ if (seed == 0)
+ return XXH3_hashLong_64b_internal(input, len,
+ XXH3_kSecret, sizeof(XXH3_kSecret),
+ f_acc512, f_scramble);
+#endif
+ { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
+ f_initSec(secret, seed);
+ return XXH3_hashLong_64b_internal(input, len, secret, sizeof(secret),
+ f_acc512, f_scramble);
+ }
+}
+
+/*
+ * It's important for performance that XXH3_hashLong is not inlined.
+ */
+XXH_NO_INLINE XXH64_hash_t
+XXH3_hashLong_64b_withSeed(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)secret; (void)secretLen;
+ return XXH3_hashLong_64b_withSeed_internal(input, len, seed,
+ XXH3_accumulate_512, XXH3_scrambleAcc, XXH3_initCustomSecret);
+}
+
+
+typedef XXH64_hash_t (*XXH3_hashLong64_f)(const void* XXH_RESTRICT, size_t,
+ XXH64_hash_t, const xxh_u8* XXH_RESTRICT, size_t);
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3_64bits_internal(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen,
+ XXH3_hashLong64_f f_hashLong)
+{
+ XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN);
+ /*
+ * If an action is to be taken if `secretLen` condition is not respected,
+ * it should be done here.
+ * For now, it's a contract pre-condition.
+ * Adding a check and a branch here would cost performance at every hash.
+ * Also, note that function signature doesn't offer room to return an error.
+ */
+ if (len <= 16)
+ return XXH3_len_0to16_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, seed64);
+ if (len <= 128)
+ return XXH3_len_17to128_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
+ if (len <= XXH3_MIDSIZE_MAX)
+ return XXH3_len_129to240_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
+ return f_hashLong(input, len, seed64, (const xxh_u8*)secret, secretLen);
+}
+
+
+/* === Public entry point === */
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(const void* input, size_t length)
+{
+ return XXH3_64bits_internal(input, length, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t
+XXH3_64bits_withSecret(const void* input, size_t length, const void* secret, size_t secretSize)
+{
+ return XXH3_64bits_internal(input, length, 0, secret, secretSize, XXH3_hashLong_64b_withSecret);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t
+XXH3_64bits_withSeed(const void* input, size_t length, XXH64_hash_t seed)
+{
+ return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed);
+}
+
+XXH_PUBLIC_API XXH64_hash_t
+XXH3_64bits_withSecretandSeed(const void* input, size_t length, const void* secret, size_t secretSize, XXH64_hash_t seed)
+{
+ if (length <= XXH3_MIDSIZE_MAX)
+ return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL);
+ return XXH3_hashLong_64b_withSecret(input, length, seed, (const xxh_u8*)secret, secretSize);
+}
+
+
+/* === XXH3 streaming === */
+#ifndef XXH_NO_STREAM
+/*
+ * Malloc's a pointer that is always aligned to align.
+ *
+ * This must be freed with `XXH_alignedFree()`.
+ *
+ * malloc typically guarantees 16 byte alignment on 64-bit systems and 8 byte
+ * alignment on 32-bit. This isn't enough for the 32 byte aligned loads in AVX2
+ * or on 32-bit, the 16 byte aligned loads in SSE2 and NEON.
+ *
+ * This underalignment previously caused a rather obvious crash which went
+ * completely unnoticed due to XXH3_createState() not actually being tested.
+ * Credit to RedSpah for noticing this bug.
+ *
+ * The alignment is done manually: Functions like posix_memalign or _mm_malloc
+ * are avoided: To maintain portability, we would have to write a fallback
+ * like this anyways, and besides, testing for the existence of library
+ * functions without relying on external build tools is impossible.
+ *
+ * The method is simple: Overallocate, manually align, and store the offset
+ * to the original behind the returned pointer.
+ *
+ * Align must be a power of 2 and 8 <= align <= 128.
+ */
+static XXH_MALLOCF void* XXH_alignedMalloc(size_t s, size_t align)
+{
+ XXH_ASSERT(align <= 128 && align >= 8); /* range check */
+ XXH_ASSERT((align & (align-1)) == 0); /* power of 2 */
+ XXH_ASSERT(s != 0 && s < (s + align)); /* empty/overflow */
+ { /* Overallocate to make room for manual realignment and an offset byte */
+ xxh_u8* base = (xxh_u8*)XXH_malloc(s + align);
+ if (base != NULL) {
+ /*
+ * Get the offset needed to align this pointer.
+ *
+ * Even if the returned pointer is aligned, there will always be
+ * at least one byte to store the offset to the original pointer.
+ */
+ size_t offset = align - ((size_t)base & (align - 1)); /* base % align */
+ /* Add the offset for the now-aligned pointer */
+ xxh_u8* ptr = base + offset;
+
+ XXH_ASSERT((size_t)ptr % align == 0);
+
+ /* Store the offset immediately before the returned pointer. */
+ ptr[-1] = (xxh_u8)offset;
+ return ptr;
+ }
+ return NULL;
+ }
+}
+/*
+ * Frees an aligned pointer allocated by XXH_alignedMalloc(). Don't pass
+ * normal malloc'd pointers, XXH_alignedMalloc has a specific data layout.
+ */
+static void XXH_alignedFree(void* p)
+{
+ if (p != NULL) {
+ xxh_u8* ptr = (xxh_u8*)p;
+ /* Get the offset byte we added in XXH_malloc. */
+ xxh_u8 offset = ptr[-1];
+ /* Free the original malloc'd pointer */
+ xxh_u8* base = ptr - offset;
+ XXH_free(base);
+ }
+}
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void)
+{
+ XXH3_state_t* const state = (XXH3_state_t*)XXH_alignedMalloc(sizeof(XXH3_state_t), 64);
+ if (state==NULL) return NULL;
+ XXH3_INITSTATE(state);
+ return state;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr)
+{
+ XXH_alignedFree(statePtr);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API void
+XXH3_copyState(XXH3_state_t* dst_state, const XXH3_state_t* src_state)
+{
+ XXH_memcpy(dst_state, src_state, sizeof(*dst_state));
+}
+
+static void
+XXH3_reset_internal(XXH3_state_t* statePtr,
+ XXH64_hash_t seed,
+ const void* secret, size_t secretSize)
+{
+ size_t const initStart = offsetof(XXH3_state_t, bufferedSize);
+ size_t const initLength = offsetof(XXH3_state_t, nbStripesPerBlock) - initStart;
+ XXH_ASSERT(offsetof(XXH3_state_t, nbStripesPerBlock) > initStart);
+ XXH_ASSERT(statePtr != NULL);
+ /* set members from bufferedSize to nbStripesPerBlock (excluded) to 0 */
+ memset((char*)statePtr + initStart, 0, initLength);
+ statePtr->acc[0] = XXH_PRIME32_3;
+ statePtr->acc[1] = XXH_PRIME64_1;
+ statePtr->acc[2] = XXH_PRIME64_2;
+ statePtr->acc[3] = XXH_PRIME64_3;
+ statePtr->acc[4] = XXH_PRIME64_4;
+ statePtr->acc[5] = XXH_PRIME32_2;
+ statePtr->acc[6] = XXH_PRIME64_5;
+ statePtr->acc[7] = XXH_PRIME32_1;
+ statePtr->seed = seed;
+ statePtr->useSeed = (seed != 0);
+ statePtr->extSecret = (const unsigned char*)secret;
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
+ statePtr->secretLimit = secretSize - XXH_STRIPE_LEN;
+ statePtr->nbStripesPerBlock = statePtr->secretLimit / XXH_SECRET_CONSUME_RATE;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset(XXH3_state_t* statePtr)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3_reset_internal(statePtr, 0, secret, secretSize);
+ if (secret == NULL) return XXH_ERROR;
+ if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ if (seed==0) return XXH3_64bits_reset(statePtr);
+ if ((seed != statePtr->seed) || (statePtr->extSecret != NULL))
+ XXH3_initCustomSecret(statePtr->customSecret, seed);
+ XXH3_reset_internal(statePtr, seed, NULL, XXH_SECRET_DEFAULT_SIZE);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed64)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ if (secret == NULL) return XXH_ERROR;
+ if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
+ XXH3_reset_internal(statePtr, seed64, secret, secretSize);
+ statePtr->useSeed = 1; /* always, even if seed64==0 */
+ return XXH_OK;
+}
+
+/* Note : when XXH3_consumeStripes() is invoked,
+ * there must be a guarantee that at least one more byte must be consumed from input
+ * so that the function can blindly consume all stripes using the "normal" secret segment */
+XXH_FORCE_INLINE void
+XXH3_consumeStripes(xxh_u64* XXH_RESTRICT acc,
+ size_t* XXH_RESTRICT nbStripesSoFarPtr, size_t nbStripesPerBlock,
+ const xxh_u8* XXH_RESTRICT input, size_t nbStripes,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretLimit,
+ XXH3_f_accumulate_512 f_acc512,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ XXH_ASSERT(nbStripes <= nbStripesPerBlock); /* can handle max 1 scramble per invocation */
+ XXH_ASSERT(*nbStripesSoFarPtr < nbStripesPerBlock);
+ if (nbStripesPerBlock - *nbStripesSoFarPtr <= nbStripes) {
+ /* need a scrambling operation */
+ size_t const nbStripesToEndofBlock = nbStripesPerBlock - *nbStripesSoFarPtr;
+ size_t const nbStripesAfterBlock = nbStripes - nbStripesToEndofBlock;
+ XXH3_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, nbStripesToEndofBlock, f_acc512);
+ f_scramble(acc, secret + secretLimit);
+ XXH3_accumulate(acc, input + nbStripesToEndofBlock * XXH_STRIPE_LEN, secret, nbStripesAfterBlock, f_acc512);
+ *nbStripesSoFarPtr = nbStripesAfterBlock;
+ } else {
+ XXH3_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, nbStripes, f_acc512);
+ *nbStripesSoFarPtr += nbStripes;
+ }
+}
+
+#ifndef XXH3_STREAM_USE_STACK
+# if XXH_SIZE_OPT <= 0 && !defined(__clang__) /* clang doesn't need additional stack space */
+# define XXH3_STREAM_USE_STACK 1
+# endif
+#endif
+/*
+ * Both XXH3_64bits_update and XXH3_128bits_update use this routine.
+ */
+XXH_FORCE_INLINE XXH_errorcode
+XXH3_update(XXH3_state_t* XXH_RESTRICT const state,
+ const xxh_u8* XXH_RESTRICT input, size_t len,
+ XXH3_f_accumulate_512 f_acc512,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ if (input==NULL) {
+ XXH_ASSERT(len == 0);
+ return XXH_OK;
+ }
+
+ XXH_ASSERT(state != NULL);
+ { const xxh_u8* const bEnd = input + len;
+ const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
+#if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1
+ /* For some reason, gcc and MSVC seem to suffer greatly
+ * when operating accumulators directly into state.
+ * Operating into stack space seems to enable proper optimization.
+ * clang, on the other hand, doesn't seem to need this trick */
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[8]; memcpy(acc, state->acc, sizeof(acc));
+#else
+ xxh_u64* XXH_RESTRICT const acc = state->acc;
+#endif
+ state->totalLen += len;
+ XXH_ASSERT(state->bufferedSize <= XXH3_INTERNALBUFFER_SIZE);
+
+ /* small input : just fill in tmp buffer */
+ if (state->bufferedSize + len <= XXH3_INTERNALBUFFER_SIZE) {
+ XXH_memcpy(state->buffer + state->bufferedSize, input, len);
+ state->bufferedSize += (XXH32_hash_t)len;
+ return XXH_OK;
+ }
+
+ /* total input is now > XXH3_INTERNALBUFFER_SIZE */
+ #define XXH3_INTERNALBUFFER_STRIPES (XXH3_INTERNALBUFFER_SIZE / XXH_STRIPE_LEN)
+ XXH_STATIC_ASSERT(XXH3_INTERNALBUFFER_SIZE % XXH_STRIPE_LEN == 0); /* clean multiple */
+
+ /*
+ * Internal buffer is partially filled (always, except at beginning)
+ * Complete it, then consume it.
+ */
+ if (state->bufferedSize) {
+ size_t const loadSize = XXH3_INTERNALBUFFER_SIZE - state->bufferedSize;
+ XXH_memcpy(state->buffer + state->bufferedSize, input, loadSize);
+ input += loadSize;
+ XXH3_consumeStripes(acc,
+ &state->nbStripesSoFar, state->nbStripesPerBlock,
+ state->buffer, XXH3_INTERNALBUFFER_STRIPES,
+ secret, state->secretLimit,
+ f_acc512, f_scramble);
+ state->bufferedSize = 0;
+ }
+ XXH_ASSERT(input < bEnd);
+
+ /* large input to consume : ingest per full block */
+ if ((size_t)(bEnd - input) > state->nbStripesPerBlock * XXH_STRIPE_LEN) {
+ size_t nbStripes = (size_t)(bEnd - 1 - input) / XXH_STRIPE_LEN;
+ XXH_ASSERT(state->nbStripesPerBlock >= state->nbStripesSoFar);
+ /* join to current block's end */
+ { size_t const nbStripesToEnd = state->nbStripesPerBlock - state->nbStripesSoFar;
+ XXH_ASSERT(nbStripesToEnd <= nbStripes);
+ XXH3_accumulate(acc, input, secret + state->nbStripesSoFar * XXH_SECRET_CONSUME_RATE, nbStripesToEnd, f_acc512);
+ f_scramble(acc, secret + state->secretLimit);
+ state->nbStripesSoFar = 0;
+ input += nbStripesToEnd * XXH_STRIPE_LEN;
+ nbStripes -= nbStripesToEnd;
+ }
+ /* consume per entire blocks */
+ while(nbStripes >= state->nbStripesPerBlock) {
+ XXH3_accumulate(acc, input, secret, state->nbStripesPerBlock, f_acc512);
+ f_scramble(acc, secret + state->secretLimit);
+ input += state->nbStripesPerBlock * XXH_STRIPE_LEN;
+ nbStripes -= state->nbStripesPerBlock;
+ }
+ /* consume last partial block */
+ XXH3_accumulate(acc, input, secret, nbStripes, f_acc512);
+ input += nbStripes * XXH_STRIPE_LEN;
+ XXH_ASSERT(input < bEnd); /* at least some bytes left */
+ state->nbStripesSoFar = nbStripes;
+ /* buffer predecessor of last partial stripe */
+ XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN);
+ XXH_ASSERT(bEnd - input <= XXH_STRIPE_LEN);
+ } else {
+ /* content to consume <= block size */
+ /* Consume input by a multiple of internal buffer size */
+ if (bEnd - input > XXH3_INTERNALBUFFER_SIZE) {
+ const xxh_u8* const limit = bEnd - XXH3_INTERNALBUFFER_SIZE;
+ do {
+ XXH3_consumeStripes(acc,
+ &state->nbStripesSoFar, state->nbStripesPerBlock,
+ input, XXH3_INTERNALBUFFER_STRIPES,
+ secret, state->secretLimit,
+ f_acc512, f_scramble);
+ input += XXH3_INTERNALBUFFER_SIZE;
+ } while (input<limit);
+ /* buffer predecessor of last partial stripe */
+ XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN);
+ }
+ }
+
+ /* Some remaining input (always) : buffer it */
+ XXH_ASSERT(input < bEnd);
+ XXH_ASSERT(bEnd - input <= XXH3_INTERNALBUFFER_SIZE);
+ XXH_ASSERT(state->bufferedSize == 0);
+ XXH_memcpy(state->buffer, input, (size_t)(bEnd-input));
+ state->bufferedSize = (XXH32_hash_t)(bEnd-input);
+#if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1
+ /* save stack accumulators into state */
+ memcpy(state->acc, acc, sizeof(acc));
+#endif
+ }
+
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_update(XXH3_state_t* state, const void* input, size_t len)
+{
+ return XXH3_update(state, (const xxh_u8*)input, len,
+ XXH3_accumulate_512, XXH3_scrambleAcc);
+}
+
+
+XXH_FORCE_INLINE void
+XXH3_digest_long (XXH64_hash_t* acc,
+ const XXH3_state_t* state,
+ const unsigned char* secret)
+{
+ /*
+ * Digest on a local copy. This way, the state remains unaltered, and it can
+ * continue ingesting more input afterwards.
+ */
+ XXH_memcpy(acc, state->acc, sizeof(state->acc));
+ if (state->bufferedSize >= XXH_STRIPE_LEN) {
+ size_t const nbStripes = (state->bufferedSize - 1) / XXH_STRIPE_LEN;
+ size_t nbStripesSoFar = state->nbStripesSoFar;
+ XXH3_consumeStripes(acc,
+ &nbStripesSoFar, state->nbStripesPerBlock,
+ state->buffer, nbStripes,
+ secret, state->secretLimit,
+ XXH3_accumulate_512, XXH3_scrambleAcc);
+ /* last stripe */
+ XXH3_accumulate_512(acc,
+ state->buffer + state->bufferedSize - XXH_STRIPE_LEN,
+ secret + state->secretLimit - XXH_SECRET_LASTACC_START);
+ } else { /* bufferedSize < XXH_STRIPE_LEN */
+ xxh_u8 lastStripe[XXH_STRIPE_LEN];
+ size_t const catchupSize = XXH_STRIPE_LEN - state->bufferedSize;
+ XXH_ASSERT(state->bufferedSize > 0); /* there is always some input buffered */
+ XXH_memcpy(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize);
+ XXH_memcpy(lastStripe + catchupSize, state->buffer, state->bufferedSize);
+ XXH3_accumulate_512(acc,
+ lastStripe,
+ secret + state->secretLimit - XXH_SECRET_LASTACC_START);
+ }
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* state)
+{
+ const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
+ if (state->totalLen > XXH3_MIDSIZE_MAX) {
+ XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[XXH_ACC_NB];
+ XXH3_digest_long(acc, state, secret);
+ return XXH3_mergeAccs(acc,
+ secret + XXH_SECRET_MERGEACCS_START,
+ (xxh_u64)state->totalLen * XXH_PRIME64_1);
+ }
+ /* totalLen <= XXH3_MIDSIZE_MAX: digesting a short input */
+ if (state->useSeed)
+ return XXH3_64bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed);
+ return XXH3_64bits_withSecret(state->buffer, (size_t)(state->totalLen),
+ secret, state->secretLimit + XXH_STRIPE_LEN);
+}
+#endif /* !XXH_NO_STREAM */
+
+
+/* ==========================================
+ * XXH3 128 bits (a.k.a XXH128)
+ * ==========================================
+ * XXH3's 128-bit variant has better mixing and strength than the 64-bit variant,
+ * even without counting the significantly larger output size.
+ *
+ * For example, extra steps are taken to avoid the seed-dependent collisions
+ * in 17-240 byte inputs (See XXH3_mix16B and XXH128_mix32B).
+ *
+ * This strength naturally comes at the cost of some speed, especially on short
+ * lengths. Note that longer hashes are about as fast as the 64-bit version
+ * due to it using only a slight modification of the 64-bit loop.
+ *
+ * XXH128 is also more oriented towards 64-bit machines. It is still extremely
+ * fast for a _128-bit_ hash on 32-bit (it usually clears XXH64).
+ */
+
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_1to3_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ /* A doubled version of 1to3_64b with different constants. */
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(1 <= len && len <= 3);
+ XXH_ASSERT(secret != NULL);
+ /*
+ * len = 1: combinedl = { input[0], 0x01, input[0], input[0] }
+ * len = 2: combinedl = { input[1], 0x02, input[0], input[1] }
+ * len = 3: combinedl = { input[2], 0x03, input[0], input[1] }
+ */
+ { xxh_u8 const c1 = input[0];
+ xxh_u8 const c2 = input[len >> 1];
+ xxh_u8 const c3 = input[len - 1];
+ xxh_u32 const combinedl = ((xxh_u32)c1 <<16) | ((xxh_u32)c2 << 24)
+ | ((xxh_u32)c3 << 0) | ((xxh_u32)len << 8);
+ xxh_u32 const combinedh = XXH_rotl32(XXH_swap32(combinedl), 13);
+ xxh_u64 const bitflipl = (XXH_readLE32(secret) ^ XXH_readLE32(secret+4)) + seed;
+ xxh_u64 const bitfliph = (XXH_readLE32(secret+8) ^ XXH_readLE32(secret+12)) - seed;
+ xxh_u64 const keyed_lo = (xxh_u64)combinedl ^ bitflipl;
+ xxh_u64 const keyed_hi = (xxh_u64)combinedh ^ bitfliph;
+ XXH128_hash_t h128;
+ h128.low64 = XXH64_avalanche(keyed_lo);
+ h128.high64 = XXH64_avalanche(keyed_hi);
+ return h128;
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_4to8_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(4 <= len && len <= 8);
+ seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32;
+ { xxh_u32 const input_lo = XXH_readLE32(input);
+ xxh_u32 const input_hi = XXH_readLE32(input + len - 4);
+ xxh_u64 const input_64 = input_lo + ((xxh_u64)input_hi << 32);
+ xxh_u64 const bitflip = (XXH_readLE64(secret+16) ^ XXH_readLE64(secret+24)) + seed;
+ xxh_u64 const keyed = input_64 ^ bitflip;
+
+ /* Shift len to the left to ensure it is even, this avoids even multiplies. */
+ XXH128_hash_t m128 = XXH_mult64to128(keyed, XXH_PRIME64_1 + (len << 2));
+
+ m128.high64 += (m128.low64 << 1);
+ m128.low64 ^= (m128.high64 >> 3);
+
+ m128.low64 = XXH_xorshift64(m128.low64, 35);
+ m128.low64 *= 0x9FB21C651E98DF25ULL;
+ m128.low64 = XXH_xorshift64(m128.low64, 28);
+ m128.high64 = XXH3_avalanche(m128.high64);
+ return m128;
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_9to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(9 <= len && len <= 16);
+ { xxh_u64 const bitflipl = (XXH_readLE64(secret+32) ^ XXH_readLE64(secret+40)) - seed;
+ xxh_u64 const bitfliph = (XXH_readLE64(secret+48) ^ XXH_readLE64(secret+56)) + seed;
+ xxh_u64 const input_lo = XXH_readLE64(input);
+ xxh_u64 input_hi = XXH_readLE64(input + len - 8);
+ XXH128_hash_t m128 = XXH_mult64to128(input_lo ^ input_hi ^ bitflipl, XXH_PRIME64_1);
+ /*
+ * Put len in the middle of m128 to ensure that the length gets mixed to
+ * both the low and high bits in the 128x64 multiply below.
+ */
+ m128.low64 += (xxh_u64)(len - 1) << 54;
+ input_hi ^= bitfliph;
+ /*
+ * Add the high 32 bits of input_hi to the high 32 bits of m128, then
+ * add the long product of the low 32 bits of input_hi and XXH_PRIME32_2 to
+ * the high 64 bits of m128.
+ *
+ * The best approach to this operation is different on 32-bit and 64-bit.
+ */
+ if (sizeof(void *) < sizeof(xxh_u64)) { /* 32-bit */
+ /*
+ * 32-bit optimized version, which is more readable.
+ *
+ * On 32-bit, it removes an ADC and delays a dependency between the two
+ * halves of m128.high64, but it generates an extra mask on 64-bit.
+ */
+ m128.high64 += (input_hi & 0xFFFFFFFF00000000ULL) + XXH_mult32to64((xxh_u32)input_hi, XXH_PRIME32_2);
+ } else {
+ /*
+ * 64-bit optimized (albeit more confusing) version.
+ *
+ * Uses some properties of addition and multiplication to remove the mask:
+ *
+ * Let:
+ * a = input_hi.lo = (input_hi & 0x00000000FFFFFFFF)
+ * b = input_hi.hi = (input_hi & 0xFFFFFFFF00000000)
+ * c = XXH_PRIME32_2
+ *
+ * a + (b * c)
+ * Inverse Property: x + y - x == y
+ * a + (b * (1 + c - 1))
+ * Distributive Property: x * (y + z) == (x * y) + (x * z)
+ * a + (b * 1) + (b * (c - 1))
+ * Identity Property: x * 1 == x
+ * a + b + (b * (c - 1))
+ *
+ * Substitute a, b, and c:
+ * input_hi.hi + input_hi.lo + ((xxh_u64)input_hi.lo * (XXH_PRIME32_2 - 1))
+ *
+ * Since input_hi.hi + input_hi.lo == input_hi, we get this:
+ * input_hi + ((xxh_u64)input_hi.lo * (XXH_PRIME32_2 - 1))
+ */
+ m128.high64 += input_hi + XXH_mult32to64((xxh_u32)input_hi, XXH_PRIME32_2 - 1);
+ }
+ /* m128 ^= XXH_swap64(m128 >> 64); */
+ m128.low64 ^= XXH_swap64(m128.high64);
+
+ { /* 128x64 multiply: h128 = m128 * XXH_PRIME64_2; */
+ XXH128_hash_t h128 = XXH_mult64to128(m128.low64, XXH_PRIME64_2);
+ h128.high64 += m128.high64 * XXH_PRIME64_2;
+
+ h128.low64 = XXH3_avalanche(h128.low64);
+ h128.high64 = XXH3_avalanche(h128.high64);
+ return h128;
+ } }
+}
+
+/*
+ * Assumption: `secret` size is >= XXH3_SECRET_SIZE_MIN
+ */
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_0to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(len <= 16);
+ { if (len > 8) return XXH3_len_9to16_128b(input, len, secret, seed);
+ if (len >= 4) return XXH3_len_4to8_128b(input, len, secret, seed);
+ if (len) return XXH3_len_1to3_128b(input, len, secret, seed);
+ { XXH128_hash_t h128;
+ xxh_u64 const bitflipl = XXH_readLE64(secret+64) ^ XXH_readLE64(secret+72);
+ xxh_u64 const bitfliph = XXH_readLE64(secret+80) ^ XXH_readLE64(secret+88);
+ h128.low64 = XXH64_avalanche(seed ^ bitflipl);
+ h128.high64 = XXH64_avalanche( seed ^ bitfliph);
+ return h128;
+ } }
+}
+
+/*
+ * A bit slower than XXH3_mix16B, but handles multiply by zero better.
+ */
+XXH_FORCE_INLINE XXH128_hash_t
+XXH128_mix32B(XXH128_hash_t acc, const xxh_u8* input_1, const xxh_u8* input_2,
+ const xxh_u8* secret, XXH64_hash_t seed)
+{
+ acc.low64 += XXH3_mix16B (input_1, secret+0, seed);
+ acc.low64 ^= XXH_readLE64(input_2) + XXH_readLE64(input_2 + 8);
+ acc.high64 += XXH3_mix16B (input_2, secret+16, seed);
+ acc.high64 ^= XXH_readLE64(input_1) + XXH_readLE64(input_1 + 8);
+ return acc;
+}
+
+
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(16 < len && len <= 128);
+
+ { XXH128_hash_t acc;
+ acc.low64 = len * XXH_PRIME64_1;
+ acc.high64 = 0;
+
+#if XXH_SIZE_OPT >= 1
+ {
+ /* Smaller, but slightly slower. */
+ size_t i = (len - 1) / 32;
+ do {
+ acc = XXH128_mix32B(acc, input+16*i, input+len-16*(i+1), secret+32*i, seed);
+ } while (i-- != 0);
+ }
+#else
+ if (len > 32) {
+ if (len > 64) {
+ if (len > 96) {
+ acc = XXH128_mix32B(acc, input+48, input+len-64, secret+96, seed);
+ }
+ acc = XXH128_mix32B(acc, input+32, input+len-48, secret+64, seed);
+ }
+ acc = XXH128_mix32B(acc, input+16, input+len-32, secret+32, seed);
+ }
+ acc = XXH128_mix32B(acc, input, input+len-16, secret, seed);
+#endif
+ { XXH128_hash_t h128;
+ h128.low64 = acc.low64 + acc.high64;
+ h128.high64 = (acc.low64 * XXH_PRIME64_1)
+ + (acc.high64 * XXH_PRIME64_4)
+ + ((len - seed) * XXH_PRIME64_2);
+ h128.low64 = XXH3_avalanche(h128.low64);
+ h128.high64 = (XXH64_hash_t)0 - XXH3_avalanche(h128.high64);
+ return h128;
+ }
+ }
+}
+
+XXH_NO_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX);
+
+ { XXH128_hash_t acc;
+ int const nbRounds = (int)len / 32;
+ int i;
+ acc.low64 = len * XXH_PRIME64_1;
+ acc.high64 = 0;
+ for (i=0; i<4; i++) {
+ acc = XXH128_mix32B(acc,
+ input + (32 * i),
+ input + (32 * i) + 16,
+ secret + (32 * i),
+ seed);
+ }
+ acc.low64 = XXH3_avalanche(acc.low64);
+ acc.high64 = XXH3_avalanche(acc.high64);
+ XXH_ASSERT(nbRounds >= 4);
+ for (i=4 ; i < nbRounds; i++) {
+ acc = XXH128_mix32B(acc,
+ input + (32 * i),
+ input + (32 * i) + 16,
+ secret + XXH3_MIDSIZE_STARTOFFSET + (32 * (i - 4)),
+ seed);
+ }
+ /* last bytes */
+ acc = XXH128_mix32B(acc,
+ input + len - 16,
+ input + len - 32,
+ secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET - 16,
+ 0ULL - seed);
+
+ { XXH128_hash_t h128;
+ h128.low64 = acc.low64 + acc.high64;
+ h128.high64 = (acc.low64 * XXH_PRIME64_1)
+ + (acc.high64 * XXH_PRIME64_4)
+ + ((len - seed) * XXH_PRIME64_2);
+ h128.low64 = XXH3_avalanche(h128.low64);
+ h128.high64 = (XXH64_hash_t)0 - XXH3_avalanche(h128.high64);
+ return h128;
+ }
+ }
+}
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3_hashLong_128b_internal(const void* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH3_f_accumulate_512 f_acc512,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC;
+
+ XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, secret, secretSize, f_acc512, f_scramble);
+
+ /* converge into final hash */
+ XXH_STATIC_ASSERT(sizeof(acc) == 64);
+ XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
+ { XXH128_hash_t h128;
+ h128.low64 = XXH3_mergeAccs(acc,
+ secret + XXH_SECRET_MERGEACCS_START,
+ (xxh_u64)len * XXH_PRIME64_1);
+ h128.high64 = XXH3_mergeAccs(acc,
+ secret + secretSize
+ - sizeof(acc) - XXH_SECRET_MERGEACCS_START,
+ ~((xxh_u64)len * XXH_PRIME64_2));
+ return h128;
+ }
+}
+
+/*
+ * It's important for performance that XXH3_hashLong() is not inlined.
+ */
+XXH_NO_INLINE XXH_PUREF XXH128_hash_t
+XXH3_hashLong_128b_default(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64,
+ const void* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)seed64; (void)secret; (void)secretLen;
+ return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret),
+ XXH3_accumulate_512, XXH3_scrambleAcc);
+}
+
+/*
+ * It's important for performance to pass @p secretLen (when it's static)
+ * to the compiler, so that it can properly optimize the vectorized loop.
+ */
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3_hashLong_128b_withSecret(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64,
+ const void* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)seed64;
+ return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen,
+ XXH3_accumulate_512, XXH3_scrambleAcc);
+}
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3_hashLong_128b_withSeed_internal(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64,
+ XXH3_f_accumulate_512 f_acc512,
+ XXH3_f_scrambleAcc f_scramble,
+ XXH3_f_initCustomSecret f_initSec)
+{
+ if (seed64 == 0)
+ return XXH3_hashLong_128b_internal(input, len,
+ XXH3_kSecret, sizeof(XXH3_kSecret),
+ f_acc512, f_scramble);
+ { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
+ f_initSec(secret, seed64);
+ return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, sizeof(secret),
+ f_acc512, f_scramble);
+ }
+}
+
+/*
+ * It's important for performance that XXH3_hashLong is not inlined.
+ */
+XXH_NO_INLINE XXH128_hash_t
+XXH3_hashLong_128b_withSeed(const void* input, size_t len,
+ XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)secret; (void)secretLen;
+ return XXH3_hashLong_128b_withSeed_internal(input, len, seed64,
+ XXH3_accumulate_512, XXH3_scrambleAcc, XXH3_initCustomSecret);
+}
+
+typedef XXH128_hash_t (*XXH3_hashLong128_f)(const void* XXH_RESTRICT, size_t,
+ XXH64_hash_t, const void* XXH_RESTRICT, size_t);
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3_128bits_internal(const void* input, size_t len,
+ XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen,
+ XXH3_hashLong128_f f_hl128)
+{
+ XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN);
+ /*
+ * If an action is to be taken if `secret` conditions are not respected,
+ * it should be done here.
+ * For now, it's a contract pre-condition.
+ * Adding a check and a branch here would cost performance at every hash.
+ */
+ if (len <= 16)
+ return XXH3_len_0to16_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, seed64);
+ if (len <= 128)
+ return XXH3_len_17to128_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
+ if (len <= XXH3_MIDSIZE_MAX)
+ return XXH3_len_129to240_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
+ return f_hl128(input, len, seed64, secret, secretLen);
+}
+
+
+/* === Public XXH128 API === */
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(const void* input, size_t len)
+{
+ return XXH3_128bits_internal(input, len, 0,
+ XXH3_kSecret, sizeof(XXH3_kSecret),
+ XXH3_hashLong_128b_default);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH3_128bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize)
+{
+ return XXH3_128bits_internal(input, len, 0,
+ (const xxh_u8*)secret, secretSize,
+ XXH3_hashLong_128b_withSecret);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH3_128bits_withSeed(const void* input, size_t len, XXH64_hash_t seed)
+{
+ return XXH3_128bits_internal(input, len, seed,
+ XXH3_kSecret, sizeof(XXH3_kSecret),
+ XXH3_hashLong_128b_withSeed);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH3_128bits_withSecretandSeed(const void* input, size_t len, const void* secret, size_t secretSize, XXH64_hash_t seed)
+{
+ if (len <= XXH3_MIDSIZE_MAX)
+ return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL);
+ return XXH3_hashLong_128b_withSecret(input, len, seed, secret, secretSize);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH128(const void* input, size_t len, XXH64_hash_t seed)
+{
+ return XXH3_128bits_withSeed(input, len, seed);
+}
+
+
+/* === XXH3 128-bit streaming === */
+#ifndef XXH_NO_STREAM
+/*
+ * All initialization and update functions are identical to 64-bit streaming variant.
+ * The only difference is the finalization routine.
+ */
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset(XXH3_state_t* statePtr)
+{
+ return XXH3_64bits_reset(statePtr);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize)
+{
+ return XXH3_64bits_reset_withSecret(statePtr, secret, secretSize);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed)
+{
+ return XXH3_64bits_reset_withSeed(statePtr, seed);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed)
+{
+ return XXH3_64bits_reset_withSecretandSeed(statePtr, secret, secretSize, seed);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_update(XXH3_state_t* state, const void* input, size_t len)
+{
+ return XXH3_update(state, (const xxh_u8*)input, len,
+ XXH3_accumulate_512, XXH3_scrambleAcc);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* state)
+{
+ const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
+ if (state->totalLen > XXH3_MIDSIZE_MAX) {
+ XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[XXH_ACC_NB];
+ XXH3_digest_long(acc, state, secret);
+ XXH_ASSERT(state->secretLimit + XXH_STRIPE_LEN >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
+ { XXH128_hash_t h128;
+ h128.low64 = XXH3_mergeAccs(acc,
+ secret + XXH_SECRET_MERGEACCS_START,
+ (xxh_u64)state->totalLen * XXH_PRIME64_1);
+ h128.high64 = XXH3_mergeAccs(acc,
+ secret + state->secretLimit + XXH_STRIPE_LEN
+ - sizeof(acc) - XXH_SECRET_MERGEACCS_START,
+ ~((xxh_u64)state->totalLen * XXH_PRIME64_2));
+ return h128;
+ }
+ }
+ /* len <= XXH3_MIDSIZE_MAX : short code */
+ if (state->seed)
+ return XXH3_128bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed);
+ return XXH3_128bits_withSecret(state->buffer, (size_t)(state->totalLen),
+ secret, state->secretLimit + XXH_STRIPE_LEN);
+}
+#endif /* !XXH_NO_STREAM */
+/* 128-bit utility functions */
+
+#include <string.h> /* memcmp, memcpy */
+
+/* return : 1 is equal, 0 if different */
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2)
+{
+ /* note : XXH128_hash_t is compact, it has no padding byte */
+ return !(memcmp(&h1, &h2, sizeof(h1)));
+}
+
+/* This prototype is compatible with stdlib's qsort().
+ * @return : >0 if *h128_1 > *h128_2
+ * <0 if *h128_1 < *h128_2
+ * =0 if *h128_1 == *h128_2 */
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2)
+{
+ XXH128_hash_t const h1 = *(const XXH128_hash_t*)h128_1;
+ XXH128_hash_t const h2 = *(const XXH128_hash_t*)h128_2;
+ int const hcmp = (h1.high64 > h2.high64) - (h2.high64 > h1.high64);
+ /* note : bets that, in most cases, hash values are different */
+ if (hcmp) return hcmp;
+ return (h1.low64 > h2.low64) - (h2.low64 > h1.low64);
+}
+
+
+/*====== Canonical representation ======*/
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API void
+XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash)
+{
+ XXH_STATIC_ASSERT(sizeof(XXH128_canonical_t) == sizeof(XXH128_hash_t));
+ if (XXH_CPU_LITTLE_ENDIAN) {
+ hash.high64 = XXH_swap64(hash.high64);
+ hash.low64 = XXH_swap64(hash.low64);
+ }
+ XXH_memcpy(dst, &hash.high64, sizeof(hash.high64));
+ XXH_memcpy((char*)dst + sizeof(hash.high64), &hash.low64, sizeof(hash.low64));
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH128_hashFromCanonical(const XXH128_canonical_t* src)
+{
+ XXH128_hash_t h;
+ h.high64 = XXH_readBE64(src);
+ h.low64 = XXH_readBE64(src->digest + 8);
+ return h;
+}
+
+
+
+/* ==========================================
+ * Secret generators
+ * ==========================================
+ */
+#define XXH_MIN(x, y) (((x) > (y)) ? (y) : (x))
+
+XXH_FORCE_INLINE void XXH3_combine16(void* dst, XXH128_hash_t h128)
+{
+ XXH_writeLE64( dst, XXH_readLE64(dst) ^ h128.low64 );
+ XXH_writeLE64( (char*)dst+8, XXH_readLE64((char*)dst+8) ^ h128.high64 );
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize)
+{
+#if (XXH_DEBUGLEVEL >= 1)
+ XXH_ASSERT(secretBuffer != NULL);
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
+#else
+ /* production mode, assert() are disabled */
+ if (secretBuffer == NULL) return XXH_ERROR;
+ if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
+#endif
+
+ if (customSeedSize == 0) {
+ customSeed = XXH3_kSecret;
+ customSeedSize = XXH_SECRET_DEFAULT_SIZE;
+ }
+#if (XXH_DEBUGLEVEL >= 1)
+ XXH_ASSERT(customSeed != NULL);
+#else
+ if (customSeed == NULL) return XXH_ERROR;
+#endif
+
+ /* Fill secretBuffer with a copy of customSeed - repeat as needed */
+ { size_t pos = 0;
+ while (pos < secretSize) {
+ size_t const toCopy = XXH_MIN((secretSize - pos), customSeedSize);
+ memcpy((char*)secretBuffer + pos, customSeed, toCopy);
+ pos += toCopy;
+ } }
+
+ { size_t const nbSeg16 = secretSize / 16;
+ size_t n;
+ XXH128_canonical_t scrambler;
+ XXH128_canonicalFromHash(&scrambler, XXH128(customSeed, customSeedSize, 0));
+ for (n=0; n<nbSeg16; n++) {
+ XXH128_hash_t const h128 = XXH128(&scrambler, sizeof(scrambler), n);
+ XXH3_combine16((char*)secretBuffer + n*16, h128);
+ }
+ /* last segment */
+ XXH3_combine16((char*)secretBuffer + secretSize - 16, XXH128_hashFromCanonical(&scrambler));
+ }
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API void
+XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed)
+{
+ XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
+ XXH3_initCustomSecret(secret, seed);
+ XXH_ASSERT(secretBuffer != NULL);
+ memcpy(secretBuffer, secret, XXH_SECRET_DEFAULT_SIZE);
+}
+
+
+
+/* Pop our optimization override from above */
+#if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \
+ && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \
+ && defined(__OPTIMIZE__) && XXH_SIZE_OPT <= 0 /* respect -O0 and -Os */
+# pragma GCC pop_options
+#endif
+
+#endif /* XXH_NO_LONG_LONG */
+
+#endif /* XXH_NO_XXH3 */
+
+/*!
+ * @}
+ */
+#endif /* XXH_IMPLEMENTATION */
+
+
+#if defined (__cplusplus)
+}
+#endif