8487 lines
279 KiB
C++
8487 lines
279 KiB
C++
#pragma once
|
|
/*
|
|
CSV for C++, version 2.1.3
|
|
https://github.com/vincentlaucsb/csv-parser
|
|
|
|
MIT License
|
|
|
|
Copyright (c) 2017-2020 Vincent La
|
|
|
|
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 CSV_HPP
|
|
#define CSV_HPP
|
|
|
|
/** @file
|
|
* @brief Defines functionality needed for basic CSV parsing
|
|
*/
|
|
|
|
|
|
#include <algorithm>
|
|
#include <deque>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
/* Copyright 2017 https://github.com/mandreyel
|
|
*
|
|
* 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 MIO_MMAP_HEADER
|
|
#define MIO_MMAP_HEADER
|
|
|
|
// #include "mio/page.hpp"
|
|
/* Copyright 2017 https://github.com/mandreyel
|
|
*
|
|
* 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 MIO_PAGE_HEADER
|
|
#define MIO_PAGE_HEADER
|
|
|
|
#ifdef _WIN32
|
|
# include <windows.h>
|
|
#else
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
namespace mio {
|
|
|
|
/**
|
|
* This is used by `basic_mmap` to determine whether to create a read-only or
|
|
* a read-write memory mapping.
|
|
*/
|
|
enum class access_mode
|
|
{
|
|
read,
|
|
write
|
|
};
|
|
|
|
/**
|
|
* Determines the operating system's page allocation granularity.
|
|
*
|
|
* On the first call to this function, it invokes the operating system specific syscall
|
|
* to determine the page size, caches the value, and returns it. Any subsequent call to
|
|
* this function serves the cached value, so no further syscalls are made.
|
|
*/
|
|
inline size_t page_size()
|
|
{
|
|
static const size_t page_size = []
|
|
{
|
|
#ifdef _WIN32
|
|
SYSTEM_INFO SystemInfo;
|
|
GetSystemInfo(&SystemInfo);
|
|
return SystemInfo.dwAllocationGranularity;
|
|
#else
|
|
return sysconf(_SC_PAGE_SIZE);
|
|
#endif
|
|
}();
|
|
return page_size;
|
|
}
|
|
|
|
/**
|
|
* Alligns `offset` to the operating's system page size such that it subtracts the
|
|
* difference until the nearest page boundary before `offset`, or does nothing if
|
|
* `offset` is already page aligned.
|
|
*/
|
|
inline size_t make_offset_page_aligned(size_t offset) noexcept
|
|
{
|
|
const size_t page_size_ = page_size();
|
|
// Use integer division to round down to the nearest page alignment.
|
|
return offset / page_size_ * page_size_;
|
|
}
|
|
|
|
} // namespace mio
|
|
|
|
#endif // MIO_PAGE_HEADER
|
|
|
|
|
|
#include <iterator>
|
|
#include <string>
|
|
#include <system_error>
|
|
#include <cstdint>
|
|
|
|
#ifdef _WIN32
|
|
# ifndef WIN32_LEAN_AND_MEAN
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# endif // WIN32_LEAN_AND_MEAN
|
|
# include <windows.h>
|
|
#else // ifdef _WIN32
|
|
# define INVALID_HANDLE_VALUE -1
|
|
#endif // ifdef _WIN32
|
|
|
|
namespace mio {
|
|
|
|
// This value may be provided as the `length` parameter to the constructor or
|
|
// `map`, in which case a memory mapping of the entire file is created.
|
|
enum { map_entire_file = 0 };
|
|
|
|
#ifdef _WIN32
|
|
using file_handle_type = HANDLE;
|
|
#else
|
|
using file_handle_type = int;
|
|
#endif
|
|
|
|
// This value represents an invalid file handle type. This can be used to
|
|
// determine whether `basic_mmap::file_handle` is valid, for example.
|
|
const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE;
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
struct basic_mmap
|
|
{
|
|
using value_type = ByteT;
|
|
using size_type = size_t;
|
|
using reference = value_type&;
|
|
using const_reference = const value_type&;
|
|
using pointer = value_type*;
|
|
using const_pointer = const value_type*;
|
|
using difference_type = std::ptrdiff_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>;
|
|
using iterator_category = std::random_access_iterator_tag;
|
|
using handle_type = file_handle_type;
|
|
|
|
static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char.");
|
|
|
|
private:
|
|
// Points to the first requested byte, and not to the actual start of the mapping.
|
|
pointer data_ = nullptr;
|
|
|
|
// Length--in bytes--requested by user (which may not be the length of the
|
|
// full mapping) and the length of the full mapping.
|
|
size_type length_ = 0;
|
|
size_type mapped_length_ = 0;
|
|
|
|
// Letting user map a file using both an existing file handle and a path
|
|
// introcudes some complexity (see `is_handle_internal_`).
|
|
// On POSIX, we only need a file handle to create a mapping, while on
|
|
// Windows systems the file handle is necessary to retrieve a file mapping
|
|
// handle, but any subsequent operations on the mapped region must be done
|
|
// through the latter.
|
|
handle_type file_handle_ = INVALID_HANDLE_VALUE;
|
|
#ifdef _WIN32
|
|
handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE;
|
|
#endif
|
|
|
|
// Letting user map a file using both an existing file handle and a path
|
|
// introcudes some complexity in that we must not close the file handle if
|
|
// user provided it, but we must close it if we obtained it using the
|
|
// provided path. For this reason, this flag is used to determine when to
|
|
// close `file_handle_`.
|
|
bool is_handle_internal_;
|
|
|
|
public:
|
|
/**
|
|
* The default constructed mmap object is in a non-mapped state, that is,
|
|
* any operation that attempts to access nonexistent underlying data will
|
|
* result in undefined behaviour/segmentation faults.
|
|
*/
|
|
basic_mmap() = default;
|
|
|
|
#ifdef __cpp_exceptions
|
|
/**
|
|
* The same as invoking the `map` function, except any error that may occur
|
|
* while establishing the mapping is wrapped in a `std::system_error` and is
|
|
* thrown.
|
|
*/
|
|
template<typename String>
|
|
basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file)
|
|
{
|
|
std::error_code error;
|
|
map(path, offset, length, error);
|
|
if(error) { throw std::system_error(error); }
|
|
}
|
|
|
|
/**
|
|
* The same as invoking the `map` function, except any error that may occur
|
|
* while establishing the mapping is wrapped in a `std::system_error` and is
|
|
* thrown.
|
|
*/
|
|
basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file)
|
|
{
|
|
std::error_code error;
|
|
map(handle, offset, length, error);
|
|
if(error) { throw std::system_error(error); }
|
|
}
|
|
#endif // __cpp_exceptions
|
|
|
|
/**
|
|
* `basic_mmap` has single-ownership semantics, so transferring ownership
|
|
* may only be accomplished by moving the object.
|
|
*/
|
|
basic_mmap(const basic_mmap&) = delete;
|
|
basic_mmap(basic_mmap&&);
|
|
basic_mmap& operator=(const basic_mmap&) = delete;
|
|
basic_mmap& operator=(basic_mmap&&);
|
|
|
|
/**
|
|
* If this is a read-write mapping, the destructor invokes sync. Regardless
|
|
* of the access mode, unmap is invoked as a final step.
|
|
*/
|
|
~basic_mmap();
|
|
|
|
/**
|
|
* On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
|
|
* however, a mapped region of a file gets its own handle, which is returned by
|
|
* 'mapping_handle'.
|
|
*/
|
|
handle_type file_handle() const noexcept { return file_handle_; }
|
|
handle_type mapping_handle() const noexcept;
|
|
|
|
/** Returns whether a valid memory mapping has been created. */
|
|
bool is_open() const noexcept { return file_handle_ != invalid_handle; }
|
|
|
|
/**
|
|
* Returns true if no mapping was established, that is, conceptually the
|
|
* same as though the length that was mapped was 0. This function is
|
|
* provided so that this class has Container semantics.
|
|
*/
|
|
bool empty() const noexcept { return length() == 0; }
|
|
|
|
/** Returns true if a mapping was established. */
|
|
bool is_mapped() const noexcept;
|
|
|
|
/**
|
|
* `size` and `length` both return the logical length, i.e. the number of bytes
|
|
* user requested to be mapped, while `mapped_length` returns the actual number of
|
|
* bytes that were mapped which is a multiple of the underlying operating system's
|
|
* page allocation granularity.
|
|
*/
|
|
size_type size() const noexcept { return length(); }
|
|
size_type length() const noexcept { return length_; }
|
|
size_type mapped_length() const noexcept { return mapped_length_; }
|
|
|
|
/** Returns the offset relative to the start of the mapping. */
|
|
size_type mapping_offset() const noexcept
|
|
{
|
|
return mapped_length_ - length_;
|
|
}
|
|
|
|
/**
|
|
* Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
|
|
* exists.
|
|
*/
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> pointer data() noexcept { return data_; }
|
|
const_pointer data() const noexcept { return data_; }
|
|
|
|
/**
|
|
* Returns an iterator to the first requested byte, if a valid memory mapping
|
|
* exists, otherwise this function call is undefined behaviour.
|
|
*/
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> iterator begin() noexcept { return data(); }
|
|
const_iterator begin() const noexcept { return data(); }
|
|
const_iterator cbegin() const noexcept { return data(); }
|
|
|
|
/**
|
|
* Returns an iterator one past the last requested byte, if a valid memory mapping
|
|
* exists, otherwise this function call is undefined behaviour.
|
|
*/
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> iterator end() noexcept { return data() + length(); }
|
|
const_iterator end() const noexcept { return data() + length(); }
|
|
const_iterator cend() const noexcept { return data() + length(); }
|
|
|
|
/**
|
|
* Returns a reverse iterator to the last memory mapped byte, if a valid
|
|
* memory mapping exists, otherwise this function call is undefined
|
|
* behaviour.
|
|
*/
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
|
|
const_reverse_iterator rbegin() const noexcept
|
|
{ return const_reverse_iterator(end()); }
|
|
const_reverse_iterator crbegin() const noexcept
|
|
{ return const_reverse_iterator(end()); }
|
|
|
|
/**
|
|
* Returns a reverse iterator past the first mapped byte, if a valid memory
|
|
* mapping exists, otherwise this function call is undefined behaviour.
|
|
*/
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
|
|
const_reverse_iterator rend() const noexcept
|
|
{ return const_reverse_iterator(begin()); }
|
|
const_reverse_iterator crend() const noexcept
|
|
{ return const_reverse_iterator(begin()); }
|
|
|
|
/**
|
|
* Returns a reference to the `i`th byte from the first requested byte (as returned
|
|
* by `data`). If this is invoked when no valid memory mapping has been created
|
|
* prior to this call, undefined behaviour ensues.
|
|
*/
|
|
reference operator[](const size_type i) noexcept { return data_[i]; }
|
|
const_reference operator[](const size_type i) const noexcept { return data_[i]; }
|
|
|
|
/**
|
|
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
|
|
* reason is reported via `error` and the object remains in a state as if this
|
|
* function hadn't been called.
|
|
*
|
|
* `path`, which must be a path to an existing file, is used to retrieve a file
|
|
* handle (which is closed when the object destructs or `unmap` is called), which is
|
|
* then used to memory map the requested region. Upon failure, `error` is set to
|
|
* indicate the reason and the object remains in an unmapped state.
|
|
*
|
|
* `offset` is the number of bytes, relative to the start of the file, where the
|
|
* mapping should begin. When specifying it, there is no need to worry about
|
|
* providing a value that is aligned with the operating system's page allocation
|
|
* granularity. This is adjusted by the implementation such that the first requested
|
|
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
|
|
* `offset` from the start of the file.
|
|
*
|
|
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
|
|
* case a mapping of the entire file is created.
|
|
*/
|
|
template<typename String>
|
|
void map(const String& path, const size_type offset,
|
|
const size_type length, std::error_code& error);
|
|
|
|
/**
|
|
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
|
|
* reason is reported via `error` and the object remains in a state as if this
|
|
* function hadn't been called.
|
|
*
|
|
* `path`, which must be a path to an existing file, is used to retrieve a file
|
|
* handle (which is closed when the object destructs or `unmap` is called), which is
|
|
* then used to memory map the requested region. Upon failure, `error` is set to
|
|
* indicate the reason and the object remains in an unmapped state.
|
|
*
|
|
* The entire file is mapped.
|
|
*/
|
|
template<typename String>
|
|
void map(const String& path, std::error_code& error)
|
|
{
|
|
map(path, 0, map_entire_file, error);
|
|
}
|
|
|
|
/**
|
|
* Establishes a memory mapping with AccessMode. If the mapping is
|
|
* unsuccesful, the reason is reported via `error` and the object remains in
|
|
* a state as if this function hadn't been called.
|
|
*
|
|
* `handle`, which must be a valid file handle, which is used to memory map the
|
|
* requested region. Upon failure, `error` is set to indicate the reason and the
|
|
* object remains in an unmapped state.
|
|
*
|
|
* `offset` is the number of bytes, relative to the start of the file, where the
|
|
* mapping should begin. When specifying it, there is no need to worry about
|
|
* providing a value that is aligned with the operating system's page allocation
|
|
* granularity. This is adjusted by the implementation such that the first requested
|
|
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
|
|
* `offset` from the start of the file.
|
|
*
|
|
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
|
|
* case a mapping of the entire file is created.
|
|
*/
|
|
void map(const handle_type handle, const size_type offset,
|
|
const size_type length, std::error_code& error);
|
|
|
|
/**
|
|
* Establishes a memory mapping with AccessMode. If the mapping is
|
|
* unsuccesful, the reason is reported via `error` and the object remains in
|
|
* a state as if this function hadn't been called.
|
|
*
|
|
* `handle`, which must be a valid file handle, which is used to memory map the
|
|
* requested region. Upon failure, `error` is set to indicate the reason and the
|
|
* object remains in an unmapped state.
|
|
*
|
|
* The entire file is mapped.
|
|
*/
|
|
void map(const handle_type handle, std::error_code& error)
|
|
{
|
|
map(handle, 0, map_entire_file, error);
|
|
}
|
|
|
|
/**
|
|
* If a valid memory mapping has been created prior to this call, this call
|
|
* instructs the kernel to unmap the memory region and disassociate this object
|
|
* from the file.
|
|
*
|
|
* The file handle associated with the file that is mapped is only closed if the
|
|
* mapping was created using a file path. If, on the other hand, an existing
|
|
* file handle was used to create the mapping, the file handle is not closed.
|
|
*/
|
|
void unmap();
|
|
|
|
void swap(basic_mmap& other);
|
|
|
|
/** Flushes the memory mapped page to disk. Errors are reported via `error`. */
|
|
template<access_mode A = AccessMode>
|
|
typename std::enable_if<A == access_mode::write, void>::type
|
|
sync(std::error_code& error);
|
|
|
|
/**
|
|
* All operators compare the address of the first byte and size of the two mapped
|
|
* regions.
|
|
*/
|
|
|
|
private:
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> pointer get_mapping_start() noexcept
|
|
{
|
|
return !data() ? nullptr : data() - mapping_offset();
|
|
}
|
|
|
|
const_pointer get_mapping_start() const noexcept
|
|
{
|
|
return !data() ? nullptr : data() - mapping_offset();
|
|
}
|
|
|
|
/**
|
|
* The destructor syncs changes to disk if `AccessMode` is `write`, but not
|
|
* if it's `read`, but since the destructor cannot be templated, we need to
|
|
* do SFINAE in a dedicated function, where one syncs and the other is a noop.
|
|
*/
|
|
template<access_mode A = AccessMode>
|
|
typename std::enable_if<A == access_mode::write, void>::type
|
|
conditional_sync();
|
|
template<access_mode A = AccessMode>
|
|
typename std::enable_if<A == access_mode::read, void>::type conditional_sync();
|
|
};
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator==(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b);
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator!=(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b);
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator<(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b);
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator<=(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b);
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator>(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b);
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator>=(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b);
|
|
|
|
/**
|
|
* This is the basis for all read-only mmap objects and should be preferred over
|
|
* directly using `basic_mmap`.
|
|
*/
|
|
template<typename ByteT>
|
|
using basic_mmap_source = basic_mmap<access_mode::read, ByteT>;
|
|
|
|
/**
|
|
* This is the basis for all read-write mmap objects and should be preferred over
|
|
* directly using `basic_mmap`.
|
|
*/
|
|
template<typename ByteT>
|
|
using basic_mmap_sink = basic_mmap<access_mode::write, ByteT>;
|
|
|
|
/**
|
|
* These aliases cover the most common use cases, both representing a raw byte stream
|
|
* (either with a char or an unsigned char/uint8_t).
|
|
*/
|
|
using mmap_source = basic_mmap_source<char>;
|
|
using ummap_source = basic_mmap_source<unsigned char>;
|
|
|
|
using mmap_sink = basic_mmap_sink<char>;
|
|
using ummap_sink = basic_mmap_sink<unsigned char>;
|
|
|
|
/**
|
|
* Convenience factory method that constructs a mapping for any `basic_mmap` or
|
|
* `basic_mmap` type.
|
|
*/
|
|
template<
|
|
typename MMap,
|
|
typename MappingToken
|
|
> MMap make_mmap(const MappingToken& token,
|
|
int64_t offset, int64_t length, std::error_code& error)
|
|
{
|
|
MMap mmap;
|
|
mmap.map(token, offset, length, error);
|
|
return mmap;
|
|
}
|
|
|
|
/**
|
|
* Convenience factory method.
|
|
*
|
|
* MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
|
|
* `std::filesystem::path`, `std::vector<char>`, or similar), or a
|
|
* `mmap_source::handle_type`.
|
|
*/
|
|
template<typename MappingToken>
|
|
mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset,
|
|
mmap_source::size_type length, std::error_code& error)
|
|
{
|
|
return make_mmap<mmap_source>(token, offset, length, error);
|
|
}
|
|
|
|
template<typename MappingToken>
|
|
mmap_source make_mmap_source(const MappingToken& token, std::error_code& error)
|
|
{
|
|
return make_mmap_source(token, 0, map_entire_file, error);
|
|
}
|
|
|
|
/**
|
|
* Convenience factory method.
|
|
*
|
|
* MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
|
|
* `std::filesystem::path`, `std::vector<char>`, or similar), or a
|
|
* `mmap_sink::handle_type`.
|
|
*/
|
|
template<typename MappingToken>
|
|
mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset,
|
|
mmap_sink::size_type length, std::error_code& error)
|
|
{
|
|
return make_mmap<mmap_sink>(token, offset, length, error);
|
|
}
|
|
|
|
template<typename MappingToken>
|
|
mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error)
|
|
{
|
|
return make_mmap_sink(token, 0, map_entire_file, error);
|
|
}
|
|
|
|
} // namespace mio
|
|
|
|
// #include "detail/mmap.ipp"
|
|
/* Copyright 2017 https://github.com/mandreyel
|
|
*
|
|
* 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 MIO_BASIC_MMAP_IMPL
|
|
#define MIO_BASIC_MMAP_IMPL
|
|
|
|
// #include "mio/mmap.hpp"
|
|
|
|
// #include "mio/page.hpp"
|
|
|
|
// #include "mio/detail/string_util.hpp"
|
|
/* Copyright 2017 https://github.com/mandreyel
|
|
*
|
|
* 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 MIO_STRING_UTIL_HEADER
|
|
#define MIO_STRING_UTIL_HEADER
|
|
|
|
#include <type_traits>
|
|
|
|
namespace mio {
|
|
namespace detail {
|
|
|
|
template<
|
|
typename S,
|
|
typename C = typename std::decay<S>::type,
|
|
typename = decltype(std::declval<C>().data()),
|
|
typename = typename std::enable_if<
|
|
std::is_same<typename C::value_type, char>::value
|
|
#ifdef _WIN32
|
|
|| std::is_same<typename C::value_type, wchar_t>::value
|
|
#endif
|
|
>::type
|
|
> struct char_type_helper {
|
|
using type = typename C::value_type;
|
|
};
|
|
|
|
template<class T>
|
|
struct char_type {
|
|
using type = typename char_type_helper<T>::type;
|
|
};
|
|
|
|
// TODO: can we avoid this brute force approach?
|
|
template<>
|
|
struct char_type<char*> {
|
|
using type = char;
|
|
};
|
|
|
|
template<>
|
|
struct char_type<const char*> {
|
|
using type = char;
|
|
};
|
|
|
|
template<size_t N>
|
|
struct char_type<char[N]> {
|
|
using type = char;
|
|
};
|
|
|
|
template<size_t N>
|
|
struct char_type<const char[N]> {
|
|
using type = char;
|
|
};
|
|
|
|
#ifdef _WIN32
|
|
template<>
|
|
struct char_type<wchar_t*> {
|
|
using type = wchar_t;
|
|
};
|
|
|
|
template<>
|
|
struct char_type<const wchar_t*> {
|
|
using type = wchar_t;
|
|
};
|
|
|
|
template<size_t N>
|
|
struct char_type<wchar_t[N]> {
|
|
using type = wchar_t;
|
|
};
|
|
|
|
template<size_t N>
|
|
struct char_type<const wchar_t[N]> {
|
|
using type = wchar_t;
|
|
};
|
|
#endif // _WIN32
|
|
|
|
template<typename CharT, typename S>
|
|
struct is_c_str_helper
|
|
{
|
|
static constexpr bool value = std::is_same<
|
|
CharT*,
|
|
// TODO: I'm so sorry for this... Can this be made cleaner?
|
|
typename std::add_pointer<
|
|
typename std::remove_cv<
|
|
typename std::remove_pointer<
|
|
typename std::decay<
|
|
S
|
|
>::type
|
|
>::type
|
|
>::type
|
|
>::type
|
|
>::value;
|
|
};
|
|
|
|
template<typename S>
|
|
struct is_c_str
|
|
{
|
|
static constexpr bool value = is_c_str_helper<char, S>::value;
|
|
};
|
|
|
|
#ifdef _WIN32
|
|
template<typename S>
|
|
struct is_c_wstr
|
|
{
|
|
static constexpr bool value = is_c_str_helper<wchar_t, S>::value;
|
|
};
|
|
#endif // _WIN32
|
|
|
|
template<typename S>
|
|
struct is_c_str_or_c_wstr
|
|
{
|
|
static constexpr bool value = is_c_str<S>::value
|
|
#ifdef _WIN32
|
|
|| is_c_wstr<S>::value
|
|
#endif
|
|
;
|
|
};
|
|
|
|
template<
|
|
typename String,
|
|
typename = decltype(std::declval<String>().data()),
|
|
typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type
|
|
> const typename char_type<String>::type* c_str(const String& path)
|
|
{
|
|
return path.data();
|
|
}
|
|
|
|
template<
|
|
typename String,
|
|
typename = decltype(std::declval<String>().empty()),
|
|
typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type
|
|
> bool empty(const String& path)
|
|
{
|
|
return path.empty();
|
|
}
|
|
|
|
template<
|
|
typename String,
|
|
typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type
|
|
> const typename char_type<String>::type* c_str(String path)
|
|
{
|
|
return path;
|
|
}
|
|
|
|
template<
|
|
typename String,
|
|
typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type
|
|
> bool empty(String path)
|
|
{
|
|
return !path || (*path == 0);
|
|
}
|
|
|
|
} // namespace detail
|
|
} // namespace mio
|
|
|
|
#endif // MIO_STRING_UTIL_HEADER
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#ifndef _WIN32
|
|
# include <unistd.h>
|
|
# include <fcntl.h>
|
|
# include <sys/mman.h>
|
|
# include <sys/stat.h>
|
|
#endif
|
|
|
|
namespace mio {
|
|
namespace detail {
|
|
|
|
#ifdef _WIN32
|
|
namespace win {
|
|
|
|
/** Returns the 4 upper bytes of an 8-byte integer. */
|
|
inline DWORD int64_high(int64_t n) noexcept
|
|
{
|
|
return n >> 32;
|
|
}
|
|
|
|
/** Returns the 4 lower bytes of an 8-byte integer. */
|
|
inline DWORD int64_low(int64_t n) noexcept
|
|
{
|
|
return n & 0xffffffff;
|
|
}
|
|
|
|
template<
|
|
typename String,
|
|
typename = typename std::enable_if<
|
|
std::is_same<typename char_type<String>::type, char>::value
|
|
>::type
|
|
> file_handle_type open_file_helper(const String& path, const access_mode mode)
|
|
{
|
|
return ::CreateFileA(c_str(path),
|
|
mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
0,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
0);
|
|
}
|
|
|
|
template<typename String>
|
|
typename std::enable_if<
|
|
std::is_same<typename char_type<String>::type, wchar_t>::value,
|
|
file_handle_type
|
|
>::type open_file_helper(const String& path, const access_mode mode)
|
|
{
|
|
return ::CreateFileW(c_str(path),
|
|
mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
0,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
0);
|
|
}
|
|
|
|
} // win
|
|
#endif // _WIN32
|
|
|
|
/**
|
|
* Returns the last platform specific system error (errno on POSIX and
|
|
* GetLastError on Win) as a `std::error_code`.
|
|
*/
|
|
inline std::error_code last_error() noexcept
|
|
{
|
|
std::error_code error;
|
|
#ifdef _WIN32
|
|
error.assign(GetLastError(), std::system_category());
|
|
#else
|
|
error.assign(errno, std::system_category());
|
|
#endif
|
|
return error;
|
|
}
|
|
|
|
template<typename String>
|
|
file_handle_type open_file(const String& path, const access_mode mode,
|
|
std::error_code& error)
|
|
{
|
|
error.clear();
|
|
if(detail::empty(path))
|
|
{
|
|
error = std::make_error_code(std::errc::invalid_argument);
|
|
return invalid_handle;
|
|
}
|
|
#ifdef _WIN32
|
|
const auto handle = win::open_file_helper(path, mode);
|
|
#else // POSIX
|
|
const auto handle = ::open(c_str(path),
|
|
mode == access_mode::read ? O_RDONLY : O_RDWR);
|
|
#endif
|
|
if(handle == invalid_handle)
|
|
{
|
|
error = detail::last_error();
|
|
}
|
|
return handle;
|
|
}
|
|
|
|
inline size_t query_file_size(file_handle_type handle, std::error_code& error)
|
|
{
|
|
error.clear();
|
|
#ifdef _WIN32
|
|
LARGE_INTEGER file_size;
|
|
if(::GetFileSizeEx(handle, &file_size) == 0)
|
|
{
|
|
error = detail::last_error();
|
|
return 0;
|
|
}
|
|
return static_cast<int64_t>(file_size.QuadPart);
|
|
#else // POSIX
|
|
struct stat sbuf;
|
|
if(::fstat(handle, &sbuf) == -1)
|
|
{
|
|
error = detail::last_error();
|
|
return 0;
|
|
}
|
|
return sbuf.st_size;
|
|
#endif
|
|
}
|
|
|
|
struct mmap_context
|
|
{
|
|
char* data;
|
|
int64_t length;
|
|
int64_t mapped_length;
|
|
#ifdef _WIN32
|
|
file_handle_type file_mapping_handle;
|
|
#endif
|
|
};
|
|
|
|
inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset,
|
|
const int64_t length, const access_mode mode, std::error_code& error)
|
|
{
|
|
const int64_t aligned_offset = make_offset_page_aligned(offset);
|
|
const int64_t length_to_map = offset - aligned_offset + length;
|
|
#ifdef _WIN32
|
|
const int64_t max_file_size = offset + length;
|
|
const auto file_mapping_handle = ::CreateFileMapping(
|
|
file_handle,
|
|
0,
|
|
mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE,
|
|
win::int64_high(max_file_size),
|
|
win::int64_low(max_file_size),
|
|
0);
|
|
if(file_mapping_handle == invalid_handle)
|
|
{
|
|
error = detail::last_error();
|
|
return {};
|
|
}
|
|
char* mapping_start = static_cast<char*>(::MapViewOfFile(
|
|
file_mapping_handle,
|
|
mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE,
|
|
win::int64_high(aligned_offset),
|
|
win::int64_low(aligned_offset),
|
|
length_to_map));
|
|
if(mapping_start == nullptr)
|
|
{
|
|
// Close file handle if mapping it failed.
|
|
::CloseHandle(file_mapping_handle);
|
|
error = detail::last_error();
|
|
return {};
|
|
}
|
|
#else // POSIX
|
|
char* mapping_start = static_cast<char*>(::mmap(
|
|
0, // Don't give hint as to where to map.
|
|
length_to_map,
|
|
mode == access_mode::read ? PROT_READ : PROT_WRITE,
|
|
MAP_SHARED,
|
|
file_handle,
|
|
aligned_offset));
|
|
if(mapping_start == MAP_FAILED)
|
|
{
|
|
error = detail::last_error();
|
|
return {};
|
|
}
|
|
#endif
|
|
mmap_context ctx;
|
|
ctx.data = mapping_start + offset - aligned_offset;
|
|
ctx.length = length;
|
|
ctx.mapped_length = length_to_map;
|
|
#ifdef _WIN32
|
|
ctx.file_mapping_handle = file_mapping_handle;
|
|
#endif
|
|
return ctx;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
// -- basic_mmap --
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
basic_mmap<AccessMode, ByteT>::~basic_mmap()
|
|
{
|
|
conditional_sync();
|
|
unmap();
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
basic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap&& other)
|
|
: data_(std::move(other.data_))
|
|
, length_(std::move(other.length_))
|
|
, mapped_length_(std::move(other.mapped_length_))
|
|
, file_handle_(std::move(other.file_handle_))
|
|
#ifdef _WIN32
|
|
, file_mapping_handle_(std::move(other.file_mapping_handle_))
|
|
#endif
|
|
, is_handle_internal_(std::move(other.is_handle_internal_))
|
|
{
|
|
other.data_ = nullptr;
|
|
other.length_ = other.mapped_length_ = 0;
|
|
other.file_handle_ = invalid_handle;
|
|
#ifdef _WIN32
|
|
other.file_mapping_handle_ = invalid_handle;
|
|
#endif
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
basic_mmap<AccessMode, ByteT>&
|
|
basic_mmap<AccessMode, ByteT>::operator=(basic_mmap&& other)
|
|
{
|
|
if(this != &other)
|
|
{
|
|
// First the existing mapping needs to be removed.
|
|
unmap();
|
|
data_ = std::move(other.data_);
|
|
length_ = std::move(other.length_);
|
|
mapped_length_ = std::move(other.mapped_length_);
|
|
file_handle_ = std::move(other.file_handle_);
|
|
#ifdef _WIN32
|
|
file_mapping_handle_ = std::move(other.file_mapping_handle_);
|
|
#endif
|
|
is_handle_internal_ = std::move(other.is_handle_internal_);
|
|
|
|
// The moved from basic_mmap's fields need to be reset, because
|
|
// otherwise other's destructor will unmap the same mapping that was
|
|
// just moved into this.
|
|
other.data_ = nullptr;
|
|
other.length_ = other.mapped_length_ = 0;
|
|
other.file_handle_ = invalid_handle;
|
|
#ifdef _WIN32
|
|
other.file_mapping_handle_ = invalid_handle;
|
|
#endif
|
|
other.is_handle_internal_ = false;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
typename basic_mmap<AccessMode, ByteT>::handle_type
|
|
basic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept
|
|
{
|
|
#ifdef _WIN32
|
|
return file_mapping_handle_;
|
|
#else
|
|
return file_handle_;
|
|
#endif
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
template<typename String>
|
|
void basic_mmap<AccessMode, ByteT>::map(const String& path, const size_type offset,
|
|
const size_type length, std::error_code& error)
|
|
{
|
|
error.clear();
|
|
if(detail::empty(path))
|
|
{
|
|
error = std::make_error_code(std::errc::invalid_argument);
|
|
return;
|
|
}
|
|
const auto handle = detail::open_file(path, AccessMode, error);
|
|
if(error)
|
|
{
|
|
return;
|
|
}
|
|
|
|
map(handle, offset, length, error);
|
|
// This MUST be after the call to map, as that sets this to true.
|
|
if(!error)
|
|
{
|
|
is_handle_internal_ = true;
|
|
}
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
void basic_mmap<AccessMode, ByteT>::map(const handle_type handle,
|
|
const size_type offset, const size_type length, std::error_code& error)
|
|
{
|
|
error.clear();
|
|
if(handle == invalid_handle)
|
|
{
|
|
error = std::make_error_code(std::errc::bad_file_descriptor);
|
|
return;
|
|
}
|
|
|
|
const auto file_size = detail::query_file_size(handle, error);
|
|
if(error)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(offset + length > file_size)
|
|
{
|
|
error = std::make_error_code(std::errc::invalid_argument);
|
|
return;
|
|
}
|
|
|
|
const auto ctx = detail::memory_map(handle, offset,
|
|
length == map_entire_file ? (file_size - offset) : length,
|
|
AccessMode, error);
|
|
if(!error)
|
|
{
|
|
// We must unmap the previous mapping that may have existed prior to this call.
|
|
// Note that this must only be invoked after a new mapping has been created in
|
|
// order to provide the strong guarantee that, should the new mapping fail, the
|
|
// `map` function leaves this instance in a state as though the function had
|
|
// never been invoked.
|
|
unmap();
|
|
file_handle_ = handle;
|
|
is_handle_internal_ = false;
|
|
data_ = reinterpret_cast<pointer>(ctx.data);
|
|
length_ = ctx.length;
|
|
mapped_length_ = ctx.mapped_length;
|
|
#ifdef _WIN32
|
|
file_mapping_handle_ = ctx.file_mapping_handle;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
template<access_mode A>
|
|
typename std::enable_if<A == access_mode::write, void>::type
|
|
basic_mmap<AccessMode, ByteT>::sync(std::error_code& error)
|
|
{
|
|
error.clear();
|
|
if(!is_open())
|
|
{
|
|
error = std::make_error_code(std::errc::bad_file_descriptor);
|
|
return;
|
|
}
|
|
|
|
if(data())
|
|
{
|
|
#ifdef _WIN32
|
|
if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0
|
|
|| ::FlushFileBuffers(file_handle_) == 0)
|
|
#else // POSIX
|
|
if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0)
|
|
#endif
|
|
{
|
|
error = detail::last_error();
|
|
return;
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
if(::FlushFileBuffers(file_handle_) == 0)
|
|
{
|
|
error = detail::last_error();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
void basic_mmap<AccessMode, ByteT>::unmap()
|
|
{
|
|
if(!is_open()) { return; }
|
|
// TODO do we care about errors here?
|
|
#ifdef _WIN32
|
|
if(is_mapped())
|
|
{
|
|
::UnmapViewOfFile(get_mapping_start());
|
|
::CloseHandle(file_mapping_handle_);
|
|
}
|
|
#else // POSIX
|
|
if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); }
|
|
#endif
|
|
|
|
// If `file_handle_` was obtained by our opening it (when map is called with
|
|
// a path, rather than an existing file handle), we need to close it,
|
|
// otherwise it must not be closed as it may still be used outside this
|
|
// instance.
|
|
if(is_handle_internal_)
|
|
{
|
|
#ifdef _WIN32
|
|
::CloseHandle(file_handle_);
|
|
#else // POSIX
|
|
::close(file_handle_);
|
|
#endif
|
|
}
|
|
|
|
// Reset fields to their default values.
|
|
data_ = nullptr;
|
|
length_ = mapped_length_ = 0;
|
|
file_handle_ = invalid_handle;
|
|
#ifdef _WIN32
|
|
file_mapping_handle_ = invalid_handle;
|
|
#endif
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept
|
|
{
|
|
#ifdef _WIN32
|
|
return file_mapping_handle_ != invalid_handle;
|
|
#else // POSIX
|
|
return is_open();
|
|
#endif
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
void basic_mmap<AccessMode, ByteT>::swap(basic_mmap& other)
|
|
{
|
|
if(this != &other)
|
|
{
|
|
using std::swap;
|
|
swap(data_, other.data_);
|
|
swap(file_handle_, other.file_handle_);
|
|
#ifdef _WIN32
|
|
swap(file_mapping_handle_, other.file_mapping_handle_);
|
|
#endif
|
|
swap(length_, other.length_);
|
|
swap(mapped_length_, other.mapped_length_);
|
|
swap(is_handle_internal_, other.is_handle_internal_);
|
|
}
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
template<access_mode A>
|
|
typename std::enable_if<A == access_mode::write, void>::type
|
|
basic_mmap<AccessMode, ByteT>::conditional_sync()
|
|
{
|
|
// This is invoked from the destructor, so not much we can do about
|
|
// failures here.
|
|
std::error_code ec;
|
|
sync(ec);
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
template<access_mode A>
|
|
typename std::enable_if<A == access_mode::read, void>::type
|
|
basic_mmap<AccessMode, ByteT>::conditional_sync()
|
|
{
|
|
// noop
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator==(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b)
|
|
{
|
|
return a.data() == b.data()
|
|
&& a.size() == b.size();
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator!=(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator<(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b)
|
|
{
|
|
if(a.data() == b.data()) { return a.size() < b.size(); }
|
|
return a.data() < b.data();
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator<=(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b)
|
|
{
|
|
return !(a > b);
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator>(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b)
|
|
{
|
|
if(a.data() == b.data()) { return a.size() > b.size(); }
|
|
return a.data() > b.data();
|
|
}
|
|
|
|
template<access_mode AccessMode, typename ByteT>
|
|
bool operator>=(const basic_mmap<AccessMode, ByteT>& a,
|
|
const basic_mmap<AccessMode, ByteT>& b)
|
|
{
|
|
return !(a < b);
|
|
}
|
|
|
|
} // namespace mio
|
|
|
|
#endif // MIO_BASIC_MMAP_IMPL
|
|
|
|
|
|
#endif // MIO_MMAP_HEADER
|
|
/* Copyright 2017 https://github.com/mandreyel
|
|
*
|
|
* 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 MIO_PAGE_HEADER
|
|
#define MIO_PAGE_HEADER
|
|
|
|
#ifdef _WIN32
|
|
# include <windows.h>
|
|
#else
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
namespace mio {
|
|
|
|
/**
|
|
* This is used by `basic_mmap` to determine whether to create a read-only or
|
|
* a read-write memory mapping.
|
|
*/
|
|
enum class access_mode
|
|
{
|
|
read,
|
|
write
|
|
};
|
|
|
|
/**
|
|
* Determines the operating system's page allocation granularity.
|
|
*
|
|
* On the first call to this function, it invokes the operating system specific syscall
|
|
* to determine the page size, caches the value, and returns it. Any subsequent call to
|
|
* this function serves the cached value, so no further syscalls are made.
|
|
*/
|
|
inline size_t page_size()
|
|
{
|
|
static const size_t page_size = []
|
|
{
|
|
#ifdef _WIN32
|
|
SYSTEM_INFO SystemInfo;
|
|
GetSystemInfo(&SystemInfo);
|
|
return SystemInfo.dwAllocationGranularity;
|
|
#else
|
|
return sysconf(_SC_PAGE_SIZE);
|
|
#endif
|
|
}();
|
|
return page_size;
|
|
}
|
|
|
|
/**
|
|
* Alligns `offset` to the operating's system page size such that it subtracts the
|
|
* difference until the nearest page boundary before `offset`, or does nothing if
|
|
* `offset` is already page aligned.
|
|
*/
|
|
inline size_t make_offset_page_aligned(size_t offset) noexcept
|
|
{
|
|
const size_t page_size_ = page_size();
|
|
// Use integer division to round down to the nearest page alignment.
|
|
return offset / page_size_ * page_size_;
|
|
}
|
|
|
|
} // namespace mio
|
|
|
|
#endif // MIO_PAGE_HEADER
|
|
/* Copyright 2017 https://github.com/mandreyel
|
|
*
|
|
* 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 MIO_SHARED_MMAP_HEADER
|
|
#define MIO_SHARED_MMAP_HEADER
|
|
|
|
// #include "mio/mmap.hpp"
|
|
|
|
|
|
#include <system_error> // std::error_code
|
|
#include <memory> // std::shared_ptr
|
|
|
|
namespace mio {
|
|
|
|
/**
|
|
* Exposes (nearly) the same interface as `basic_mmap`, but endowes it with
|
|
* `std::shared_ptr` semantics.
|
|
*
|
|
* This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if
|
|
* shared semantics are not required.
|
|
*/
|
|
template<
|
|
access_mode AccessMode,
|
|
typename ByteT
|
|
> class basic_shared_mmap
|
|
{
|
|
using impl_type = basic_mmap<AccessMode, ByteT>;
|
|
std::shared_ptr<impl_type> pimpl_;
|
|
|
|
public:
|
|
using value_type = typename impl_type::value_type;
|
|
using size_type = typename impl_type::size_type;
|
|
using reference = typename impl_type::reference;
|
|
using const_reference = typename impl_type::const_reference;
|
|
using pointer = typename impl_type::pointer;
|
|
using const_pointer = typename impl_type::const_pointer;
|
|
using difference_type = typename impl_type::difference_type;
|
|
using iterator = typename impl_type::iterator;
|
|
using const_iterator = typename impl_type::const_iterator;
|
|
using reverse_iterator = typename impl_type::reverse_iterator;
|
|
using const_reverse_iterator = typename impl_type::const_reverse_iterator;
|
|
using iterator_category = typename impl_type::iterator_category;
|
|
using handle_type = typename impl_type::handle_type;
|
|
using mmap_type = impl_type;
|
|
|
|
basic_shared_mmap() = default;
|
|
basic_shared_mmap(const basic_shared_mmap&) = default;
|
|
basic_shared_mmap& operator=(const basic_shared_mmap&) = default;
|
|
basic_shared_mmap(basic_shared_mmap&&) = default;
|
|
basic_shared_mmap& operator=(basic_shared_mmap&&) = default;
|
|
|
|
/** Takes ownership of an existing mmap object. */
|
|
basic_shared_mmap(mmap_type&& mmap)
|
|
: pimpl_(std::make_shared<mmap_type>(std::move(mmap)))
|
|
{}
|
|
|
|
/** Takes ownership of an existing mmap object. */
|
|
basic_shared_mmap& operator=(mmap_type&& mmap)
|
|
{
|
|
pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
|
|
return *this;
|
|
}
|
|
|
|
/** Initializes this object with an already established shared mmap. */
|
|
basic_shared_mmap(std::shared_ptr<mmap_type> mmap) : pimpl_(std::move(mmap)) {}
|
|
|
|
/** Initializes this object with an already established shared mmap. */
|
|
basic_shared_mmap& operator=(std::shared_ptr<mmap_type> mmap)
|
|
{
|
|
pimpl_ = std::move(mmap);
|
|
return *this;
|
|
}
|
|
|
|
#ifdef __cpp_exceptions
|
|
/**
|
|
* The same as invoking the `map` function, except any error that may occur
|
|
* while establishing the mapping is wrapped in a `std::system_error` and is
|
|
* thrown.
|
|
*/
|
|
template<typename String>
|
|
basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file)
|
|
{
|
|
std::error_code error;
|
|
map(path, offset, length, error);
|
|
if(error) { throw std::system_error(error); }
|
|
}
|
|
|
|
/**
|
|
* The same as invoking the `map` function, except any error that may occur
|
|
* while establishing the mapping is wrapped in a `std::system_error` and is
|
|
* thrown.
|
|
*/
|
|
basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file)
|
|
{
|
|
std::error_code error;
|
|
map(handle, offset, length, error);
|
|
if(error) { throw std::system_error(error); }
|
|
}
|
|
#endif // __cpp_exceptions
|
|
|
|
/**
|
|
* If this is a read-write mapping and the last reference to the mapping,
|
|
* the destructor invokes sync. Regardless of the access mode, unmap is
|
|
* invoked as a final step.
|
|
*/
|
|
~basic_shared_mmap() = default;
|
|
|
|
/** Returns the underlying `std::shared_ptr` instance that holds the mmap. */
|
|
std::shared_ptr<mmap_type> get_shared_ptr() { return pimpl_; }
|
|
|
|
/**
|
|
* On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
|
|
* however, a mapped region of a file gets its own handle, which is returned by
|
|
* 'mapping_handle'.
|
|
*/
|
|
handle_type file_handle() const noexcept
|
|
{
|
|
return pimpl_ ? pimpl_->file_handle() : invalid_handle;
|
|
}
|
|
|
|
handle_type mapping_handle() const noexcept
|
|
{
|
|
return pimpl_ ? pimpl_->mapping_handle() : invalid_handle;
|
|
}
|
|
|
|
/** Returns whether a valid memory mapping has been created. */
|
|
bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); }
|
|
|
|
/**
|
|
* Returns true if no mapping was established, that is, conceptually the
|
|
* same as though the length that was mapped was 0. This function is
|
|
* provided so that this class has Container semantics.
|
|
*/
|
|
bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); }
|
|
|
|
/**
|
|
* `size` and `length` both return the logical length, i.e. the number of bytes
|
|
* user requested to be mapped, while `mapped_length` returns the actual number of
|
|
* bytes that were mapped which is a multiple of the underlying operating system's
|
|
* page allocation granularity.
|
|
*/
|
|
size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; }
|
|
size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; }
|
|
size_type mapped_length() const noexcept
|
|
{
|
|
return pimpl_ ? pimpl_->mapped_length() : 0;
|
|
}
|
|
|
|
/**
|
|
* Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
|
|
* exists.
|
|
*/
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> pointer data() noexcept { return pimpl_->data(); }
|
|
const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; }
|
|
|
|
/**
|
|
* Returns an iterator to the first requested byte, if a valid memory mapping
|
|
* exists, otherwise this function call is undefined behaviour.
|
|
*/
|
|
iterator begin() noexcept { return pimpl_->begin(); }
|
|
const_iterator begin() const noexcept { return pimpl_->begin(); }
|
|
const_iterator cbegin() const noexcept { return pimpl_->cbegin(); }
|
|
|
|
/**
|
|
* Returns an iterator one past the last requested byte, if a valid memory mapping
|
|
* exists, otherwise this function call is undefined behaviour.
|
|
*/
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> iterator end() noexcept { return pimpl_->end(); }
|
|
const_iterator end() const noexcept { return pimpl_->end(); }
|
|
const_iterator cend() const noexcept { return pimpl_->cend(); }
|
|
|
|
/**
|
|
* Returns a reverse iterator to the last memory mapped byte, if a valid
|
|
* memory mapping exists, otherwise this function call is undefined
|
|
* behaviour.
|
|
*/
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); }
|
|
const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); }
|
|
const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); }
|
|
|
|
/**
|
|
* Returns a reverse iterator past the first mapped byte, if a valid memory
|
|
* mapping exists, otherwise this function call is undefined behaviour.
|
|
*/
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> reverse_iterator rend() noexcept { return pimpl_->rend(); }
|
|
const_reverse_iterator rend() const noexcept { return pimpl_->rend(); }
|
|
const_reverse_iterator crend() const noexcept { return pimpl_->crend(); }
|
|
|
|
/**
|
|
* Returns a reference to the `i`th byte from the first requested byte (as returned
|
|
* by `data`). If this is invoked when no valid memory mapping has been created
|
|
* prior to this call, undefined behaviour ensues.
|
|
*/
|
|
reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; }
|
|
const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; }
|
|
|
|
/**
|
|
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
|
|
* reason is reported via `error` and the object remains in a state as if this
|
|
* function hadn't been called.
|
|
*
|
|
* `path`, which must be a path to an existing file, is used to retrieve a file
|
|
* handle (which is closed when the object destructs or `unmap` is called), which is
|
|
* then used to memory map the requested region. Upon failure, `error` is set to
|
|
* indicate the reason and the object remains in an unmapped state.
|
|
*
|
|
* `offset` is the number of bytes, relative to the start of the file, where the
|
|
* mapping should begin. When specifying it, there is no need to worry about
|
|
* providing a value that is aligned with the operating system's page allocation
|
|
* granularity. This is adjusted by the implementation such that the first requested
|
|
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
|
|
* `offset` from the start of the file.
|
|
*
|
|
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
|
|
* case a mapping of the entire file is created.
|
|
*/
|
|
template<typename String>
|
|
void map(const String& path, const size_type offset,
|
|
const size_type length, std::error_code& error)
|
|
{
|
|
map_impl(path, offset, length, error);
|
|
}
|
|
|
|
/**
|
|
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
|
|
* reason is reported via `error` and the object remains in a state as if this
|
|
* function hadn't been called.
|
|
*
|
|
* `path`, which must be a path to an existing file, is used to retrieve a file
|
|
* handle (which is closed when the object destructs or `unmap` is called), which is
|
|
* then used to memory map the requested region. Upon failure, `error` is set to
|
|
* indicate the reason and the object remains in an unmapped state.
|
|
*
|
|
* The entire file is mapped.
|
|
*/
|
|
template<typename String>
|
|
void map(const String& path, std::error_code& error)
|
|
{
|
|
map_impl(path, 0, map_entire_file, error);
|
|
}
|
|
|
|
/**
|
|
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
|
|
* reason is reported via `error` and the object remains in a state as if this
|
|
* function hadn't been called.
|
|
*
|
|
* `handle`, which must be a valid file handle, which is used to memory map the
|
|
* requested region. Upon failure, `error` is set to indicate the reason and the
|
|
* object remains in an unmapped state.
|
|
*
|
|
* `offset` is the number of bytes, relative to the start of the file, where the
|
|
* mapping should begin. When specifying it, there is no need to worry about
|
|
* providing a value that is aligned with the operating system's page allocation
|
|
* granularity. This is adjusted by the implementation such that the first requested
|
|
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
|
|
* `offset` from the start of the file.
|
|
*
|
|
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
|
|
* case a mapping of the entire file is created.
|
|
*/
|
|
void map(const handle_type handle, const size_type offset,
|
|
const size_type length, std::error_code& error)
|
|
{
|
|
map_impl(handle, offset, length, error);
|
|
}
|
|
|
|
/**
|
|
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
|
|
* reason is reported via `error` and the object remains in a state as if this
|
|
* function hadn't been called.
|
|
*
|
|
* `handle`, which must be a valid file handle, which is used to memory map the
|
|
* requested region. Upon failure, `error` is set to indicate the reason and the
|
|
* object remains in an unmapped state.
|
|
*
|
|
* The entire file is mapped.
|
|
*/
|
|
void map(const handle_type handle, std::error_code& error)
|
|
{
|
|
map_impl(handle, 0, map_entire_file, error);
|
|
}
|
|
|
|
/**
|
|
* If a valid memory mapping has been created prior to this call, this call
|
|
* instructs the kernel to unmap the memory region and disassociate this object
|
|
* from the file.
|
|
*
|
|
* The file handle associated with the file that is mapped is only closed if the
|
|
* mapping was created using a file path. If, on the other hand, an existing
|
|
* file handle was used to create the mapping, the file handle is not closed.
|
|
*/
|
|
void unmap() { if(pimpl_) pimpl_->unmap(); }
|
|
|
|
void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); }
|
|
|
|
/** Flushes the memory mapped page to disk. Errors are reported via `error`. */
|
|
template<
|
|
access_mode A = AccessMode,
|
|
typename = typename std::enable_if<A == access_mode::write>::type
|
|
> void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); }
|
|
|
|
/** All operators compare the underlying `basic_mmap`'s addresses. */
|
|
|
|
friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b)
|
|
{
|
|
return a.pimpl_ == b.pimpl_;
|
|
}
|
|
|
|
friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b)
|
|
{
|
|
return a.pimpl_ < b.pimpl_;
|
|
}
|
|
|
|
friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b)
|
|
{
|
|
return a.pimpl_ <= b.pimpl_;
|
|
}
|
|
|
|
friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b)
|
|
{
|
|
return a.pimpl_ > b.pimpl_;
|
|
}
|
|
|
|
friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b)
|
|
{
|
|
return a.pimpl_ >= b.pimpl_;
|
|
}
|
|
|
|
private:
|
|
template<typename MappingToken>
|
|
void map_impl(const MappingToken& token, const size_type offset,
|
|
const size_type length, std::error_code& error)
|
|
{
|
|
if(!pimpl_)
|
|
{
|
|
mmap_type mmap = make_mmap<mmap_type>(token, offset, length, error);
|
|
if(error) { return; }
|
|
pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
|
|
}
|
|
else
|
|
{
|
|
pimpl_->map(token, offset, length, error);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* This is the basis for all read-only mmap objects and should be preferred over
|
|
* directly using basic_shared_mmap.
|
|
*/
|
|
template<typename ByteT>
|
|
using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>;
|
|
|
|
/**
|
|
* This is the basis for all read-write mmap objects and should be preferred over
|
|
* directly using basic_shared_mmap.
|
|
*/
|
|
template<typename ByteT>
|
|
using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>;
|
|
|
|
/**
|
|
* These aliases cover the most common use cases, both representing a raw byte stream
|
|
* (either with a char or an unsigned char/uint8_t).
|
|
*/
|
|
using shared_mmap_source = basic_shared_mmap_source<char>;
|
|
using shared_ummap_source = basic_shared_mmap_source<unsigned char>;
|
|
|
|
using shared_mmap_sink = basic_shared_mmap_sink<char>;
|
|
using shared_ummap_sink = basic_shared_mmap_sink<unsigned char>;
|
|
|
|
} // namespace mio
|
|
|
|
#endif // MIO_SHARED_MMAP_HEADER
|
|
|
|
/** @file
|
|
* @brief Contains the main CSV parsing algorithm and various utility functions
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <condition_variable>
|
|
#include <deque>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
/** @file
|
|
* A standalone header file containing shared code
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <deque>
|
|
|
|
#if defined(_WIN32)
|
|
# ifndef WIN32_LEAN_AND_MEAN
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# endif
|
|
# include <Windows.h>
|
|
# undef max
|
|
# undef min
|
|
#elif defined(__linux__)
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
/** Helper macro which should be #defined as "inline"
|
|
* in the single header version
|
|
*/
|
|
#define CSV_INLINE inline
|
|
|
|
#include <type_traits>
|
|
|
|
// Copyright 2017-2019 by Martin Moene
|
|
//
|
|
// string-view lite, a C++17-like string_view for C++98 and later.
|
|
// For more information see https://github.com/martinmoene/string-view-lite
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
|
|
#ifndef NONSTD_SV_LITE_H_INCLUDED
|
|
#define NONSTD_SV_LITE_H_INCLUDED
|
|
|
|
#define string_view_lite_MAJOR 1
|
|
#define string_view_lite_MINOR 1
|
|
#define string_view_lite_PATCH 0
|
|
|
|
#define string_view_lite_VERSION nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY(string_view_lite_PATCH)
|
|
|
|
#define nssv_STRINGIFY( x ) nssv_STRINGIFY_( x )
|
|
#define nssv_STRINGIFY_( x ) #x
|
|
|
|
// string-view lite configuration:
|
|
|
|
#define nssv_STRING_VIEW_DEFAULT 0
|
|
#define nssv_STRING_VIEW_NONSTD 1
|
|
#define nssv_STRING_VIEW_STD 2
|
|
|
|
#if !defined( nssv_CONFIG_SELECT_STRING_VIEW )
|
|
# define nssv_CONFIG_SELECT_STRING_VIEW ( nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD )
|
|
#endif
|
|
|
|
#if defined( nssv_CONFIG_SELECT_STD_STRING_VIEW ) || defined( nssv_CONFIG_SELECT_NONSTD_STRING_VIEW )
|
|
# error nssv_CONFIG_SELECT_STD_STRING_VIEW and nssv_CONFIG_SELECT_NONSTD_STRING_VIEW are deprecated and removed, please use nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_...
|
|
#endif
|
|
|
|
#ifndef nssv_CONFIG_STD_SV_OPERATOR
|
|
# define nssv_CONFIG_STD_SV_OPERATOR 0
|
|
#endif
|
|
|
|
#ifndef nssv_CONFIG_USR_SV_OPERATOR
|
|
# define nssv_CONFIG_USR_SV_OPERATOR 1
|
|
#endif
|
|
|
|
#ifdef nssv_CONFIG_CONVERSION_STD_STRING
|
|
# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING
|
|
# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING
|
|
#endif
|
|
|
|
#ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS
|
|
# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1
|
|
#endif
|
|
|
|
#ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
|
|
# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1
|
|
#endif
|
|
|
|
// Control presence of exception handling (try and auto discover):
|
|
|
|
#ifndef nssv_CONFIG_NO_EXCEPTIONS
|
|
# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
|
|
# define nssv_CONFIG_NO_EXCEPTIONS 0
|
|
# else
|
|
# define nssv_CONFIG_NO_EXCEPTIONS 1
|
|
# endif
|
|
#endif
|
|
|
|
// C++ language version detection (C++20 is speculative):
|
|
// Note: VC14.0/1900 (VS2015) lacks too much from C++14.
|
|
|
|
#ifndef nssv_CPLUSPLUS
|
|
# if defined(_MSVC_LANG ) && !defined(__clang__)
|
|
# define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG )
|
|
# else
|
|
# define nssv_CPLUSPLUS __cplusplus
|
|
# endif
|
|
#endif
|
|
|
|
#define nssv_CPP98_OR_GREATER ( nssv_CPLUSPLUS >= 199711L )
|
|
#define nssv_CPP11_OR_GREATER ( nssv_CPLUSPLUS >= 201103L )
|
|
#define nssv_CPP11_OR_GREATER_ ( nssv_CPLUSPLUS >= 201103L )
|
|
#define nssv_CPP14_OR_GREATER ( nssv_CPLUSPLUS >= 201402L )
|
|
#define nssv_CPP17_OR_GREATER ( nssv_CPLUSPLUS >= 201703L )
|
|
#define nssv_CPP20_OR_GREATER ( nssv_CPLUSPLUS >= 202000L )
|
|
|
|
// use C++17 std::string_view if available and requested:
|
|
|
|
#if nssv_CPP17_OR_GREATER && defined(__has_include )
|
|
# if __has_include( <string_view> )
|
|
# define nssv_HAVE_STD_STRING_VIEW 1
|
|
# else
|
|
# define nssv_HAVE_STD_STRING_VIEW 0
|
|
# endif
|
|
#else
|
|
# define nssv_HAVE_STD_STRING_VIEW 0
|
|
#endif
|
|
|
|
#define nssv_USES_STD_STRING_VIEW ( (nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) || ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW) )
|
|
|
|
#define nssv_HAVE_STARTS_WITH ( nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW )
|
|
#define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH
|
|
|
|
//
|
|
// Use C++17 std::string_view:
|
|
//
|
|
|
|
#if nssv_USES_STD_STRING_VIEW
|
|
|
|
#include <string_view>
|
|
|
|
// Extensions for std::string:
|
|
|
|
#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
|
|
|
|
namespace nonstd {
|
|
|
|
template< class CharT, class Traits, class Allocator = std::allocator<CharT> >
|
|
std::basic_string<CharT, Traits, Allocator>
|
|
to_string( std::basic_string_view<CharT, Traits> v, Allocator const & a = Allocator() )
|
|
{
|
|
return std::basic_string<CharT,Traits, Allocator>( v.begin(), v.end(), a );
|
|
}
|
|
|
|
template< class CharT, class Traits, class Allocator >
|
|
std::basic_string_view<CharT, Traits>
|
|
to_string_view( std::basic_string<CharT, Traits, Allocator> const & s )
|
|
{
|
|
return std::basic_string_view<CharT, Traits>( s.data(), s.size() );
|
|
}
|
|
|
|
// Literal operators sv and _sv:
|
|
|
|
#if nssv_CONFIG_STD_SV_OPERATOR
|
|
|
|
using namespace std::literals::string_view_literals;
|
|
|
|
#endif
|
|
|
|
#if nssv_CONFIG_USR_SV_OPERATOR
|
|
|
|
inline namespace literals {
|
|
inline namespace string_view_literals {
|
|
|
|
|
|
constexpr std::string_view operator "" _sv( const char* str, size_t len ) noexcept // (1)
|
|
{
|
|
return std::string_view{ str, len };
|
|
}
|
|
|
|
constexpr std::u16string_view operator "" _sv( const char16_t* str, size_t len ) noexcept // (2)
|
|
{
|
|
return std::u16string_view{ str, len };
|
|
}
|
|
|
|
constexpr std::u32string_view operator "" _sv( const char32_t* str, size_t len ) noexcept // (3)
|
|
{
|
|
return std::u32string_view{ str, len };
|
|
}
|
|
|
|
constexpr std::wstring_view operator "" _sv( const wchar_t* str, size_t len ) noexcept // (4)
|
|
{
|
|
return std::wstring_view{ str, len };
|
|
}
|
|
|
|
}} // namespace literals::string_view_literals
|
|
|
|
#endif // nssv_CONFIG_USR_SV_OPERATOR
|
|
|
|
} // namespace nonstd
|
|
|
|
#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
|
|
|
|
namespace nonstd {
|
|
|
|
using std::string_view;
|
|
using std::wstring_view;
|
|
using std::u16string_view;
|
|
using std::u32string_view;
|
|
using std::basic_string_view;
|
|
|
|
// literal "sv" and "_sv", see above
|
|
|
|
using std::operator==;
|
|
using std::operator!=;
|
|
using std::operator<;
|
|
using std::operator<=;
|
|
using std::operator>;
|
|
using std::operator>=;
|
|
|
|
using std::operator<<;
|
|
|
|
} // namespace nonstd
|
|
|
|
#else // nssv_HAVE_STD_STRING_VIEW
|
|
|
|
//
|
|
// Before C++17: use string_view lite:
|
|
//
|
|
|
|
// Compiler versions:
|
|
//
|
|
// MSVC++ 6.0 _MSC_VER == 1200 (Visual Studio 6.0)
|
|
// MSVC++ 7.0 _MSC_VER == 1300 (Visual Studio .NET 2002)
|
|
// MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio .NET 2003)
|
|
// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005)
|
|
// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008)
|
|
// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
|
|
// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
|
|
// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
|
|
// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
|
|
// MSVC++ 14.1 _MSC_VER >= 1910 (Visual Studio 2017)
|
|
|
|
#if defined(_MSC_VER ) && !defined(__clang__)
|
|
# define nssv_COMPILER_MSVC_VER (_MSC_VER )
|
|
# define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) )
|
|
#else
|
|
# define nssv_COMPILER_MSVC_VER 0
|
|
# define nssv_COMPILER_MSVC_VERSION 0
|
|
#endif
|
|
|
|
#define nssv_COMPILER_VERSION( major, minor, patch ) (10 * ( 10 * major + minor) + patch)
|
|
|
|
#if defined(__clang__)
|
|
# define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
|
|
#else
|
|
# define nssv_COMPILER_CLANG_VERSION 0
|
|
#endif
|
|
|
|
#if defined(__GNUC__) && !defined(__clang__)
|
|
# define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
|
|
#else
|
|
# define nssv_COMPILER_GNUC_VERSION 0
|
|
#endif
|
|
|
|
// half-open range [lo..hi):
|
|
#define nssv_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) )
|
|
|
|
// Presence of language and library features:
|
|
|
|
#ifdef _HAS_CPP0X
|
|
# define nssv_HAS_CPP0X _HAS_CPP0X
|
|
#else
|
|
# define nssv_HAS_CPP0X 0
|
|
#endif
|
|
|
|
// Unless defined otherwise below, consider VC14 as C++11 for variant-lite:
|
|
|
|
#if nssv_COMPILER_MSVC_VER >= 1900
|
|
# undef nssv_CPP11_OR_GREATER
|
|
# define nssv_CPP11_OR_GREATER 1
|
|
#endif
|
|
|
|
#define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500)
|
|
#define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600)
|
|
#define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700)
|
|
#define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800)
|
|
#define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900)
|
|
#define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910)
|
|
|
|
#define nssv_CPP14_000 (nssv_CPP14_OR_GREATER)
|
|
#define nssv_CPP17_000 (nssv_CPP17_OR_GREATER)
|
|
|
|
// Presence of C++11 language features:
|
|
|
|
#define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140
|
|
#define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140
|
|
#define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140
|
|
#define nssv_HAVE_NOEXCEPT nssv_CPP11_140
|
|
#define nssv_HAVE_NULLPTR nssv_CPP11_100
|
|
#define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140
|
|
#define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140
|
|
#define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140
|
|
#define nssv_HAVE_WCHAR16_T nssv_CPP11_100
|
|
#define nssv_HAVE_WCHAR32_T nssv_CPP11_100
|
|
|
|
#if ! ( ( nssv_CPP11 && nssv_COMPILER_CLANG_VERSION ) || nssv_BETWEEN( nssv_COMPILER_CLANG_VERSION, 300, 400 ) )
|
|
# define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140
|
|
#endif
|
|
|
|
// Presence of C++14 language features:
|
|
|
|
#define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000
|
|
|
|
// Presence of C++17 language features:
|
|
|
|
#define nssv_HAVE_NODISCARD nssv_CPP17_000
|
|
|
|
// Presence of C++ library features:
|
|
|
|
#define nssv_HAVE_STD_HASH nssv_CPP11_120
|
|
|
|
// C++ feature usage:
|
|
|
|
#if nssv_HAVE_CONSTEXPR_11
|
|
# define nssv_constexpr constexpr
|
|
#else
|
|
# define nssv_constexpr /*constexpr*/
|
|
#endif
|
|
|
|
#if nssv_HAVE_CONSTEXPR_14
|
|
# define nssv_constexpr14 constexpr
|
|
#else
|
|
# define nssv_constexpr14 /*constexpr*/
|
|
#endif
|
|
|
|
#if nssv_HAVE_EXPLICIT_CONVERSION
|
|
# define nssv_explicit explicit
|
|
#else
|
|
# define nssv_explicit /*explicit*/
|
|
#endif
|
|
|
|
#if nssv_HAVE_INLINE_NAMESPACE
|
|
# define nssv_inline_ns inline
|
|
#else
|
|
# define nssv_inline_ns /*inline*/
|
|
#endif
|
|
|
|
#if nssv_HAVE_NOEXCEPT
|
|
# define nssv_noexcept noexcept
|
|
#else
|
|
# define nssv_noexcept /*noexcept*/
|
|
#endif
|
|
|
|
//#if nssv_HAVE_REF_QUALIFIER
|
|
//# define nssv_ref_qual &
|
|
//# define nssv_refref_qual &&
|
|
//#else
|
|
//# define nssv_ref_qual /*&*/
|
|
//# define nssv_refref_qual /*&&*/
|
|
//#endif
|
|
|
|
#if nssv_HAVE_NULLPTR
|
|
# define nssv_nullptr nullptr
|
|
#else
|
|
# define nssv_nullptr NULL
|
|
#endif
|
|
|
|
#if nssv_HAVE_NODISCARD
|
|
# define nssv_nodiscard [[nodiscard]]
|
|
#else
|
|
# define nssv_nodiscard /*[[nodiscard]]*/
|
|
#endif
|
|
|
|
// Additional includes:
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <ostream>
|
|
#include <string> // std::char_traits<>
|
|
|
|
#if ! nssv_CONFIG_NO_EXCEPTIONS
|
|
# include <stdexcept>
|
|
#endif
|
|
|
|
#if nssv_CPP11_OR_GREATER
|
|
# include <type_traits>
|
|
#endif
|
|
|
|
// Clang, GNUC, MSVC warning suppression macros:
|
|
|
|
#if defined(__clang__)
|
|
# pragma clang diagnostic ignored "-Wreserved-user-defined-literal"
|
|
# pragma clang diagnostic push
|
|
# pragma clang diagnostic ignored "-Wuser-defined-literals"
|
|
#elif defined(__GNUC__)
|
|
# pragma GCC diagnostic push
|
|
# pragma GCC diagnostic ignored "-Wliteral-suffix"
|
|
#endif // __clang__
|
|
|
|
#if nssv_COMPILER_MSVC_VERSION >= 140
|
|
# define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]]
|
|
# define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress: code) )
|
|
# define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes))
|
|
#else
|
|
# define nssv_SUPPRESS_MSGSL_WARNING(expr)
|
|
# define nssv_SUPPRESS_MSVC_WARNING(code, descr)
|
|
# define nssv_DISABLE_MSVC_WARNINGS(codes)
|
|
#endif
|
|
|
|
#if defined(__clang__)
|
|
# define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop")
|
|
#elif defined(__GNUC__)
|
|
# define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop")
|
|
#elif nssv_COMPILER_MSVC_VERSION >= 140
|
|
# define nssv_RESTORE_WARNINGS() __pragma(warning(pop ))
|
|
#else
|
|
# define nssv_RESTORE_WARNINGS()
|
|
#endif
|
|
|
|
// Suppress the following MSVC (GSL) warnings:
|
|
// - C4455, non-gsl : 'operator ""sv': literal suffix identifiers that do not
|
|
// start with an underscore are reserved
|
|
// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions;
|
|
// use brace initialization, gsl::narrow_cast or gsl::narow
|
|
// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead
|
|
|
|
nssv_DISABLE_MSVC_WARNINGS( 4455 26481 26472 )
|
|
//nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" )
|
|
//nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix )
|
|
|
|
namespace nonstd { namespace sv_lite {
|
|
|
|
template
|
|
<
|
|
class CharT,
|
|
class Traits = std::char_traits<CharT>
|
|
>
|
|
class basic_string_view;
|
|
|
|
//
|
|
// basic_string_view:
|
|
//
|
|
|
|
template
|
|
<
|
|
class CharT,
|
|
class Traits /* = std::char_traits<CharT> */
|
|
>
|
|
class basic_string_view
|
|
{
|
|
public:
|
|
// Member types:
|
|
|
|
typedef Traits traits_type;
|
|
typedef CharT value_type;
|
|
|
|
typedef CharT * pointer;
|
|
typedef CharT const * const_pointer;
|
|
typedef CharT & reference;
|
|
typedef CharT const & const_reference;
|
|
|
|
typedef const_pointer iterator;
|
|
typedef const_pointer const_iterator;
|
|
typedef std::reverse_iterator< const_iterator > reverse_iterator;
|
|
typedef std::reverse_iterator< const_iterator > const_reverse_iterator;
|
|
|
|
typedef std::size_t size_type;
|
|
typedef std::ptrdiff_t difference_type;
|
|
|
|
// 24.4.2.1 Construction and assignment:
|
|
|
|
nssv_constexpr basic_string_view() nssv_noexcept
|
|
: data_( nssv_nullptr )
|
|
, size_( 0 )
|
|
{}
|
|
|
|
#if nssv_CPP11_OR_GREATER
|
|
nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept = default;
|
|
#else
|
|
nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept
|
|
: data_( other.data_)
|
|
, size_( other.size_)
|
|
{}
|
|
#endif
|
|
|
|
nssv_constexpr basic_string_view( CharT const * s, size_type count )
|
|
: data_( s )
|
|
, size_( count )
|
|
{}
|
|
|
|
nssv_constexpr basic_string_view( CharT const * s)
|
|
: data_( s )
|
|
, size_( Traits::length(s) )
|
|
{}
|
|
|
|
// Assignment:
|
|
|
|
#if nssv_CPP11_OR_GREATER
|
|
nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept = default;
|
|
#else
|
|
nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept
|
|
{
|
|
data_ = other.data_;
|
|
size_ = other.size_;
|
|
return *this;
|
|
}
|
|
#endif
|
|
|
|
// 24.4.2.2 Iterator support:
|
|
|
|
nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; }
|
|
nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; }
|
|
|
|
nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); }
|
|
nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); }
|
|
|
|
nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator( end() ); }
|
|
nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator( begin() ); }
|
|
|
|
nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); }
|
|
nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); }
|
|
|
|
// 24.4.2.3 Capacity:
|
|
|
|
nssv_constexpr size_type size() const nssv_noexcept { return size_; }
|
|
nssv_constexpr size_type length() const nssv_noexcept { return size_; }
|
|
nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits< size_type >::max)(); }
|
|
|
|
// since C++20
|
|
nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept
|
|
{
|
|
return 0 == size_;
|
|
}
|
|
|
|
// 24.4.2.4 Element access:
|
|
|
|
nssv_constexpr const_reference operator[]( size_type pos ) const
|
|
{
|
|
return data_at( pos );
|
|
}
|
|
|
|
nssv_constexpr14 const_reference at( size_type pos ) const
|
|
{
|
|
#if nssv_CONFIG_NO_EXCEPTIONS
|
|
assert( pos < size() );
|
|
#else
|
|
if ( pos >= size() )
|
|
{
|
|
throw std::out_of_range("nonst::string_view::at()");
|
|
}
|
|
#endif
|
|
return data_at( pos );
|
|
}
|
|
|
|
nssv_constexpr const_reference front() const { return data_at( 0 ); }
|
|
nssv_constexpr const_reference back() const { return data_at( size() - 1 ); }
|
|
|
|
nssv_constexpr const_pointer data() const nssv_noexcept { return data_; }
|
|
|
|
// 24.4.2.5 Modifiers:
|
|
|
|
nssv_constexpr14 void remove_prefix( size_type n )
|
|
{
|
|
assert( n <= size() );
|
|
data_ += n;
|
|
size_ -= n;
|
|
}
|
|
|
|
nssv_constexpr14 void remove_suffix( size_type n )
|
|
{
|
|
assert( n <= size() );
|
|
size_ -= n;
|
|
}
|
|
|
|
nssv_constexpr14 void swap( basic_string_view & other ) nssv_noexcept
|
|
{
|
|
using std::swap;
|
|
swap( data_, other.data_ );
|
|
swap( size_, other.size_ );
|
|
}
|
|
|
|
// 24.4.2.6 String operations:
|
|
|
|
size_type copy( CharT * dest, size_type n, size_type pos = 0 ) const
|
|
{
|
|
#if nssv_CONFIG_NO_EXCEPTIONS
|
|
assert( pos <= size() );
|
|
#else
|
|
if ( pos > size() )
|
|
{
|
|
throw std::out_of_range("nonst::string_view::copy()");
|
|
}
|
|
#endif
|
|
const size_type rlen = (std::min)( n, size() - pos );
|
|
|
|
(void) Traits::copy( dest, data() + pos, rlen );
|
|
|
|
return rlen;
|
|
}
|
|
|
|
nssv_constexpr14 basic_string_view substr( size_type pos = 0, size_type n = npos ) const
|
|
{
|
|
#if nssv_CONFIG_NO_EXCEPTIONS
|
|
assert( pos <= size() );
|
|
#else
|
|
if ( pos > size() )
|
|
{
|
|
throw std::out_of_range("nonst::string_view::substr()");
|
|
}
|
|
#endif
|
|
return basic_string_view( data() + pos, (std::min)( n, size() - pos ) );
|
|
}
|
|
|
|
// compare(), 6x:
|
|
|
|
nssv_constexpr14 int compare( basic_string_view other ) const nssv_noexcept // (1)
|
|
{
|
|
if ( const int result = Traits::compare( data(), other.data(), (std::min)( size(), other.size() ) ) )
|
|
return result;
|
|
|
|
return size() == other.size() ? 0 : size() < other.size() ? -1 : 1;
|
|
}
|
|
|
|
nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other ) const // (2)
|
|
{
|
|
return substr( pos1, n1 ).compare( other );
|
|
}
|
|
|
|
nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other, size_type pos2, size_type n2 ) const // (3)
|
|
{
|
|
return substr( pos1, n1 ).compare( other.substr( pos2, n2 ) );
|
|
}
|
|
|
|
nssv_constexpr int compare( CharT const * s ) const // (4)
|
|
{
|
|
return compare( basic_string_view( s ) );
|
|
}
|
|
|
|
nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s ) const // (5)
|
|
{
|
|
return substr( pos1, n1 ).compare( basic_string_view( s ) );
|
|
}
|
|
|
|
nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s, size_type n2 ) const // (6)
|
|
{
|
|
return substr( pos1, n1 ).compare( basic_string_view( s, n2 ) );
|
|
}
|
|
|
|
// 24.4.2.7 Searching:
|
|
|
|
// starts_with(), 3x, since C++20:
|
|
|
|
nssv_constexpr bool starts_with( basic_string_view v ) const nssv_noexcept // (1)
|
|
{
|
|
return size() >= v.size() && compare( 0, v.size(), v ) == 0;
|
|
}
|
|
|
|
nssv_constexpr bool starts_with( CharT c ) const nssv_noexcept // (2)
|
|
{
|
|
return starts_with( basic_string_view( &c, 1 ) );
|
|
}
|
|
|
|
nssv_constexpr bool starts_with( CharT const * s ) const // (3)
|
|
{
|
|
return starts_with( basic_string_view( s ) );
|
|
}
|
|
|
|
// ends_with(), 3x, since C++20:
|
|
|
|
nssv_constexpr bool ends_with( basic_string_view v ) const nssv_noexcept // (1)
|
|
{
|
|
return size() >= v.size() && compare( size() - v.size(), npos, v ) == 0;
|
|
}
|
|
|
|
nssv_constexpr bool ends_with( CharT c ) const nssv_noexcept // (2)
|
|
{
|
|
return ends_with( basic_string_view( &c, 1 ) );
|
|
}
|
|
|
|
nssv_constexpr bool ends_with( CharT const * s ) const // (3)
|
|
{
|
|
return ends_with( basic_string_view( s ) );
|
|
}
|
|
|
|
// find(), 4x:
|
|
|
|
nssv_constexpr14 size_type find( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1)
|
|
{
|
|
return assert( v.size() == 0 || v.data() != nssv_nullptr )
|
|
, pos >= size()
|
|
? npos
|
|
: to_pos( std::search( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) );
|
|
}
|
|
|
|
nssv_constexpr14 size_type find( CharT c, size_type pos = 0 ) const nssv_noexcept // (2)
|
|
{
|
|
return find( basic_string_view( &c, 1 ), pos );
|
|
}
|
|
|
|
nssv_constexpr14 size_type find( CharT const * s, size_type pos, size_type n ) const // (3)
|
|
{
|
|
return find( basic_string_view( s, n ), pos );
|
|
}
|
|
|
|
nssv_constexpr14 size_type find( CharT const * s, size_type pos = 0 ) const // (4)
|
|
{
|
|
return find( basic_string_view( s ), pos );
|
|
}
|
|
|
|
// rfind(), 4x:
|
|
|
|
nssv_constexpr14 size_type rfind( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1)
|
|
{
|
|
if ( size() < v.size() )
|
|
return npos;
|
|
|
|
if ( v.empty() )
|
|
return (std::min)( size(), pos );
|
|
|
|
const_iterator last = cbegin() + (std::min)( size() - v.size(), pos ) + v.size();
|
|
const_iterator result = std::find_end( cbegin(), last, v.cbegin(), v.cend(), Traits::eq );
|
|
|
|
return result != last ? size_type( result - cbegin() ) : npos;
|
|
}
|
|
|
|
nssv_constexpr14 size_type rfind( CharT c, size_type pos = npos ) const nssv_noexcept // (2)
|
|
{
|
|
return rfind( basic_string_view( &c, 1 ), pos );
|
|
}
|
|
|
|
nssv_constexpr14 size_type rfind( CharT const * s, size_type pos, size_type n ) const // (3)
|
|
{
|
|
return rfind( basic_string_view( s, n ), pos );
|
|
}
|
|
|
|
nssv_constexpr14 size_type rfind( CharT const * s, size_type pos = npos ) const // (4)
|
|
{
|
|
return rfind( basic_string_view( s ), pos );
|
|
}
|
|
|
|
// find_first_of(), 4x:
|
|
|
|
nssv_constexpr size_type find_first_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1)
|
|
{
|
|
return pos >= size()
|
|
? npos
|
|
: to_pos( std::find_first_of( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) );
|
|
}
|
|
|
|
nssv_constexpr size_type find_first_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2)
|
|
{
|
|
return find_first_of( basic_string_view( &c, 1 ), pos );
|
|
}
|
|
|
|
nssv_constexpr size_type find_first_of( CharT const * s, size_type pos, size_type n ) const // (3)
|
|
{
|
|
return find_first_of( basic_string_view( s, n ), pos );
|
|
}
|
|
|
|
nssv_constexpr size_type find_first_of( CharT const * s, size_type pos = 0 ) const // (4)
|
|
{
|
|
return find_first_of( basic_string_view( s ), pos );
|
|
}
|
|
|
|
// find_last_of(), 4x:
|
|
|
|
nssv_constexpr size_type find_last_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1)
|
|
{
|
|
return empty()
|
|
? npos
|
|
: pos >= size()
|
|
? find_last_of( v, size() - 1 )
|
|
: to_pos( std::find_first_of( const_reverse_iterator( cbegin() + pos + 1 ), crend(), v.cbegin(), v.cend(), Traits::eq ) );
|
|
}
|
|
|
|
nssv_constexpr size_type find_last_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2)
|
|
{
|
|
return find_last_of( basic_string_view( &c, 1 ), pos );
|
|
}
|
|
|
|
nssv_constexpr size_type find_last_of( CharT const * s, size_type pos, size_type count ) const // (3)
|
|
{
|
|
return find_last_of( basic_string_view( s, count ), pos );
|
|
}
|
|
|
|
nssv_constexpr size_type find_last_of( CharT const * s, size_type pos = npos ) const // (4)
|
|
{
|
|
return find_last_of( basic_string_view( s ), pos );
|
|
}
|
|
|
|
// find_first_not_of(), 4x:
|
|
|
|
nssv_constexpr size_type find_first_not_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1)
|
|
{
|
|
return pos >= size()
|
|
? npos
|
|
: to_pos( std::find_if( cbegin() + pos, cend(), not_in_view( v ) ) );
|
|
}
|
|
|
|
nssv_constexpr size_type find_first_not_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2)
|
|
{
|
|
return find_first_not_of( basic_string_view( &c, 1 ), pos );
|
|
}
|
|
|
|
nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos, size_type count ) const // (3)
|
|
{
|
|
return find_first_not_of( basic_string_view( s, count ), pos );
|
|
}
|
|
|
|
nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos = 0 ) const // (4)
|
|
{
|
|
return find_first_not_of( basic_string_view( s ), pos );
|
|
}
|
|
|
|
// find_last_not_of(), 4x:
|
|
|
|
nssv_constexpr size_type find_last_not_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1)
|
|
{
|
|
return empty()
|
|
? npos
|
|
: pos >= size()
|
|
? find_last_not_of( v, size() - 1 )
|
|
: to_pos( std::find_if( const_reverse_iterator( cbegin() + pos + 1 ), crend(), not_in_view( v ) ) );
|
|
}
|
|
|
|
nssv_constexpr size_type find_last_not_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2)
|
|
{
|
|
return find_last_not_of( basic_string_view( &c, 1 ), pos );
|
|
}
|
|
|
|
nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos, size_type count ) const // (3)
|
|
{
|
|
return find_last_not_of( basic_string_view( s, count ), pos );
|
|
}
|
|
|
|
nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos = npos ) const // (4)
|
|
{
|
|
return find_last_not_of( basic_string_view( s ), pos );
|
|
}
|
|
|
|
// Constants:
|
|
|
|
#if nssv_CPP17_OR_GREATER
|
|
static nssv_constexpr size_type npos = size_type(-1);
|
|
#elif nssv_CPP11_OR_GREATER
|
|
enum : size_type { npos = size_type(-1) };
|
|
#else
|
|
enum { npos = size_type(-1) };
|
|
#endif
|
|
|
|
private:
|
|
struct not_in_view
|
|
{
|
|
const basic_string_view v;
|
|
|
|
nssv_constexpr not_in_view( basic_string_view v ) : v( v ) {}
|
|
|
|
nssv_constexpr bool operator()( CharT c ) const
|
|
{
|
|
return npos == v.find_first_of( c );
|
|
}
|
|
};
|
|
|
|
nssv_constexpr size_type to_pos( const_iterator it ) const
|
|
{
|
|
return it == cend() ? npos : size_type( it - cbegin() );
|
|
}
|
|
|
|
nssv_constexpr size_type to_pos( const_reverse_iterator it ) const
|
|
{
|
|
return it == crend() ? npos : size_type( crend() - it - 1 );
|
|
}
|
|
|
|
nssv_constexpr const_reference data_at( size_type pos ) const
|
|
{
|
|
#if nssv_BETWEEN( nssv_COMPILER_GNUC_VERSION, 1, 500 )
|
|
return data_[pos];
|
|
#else
|
|
return assert( pos < size() ), data_[pos];
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
const_pointer data_;
|
|
size_type size_;
|
|
|
|
public:
|
|
#if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS
|
|
|
|
template< class Allocator >
|
|
basic_string_view( std::basic_string<CharT, Traits, Allocator> const & s ) nssv_noexcept
|
|
: data_( s.data() )
|
|
, size_( s.size() )
|
|
{}
|
|
|
|
#if nssv_HAVE_EXPLICIT_CONVERSION
|
|
|
|
template< class Allocator >
|
|
explicit operator std::basic_string<CharT, Traits, Allocator>() const
|
|
{
|
|
return to_string( Allocator() );
|
|
}
|
|
|
|
#endif // nssv_HAVE_EXPLICIT_CONVERSION
|
|
|
|
#if nssv_CPP11_OR_GREATER
|
|
|
|
template< class Allocator = std::allocator<CharT> >
|
|
std::basic_string<CharT, Traits, Allocator>
|
|
to_string( Allocator const & a = Allocator() ) const
|
|
{
|
|
return std::basic_string<CharT, Traits, Allocator>( begin(), end(), a );
|
|
}
|
|
|
|
#else
|
|
|
|
std::basic_string<CharT, Traits>
|
|
to_string() const
|
|
{
|
|
return std::basic_string<CharT, Traits>( begin(), end() );
|
|
}
|
|
|
|
template< class Allocator >
|
|
std::basic_string<CharT, Traits, Allocator>
|
|
to_string( Allocator const & a ) const
|
|
{
|
|
return std::basic_string<CharT, Traits, Allocator>( begin(), end(), a );
|
|
}
|
|
|
|
#endif // nssv_CPP11_OR_GREATER
|
|
|
|
#endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS
|
|
};
|
|
|
|
//
|
|
// Non-member functions:
|
|
//
|
|
|
|
// 24.4.3 Non-member comparison functions:
|
|
// lexicographically compare two string views (function template):
|
|
|
|
template< class CharT, class Traits >
|
|
nssv_constexpr bool operator== (
|
|
basic_string_view <CharT, Traits> lhs,
|
|
basic_string_view <CharT, Traits> rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) == 0 ; }
|
|
|
|
template< class CharT, class Traits >
|
|
nssv_constexpr bool operator!= (
|
|
basic_string_view <CharT, Traits> lhs,
|
|
basic_string_view <CharT, Traits> rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) != 0 ; }
|
|
|
|
template< class CharT, class Traits >
|
|
nssv_constexpr bool operator< (
|
|
basic_string_view <CharT, Traits> lhs,
|
|
basic_string_view <CharT, Traits> rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) < 0 ; }
|
|
|
|
template< class CharT, class Traits >
|
|
nssv_constexpr bool operator<= (
|
|
basic_string_view <CharT, Traits> lhs,
|
|
basic_string_view <CharT, Traits> rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) <= 0 ; }
|
|
|
|
template< class CharT, class Traits >
|
|
nssv_constexpr bool operator> (
|
|
basic_string_view <CharT, Traits> lhs,
|
|
basic_string_view <CharT, Traits> rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) > 0 ; }
|
|
|
|
template< class CharT, class Traits >
|
|
nssv_constexpr bool operator>= (
|
|
basic_string_view <CharT, Traits> lhs,
|
|
basic_string_view <CharT, Traits> rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) >= 0 ; }
|
|
|
|
// Let S be basic_string_view<CharT, Traits>, and sv be an instance of S.
|
|
// Implementations shall provide sufficient additional overloads marked
|
|
// constexpr and noexcept so that an object t with an implicit conversion
|
|
// to S can be compared according to Table 67.
|
|
|
|
#if nssv_CPP11_OR_GREATER && ! nssv_BETWEEN( nssv_COMPILER_MSVC_VERSION, 100, 141 )
|
|
|
|
#define nssv_BASIC_STRING_VIEW_I(T,U) typename std::decay< basic_string_view<T,U> >::type
|
|
|
|
#if nssv_BETWEEN( nssv_COMPILER_MSVC_VERSION, 140, 150 )
|
|
# define nssv_MSVC_ORDER(x) , int=x
|
|
#else
|
|
# define nssv_MSVC_ORDER(x) /*, int=x*/
|
|
#endif
|
|
|
|
// ==
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(1) >
|
|
nssv_constexpr bool operator==(
|
|
basic_string_view <CharT, Traits> lhs,
|
|
nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) == 0; }
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(2) >
|
|
nssv_constexpr bool operator==(
|
|
nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs,
|
|
basic_string_view <CharT, Traits> rhs ) nssv_noexcept
|
|
{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; }
|
|
|
|
// !=
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(1) >
|
|
nssv_constexpr bool operator!= (
|
|
basic_string_view < CharT, Traits > lhs,
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
|
|
{ return lhs.size() != rhs.size() || lhs.compare( rhs ) != 0 ; }
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(2) >
|
|
nssv_constexpr bool operator!= (
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
|
|
basic_string_view < CharT, Traits > rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) != 0 ; }
|
|
|
|
// <
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(1) >
|
|
nssv_constexpr bool operator< (
|
|
basic_string_view < CharT, Traits > lhs,
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) < 0 ; }
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(2) >
|
|
nssv_constexpr bool operator< (
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
|
|
basic_string_view < CharT, Traits > rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) < 0 ; }
|
|
|
|
// <=
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(1) >
|
|
nssv_constexpr bool operator<= (
|
|
basic_string_view < CharT, Traits > lhs,
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) <= 0 ; }
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(2) >
|
|
nssv_constexpr bool operator<= (
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
|
|
basic_string_view < CharT, Traits > rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) <= 0 ; }
|
|
|
|
// >
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(1) >
|
|
nssv_constexpr bool operator> (
|
|
basic_string_view < CharT, Traits > lhs,
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) > 0 ; }
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(2) >
|
|
nssv_constexpr bool operator> (
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
|
|
basic_string_view < CharT, Traits > rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) > 0 ; }
|
|
|
|
// >=
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(1) >
|
|
nssv_constexpr bool operator>= (
|
|
basic_string_view < CharT, Traits > lhs,
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) >= 0 ; }
|
|
|
|
template< class CharT, class Traits nssv_MSVC_ORDER(2) >
|
|
nssv_constexpr bool operator>= (
|
|
nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
|
|
basic_string_view < CharT, Traits > rhs ) nssv_noexcept
|
|
{ return lhs.compare( rhs ) >= 0 ; }
|
|
|
|
#undef nssv_MSVC_ORDER
|
|
#undef nssv_BASIC_STRING_VIEW_I
|
|
|
|
#endif // nssv_CPP11_OR_GREATER
|
|
|
|
// 24.4.4 Inserters and extractors:
|
|
|
|
namespace detail {
|
|
|
|
template< class Stream >
|
|
void write_padding( Stream & os, std::streamsize n )
|
|
{
|
|
for ( std::streamsize i = 0; i < n; ++i )
|
|
os.rdbuf()->sputc( os.fill() );
|
|
}
|
|
|
|
template< class Stream, class View >
|
|
Stream & write_to_stream( Stream & os, View const & sv )
|
|
{
|
|
typename Stream::sentry sentry( os );
|
|
|
|
if ( !os )
|
|
return os;
|
|
|
|
const std::streamsize length = static_cast<std::streamsize>( sv.length() );
|
|
|
|
// Whether, and how, to pad:
|
|
const bool pad = ( length < os.width() );
|
|
const bool left_pad = pad && ( os.flags() & std::ios_base::adjustfield ) == std::ios_base::right;
|
|
|
|
if ( left_pad )
|
|
write_padding( os, os.width() - length );
|
|
|
|
// Write span characters:
|
|
os.rdbuf()->sputn( sv.begin(), length );
|
|
|
|
if ( pad && !left_pad )
|
|
write_padding( os, os.width() - length );
|
|
|
|
// Reset output stream width:
|
|
os.width( 0 );
|
|
|
|
return os;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
template< class CharT, class Traits >
|
|
std::basic_ostream<CharT, Traits> &
|
|
operator<<(
|
|
std::basic_ostream<CharT, Traits>& os,
|
|
basic_string_view <CharT, Traits> sv )
|
|
{
|
|
return detail::write_to_stream( os, sv );
|
|
}
|
|
|
|
// Several typedefs for common character types are provided:
|
|
|
|
typedef basic_string_view<char> string_view;
|
|
typedef basic_string_view<wchar_t> wstring_view;
|
|
#if nssv_HAVE_WCHAR16_T
|
|
typedef basic_string_view<char16_t> u16string_view;
|
|
typedef basic_string_view<char32_t> u32string_view;
|
|
#endif
|
|
|
|
}} // namespace nonstd::sv_lite
|
|
|
|
//
|
|
// 24.4.6 Suffix for basic_string_view literals:
|
|
//
|
|
|
|
#if nssv_HAVE_USER_DEFINED_LITERALS
|
|
|
|
namespace nonstd {
|
|
nssv_inline_ns namespace literals {
|
|
nssv_inline_ns namespace string_view_literals {
|
|
|
|
#if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS
|
|
|
|
nssv_constexpr nonstd::sv_lite::string_view operator "" sv( const char* str, size_t len ) nssv_noexcept // (1)
|
|
{
|
|
return nonstd::sv_lite::string_view{ str, len };
|
|
}
|
|
|
|
nssv_constexpr nonstd::sv_lite::u16string_view operator "" sv( const char16_t* str, size_t len ) nssv_noexcept // (2)
|
|
{
|
|
return nonstd::sv_lite::u16string_view{ str, len };
|
|
}
|
|
|
|
nssv_constexpr nonstd::sv_lite::u32string_view operator "" sv( const char32_t* str, size_t len ) nssv_noexcept // (3)
|
|
{
|
|
return nonstd::sv_lite::u32string_view{ str, len };
|
|
}
|
|
|
|
nssv_constexpr nonstd::sv_lite::wstring_view operator "" sv( const wchar_t* str, size_t len ) nssv_noexcept // (4)
|
|
{
|
|
return nonstd::sv_lite::wstring_view{ str, len };
|
|
}
|
|
|
|
#endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS
|
|
|
|
#if nssv_CONFIG_USR_SV_OPERATOR
|
|
|
|
nssv_constexpr nonstd::sv_lite::string_view operator "" _sv( const char* str, size_t len ) nssv_noexcept // (1)
|
|
{
|
|
return nonstd::sv_lite::string_view{ str, len };
|
|
}
|
|
|
|
nssv_constexpr nonstd::sv_lite::u16string_view operator "" _sv( const char16_t* str, size_t len ) nssv_noexcept // (2)
|
|
{
|
|
return nonstd::sv_lite::u16string_view{ str, len };
|
|
}
|
|
|
|
nssv_constexpr nonstd::sv_lite::u32string_view operator "" _sv( const char32_t* str, size_t len ) nssv_noexcept // (3)
|
|
{
|
|
return nonstd::sv_lite::u32string_view{ str, len };
|
|
}
|
|
|
|
nssv_constexpr nonstd::sv_lite::wstring_view operator "" _sv( const wchar_t* str, size_t len ) nssv_noexcept // (4)
|
|
{
|
|
return nonstd::sv_lite::wstring_view{ str, len };
|
|
}
|
|
|
|
#endif // nssv_CONFIG_USR_SV_OPERATOR
|
|
|
|
}}} // namespace nonstd::literals::string_view_literals
|
|
|
|
#endif
|
|
|
|
//
|
|
// Extensions for std::string:
|
|
//
|
|
|
|
#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
|
|
|
|
namespace nonstd {
|
|
namespace sv_lite {
|
|
|
|
// Exclude MSVC 14 (19.00): it yields ambiguous to_string():
|
|
|
|
#if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140
|
|
|
|
template< class CharT, class Traits, class Allocator = std::allocator<CharT> >
|
|
std::basic_string<CharT, Traits, Allocator>
|
|
to_string( basic_string_view<CharT, Traits> v, Allocator const & a = Allocator() )
|
|
{
|
|
return std::basic_string<CharT,Traits, Allocator>( v.begin(), v.end(), a );
|
|
}
|
|
|
|
#else
|
|
|
|
template< class CharT, class Traits >
|
|
std::basic_string<CharT, Traits>
|
|
to_string( basic_string_view<CharT, Traits> v )
|
|
{
|
|
return std::basic_string<CharT, Traits>( v.begin(), v.end() );
|
|
}
|
|
|
|
template< class CharT, class Traits, class Allocator >
|
|
std::basic_string<CharT, Traits, Allocator>
|
|
to_string( basic_string_view<CharT, Traits> v, Allocator const & a )
|
|
{
|
|
return std::basic_string<CharT, Traits, Allocator>( v.begin(), v.end(), a );
|
|
}
|
|
|
|
#endif // nssv_CPP11_OR_GREATER
|
|
|
|
template< class CharT, class Traits, class Allocator >
|
|
basic_string_view<CharT, Traits>
|
|
to_string_view( std::basic_string<CharT, Traits, Allocator> const & s )
|
|
{
|
|
return basic_string_view<CharT, Traits>( s.data(), s.size() );
|
|
}
|
|
|
|
}} // namespace nonstd::sv_lite
|
|
|
|
#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
|
|
|
|
//
|
|
// make types and algorithms available in namespace nonstd:
|
|
//
|
|
|
|
namespace nonstd {
|
|
|
|
using sv_lite::basic_string_view;
|
|
using sv_lite::string_view;
|
|
using sv_lite::wstring_view;
|
|
|
|
#if nssv_HAVE_WCHAR16_T
|
|
using sv_lite::u16string_view;
|
|
#endif
|
|
#if nssv_HAVE_WCHAR32_T
|
|
using sv_lite::u32string_view;
|
|
#endif
|
|
|
|
// literal "sv"
|
|
|
|
using sv_lite::operator==;
|
|
using sv_lite::operator!=;
|
|
using sv_lite::operator<;
|
|
using sv_lite::operator<=;
|
|
using sv_lite::operator>;
|
|
using sv_lite::operator>=;
|
|
|
|
using sv_lite::operator<<;
|
|
|
|
#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
|
|
using sv_lite::to_string;
|
|
using sv_lite::to_string_view;
|
|
#endif
|
|
|
|
} // namespace nonstd
|
|
|
|
// 24.4.5 Hash support (C++11):
|
|
|
|
// Note: The hash value of a string view object is equal to the hash value of
|
|
// the corresponding string object.
|
|
|
|
#if nssv_HAVE_STD_HASH
|
|
|
|
#include <functional>
|
|
|
|
namespace std {
|
|
|
|
template<>
|
|
struct hash< nonstd::string_view >
|
|
{
|
|
public:
|
|
std::size_t operator()( nonstd::string_view v ) const nssv_noexcept
|
|
{
|
|
return std::hash<std::string>()( std::string( v.data(), v.size() ) );
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct hash< nonstd::wstring_view >
|
|
{
|
|
public:
|
|
std::size_t operator()( nonstd::wstring_view v ) const nssv_noexcept
|
|
{
|
|
return std::hash<std::wstring>()( std::wstring( v.data(), v.size() ) );
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct hash< nonstd::u16string_view >
|
|
{
|
|
public:
|
|
std::size_t operator()( nonstd::u16string_view v ) const nssv_noexcept
|
|
{
|
|
return std::hash<std::u16string>()( std::u16string( v.data(), v.size() ) );
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct hash< nonstd::u32string_view >
|
|
{
|
|
public:
|
|
std::size_t operator()( nonstd::u32string_view v ) const nssv_noexcept
|
|
{
|
|
return std::hash<std::u32string>()( std::u32string( v.data(), v.size() ) );
|
|
}
|
|
};
|
|
|
|
} // namespace std
|
|
|
|
#endif // nssv_HAVE_STD_HASH
|
|
|
|
nssv_RESTORE_WARNINGS()
|
|
|
|
#endif // nssv_HAVE_STD_STRING_VIEW
|
|
#endif // NONSTD_SV_LITE_H_INCLUDED
|
|
|
|
|
|
// If there is another version of Hedley, then the newer one
|
|
// takes precedence.
|
|
// See: https://github.com/nemequ/hedley
|
|
/* Hedley - https://nemequ.github.io/hedley
|
|
* Created by Evan Nemerson <evan@nemerson.com>
|
|
*
|
|
* To the extent possible under law, the author(s) have dedicated all
|
|
* copyright and related and neighboring rights to this software to
|
|
* the public domain worldwide. This software is distributed without
|
|
* any warranty.
|
|
*
|
|
* For details, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
* SPDX-License-Identifier: CC0-1.0
|
|
*/
|
|
|
|
#if !defined(HEDLEY_VERSION) || (HEDLEY_VERSION < 9)
|
|
#if defined(HEDLEY_VERSION)
|
|
# undef HEDLEY_VERSION
|
|
#endif
|
|
#define HEDLEY_VERSION 9
|
|
|
|
#if defined(HEDLEY_STRINGIFY_EX)
|
|
# undef HEDLEY_STRINGIFY_EX
|
|
#endif
|
|
#define HEDLEY_STRINGIFY_EX(x) #x
|
|
|
|
#if defined(HEDLEY_STRINGIFY)
|
|
# undef HEDLEY_STRINGIFY
|
|
#endif
|
|
#define HEDLEY_STRINGIFY(x) HEDLEY_STRINGIFY_EX(x)
|
|
|
|
#if defined(HEDLEY_CONCAT_EX)
|
|
# undef HEDLEY_CONCAT_EX
|
|
#endif
|
|
#define HEDLEY_CONCAT_EX(a,b) a##b
|
|
|
|
#if defined(HEDLEY_CONCAT)
|
|
# undef HEDLEY_CONCAT
|
|
#endif
|
|
#define HEDLEY_CONCAT(a,b) HEDLEY_CONCAT_EX(a,b)
|
|
|
|
#if defined(HEDLEY_VERSION_ENCODE)
|
|
# undef HEDLEY_VERSION_ENCODE
|
|
#endif
|
|
#define HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision))
|
|
|
|
#if defined(HEDLEY_VERSION_DECODE_MAJOR)
|
|
# undef HEDLEY_VERSION_DECODE_MAJOR
|
|
#endif
|
|
#define HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000)
|
|
|
|
#if defined(HEDLEY_VERSION_DECODE_MINOR)
|
|
# undef HEDLEY_VERSION_DECODE_MINOR
|
|
#endif
|
|
#define HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000)
|
|
|
|
#if defined(HEDLEY_VERSION_DECODE_REVISION)
|
|
# undef HEDLEY_VERSION_DECODE_REVISION
|
|
#endif
|
|
#define HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000)
|
|
|
|
#if defined(HEDLEY_GNUC_VERSION)
|
|
# undef HEDLEY_GNUC_VERSION
|
|
#endif
|
|
#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__)
|
|
# define HEDLEY_GNUC_VERSION HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
|
|
#elif defined(__GNUC__)
|
|
# define HEDLEY_GNUC_VERSION HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GNUC_VERSION_CHECK)
|
|
# undef HEDLEY_GNUC_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_GNUC_VERSION)
|
|
# define HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (HEDLEY_GNUC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_MSVC_VERSION)
|
|
# undef HEDLEY_MSVC_VERSION
|
|
#endif
|
|
#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000)
|
|
# define HEDLEY_MSVC_VERSION HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100)
|
|
#elif defined(_MSC_FULL_VER)
|
|
# define HEDLEY_MSVC_VERSION HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10)
|
|
#elif defined(_MSC_VER)
|
|
# define HEDLEY_MSVC_VERSION HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_MSVC_VERSION_CHECK)
|
|
# undef HEDLEY_MSVC_VERSION_CHECK
|
|
#endif
|
|
#if !defined(_MSC_VER)
|
|
# define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0)
|
|
#elif defined(_MSC_VER) && (_MSC_VER >= 1400)
|
|
# define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch)))
|
|
#elif defined(_MSC_VER) && (_MSC_VER >= 1200)
|
|
# define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch)))
|
|
#else
|
|
# define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor)))
|
|
#endif
|
|
|
|
#if defined(HEDLEY_INTEL_VERSION)
|
|
# undef HEDLEY_INTEL_VERSION
|
|
#endif
|
|
#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE)
|
|
# define HEDLEY_INTEL_VERSION HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE)
|
|
#elif defined(__INTEL_COMPILER)
|
|
# define HEDLEY_INTEL_VERSION HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_INTEL_VERSION_CHECK)
|
|
# undef HEDLEY_INTEL_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_INTEL_VERSION)
|
|
# define HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (HEDLEY_INTEL_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_PGI_VERSION)
|
|
# undef HEDLEY_PGI_VERSION
|
|
#endif
|
|
#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__)
|
|
# define HEDLEY_PGI_VERSION HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_PGI_VERSION_CHECK)
|
|
# undef HEDLEY_PGI_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_PGI_VERSION)
|
|
# define HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (HEDLEY_PGI_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_SUNPRO_VERSION)
|
|
# undef HEDLEY_SUNPRO_VERSION
|
|
#endif
|
|
#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000)
|
|
# define HEDLEY_SUNPRO_VERSION HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10)
|
|
#elif defined(__SUNPRO_C)
|
|
# define HEDLEY_SUNPRO_VERSION HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf)
|
|
#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000)
|
|
# define HEDLEY_SUNPRO_VERSION HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10)
|
|
#elif defined(__SUNPRO_CC)
|
|
# define HEDLEY_SUNPRO_VERSION HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_SUNPRO_VERSION_CHECK)
|
|
# undef HEDLEY_SUNPRO_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_SUNPRO_VERSION)
|
|
# define HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (HEDLEY_SUNPRO_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_EMSCRIPTEN_VERSION)
|
|
# undef HEDLEY_EMSCRIPTEN_VERSION
|
|
#endif
|
|
#if defined(__EMSCRIPTEN__)
|
|
# define HEDLEY_EMSCRIPTEN_VERSION HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_EMSCRIPTEN_VERSION_CHECK)
|
|
# undef HEDLEY_EMSCRIPTEN_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_EMSCRIPTEN_VERSION)
|
|
# define HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (HEDLEY_EMSCRIPTEN_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_ARM_VERSION)
|
|
# undef HEDLEY_ARM_VERSION
|
|
#endif
|
|
#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION)
|
|
# define HEDLEY_ARM_VERSION HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100)
|
|
#elif defined(__CC_ARM) && defined(__ARMCC_VERSION)
|
|
# define HEDLEY_ARM_VERSION HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_ARM_VERSION_CHECK)
|
|
# undef HEDLEY_ARM_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_ARM_VERSION)
|
|
# define HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (HEDLEY_ARM_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_IBM_VERSION)
|
|
# undef HEDLEY_IBM_VERSION
|
|
#endif
|
|
#if defined(__ibmxl__)
|
|
# define HEDLEY_IBM_VERSION HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__)
|
|
#elif defined(__xlC__) && defined(__xlC_ver__)
|
|
# define HEDLEY_IBM_VERSION HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff)
|
|
#elif defined(__xlC__)
|
|
# define HEDLEY_IBM_VERSION HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_IBM_VERSION_CHECK)
|
|
# undef HEDLEY_IBM_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_IBM_VERSION)
|
|
# define HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (HEDLEY_IBM_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_TI_VERSION)
|
|
# undef HEDLEY_TI_VERSION
|
|
#endif
|
|
#if defined(__TI_COMPILER_VERSION__)
|
|
# define HEDLEY_TI_VERSION HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))
|
|
#endif
|
|
|
|
#if defined(HEDLEY_TI_VERSION_CHECK)
|
|
# undef HEDLEY_TI_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_TI_VERSION)
|
|
# define HEDLEY_TI_VERSION_CHECK(major,minor,patch) (HEDLEY_TI_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_CRAY_VERSION)
|
|
# undef HEDLEY_CRAY_VERSION
|
|
#endif
|
|
#if defined(_CRAYC)
|
|
# if defined(_RELEASE_PATCHLEVEL)
|
|
# define HEDLEY_CRAY_VERSION HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL)
|
|
# else
|
|
# define HEDLEY_CRAY_VERSION HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0)
|
|
# endif
|
|
#endif
|
|
|
|
#if defined(HEDLEY_CRAY_VERSION_CHECK)
|
|
# undef HEDLEY_CRAY_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_CRAY_VERSION)
|
|
# define HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (HEDLEY_CRAY_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_IAR_VERSION)
|
|
# undef HEDLEY_IAR_VERSION
|
|
#endif
|
|
#if defined(__IAR_SYSTEMS_ICC__)
|
|
# if __VER__ > 1000
|
|
# define HEDLEY_IAR_VERSION HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000))
|
|
# else
|
|
# define HEDLEY_IAR_VERSION HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0)
|
|
# endif
|
|
#endif
|
|
|
|
#if defined(HEDLEY_IAR_VERSION_CHECK)
|
|
# undef HEDLEY_IAR_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_IAR_VERSION)
|
|
# define HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (HEDLEY_IAR_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_TINYC_VERSION)
|
|
# undef HEDLEY_TINYC_VERSION
|
|
#endif
|
|
#if defined(__TINYC__)
|
|
# define HEDLEY_TINYC_VERSION HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_TINYC_VERSION_CHECK)
|
|
# undef HEDLEY_TINYC_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_TINYC_VERSION)
|
|
# define HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (HEDLEY_TINYC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_DMC_VERSION)
|
|
# undef HEDLEY_DMC_VERSION
|
|
#endif
|
|
#if defined(__DMC__)
|
|
# define HEDLEY_DMC_VERSION HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_DMC_VERSION_CHECK)
|
|
# undef HEDLEY_DMC_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_DMC_VERSION)
|
|
# define HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (HEDLEY_DMC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_COMPCERT_VERSION)
|
|
# undef HEDLEY_COMPCERT_VERSION
|
|
#endif
|
|
#if defined(__COMPCERT_VERSION__)
|
|
# define HEDLEY_COMPCERT_VERSION HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_COMPCERT_VERSION_CHECK)
|
|
# undef HEDLEY_COMPCERT_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_COMPCERT_VERSION)
|
|
# define HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (HEDLEY_COMPCERT_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_PELLES_VERSION)
|
|
# undef HEDLEY_PELLES_VERSION
|
|
#endif
|
|
#if defined(__POCC__)
|
|
# define HEDLEY_PELLES_VERSION HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_PELLES_VERSION_CHECK)
|
|
# undef HEDLEY_PELLES_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_PELLES_VERSION)
|
|
# define HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (HEDLEY_PELLES_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GCC_VERSION)
|
|
# undef HEDLEY_GCC_VERSION
|
|
#endif
|
|
#if \
|
|
defined(HEDLEY_GNUC_VERSION) && \
|
|
!defined(__clang__) && \
|
|
!defined(HEDLEY_INTEL_VERSION) && \
|
|
!defined(HEDLEY_PGI_VERSION) && \
|
|
!defined(HEDLEY_ARM_VERSION) && \
|
|
!defined(HEDLEY_TI_VERSION) && \
|
|
!defined(__COMPCERT__)
|
|
# define HEDLEY_GCC_VERSION HEDLEY_GNUC_VERSION
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GCC_VERSION_CHECK)
|
|
# undef HEDLEY_GCC_VERSION_CHECK
|
|
#endif
|
|
#if defined(HEDLEY_GCC_VERSION)
|
|
# define HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (HEDLEY_GCC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
|
|
#else
|
|
# define HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_HAS_ATTRIBUTE)
|
|
# undef HEDLEY_HAS_ATTRIBUTE
|
|
#endif
|
|
#if defined(__has_attribute)
|
|
# define HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute)
|
|
#else
|
|
# define HEDLEY_HAS_ATTRIBUTE(attribute) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GNUC_HAS_ATTRIBUTE)
|
|
# undef HEDLEY_GNUC_HAS_ATTRIBUTE
|
|
#endif
|
|
#if defined(__has_attribute)
|
|
# define HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute)
|
|
#else
|
|
# define HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GCC_HAS_ATTRIBUTE)
|
|
# undef HEDLEY_GCC_HAS_ATTRIBUTE
|
|
#endif
|
|
#if defined(__has_attribute)
|
|
# define HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute)
|
|
#else
|
|
# define HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_HAS_CPP_ATTRIBUTE)
|
|
# undef HEDLEY_HAS_CPP_ATTRIBUTE
|
|
#endif
|
|
#if defined(__has_cpp_attribute) && defined(__cplusplus)
|
|
# define HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute)
|
|
#else
|
|
# define HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GNUC_HAS_CPP_ATTRIBUTE)
|
|
# undef HEDLEY_GNUC_HAS_CPP_ATTRIBUTE
|
|
#endif
|
|
#if defined(__has_cpp_attribute) && defined(__cplusplus)
|
|
# define HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)
|
|
#else
|
|
# define HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GCC_HAS_CPP_ATTRIBUTE)
|
|
# undef HEDLEY_GCC_HAS_CPP_ATTRIBUTE
|
|
#endif
|
|
#if defined(__has_cpp_attribute) && defined(__cplusplus)
|
|
# define HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)
|
|
#else
|
|
# define HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_HAS_BUILTIN)
|
|
# undef HEDLEY_HAS_BUILTIN
|
|
#endif
|
|
#if defined(__has_builtin)
|
|
# define HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin)
|
|
#else
|
|
# define HEDLEY_HAS_BUILTIN(builtin) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GNUC_HAS_BUILTIN)
|
|
# undef HEDLEY_GNUC_HAS_BUILTIN
|
|
#endif
|
|
#if defined(__has_builtin)
|
|
# define HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)
|
|
#else
|
|
# define HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GCC_HAS_BUILTIN)
|
|
# undef HEDLEY_GCC_HAS_BUILTIN
|
|
#endif
|
|
#if defined(__has_builtin)
|
|
# define HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)
|
|
#else
|
|
# define HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_HAS_FEATURE)
|
|
# undef HEDLEY_HAS_FEATURE
|
|
#endif
|
|
#if defined(__has_feature)
|
|
# define HEDLEY_HAS_FEATURE(feature) __has_feature(feature)
|
|
#else
|
|
# define HEDLEY_HAS_FEATURE(feature) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GNUC_HAS_FEATURE)
|
|
# undef HEDLEY_GNUC_HAS_FEATURE
|
|
#endif
|
|
#if defined(__has_feature)
|
|
# define HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)
|
|
#else
|
|
# define HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GCC_HAS_FEATURE)
|
|
# undef HEDLEY_GCC_HAS_FEATURE
|
|
#endif
|
|
#if defined(__has_feature)
|
|
# define HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)
|
|
#else
|
|
# define HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_HAS_EXTENSION)
|
|
# undef HEDLEY_HAS_EXTENSION
|
|
#endif
|
|
#if defined(__has_extension)
|
|
# define HEDLEY_HAS_EXTENSION(extension) __has_extension(extension)
|
|
#else
|
|
# define HEDLEY_HAS_EXTENSION(extension) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GNUC_HAS_EXTENSION)
|
|
# undef HEDLEY_GNUC_HAS_EXTENSION
|
|
#endif
|
|
#if defined(__has_extension)
|
|
# define HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)
|
|
#else
|
|
# define HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GCC_HAS_EXTENSION)
|
|
# undef HEDLEY_GCC_HAS_EXTENSION
|
|
#endif
|
|
#if defined(__has_extension)
|
|
# define HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)
|
|
#else
|
|
# define HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_HAS_DECLSPEC_ATTRIBUTE)
|
|
# undef HEDLEY_HAS_DECLSPEC_ATTRIBUTE
|
|
#endif
|
|
#if defined(__has_declspec_attribute)
|
|
# define HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute)
|
|
#else
|
|
# define HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE)
|
|
# undef HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE
|
|
#endif
|
|
#if defined(__has_declspec_attribute)
|
|
# define HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)
|
|
#else
|
|
# define HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE)
|
|
# undef HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE
|
|
#endif
|
|
#if defined(__has_declspec_attribute)
|
|
# define HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)
|
|
#else
|
|
# define HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_HAS_WARNING)
|
|
# undef HEDLEY_HAS_WARNING
|
|
#endif
|
|
#if defined(__has_warning)
|
|
# define HEDLEY_HAS_WARNING(warning) __has_warning(warning)
|
|
#else
|
|
# define HEDLEY_HAS_WARNING(warning) (0)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GNUC_HAS_WARNING)
|
|
# undef HEDLEY_GNUC_HAS_WARNING
|
|
#endif
|
|
#if defined(__has_warning)
|
|
# define HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)
|
|
#else
|
|
# define HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_GCC_HAS_WARNING)
|
|
# undef HEDLEY_GCC_HAS_WARNING
|
|
#endif
|
|
#if defined(__has_warning)
|
|
# define HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)
|
|
#else
|
|
# define HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if \
|
|
(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \
|
|
defined(__clang__) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,0,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_IAR_VERSION_CHECK(8,0,0) || \
|
|
HEDLEY_PGI_VERSION_CHECK(18,4,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(6,0,0) || \
|
|
HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \
|
|
HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR))
|
|
# define HEDLEY_PRAGMA(value) _Pragma(#value)
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(15,0,0)
|
|
# define HEDLEY_PRAGMA(value) __pragma(value)
|
|
#else
|
|
# define HEDLEY_PRAGMA(value)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_DIAGNOSTIC_PUSH)
|
|
# undef HEDLEY_DIAGNOSTIC_PUSH
|
|
#endif
|
|
#if defined(HEDLEY_DIAGNOSTIC_POP)
|
|
# undef HEDLEY_DIAGNOSTIC_POP
|
|
#endif
|
|
#if defined(__clang__)
|
|
# define HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push")
|
|
# define HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop")
|
|
#elif HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)")
|
|
# define HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)")
|
|
#elif HEDLEY_GCC_VERSION_CHECK(4,6,0)
|
|
# define HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")
|
|
# define HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop")
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(15,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push))
|
|
# define HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop))
|
|
#elif HEDLEY_ARM_VERSION_CHECK(5,6,0)
|
|
# define HEDLEY_DIAGNOSTIC_PUSH _Pragma("push")
|
|
# define HEDLEY_DIAGNOSTIC_POP _Pragma("pop")
|
|
#elif HEDLEY_TI_VERSION_CHECK(8,1,0)
|
|
# define HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push")
|
|
# define HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop")
|
|
#elif HEDLEY_PELLES_VERSION_CHECK(2,90,0)
|
|
# define HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)")
|
|
# define HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)")
|
|
#else
|
|
# define HEDLEY_DIAGNOSTIC_PUSH
|
|
# define HEDLEY_DIAGNOSTIC_POP
|
|
#endif
|
|
|
|
#if defined(HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED)
|
|
# undef HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
|
#endif
|
|
#if HEDLEY_HAS_WARNING("-Wdeprecated-declarations")
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
|
|
#elif HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)")
|
|
#elif HEDLEY_PGI_VERSION_CHECK(17,10,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444")
|
|
#elif HEDLEY_GCC_VERSION_CHECK(4,3,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(15,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996))
|
|
#elif HEDLEY_TI_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718")
|
|
#elif HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)")
|
|
#elif HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)")
|
|
#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215")
|
|
#elif HEDLEY_PELLES_VERSION_CHECK(2,90,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)")
|
|
#else
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
|
#endif
|
|
|
|
#if defined(HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS)
|
|
# undef HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS
|
|
#endif
|
|
#if HEDLEY_HAS_WARNING("-Wunknown-pragmas")
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"")
|
|
#elif HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)")
|
|
#elif HEDLEY_PGI_VERSION_CHECK(17,10,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675")
|
|
#elif HEDLEY_GCC_VERSION_CHECK(4,3,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"")
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(15,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068))
|
|
#elif HEDLEY_TI_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163")
|
|
#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161")
|
|
#else
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS
|
|
#endif
|
|
|
|
#if defined(HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL)
|
|
# undef HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL
|
|
#endif
|
|
#if HEDLEY_HAS_WARNING("-Wcast-qual")
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"")
|
|
#elif HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)")
|
|
#elif HEDLEY_GCC_VERSION_CHECK(3,0,0)
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"")
|
|
#else
|
|
# define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL
|
|
#endif
|
|
|
|
#if defined(HEDLEY_DEPRECATED)
|
|
# undef HEDLEY_DEPRECATED
|
|
#endif
|
|
#if defined(HEDLEY_DEPRECATED_FOR)
|
|
# undef HEDLEY_DEPRECATED_FOR
|
|
#endif
|
|
#if defined(__cplusplus) && (__cplusplus >= 201402L)
|
|
# define HEDLEY_DEPRECATED(since) [[deprecated("Since " #since)]]
|
|
# define HEDLEY_DEPRECATED_FOR(since, replacement) [[deprecated("Since " #since "; use " #replacement)]]
|
|
#elif \
|
|
HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,5,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(5,6,0) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \
|
|
HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,3,0)
|
|
# define HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since)))
|
|
# define HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement)))
|
|
#elif \
|
|
HEDLEY_HAS_ATTRIBUTE(deprecated) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))
|
|
# define HEDLEY_DEPRECATED(since) __attribute__((__deprecated__))
|
|
# define HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__))
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(14,0,0)
|
|
# define HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since))
|
|
# define HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement))
|
|
#elif \
|
|
HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
|
|
HEDLEY_PELLES_VERSION_CHECK(6,50,0)
|
|
# define HEDLEY_DEPRECATED(since) _declspec(deprecated)
|
|
# define HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated)
|
|
#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_DEPRECATED(since) _Pragma("deprecated")
|
|
# define HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated")
|
|
#else
|
|
# define HEDLEY_DEPRECATED(since)
|
|
# define HEDLEY_DEPRECATED_FOR(since, replacement)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_UNAVAILABLE)
|
|
# undef HEDLEY_UNAVAILABLE
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(warning) || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,3,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since)))
|
|
#else
|
|
# define HEDLEY_UNAVAILABLE(available_since)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_WARN_UNUSED_RESULT)
|
|
# undef HEDLEY_WARN_UNUSED_RESULT
|
|
#endif
|
|
#if defined(__cplusplus) && (__cplusplus >= 201703L)
|
|
# define HEDLEY_WARN_UNUSED_RESULT [[nodiscard]]
|
|
#elif \
|
|
HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,4,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
|
|
(HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \
|
|
HEDLEY_PGI_VERSION_CHECK(17,10,0)
|
|
# define HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
|
|
#elif defined(_Check_return_) /* SAL */
|
|
# define HEDLEY_WARN_UNUSED_RESULT _Check_return_
|
|
#else
|
|
# define HEDLEY_WARN_UNUSED_RESULT
|
|
#endif
|
|
|
|
#if defined(HEDLEY_SENTINEL)
|
|
# undef HEDLEY_SENTINEL
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(sentinel) || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,0,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(5,4,0)
|
|
# define HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position)))
|
|
#else
|
|
# define HEDLEY_SENTINEL(position)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_NO_RETURN)
|
|
# undef HEDLEY_NO_RETURN
|
|
#endif
|
|
#if HEDLEY_IAR_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_NO_RETURN __noreturn
|
|
#elif HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_NO_RETURN __attribute__((__noreturn__))
|
|
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
|
# define HEDLEY_NO_RETURN _Noreturn
|
|
#elif defined(__cplusplus) && (__cplusplus >= 201103L)
|
|
# define HEDLEY_NO_RETURN [[noreturn]]
|
|
#elif \
|
|
HEDLEY_HAS_ATTRIBUTE(noreturn) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,2,0) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(18,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(17,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))
|
|
# define HEDLEY_NO_RETURN __attribute__((__noreturn__))
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(13,10,0)
|
|
# define HEDLEY_NO_RETURN __declspec(noreturn)
|
|
#elif HEDLEY_TI_VERSION_CHECK(6,0,0) && defined(__cplusplus)
|
|
# define HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;")
|
|
#elif HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)
|
|
# define HEDLEY_NO_RETURN __attribute((noreturn))
|
|
#elif HEDLEY_PELLES_VERSION_CHECK(9,0,0)
|
|
# define HEDLEY_NO_RETURN __declspec(noreturn)
|
|
#else
|
|
# define HEDLEY_NO_RETURN
|
|
#endif
|
|
|
|
#if defined(HEDLEY_UNREACHABLE)
|
|
# undef HEDLEY_UNREACHABLE
|
|
#endif
|
|
#if defined(HEDLEY_UNREACHABLE_RETURN)
|
|
# undef HEDLEY_UNREACHABLE_RETURN
|
|
#endif
|
|
#if \
|
|
(HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(HEDLEY_ARM_VERSION))) || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,5,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(13,1,5)
|
|
# define HEDLEY_UNREACHABLE() __builtin_unreachable()
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(13,10,0)
|
|
# define HEDLEY_UNREACHABLE() __assume(0)
|
|
#elif HEDLEY_TI_VERSION_CHECK(6,0,0)
|
|
# if defined(__cplusplus)
|
|
# define HEDLEY_UNREACHABLE() std::_nassert(0)
|
|
# else
|
|
# define HEDLEY_UNREACHABLE() _nassert(0)
|
|
# endif
|
|
# define HEDLEY_UNREACHABLE_RETURN(value) return value
|
|
#elif defined(EXIT_FAILURE)
|
|
# define HEDLEY_UNREACHABLE() abort()
|
|
#else
|
|
# define HEDLEY_UNREACHABLE()
|
|
# define HEDLEY_UNREACHABLE_RETURN(value) return value
|
|
#endif
|
|
#if !defined(HEDLEY_UNREACHABLE_RETURN)
|
|
# define HEDLEY_UNREACHABLE_RETURN(value) HEDLEY_UNREACHABLE()
|
|
#endif
|
|
|
|
#if defined(HEDLEY_ASSUME)
|
|
# undef HEDLEY_ASSUME
|
|
#endif
|
|
#if \
|
|
HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_ASSUME(expr) __assume(expr)
|
|
#elif HEDLEY_HAS_BUILTIN(__builtin_assume)
|
|
# define HEDLEY_ASSUME(expr) __builtin_assume(expr)
|
|
#elif HEDLEY_TI_VERSION_CHECK(6,0,0)
|
|
# if defined(__cplusplus)
|
|
# define HEDLEY_ASSUME(expr) std::_nassert(expr)
|
|
# else
|
|
# define HEDLEY_ASSUME(expr) _nassert(expr)
|
|
# endif
|
|
#elif \
|
|
(HEDLEY_HAS_BUILTIN(__builtin_unreachable) && !defined(HEDLEY_ARM_VERSION)) || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,5,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(13,1,5)
|
|
# define HEDLEY_ASSUME(expr) ((void) ((expr) ? 1 : (__builtin_unreachable(), 1)))
|
|
#else
|
|
# define HEDLEY_ASSUME(expr) ((void) (expr))
|
|
#endif
|
|
|
|
|
|
HEDLEY_DIAGNOSTIC_PUSH
|
|
#if \
|
|
HEDLEY_HAS_WARNING("-Wvariadic-macros") || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,0,0)
|
|
# if defined(__clang__)
|
|
# pragma clang diagnostic ignored "-Wvariadic-macros"
|
|
# elif defined(HEDLEY_GCC_VERSION)
|
|
# pragma GCC diagnostic ignored "-Wvariadic-macros"
|
|
# endif
|
|
#endif
|
|
#if defined(HEDLEY_NON_NULL)
|
|
# undef HEDLEY_NON_NULL
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(nonnull) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,3,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0)
|
|
# define HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__)))
|
|
#else
|
|
# define HEDLEY_NON_NULL(...)
|
|
#endif
|
|
HEDLEY_DIAGNOSTIC_POP
|
|
|
|
#if defined(HEDLEY_PRINTF_FORMAT)
|
|
# undef HEDLEY_PRINTF_FORMAT
|
|
#endif
|
|
#if defined(__MINGW32__) && HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO)
|
|
# define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check)))
|
|
#elif defined(__MINGW32__) && HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO)
|
|
# define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check)))
|
|
#elif \
|
|
HEDLEY_HAS_ATTRIBUTE(format) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(5,6,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))
|
|
# define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check)))
|
|
#elif HEDLEY_PELLES_VERSION_CHECK(6,0,0)
|
|
# define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check))
|
|
#else
|
|
# define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_CONSTEXPR)
|
|
# undef HEDLEY_CONSTEXPR
|
|
#endif
|
|
#if defined(__cplusplus)
|
|
# if __cplusplus >= 201103L
|
|
# define HEDLEY_CONSTEXPR constexpr
|
|
# endif
|
|
#endif
|
|
#if !defined(HEDLEY_CONSTEXPR)
|
|
# define HEDLEY_CONSTEXPR
|
|
#endif
|
|
|
|
#if defined(HEDLEY_PREDICT)
|
|
# undef HEDLEY_PREDICT
|
|
#endif
|
|
#if defined(HEDLEY_LIKELY)
|
|
# undef HEDLEY_LIKELY
|
|
#endif
|
|
#if defined(HEDLEY_UNLIKELY)
|
|
# undef HEDLEY_UNLIKELY
|
|
#endif
|
|
#if defined(HEDLEY_UNPREDICTABLE)
|
|
# undef HEDLEY_UNPREDICTABLE
|
|
#endif
|
|
#if HEDLEY_HAS_BUILTIN(__builtin_unpredictable)
|
|
# define HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable(!!(expr))
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \
|
|
HEDLEY_GCC_VERSION_CHECK(9,0,0)
|
|
# define HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability(expr, value, probability)
|
|
# define HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1, probability)
|
|
# define HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0, probability)
|
|
# define HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1)
|
|
# define HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0)
|
|
# if !defined(HEDLEY_BUILTIN_UNPREDICTABLE)
|
|
# define HEDLEY_BUILTIN_UNPREDICTABLE(expr) __builtin_expect_with_probability(!!(expr), 1, 0.5)
|
|
# endif
|
|
#elif \
|
|
HEDLEY_HAS_BUILTIN(__builtin_expect) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,0,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
(HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(6,1,0) || \
|
|
HEDLEY_TINYC_VERSION_CHECK(0,9,27)
|
|
# define HEDLEY_PREDICT(expr, expected, probability) \
|
|
(((probability) >= 0.9) ? __builtin_expect(!!(expr), (expected)) : (((void) (expected)), !!(expr)))
|
|
# define HEDLEY_PREDICT_TRUE(expr, probability) \
|
|
(__extension__ ({ \
|
|
HEDLEY_CONSTEXPR double hedley_probability_ = (probability); \
|
|
((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \
|
|
}))
|
|
# define HEDLEY_PREDICT_FALSE(expr, probability) \
|
|
(__extension__ ({ \
|
|
HEDLEY_CONSTEXPR double hedley_probability_ = (probability); \
|
|
((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \
|
|
}))
|
|
# define HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1)
|
|
# define HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0)
|
|
#else
|
|
# define HEDLEY_PREDICT(expr, expected, probability) (((void) (expected)), !!(expr))
|
|
# define HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr))
|
|
# define HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr))
|
|
# define HEDLEY_LIKELY(expr) (!!(expr))
|
|
# define HEDLEY_UNLIKELY(expr) (!!(expr))
|
|
#endif
|
|
#if !defined(HEDLEY_UNPREDICTABLE)
|
|
# define HEDLEY_UNPREDICTABLE(expr) HEDLEY_PREDICT(expr, 1, 0.5)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_MALLOC)
|
|
# undef HEDLEY_MALLOC
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(malloc) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(12,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))
|
|
# define HEDLEY_MALLOC __attribute__((__malloc__))
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(14, 0, 0)
|
|
# define HEDLEY_MALLOC __declspec(restrict)
|
|
#else
|
|
# define HEDLEY_MALLOC
|
|
#endif
|
|
|
|
#if defined(HEDLEY_PURE)
|
|
# undef HEDLEY_PURE
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(pure) || \
|
|
HEDLEY_GCC_VERSION_CHECK(2,96,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
|
|
HEDLEY_PGI_VERSION_CHECK(17,10,0)
|
|
# define HEDLEY_PURE __attribute__((__pure__))
|
|
#elif HEDLEY_TI_VERSION_CHECK(6,0,0) && defined(__cplusplus)
|
|
# define HEDLEY_PURE _Pragma("FUNC_IS_PURE;")
|
|
#else
|
|
# define HEDLEY_PURE
|
|
#endif
|
|
|
|
#if defined(HEDLEY_CONST)
|
|
# undef HEDLEY_CONST
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(const) || \
|
|
HEDLEY_GCC_VERSION_CHECK(2,5,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
|
|
HEDLEY_PGI_VERSION_CHECK(17,10,0)
|
|
# define HEDLEY_CONST __attribute__((__const__))
|
|
#else
|
|
# define HEDLEY_CONST HEDLEY_PURE
|
|
#endif
|
|
|
|
#if defined(HEDLEY_RESTRICT)
|
|
# undef HEDLEY_RESTRICT
|
|
#endif
|
|
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus)
|
|
# define HEDLEY_RESTRICT restrict
|
|
#elif \
|
|
HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
|
|
HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
|
|
HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \
|
|
HEDLEY_IAR_VERSION_CHECK(8,0,0) || \
|
|
defined(__clang__)
|
|
# define HEDLEY_RESTRICT __restrict
|
|
#elif HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus)
|
|
# define HEDLEY_RESTRICT _Restrict
|
|
#else
|
|
# define HEDLEY_RESTRICT
|
|
#endif
|
|
|
|
#if defined(HEDLEY_INLINE)
|
|
# undef HEDLEY_INLINE
|
|
#endif
|
|
#if \
|
|
(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \
|
|
(defined(__cplusplus) && (__cplusplus >= 199711L))
|
|
# define HEDLEY_INLINE inline
|
|
#elif \
|
|
defined(HEDLEY_GCC_VERSION) || \
|
|
HEDLEY_ARM_VERSION_CHECK(6,2,0)
|
|
# define HEDLEY_INLINE __inline__
|
|
#elif \
|
|
HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_INLINE __inline
|
|
#else
|
|
# define HEDLEY_INLINE
|
|
#endif
|
|
|
|
#if defined(HEDLEY_ALWAYS_INLINE)
|
|
# undef HEDLEY_ALWAYS_INLINE
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(always_inline) || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,0,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))
|
|
# define HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) HEDLEY_INLINE
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(12,0,0)
|
|
# define HEDLEY_ALWAYS_INLINE __forceinline
|
|
#elif HEDLEY_TI_VERSION_CHECK(7,0,0) && defined(__cplusplus)
|
|
# define HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;")
|
|
#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_ALWAYS_INLINE _Pragma("inline=forced")
|
|
#else
|
|
# define HEDLEY_ALWAYS_INLINE HEDLEY_INLINE
|
|
#endif
|
|
|
|
#if defined(HEDLEY_NEVER_INLINE)
|
|
# undef HEDLEY_NEVER_INLINE
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(noinline) || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,0,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))
|
|
# define HEDLEY_NEVER_INLINE __attribute__((__noinline__))
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(13,10,0)
|
|
# define HEDLEY_NEVER_INLINE __declspec(noinline)
|
|
#elif HEDLEY_PGI_VERSION_CHECK(10,2,0)
|
|
# define HEDLEY_NEVER_INLINE _Pragma("noinline")
|
|
#elif HEDLEY_TI_VERSION_CHECK(6,0,0) && defined(__cplusplus)
|
|
# define HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;")
|
|
#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_NEVER_INLINE _Pragma("inline=never")
|
|
#elif HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)
|
|
# define HEDLEY_NEVER_INLINE __attribute((noinline))
|
|
#elif HEDLEY_PELLES_VERSION_CHECK(9,0,0)
|
|
# define HEDLEY_NEVER_INLINE __declspec(noinline)
|
|
#else
|
|
# define HEDLEY_NEVER_INLINE
|
|
#endif
|
|
|
|
#if defined(HEDLEY_PRIVATE)
|
|
# undef HEDLEY_PRIVATE
|
|
#endif
|
|
#if defined(HEDLEY_PUBLIC)
|
|
# undef HEDLEY_PUBLIC
|
|
#endif
|
|
#if defined(HEDLEY_IMPORT)
|
|
# undef HEDLEY_IMPORT
|
|
#endif
|
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
# define HEDLEY_PRIVATE
|
|
# define HEDLEY_PUBLIC __declspec(dllexport)
|
|
# define HEDLEY_IMPORT __declspec(dllimport)
|
|
#else
|
|
# if \
|
|
HEDLEY_HAS_ATTRIBUTE(visibility) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,3,0) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(13,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(8,0,0) || \
|
|
(HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_EABI__) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))
|
|
# define HEDLEY_PRIVATE __attribute__((__visibility__("hidden")))
|
|
# define HEDLEY_PUBLIC __attribute__((__visibility__("default")))
|
|
# else
|
|
# define HEDLEY_PRIVATE
|
|
# define HEDLEY_PUBLIC
|
|
# endif
|
|
# define HEDLEY_IMPORT extern
|
|
#endif
|
|
|
|
#if defined(HEDLEY_NO_THROW)
|
|
# undef HEDLEY_NO_THROW
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(nothrow) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,3,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_NO_THROW __attribute__((__nothrow__))
|
|
#elif \
|
|
HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0)
|
|
# define HEDLEY_NO_THROW __declspec(nothrow)
|
|
#else
|
|
# define HEDLEY_NO_THROW
|
|
#endif
|
|
|
|
#if defined(HEDLEY_FALL_THROUGH)
|
|
# undef HEDLEY_FALL_THROUGH
|
|
#endif
|
|
#if \
|
|
defined(__cplusplus) && \
|
|
(!defined(HEDLEY_SUNPRO_VERSION) || HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \
|
|
!defined(HEDLEY_PGI_VERSION)
|
|
# if \
|
|
(__cplusplus >= 201703L) || \
|
|
((__cplusplus >= 201103L) && HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough))
|
|
# define HEDLEY_FALL_THROUGH [[fallthrough]]
|
|
# elif (__cplusplus >= 201103L) && HEDLEY_HAS_CPP_ATTRIBUTE(clang::fallthrough)
|
|
# define HEDLEY_FALL_THROUGH [[clang::fallthrough]]
|
|
# elif (__cplusplus >= 201103L) && HEDLEY_GCC_VERSION_CHECK(7,0,0)
|
|
# define HEDLEY_FALL_THROUGH [[gnu::fallthrough]]
|
|
# endif
|
|
#endif
|
|
#if !defined(HEDLEY_FALL_THROUGH)
|
|
# if HEDLEY_GNUC_HAS_ATTRIBUTE(fallthrough,7,0,0) && !defined(HEDLEY_PGI_VERSION)
|
|
# define HEDLEY_FALL_THROUGH __attribute__((__fallthrough__))
|
|
# elif defined(__fallthrough) /* SAL */
|
|
# define HEDLEY_FALL_THROUGH __fallthrough
|
|
# else
|
|
# define HEDLEY_FALL_THROUGH
|
|
# endif
|
|
#endif
|
|
|
|
#if defined(HEDLEY_RETURNS_NON_NULL)
|
|
# undef HEDLEY_RETURNS_NON_NULL
|
|
#endif
|
|
#if \
|
|
HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,9,0)
|
|
# define HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__))
|
|
#elif defined(_Ret_notnull_) /* SAL */
|
|
# define HEDLEY_RETURNS_NON_NULL _Ret_notnull_
|
|
#else
|
|
# define HEDLEY_RETURNS_NON_NULL
|
|
#endif
|
|
|
|
#if defined(HEDLEY_ARRAY_PARAM)
|
|
# undef HEDLEY_ARRAY_PARAM
|
|
#endif
|
|
#if \
|
|
defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
|
|
!defined(__STDC_NO_VLA__) && \
|
|
!defined(__cplusplus) && \
|
|
!defined(HEDLEY_PGI_VERSION) && \
|
|
!defined(HEDLEY_TINYC_VERSION)
|
|
# define HEDLEY_ARRAY_PARAM(name) (name)
|
|
#else
|
|
# define HEDLEY_ARRAY_PARAM(name)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_IS_CONSTANT)
|
|
# undef HEDLEY_IS_CONSTANT
|
|
#endif
|
|
#if defined(HEDLEY_REQUIRE_CONSTEXPR)
|
|
# undef HEDLEY_REQUIRE_CONSTEXPR
|
|
#endif
|
|
/* Note the double-underscore. For internal use only; no API
|
|
* guarantees! */
|
|
#if defined(HEDLEY__IS_CONSTEXPR)
|
|
# undef HEDLEY__IS_CONSTEXPR
|
|
#endif
|
|
|
|
#if \
|
|
HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,4,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \
|
|
HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(13,1,0) || \
|
|
HEDLEY_TI_VERSION_CHECK(6,1,0) || \
|
|
HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) || \
|
|
HEDLEY_CRAY_VERSION_CHECK(8,1,0)
|
|
# define HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr)
|
|
#endif
|
|
#if !defined(__cplusplus)
|
|
# if \
|
|
HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \
|
|
HEDLEY_GCC_VERSION_CHECK(3,4,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(13,1,0) || \
|
|
HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(5,4,0) || \
|
|
HEDLEY_TINYC_VERSION_CHECK(0,9,24)
|
|
# if defined(__INTPTR_TYPE__)
|
|
# define HEDLEY__IS_CONSTEXPR(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)
|
|
# else
|
|
# include <stdint.h>
|
|
# define HEDLEY__IS_CONSTEXPR(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*)
|
|
# endif
|
|
# elif \
|
|
(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(HEDLEY_SUNPRO_VERSION) && !defined(HEDLEY_PGI_VERSION)) || \
|
|
HEDLEY_HAS_EXTENSION(c_generic_selections) || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,9,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \
|
|
HEDLEY_IBM_VERSION_CHECK(12,1,0) || \
|
|
HEDLEY_ARM_VERSION_CHECK(5,3,0)
|
|
# if defined(__INTPTR_TYPE__)
|
|
# define HEDLEY__IS_CONSTEXPR(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)
|
|
# else
|
|
# include <stdint.h>
|
|
# define HEDLEY__IS_CONSTEXPR(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0)
|
|
# endif
|
|
# elif \
|
|
defined(HEDLEY_GCC_VERSION) || \
|
|
defined(HEDLEY_INTEL_VERSION) || \
|
|
defined(HEDLEY_TINYC_VERSION) || \
|
|
defined(HEDLEY_TI_VERSION) || \
|
|
defined(__clang__)
|
|
# define HEDLEY__IS_CONSTEXPR(expr) ( \
|
|
sizeof(void) != \
|
|
sizeof(*( \
|
|
1 ? \
|
|
((void*) ((expr) * 0L) ) : \
|
|
((struct { char v[sizeof(void) * 2]; } *) 1) \
|
|
) \
|
|
) \
|
|
)
|
|
# endif
|
|
#endif
|
|
#if defined(HEDLEY__IS_CONSTEXPR)
|
|
# if !defined(HEDLEY_IS_CONSTANT)
|
|
# define HEDLEY_IS_CONSTANT(expr) HEDLEY__IS_CONSTEXPR(expr)
|
|
# endif
|
|
# define HEDLEY_REQUIRE_CONSTEXPR(expr) (HEDLEY__IS_CONSTEXPR(expr) ? (expr) : (-1))
|
|
#else
|
|
# if !defined(HEDLEY_IS_CONSTANT)
|
|
# define HEDLEY_IS_CONSTANT(expr) (0)
|
|
# endif
|
|
# define HEDLEY_REQUIRE_CONSTEXPR(expr) (expr)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_BEGIN_C_DECLS)
|
|
# undef HEDLEY_BEGIN_C_DECLS
|
|
#endif
|
|
#if defined(HEDLEY_END_C_DECLS)
|
|
# undef HEDLEY_END_C_DECLS
|
|
#endif
|
|
#if defined(HEDLEY_C_DECL)
|
|
# undef HEDLEY_C_DECL
|
|
#endif
|
|
#if defined(__cplusplus)
|
|
# define HEDLEY_BEGIN_C_DECLS extern "C" {
|
|
# define HEDLEY_END_C_DECLS }
|
|
# define HEDLEY_C_DECL extern "C"
|
|
#else
|
|
# define HEDLEY_BEGIN_C_DECLS
|
|
# define HEDLEY_END_C_DECLS
|
|
# define HEDLEY_C_DECL
|
|
#endif
|
|
|
|
#if defined(HEDLEY_STATIC_ASSERT)
|
|
# undef HEDLEY_STATIC_ASSERT
|
|
#endif
|
|
#if \
|
|
!defined(__cplusplus) && ( \
|
|
(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \
|
|
HEDLEY_HAS_FEATURE(c_static_assert) || \
|
|
HEDLEY_GCC_VERSION_CHECK(6,0,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
|
|
defined(_Static_assert) \
|
|
)
|
|
# define HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message)
|
|
#elif \
|
|
(defined(__cplusplus) && (__cplusplus >= 201703L)) || \
|
|
HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \
|
|
(defined(__cplusplus) && HEDLEY_TI_VERSION_CHECK(8,3,0))
|
|
# define HEDLEY_STATIC_ASSERT(expr, message) static_assert(expr, message)
|
|
#elif defined(__cplusplus) && (__cplusplus >= 201103L)
|
|
# define HEDLEY_STATIC_ASSERT(expr, message) static_assert(expr)
|
|
#else
|
|
# define HEDLEY_STATIC_ASSERT(expr, message)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_CONST_CAST)
|
|
# undef HEDLEY_CONST_CAST
|
|
#endif
|
|
#if defined(__cplusplus)
|
|
# define HEDLEY_CONST_CAST(T, expr) (const_cast<T>(expr))
|
|
#elif \
|
|
HEDLEY_HAS_WARNING("-Wcast-qual") || \
|
|
HEDLEY_GCC_VERSION_CHECK(4,6,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \
|
|
HEDLEY_DIAGNOSTIC_PUSH \
|
|
HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \
|
|
((T) (expr)); \
|
|
HEDLEY_DIAGNOSTIC_POP \
|
|
}))
|
|
#else
|
|
# define HEDLEY_CONST_CAST(T, expr) ((T) (expr))
|
|
#endif
|
|
|
|
#if defined(HEDLEY_REINTERPRET_CAST)
|
|
# undef HEDLEY_REINTERPRET_CAST
|
|
#endif
|
|
#if defined(__cplusplus)
|
|
# define HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast<T>(expr))
|
|
#else
|
|
# define HEDLEY_REINTERPRET_CAST(T, expr) (*((T*) &(expr)))
|
|
#endif
|
|
|
|
#if defined(HEDLEY_STATIC_CAST)
|
|
# undef HEDLEY_STATIC_CAST
|
|
#endif
|
|
#if defined(__cplusplus)
|
|
# define HEDLEY_STATIC_CAST(T, expr) (static_cast<T>(expr))
|
|
#else
|
|
# define HEDLEY_STATIC_CAST(T, expr) ((T) (expr))
|
|
#endif
|
|
|
|
#if defined(HEDLEY_CPP_CAST)
|
|
# undef HEDLEY_CPP_CAST
|
|
#endif
|
|
#if defined(__cplusplus)
|
|
# define HEDLEY_CPP_CAST(T, expr) static_cast<T>(expr)
|
|
#else
|
|
# define HEDLEY_CPP_CAST(T, expr) (expr)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_MESSAGE)
|
|
# undef HEDLEY_MESSAGE
|
|
#endif
|
|
#if HEDLEY_HAS_WARNING("-Wunknown-pragmas")
|
|
# define HEDLEY_MESSAGE(msg) \
|
|
HEDLEY_DIAGNOSTIC_PUSH \
|
|
HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \
|
|
HEDLEY_PRAGMA(message msg) \
|
|
HEDLEY_DIAGNOSTIC_POP
|
|
#elif \
|
|
HEDLEY_GCC_VERSION_CHECK(4,4,0) || \
|
|
HEDLEY_INTEL_VERSION_CHECK(13,0,0)
|
|
# define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(message msg)
|
|
#elif HEDLEY_CRAY_VERSION_CHECK(5,0,0)
|
|
# define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(_CRI message msg)
|
|
#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
|
|
# define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(message(msg))
|
|
#elif HEDLEY_PELLES_VERSION_CHECK(2,0,0)
|
|
# define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(message(msg))
|
|
#else
|
|
# define HEDLEY_MESSAGE(msg)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_WARNING)
|
|
# undef HEDLEY_WARNING
|
|
#endif
|
|
#if HEDLEY_HAS_WARNING("-Wunknown-pragmas")
|
|
# define HEDLEY_WARNING(msg) \
|
|
HEDLEY_DIAGNOSTIC_PUSH \
|
|
HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \
|
|
HEDLEY_PRAGMA(clang warning msg) \
|
|
HEDLEY_DIAGNOSTIC_POP
|
|
#elif \
|
|
HEDLEY_GCC_VERSION_CHECK(4,8,0) || \
|
|
HEDLEY_PGI_VERSION_CHECK(18,4,0)
|
|
# define HEDLEY_WARNING(msg) HEDLEY_PRAGMA(GCC warning msg)
|
|
#elif HEDLEY_MSVC_VERSION_CHECK(15,0,0)
|
|
# define HEDLEY_WARNING(msg) HEDLEY_PRAGMA(message(msg))
|
|
#else
|
|
# define HEDLEY_WARNING(msg) HEDLEY_MESSAGE(msg)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_REQUIRE_MSG)
|
|
# undef HEDLEY_REQUIRE_MSG
|
|
#endif
|
|
#if HEDLEY_HAS_ATTRIBUTE(diagnose_if)
|
|
# if HEDLEY_HAS_WARNING("-Wgcc-compat")
|
|
# define HEDLEY_REQUIRE_MSG(expr, msg) \
|
|
HEDLEY_DIAGNOSTIC_PUSH \
|
|
_Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \
|
|
__attribute__((__diagnose_if__(!(expr), msg, "error"))) \
|
|
HEDLEY_DIAGNOSTIC_POP
|
|
# else
|
|
# define HEDLEY_REQUIRE_MSG(expr, msg) __attribute__((__diagnose_if__(!(expr), msg, "error")))
|
|
# endif
|
|
#else
|
|
# define HEDLEY_REQUIRE_MSG(expr, msg)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_REQUIRE)
|
|
# undef HEDLEY_REQUIRE
|
|
#endif
|
|
#define HEDLEY_REQUIRE(expr) HEDLEY_REQUIRE_MSG(expr, #expr)
|
|
|
|
#if defined(HEDLEY_FLAGS)
|
|
# undef HEDLEY_FLAGS
|
|
#endif
|
|
#if HEDLEY_HAS_ATTRIBUTE(flag_enum)
|
|
# define HEDLEY_FLAGS __attribute__((__flag_enum__))
|
|
#endif
|
|
|
|
#if defined(HEDLEY_FLAGS_CAST)
|
|
# undef HEDLEY_FLAGS_CAST
|
|
#endif
|
|
#if HEDLEY_INTEL_VERSION_CHECK(19,0,0)
|
|
# define HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \
|
|
HEDLEY_DIAGNOSTIC_PUSH \
|
|
_Pragma("warning(disable:188)") \
|
|
((T) (expr)); \
|
|
HEDLEY_DIAGNOSTIC_POP \
|
|
}))
|
|
#else
|
|
# define HEDLEY_FLAGS_CAST(T, expr) HEDLEY_STATIC_CAST(T, expr)
|
|
#endif
|
|
|
|
/* Remaining macros are deprecated. */
|
|
|
|
#if defined(HEDLEY_GCC_NOT_CLANG_VERSION_CHECK)
|
|
# undef HEDLEY_GCC_NOT_CLANG_VERSION_CHECK
|
|
#endif
|
|
#if defined(__clang__)
|
|
# define HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0)
|
|
#else
|
|
# define HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
|
|
#endif
|
|
|
|
#if defined(HEDLEY_CLANG_HAS_ATTRIBUTE)
|
|
# undef HEDLEY_CLANG_HAS_ATTRIBUTE
|
|
#endif
|
|
#define HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) HEDLEY_HAS_ATTRIBUTE(attribute)
|
|
|
|
#if defined(HEDLEY_CLANG_HAS_CPP_ATTRIBUTE)
|
|
# undef HEDLEY_CLANG_HAS_CPP_ATTRIBUTE
|
|
#endif
|
|
#define HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) HEDLEY_HAS_CPP_ATTRIBUTE(attribute)
|
|
|
|
#if defined(HEDLEY_CLANG_HAS_BUILTIN)
|
|
# undef HEDLEY_CLANG_HAS_BUILTIN
|
|
#endif
|
|
#define HEDLEY_CLANG_HAS_BUILTIN(builtin) HEDLEY_HAS_BUILTIN(builtin)
|
|
|
|
#if defined(HEDLEY_CLANG_HAS_FEATURE)
|
|
# undef HEDLEY_CLANG_HAS_FEATURE
|
|
#endif
|
|
#define HEDLEY_CLANG_HAS_FEATURE(feature) HEDLEY_HAS_FEATURE(feature)
|
|
|
|
#if defined(HEDLEY_CLANG_HAS_EXTENSION)
|
|
# undef HEDLEY_CLANG_HAS_EXTENSION
|
|
#endif
|
|
#define HEDLEY_CLANG_HAS_EXTENSION(extension) HEDLEY_HAS_EXTENSION(extension)
|
|
|
|
#if defined(HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE)
|
|
# undef HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE
|
|
#endif
|
|
#define HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute)
|
|
|
|
#if defined(HEDLEY_CLANG_HAS_WARNING)
|
|
# undef HEDLEY_CLANG_HAS_WARNING
|
|
#endif
|
|
#define HEDLEY_CLANG_HAS_WARNING(warning) HEDLEY_HAS_WARNING(warning)
|
|
|
|
#endif /* !defined(HEDLEY_VERSION) || (HEDLEY_VERSION < X) */
|
|
|
|
|
|
namespace csv {
|
|
#ifdef _MSC_VER
|
|
#pragma region Compatibility Macros
|
|
#endif
|
|
/**
|
|
* @def IF_CONSTEXPR
|
|
* Expands to `if constexpr` in C++17 and `if` otherwise
|
|
*
|
|
* @def CONSTEXPR_VALUE
|
|
* Expands to `constexpr` in C++17 and `const` otherwise.
|
|
* Mainly used for global variables.
|
|
*
|
|
* @def CONSTEXPR
|
|
* Expands to `constexpr` in decent compilers and `inline` otherwise.
|
|
* Intended for functions and methods.
|
|
*/
|
|
|
|
#define STATIC_ASSERT(x) static_assert(x, "Assertion failed")
|
|
|
|
#if CMAKE_CXX_STANDARD == 17 || __cplusplus >= 201703L
|
|
#define CSV_HAS_CXX17
|
|
#endif
|
|
|
|
#if CMAKE_CXX_STANDARD >= 14 || __cplusplus >= 201402L
|
|
#define CSV_HAS_CXX14
|
|
#endif
|
|
|
|
#ifdef CSV_HAS_CXX17
|
|
#include <string_view>
|
|
/** @typedef string_view
|
|
* The string_view class used by this library.
|
|
*/
|
|
using string_view = std::string_view;
|
|
#else
|
|
/** @typedef string_view
|
|
* The string_view class used by this library.
|
|
*/
|
|
using string_view = nonstd::string_view;
|
|
#endif
|
|
|
|
#ifdef CSV_HAS_CXX17
|
|
#define IF_CONSTEXPR if constexpr
|
|
#define CONSTEXPR_VALUE constexpr
|
|
|
|
#define CONSTEXPR_17 constexpr
|
|
#else
|
|
#define IF_CONSTEXPR if
|
|
#define CONSTEXPR_VALUE const
|
|
|
|
#define CONSTEXPR_17 inline
|
|
#endif
|
|
|
|
#ifdef CSV_HAS_CXX14
|
|
template<bool B, class T = void>
|
|
using enable_if_t = std::enable_if_t<B, T>;
|
|
|
|
#define CONSTEXPR_14 constexpr
|
|
#define CONSTEXPR_VALUE_14 constexpr
|
|
#else
|
|
template<bool B, class T = void>
|
|
using enable_if_t = typename std::enable_if<B, T>::type;
|
|
|
|
#define CONSTEXPR_14 inline
|
|
#define CONSTEXPR_VALUE_14 const
|
|
#endif
|
|
|
|
// Resolves g++ bug with regard to constexpr methods
|
|
// See: https://stackoverflow.com/questions/36489369/constexpr-non-static-member-function-with-non-constexpr-constructor-gcc-clang-d
|
|
#if defined __GNUC__ && !defined __clang__
|
|
#if (__GNUC__ >= 7 &&__GNUC_MINOR__ >= 2) || (__GNUC__ >= 8)
|
|
#define CONSTEXPR constexpr
|
|
#endif
|
|
#else
|
|
#ifdef CSV_HAS_CXX17
|
|
#define CONSTEXPR constexpr
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef CONSTEXPR
|
|
#define CONSTEXPR inline
|
|
#endif
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma endregion
|
|
#endif
|
|
|
|
namespace internals {
|
|
// PAGE_SIZE macro could be already defined by the host system.
|
|
#if defined(PAGE_SIZE)
|
|
#undef PAGE_SIZE
|
|
#endif
|
|
|
|
// Get operating system specific details
|
|
#if defined(_WIN32)
|
|
inline int getpagesize() {
|
|
_SYSTEM_INFO sys_info = {};
|
|
GetSystemInfo(&sys_info);
|
|
return std::max(sys_info.dwPageSize, sys_info.dwAllocationGranularity);
|
|
}
|
|
|
|
const int PAGE_SIZE = getpagesize();
|
|
#elif defined(__linux__)
|
|
const int PAGE_SIZE = getpagesize();
|
|
#else
|
|
/** Size of a memory page in bytes. Used by
|
|
* csv::internals::CSVFieldArray when allocating blocks.
|
|
*/
|
|
const int PAGE_SIZE = 4096;
|
|
#endif
|
|
|
|
/** For functions that lazy load a large CSV, this determines how
|
|
* many bytes are read at a time
|
|
*/
|
|
constexpr size_t ITERATION_CHUNK_SIZE = 10000000; // 10MB
|
|
|
|
template<typename T>
|
|
inline bool is_equal(T a, T b, T epsilon = 0.001) {
|
|
/** Returns true if two floating point values are about the same */
|
|
static_assert(std::is_floating_point<T>::value, "T must be a floating point type.");
|
|
return std::abs(a - b) < epsilon;
|
|
}
|
|
|
|
/** @typedef ParseFlags
|
|
* An enum used for describing the significance of each character
|
|
* with respect to CSV parsing
|
|
*
|
|
* @see quote_escape_flag
|
|
*/
|
|
enum class ParseFlags {
|
|
QUOTE_ESCAPE_QUOTE = 0, /**< A quote inside or terminating a quote_escaped field */
|
|
QUOTE = 2 | 1, /**< Characters which may signify a quote escape */
|
|
NOT_SPECIAL = 4, /**< Characters with no special meaning or escaped delimiters and newlines */
|
|
DELIMITER = 4 | 2, /**< Characters which signify a new field */
|
|
NEWLINE = 4 | 2 | 1 /**< Characters which signify a new row */
|
|
};
|
|
|
|
/** Transform the ParseFlags given the context of whether or not the current
|
|
* field is quote escaped */
|
|
constexpr ParseFlags quote_escape_flag(ParseFlags flag, bool quote_escape) noexcept {
|
|
return (ParseFlags)((int)flag & ~((int)ParseFlags::QUOTE * quote_escape));
|
|
}
|
|
|
|
// Assumed to be true by parsing functions: allows for testing
|
|
// if an item is DELIMITER or NEWLINE with a >= statement
|
|
STATIC_ASSERT(ParseFlags::DELIMITER < ParseFlags::NEWLINE);
|
|
|
|
/** Optimizations for reducing branching in parsing loop
|
|
*
|
|
* Idea: The meaning of all non-quote characters changes depending
|
|
* on whether or not the parser is in a quote-escaped mode (0 or 1)
|
|
*/
|
|
STATIC_ASSERT(quote_escape_flag(ParseFlags::NOT_SPECIAL, false) == ParseFlags::NOT_SPECIAL);
|
|
STATIC_ASSERT(quote_escape_flag(ParseFlags::QUOTE, false) == ParseFlags::QUOTE);
|
|
STATIC_ASSERT(quote_escape_flag(ParseFlags::DELIMITER, false) == ParseFlags::DELIMITER);
|
|
STATIC_ASSERT(quote_escape_flag(ParseFlags::NEWLINE, false) == ParseFlags::NEWLINE);
|
|
|
|
STATIC_ASSERT(quote_escape_flag(ParseFlags::NOT_SPECIAL, true) == ParseFlags::NOT_SPECIAL);
|
|
STATIC_ASSERT(quote_escape_flag(ParseFlags::QUOTE, true) == ParseFlags::QUOTE_ESCAPE_QUOTE);
|
|
STATIC_ASSERT(quote_escape_flag(ParseFlags::DELIMITER, true) == ParseFlags::NOT_SPECIAL);
|
|
STATIC_ASSERT(quote_escape_flag(ParseFlags::NEWLINE, true) == ParseFlags::NOT_SPECIAL);
|
|
|
|
/** An array which maps ASCII chars to a parsing flag */
|
|
using ParseFlagMap = std::array<ParseFlags, 256>;
|
|
|
|
/** An array which maps ASCII chars to a flag indicating if it is whitespace */
|
|
using WhitespaceMap = std::array<bool, 256>;
|
|
}
|
|
|
|
/** Integer indicating a requested column wasn't found. */
|
|
constexpr int CSV_NOT_FOUND = -1;
|
|
}
|
|
|
|
|
|
namespace csv {
|
|
namespace internals {
|
|
struct ColNames;
|
|
using ColNamesPtr = std::shared_ptr<ColNames>;
|
|
|
|
/** @struct ColNames
|
|
* A data structure for handling column name information.
|
|
*
|
|
* These are created by CSVReader and passed (via smart pointer)
|
|
* to CSVRow objects it creates, thus
|
|
* allowing for indexing by column name.
|
|
*/
|
|
struct ColNames {
|
|
public:
|
|
ColNames() = default;
|
|
ColNames(const std::vector<std::string>& names) {
|
|
set_col_names(names);
|
|
}
|
|
|
|
std::vector<std::string> get_col_names() const;
|
|
void set_col_names(const std::vector<std::string>&);
|
|
int index_of(csv::string_view) const;
|
|
|
|
bool empty() const noexcept { return this->col_names.empty(); }
|
|
size_t size() const noexcept;
|
|
|
|
private:
|
|
std::vector<std::string> col_names;
|
|
std::unordered_map<std::string, size_t> col_pos;
|
|
};
|
|
}
|
|
}
|
|
/** @file
|
|
* Defines an object used to store CSV format settings
|
|
*/
|
|
|
|
#include <iterator>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
|
|
namespace csv {
|
|
namespace internals {
|
|
class IBasicCSVParser;
|
|
}
|
|
|
|
class CSVReader;
|
|
|
|
/** Determines how to handle rows that are shorter or longer than the majority */
|
|
enum class VariableColumnPolicy {
|
|
THROW = -1,
|
|
IGNORE_ROW = 0,
|
|
KEEP = 1
|
|
};
|
|
|
|
/** Stores the inferred format of a CSV file. */
|
|
struct CSVGuessResult {
|
|
char delim;
|
|
int header_row;
|
|
};
|
|
|
|
/** Stores information about how to parse a CSV file.
|
|
* Can be used to construct a csv::CSVReader.
|
|
*/
|
|
class CSVFormat {
|
|
public:
|
|
/** Settings for parsing a RFC 4180 CSV file */
|
|
CSVFormat() = default;
|
|
|
|
/** Sets the delimiter of the CSV file
|
|
*
|
|
* @throws `std::runtime_error` thrown if trim, quote, or possible delimiting characters overlap
|
|
*/
|
|
CSVFormat& delimiter(char delim);
|
|
|
|
/** Sets a list of potential delimiters
|
|
*
|
|
* @throws `std::runtime_error` thrown if trim, quote, or possible delimiting characters overlap
|
|
* @param[in] delim An array of possible delimiters to try parsing the CSV with
|
|
*/
|
|
CSVFormat& delimiter(const std::vector<char> & delim);
|
|
|
|
/** Sets the whitespace characters to be trimmed
|
|
*
|
|
* @throws `std::runtime_error` thrown if trim, quote, or possible delimiting characters overlap
|
|
* @param[in] ws An array of whitespace characters that should be trimmed
|
|
*/
|
|
CSVFormat& trim(const std::vector<char> & ws);
|
|
|
|
/** Sets the quote character
|
|
*
|
|
* @throws `std::runtime_error` thrown if trim, quote, or possible delimiting characters overlap
|
|
*/
|
|
CSVFormat& quote(char quote);
|
|
|
|
/** Sets the column names.
|
|
*
|
|
* @note Unsets any values set by header_row()
|
|
*/
|
|
CSVFormat& column_names(const std::vector<std::string>& names);
|
|
|
|
/** Sets the header row
|
|
*
|
|
* @note Unsets any values set by column_names()
|
|
*/
|
|
CSVFormat& header_row(int row);
|
|
|
|
/** Tells the parser that this CSV has no header row
|
|
*
|
|
* @note Equivalent to `header_row(-1)`
|
|
*
|
|
*/
|
|
CSVFormat& no_header() {
|
|
this->header_row(-1);
|
|
return *this;
|
|
}
|
|
|
|
/** Turn quoting on or off */
|
|
CSVFormat& quote(bool use_quote) {
|
|
this->no_quote = !use_quote;
|
|
return *this;
|
|
}
|
|
|
|
/** Tells the parser how to handle columns of a different length than the others */
|
|
CONSTEXPR_14 CSVFormat& variable_columns(VariableColumnPolicy policy = VariableColumnPolicy::IGNORE_ROW) {
|
|
this->variable_column_policy = policy;
|
|
return *this;
|
|
}
|
|
|
|
/** Tells the parser how to handle columns of a different length than the others */
|
|
CONSTEXPR_14 CSVFormat& variable_columns(bool policy) {
|
|
this->variable_column_policy = (VariableColumnPolicy)policy;
|
|
return *this;
|
|
}
|
|
|
|
#ifndef DOXYGEN_SHOULD_SKIP_THIS
|
|
char get_delim() const {
|
|
// This error should never be received by end users.
|
|
if (this->possible_delimiters.size() > 1) {
|
|
throw std::runtime_error("There is more than one possible delimiter.");
|
|
}
|
|
|
|
return this->possible_delimiters.at(0);
|
|
}
|
|
|
|
CONSTEXPR bool is_quoting_enabled() const { return !this->no_quote; }
|
|
CONSTEXPR char get_quote_char() const { return this->quote_char; }
|
|
CONSTEXPR int get_header() const { return this->header; }
|
|
std::vector<char> get_possible_delims() const { return this->possible_delimiters; }
|
|
std::vector<char> get_trim_chars() const { return this->trim_chars; }
|
|
CONSTEXPR VariableColumnPolicy get_variable_column_policy() const { return this->variable_column_policy; }
|
|
#endif
|
|
|
|
/** CSVFormat for guessing the delimiter */
|
|
CSV_INLINE static CSVFormat guess_csv() {
|
|
CSVFormat format;
|
|
format.delimiter({ ',', '|', '\t', ';', '^' })
|
|
.quote('"')
|
|
.header_row(0);
|
|
|
|
return format;
|
|
}
|
|
|
|
bool guess_delim() {
|
|
return this->possible_delimiters.size() > 1;
|
|
}
|
|
|
|
friend CSVReader;
|
|
friend internals::IBasicCSVParser;
|
|
|
|
private:
|
|
/**< Throws an error if delimiters and trim characters overlap */
|
|
void assert_no_char_overlap();
|
|
|
|
/**< Set of possible delimiters */
|
|
std::vector<char> possible_delimiters = { ',' };
|
|
|
|
/**< Set of whitespace characters to trim */
|
|
std::vector<char> trim_chars = {};
|
|
|
|
/**< Row number with columns (ignored if col_names is non-empty) */
|
|
int header = 0;
|
|
|
|
/**< Whether or not to use quoting */
|
|
bool no_quote = false;
|
|
|
|
/**< Quote character */
|
|
char quote_char = '"';
|
|
|
|
/**< Should be left empty unless file doesn't include header */
|
|
std::vector<std::string> col_names = {};
|
|
|
|
/**< Allow variable length columns? */
|
|
VariableColumnPolicy variable_column_policy = VariableColumnPolicy::IGNORE_ROW;
|
|
};
|
|
}
|
|
/** @file
|
|
* Defines the data type used for storing information about a CSV row
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <iterator>
|
|
#include <memory> // For CSVField
|
|
#include <limits> // For CSVField
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
/** @file
|
|
* @brief Implements data type parsing functionality
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <cctype>
|
|
#include <string>
|
|
#include <cassert>
|
|
|
|
|
|
namespace csv {
|
|
/** Enumerates the different CSV field types that are
|
|
* recognized by this library
|
|
*
|
|
* @note Overflowing integers will be stored and classified as doubles.
|
|
* @note Unlike previous releases, integer enums here are platform agnostic.
|
|
*/
|
|
enum class DataType {
|
|
UNKNOWN = -1,
|
|
CSV_NULL, /**< Empty string */
|
|
CSV_STRING, /**< Non-numeric string */
|
|
CSV_INT8, /**< 8-bit integer */
|
|
CSV_INT16, /**< 16-bit integer (short on MSVC/GCC) */
|
|
CSV_INT32, /**< 32-bit integer (int on MSVC/GCC) */
|
|
CSV_INT64, /**< 64-bit integer (long long on MSVC/GCC) */
|
|
CSV_DOUBLE /**< Floating point value */
|
|
};
|
|
|
|
static_assert(DataType::CSV_STRING < DataType::CSV_INT8, "String type should come before numeric types.");
|
|
static_assert(DataType::CSV_INT8 < DataType::CSV_INT64, "Smaller integer types should come before larger integer types.");
|
|
static_assert(DataType::CSV_INT64 < DataType::CSV_DOUBLE, "Integer types should come before floating point value types.");
|
|
|
|
namespace internals {
|
|
/** Compute 10 to the power of n */
|
|
template<typename T>
|
|
HEDLEY_CONST CONSTEXPR_14
|
|
long double pow10(const T& n) noexcept {
|
|
long double multiplicand = n > 0 ? 10 : 0.1,
|
|
ret = 1;
|
|
|
|
// Make all numbers positive
|
|
T iterations = n > 0 ? n : -n;
|
|
|
|
for (T i = 0; i < iterations; i++) {
|
|
ret *= multiplicand;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** Compute 10 to the power of n */
|
|
template<>
|
|
HEDLEY_CONST CONSTEXPR_14
|
|
long double pow10(const unsigned& n) noexcept {
|
|
long double multiplicand = n > 0 ? 10 : 0.1,
|
|
ret = 1;
|
|
|
|
for (unsigned i = 0; i < n; i++) {
|
|
ret *= multiplicand;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifndef DOXYGEN_SHOULD_SKIP_THIS
|
|
/** Private site-indexed array mapping byte sizes to an integer size enum */
|
|
constexpr DataType int_type_arr[8] = {
|
|
DataType::CSV_INT8, // 1
|
|
DataType::CSV_INT16, // 2
|
|
DataType::UNKNOWN,
|
|
DataType::CSV_INT32, // 4
|
|
DataType::UNKNOWN,
|
|
DataType::UNKNOWN,
|
|
DataType::UNKNOWN,
|
|
DataType::CSV_INT64 // 8
|
|
};
|
|
|
|
template<typename T>
|
|
inline DataType type_num() {
|
|
static_assert(std::is_integral<T>::value, "T should be an integral type.");
|
|
static_assert(sizeof(T) <= 8, "Byte size must be no greater than 8.");
|
|
return int_type_arr[sizeof(T) - 1];
|
|
}
|
|
|
|
template<> inline DataType type_num<float>() { return DataType::CSV_DOUBLE; }
|
|
template<> inline DataType type_num<double>() { return DataType::CSV_DOUBLE; }
|
|
template<> inline DataType type_num<long double>() { return DataType::CSV_DOUBLE; }
|
|
template<> inline DataType type_num<std::nullptr_t>() { return DataType::CSV_NULL; }
|
|
template<> inline DataType type_num<std::string>() { return DataType::CSV_STRING; }
|
|
|
|
CONSTEXPR_14 DataType data_type(csv::string_view in, long double* const out = nullptr);
|
|
#endif
|
|
|
|
/** Given a byte size, return the largest number than can be stored in
|
|
* an integer of that size
|
|
*
|
|
* Note: Provides a platform-agnostic way of mapping names like "long int" to
|
|
* byte sizes
|
|
*/
|
|
template<size_t Bytes>
|
|
CONSTEXPR_14 long double get_int_max() {
|
|
static_assert(Bytes == 1 || Bytes == 2 || Bytes == 4 || Bytes == 8,
|
|
"Bytes must be a power of 2 below 8.");
|
|
|
|
IF_CONSTEXPR (sizeof(signed char) == Bytes) {
|
|
return (long double)std::numeric_limits<signed char>::max();
|
|
}
|
|
|
|
IF_CONSTEXPR (sizeof(short) == Bytes) {
|
|
return (long double)std::numeric_limits<short>::max();
|
|
}
|
|
|
|
IF_CONSTEXPR (sizeof(int) == Bytes) {
|
|
return (long double)std::numeric_limits<int>::max();
|
|
}
|
|
|
|
IF_CONSTEXPR (sizeof(long int) == Bytes) {
|
|
return (long double)std::numeric_limits<long int>::max();
|
|
}
|
|
|
|
IF_CONSTEXPR (sizeof(long long int) == Bytes) {
|
|
return (long double)std::numeric_limits<long long int>::max();
|
|
}
|
|
|
|
HEDLEY_UNREACHABLE();
|
|
}
|
|
|
|
/** Given a byte size, return the largest number than can be stored in
|
|
* an unsigned integer of that size
|
|
*/
|
|
template<size_t Bytes>
|
|
CONSTEXPR_14 long double get_uint_max() {
|
|
static_assert(Bytes == 1 || Bytes == 2 || Bytes == 4 || Bytes == 8,
|
|
"Bytes must be a power of 2 below 8.");
|
|
|
|
IF_CONSTEXPR(sizeof(unsigned char) == Bytes) {
|
|
return (long double)std::numeric_limits<unsigned char>::max();
|
|
}
|
|
|
|
IF_CONSTEXPR(sizeof(unsigned short) == Bytes) {
|
|
return (long double)std::numeric_limits<unsigned short>::max();
|
|
}
|
|
|
|
IF_CONSTEXPR(sizeof(unsigned int) == Bytes) {
|
|
return (long double)std::numeric_limits<unsigned int>::max();
|
|
}
|
|
|
|
IF_CONSTEXPR(sizeof(unsigned long int) == Bytes) {
|
|
return (long double)std::numeric_limits<unsigned long int>::max();
|
|
}
|
|
|
|
IF_CONSTEXPR(sizeof(unsigned long long int) == Bytes) {
|
|
return (long double)std::numeric_limits<unsigned long long int>::max();
|
|
}
|
|
|
|
HEDLEY_UNREACHABLE();
|
|
}
|
|
|
|
/** Largest number that can be stored in a 8-bit integer */
|
|
CONSTEXPR_VALUE_14 long double CSV_INT8_MAX = get_int_max<1>();
|
|
|
|
/** Largest number that can be stored in a 16-bit integer */
|
|
CONSTEXPR_VALUE_14 long double CSV_INT16_MAX = get_int_max<2>();
|
|
|
|
/** Largest number that can be stored in a 32-bit integer */
|
|
CONSTEXPR_VALUE_14 long double CSV_INT32_MAX = get_int_max<4>();
|
|
|
|
/** Largest number that can be stored in a 64-bit integer */
|
|
CONSTEXPR_VALUE_14 long double CSV_INT64_MAX = get_int_max<8>();
|
|
|
|
/** Largest number that can be stored in a 8-bit ungisned integer */
|
|
CONSTEXPR_VALUE_14 long double CSV_UINT8_MAX = get_uint_max<1>();
|
|
|
|
/** Largest number that can be stored in a 16-bit unsigned integer */
|
|
CONSTEXPR_VALUE_14 long double CSV_UINT16_MAX = get_uint_max<2>();
|
|
|
|
/** Largest number that can be stored in a 32-bit unsigned integer */
|
|
CONSTEXPR_VALUE_14 long double CSV_UINT32_MAX = get_uint_max<4>();
|
|
|
|
/** Largest number that can be stored in a 64-bit unsigned integer */
|
|
CONSTEXPR_VALUE_14 long double CSV_UINT64_MAX = get_uint_max<8>();
|
|
|
|
/** Given a pointer to the start of what is start of
|
|
* the exponential part of a number written (possibly) in scientific notation
|
|
* parse the exponent
|
|
*/
|
|
HEDLEY_PRIVATE CONSTEXPR_14
|
|
DataType _process_potential_exponential(
|
|
csv::string_view exponential_part,
|
|
const long double& coeff,
|
|
long double * const out) {
|
|
long double exponent = 0;
|
|
auto result = data_type(exponential_part, &exponent);
|
|
|
|
// Exponents in scientific notation should not be decimal numbers
|
|
if (result >= DataType::CSV_INT8 && result < DataType::CSV_DOUBLE) {
|
|
if (out) *out = coeff * pow10(exponent);
|
|
return DataType::CSV_DOUBLE;
|
|
}
|
|
|
|
return DataType::CSV_STRING;
|
|
}
|
|
|
|
/** Given the absolute value of an integer, determine what numeric type
|
|
* it fits in
|
|
*/
|
|
HEDLEY_PRIVATE HEDLEY_PURE CONSTEXPR_14
|
|
DataType _determine_integral_type(const long double& number) noexcept {
|
|
// We can assume number is always non-negative
|
|
assert(number >= 0);
|
|
|
|
if (number <= internals::CSV_INT8_MAX)
|
|
return DataType::CSV_INT8;
|
|
else if (number <= internals::CSV_INT16_MAX)
|
|
return DataType::CSV_INT16;
|
|
else if (number <= internals::CSV_INT32_MAX)
|
|
return DataType::CSV_INT32;
|
|
else if (number <= internals::CSV_INT64_MAX)
|
|
return DataType::CSV_INT64;
|
|
else // Conversion to long long will cause an overflow
|
|
return DataType::CSV_DOUBLE;
|
|
}
|
|
|
|
/** Distinguishes numeric from other text values. Used by various
|
|
* type casting functions, like csv_parser::CSVReader::read_row()
|
|
*
|
|
* #### Rules
|
|
* - Leading and trailing whitespace ("padding") ignored
|
|
* - A string of just whitespace is NULL
|
|
*
|
|
* @param[in] in String value to be examined
|
|
* @param[out] out Pointer to long double where results of numeric parsing
|
|
* get stored
|
|
*/
|
|
CONSTEXPR_14
|
|
DataType data_type(csv::string_view in, long double* const out) {
|
|
// Empty string --> NULL
|
|
if (in.size() == 0)
|
|
return DataType::CSV_NULL;
|
|
|
|
bool ws_allowed = true,
|
|
neg_allowed = true,
|
|
dot_allowed = true,
|
|
digit_allowed = true,
|
|
has_digit = false,
|
|
prob_float = false;
|
|
|
|
unsigned places_after_decimal = 0;
|
|
long double integral_part = 0,
|
|
decimal_part = 0;
|
|
|
|
for (size_t i = 0, ilen = in.size(); i < ilen; i++) {
|
|
const char& current = in[i];
|
|
|
|
switch (current) {
|
|
case ' ':
|
|
if (!ws_allowed) {
|
|
if (isdigit(in[i - 1])) {
|
|
digit_allowed = false;
|
|
ws_allowed = true;
|
|
}
|
|
else {
|
|
// Ex: '510 123 4567'
|
|
return DataType::CSV_STRING;
|
|
}
|
|
}
|
|
break;
|
|
case '-':
|
|
if (!neg_allowed) {
|
|
// Ex: '510-123-4567'
|
|
return DataType::CSV_STRING;
|
|
}
|
|
|
|
neg_allowed = false;
|
|
break;
|
|
case '.':
|
|
if (!dot_allowed) {
|
|
return DataType::CSV_STRING;
|
|
}
|
|
|
|
dot_allowed = false;
|
|
prob_float = true;
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
// Process scientific notation
|
|
if (prob_float || (i && i + 1 < ilen && isdigit(in[i - 1]))) {
|
|
size_t exponent_start_idx = i + 1;
|
|
prob_float = true;
|
|
|
|
// Strip out plus sign
|
|
if (in[i + 1] == '+') {
|
|
exponent_start_idx++;
|
|
}
|
|
|
|
return _process_potential_exponential(
|
|
in.substr(exponent_start_idx),
|
|
neg_allowed ? integral_part + decimal_part : -(integral_part + decimal_part),
|
|
out
|
|
);
|
|
}
|
|
|
|
return DataType::CSV_STRING;
|
|
break;
|
|
default:
|
|
short digit = static_cast<short>(current - '0');
|
|
if (digit >= 0 && digit <= 9) {
|
|
// Process digit
|
|
has_digit = true;
|
|
|
|
if (!digit_allowed)
|
|
return DataType::CSV_STRING;
|
|
else if (ws_allowed) // Ex: '510 456'
|
|
ws_allowed = false;
|
|
|
|
// Build current number
|
|
if (prob_float)
|
|
decimal_part += digit / pow10(++places_after_decimal);
|
|
else
|
|
integral_part = (integral_part * 10) + digit;
|
|
}
|
|
else {
|
|
return DataType::CSV_STRING;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No non-numeric/non-whitespace characters found
|
|
if (has_digit) {
|
|
long double number = integral_part + decimal_part;
|
|
if (out) {
|
|
*out = neg_allowed ? number : -number;
|
|
}
|
|
|
|
return prob_float ? DataType::CSV_DOUBLE : _determine_integral_type(number);
|
|
}
|
|
|
|
// Just whitespace
|
|
return DataType::CSV_NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace csv {
|
|
namespace internals {
|
|
class IBasicCSVParser;
|
|
|
|
static const std::string ERROR_NAN = "Not a number.";
|
|
static const std::string ERROR_OVERFLOW = "Overflow error.";
|
|
static const std::string ERROR_FLOAT_TO_INT =
|
|
"Attempted to convert a floating point value to an integral type.";
|
|
static const std::string ERROR_NEG_TO_UNSIGNED = "Negative numbers cannot be converted to unsigned types.";
|
|
|
|
std::string json_escape_string(csv::string_view s) noexcept;
|
|
|
|
/** A barebones class used for describing CSV fields */
|
|
struct RawCSVField {
|
|
RawCSVField() = default;
|
|
RawCSVField(size_t _start, size_t _length, bool _double_quote = false) {
|
|
start = _start;
|
|
length = _length;
|
|
has_double_quote = _double_quote;
|
|
}
|
|
|
|
/** The start of the field, relative to the beginning of the row */
|
|
size_t start;
|
|
|
|
/** The length of the row, ignoring quote escape characters */
|
|
size_t length;
|
|
|
|
/** Whether or not the field contains an escaped quote */
|
|
bool has_double_quote;
|
|
};
|
|
|
|
/** A class used for efficiently storing RawCSVField objects and expanding as necessary
|
|
*
|
|
* @par Implementation
|
|
* This data structure stores RawCSVField in continguous blocks. When more capacity
|
|
* is needed, a new block is allocated, but previous data stays put.
|
|
*
|
|
* @par Thread Safety
|
|
* This class may be safely read from multiple threads and written to from one,
|
|
* as long as the writing thread does not actively touch fields which are being
|
|
* read.
|
|
*/
|
|
class CSVFieldList {
|
|
public:
|
|
/** Construct a CSVFieldList which allocates blocks of a certain size */
|
|
CSVFieldList(size_t single_buffer_capacity = (size_t)(internals::PAGE_SIZE / sizeof(RawCSVField))) :
|
|
_single_buffer_capacity(single_buffer_capacity) {
|
|
this->allocate();
|
|
}
|
|
|
|
// No copy constructor
|
|
CSVFieldList(const CSVFieldList& other) = delete;
|
|
|
|
// CSVFieldArrays may be moved
|
|
CSVFieldList(CSVFieldList&& other) :
|
|
_single_buffer_capacity(other._single_buffer_capacity) {
|
|
buffers = std::move(other.buffers);
|
|
_current_buffer_size = other._current_buffer_size;
|
|
_back = other._back;
|
|
}
|
|
|
|
~CSVFieldList() {
|
|
for (auto& buffer : buffers)
|
|
delete[] buffer;
|
|
}
|
|
|
|
template <class... Args>
|
|
void emplace_back(Args&&... args) {
|
|
if (this->_current_buffer_size == this->_single_buffer_capacity) {
|
|
this->allocate();
|
|
}
|
|
|
|
*(_back++) = RawCSVField(std::forward<Args>(args)...);
|
|
_current_buffer_size++;
|
|
}
|
|
|
|
size_t size() const noexcept {
|
|
return this->_current_buffer_size + ((this->buffers.size() - 1) * this->_single_buffer_capacity);
|
|
}
|
|
|
|
RawCSVField& operator[](size_t n) const;
|
|
|
|
private:
|
|
const size_t _single_buffer_capacity;
|
|
|
|
std::vector<RawCSVField*> buffers = {};
|
|
|
|
/** Number of items in the current buffer */
|
|
size_t _current_buffer_size = 0;
|
|
|
|
/** Pointer to the current empty field */
|
|
RawCSVField* _back = nullptr;
|
|
|
|
/** Allocate a new page of memory */
|
|
void allocate();
|
|
};
|
|
|
|
|
|
/** A class for storing raw CSV data and associated metadata */
|
|
struct RawCSVData {
|
|
std::shared_ptr<void> _data = nullptr;
|
|
csv::string_view data = "";
|
|
|
|
internals::CSVFieldList fields;
|
|
|
|
std::unordered_set<size_t> has_double_quotes = {};
|
|
|
|
// TODO: Consider replacing with a more thread-safe structure
|
|
std::unordered_map<size_t, std::string> double_quote_fields = {};
|
|
|
|
internals::ColNamesPtr col_names = nullptr;
|
|
internals::ParseFlagMap parse_flags;
|
|
internals::WhitespaceMap ws_flags;
|
|
};
|
|
|
|
using RawCSVDataPtr = std::shared_ptr<RawCSVData>;
|
|
}
|
|
|
|
/**
|
|
* @class CSVField
|
|
* @brief Data type representing individual CSV values.
|
|
* CSVFields can be obtained by using CSVRow::operator[]
|
|
*/
|
|
class CSVField {
|
|
public:
|
|
/** Constructs a CSVField from a string_view */
|
|
constexpr explicit CSVField(csv::string_view _sv) noexcept : sv(_sv) { };
|
|
|
|
operator std::string() const {
|
|
return std::string("<CSVField> ") + std::string(this->sv);
|
|
}
|
|
|
|
/** Returns the value casted to the requested type, performing type checking before.
|
|
*
|
|
* \par Valid options for T
|
|
* - std::string or csv::string_view
|
|
* - signed integral types (signed char, short, int, long int, long long int)
|
|
* - floating point types (float, double, long double)
|
|
* - unsigned integers are not supported at this time, but may be in a later release
|
|
*
|
|
* \par Invalid conversions
|
|
* - Converting non-numeric values to any numeric type
|
|
* - Converting floating point values to integers
|
|
* - Converting a large integer to a smaller type that will not hold it
|
|
*
|
|
* @note This method is capable of parsing scientific E-notation.
|
|
* See [this page](md_docs_source_scientific_notation.html)
|
|
* for more details.
|
|
*
|
|
* @throws std::runtime_error Thrown if an invalid conversion is performed.
|
|
*
|
|
* @warning Currently, conversions to floating point types are not
|
|
* checked for loss of precision
|
|
*
|
|
* @warning Any string_views returned are only guaranteed to be valid
|
|
* if the parent CSVRow is still alive. If you are concerned
|
|
* about object lifetimes, then grab a std::string or a
|
|
* numeric value.
|
|
*
|
|
*/
|
|
template<typename T = std::string> T get() {
|
|
IF_CONSTEXPR(std::is_arithmetic<T>::value) {
|
|
// Note: this->type() also converts the CSV value to float
|
|
if (this->type() <= DataType::CSV_STRING) {
|
|
throw std::runtime_error(internals::ERROR_NAN);
|
|
}
|
|
}
|
|
|
|
IF_CONSTEXPR(std::is_integral<T>::value) {
|
|
// Note: this->is_float() also converts the CSV value to float
|
|
if (this->is_float()) {
|
|
throw std::runtime_error(internals::ERROR_FLOAT_TO_INT);
|
|
}
|
|
|
|
IF_CONSTEXPR(std::is_unsigned<T>::value) {
|
|
if (this->value < 0) {
|
|
throw std::runtime_error(internals::ERROR_NEG_TO_UNSIGNED);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Allow fallthrough from previous if branch
|
|
IF_CONSTEXPR(!std::is_floating_point<T>::value) {
|
|
IF_CONSTEXPR(std::is_unsigned<T>::value) {
|
|
// Quick hack to perform correct unsigned integer boundary checks
|
|
if (this->value > internals::get_uint_max<sizeof(T)>()) {
|
|
throw std::runtime_error(internals::ERROR_OVERFLOW);
|
|
}
|
|
}
|
|
else if (internals::type_num<T>() < this->_type) {
|
|
throw std::runtime_error(internals::ERROR_OVERFLOW);
|
|
}
|
|
}
|
|
|
|
return static_cast<T>(this->value);
|
|
}
|
|
|
|
/** Parse a hexadecimal value, returning false if the value is not hex. */
|
|
bool try_parse_hex(int& parsedValue);
|
|
|
|
/** Compares the contents of this field to a numeric value. If this
|
|
* field does not contain a numeric value, then all comparisons return
|
|
* false.
|
|
*
|
|
* @note Floating point values are considered equal if they are within
|
|
* `0.000001` of each other.
|
|
*
|
|
* @warning Multiple numeric comparisons involving the same field can
|
|
* be done more efficiently by calling the CSVField::get<>() method.
|
|
*
|
|
* @sa csv::CSVField::operator==(const char * other)
|
|
* @sa csv::CSVField::operator==(csv::string_view other)
|
|
*/
|
|
template<typename T>
|
|
CONSTEXPR_14 bool operator==(T other) const noexcept
|
|
{
|
|
static_assert(std::is_arithmetic<T>::value,
|
|
"T should be a numeric value.");
|
|
|
|
if (this->_type != DataType::UNKNOWN) {
|
|
if (this->_type == DataType::CSV_STRING) {
|
|
return false;
|
|
}
|
|
|
|
return internals::is_equal(value, static_cast<long double>(other), 0.000001L);
|
|
}
|
|
|
|
long double out = 0;
|
|
if (internals::data_type(this->sv, &out) == DataType::CSV_STRING) {
|
|
return false;
|
|
}
|
|
|
|
return internals::is_equal(out, static_cast<long double>(other), 0.000001L);
|
|
}
|
|
|
|
/** Return a string view over the field's contents */
|
|
CONSTEXPR csv::string_view get_sv() const noexcept { return this->sv; }
|
|
|
|
/** Returns true if field is an empty string or string of whitespace characters */
|
|
CONSTEXPR_14 bool is_null() noexcept { return type() == DataType::CSV_NULL; }
|
|
|
|
/** Returns true if field is a non-numeric, non-empty string */
|
|
CONSTEXPR_14 bool is_str() noexcept { return type() == DataType::CSV_STRING; }
|
|
|
|
/** Returns true if field is an integer or float */
|
|
CONSTEXPR_14 bool is_num() noexcept { return type() >= DataType::CSV_INT8; }
|
|
|
|
/** Returns true if field is an integer */
|
|
CONSTEXPR_14 bool is_int() noexcept {
|
|
return (type() >= DataType::CSV_INT8) && (type() <= DataType::CSV_INT64);
|
|
}
|
|
|
|
/** Returns true if field is a floating point value */
|
|
CONSTEXPR_14 bool is_float() noexcept { return type() == DataType::CSV_DOUBLE; };
|
|
|
|
/** Return the type of the underlying CSV data */
|
|
CONSTEXPR_14 DataType type() noexcept {
|
|
this->get_value();
|
|
return _type;
|
|
}
|
|
|
|
private:
|
|
long double value = 0; /**< Cached numeric value */
|
|
csv::string_view sv = ""; /**< A pointer to this field's text */
|
|
DataType _type = DataType::UNKNOWN; /**< Cached data type value */
|
|
CONSTEXPR_14 void get_value() noexcept {
|
|
/* Check to see if value has been cached previously, if not
|
|
* evaluate it
|
|
*/
|
|
if ((int)_type < 0) {
|
|
this->_type = internals::data_type(this->sv, &this->value);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Data structure for representing CSV rows */
|
|
class CSVRow {
|
|
public:
|
|
friend internals::IBasicCSVParser;
|
|
|
|
CSVRow() = default;
|
|
|
|
/** Construct a CSVRow from a RawCSVDataPtr */
|
|
CSVRow(internals::RawCSVDataPtr _data) : data(_data) {}
|
|
CSVRow(internals::RawCSVDataPtr _data, size_t _data_start, size_t _field_bounds)
|
|
: data(_data), data_start(_data_start), fields_start(_field_bounds) {}
|
|
|
|
/** Indicates whether row is empty or not */
|
|
CONSTEXPR bool empty() const noexcept { return this->size() == 0; }
|
|
|
|
/** Return the number of fields in this row */
|
|
CONSTEXPR size_t size() const noexcept { return row_length; }
|
|
|
|
/** @name Value Retrieval */
|
|
///@{
|
|
CSVField operator[](size_t n) const;
|
|
CSVField operator[](const std::string&) const;
|
|
std::string to_json(const std::vector<std::string>& subset = {}) const;
|
|
std::string to_json_array(const std::vector<std::string>& subset = {}) const;
|
|
|
|
/** Retrieve this row's associated column names */
|
|
std::vector<std::string> get_col_names() const {
|
|
return this->data->col_names->get_col_names();
|
|
}
|
|
|
|
/** Convert this CSVRow into a vector of strings.
|
|
* **Note**: This is a less efficient method of
|
|
* accessing data than using the [] operator.
|
|
*/
|
|
operator std::vector<std::string>() const;
|
|
///@}
|
|
|
|
/** A random access iterator over the contents of a CSV row.
|
|
* Each iterator points to a CSVField.
|
|
*/
|
|
class iterator {
|
|
public:
|
|
#ifndef DOXYGEN_SHOULD_SKIP_THIS
|
|
using value_type = CSVField;
|
|
using difference_type = int;
|
|
|
|
// Using CSVField * as pointer type causes segfaults in MSVC debug builds
|
|
// but using shared_ptr as pointer type won't compile in g++
|
|
#ifdef _MSC_BUILD
|
|
using pointer = std::shared_ptr<CSVField>;
|
|
#else
|
|
using pointer = CSVField * ;
|
|
#endif
|
|
|
|
using reference = CSVField & ;
|
|
using iterator_category = std::random_access_iterator_tag;
|
|
#endif
|
|
iterator(const CSVRow*, int i);
|
|
|
|
reference operator*() const;
|
|
pointer operator->() const;
|
|
|
|
iterator operator++(int);
|
|
iterator& operator++();
|
|
iterator operator--(int);
|
|
iterator& operator--();
|
|
iterator operator+(difference_type n) const;
|
|
iterator operator-(difference_type n) const;
|
|
|
|
/** Two iterators are equal if they point to the same field */
|
|
CONSTEXPR bool operator==(const iterator& other) const noexcept {
|
|
return this->i == other.i;
|
|
};
|
|
|
|
CONSTEXPR bool operator!=(const iterator& other) const noexcept { return !operator==(other); }
|
|
|
|
#ifndef NDEBUG
|
|
friend CSVRow;
|
|
#endif
|
|
|
|
private:
|
|
const CSVRow * daddy = nullptr; // Pointer to parent
|
|
std::shared_ptr<CSVField> field = nullptr; // Current field pointed at
|
|
int i = 0; // Index of current field
|
|
};
|
|
|
|
/** A reverse iterator over the contents of a CSVRow. */
|
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
|
|
|
/** @name Iterators
|
|
* @brief Each iterator points to a CSVField object.
|
|
*/
|
|
///@{
|
|
iterator begin() const;
|
|
iterator end() const noexcept;
|
|
reverse_iterator rbegin() const noexcept;
|
|
reverse_iterator rend() const;
|
|
///@}
|
|
|
|
private:
|
|
/** Retrieve a string view corresponding to the specified index */
|
|
csv::string_view get_field(size_t index) const;
|
|
|
|
internals::RawCSVDataPtr data;
|
|
|
|
/** Where in RawCSVData.data we start */
|
|
size_t data_start = 0;
|
|
|
|
/** Where in the RawCSVDataPtr.fields array we start */
|
|
size_t fields_start = 0;
|
|
|
|
/** How many columns this row spans */
|
|
size_t row_length = 0;
|
|
};
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma region CSVField::get Specializations
|
|
#endif
|
|
/** Retrieve this field's original string */
|
|
template<>
|
|
inline std::string CSVField::get<std::string>() {
|
|
return std::string(this->sv);
|
|
}
|
|
|
|
/** Retrieve a view over this field's string
|
|
*
|
|
* @warning This string_view is only guaranteed to be valid as long as this
|
|
* CSVRow is still alive.
|
|
*/
|
|
template<>
|
|
CONSTEXPR_14 csv::string_view CSVField::get<csv::string_view>() {
|
|
return this->sv;
|
|
}
|
|
|
|
/** Retrieve this field's value as a long double */
|
|
template<>
|
|
CONSTEXPR_14 long double CSVField::get<long double>() {
|
|
if (!is_num())
|
|
throw std::runtime_error(internals::ERROR_NAN);
|
|
|
|
return this->value;
|
|
}
|
|
#ifdef _MSC_VER
|
|
#pragma endregion CSVField::get Specializations
|
|
#endif
|
|
|
|
/** Compares the contents of this field to a string */
|
|
template<>
|
|
CONSTEXPR bool CSVField::operator==(const char * other) const noexcept
|
|
{
|
|
return this->sv == other;
|
|
}
|
|
|
|
/** Compares the contents of this field to a string */
|
|
template<>
|
|
CONSTEXPR bool CSVField::operator==(csv::string_view other) const noexcept
|
|
{
|
|
return this->sv == other;
|
|
}
|
|
}
|
|
|
|
inline std::ostream& operator << (std::ostream& os, csv::CSVField const& value) {
|
|
os << std::string(value);
|
|
return os;
|
|
}
|
|
|
|
|
|
namespace csv {
|
|
namespace internals {
|
|
/** Create a vector v where each index i corresponds to the
|
|
* ASCII number for a character and, v[i + 128] labels it according to
|
|
* the CSVReader::ParseFlags enum
|
|
*/
|
|
HEDLEY_CONST CONSTEXPR_17 ParseFlagMap make_parse_flags(char delimiter) {
|
|
std::array<ParseFlags, 256> ret = {};
|
|
for (int i = -128; i < 128; i++) {
|
|
const int arr_idx = i + 128;
|
|
char ch = char(i);
|
|
|
|
if (ch == delimiter)
|
|
ret[arr_idx] = ParseFlags::DELIMITER;
|
|
else if (ch == '\r' || ch == '\n')
|
|
ret[arr_idx] = ParseFlags::NEWLINE;
|
|
else
|
|
ret[arr_idx] = ParseFlags::NOT_SPECIAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** Create a vector v where each index i corresponds to the
|
|
* ASCII number for a character and, v[i + 128] labels it according to
|
|
* the CSVReader::ParseFlags enum
|
|
*/
|
|
HEDLEY_CONST CONSTEXPR_17 ParseFlagMap make_parse_flags(char delimiter, char quote_char) {
|
|
std::array<ParseFlags, 256> ret = make_parse_flags(delimiter);
|
|
ret[(size_t)quote_char + 128] = ParseFlags::QUOTE;
|
|
return ret;
|
|
}
|
|
|
|
/** Create a vector v where each index i corresponds to the
|
|
* ASCII number for a character c and, v[i + 128] is true if
|
|
* c is a whitespace character
|
|
*/
|
|
HEDLEY_CONST CONSTEXPR_17 WhitespaceMap make_ws_flags(const char* ws_chars, size_t n_chars) {
|
|
std::array<bool, 256> ret = {};
|
|
for (int i = -128; i < 128; i++) {
|
|
const int arr_idx = i + 128;
|
|
char ch = char(i);
|
|
ret[arr_idx] = false;
|
|
|
|
for (size_t j = 0; j < n_chars; j++) {
|
|
if (ws_chars[j] == ch) {
|
|
ret[arr_idx] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
inline WhitespaceMap make_ws_flags(const std::vector<char>& flags) {
|
|
return make_ws_flags(flags.data(), flags.size());
|
|
}
|
|
|
|
CSV_INLINE size_t get_file_size(csv::string_view filename);
|
|
|
|
CSV_INLINE std::string get_csv_head(csv::string_view filename);
|
|
|
|
/** Read the first 500KB of a CSV file */
|
|
CSV_INLINE std::string get_csv_head(csv::string_view filename, size_t file_size);
|
|
|
|
/** A std::deque wrapper which allows multiple read and write threads to concurrently
|
|
* access it along with providing read threads the ability to wait for the deque
|
|
* to become populated
|
|
*/
|
|
template<typename T>
|
|
class ThreadSafeDeque {
|
|
public:
|
|
ThreadSafeDeque(size_t notify_size = 100) : _notify_size(notify_size) {};
|
|
ThreadSafeDeque(const ThreadSafeDeque& other) {
|
|
this->data = other.data;
|
|
this->_notify_size = other._notify_size;
|
|
}
|
|
|
|
ThreadSafeDeque(const std::deque<T>& source) : ThreadSafeDeque() {
|
|
this->data = source;
|
|
}
|
|
|
|
void clear() noexcept { this->data.clear(); }
|
|
|
|
bool empty() const noexcept {
|
|
return this->data.empty();
|
|
}
|
|
|
|
T& front() noexcept {
|
|
return this->data.front();
|
|
}
|
|
|
|
T& operator[](size_t n) {
|
|
return this->data[n];
|
|
}
|
|
|
|
void push_back(T&& item) {
|
|
std::lock_guard<std::mutex> lock{ this->_lock };
|
|
this->data.push_back(std::move(item));
|
|
|
|
if (this->size() >= _notify_size) {
|
|
this->_cond.notify_all();
|
|
}
|
|
}
|
|
|
|
T pop_front() noexcept {
|
|
std::lock_guard<std::mutex> lock{ this->_lock };
|
|
T item = std::move(data.front());
|
|
data.pop_front();
|
|
return item;
|
|
}
|
|
|
|
size_t size() const noexcept { return this->data.size(); }
|
|
|
|
/** Returns true if a thread is actively pushing items to this deque */
|
|
constexpr bool is_waitable() const noexcept { return this->_is_waitable; }
|
|
|
|
/** Wait for an item to become available */
|
|
void wait() {
|
|
if (!is_waitable()) {
|
|
return;
|
|
}
|
|
|
|
std::unique_lock<std::mutex> lock{ this->_lock };
|
|
this->_cond.wait(lock, [this] { return this->size() >= _notify_size || !this->is_waitable(); });
|
|
lock.unlock();
|
|
}
|
|
|
|
typename std::deque<T>::iterator begin() noexcept {
|
|
return this->data.begin();
|
|
}
|
|
|
|
typename std::deque<T>::iterator end() noexcept {
|
|
return this->data.end();
|
|
}
|
|
|
|
/** Tell listeners that this deque is actively being pushed to */
|
|
void notify_all() {
|
|
std::unique_lock<std::mutex> lock{ this->_lock };
|
|
this->_is_waitable = true;
|
|
this->_cond.notify_all();
|
|
}
|
|
|
|
/** Tell all listeners to stop */
|
|
void kill_all() {
|
|
std::unique_lock<std::mutex> lock{ this->_lock };
|
|
this->_is_waitable = false;
|
|
this->_cond.notify_all();
|
|
}
|
|
|
|
private:
|
|
bool _is_waitable = false;
|
|
size_t _notify_size;
|
|
std::mutex _lock;
|
|
std::condition_variable _cond;
|
|
std::deque<T> data;
|
|
};
|
|
|
|
constexpr const int UNINITIALIZED_FIELD = -1;
|
|
}
|
|
|
|
/** Standard type for storing collection of rows */
|
|
using RowCollection = internals::ThreadSafeDeque<CSVRow>;
|
|
|
|
namespace internals {
|
|
/** Abstract base class which provides CSV parsing logic.
|
|
*
|
|
* Concrete implementations may customize this logic across
|
|
* different input sources, such as memory mapped files, stringstreams,
|
|
* etc...
|
|
*/
|
|
class IBasicCSVParser {
|
|
public:
|
|
IBasicCSVParser() = default;
|
|
IBasicCSVParser(const CSVFormat&, const ColNamesPtr&);
|
|
IBasicCSVParser(const ParseFlagMap& parse_flags, const WhitespaceMap& ws_flags
|
|
) : _parse_flags(parse_flags), _ws_flags(ws_flags) {};
|
|
|
|
virtual ~IBasicCSVParser() {}
|
|
|
|
/** Whether or not we have reached the end of source */
|
|
bool eof() { return this->_eof; }
|
|
|
|
/** Parse the next block of data */
|
|
virtual void next(size_t bytes) = 0;
|
|
|
|
/** Indicate the last block of data has been parsed */
|
|
void end_feed();
|
|
|
|
CONSTEXPR_17 ParseFlags parse_flag(const char ch) const noexcept {
|
|
return _parse_flags.data()[ch + 128];
|
|
}
|
|
|
|
CONSTEXPR_17 ParseFlags compound_parse_flag(const char ch) const noexcept {
|
|
return quote_escape_flag(parse_flag(ch), this->quote_escape);
|
|
}
|
|
|
|
/** Whether or not this CSV has a UTF-8 byte order mark */
|
|
CONSTEXPR bool utf8_bom() const { return this->_utf8_bom; }
|
|
|
|
void set_output(RowCollection& rows) { this->_records = &rows; }
|
|
|
|
protected:
|
|
/** @name Current Parser State */
|
|
///@{
|
|
CSVRow current_row;
|
|
RawCSVDataPtr data_ptr = nullptr;
|
|
ColNamesPtr _col_names = nullptr;
|
|
CSVFieldList* fields = nullptr;
|
|
int field_start = UNINITIALIZED_FIELD;
|
|
size_t field_length = 0;
|
|
|
|
/** An array where the (i + 128)th slot gives the ParseFlags for ASCII character i */
|
|
ParseFlagMap _parse_flags;
|
|
///@}
|
|
|
|
/** @name Current Stream/File State */
|
|
///@{
|
|
bool _eof = false;
|
|
|
|
/** The size of the incoming CSV */
|
|
size_t source_size = 0;
|
|
///@}
|
|
|
|
/** Whether or not source needs to be read in chunks */
|
|
CONSTEXPR bool no_chunk() const { return this->source_size < ITERATION_CHUNK_SIZE; }
|
|
|
|
/** Parse the current chunk of data *
|
|
*
|
|
* @returns How many character were read that are part of complete rows
|
|
*/
|
|
size_t parse();
|
|
|
|
/** Create a new RawCSVDataPtr for a new chunk of data */
|
|
void reset_data_ptr();
|
|
private:
|
|
/** An array where the (i + 128)th slot determines whether ASCII character i should
|
|
* be trimmed
|
|
*/
|
|
WhitespaceMap _ws_flags;
|
|
bool quote_escape = false;
|
|
bool field_has_double_quote = false;
|
|
|
|
/** Where we are in the current data block */
|
|
size_t data_pos = 0;
|
|
|
|
/** Whether or not an attempt to find Unicode BOM has been made */
|
|
bool unicode_bom_scan = false;
|
|
bool _utf8_bom = false;
|
|
|
|
/** Where complete rows should be pushed to */
|
|
RowCollection* _records = nullptr;
|
|
|
|
CONSTEXPR_17 bool ws_flag(const char ch) const noexcept {
|
|
return _ws_flags.data()[ch + 128];
|
|
}
|
|
|
|
size_t& current_row_start() {
|
|
return this->current_row.data_start;
|
|
}
|
|
|
|
void parse_field() noexcept;
|
|
|
|
/** Finish parsing the current field */
|
|
void push_field();
|
|
|
|
/** Finish parsing the current row */
|
|
void push_row();
|
|
|
|
/** Handle possible Unicode byte order mark */
|
|
void trim_utf8_bom();
|
|
};
|
|
|
|
/** A class for parsing CSV data from a `std::stringstream`
|
|
* or an `std::ifstream`
|
|
*/
|
|
template<typename TStream>
|
|
class StreamParser: public IBasicCSVParser {
|
|
using RowCollection = ThreadSafeDeque<CSVRow>;
|
|
|
|
public:
|
|
StreamParser(TStream& source,
|
|
const CSVFormat& format,
|
|
const ColNamesPtr& col_names = nullptr
|
|
) : IBasicCSVParser(format, col_names), _source(std::move(source)) {};
|
|
|
|
StreamParser(
|
|
TStream& source,
|
|
internals::ParseFlagMap parse_flags,
|
|
internals::WhitespaceMap ws_flags) :
|
|
IBasicCSVParser(parse_flags, ws_flags),
|
|
_source(std::move(source))
|
|
{};
|
|
|
|
~StreamParser() {}
|
|
|
|
void next(size_t bytes = ITERATION_CHUNK_SIZE) override {
|
|
if (this->eof()) return;
|
|
|
|
this->reset_data_ptr();
|
|
this->data_ptr->_data = std::make_shared<std::string>();
|
|
|
|
if (source_size == 0) {
|
|
const auto start = _source.tellg();
|
|
_source.seekg(0, std::ios::end);
|
|
const auto end = _source.tellg();
|
|
_source.seekg(0, std::ios::beg);
|
|
|
|
source_size = end - start;
|
|
}
|
|
|
|
// Read data into buffer
|
|
size_t length = std::min(source_size - stream_pos, bytes);
|
|
std::unique_ptr<char[]> buff(new char[length]);
|
|
_source.seekg(stream_pos, std::ios::beg);
|
|
_source.read(buff.get(), length);
|
|
stream_pos = _source.tellg();
|
|
((std::string*)(this->data_ptr->_data.get()))->assign(buff.get(), length);
|
|
|
|
// Create string_view
|
|
this->data_ptr->data = *((std::string*)this->data_ptr->_data.get());
|
|
|
|
// Parse
|
|
this->current_row = CSVRow(this->data_ptr);
|
|
size_t remainder = this->parse();
|
|
|
|
if (stream_pos == source_size || no_chunk()) {
|
|
this->_eof = true;
|
|
this->end_feed();
|
|
}
|
|
else {
|
|
this->stream_pos -= (length - remainder);
|
|
}
|
|
}
|
|
|
|
private:
|
|
TStream _source;
|
|
size_t stream_pos = 0;
|
|
};
|
|
|
|
/** Parser for memory-mapped files
|
|
*
|
|
* @par Implementation
|
|
* This class constructs moving windows over a file to avoid
|
|
* creating massive memory maps which may require more RAM
|
|
* than the user has available. It contains logic to automatically
|
|
* re-align each memory map to the beginning of a CSV row.
|
|
*
|
|
*/
|
|
class MmapParser : public IBasicCSVParser {
|
|
public:
|
|
MmapParser(csv::string_view filename,
|
|
const CSVFormat& format,
|
|
const ColNamesPtr& col_names = nullptr
|
|
) : IBasicCSVParser(format, col_names) {
|
|
this->_filename = filename.data();
|
|
this->source_size = get_file_size(filename);
|
|
};
|
|
|
|
~MmapParser() {}
|
|
|
|
void next(size_t bytes) override;
|
|
|
|
private:
|
|
std::string _filename;
|
|
size_t mmap_pos = 0;
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
/** The all encompassing namespace */
|
|
namespace csv {
|
|
/** Stuff that is generally not of interest to end-users */
|
|
namespace internals {
|
|
std::string format_row(const std::vector<std::string>& row, csv::string_view delim = ", ");
|
|
|
|
std::vector<std::string> _get_col_names( csv::string_view head, const CSVFormat format = CSVFormat::guess_csv());
|
|
|
|
struct GuessScore {
|
|
double score;
|
|
size_t header;
|
|
};
|
|
|
|
CSV_INLINE GuessScore calculate_score(csv::string_view head, CSVFormat format);
|
|
|
|
CSVGuessResult _guess_format(csv::string_view head, const std::vector<char>& delims = { ',', '|', '\t', ';', '^', '~' });
|
|
}
|
|
|
|
std::vector<std::string> get_col_names(
|
|
csv::string_view filename,
|
|
const CSVFormat format = CSVFormat::guess_csv());
|
|
|
|
/** Guess the delimiter used by a delimiter-separated values file */
|
|
CSVGuessResult guess_format(csv::string_view filename,
|
|
const std::vector<char>& delims = { ',', '|', '\t', ';', '^', '~' });
|
|
|
|
/** @class CSVReader
|
|
* @brief Main class for parsing CSVs from files and in-memory sources
|
|
*
|
|
* All rows are compared to the column names for length consistency
|
|
* - By default, rows that are too short or too long are dropped
|
|
* - Custom behavior can be defined by overriding bad_row_handler in a subclass
|
|
*/
|
|
class CSVReader {
|
|
public:
|
|
/**
|
|
* An input iterator capable of handling large files.
|
|
* @note Created by CSVReader::begin() and CSVReader::end().
|
|
*
|
|
* @par Iterating over a file
|
|
* @snippet tests/test_csv_iterator.cpp CSVReader Iterator 1
|
|
*
|
|
* @par Using with `<algorithm>` library
|
|
* @snippet tests/test_csv_iterator.cpp CSVReader Iterator 2
|
|
*/
|
|
class iterator {
|
|
public:
|
|
#ifndef DOXYGEN_SHOULD_SKIP_THIS
|
|
using value_type = CSVRow;
|
|
using difference_type = std::ptrdiff_t;
|
|
using pointer = CSVRow * ;
|
|
using reference = CSVRow & ;
|
|
using iterator_category = std::input_iterator_tag;
|
|
#endif
|
|
|
|
iterator() = default;
|
|
iterator(CSVReader* reader) : daddy(reader) {};
|
|
iterator(CSVReader*, CSVRow&&);
|
|
|
|
/** Access the CSVRow held by the iterator */
|
|
CONSTEXPR_14 reference operator*() { return this->row; }
|
|
|
|
/** Return a pointer to the CSVRow the iterator has stopped at */
|
|
CONSTEXPR_14 pointer operator->() { return &(this->row); }
|
|
|
|
iterator& operator++(); /**< Pre-increment iterator */
|
|
iterator operator++(int); /**< Post-increment ierator */
|
|
iterator& operator--();
|
|
|
|
/** Returns true if iterators were constructed from the same CSVReader
|
|
* and point to the same row
|
|
*/
|
|
CONSTEXPR bool operator==(const iterator& other) const noexcept {
|
|
return (this->daddy == other.daddy) && (this->i == other.i);
|
|
}
|
|
|
|
CONSTEXPR bool operator!=(const iterator& other) const noexcept { return !operator==(other); }
|
|
private:
|
|
CSVReader * daddy = nullptr; // Pointer to parent
|
|
CSVRow row; // Current row
|
|
size_t i = 0; // Index of current row
|
|
};
|
|
|
|
/** @name Constructors
|
|
* Constructors for iterating over large files and parsing in-memory sources.
|
|
*/
|
|
///@{
|
|
CSVReader(csv::string_view filename, CSVFormat format = CSVFormat::guess_csv());
|
|
|
|
/** Allows parsing stream sources such as `std::stringstream` or `std::ifstream`
|
|
*
|
|
* @tparam TStream An input stream deriving from `std::istream`
|
|
* @note Currently this constructor requires special CSV dialects to be manually
|
|
* specified.
|
|
*/
|
|
template<typename TStream,
|
|
csv::enable_if_t<std::is_base_of<std::istream, TStream>::value, int> = 0>
|
|
CSVReader(TStream& source, CSVFormat format = CSVFormat()) : _format(format) {
|
|
using Parser = internals::StreamParser<TStream>;
|
|
|
|
if (!format.col_names.empty())
|
|
this->set_col_names(format.col_names);
|
|
|
|
this->parser = std::unique_ptr<Parser>(
|
|
new Parser(source, format, col_names)); // For C++11
|
|
this->initial_read();
|
|
}
|
|
///@}
|
|
|
|
CSVReader(const CSVReader&) = delete; // No copy constructor
|
|
CSVReader(CSVReader&&) = default; // Move constructor
|
|
CSVReader& operator=(const CSVReader&) = delete; // No copy assignment
|
|
CSVReader& operator=(CSVReader&& other) = default;
|
|
~CSVReader() {
|
|
if (this->read_csv_worker.joinable()) {
|
|
this->read_csv_worker.join();
|
|
}
|
|
}
|
|
|
|
/** @name Retrieving CSV Rows */
|
|
///@{
|
|
bool read_row(CSVRow &row);
|
|
iterator begin();
|
|
HEDLEY_CONST iterator end() const noexcept;
|
|
|
|
/** Returns true if we have reached end of file */
|
|
bool eof() const noexcept { return this->parser->eof(); };
|
|
///@}
|
|
|
|
/** @name CSV Metadata */
|
|
///@{
|
|
CSVFormat get_format() const;
|
|
std::vector<std::string> get_col_names() const;
|
|
int index_of(csv::string_view col_name) const;
|
|
///@}
|
|
|
|
/** @name CSV Metadata: Attributes */
|
|
///@{
|
|
/** Whether or not the file or stream contains valid CSV rows,
|
|
* not including the header.
|
|
*
|
|
* @note Gives an accurate answer regardless of when it is called.
|
|
*
|
|
*/
|
|
CONSTEXPR bool empty() const noexcept { return this->n_rows() == 0; }
|
|
|
|
/** Retrieves the number of rows that have been read so far */
|
|
CONSTEXPR size_t n_rows() const noexcept { return this->_n_rows; }
|
|
|
|
/** Whether or not CSV was prefixed with a UTF-8 bom */
|
|
bool utf8_bom() const noexcept { return this->parser->utf8_bom(); }
|
|
///@}
|
|
|
|
protected:
|
|
/**
|
|
* \defgroup csv_internal CSV Parser Internals
|
|
* @brief Internals of CSVReader. Only maintainers and those looking to
|
|
* extend the parser should read this.
|
|
* @{
|
|
*/
|
|
|
|
/** Sets this reader's column names and associated data */
|
|
void set_col_names(const std::vector<std::string>&);
|
|
|
|
/** @name CSV Settings **/
|
|
///@{
|
|
CSVFormat _format;
|
|
///@}
|
|
|
|
/** @name Parser State */
|
|
///@{
|
|
/** Pointer to a object containing column information */
|
|
internals::ColNamesPtr col_names = std::make_shared<internals::ColNames>();
|
|
|
|
/** Helper class which actually does the parsing */
|
|
std::unique_ptr<internals::IBasicCSVParser> parser = nullptr;
|
|
|
|
/** Queue of parsed CSV rows */
|
|
std::unique_ptr<RowCollection> records{new RowCollection(100)};
|
|
|
|
size_t n_cols = 0; /**< The number of columns in this CSV */
|
|
size_t _n_rows = 0; /**< How many rows (minus header) have been read so far */
|
|
|
|
/** @name Multi-Threaded File Reading Functions */
|
|
///@{
|
|
bool read_csv(size_t bytes = internals::ITERATION_CHUNK_SIZE);
|
|
///@}
|
|
|
|
/**@}*/
|
|
|
|
private:
|
|
/** Whether or not rows before header were trimmed */
|
|
bool header_trimmed = false;
|
|
|
|
/** @name Multi-Threaded File Reading: Flags and State */
|
|
///@{
|
|
std::thread read_csv_worker; /**< Worker thread for read_csv() */
|
|
///@}
|
|
|
|
/** Read initial chunk to get metadata */
|
|
void initial_read() {
|
|
this->read_csv_worker = std::thread(&CSVReader::read_csv, this, internals::ITERATION_CHUNK_SIZE);
|
|
this->read_csv_worker.join();
|
|
}
|
|
|
|
void trim_header();
|
|
};
|
|
}
|
|
|
|
/** @file
|
|
* Calculates statistics from CSV files
|
|
*/
|
|
|
|
#include <unordered_map>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
namespace csv {
|
|
/** Class for calculating statistics from CSV files and in-memory sources
|
|
*
|
|
* **Example**
|
|
* \include programs/csv_stats.cpp
|
|
*
|
|
*/
|
|
class CSVStat {
|
|
public:
|
|
using FreqCount = std::unordered_map<std::string, size_t>;
|
|
using TypeCount = std::unordered_map<DataType, size_t>;
|
|
|
|
std::vector<long double> get_mean() const;
|
|
std::vector<long double> get_variance() const;
|
|
std::vector<long double> get_mins() const;
|
|
std::vector<long double> get_maxes() const;
|
|
std::vector<FreqCount> get_counts() const;
|
|
std::vector<TypeCount> get_dtypes() const;
|
|
|
|
std::vector<std::string> get_col_names() const {
|
|
return this->reader.get_col_names();
|
|
}
|
|
|
|
CSVStat(csv::string_view filename, CSVFormat format = CSVFormat::guess_csv());
|
|
CSVStat(std::stringstream& source, CSVFormat format = CSVFormat());
|
|
private:
|
|
// An array of rolling averages
|
|
// Each index corresponds to the rolling mean for the column at said index
|
|
std::vector<long double> rolling_means;
|
|
std::vector<long double> rolling_vars;
|
|
std::vector<long double> mins;
|
|
std::vector<long double> maxes;
|
|
std::vector<FreqCount> counts;
|
|
std::vector<TypeCount> dtypes;
|
|
std::vector<long double> n;
|
|
|
|
// Statistic calculators
|
|
void variance(const long double&, const size_t&);
|
|
void count(CSVField&, const size_t&);
|
|
void min_max(const long double&, const size_t&);
|
|
void dtype(CSVField&, const size_t&);
|
|
|
|
void calc();
|
|
void calc_chunk();
|
|
void calc_worker(const size_t&);
|
|
|
|
CSVReader reader;
|
|
std::deque<CSVRow> records = {};
|
|
};
|
|
}
|
|
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
|
|
namespace csv {
|
|
/** Returned by get_file_info() */
|
|
struct CSVFileInfo {
|
|
std::string filename; /**< Filename */
|
|
std::vector<std::string> col_names; /**< CSV column names */
|
|
char delim; /**< Delimiting character */
|
|
size_t n_rows; /**< Number of rows in a file */
|
|
size_t n_cols; /**< Number of columns in a CSV */
|
|
};
|
|
|
|
/** @name Shorthand Parsing Functions
|
|
* @brief Convienience functions for parsing small strings
|
|
*/
|
|
///@{
|
|
CSVReader operator ""_csv(const char*, size_t);
|
|
CSVReader operator ""_csv_no_header(const char*, size_t);
|
|
CSVReader parse(csv::string_view in, CSVFormat format = CSVFormat());
|
|
CSVReader parse_no_header(csv::string_view in);
|
|
///@}
|
|
|
|
/** @name Utility Functions */
|
|
///@{
|
|
std::unordered_map<std::string, DataType> csv_data_types(const std::string&);
|
|
CSVFileInfo get_file_info(const std::string& filename);
|
|
int get_col_pos(csv::string_view filename, csv::string_view col_name,
|
|
const CSVFormat& format = CSVFormat::guess_csv());
|
|
///@}
|
|
}
|
|
/** @file
|
|
* A standalone header file for writing delimiter-separated files
|
|
*/
|
|
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
|
|
namespace csv {
|
|
namespace internals {
|
|
static int DECIMAL_PLACES = 5;
|
|
|
|
/** to_string() for unsigned integers */
|
|
template<typename T,
|
|
csv::enable_if_t<std::is_unsigned<T>::value, int> = 0>
|
|
inline std::string to_string(T value) {
|
|
std::string digits_reverse = "";
|
|
|
|
if (value == 0) return "0";
|
|
|
|
while (value > 0) {
|
|
digits_reverse += (char)('0' + (value % 10));
|
|
value /= 10;
|
|
}
|
|
|
|
return std::string(digits_reverse.rbegin(), digits_reverse.rend());
|
|
}
|
|
|
|
/** to_string() for signed integers */
|
|
template<
|
|
typename T,
|
|
csv::enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, int> = 0
|
|
>
|
|
inline std::string to_string(T value) {
|
|
if (value >= 0)
|
|
return to_string((size_t)value);
|
|
|
|
return "-" + to_string((size_t)(value * -1));
|
|
}
|
|
|
|
/** to_string() for floating point numbers */
|
|
template<
|
|
typename T,
|
|
csv::enable_if_t<std::is_floating_point<T>::value, int> = 0
|
|
>
|
|
inline std::string to_string(T value) {
|
|
std::string result;
|
|
|
|
T integral_part;
|
|
T fractional_part = std::abs(std::modf(value, &integral_part));
|
|
integral_part = std::abs(integral_part);
|
|
|
|
// Integral part
|
|
if (value < 0) result = "-";
|
|
|
|
if (integral_part == 0) {
|
|
result = "0";
|
|
}
|
|
else {
|
|
for (int n_digits = (int)(std::log(integral_part) / std::log(10));
|
|
n_digits + 1 > 0; n_digits --) {
|
|
int digit = (int)(std::fmod(integral_part, pow10(n_digits + 1)) / pow10(n_digits));
|
|
result += (char)('0' + digit);
|
|
}
|
|
}
|
|
|
|
// Decimal part
|
|
result += ".";
|
|
|
|
if (fractional_part > 0) {
|
|
fractional_part *= (T)(pow10(DECIMAL_PLACES));
|
|
for (int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) {
|
|
int digit = (int)(std::fmod(fractional_part, pow10(n_digits)) / pow10(n_digits - 1));
|
|
result += (char)('0' + digit);
|
|
}
|
|
}
|
|
else {
|
|
result += "0";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/** Sets how many places after the decimal will be written for floating point numbers
|
|
*
|
|
* @param precision Number of decimal places
|
|
*/
|
|
inline static void set_decimal_places(int precision) {
|
|
internals::DECIMAL_PLACES = precision;
|
|
}
|
|
|
|
/** @name CSV Writing */
|
|
///@{
|
|
/**
|
|
* Class for writing delimiter separated values files
|
|
*
|
|
* To write formatted strings, one should
|
|
* -# Initialize a DelimWriter with respect to some output stream
|
|
* -# Call write_row() on std::vector<std::string>s of unformatted text
|
|
*
|
|
* @tparam OutputStream The output stream, e.g. `std::ofstream`, `std::stringstream`
|
|
* @tparam Delim The delimiter character
|
|
* @tparam Quote The quote character
|
|
* @tparam Flush True: flush after every writing function,
|
|
* false: you need to flush explicitly if needed.
|
|
* In both cases the destructor will flush.
|
|
*
|
|
* @par Hint
|
|
* Use the aliases csv::CSVWriter<OutputStream> to write CSV
|
|
* formatted strings and csv::TSVWriter<OutputStream>
|
|
* to write tab separated strings
|
|
*
|
|
* @par Example w/ std::vector, std::deque, std::list
|
|
* @snippet test_write_csv.cpp CSV Writer Example
|
|
*
|
|
* @par Example w/ std::tuple
|
|
* @snippet test_write_csv.cpp CSV Writer Tuple Example
|
|
*/
|
|
template<class OutputStream, char Delim, char Quote, bool Flush>
|
|
class DelimWriter {
|
|
public:
|
|
/** Construct a DelimWriter over the specified output stream
|
|
*
|
|
* @param _out Stream to write to
|
|
* @param _quote_minimal Limit field quoting to only when necessary
|
|
*/
|
|
|
|
DelimWriter(OutputStream& _out, bool _quote_minimal = true)
|
|
: out(_out), quote_minimal(_quote_minimal) {};
|
|
|
|
/** Construct a DelimWriter over the file
|
|
*
|
|
* @param[out] filename File to write to
|
|
*/
|
|
DelimWriter(const std::string& filename) : DelimWriter(std::ifstream(filename)) {};
|
|
|
|
/** Destructor will flush remaining data
|
|
*
|
|
*/
|
|
~DelimWriter() {
|
|
out.flush();
|
|
}
|
|
|
|
/** Format a sequence of strings and write to CSV according to RFC 4180
|
|
*
|
|
* @warning This does not check to make sure row lengths are consistent
|
|
*
|
|
* @param[in] record Sequence of strings to be formatted
|
|
*
|
|
* @return The current DelimWriter instance (allowing for operator chaining)
|
|
*/
|
|
template<typename T, size_t Size>
|
|
DelimWriter& operator<<(const std::array<T, Size>& record) {
|
|
for (size_t i = 0; i < Size; i++) {
|
|
out << csv_escape(record[i]);
|
|
if (i + 1 != Size) out << Delim;
|
|
}
|
|
|
|
end_out();
|
|
return *this;
|
|
}
|
|
|
|
/** @copydoc operator<< */
|
|
template<typename... T>
|
|
DelimWriter& operator<<(const std::tuple<T...>& record) {
|
|
this->write_tuple<0, T...>(record);
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* @tparam T A container such as std::vector, std::deque, or std::list
|
|
*
|
|
* @copydoc operator<<
|
|
*/
|
|
template<
|
|
typename T, typename Alloc, template <typename, typename> class Container,
|
|
|
|
// Avoid conflicting with tuples with two elements
|
|
csv::enable_if_t<std::is_class<Alloc>::value, int> = 0
|
|
>
|
|
DelimWriter& operator<<(const Container<T, Alloc>& record) {
|
|
const size_t ilen = record.size();
|
|
size_t i = 0;
|
|
for (const auto& field : record) {
|
|
out << csv_escape(field);
|
|
if (i + 1 != ilen) out << Delim;
|
|
i++;
|
|
}
|
|
|
|
end_out();
|
|
return *this;
|
|
}
|
|
|
|
/** Flushes the written data
|
|
*
|
|
*/
|
|
void flush() {
|
|
out.flush();
|
|
}
|
|
|
|
private:
|
|
template<
|
|
typename T,
|
|
csv::enable_if_t<
|
|
!std::is_convertible<T, std::string>::value
|
|
&& !std::is_convertible<T, csv::string_view>::value
|
|
, int> = 0
|
|
>
|
|
std::string csv_escape(T in) {
|
|
return internals::to_string(in);
|
|
}
|
|
|
|
template<
|
|
typename T,
|
|
csv::enable_if_t<
|
|
std::is_convertible<T, std::string>::value
|
|
|| std::is_convertible<T, csv::string_view>::value
|
|
, int> = 0
|
|
>
|
|
std::string csv_escape(T in) {
|
|
IF_CONSTEXPR(std::is_convertible<T, csv::string_view>::value) {
|
|
return _csv_escape(in);
|
|
}
|
|
|
|
return _csv_escape(std::string(in));
|
|
}
|
|
|
|
std::string _csv_escape(csv::string_view in) {
|
|
/** Format a string to be RFC 4180-compliant
|
|
* @param[in] in String to be CSV-formatted
|
|
* @param[out] quote_minimal Only quote fields if necessary.
|
|
* If False, everything is quoted.
|
|
*/
|
|
|
|
// Do we need a quote escape
|
|
bool quote_escape = false;
|
|
|
|
for (auto ch : in) {
|
|
if (ch == Quote || ch == Delim || ch == '\r' || ch == '\n') {
|
|
quote_escape = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!quote_escape) {
|
|
if (quote_minimal) return std::string(in);
|
|
else {
|
|
std::string ret(1, Quote);
|
|
ret += in.data();
|
|
ret += Quote;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// Start initial quote escape sequence
|
|
std::string ret(1, Quote);
|
|
for (auto ch: in) {
|
|
if (ch == Quote) ret += std::string(2, Quote);
|
|
else ret += ch;
|
|
}
|
|
|
|
// Finish off quote escape
|
|
ret += Quote;
|
|
return ret;
|
|
}
|
|
|
|
/** Recurisve template for writing std::tuples */
|
|
template<size_t Index = 0, typename... T>
|
|
typename std::enable_if<Index < sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
|
|
out << csv_escape(std::get<Index>(record));
|
|
|
|
IF_CONSTEXPR (Index + 1 < sizeof...(T)) out << Delim;
|
|
|
|
this->write_tuple<Index + 1>(record);
|
|
}
|
|
|
|
/** Base case for writing std::tuples */
|
|
template<size_t Index = 0, typename... T>
|
|
typename std::enable_if<Index == sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
|
|
(void)record;
|
|
end_out();
|
|
}
|
|
|
|
/** Ends a line in 'out' and flushes, if Flush is true.*/
|
|
void end_out() {
|
|
out << '\n';
|
|
IF_CONSTEXPR(Flush) out.flush();
|
|
}
|
|
|
|
OutputStream & out;
|
|
bool quote_minimal;
|
|
};
|
|
|
|
/** An alias for csv::DelimWriter for writing standard CSV files
|
|
*
|
|
* @sa csv::DelimWriter::operator<<()
|
|
*
|
|
* @note Use `csv::make_csv_writer()` to in instatiate this class over
|
|
* an actual output stream.
|
|
*/
|
|
template<class OutputStream, bool Flush = true>
|
|
using CSVWriter = DelimWriter<OutputStream, ',', '"', Flush>;
|
|
|
|
/** Class for writing tab-separated values files
|
|
*
|
|
* @sa csv::DelimWriter::write_row()
|
|
* @sa csv::DelimWriter::operator<<()
|
|
*
|
|
* @note Use `csv::make_tsv_writer()` to in instatiate this class over
|
|
* an actual output stream.
|
|
*/
|
|
template<class OutputStream, bool Flush = true>
|
|
using TSVWriter = DelimWriter<OutputStream, '\t', '"', Flush>;
|
|
|
|
/** Return a csv::CSVWriter over the output stream */
|
|
template<class OutputStream>
|
|
inline CSVWriter<OutputStream> make_csv_writer(OutputStream& out, bool quote_minimal=true) {
|
|
return CSVWriter<OutputStream>(out, quote_minimal);
|
|
}
|
|
|
|
/** Return a buffered csv::CSVWriter over the output stream (does not auto flush) */
|
|
template<class OutputStream>
|
|
inline CSVWriter<OutputStream, false> make_csv_writer_buffered(OutputStream& out, bool quote_minimal=true) {
|
|
return CSVWriter<OutputStream, false>(out, quote_minimal);
|
|
}
|
|
|
|
/** Return a csv::TSVWriter over the output stream */
|
|
template<class OutputStream>
|
|
inline TSVWriter<OutputStream> make_tsv_writer(OutputStream& out, bool quote_minimal=true) {
|
|
return TSVWriter<OutputStream>(out, quote_minimal);
|
|
}
|
|
|
|
/** Return a buffered csv::TSVWriter over the output stream (does not auto flush) */
|
|
template<class OutputStream>
|
|
inline TSVWriter<OutputStream, false> make_tsv_writer_buffered(OutputStream& out, bool quote_minimal=true) {
|
|
return TSVWriter<OutputStream, false>(out, quote_minimal);
|
|
}
|
|
///@}
|
|
}
|
|
|
|
|
|
namespace csv {
|
|
namespace internals {
|
|
CSV_INLINE size_t get_file_size(csv::string_view filename) {
|
|
std::ifstream infile(std::string(filename), std::ios::binary);
|
|
const auto start = infile.tellg();
|
|
infile.seekg(0, std::ios::end);
|
|
const auto end = infile.tellg();
|
|
|
|
return end - start;
|
|
}
|
|
|
|
CSV_INLINE std::string get_csv_head(csv::string_view filename) {
|
|
return get_csv_head(filename, get_file_size(filename));
|
|
}
|
|
|
|
CSV_INLINE std::string get_csv_head(csv::string_view filename, size_t file_size) {
|
|
const size_t bytes = 500000;
|
|
|
|
std::error_code error;
|
|
size_t length = std::min((size_t)file_size, bytes);
|
|
auto mmap = mio::make_mmap_source(std::string(filename), 0, length, error);
|
|
|
|
if (error) {
|
|
throw std::runtime_error("Cannot open file " + std::string(filename));
|
|
}
|
|
|
|
return std::string(mmap.begin(), mmap.end());
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma region IBasicCVParser
|
|
#endif
|
|
CSV_INLINE IBasicCSVParser::IBasicCSVParser(
|
|
const CSVFormat& format,
|
|
const ColNamesPtr& col_names
|
|
) : _col_names(col_names) {
|
|
if (format.no_quote) {
|
|
_parse_flags = internals::make_parse_flags(format.get_delim());
|
|
}
|
|
else {
|
|
_parse_flags = internals::make_parse_flags(format.get_delim(), format.quote_char);
|
|
}
|
|
|
|
_ws_flags = internals::make_ws_flags(
|
|
format.trim_chars.data(), format.trim_chars.size()
|
|
);
|
|
}
|
|
|
|
CSV_INLINE void IBasicCSVParser::end_feed() {
|
|
using internals::ParseFlags;
|
|
|
|
bool empty_last_field = this->data_ptr
|
|
&& this->data_ptr->_data
|
|
&& !this->data_ptr->data.empty()
|
|
&& parse_flag(this->data_ptr->data.back()) == ParseFlags::DELIMITER;
|
|
|
|
// Push field
|
|
if (this->field_length > 0 || empty_last_field) {
|
|
this->push_field();
|
|
}
|
|
|
|
// Push row
|
|
if (this->current_row.size() > 0)
|
|
this->push_row();
|
|
}
|
|
|
|
CSV_INLINE void IBasicCSVParser::parse_field() noexcept {
|
|
using internals::ParseFlags;
|
|
auto& in = this->data_ptr->data;
|
|
|
|
// Trim off leading whitespace
|
|
while (data_pos < in.size() && ws_flag(in[data_pos]))
|
|
data_pos++;
|
|
|
|
if (field_start == UNINITIALIZED_FIELD)
|
|
field_start = (int)(data_pos - current_row_start());
|
|
|
|
// Optimization: Since NOT_SPECIAL characters tend to occur in contiguous
|
|
// sequences, use the loop below to avoid having to go through the outer
|
|
// switch statement as much as possible
|
|
while (data_pos < in.size() && compound_parse_flag(in[data_pos]) == ParseFlags::NOT_SPECIAL)
|
|
data_pos++;
|
|
|
|
field_length = data_pos - (field_start + current_row_start());
|
|
|
|
// Trim off trailing whitespace, this->field_length constraint matters
|
|
// when field is entirely whitespace
|
|
for (size_t j = data_pos - 1; ws_flag(in[j]) && this->field_length > 0; j--)
|
|
this->field_length--;
|
|
}
|
|
|
|
CSV_INLINE void IBasicCSVParser::push_field()
|
|
{
|
|
// Update
|
|
if (field_has_double_quote) {
|
|
fields->emplace_back(
|
|
field_start == UNINITIALIZED_FIELD ? 0 : (unsigned int)field_start,
|
|
field_length,
|
|
true
|
|
);
|
|
field_has_double_quote = false;
|
|
|
|
}
|
|
else {
|
|
fields->emplace_back(
|
|
field_start == UNINITIALIZED_FIELD ? 0 : (unsigned int)field_start,
|
|
field_length
|
|
);
|
|
}
|
|
|
|
current_row.row_length++;
|
|
|
|
// Reset field state
|
|
field_start = UNINITIALIZED_FIELD;
|
|
field_length = 0;
|
|
}
|
|
|
|
/** @return The number of characters parsed that belong to complete rows */
|
|
CSV_INLINE size_t IBasicCSVParser::parse()
|
|
{
|
|
using internals::ParseFlags;
|
|
|
|
this->quote_escape = false;
|
|
this->data_pos = 0;
|
|
this->current_row_start() = 0;
|
|
this->trim_utf8_bom();
|
|
|
|
auto& in = this->data_ptr->data;
|
|
while (this->data_pos < in.size()) {
|
|
switch (compound_parse_flag(in[this->data_pos])) {
|
|
case ParseFlags::DELIMITER:
|
|
this->push_field();
|
|
this->data_pos++;
|
|
break;
|
|
|
|
case ParseFlags::NEWLINE:
|
|
this->data_pos++;
|
|
|
|
// Catches CRLF (or LFLF)
|
|
if (this->data_pos < in.size() && parse_flag(in[this->data_pos]) == ParseFlags::NEWLINE)
|
|
this->data_pos++;
|
|
|
|
// End of record -> Write record
|
|
this->push_field();
|
|
this->push_row();
|
|
|
|
// Reset
|
|
this->current_row = CSVRow(data_ptr, this->data_pos, fields->size());
|
|
break;
|
|
|
|
case ParseFlags::NOT_SPECIAL:
|
|
this->parse_field();
|
|
break;
|
|
|
|
case ParseFlags::QUOTE_ESCAPE_QUOTE:
|
|
if (data_pos + 1 == in.size()) return this->current_row_start();
|
|
else if (data_pos + 1 < in.size()) {
|
|
auto next_ch = parse_flag(in[data_pos + 1]);
|
|
if (next_ch >= ParseFlags::DELIMITER) {
|
|
quote_escape = false;
|
|
data_pos++;
|
|
break;
|
|
}
|
|
else if (next_ch == ParseFlags::QUOTE) {
|
|
// Case: Escaped quote
|
|
data_pos += 2;
|
|
this->field_length += 2;
|
|
this->field_has_double_quote = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Case: Unescaped single quote => not strictly valid but we'll keep it
|
|
this->field_length++;
|
|
data_pos++;
|
|
|
|
break;
|
|
|
|
default: // Quote (currently not quote escaped)
|
|
if (this->field_length == 0) {
|
|
quote_escape = true;
|
|
data_pos++;
|
|
if (field_start == UNINITIALIZED_FIELD && data_pos < in.size() && !ws_flag(in[data_pos]))
|
|
field_start = (int)(data_pos - current_row_start());
|
|
break;
|
|
}
|
|
|
|
// Case: Unescaped quote
|
|
this->field_length++;
|
|
data_pos++;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return this->current_row_start();
|
|
}
|
|
|
|
CSV_INLINE void IBasicCSVParser::push_row() {
|
|
current_row.row_length = fields->size() - current_row.fields_start;
|
|
this->_records->push_back(std::move(current_row));
|
|
}
|
|
|
|
CSV_INLINE void IBasicCSVParser::reset_data_ptr() {
|
|
this->data_ptr = std::make_shared<RawCSVData>();
|
|
this->data_ptr->parse_flags = this->_parse_flags;
|
|
this->data_ptr->col_names = this->_col_names;
|
|
this->fields = &(this->data_ptr->fields);
|
|
}
|
|
|
|
CSV_INLINE void IBasicCSVParser::trim_utf8_bom() {
|
|
auto& data = this->data_ptr->data;
|
|
|
|
if (!this->unicode_bom_scan && data.size() >= 3) {
|
|
if (data[0] == '\xEF' && data[1] == '\xBB' && data[2] == '\xBF') {
|
|
this->data_pos += 3; // Remove BOM from input string
|
|
this->_utf8_bom = true;
|
|
}
|
|
|
|
this->unicode_bom_scan = true;
|
|
}
|
|
}
|
|
#ifdef _MSC_VER
|
|
#pragma endregion
|
|
#endif
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma region Specializations
|
|
#endif
|
|
CSV_INLINE void MmapParser::next(size_t bytes = ITERATION_CHUNK_SIZE) {
|
|
// Reset parser state
|
|
this->field_start = UNINITIALIZED_FIELD;
|
|
this->field_length = 0;
|
|
this->reset_data_ptr();
|
|
|
|
// Create memory map
|
|
size_t length = std::min(this->source_size - this->mmap_pos, bytes);
|
|
std::error_code error;
|
|
this->data_ptr->_data = std::make_shared<mio::basic_mmap_source<char>>(mio::make_mmap_source(this->_filename, this->mmap_pos, length, error));
|
|
this->mmap_pos += length;
|
|
if (error) throw error;
|
|
|
|
auto mmap_ptr = (mio::basic_mmap_source<char>*)(this->data_ptr->_data.get());
|
|
|
|
// Create string view
|
|
this->data_ptr->data = csv::string_view(mmap_ptr->data(), mmap_ptr->length());
|
|
|
|
// Parse
|
|
this->current_row = CSVRow(this->data_ptr);
|
|
size_t remainder = this->parse();
|
|
|
|
if (this->mmap_pos == this->source_size || no_chunk()) {
|
|
this->_eof = true;
|
|
this->end_feed();
|
|
}
|
|
|
|
this->mmap_pos -= (length - remainder);
|
|
}
|
|
#ifdef _MSC_VER
|
|
#pragma endregion
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
namespace csv {
|
|
namespace internals {
|
|
CSV_INLINE std::vector<std::string> ColNames::get_col_names() const {
|
|
return this->col_names;
|
|
}
|
|
|
|
CSV_INLINE void ColNames::set_col_names(const std::vector<std::string>& cnames) {
|
|
this->col_names = cnames;
|
|
|
|
for (size_t i = 0; i < cnames.size(); i++) {
|
|
this->col_pos[cnames[i]] = i;
|
|
}
|
|
}
|
|
|
|
CSV_INLINE int ColNames::index_of(csv::string_view col_name) const {
|
|
auto pos = this->col_pos.find(col_name.data());
|
|
if (pos != this->col_pos.end())
|
|
return (int)pos->second;
|
|
|
|
return CSV_NOT_FOUND;
|
|
}
|
|
|
|
CSV_INLINE size_t ColNames::size() const noexcept {
|
|
return this->col_names.size();
|
|
}
|
|
|
|
}
|
|
}
|
|
/** @file
|
|
* Defines an object used to store CSV format settings
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <set>
|
|
|
|
|
|
namespace csv {
|
|
CSV_INLINE CSVFormat& CSVFormat::delimiter(char delim) {
|
|
this->possible_delimiters = { delim };
|
|
this->assert_no_char_overlap();
|
|
return *this;
|
|
}
|
|
|
|
CSV_INLINE CSVFormat& CSVFormat::delimiter(const std::vector<char> & delim) {
|
|
this->possible_delimiters = delim;
|
|
this->assert_no_char_overlap();
|
|
return *this;
|
|
}
|
|
|
|
CSV_INLINE CSVFormat& CSVFormat::quote(char quote) {
|
|
this->no_quote = false;
|
|
this->quote_char = quote;
|
|
this->assert_no_char_overlap();
|
|
return *this;
|
|
}
|
|
|
|
CSV_INLINE CSVFormat& CSVFormat::trim(const std::vector<char> & chars) {
|
|
this->trim_chars = chars;
|
|
this->assert_no_char_overlap();
|
|
return *this;
|
|
}
|
|
|
|
CSV_INLINE CSVFormat& CSVFormat::column_names(const std::vector<std::string>& names) {
|
|
this->col_names = names;
|
|
this->header = -1;
|
|
return *this;
|
|
}
|
|
|
|
CSV_INLINE CSVFormat& CSVFormat::header_row(int row) {
|
|
if (row < 0) this->variable_column_policy = VariableColumnPolicy::KEEP;
|
|
|
|
this->header = row;
|
|
this->col_names = {};
|
|
return *this;
|
|
}
|
|
|
|
CSV_INLINE void CSVFormat::assert_no_char_overlap()
|
|
{
|
|
auto delims = std::set<char>(
|
|
this->possible_delimiters.begin(), this->possible_delimiters.end()),
|
|
trims = std::set<char>(
|
|
this->trim_chars.begin(), this->trim_chars.end());
|
|
|
|
// Stores intersection of possible delimiters and trim characters
|
|
std::vector<char> intersection = {};
|
|
|
|
// Find which characters overlap, if any
|
|
std::set_intersection(
|
|
delims.begin(), delims.end(),
|
|
trims.begin(), trims.end(),
|
|
std::back_inserter(intersection));
|
|
|
|
// Make sure quote character is not contained in possible delimiters
|
|
// or whitespace characters
|
|
if (delims.find(this->quote_char) != delims.end() ||
|
|
trims.find(this->quote_char) != trims.end()) {
|
|
intersection.push_back(this->quote_char);
|
|
}
|
|
|
|
if (!intersection.empty()) {
|
|
std::string err_msg = "There should be no overlap between the quote character, "
|
|
"the set of possible delimiters "
|
|
"and the set of whitespace characters. Offending characters: ";
|
|
|
|
// Create a pretty error message with the list of overlapping
|
|
// characters
|
|
for (size_t i = 0; i < intersection.size(); i++) {
|
|
err_msg += "'";
|
|
err_msg += intersection[i];
|
|
err_msg += "'";
|
|
|
|
if (i + 1 < intersection.size())
|
|
err_msg += ", ";
|
|
}
|
|
|
|
throw std::runtime_error(err_msg + '.');
|
|
}
|
|
}
|
|
}
|
|
/** @file
|
|
* @brief Defines functionality needed for basic CSV parsing
|
|
*/
|
|
|
|
|
|
namespace csv {
|
|
namespace internals {
|
|
CSV_INLINE std::string format_row(const std::vector<std::string>& row, csv::string_view delim) {
|
|
/** Print a CSV row */
|
|
std::stringstream ret;
|
|
for (size_t i = 0; i < row.size(); i++) {
|
|
ret << row[i];
|
|
if (i + 1 < row.size()) ret << delim;
|
|
else ret << '\n';
|
|
}
|
|
ret.flush();
|
|
|
|
return ret.str();
|
|
}
|
|
|
|
/** Return a CSV's column names
|
|
*
|
|
* @param[in] filename Path to CSV file
|
|
* @param[in] format Format of the CSV file
|
|
*
|
|
*/
|
|
CSV_INLINE std::vector<std::string> _get_col_names(csv::string_view head, CSVFormat format) {
|
|
// Parse the CSV
|
|
auto trim_chars = format.get_trim_chars();
|
|
std::stringstream source(head.data());
|
|
RowCollection rows;
|
|
|
|
StreamParser<std::stringstream> parser(source, format);
|
|
parser.set_output(rows);
|
|
parser.next();
|
|
|
|
return CSVRow(std::move(rows[format.get_header()]));
|
|
}
|
|
|
|
CSV_INLINE GuessScore calculate_score(csv::string_view head, CSVFormat format) {
|
|
// Frequency counter of row length
|
|
std::unordered_map<size_t, size_t> row_tally = { { 0, 0 } };
|
|
|
|
// Map row lengths to row num where they first occurred
|
|
std::unordered_map<size_t, size_t> row_when = { { 0, 0 } };
|
|
|
|
// Parse the CSV
|
|
std::stringstream source(head.data());
|
|
RowCollection rows;
|
|
|
|
StreamParser<std::stringstream> parser(source, format);
|
|
parser.set_output(rows);
|
|
parser.next();
|
|
|
|
for (size_t i = 0; i < rows.size(); i++) {
|
|
auto& row = rows[i];
|
|
|
|
// Ignore zero-length rows
|
|
if (row.size() > 0) {
|
|
if (row_tally.find(row.size()) != row_tally.end()) {
|
|
row_tally[row.size()]++;
|
|
}
|
|
else {
|
|
row_tally[row.size()] = 1;
|
|
row_when[row.size()] = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
double final_score = 0;
|
|
size_t header_row = 0;
|
|
|
|
// Final score is equal to the largest
|
|
// row size times rows of that size
|
|
for (auto& pair : row_tally) {
|
|
auto row_size = pair.first;
|
|
auto row_count = pair.second;
|
|
double score = (double)(row_size * row_count);
|
|
if (score > final_score) {
|
|
final_score = score;
|
|
header_row = row_when[row_size];
|
|
}
|
|
}
|
|
|
|
return {
|
|
final_score,
|
|
header_row
|
|
};
|
|
}
|
|
|
|
/** Guess the delimiter used by a delimiter-separated values file */
|
|
CSV_INLINE CSVGuessResult _guess_format(csv::string_view head, const std::vector<char>& delims) {
|
|
/** For each delimiter, find out which row length was most common.
|
|
* The delimiter with the longest mode row length wins.
|
|
* Then, the line number of the header row is the first row with
|
|
* the mode row length.
|
|
*/
|
|
|
|
CSVFormat format;
|
|
size_t max_score = 0,
|
|
header = 0;
|
|
char current_delim = delims[0];
|
|
|
|
for (char cand_delim : delims) {
|
|
auto result = calculate_score(head, format.delimiter(cand_delim));
|
|
|
|
if ((size_t)result.score > max_score) {
|
|
max_score = (size_t)result.score;
|
|
current_delim = cand_delim;
|
|
header = result.header;
|
|
}
|
|
}
|
|
|
|
return { current_delim, (int)header };
|
|
}
|
|
}
|
|
|
|
/** Return a CSV's column names
|
|
*
|
|
* @param[in] filename Path to CSV file
|
|
* @param[in] format Format of the CSV file
|
|
*
|
|
*/
|
|
CSV_INLINE std::vector<std::string> get_col_names(csv::string_view filename, CSVFormat format) {
|
|
auto head = internals::get_csv_head(filename);
|
|
|
|
/** Guess delimiter and header row */
|
|
if (format.guess_delim()) {
|
|
auto guess_result = guess_format(filename, format.get_possible_delims());
|
|
format.delimiter(guess_result.delim).header_row(guess_result.header_row);
|
|
}
|
|
|
|
return internals::_get_col_names(head, format);
|
|
}
|
|
|
|
/** Guess the delimiter used by a delimiter-separated values file */
|
|
CSV_INLINE CSVGuessResult guess_format(csv::string_view filename, const std::vector<char>& delims) {
|
|
auto head = internals::get_csv_head(filename);
|
|
return internals::_guess_format(head, delims);
|
|
}
|
|
|
|
/** Reads an arbitrarily large CSV file using memory-mapped IO.
|
|
*
|
|
* **Details:** Reads the first block of a CSV file synchronously to get information
|
|
* such as column names and delimiting character.
|
|
*
|
|
* @param[in] filename Path to CSV file
|
|
* @param[in] format Format of the CSV file
|
|
*
|
|
* \snippet tests/test_read_csv.cpp CSVField Example
|
|
*
|
|
*/
|
|
CSV_INLINE CSVReader::CSVReader(csv::string_view filename, CSVFormat format) : _format(format) {
|
|
auto head = internals::get_csv_head(filename);
|
|
using Parser = internals::MmapParser;
|
|
|
|
/** Guess delimiter and header row */
|
|
if (format.guess_delim()) {
|
|
auto guess_result = internals::_guess_format(head, format.possible_delimiters);
|
|
format.delimiter(guess_result.delim);
|
|
format.header = guess_result.header_row;
|
|
this->_format = format;
|
|
}
|
|
|
|
if (!format.col_names.empty())
|
|
this->set_col_names(format.col_names);
|
|
|
|
this->parser = std::unique_ptr<Parser>(new Parser(filename, format, this->col_names)); // For C++11
|
|
this->initial_read();
|
|
}
|
|
|
|
/** Return the format of the original raw CSV */
|
|
CSV_INLINE CSVFormat CSVReader::get_format() const {
|
|
CSVFormat new_format = this->_format;
|
|
|
|
// Since users are normally not allowed to set
|
|
// column names and header row simulatenously,
|
|
// we will set the backing variables directly here
|
|
new_format.col_names = this->col_names->get_col_names();
|
|
new_format.header = this->_format.header;
|
|
|
|
return new_format;
|
|
}
|
|
|
|
/** Return the CSV's column names as a vector of strings. */
|
|
CSV_INLINE std::vector<std::string> CSVReader::get_col_names() const {
|
|
if (this->col_names) {
|
|
return this->col_names->get_col_names();
|
|
}
|
|
|
|
return std::vector<std::string>();
|
|
}
|
|
|
|
/** Return the index of the column name if found or
|
|
* csv::CSV_NOT_FOUND otherwise.
|
|
*/
|
|
CSV_INLINE int CSVReader::index_of(csv::string_view col_name) const {
|
|
auto _col_names = this->get_col_names();
|
|
for (size_t i = 0; i < _col_names.size(); i++)
|
|
if (_col_names[i] == col_name) return (int)i;
|
|
|
|
return CSV_NOT_FOUND;
|
|
}
|
|
|
|
CSV_INLINE void CSVReader::trim_header() {
|
|
if (!this->header_trimmed) {
|
|
for (int i = 0; i <= this->_format.header && !this->records->empty(); i++) {
|
|
if (i == this->_format.header && this->col_names->empty()) {
|
|
this->set_col_names(this->records->pop_front());
|
|
}
|
|
else {
|
|
this->records->pop_front();
|
|
}
|
|
}
|
|
|
|
this->header_trimmed = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param[in] names Column names
|
|
*/
|
|
CSV_INLINE void CSVReader::set_col_names(const std::vector<std::string>& names)
|
|
{
|
|
this->col_names->set_col_names(names);
|
|
this->n_cols = names.size();
|
|
}
|
|
|
|
/**
|
|
* Read a chunk of CSV data.
|
|
*
|
|
* @note This method is meant to be run on its own thread. Only one `read_csv()` thread
|
|
* should be active at a time.
|
|
*
|
|
* @param[in] bytes Number of bytes to read.
|
|
*
|
|
* @see CSVReader::read_csv_worker
|
|
* @see CSVReader::read_row()
|
|
*/
|
|
CSV_INLINE bool CSVReader::read_csv(size_t bytes) {
|
|
// Tell read_row() to listen for CSV rows
|
|
this->records->notify_all();
|
|
|
|
this->parser->set_output(*this->records);
|
|
this->parser->next(bytes);
|
|
|
|
if (!this->header_trimmed) {
|
|
this->trim_header();
|
|
}
|
|
|
|
// Tell read_row() to stop waiting
|
|
this->records->kill_all();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Retrieve rows as CSVRow objects, returning true if more rows are available.
|
|
*
|
|
* @par Performance Notes
|
|
* - Reads chunks of data that are csv::internals::ITERATION_CHUNK_SIZE bytes large at a time
|
|
* - For performance details, read the documentation for CSVRow and CSVField.
|
|
*
|
|
* @param[out] row The variable where the parsed row will be stored
|
|
* @see CSVRow, CSVField
|
|
*
|
|
* **Example:**
|
|
* \snippet tests/test_read_csv.cpp CSVField Example
|
|
*
|
|
*/
|
|
CSV_INLINE bool CSVReader::read_row(CSVRow &row) {
|
|
while (true) {
|
|
if (this->records->empty()) {
|
|
if (this->records->is_waitable())
|
|
// Reading thread is currently active => wait for it to populate records
|
|
this->records->wait();
|
|
else if (this->parser->eof())
|
|
// End of file and no more records
|
|
return false;
|
|
else {
|
|
// Reading thread is not active => start another one
|
|
if (this->read_csv_worker.joinable())
|
|
this->read_csv_worker.join();
|
|
|
|
this->read_csv_worker = std::thread(&CSVReader::read_csv, this, internals::ITERATION_CHUNK_SIZE);
|
|
}
|
|
}
|
|
else if (this->records->front().size() != this->n_cols &&
|
|
this->_format.variable_column_policy != VariableColumnPolicy::KEEP) {
|
|
auto errored_row = this->records->pop_front();
|
|
|
|
if (this->_format.variable_column_policy == VariableColumnPolicy::THROW) {
|
|
if (errored_row.size() < this->n_cols)
|
|
throw std::runtime_error("Line too short " + internals::format_row(errored_row));
|
|
|
|
throw std::runtime_error("Line too long " + internals::format_row(errored_row));
|
|
}
|
|
}
|
|
else {
|
|
row = this->records->pop_front();
|
|
this->_n_rows++;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** @file
|
|
* Defines an input iterator for csv::CSVReader
|
|
*/
|
|
|
|
|
|
namespace csv {
|
|
/** Return an iterator to the first row in the reader */
|
|
CSV_INLINE CSVReader::iterator CSVReader::begin() {
|
|
if (this->records->empty()) {
|
|
this->read_csv_worker = std::thread(&CSVReader::read_csv, this, internals::ITERATION_CHUNK_SIZE);
|
|
this->read_csv_worker.join();
|
|
|
|
// Still empty => return end iterator
|
|
if (this->records->empty()) return this->end();
|
|
}
|
|
|
|
CSVReader::iterator ret(this, this->records->pop_front());
|
|
return ret;
|
|
}
|
|
|
|
/** A placeholder for the imaginary past the end row in a CSV.
|
|
* Attempting to deference this will lead to bad things.
|
|
*/
|
|
CSV_INLINE HEDLEY_CONST CSVReader::iterator CSVReader::end() const noexcept {
|
|
return CSVReader::iterator();
|
|
}
|
|
|
|
/////////////////////////
|
|
// CSVReader::iterator //
|
|
/////////////////////////
|
|
|
|
CSV_INLINE CSVReader::iterator::iterator(CSVReader* _daddy, CSVRow&& _row) :
|
|
daddy(_daddy) {
|
|
row = std::move(_row);
|
|
}
|
|
|
|
/** Advance the iterator by one row. If this CSVReader has an
|
|
* associated file, then the iterator will lazily pull more data from
|
|
* that file until the end of file is reached.
|
|
*
|
|
* @note This iterator does **not** block the thread responsible for parsing CSV.
|
|
*
|
|
*/
|
|
CSV_INLINE CSVReader::iterator& CSVReader::iterator::operator++() {
|
|
if (!daddy->read_row(this->row)) {
|
|
this->daddy = nullptr; // this == end()
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
/** Post-increment iterator */
|
|
CSV_INLINE CSVReader::iterator CSVReader::iterator::operator++(int) {
|
|
auto temp = *this;
|
|
if (!daddy->read_row(this->row)) {
|
|
this->daddy = nullptr; // this == end()
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
}
|
|
|
|
/** @file
|
|
* Defines the data type used for storing information about a CSV row
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <functional>
|
|
|
|
namespace csv {
|
|
namespace internals {
|
|
CSV_INLINE RawCSVField& CSVFieldList::operator[](size_t n) const {
|
|
const size_t page_no = n / _single_buffer_capacity;
|
|
const size_t buffer_idx = (page_no < 1) ? n : n % _single_buffer_capacity;
|
|
return this->buffers[page_no][buffer_idx];
|
|
}
|
|
|
|
CSV_INLINE void CSVFieldList::allocate() {
|
|
RawCSVField * buffer = new RawCSVField[_single_buffer_capacity];
|
|
buffers.push_back(buffer);
|
|
_current_buffer_size = 0;
|
|
_back = &(buffers.back()[0]);
|
|
}
|
|
}
|
|
|
|
/** Return a CSVField object corrsponding to the nth value in the row.
|
|
*
|
|
* @note This method performs bounds checking, and will throw an
|
|
* `std::runtime_error` if n is invalid.
|
|
*
|
|
* @complexity
|
|
* Constant, by calling csv::CSVRow::get_csv::string_view()
|
|
*
|
|
*/
|
|
CSV_INLINE CSVField CSVRow::operator[](size_t n) const {
|
|
return CSVField(this->get_field(n));
|
|
}
|
|
|
|
/** Retrieve a value by its associated column name. If the column
|
|
* specified can't be round, a runtime error is thrown.
|
|
*
|
|
* @complexity
|
|
* Constant. This calls the other CSVRow::operator[]() after
|
|
* converting column names into indices using a hash table.
|
|
*
|
|
* @param[in] col_name The column to look for
|
|
*/
|
|
CSV_INLINE CSVField CSVRow::operator[](const std::string& col_name) const {
|
|
auto & col_names = this->data->col_names;
|
|
auto col_pos = col_names->index_of(col_name);
|
|
if (col_pos > -1) {
|
|
return this->operator[](col_pos);
|
|
}
|
|
|
|
throw std::runtime_error("Can't find a column named " + col_name);
|
|
}
|
|
|
|
CSV_INLINE CSVRow::operator std::vector<std::string>() const {
|
|
std::vector<std::string> ret;
|
|
for (size_t i = 0; i < size(); i++)
|
|
ret.push_back(std::string(this->get_field(i)));
|
|
|
|
return ret;
|
|
}
|
|
|
|
CSV_INLINE csv::string_view CSVRow::get_field(size_t index) const
|
|
{
|
|
using internals::ParseFlags;
|
|
|
|
if (index >= this->size())
|
|
throw std::runtime_error("Index out of bounds.");
|
|
|
|
const size_t field_index = this->fields_start + index;
|
|
auto& field = this->data->fields[field_index];
|
|
auto field_str = csv::string_view(this->data->data).substr(this->data_start + field.start);
|
|
|
|
if (field.has_double_quote) {
|
|
auto& value = this->data->double_quote_fields[field_index];
|
|
if (value.empty()) {
|
|
bool prev_ch_quote = false;
|
|
for (size_t i = 0; i < field.length; i++) {
|
|
if (this->data->parse_flags[field_str[i] + 128] == ParseFlags::QUOTE) {
|
|
if (prev_ch_quote) {
|
|
prev_ch_quote = false;
|
|
continue;
|
|
}
|
|
else {
|
|
prev_ch_quote = true;
|
|
}
|
|
}
|
|
|
|
value += field_str[i];
|
|
}
|
|
}
|
|
|
|
return csv::string_view(value);
|
|
}
|
|
|
|
return field_str.substr(0, field.length);
|
|
}
|
|
|
|
CSV_INLINE bool CSVField::try_parse_hex(int& parsedValue) {
|
|
size_t start = 0, end = 0;
|
|
|
|
// Trim out whitespace chars
|
|
for (; start < this->sv.size() && this->sv[start] == ' '; start++);
|
|
for (end = start; end < this->sv.size() && this->sv[end] != ' '; end++);
|
|
|
|
unsigned long long int value = 0;
|
|
|
|
size_t digits = (end - start);
|
|
size_t base16_exponent = digits - 1;
|
|
|
|
if (digits == 0) return false;
|
|
|
|
for (const auto& ch : this->sv.substr(start, digits)) {
|
|
int digit = 0;
|
|
|
|
switch (ch) {
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
digit = static_cast<int>(ch - '0');
|
|
break;
|
|
case 'a':
|
|
case 'A':
|
|
digit = 10;
|
|
break;
|
|
case 'b':
|
|
case 'B':
|
|
digit = 11;
|
|
break;
|
|
case 'c':
|
|
case 'C':
|
|
digit = 12;
|
|
break;
|
|
case 'd':
|
|
case 'D':
|
|
digit = 13;
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
digit = 14;
|
|
break;
|
|
case 'f':
|
|
case 'F':
|
|
digit = 15;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
value += digit * pow(16, base16_exponent);
|
|
base16_exponent--;
|
|
}
|
|
|
|
parsedValue = value;
|
|
return true;
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma region CSVRow Iterator
|
|
#endif
|
|
/** Return an iterator pointing to the first field. */
|
|
CSV_INLINE CSVRow::iterator CSVRow::begin() const {
|
|
return CSVRow::iterator(this, 0);
|
|
}
|
|
|
|
/** Return an iterator pointing to just after the end of the CSVRow.
|
|
*
|
|
* @warning Attempting to dereference the end iterator results
|
|
* in dereferencing a null pointer.
|
|
*/
|
|
CSV_INLINE CSVRow::iterator CSVRow::end() const noexcept {
|
|
return CSVRow::iterator(this, (int)this->size());
|
|
}
|
|
|
|
CSV_INLINE CSVRow::reverse_iterator CSVRow::rbegin() const noexcept {
|
|
return std::reverse_iterator<CSVRow::iterator>(this->end());
|
|
}
|
|
|
|
CSV_INLINE CSVRow::reverse_iterator CSVRow::rend() const {
|
|
return std::reverse_iterator<CSVRow::iterator>(this->begin());
|
|
}
|
|
|
|
CSV_INLINE HEDLEY_NON_NULL(2)
|
|
CSVRow::iterator::iterator(const CSVRow* _reader, int _i)
|
|
: daddy(_reader), i(_i) {
|
|
if (_i < (int)this->daddy->size())
|
|
this->field = std::make_shared<CSVField>(
|
|
this->daddy->operator[](_i));
|
|
else
|
|
this->field = nullptr;
|
|
}
|
|
|
|
CSV_INLINE CSVRow::iterator::reference CSVRow::iterator::operator*() const {
|
|
return *(this->field.get());
|
|
}
|
|
|
|
CSV_INLINE CSVRow::iterator::pointer CSVRow::iterator::operator->() const {
|
|
// Using CSVField * as pointer type causes segfaults in MSVC debug builds
|
|
#ifdef _MSC_BUILD
|
|
return this->field;
|
|
#else
|
|
return this->field.get();
|
|
#endif
|
|
}
|
|
|
|
CSV_INLINE CSVRow::iterator& CSVRow::iterator::operator++() {
|
|
// Pre-increment operator
|
|
this->i++;
|
|
if (this->i < (int)this->daddy->size())
|
|
this->field = std::make_shared<CSVField>(
|
|
this->daddy->operator[](i));
|
|
else // Reached the end of row
|
|
this->field = nullptr;
|
|
return *this;
|
|
}
|
|
|
|
CSV_INLINE CSVRow::iterator CSVRow::iterator::operator++(int) {
|
|
// Post-increment operator
|
|
auto temp = *this;
|
|
this->operator++();
|
|
return temp;
|
|
}
|
|
|
|
CSV_INLINE CSVRow::iterator& CSVRow::iterator::operator--() {
|
|
// Pre-decrement operator
|
|
this->i--;
|
|
this->field = std::make_shared<CSVField>(
|
|
this->daddy->operator[](this->i));
|
|
return *this;
|
|
}
|
|
|
|
CSV_INLINE CSVRow::iterator CSVRow::iterator::operator--(int) {
|
|
// Post-decrement operator
|
|
auto temp = *this;
|
|
this->operator--();
|
|
return temp;
|
|
}
|
|
|
|
CSV_INLINE CSVRow::iterator CSVRow::iterator::operator+(difference_type n) const {
|
|
// Allows for iterator arithmetic
|
|
return CSVRow::iterator(this->daddy, i + (int)n);
|
|
}
|
|
|
|
CSV_INLINE CSVRow::iterator CSVRow::iterator::operator-(difference_type n) const {
|
|
// Allows for iterator arithmetic
|
|
return CSVRow::iterator::operator+(-n);
|
|
}
|
|
#ifdef _MSC_VER
|
|
#pragma endregion CSVRow Iterator
|
|
#endif
|
|
}
|
|
|
|
/** @file
|
|
* Implements JSON serialization abilities
|
|
*/
|
|
|
|
|
|
namespace csv {
|
|
/*
|
|
The implementations for json_extra_space() and json_escape_string()
|
|
were modified from source code for JSON for Modern C++.
|
|
|
|
The respective license is below:
|
|
|
|
The code is licensed under the [MIT
|
|
License](http://opensource.org/licenses/MIT):
|
|
|
|
Copyright © 2013-2015 Niels Lohmann.
|
|
|
|
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.
|
|
*/
|
|
|
|
namespace internals {
|
|
/*!
|
|
@brief calculates the extra space to escape a JSON string
|
|
|
|
@param[in] s the string to escape
|
|
@return the number of characters required to escape string @a s
|
|
|
|
@complexity Linear in the length of string @a s.
|
|
*/
|
|
static std::size_t json_extra_space(csv::string_view& s) noexcept
|
|
{
|
|
std::size_t result = 0;
|
|
|
|
|
|
for (const auto& c : s)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '"':
|
|
case '\\':
|
|
case '\b':
|
|
case '\f':
|
|
case '\n':
|
|
case '\r':
|
|
case '\t':
|
|
{
|
|
// from c (1 byte) to \x (2 bytes)
|
|
result += 1;
|
|
break;
|
|
}
|
|
|
|
|
|
default:
|
|
{
|
|
if (c >= 0x00 && c <= 0x1f)
|
|
{
|
|
// from c (1 byte) to \uxxxx (6 bytes)
|
|
result += 5;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return result;
|
|
}
|
|
|
|
CSV_INLINE std::string json_escape_string(csv::string_view s) noexcept
|
|
{
|
|
const auto space = json_extra_space(s);
|
|
if (space == 0)
|
|
{
|
|
return std::string(s);
|
|
}
|
|
|
|
// create a result string of necessary size
|
|
std::string result(s.size() + space, '\\');
|
|
std::size_t pos = 0;
|
|
|
|
for (const auto& c : s)
|
|
{
|
|
switch (c)
|
|
{
|
|
// quotation mark (0x22)
|
|
case '"':
|
|
{
|
|
result[pos + 1] = '"';
|
|
pos += 2;
|
|
break;
|
|
}
|
|
|
|
|
|
// reverse solidus (0x5c)
|
|
case '\\':
|
|
{
|
|
// nothing to change
|
|
pos += 2;
|
|
break;
|
|
}
|
|
|
|
|
|
// backspace (0x08)
|
|
case '\b':
|
|
{
|
|
result[pos + 1] = 'b';
|
|
pos += 2;
|
|
break;
|
|
}
|
|
|
|
|
|
// formfeed (0x0c)
|
|
case '\f':
|
|
{
|
|
result[pos + 1] = 'f';
|
|
pos += 2;
|
|
break;
|
|
}
|
|
|
|
|
|
// newline (0x0a)
|
|
case '\n':
|
|
{
|
|
result[pos + 1] = 'n';
|
|
pos += 2;
|
|
break;
|
|
}
|
|
|
|
|
|
// carriage return (0x0d)
|
|
case '\r':
|
|
{
|
|
result[pos + 1] = 'r';
|
|
pos += 2;
|
|
break;
|
|
}
|
|
|
|
|
|
// horizontal tab (0x09)
|
|
case '\t':
|
|
{
|
|
result[pos + 1] = 't';
|
|
pos += 2;
|
|
break;
|
|
}
|
|
|
|
|
|
default:
|
|
{
|
|
if (c >= 0x00 && c <= 0x1f)
|
|
{
|
|
// print character c as \uxxxx
|
|
sprintf(&result[pos + 1], "u%04x", int(c));
|
|
pos += 6;
|
|
// overwrite trailing null character
|
|
result[pos] = '\\';
|
|
}
|
|
else
|
|
{
|
|
// all other characters are added as-is
|
|
result[pos++] = c;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/** Convert a CSV row to a JSON object, i.e.
|
|
* `{"col1":"value1","col2":"value2"}`
|
|
*
|
|
* @note All strings are properly escaped. Numeric values are not quoted.
|
|
* @param[in] subset A subset of columns to contain in the JSON.
|
|
* Leave empty for original columns.
|
|
*/
|
|
CSV_INLINE std::string CSVRow::to_json(const std::vector<std::string>& subset) const {
|
|
std::vector<std::string> col_names = subset;
|
|
if (subset.empty()) {
|
|
col_names = this->data ? this->get_col_names() : std::vector<std::string>({});
|
|
}
|
|
|
|
const size_t _n_cols = col_names.size();
|
|
std::string ret = "{";
|
|
|
|
for (size_t i = 0; i < _n_cols; i++) {
|
|
auto& col = col_names[i];
|
|
auto field = this->operator[](col);
|
|
|
|
// TODO: Possible performance enhancements by caching escaped column names
|
|
ret += '"' + internals::json_escape_string(col) + "\":";
|
|
|
|
// Add quotes around strings but not numbers
|
|
if (field.is_num())
|
|
ret += internals::json_escape_string(field.get<csv::string_view>());
|
|
else
|
|
ret += '"' + internals::json_escape_string(field.get<csv::string_view>()) + '"';
|
|
|
|
// Do not add comma after last string
|
|
if (i + 1 < _n_cols)
|
|
ret += ',';
|
|
}
|
|
|
|
ret += '}';
|
|
return ret;
|
|
}
|
|
|
|
/** Convert a CSV row to a JSON array, i.e.
|
|
* `["value1","value2",...]`
|
|
*
|
|
* @note All strings are properly escaped. Numeric values are not quoted.
|
|
* @param[in] subset A subset of columns to contain in the JSON.
|
|
* Leave empty for all columns.
|
|
*/
|
|
CSV_INLINE std::string CSVRow::to_json_array(const std::vector<std::string>& subset) const {
|
|
std::vector<std::string> col_names = subset;
|
|
if (subset.empty())
|
|
col_names = this->data ? this->get_col_names() : std::vector<std::string>({});
|
|
|
|
const size_t _n_cols = col_names.size();
|
|
std::string ret = "[";
|
|
|
|
for (size_t i = 0; i < _n_cols; i++) {
|
|
auto field = this->operator[](col_names[i]);
|
|
|
|
// Add quotes around strings but not numbers
|
|
if (field.is_num())
|
|
ret += internals::json_escape_string(field.get<csv::string_view>());
|
|
else
|
|
ret += '"' + internals::json_escape_string(field.get<csv::string_view>()) + '"';
|
|
|
|
// Do not add comma after last string
|
|
if (i + 1 < _n_cols)
|
|
ret += ',';
|
|
}
|
|
|
|
ret += ']';
|
|
return ret;
|
|
}
|
|
}
|
|
/** @file
|
|
* Calculates statistics from CSV files
|
|
*/
|
|
|
|
#include <string>
|
|
|
|
namespace csv {
|
|
/** Calculate statistics for an arbitrarily large file. When this constructor
|
|
* is called, CSVStat will process the entire file iteratively. Once finished,
|
|
* methods like get_mean(), get_counts(), etc... can be used to retrieve statistics.
|
|
*/
|
|
CSV_INLINE CSVStat::CSVStat(csv::string_view filename, CSVFormat format) :
|
|
reader(filename, format) {
|
|
this->calc();
|
|
}
|
|
|
|
/** Calculate statistics for a CSV stored in a std::stringstream */
|
|
CSV_INLINE CSVStat::CSVStat(std::stringstream& stream, CSVFormat format) :
|
|
reader(stream, format) {
|
|
this->calc();
|
|
}
|
|
|
|
/** Return current means */
|
|
CSV_INLINE std::vector<long double> CSVStat::get_mean() const {
|
|
std::vector<long double> ret;
|
|
for (size_t i = 0; i < this->get_col_names().size(); i++) {
|
|
ret.push_back(this->rolling_means[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/** Return current variances */
|
|
CSV_INLINE std::vector<long double> CSVStat::get_variance() const {
|
|
std::vector<long double> ret;
|
|
for (size_t i = 0; i < this->get_col_names().size(); i++) {
|
|
ret.push_back(this->rolling_vars[i]/(this->n[i] - 1));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/** Return current mins */
|
|
CSV_INLINE std::vector<long double> CSVStat::get_mins() const {
|
|
std::vector<long double> ret;
|
|
for (size_t i = 0; i < this->get_col_names().size(); i++) {
|
|
ret.push_back(this->mins[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/** Return current maxes */
|
|
CSV_INLINE std::vector<long double> CSVStat::get_maxes() const {
|
|
std::vector<long double> ret;
|
|
for (size_t i = 0; i < this->get_col_names().size(); i++) {
|
|
ret.push_back(this->maxes[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/** Get counts for each column */
|
|
CSV_INLINE std::vector<CSVStat::FreqCount> CSVStat::get_counts() const {
|
|
std::vector<FreqCount> ret;
|
|
for (size_t i = 0; i < this->get_col_names().size(); i++) {
|
|
ret.push_back(this->counts[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/** Get data type counts for each column */
|
|
CSV_INLINE std::vector<CSVStat::TypeCount> CSVStat::get_dtypes() const {
|
|
std::vector<TypeCount> ret;
|
|
for (size_t i = 0; i < this->get_col_names().size(); i++) {
|
|
ret.push_back(this->dtypes[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
CSV_INLINE void CSVStat::calc_chunk() {
|
|
/** Only create stats counters the first time **/
|
|
if (dtypes.empty()) {
|
|
/** Go through all records and calculate specified statistics */
|
|
for (size_t i = 0; i < this->get_col_names().size(); i++) {
|
|
dtypes.push_back({});
|
|
counts.push_back({});
|
|
rolling_means.push_back(0);
|
|
rolling_vars.push_back(0);
|
|
mins.push_back(NAN);
|
|
maxes.push_back(NAN);
|
|
n.push_back(0);
|
|
}
|
|
}
|
|
|
|
// Start threads
|
|
std::vector<std::thread> pool;
|
|
for (size_t i = 0; i < this->get_col_names().size(); i++)
|
|
pool.push_back(std::thread(&CSVStat::calc_worker, this, i));
|
|
|
|
// Block until done
|
|
for (auto& th : pool)
|
|
th.join();
|
|
|
|
this->records.clear();
|
|
}
|
|
|
|
CSV_INLINE void CSVStat::calc() {
|
|
constexpr size_t CALC_CHUNK_SIZE = 5000;
|
|
|
|
for (auto& row : reader) {
|
|
this->records.push_back(std::move(row));
|
|
|
|
/** Chunk rows */
|
|
if (this->records.size() == CALC_CHUNK_SIZE) {
|
|
calc_chunk();
|
|
}
|
|
}
|
|
|
|
if (!this->records.empty()) {
|
|
calc_chunk();
|
|
}
|
|
}
|
|
|
|
CSV_INLINE void CSVStat::calc_worker(const size_t &i) {
|
|
/** Worker thread for CSVStat::calc() which calculates statistics for one column.
|
|
*
|
|
* @param[in] i Column index
|
|
*/
|
|
|
|
auto current_record = this->records.begin();
|
|
|
|
for (size_t processed = 0; current_record != this->records.end(); processed++) {
|
|
if (current_record->size() == this->get_col_names().size()) {
|
|
auto current_field = (*current_record)[i];
|
|
|
|
// Optimization: Don't count() if there's too many distinct values in the first 1000 rows
|
|
if (processed < 1000 || this->counts[i].size() <= 500)
|
|
this->count(current_field, i);
|
|
|
|
this->dtype(current_field, i);
|
|
|
|
// Numeric Stuff
|
|
if (current_field.is_num()) {
|
|
long double x_n = current_field.get<long double>();
|
|
|
|
// This actually calculates mean AND variance
|
|
this->variance(x_n, i);
|
|
this->min_max(x_n, i);
|
|
}
|
|
}
|
|
else if (this->reader.get_format().get_variable_column_policy() == VariableColumnPolicy::THROW) {
|
|
throw std::runtime_error("Line has different length than the others " + internals::format_row(*current_record));
|
|
}
|
|
|
|
++current_record;
|
|
}
|
|
}
|
|
|
|
CSV_INLINE void CSVStat::dtype(CSVField& data, const size_t &i) {
|
|
/** Given a record update the type counter
|
|
* @param[in] record Data observation
|
|
* @param[out] i The column index that should be updated
|
|
*/
|
|
|
|
auto type = data.type();
|
|
if (this->dtypes[i].find(type) !=
|
|
this->dtypes[i].end()) {
|
|
// Increment count
|
|
this->dtypes[i][type]++;
|
|
} else {
|
|
// Initialize count
|
|
this->dtypes[i].insert(std::make_pair(type, 1));
|
|
}
|
|
}
|
|
|
|
CSV_INLINE void CSVStat::count(CSVField& data, const size_t &i) {
|
|
/** Given a record update the frequency counter
|
|
* @param[in] record Data observation
|
|
* @param[out] i The column index that should be updated
|
|
*/
|
|
|
|
auto item = data.get<std::string>();
|
|
|
|
if (this->counts[i].find(item) !=
|
|
this->counts[i].end()) {
|
|
// Increment count
|
|
this->counts[i][item]++;
|
|
} else {
|
|
// Initialize count
|
|
this->counts[i].insert(std::make_pair(item, 1));
|
|
}
|
|
}
|
|
|
|
CSV_INLINE void CSVStat::min_max(const long double &x_n, const size_t &i) {
|
|
/** Update current minimum and maximum
|
|
* @param[in] x_n Data observation
|
|
* @param[out] i The column index that should be updated
|
|
*/
|
|
if (std::isnan(this->mins[i]))
|
|
this->mins[i] = x_n;
|
|
if (std::isnan(this->maxes[i]))
|
|
this->maxes[i] = x_n;
|
|
|
|
if (x_n < this->mins[i])
|
|
this->mins[i] = x_n;
|
|
else if (x_n > this->maxes[i])
|
|
this->maxes[i] = x_n;
|
|
}
|
|
|
|
CSV_INLINE void CSVStat::variance(const long double &x_n, const size_t &i) {
|
|
/** Given a record update rolling mean and variance for all columns
|
|
* using Welford's Algorithm
|
|
* @param[in] x_n Data observation
|
|
* @param[out] i The column index that should be updated
|
|
*/
|
|
long double& current_rolling_mean = this->rolling_means[i];
|
|
long double& current_rolling_var = this->rolling_vars[i];
|
|
long double& current_n = this->n[i];
|
|
long double delta;
|
|
long double delta2;
|
|
|
|
current_n++;
|
|
|
|
if (current_n == 1) {
|
|
current_rolling_mean = x_n;
|
|
} else {
|
|
delta = x_n - current_rolling_mean;
|
|
current_rolling_mean += delta/current_n;
|
|
delta2 = x_n - current_rolling_mean;
|
|
current_rolling_var += delta*delta2;
|
|
}
|
|
}
|
|
|
|
/** Useful for uploading CSV files to SQL databases.
|
|
*
|
|
* Return a data type for each column such that every value in a column can be
|
|
* converted to the corresponding data type without data loss.
|
|
* @param[in] filename The CSV file
|
|
*
|
|
* \return A mapping of column names to csv::DataType enums
|
|
*/
|
|
CSV_INLINE std::unordered_map<std::string, DataType> csv_data_types(const std::string& filename) {
|
|
CSVStat stat(filename);
|
|
std::unordered_map<std::string, DataType> csv_dtypes;
|
|
|
|
auto col_names = stat.get_col_names();
|
|
auto temp = stat.get_dtypes();
|
|
|
|
for (size_t i = 0; i < stat.get_col_names().size(); i++) {
|
|
auto& col = temp[i];
|
|
auto& col_name = col_names[i];
|
|
|
|
if (col[DataType::CSV_STRING])
|
|
csv_dtypes[col_name] = DataType::CSV_STRING;
|
|
else if (col[DataType::CSV_INT64])
|
|
csv_dtypes[col_name] = DataType::CSV_INT64;
|
|
else if (col[DataType::CSV_INT32])
|
|
csv_dtypes[col_name] = DataType::CSV_INT32;
|
|
else if (col[DataType::CSV_INT16])
|
|
csv_dtypes[col_name] = DataType::CSV_INT16;
|
|
else if (col[DataType::CSV_INT8])
|
|
csv_dtypes[col_name] = DataType::CSV_INT8;
|
|
else
|
|
csv_dtypes[col_name] = DataType::CSV_DOUBLE;
|
|
}
|
|
|
|
return csv_dtypes;
|
|
}
|
|
}
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
|
|
namespace csv {
|
|
/** Shorthand function for parsing an in-memory CSV string
|
|
*
|
|
* @return A collection of CSVRow objects
|
|
*
|
|
* @par Example
|
|
* @snippet tests/test_read_csv.cpp Parse Example
|
|
*/
|
|
CSV_INLINE CSVReader parse(csv::string_view in, CSVFormat format) {
|
|
std::stringstream stream(in.data());
|
|
return CSVReader(stream, format);
|
|
}
|
|
|
|
/** Parses a CSV string with no headers
|
|
*
|
|
* @return A collection of CSVRow objects
|
|
*/
|
|
CSV_INLINE CSVReader parse_no_header(csv::string_view in) {
|
|
CSVFormat format;
|
|
format.header_row(-1);
|
|
|
|
return parse(in, format);
|
|
}
|
|
|
|
/** Parse a RFC 4180 CSV string, returning a collection
|
|
* of CSVRow objects
|
|
*
|
|
* @par Example
|
|
* @snippet tests/test_read_csv.cpp Escaped Comma
|
|
*
|
|
*/
|
|
CSV_INLINE CSVReader operator ""_csv(const char* in, size_t n) {
|
|
return parse(csv::string_view(in, n));
|
|
}
|
|
|
|
/** A shorthand for csv::parse_no_header() */
|
|
CSV_INLINE CSVReader operator ""_csv_no_header(const char* in, size_t n) {
|
|
return parse_no_header(csv::string_view(in, n));
|
|
}
|
|
|
|
/**
|
|
* Find the position of a column in a CSV file or CSV_NOT_FOUND otherwise
|
|
*
|
|
* @param[in] filename Path to CSV file
|
|
* @param[in] col_name Column whose position we should resolve
|
|
* @param[in] format Format of the CSV file
|
|
*/
|
|
CSV_INLINE int get_col_pos(
|
|
csv::string_view filename,
|
|
csv::string_view col_name,
|
|
const CSVFormat& format) {
|
|
CSVReader reader(filename, format);
|
|
return reader.index_of(col_name);
|
|
}
|
|
|
|
/** Get basic information about a CSV file
|
|
* @include programs/csv_info.cpp
|
|
*/
|
|
CSV_INLINE CSVFileInfo get_file_info(const std::string& filename) {
|
|
CSVReader reader(filename);
|
|
CSVFormat format = reader.get_format();
|
|
for (auto it = reader.begin(); it != reader.end(); ++it);
|
|
|
|
CSVFileInfo info = {
|
|
filename,
|
|
reader.get_col_names(),
|
|
format.get_delim(),
|
|
reader.n_rows(),
|
|
reader.get_col_names().size()
|
|
};
|
|
|
|
return info;
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|