4 Commits

Author SHA1 Message Date
ado
809939d0e2 Update parser error messages, fix parser tests 2024-03-13 19:39:07 +01:00
ado
b9f4afdd5f Add header and raw_header methods, update header usage methods error handling, write new and update existing unit tests 2024-03-13 17:15:31 +01:00
red0124
69875c238e Resolve clang-tidy warnings (#48)
* Resolve clang-tidy warnings, update single_header_generator.py

* Update single header test, resolve additional clang-tidy warnings
2024-03-12 18:31:24 +01:00
red0124
457defadaa Bugfix/odr violations (#47)
* Make common non-member functions inline, remove unreachable line from get_line_buffer

* [skip ci] Fix namespace comments
2024-03-12 10:22:10 +01:00
39 changed files with 755 additions and 831 deletions

View File

@@ -25,10 +25,10 @@ jobs:
strategy:
matrix:
xcode: ['15.2']
xcode: ['13.4.1', '14.1']
type: [Release, Debug]
runs-on: macos-latest
runs-on: macos-12
env:
DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer
@@ -42,7 +42,7 @@ jobs:
id: cores
- name: Install dependencies
run: script/ci_install_deps.sh sudo
run: script/ci_install_deps.sh
- name: Configure
run: cmake -S test -B build -DCMAKE_BUILD_TYPE=${{matrix.type}}

77
.github/workflows/ubuntu-latest-icc.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: ubuntu-latest-icc-ci
on:
workflow_dispatch:
push:
branches:
- master
- feature/**
- improvement/**
- bugfix/**
pull_request:
branches:
- master
- feature/**
- improvement/**
- bugfix/**
env:
LINUX_BASEKIT_URL: https://registrationcenter-download.intel.com/akdlm/irc_nas/17431/l_BaseKit_p_2021.1.0.2659_offline.sh
LINUX_HPCKIT_URL:
https://registrationcenter-download.intel.com/akdlm/irc_nas/17427/l_HPCKit_p_2021.1.0.2684_offline.sh
jobs:
icc_tests:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-latest
defaults:
run:
shell: bash
container:
image: gcc:10
options: -v /usr/local:/host_usr_local
steps:
- uses: actions/checkout@v2
- name: cache install
id: cache-install
uses: actions/cache@v2
with:
path: |
/opt/intel/oneapi/compiler
key: >-
install-${{env.LINUX_HPCKIT_URL}}-
${{env.LINUX_CPP_COMPONENTS_WEB}}-
compiler-${{hashFiles('**/scripts/cache_exclude_linux.sh')}}
- name: Install icc
run: >-
script/ci_install_icc.sh $LINUX_HPCKIT_URL $LINUX_CPP_COMPONENTS_WEB
- name: CMake
run: echo "/host_usr_local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: script/ci_install_deps.sh
- name: Configure
run: >-
source script/ci_setup_icc.sh &&
cmake -S test -B build -D CMAKE_BUILD_TYPE=Debug
- name: Build
run: >-
source script/ci_setup_icc.sh &&
cmake --build build -j ${{steps.cores.outputs.count}}
- name: Run
working-directory: build
run: ctest --output-on-failure

View File

@@ -31,11 +31,14 @@ jobs:
fail-fast: false
matrix:
config:
- os: windows-2019
vs: "Visual Studio 16 2019"
- os: windows-latest
vs: "Visual Studio 17 2022"
build: [Debug, Release]
platform: [x64]
platform: [Win32, x64]
runs-on: ${{matrix.config.os}}

View File

@@ -31,7 +31,7 @@ jobs:
fail-fast: false
matrix:
os: [windows-2025, windows-latest]
os: [windows-2019, windows-latest]
type: [Release, Debug]
config:
- msystem: "MINGW64"
@@ -39,6 +39,11 @@ jobs:
git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja
mingw-w64-x86_64-clang
- msystem: "MINGW32"
install: >-
git mingw-w64-i686-cmake mingw-w64-i686-ninja
mingw-w64-i686-clang
runs-on: ${{matrix.os}}
name: "${{matrix.config.msystem}}: ${{matrix.os}}: ${{matrix.type}}"

View File

@@ -31,7 +31,7 @@ jobs:
fail-fast: false
matrix:
os: [windows-2025, windows-latest]
os: [windows-2019, windows-latest]
type: [Release, Debug]
config:
- msystem: "MINGW64"
@@ -39,6 +39,11 @@ jobs:
git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja
mingw-w64-x86_64-gcc
- msystem: "MINGW32"
install: >-
git mingw-w64-i686-cmake mingw-w64-i686-ninja
mingw-w64-i686-gcc
runs-on: ${{matrix.os}}
name: "${{matrix.config.msystem}}: ${{matrix.os}}: ${{matrix.type}}"

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.14)
project(
ssp
VERSION 1.8.0
VERSION 1.7.2
DESCRIPTION "csv parser"
HOMEPAGE_URL "https://github.com/red0124/ssp"
LANGUAGES CXX

View File

@@ -9,14 +9,15 @@
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![coverage](https://coveralls.io/repos/github/red0124/ssp/badge.svg?branch=master)](https://coveralls.io/github/red0124/ssp?branch=master)
[![fuzz](https://github.com/red0124/ssp/actions/workflows/fuzz.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/fuzz.yml)
[![single-header](https://github.com/red0124/ssp/actions/workflows/single-header.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/single-header.yml)
[![ubuntu-latest-gcc](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml)
[![ubuntu-latest-clang](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml)
[![windows-msys2-gcc](https://github.com/red0124/ssp/actions/workflows/win-msys2-gcc.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/win-msys2-gcc.yml)
[![windows-msys2-clang](https://github.com/red0124/ssp/actions/workflows/win-msys2-clang.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/win-msys2-clang.yml)
[![windows-msvc](https://github.com/red0124/ssp/actions/workflows/win-msvc.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/win-msvc.yml)
[![macos-apple-clang](https://github.com/red0124/ssp/actions/workflows/macos-apple-clang.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/macos-apple-clang.yml)
[![fuzz](https://github.com/red0124/ssp/workflows/fuzz-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/fuzz.yml)
[![single-header](https://github.com/red0124/ssp/workflows/single-header-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/single-header.yml)
[![ubuntu-latest-gcc](https://github.com/red0124/ssp/workflows/ubuntu-latest-gcc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml)
[![ubuntu-latest-clang](https://github.com/red0124/ssp/workflows/ubuntu-latest-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml)
[![ubuntu-latest-icc](https://github.com/red0124/ssp/workflows/ubuntu-latest-icc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-icc.yml)
[![windows-msys2-gcc](https://github.com/red0124/ssp/workflows/win-msys2-gcc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/win-msys2-gcc.yml)
[![windows-msys2-clang](https://github.com/red0124/ssp/workflows/win-msys2-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/win-msys2-clang.yml)
[![windows-msvc](https://github.com/red0124/ssp/workflows/win-msvc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/win-msvc.yml)
[![macos-apple-clang](https://github.com/red0124/ssp/workflows/macos-apple-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/macos-apple-clang.yml)
A header only CSV parser which is fast and versatile with modern C++ API. Requires compiler with C++17 support. [Can also be used to efficiently convert strings to specific types.](#the-converter)
@@ -73,7 +74,7 @@ Bill (Heath) Gates 65 3.3
# Single header
The library can be used with a single header file **`ssp.hpp`**, but it suffers a significant performance loss when converting floating point values since the **`fast_float`** library is not present within the file.
The library can be used with a single header file **`ssp.hpp`**, but it suffers a slight performance loss when converting floating point values since the **`fast_float`** library is not present within the file.
# Installation
@@ -91,7 +92,7 @@ The library supports [CMake](#Cmake) and [meson](#Meson) build systems
## Headers
The parser can be told to use only certain columns by parsing the header. This can be done with the **`use_fields`** method. It accepts any number of string-like arguments or even an **`std::vector<std::string>`** with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error.
The parser can be told to use only certain columns by parsing the header. This can be done by using the **`use_fields`** method. It accepts any number of string-like arguments or even an **`std::vector<std::string>`** with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error.
```shell
$ cat students_with_header.csv
Id,Age,Grade
@@ -115,7 +116,7 @@ James Bailey 2.5
Brian S. Wolfe 1.9
Bill (Heath) Gates 3.3
```
The header can be ignored using the **`ss::ignore_header`** [setup](#Setup) option or by calling the **`ignore_next`** method after the parser has been constructed. If the header has been ignored calling any method related to header usage will result in a compilation error.
The header can be ignored using the **`ss::ignore_header`** [setup](#Setup) option or by calling the **`ignore_next`** method after the parser has been constructed.
```cpp
ss::parser<ss::ignore_header> p{file_name};
```
@@ -123,10 +124,10 @@ The fields with which the parser works with can be modified at any given time. T
```cpp
// ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
p.use_fields("Grade");
p.use_fields("Id", "Grade");
const auto& grade = p.get_next<std::string>();
std::cout << grade << std::endl;
const auto& [id, grade] = p.get_next<std::string, float>();
std::cout << id << ' ' << grade << std::endl;
if (p.field_exists("Id")) {
p.use_fields("Grade", "Id");
@@ -138,32 +139,10 @@ The fields with which the parser works with can be modified at any given time. T
```
```shell
$ ./a.out
2.5
1.9 Brian S. Wolfe
3.3 Bill (Heath) Gates
James Bailey 2.5
40 Brian S. Wolfe
65 Bill (Heath) Gates
```
The header is parsed with the same rules as other rows, the only difference is that **`multiline`** will be disabled when parsing the header. To get the data that is
present in the header as a **`std::vector<std::string>`**, the **`header`** method can be used, and to get the header line before it has been parsed, the **`raw_header`** method can be used:
```cpp
// ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
std::cout << p.raw_header() << std::endl;
for (const auto& field: p.header()) {
std::cout << "> " << field << std::endl;
}
// ...
```
```shell
$ ./a.out
Id,Age,Grade
> Id
> Age
> Grade
```
Methods related to headers can also fail, the error handling of these is done in the same way as for other methods.
## Conversions
An alternate loop to the example above would look like:
```cpp

View File

@@ -32,7 +32,7 @@ void assert_throw_on_error_not_defined() {
"'throw_on_error' is enabled");
}
[[nodiscard]] inline void* strict_realloc(void* ptr, size_t size) {
inline void* strict_realloc(void* ptr, size_t size) {
ptr = std::realloc(ptr, size);
if (!ptr) {
throw std::bad_alloc{};
@@ -42,20 +42,18 @@ void assert_throw_on_error_not_defined() {
}
#if __unix__
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n,
FILE* file) {
inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
return getline(&lineptr, &n, file);
}
#else
using ssize_t = intptr_t;
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n,
FILE* file) {
inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
std::array<char, get_line_initial_buffer_size> buff;
if (lineptr == nullptr || n < sizeof(buff)) {
const size_t new_n = sizeof(buff);
size_t new_n = sizeof(buff);
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
}
@@ -68,7 +66,7 @@ using ssize_t = intptr_t;
size_t buff_used = std::strlen(buff.data());
if (n <= buff_used + line_used) {
const size_t new_n = n * 2;
size_t new_n = n * 2;
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
}
@@ -87,9 +85,8 @@ using ssize_t = intptr_t;
#endif
[[nodiscard]] inline ssize_t get_line_buffer(char*& lineptr, size_t& n,
const char* const csv_data_buffer,
size_t csv_data_size,
inline ssize_t get_line_buffer(char*& lineptr, size_t& n,
const char* const csv_data_buffer, size_t csv_data_size,
size_t& curr_char) {
if (curr_char >= csv_data_size) {
return -1;
@@ -105,7 +102,7 @@ using ssize_t = intptr_t;
size_t line_used = 0;
while (curr_char < csv_data_size) {
if (line_used + 1 >= n) {
const size_t new_n = n * 2;
size_t new_n = n * 2;
char* new_lineptr =
static_cast<char*>(strict_realloc(lineptr, new_n));
@@ -125,10 +122,10 @@ using ssize_t = intptr_t;
return line_used;
}
[[nodiscard]] inline std::tuple<ssize_t, bool> get_line(
char*& buffer, size_t& buffer_size, FILE* file,
const char* const csv_data_buffer, size_t csv_data_size,
size_t& curr_char) {
inline std::tuple<ssize_t, bool> get_line(char*& buffer, size_t& buffer_size,
FILE* file,
const char* const csv_data_buffer,
size_t csv_data_size, size_t& curr_char) {
ssize_t ssize = 0;
if (file) {
ssize = get_line_file(buffer, buffer_size, file);

View File

@@ -110,19 +110,19 @@ public:
// parses line with given delimiter, returns a 'T' object created with
// extracted values of type 'Ts'
template <typename T, typename... Ts>
[[nodiscard]] T convert_object(
line_ptr_type line, const std::string& delim = default_delimiter) {
T convert_object(line_ptr_type line,
const std::string& delim = default_delimiter) {
return to_object<T>(convert<Ts...>(line, delim));
}
// parses line with given delimiter, returns tuple of objects with
// extracted values of type 'Ts'
template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> convert(
no_void_validator_tup_t<Ts...> convert(
line_ptr_type line, const std::string& delim = default_delimiter) {
split(line, delim);
if (splitter_.valid()) {
return convert<Ts...>(splitter_.get_split_data());
return convert<Ts...>(splitter_.split_data_);
} else {
handle_error_bad_split();
return {};
@@ -131,13 +131,13 @@ public:
// parses already split line, returns 'T' object with extracted values
template <typename T, typename... Ts>
[[nodiscard]] T convert_object(const split_data& elems) {
T convert_object(const split_data& elems) {
return to_object<T>(convert<Ts...>(elems));
}
// same as above, but uses cached split line
template <typename T, typename... Ts>
[[nodiscard]] T convert_object() {
T convert_object() {
return to_object<T>(convert<Ts...>());
}
@@ -146,8 +146,7 @@ public:
// one argument is given which is a class which has a tied
// method which returns a tuple, returns that type
template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert(
const split_data& elems) {
no_void_validator_tup_t<T, Ts...> convert(const split_data& elems) {
if constexpr (sizeof...(Ts) == 0 && is_instance_of_v<std::tuple, T>) {
return convert_impl(elems, static_cast<T*>(nullptr));
} else if constexpr (tied_class_v<T, Ts...>) {
@@ -163,11 +162,11 @@ public:
// same as above, but uses cached split line
template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert() {
return convert<T, Ts...>(splitter_.get_split_data());
no_void_validator_tup_t<T, Ts...> convert() {
return convert<T, Ts...>(splitter_.split_data_);
}
[[nodiscard]] bool valid() const {
bool valid() const {
if constexpr (string_error) {
return error_.empty();
} else if constexpr (throw_on_error) {
@@ -177,12 +176,12 @@ public:
}
}
[[nodiscard]] const std::string& error_msg() const {
const std::string& error_msg() const {
assert_string_error_defined<string_error>();
return error_;
}
[[nodiscard]] bool unterminated_quote() const {
bool unterminated_quote() const {
return splitter_.unterminated_quote();
}
@@ -190,9 +189,9 @@ public:
// contain the beginnings and the ends of each column of the string
const split_data& split(line_ptr_type line,
const std::string& delim = default_delimiter) {
splitter_.clear_split_data();
splitter_.split_data_.clear();
if (line[0] == '\0') {
return splitter_.get_split_data();
return splitter_.split_data_;
}
return splitter_.split(line, delim);
@@ -208,7 +207,7 @@ private:
return splitter_.resplit(new_line, new_size, delim);
}
[[nodiscard]] size_t size_shifted() {
size_t size_shifted() {
return splitter_.size_shifted();
}
@@ -224,8 +223,7 @@ private:
}
}
[[nodiscard]] std::string error_sufix(const string_range msg,
size_t pos) const {
std::string error_sufix(const string_range msg, size_t pos) const {
constexpr static auto reserve_size = 32;
std::string error;
error.reserve(reserve_size);
@@ -353,8 +351,7 @@ private:
////////////////
template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> convert_impl(
const split_data& elems) {
no_void_validator_tup_t<Ts...> convert_impl(const split_data& elems) {
clear_error();
if (!splitter_.valid()) {
@@ -385,7 +382,7 @@ private:
}
template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<std::tuple<Ts...>> convert_impl(
no_void_validator_tup_t<std::tuple<Ts...>> convert_impl(
const split_data& elems, const std::tuple<Ts...>*) {
return convert_impl<Ts...>(elems);
}
@@ -394,11 +391,11 @@ private:
// column mapping
////////////////
[[nodiscard]] bool columns_mapped() const {
bool columns_mapped() const {
return !column_mappings_.empty();
}
[[nodiscard]] size_t column_position(size_t tuple_position) const {
size_t column_position(size_t tuple_position) const {
if (!columns_mapped()) {
return tuple_position;
}
@@ -429,7 +426,7 @@ private:
}
if constexpr (std::is_same_v<T, std::string>) {
static_cast<void>(extract(msg.first, msg.second, dst));
extract(msg.first, msg.second, dst);
return;
}
@@ -475,8 +472,7 @@ private:
}
template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> extract_tuple(
const split_data& elems) {
no_void_validator_tup_t<Ts...> extract_tuple(const split_data& elems) {
static_assert(!all_of_v<std::is_void, Ts...>,
"at least one parameter must be non void");
no_void_validator_tup_t<Ts...> ret{};

View File

@@ -12,10 +12,10 @@ class exception : public std::exception {
std::string msg_;
public:
exception(std::string msg) : msg_{std::move(msg)} {
exception(std::string msg): msg_{std::move(msg)} {
}
[[nodiscard]] char const* what() const noexcept override {
char const* what() const noexcept override {
return msg_.c_str();
}
};

View File

@@ -13,8 +13,8 @@
#include <fast_float/fast_float.h>
#else
#include <algorithm>
#include <array>
#include <cstdlib>
#include <array>
#endif
namespace ss {
@@ -26,8 +26,8 @@ namespace ss {
#ifndef SSP_DISABLE_FAST_FLOAT
template <typename T>
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
to_num(const char* const begin, const char* const end) {
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
T ret;
auto [ptr, ec] = fast_float::from_chars(begin, end, ret);
@@ -40,15 +40,15 @@ to_num(const char* const begin, const char* const end) {
#else
template <typename T>
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
to_num(const char* const begin, const char* const end) {
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
static_assert(!std::is_same_v<T, long double>,
"Conversion to long double is disabled");
constexpr static auto buff_max = 64;
std::array<char, buff_max> short_buff;
const size_t string_range = std::distance(begin, end);
size_t string_range = std::distance(begin, end);
std::string long_buff;
char* buff = nullptr;
@@ -88,10 +88,10 @@ struct numeric_wrapper {
using type = T;
numeric_wrapper() = default;
numeric_wrapper(numeric_wrapper&&) noexcept = default;
numeric_wrapper(numeric_wrapper&&) = default;
numeric_wrapper(const numeric_wrapper&) = default;
numeric_wrapper& operator=(numeric_wrapper&&) noexcept = default;
numeric_wrapper& operator=(numeric_wrapper&&) = default;
numeric_wrapper& operator=(const numeric_wrapper&) = default;
~numeric_wrapper() = default;
@@ -114,7 +114,7 @@ using int8 = numeric_wrapper<int8_t>;
using uint8 = numeric_wrapper<uint8_t>;
template <typename T>
[[nodiscard]] std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret);
@@ -126,9 +126,8 @@ template <typename T>
}
template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<numeric_wrapper, T>,
std::optional<T>>
to_num(const char* const begin, const char* const end) {
std::enable_if_t<is_instance_of_v<numeric_wrapper, T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret.value);
@@ -150,8 +149,7 @@ struct unsupported_type {
} /* namespace errors */
template <typename T>
[[nodiscard]] std::enable_if_t<!std::is_integral_v<T> &&
!std::is_floating_point_v<T> &&
std::enable_if_t<!std::is_integral_v<T> && !std::is_floating_point_v<T> &&
!is_instance_of_v<std::optional, T> &&
!is_instance_of_v<std::variant, T> &&
!is_instance_of_v<numeric_wrapper, T>,
@@ -163,8 +161,7 @@ extract(const char*, const char*, T&) {
}
template <typename T>
[[nodiscard]] std::enable_if_t<std::is_integral_v<T> ||
std::is_floating_point_v<T> ||
std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T> ||
is_instance_of_v<numeric_wrapper, T>,
bool>
extract(const char* begin, const char* end, T& value) {
@@ -177,8 +174,8 @@ extract(const char* begin, const char* end, T& value) {
}
template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::optional, T>, bool>
extract(const char* begin, const char* end, T& value) {
std::enable_if_t<is_instance_of_v<std::optional, T>, bool> extract(
const char* begin, const char* end, T& value) {
typename T::value_type raw_value;
if (extract(begin, end, raw_value)) {
value = raw_value;
@@ -189,8 +186,7 @@ extract(const char* begin, const char* end, T& value) {
}
template <typename T, size_t I>
[[nodiscard]] bool extract_variant(const char* begin, const char* end,
T& value) {
bool extract_variant(const char* begin, const char* end, T& value) {
using IthType = std::variant_alternative_t<I, std::decay_t<T>>;
IthType ithValue;
if (extract<IthType>(begin, end, ithValue)) {
@@ -203,7 +199,7 @@ template <typename T, size_t I>
}
template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
const char* begin, const char* end, T& value) {
return extract_variant<T, 0>(begin, end, value);
}
@@ -213,8 +209,7 @@ template <typename T>
////////////////
template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end,
bool& value) {
inline bool extract(const char* begin, const char* end, bool& value) {
if (end == begin + 1) {
if (*begin == '1') {
value = true;
@@ -226,7 +221,7 @@ template <>
} else {
constexpr static auto true_size = 4;
constexpr static auto false_size = 5;
const size_t size = end - begin;
size_t size = end - begin;
if (size == true_size && std::strncmp(begin, "true", size) == 0) {
value = true;
} else if (size == false_size &&
@@ -241,21 +236,19 @@ template <>
}
template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end,
char& value) {
inline bool extract(const char* begin, const char* end, char& value) {
value = *begin;
return (end == begin + 1);
}
template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end,
std::string& value) {
inline bool extract(const char* begin, const char* end, std::string& value) {
value = std::string{begin, end};
return true;
}
template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end,
inline bool extract(const char* begin, const char* end,
std::string_view& value) {
value = std::string_view{begin, static_cast<size_t>(end - begin)};
return true;

View File

@@ -67,15 +67,15 @@ public:
}
}
parser(parser&& other) noexcept = default;
parser& operator=(parser&& other) noexcept = default;
parser(parser&& other) = default;
parser& operator=(parser&& other) = default;
~parser() = default;
parser() = delete;
parser(const parser& other) = delete;
parser& operator=(const parser& other) = delete;
[[nodiscard]] bool valid() const {
bool valid() const {
if constexpr (string_error) {
return error_.empty();
} else if constexpr (throw_on_error) {
@@ -85,12 +85,12 @@ public:
}
}
[[nodiscard]] const std::string& error_msg() const {
const std::string& error_msg() const {
assert_string_error_defined<string_error>();
return error_;
}
[[nodiscard]] bool eof() const {
bool eof() const {
return eof_;
}
@@ -99,21 +99,23 @@ public:
}
template <typename T, typename... Ts>
[[nodiscard]] T get_object() {
T get_object() {
return to_object<T>(get_next<Ts...>());
}
[[nodiscard]] size_t line() const {
size_t line() const {
return reader_.line_number_ > 0 ? reader_.line_number_ - 1
: reader_.line_number_;
}
[[nodiscard]] size_t position() const {
size_t position() const {
return reader_.chars_read_;
}
template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> get_next() {
no_void_validator_tup_t<T, Ts...> get_next() {
std::optional<std::string> error;
if (!eof_) {
if constexpr (throw_on_error) {
try {
@@ -162,12 +164,12 @@ public:
return value;
}
[[nodiscard]] std::string raw_header() const {
std::string raw_header() const {
assert_ignore_header_not_defined();
return raw_header_;
}
[[nodiscard]] std::vector<std::string> header() {
std::vector<std::string> header() {
assert_ignore_header_not_defined();
clear_error();
@@ -179,14 +181,14 @@ public:
}
std::vector<std::string> split_header;
for (const auto& [begin, end] : splitter.get_split_data()) {
for (const auto& [begin, end] : splitter.split_data_) {
split_header.emplace_back(begin, end);
}
return split_header;
}
[[nodiscard]] bool field_exists(const std::string& field) {
bool field_exists(const std::string& field) {
assert_ignore_header_not_defined();
clear_error();
@@ -265,17 +267,17 @@ public:
}
iterator(const iterator& other) = default;
iterator(iterator&& other) noexcept = default;
iterator(iterator&& other) = default;
~iterator() = default;
iterator& operator=(const iterator& other) = delete;
iterator& operator=(iterator&& other) noexcept = delete;
iterator& operator=(iterator&& other) = delete;
[[nodiscard]] value& operator*() {
value& operator*() {
return value_;
}
[[nodiscard]] value* operator->() {
value* operator->() {
return &value_;
}
@@ -300,15 +302,13 @@ public:
return result;
}
[[nodiscard]] friend bool operator==(const iterator& lhs,
const iterator& rhs) {
friend bool operator==(const iterator& lhs, const iterator& rhs) {
return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) ||
(lhs.parser_ == rhs.parser_ &&
&lhs.value_ == &rhs.value_);
}
[[nodiscard]] friend bool operator!=(const iterator& lhs,
const iterator& rhs) {
friend bool operator!=(const iterator& lhs, const iterator& rhs) {
return !(lhs == rhs);
}
@@ -320,11 +320,11 @@ public:
iterable(parser<Options...>* parser) : parser_{parser} {
}
[[nodiscard]] iterator begin() {
iterator begin() {
return ++iterator{parser_};
}
[[nodiscard]] iterator end() {
iterator end() {
return iterator{};
}
@@ -333,12 +333,12 @@ public:
};
template <typename... Ts>
[[nodiscard]] auto iterate() {
auto iterate() {
return iterable<false, Ts...>{this};
}
template <typename... Ts>
[[nodiscard]] auto iterate_object() {
auto iterate_object() {
return iterable<true, Ts...>{this};
}
@@ -376,7 +376,7 @@ public:
return composite_with(std::move(value));
}
[[nodiscard]] std::tuple<Ts...> values() {
std::tuple<Ts...> values() {
return values_;
}
@@ -399,7 +399,7 @@ public:
private:
template <typename T>
[[nodiscard]] composite<Ts..., T> composite_with(T&& new_value) {
composite<Ts..., T> composite_with(T&& new_value) {
auto merged_values =
std::tuple_cat(std::move(values_),
std::tuple<T>{parser_.valid()
@@ -429,7 +429,7 @@ public:
}
template <typename U, typename... Us>
[[nodiscard]] no_void_validator_tup_t<U, Us...> try_same() {
no_void_validator_tup_t<U, Us...> try_same() {
parser_.clear_error();
auto value =
parser_.reader_.converter_.template convert<U, Us...>();
@@ -450,8 +450,8 @@ public:
// tries to convert a line and returns a composite which is
// able to try additional conversions in case of failure
template <typename... Ts, typename Fun = none>
[[nodiscard]] composite<std::optional<no_void_validator_tup_t<Ts...>>>
try_next(Fun&& fun = none{}) {
composite<std::optional<no_void_validator_tup_t<Ts...>>> try_next(
Fun&& fun = none{}) {
assert_throw_on_error_not_defined<throw_on_error>();
using Ret = no_void_validator_tup_t<Ts...>;
return try_invoke_and_make_composite<
@@ -461,7 +461,7 @@ public:
// identical to try_next but returns composite with object instead of a
// tuple
template <typename T, typename... Ts, typename Fun = none>
[[nodiscard]] composite<std::optional<T>> try_object(Fun&& fun = none{}) {
composite<std::optional<T>> try_object(Fun&& fun = none{}) {
assert_throw_on_error_not_defined<throw_on_error>();
return try_invoke_and_make_composite<
std::optional<T>>(get_object<T, Ts...>(), std::forward<Fun>(fun));
@@ -512,8 +512,7 @@ private:
}
template <typename T, typename Fun = none>
[[nodiscard]] composite<T> try_invoke_and_make_composite(T&& value,
Fun&& fun) {
composite<T> try_invoke_and_make_composite(T&& value, Fun&& fun) {
if (valid()) {
try_invoke(*value, std::forward<Fun>(fun));
}
@@ -529,8 +528,11 @@ private:
"cannot use this method when 'ignore_header' is defined");
}
[[nodiscard]] bool strict_split(header_splitter& splitter,
std::string& header) {
bool strict_split(header_splitter& splitter, std::string& header) {
if (header.empty()) {
return false;
}
if constexpr (throw_on_error) {
try {
splitter.split(header.data(), reader_.delim_);
@@ -556,8 +558,13 @@ private:
return;
}
for (const auto& [begin, end] : splitter.get_split_data()) {
for (const auto& [begin, end] : splitter.split_data_) {
std::string field{begin, end};
if (field.empty()) {
handle_error_duplicate_header_field(field);
header_.clear();
return;
}
if (std::find(header_.begin(), header_.end(), field) !=
header_.end()) {
handle_error_duplicate_header_field(field);
@@ -568,7 +575,7 @@ private:
}
}
[[nodiscard]] std::optional<size_t> header_index(const std::string& field) {
std::optional<size_t> header_index(const std::string& field) {
auto it = std::find(header_.begin(), header_.end(), field);
if (it == header_.end()) {
@@ -830,7 +837,7 @@ private:
std::free(helper_buffer_);
if (file_) {
std::ignore = std::fclose(file_);
std::fclose(file_);
}
}
@@ -839,7 +846,7 @@ private:
reader& operator=(const reader& other) = delete;
// read next line each time in order to set eof_
[[nodiscard]] bool read_next() {
bool read_next() {
next_line_converter_.clear_error();
size_t size = 0;
while (size == 0) {
@@ -931,7 +938,7 @@ private:
std::swap(converter_, next_line_converter_);
}
[[nodiscard]] bool multiline_limit_reached(size_t& limit) {
bool multiline_limit_reached(size_t& limit) {
if constexpr (multiline::size > 0) {
if (limit++ >= multiline::size) {
next_line_converter_.handle_error_multiline_limit_reached();
@@ -941,7 +948,7 @@ private:
return false;
}
[[nodiscard]] bool escaped_eol(size_t size) {
bool escaped_eol(size_t size) {
const char* curr = nullptr;
for (curr = next_line_buffer_ + size - 1;
curr >= next_line_buffer_ &&
@@ -951,7 +958,7 @@ private:
return (next_line_buffer_ - curr + size) % 2 == 0;
}
[[nodiscard]] bool unterminated_quote() {
bool unterminated_quote() {
return next_line_converter_.unterminated_quote();
}
@@ -966,7 +973,7 @@ private:
}
}
[[nodiscard]] size_t remove_eol(char*& buffer, size_t ssize) {
size_t remove_eol(char*& buffer, size_t ssize) {
if (buffer[ssize - 1] != '\n') {
crlf_ = false;
return ssize;
@@ -996,8 +1003,7 @@ private:
first_size += second_size;
}
[[nodiscard]] bool append_next_line_to_buffer(char*& buffer,
size_t& line_size,
bool append_next_line_to_buffer(char*& buffer, size_t& line_size,
size_t buffer_size) {
undo_remove_eol(buffer, line_size, buffer_size);
@@ -1017,7 +1023,7 @@ private:
return true;
}
[[nodiscard]] std::string get_buffer() {
std::string get_buffer() {
return std::string{next_line_buffer_, next_line_size_};
}

View File

@@ -10,7 +10,7 @@ template <typename T, auto... Values>
struct ax {
private:
template <auto X, auto... Xs>
[[nodiscard]] bool ss_valid_impl(const T& x) const {
bool ss_valid_impl(const T& x) const {
if constexpr (sizeof...(Xs) != 0) {
return x != X && ss_valid_impl<Xs...>(x);
}
@@ -18,11 +18,11 @@ private:
}
public:
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return ss_valid_impl<Values...>(value);
}
[[nodiscard]] const char* error() const {
const char* error() const {
return "value excluded";
}
};
@@ -35,7 +35,7 @@ template <typename T, auto... Values>
struct nx {
private:
template <auto X, auto... Xs>
[[nodiscard]] bool ss_valid_impl(const T& x) const {
bool ss_valid_impl(const T& x) const {
if constexpr (sizeof...(Xs) != 0) {
return x == X || ss_valid_impl<Xs...>(x);
}
@@ -43,11 +43,11 @@ private:
}
public:
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return ss_valid_impl<Values...>(value);
}
[[nodiscard]] const char* error() const {
const char* error() const {
return "value excluded";
}
};
@@ -61,28 +61,28 @@ public:
template <typename T, auto N>
struct gt {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value > N;
}
};
template <typename T, auto N>
struct gte {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value >= N;
}
};
template <typename T, auto N>
struct lt {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value < N;
}
};
template <typename T, auto N>
struct lte {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value <= N;
}
};
@@ -93,7 +93,7 @@ struct lte {
template <typename T, auto Min, auto Max>
struct ir {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value >= Min && value <= Max;
}
};
@@ -104,7 +104,7 @@ struct ir {
template <typename T, auto Min, auto Max>
struct oor {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value < Min || value > Max;
}
};
@@ -115,11 +115,11 @@ struct oor {
template <typename T>
struct ne {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return !value.empty();
}
[[nodiscard]] const char* error() const {
const char* error() const {
return "empty field";
}
};

View File

@@ -28,7 +28,7 @@ private:
public:
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
[[nodiscard]] bool valid() const {
bool valid() const {
if constexpr (string_error) {
return error_.empty();
} else if constexpr (throw_on_error) {
@@ -38,12 +38,12 @@ public:
}
}
[[nodiscard]] const std::string& error_msg() const {
const std::string& error_msg() const {
assert_string_error_defined<string_error>();
return error_;
}
[[nodiscard]] bool unterminated_quote() const {
bool unterminated_quote() const {
return unterminated_quote_;
}
@@ -55,21 +55,13 @@ public:
return split_impl_select_delim(delimiter);
}
[[nodiscard]] const split_data& get_split_data() const {
return split_data_;
}
void clear_split_data() {
split_data_.clear();
}
private:
////////////////
// resplit
////////////////
// number of characters the end of line is shifted backwards
[[nodiscard]] size_t size_shifted() const {
size_t size_shifted() const {
return escaped_;
}
@@ -92,7 +84,7 @@ private:
}
const auto [old_line, old_begin] = *std::prev(split_data_.end());
const size_t begin = old_begin - old_line - 1;
size_t begin = old_begin - old_line - 1;
// safety measure
if (new_size != -1 && static_cast<size_t>(new_size) < begin) {
@@ -200,19 +192,19 @@ private:
// matching
////////////////
[[nodiscard]] bool match(const char* const curr, char delim) {
bool match(const char* const curr, char delim) {
return *curr == delim;
};
[[nodiscard]] bool match(const char* const curr, const std::string& delim) {
bool match(const char* const curr, const std::string& delim) {
return std::strncmp(curr, delim.c_str(), delim.size()) == 0;
};
[[nodiscard]] size_t delimiter_size(char) {
size_t delimiter_size(char) {
return 1;
}
[[nodiscard]] size_t delimiter_size(const std::string& delim) {
size_t delimiter_size(const std::string& delim) {
return delim.size();
}
@@ -233,7 +225,7 @@ private:
}
template <typename Delim>
[[nodiscard]] std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
const Delim& delim) {
line_ptr_type end = begin;
@@ -469,6 +461,7 @@ private:
// members
////////////////
public:
error_type error_{};
bool unterminated_quote_{false};
bool done_{true};

View File

@@ -366,12 +366,12 @@ constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value;
////////////////
template <class T, std::size_t... Is, class U>
[[nodiscard]] T to_object_impl(std::index_sequence<Is...>, U&& data) {
T to_object_impl(std::index_sequence<Is...>, U&& data) {
return {std::get<Is>(std::forward<U>(data))...};
}
template <class T, class U>
[[nodiscard]] T to_object(U&& data) {
T to_object(U&& data) {
using NoRefU = std::decay_t<U>;
if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
return to_object_impl<

View File

@@ -6,7 +6,7 @@ project(
'cpp_std=c++17',
'buildtype=debugoptimized',
'wrap_mode=forcefallback'],
version: '1.8.0',
version: '1.7.2',
meson_version:'>=0.54.0')
fast_float_dep = dependency('fast_float')

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash
#!/bin/bash
JOBS=4
BUILD_TYPE=Debug
set -ex
set -eux
git clone https://github.com/red0124/doctest -b master --depth 1
@@ -12,10 +12,6 @@ cmake -S doctest -B doctest/build \
-D DOCTEST_WITH_MAIN_IN_STATIC_LIB=NO \
-D DOCTEST_WITH_TESTS=NO
if [[ "${1}" == "sudo" ]]; then
sudo cmake --build doctest/build --config ${BUILD_TYPE} --target install -j ${JOBS}
else
cmake --build doctest/build --config ${BUILD_TYPE} --target install -j ${JOBS}
fi
cmake --build doctest/build --config ${BUILD_TYPE} --target install -j ${JOBS}
rm -rf doctest

23
script/ci_install_icc.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2020 Intel Corporation
#
# SPDX-License-Identifier: MIT
URL=$1
COMPONENTS=$2
curl --output webimage.sh --url "$URL" --retry 5 --retry-delay 5
chmod +x webimage.sh
./webimage.sh -x -f webimage_extracted --log extract.log
rm -rf webimage.sh
WEBIMAGE_NAME=$(ls -1 webimage_extracted/)
if [ -z "$COMPONENTS" ]; then
webimage_extracted/"$WEBIMAGE_NAME"/bootstrapper -s --action install --eula=accept --continue-with-optional-error=yes --log-dir=.
installer_exit_code=$?
else
webimage_extracted/"$WEBIMAGE_NAME"/bootstrapper -s --action install --components="$COMPONENTS" --eula=accept --continue-with-optional-error=yes --log-dir=.
installer_exit_code=$?
fi
rm -rf webimage_extracted
exit $installer_exit_code

11
script/ci_setup_icc.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2020 Intel Corporation
#
# SPDX-License-Identifier: MIT
#shellcheck disable=SC2010
LATEST_VERSION=$(ls -1 /opt/intel/oneapi/compiler/ | grep -v latest | sort | tail -1)
# shellcheck source=/dev/null
source /opt/intel/oneapi/compiler/"$LATEST_VERSION"/env/vars.sh
CXX=dpcpp

View File

@@ -37,7 +37,6 @@ for header in headers:
includes = sorted(set(includes))
print('#pragma once')
print('\n'.join(includes))
print('#define SSP_DISABLE_FAST_FLOAT')
print('\n'.join(combined_file))

288
ssp.hpp
View File

@@ -1,4 +1,3 @@
#pragma once
#include <algorithm>
#include <array>
#include <cerrno>
@@ -382,12 +381,12 @@ constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value;
////////////////
template <class T, std::size_t... Is, class U>
[[nodiscard]] T to_object_impl(std::index_sequence<Is...>, U&& data) {
T to_object_impl(std::index_sequence<Is...>, U&& data) {
return {std::get<Is>(std::forward<U>(data))...};
}
template <class T, class U>
[[nodiscard]] T to_object(U&& data) {
T to_object(U&& data) {
using NoRefU = std::decay_t<U>;
if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
return to_object_impl<
@@ -410,10 +409,10 @@ class exception : public std::exception {
std::string msg_;
public:
exception(std::string msg) : msg_{std::move(msg)} {
exception(std::string msg): msg_{std::move(msg)} {
}
[[nodiscard]] char const* what() const noexcept override {
char const* what() const noexcept override {
return msg_.c_str();
}
};
@@ -506,7 +505,7 @@ template <typename T, auto... Values>
struct ax {
private:
template <auto X, auto... Xs>
[[nodiscard]] bool ss_valid_impl(const T& x) const {
bool ss_valid_impl(const T& x) const {
if constexpr (sizeof...(Xs) != 0) {
return x != X && ss_valid_impl<Xs...>(x);
}
@@ -514,11 +513,11 @@ private:
}
public:
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return ss_valid_impl<Values...>(value);
}
[[nodiscard]] const char* error() const {
const char* error() const {
return "value excluded";
}
};
@@ -531,7 +530,7 @@ template <typename T, auto... Values>
struct nx {
private:
template <auto X, auto... Xs>
[[nodiscard]] bool ss_valid_impl(const T& x) const {
bool ss_valid_impl(const T& x) const {
if constexpr (sizeof...(Xs) != 0) {
return x == X || ss_valid_impl<Xs...>(x);
}
@@ -539,11 +538,11 @@ private:
}
public:
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return ss_valid_impl<Values...>(value);
}
[[nodiscard]] const char* error() const {
const char* error() const {
return "value excluded";
}
};
@@ -557,28 +556,28 @@ public:
template <typename T, auto N>
struct gt {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value > N;
}
};
template <typename T, auto N>
struct gte {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value >= N;
}
};
template <typename T, auto N>
struct lt {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value < N;
}
};
template <typename T, auto N>
struct lte {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value <= N;
}
};
@@ -589,7 +588,7 @@ struct lte {
template <typename T, auto Min, auto Max>
struct ir {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value >= Min && value <= Max;
}
};
@@ -600,7 +599,7 @@ struct ir {
template <typename T, auto Min, auto Max>
struct oor {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return value < Min || value > Max;
}
};
@@ -611,11 +610,11 @@ struct oor {
template <typename T>
struct ne {
[[nodiscard]] bool ss_valid(const T& value) const {
bool ss_valid(const T& value) const {
return !value.empty();
}
[[nodiscard]] const char* error() const {
const char* error() const {
return "empty field";
}
};
@@ -649,7 +648,7 @@ void assert_throw_on_error_not_defined() {
"'throw_on_error' is enabled");
}
[[nodiscard]] inline void* strict_realloc(void* ptr, size_t size) {
inline void* strict_realloc(void* ptr, size_t size) {
ptr = std::realloc(ptr, size);
if (!ptr) {
throw std::bad_alloc{};
@@ -659,20 +658,18 @@ void assert_throw_on_error_not_defined() {
}
#if __unix__
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n,
FILE* file) {
inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
return getline(&lineptr, &n, file);
}
#else
using ssize_t = intptr_t;
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n,
FILE* file) {
inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
std::array<char, get_line_initial_buffer_size> buff;
if (lineptr == nullptr || n < sizeof(buff)) {
const size_t new_n = sizeof(buff);
size_t new_n = sizeof(buff);
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
}
@@ -685,7 +682,7 @@ using ssize_t = intptr_t;
size_t buff_used = std::strlen(buff.data());
if (n <= buff_used + line_used) {
const size_t new_n = n * 2;
size_t new_n = n * 2;
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
}
@@ -704,9 +701,8 @@ using ssize_t = intptr_t;
#endif
[[nodiscard]] inline ssize_t get_line_buffer(char*& lineptr, size_t& n,
const char* const csv_data_buffer,
size_t csv_data_size,
inline ssize_t get_line_buffer(char*& lineptr, size_t& n,
const char* const csv_data_buffer, size_t csv_data_size,
size_t& curr_char) {
if (curr_char >= csv_data_size) {
return -1;
@@ -722,7 +718,7 @@ using ssize_t = intptr_t;
size_t line_used = 0;
while (curr_char < csv_data_size) {
if (line_used + 1 >= n) {
const size_t new_n = n * 2;
size_t new_n = n * 2;
char* new_lineptr =
static_cast<char*>(strict_realloc(lineptr, new_n));
@@ -742,10 +738,10 @@ using ssize_t = intptr_t;
return line_used;
}
[[nodiscard]] inline std::tuple<ssize_t, bool> get_line(
char*& buffer, size_t& buffer_size, FILE* file,
const char* const csv_data_buffer, size_t csv_data_size,
size_t& curr_char) {
inline std::tuple<ssize_t, bool> get_line(char*& buffer, size_t& buffer_size,
FILE* file,
const char* const csv_data_buffer,
size_t csv_data_size, size_t& curr_char) {
ssize_t ssize = 0;
if (file) {
ssize = get_line_file(buffer, buffer_size, file);
@@ -1083,7 +1079,7 @@ private:
public:
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
[[nodiscard]] bool valid() const {
bool valid() const {
if constexpr (string_error) {
return error_.empty();
} else if constexpr (throw_on_error) {
@@ -1093,12 +1089,12 @@ public:
}
}
[[nodiscard]] const std::string& error_msg() const {
const std::string& error_msg() const {
assert_string_error_defined<string_error>();
return error_;
}
[[nodiscard]] bool unterminated_quote() const {
bool unterminated_quote() const {
return unterminated_quote_;
}
@@ -1110,21 +1106,13 @@ public:
return split_impl_select_delim(delimiter);
}
[[nodiscard]] const split_data& get_split_data() const {
return split_data_;
}
void clear_split_data() {
split_data_.clear();
}
private:
////////////////
// resplit
////////////////
// number of characters the end of line is shifted backwards
[[nodiscard]] size_t size_shifted() const {
size_t size_shifted() const {
return escaped_;
}
@@ -1147,7 +1135,7 @@ private:
}
const auto [old_line, old_begin] = *std::prev(split_data_.end());
const size_t begin = old_begin - old_line - 1;
size_t begin = old_begin - old_line - 1;
// safety measure
if (new_size != -1 && static_cast<size_t>(new_size) < begin) {
@@ -1255,19 +1243,19 @@ private:
// matching
////////////////
[[nodiscard]] bool match(const char* const curr, char delim) {
bool match(const char* const curr, char delim) {
return *curr == delim;
};
[[nodiscard]] bool match(const char* const curr, const std::string& delim) {
bool match(const char* const curr, const std::string& delim) {
return std::strncmp(curr, delim.c_str(), delim.size()) == 0;
};
[[nodiscard]] size_t delimiter_size(char) {
size_t delimiter_size(char) {
return 1;
}
[[nodiscard]] size_t delimiter_size(const std::string& delim) {
size_t delimiter_size(const std::string& delim) {
return delim.size();
}
@@ -1288,7 +1276,7 @@ private:
}
template <typename Delim>
[[nodiscard]] std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
const Delim& delim) {
line_ptr_type end = begin;
@@ -1524,6 +1512,7 @@ private:
// members
////////////////
public:
error_type error_{};
bool unterminated_quote_{false};
bool done_{true};
@@ -1556,8 +1545,8 @@ namespace ss {
#ifndef SSP_DISABLE_FAST_FLOAT
template <typename T>
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
to_num(const char* const begin, const char* const end) {
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
T ret;
auto [ptr, ec] = fast_float::from_chars(begin, end, ret);
@@ -1570,15 +1559,15 @@ to_num(const char* const begin, const char* const end) {
#else
template <typename T>
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
to_num(const char* const begin, const char* const end) {
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
static_assert(!std::is_same_v<T, long double>,
"Conversion to long double is disabled");
constexpr static auto buff_max = 64;
std::array<char, buff_max> short_buff;
const size_t string_range = std::distance(begin, end);
size_t string_range = std::distance(begin, end);
std::string long_buff;
char* buff = nullptr;
@@ -1618,10 +1607,10 @@ struct numeric_wrapper {
using type = T;
numeric_wrapper() = default;
numeric_wrapper(numeric_wrapper&&) noexcept = default;
numeric_wrapper(numeric_wrapper&&) = default;
numeric_wrapper(const numeric_wrapper&) = default;
numeric_wrapper& operator=(numeric_wrapper&&) noexcept = default;
numeric_wrapper& operator=(numeric_wrapper&&) = default;
numeric_wrapper& operator=(const numeric_wrapper&) = default;
~numeric_wrapper() = default;
@@ -1644,7 +1633,7 @@ using int8 = numeric_wrapper<int8_t>;
using uint8 = numeric_wrapper<uint8_t>;
template <typename T>
[[nodiscard]] std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret);
@@ -1656,9 +1645,8 @@ template <typename T>
}
template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<numeric_wrapper, T>,
std::optional<T>>
to_num(const char* const begin, const char* const end) {
std::enable_if_t<is_instance_of_v<numeric_wrapper, T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret.value);
@@ -1680,8 +1668,7 @@ struct unsupported_type {
} /* namespace errors */
template <typename T>
[[nodiscard]] std::enable_if_t<!std::is_integral_v<T> &&
!std::is_floating_point_v<T> &&
std::enable_if_t<!std::is_integral_v<T> && !std::is_floating_point_v<T> &&
!is_instance_of_v<std::optional, T> &&
!is_instance_of_v<std::variant, T> &&
!is_instance_of_v<numeric_wrapper, T>,
@@ -1693,8 +1680,7 @@ extract(const char*, const char*, T&) {
}
template <typename T>
[[nodiscard]] std::enable_if_t<std::is_integral_v<T> ||
std::is_floating_point_v<T> ||
std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T> ||
is_instance_of_v<numeric_wrapper, T>,
bool>
extract(const char* begin, const char* end, T& value) {
@@ -1707,8 +1693,8 @@ extract(const char* begin, const char* end, T& value) {
}
template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::optional, T>, bool>
extract(const char* begin, const char* end, T& value) {
std::enable_if_t<is_instance_of_v<std::optional, T>, bool> extract(
const char* begin, const char* end, T& value) {
typename T::value_type raw_value;
if (extract(begin, end, raw_value)) {
value = raw_value;
@@ -1719,8 +1705,7 @@ extract(const char* begin, const char* end, T& value) {
}
template <typename T, size_t I>
[[nodiscard]] bool extract_variant(const char* begin, const char* end,
T& value) {
bool extract_variant(const char* begin, const char* end, T& value) {
using IthType = std::variant_alternative_t<I, std::decay_t<T>>;
IthType ithValue;
if (extract<IthType>(begin, end, ithValue)) {
@@ -1733,7 +1718,7 @@ template <typename T, size_t I>
}
template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
const char* begin, const char* end, T& value) {
return extract_variant<T, 0>(begin, end, value);
}
@@ -1743,8 +1728,7 @@ template <typename T>
////////////////
template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end,
bool& value) {
inline bool extract(const char* begin, const char* end, bool& value) {
if (end == begin + 1) {
if (*begin == '1') {
value = true;
@@ -1756,7 +1740,7 @@ template <>
} else {
constexpr static auto true_size = 4;
constexpr static auto false_size = 5;
const size_t size = end - begin;
size_t size = end - begin;
if (size == true_size && std::strncmp(begin, "true", size) == 0) {
value = true;
} else if (size == false_size &&
@@ -1771,21 +1755,19 @@ template <>
}
template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end,
char& value) {
inline bool extract(const char* begin, const char* end, char& value) {
value = *begin;
return (end == begin + 1);
}
template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end,
std::string& value) {
inline bool extract(const char* begin, const char* end, std::string& value) {
value = std::string{begin, end};
return true;
}
template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end,
inline bool extract(const char* begin, const char* end,
std::string_view& value) {
value = std::string_view{begin, static_cast<size_t>(end - begin)};
return true;
@@ -1894,19 +1876,19 @@ public:
// parses line with given delimiter, returns a 'T' object created with
// extracted values of type 'Ts'
template <typename T, typename... Ts>
[[nodiscard]] T convert_object(
line_ptr_type line, const std::string& delim = default_delimiter) {
T convert_object(line_ptr_type line,
const std::string& delim = default_delimiter) {
return to_object<T>(convert<Ts...>(line, delim));
}
// parses line with given delimiter, returns tuple of objects with
// extracted values of type 'Ts'
template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> convert(
no_void_validator_tup_t<Ts...> convert(
line_ptr_type line, const std::string& delim = default_delimiter) {
split(line, delim);
if (splitter_.valid()) {
return convert<Ts...>(splitter_.get_split_data());
return convert<Ts...>(splitter_.split_data_);
} else {
handle_error_bad_split();
return {};
@@ -1915,13 +1897,13 @@ public:
// parses already split line, returns 'T' object with extracted values
template <typename T, typename... Ts>
[[nodiscard]] T convert_object(const split_data& elems) {
T convert_object(const split_data& elems) {
return to_object<T>(convert<Ts...>(elems));
}
// same as above, but uses cached split line
template <typename T, typename... Ts>
[[nodiscard]] T convert_object() {
T convert_object() {
return to_object<T>(convert<Ts...>());
}
@@ -1930,8 +1912,7 @@ public:
// one argument is given which is a class which has a tied
// method which returns a tuple, returns that type
template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert(
const split_data& elems) {
no_void_validator_tup_t<T, Ts...> convert(const split_data& elems) {
if constexpr (sizeof...(Ts) == 0 && is_instance_of_v<std::tuple, T>) {
return convert_impl(elems, static_cast<T*>(nullptr));
} else if constexpr (tied_class_v<T, Ts...>) {
@@ -1947,11 +1928,11 @@ public:
// same as above, but uses cached split line
template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert() {
return convert<T, Ts...>(splitter_.get_split_data());
no_void_validator_tup_t<T, Ts...> convert() {
return convert<T, Ts...>(splitter_.split_data_);
}
[[nodiscard]] bool valid() const {
bool valid() const {
if constexpr (string_error) {
return error_.empty();
} else if constexpr (throw_on_error) {
@@ -1961,12 +1942,12 @@ public:
}
}
[[nodiscard]] const std::string& error_msg() const {
const std::string& error_msg() const {
assert_string_error_defined<string_error>();
return error_;
}
[[nodiscard]] bool unterminated_quote() const {
bool unterminated_quote() const {
return splitter_.unterminated_quote();
}
@@ -1974,9 +1955,9 @@ public:
// contain the beginnings and the ends of each column of the string
const split_data& split(line_ptr_type line,
const std::string& delim = default_delimiter) {
splitter_.clear_split_data();
splitter_.split_data_.clear();
if (line[0] == '\0') {
return splitter_.get_split_data();
return splitter_.split_data_;
}
return splitter_.split(line, delim);
@@ -1992,7 +1973,7 @@ private:
return splitter_.resplit(new_line, new_size, delim);
}
[[nodiscard]] size_t size_shifted() {
size_t size_shifted() {
return splitter_.size_shifted();
}
@@ -2008,8 +1989,7 @@ private:
}
}
[[nodiscard]] std::string error_sufix(const string_range msg,
size_t pos) const {
std::string error_sufix(const string_range msg, size_t pos) const {
constexpr static auto reserve_size = 32;
std::string error;
error.reserve(reserve_size);
@@ -2137,8 +2117,7 @@ private:
////////////////
template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> convert_impl(
const split_data& elems) {
no_void_validator_tup_t<Ts...> convert_impl(const split_data& elems) {
clear_error();
if (!splitter_.valid()) {
@@ -2169,7 +2148,7 @@ private:
}
template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<std::tuple<Ts...>> convert_impl(
no_void_validator_tup_t<std::tuple<Ts...>> convert_impl(
const split_data& elems, const std::tuple<Ts...>*) {
return convert_impl<Ts...>(elems);
}
@@ -2178,11 +2157,11 @@ private:
// column mapping
////////////////
[[nodiscard]] bool columns_mapped() const {
bool columns_mapped() const {
return !column_mappings_.empty();
}
[[nodiscard]] size_t column_position(size_t tuple_position) const {
size_t column_position(size_t tuple_position) const {
if (!columns_mapped()) {
return tuple_position;
}
@@ -2213,7 +2192,7 @@ private:
}
if constexpr (std::is_same_v<T, std::string>) {
static_cast<void>(extract(msg.first, msg.second, dst));
extract(msg.first, msg.second, dst);
return;
}
@@ -2259,8 +2238,7 @@ private:
}
template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> extract_tuple(
const split_data& elems) {
no_void_validator_tup_t<Ts...> extract_tuple(const split_data& elems) {
static_assert(!all_of_v<std::is_void, Ts...>,
"at least one parameter must be non void");
no_void_validator_tup_t<Ts...> ret{};
@@ -2341,15 +2319,15 @@ public:
}
}
parser(parser&& other) noexcept = default;
parser& operator=(parser&& other) noexcept = default;
parser(parser&& other) = default;
parser& operator=(parser&& other) = default;
~parser() = default;
parser() = delete;
parser(const parser& other) = delete;
parser& operator=(const parser& other) = delete;
[[nodiscard]] bool valid() const {
bool valid() const {
if constexpr (string_error) {
return error_.empty();
} else if constexpr (throw_on_error) {
@@ -2359,12 +2337,12 @@ public:
}
}
[[nodiscard]] const std::string& error_msg() const {
const std::string& error_msg() const {
assert_string_error_defined<string_error>();
return error_;
}
[[nodiscard]] bool eof() const {
bool eof() const {
return eof_;
}
@@ -2373,21 +2351,23 @@ public:
}
template <typename T, typename... Ts>
[[nodiscard]] T get_object() {
T get_object() {
return to_object<T>(get_next<Ts...>());
}
[[nodiscard]] size_t line() const {
size_t line() const {
return reader_.line_number_ > 0 ? reader_.line_number_ - 1
: reader_.line_number_;
}
[[nodiscard]] size_t position() const {
size_t position() const {
return reader_.chars_read_;
}
template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> get_next() {
no_void_validator_tup_t<T, Ts...> get_next() {
std::optional<std::string> error;
if (!eof_) {
if constexpr (throw_on_error) {
try {
@@ -2436,12 +2416,12 @@ public:
return value;
}
[[nodiscard]] std::string raw_header() const {
std::string raw_header() const {
assert_ignore_header_not_defined();
return raw_header_;
}
[[nodiscard]] std::vector<std::string> header() {
std::vector<std::string> header() {
assert_ignore_header_not_defined();
clear_error();
@@ -2453,14 +2433,14 @@ public:
}
std::vector<std::string> split_header;
for (const auto& [begin, end] : splitter.get_split_data()) {
for (const auto& [begin, end] : splitter.split_data_) {
split_header.emplace_back(begin, end);
}
return split_header;
}
[[nodiscard]] bool field_exists(const std::string& field) {
bool field_exists(const std::string& field) {
assert_ignore_header_not_defined();
clear_error();
@@ -2539,17 +2519,17 @@ public:
}
iterator(const iterator& other) = default;
iterator(iterator&& other) noexcept = default;
iterator(iterator&& other) = default;
~iterator() = default;
iterator& operator=(const iterator& other) = delete;
iterator& operator=(iterator&& other) noexcept = delete;
iterator& operator=(iterator&& other) = delete;
[[nodiscard]] value& operator*() {
value& operator*() {
return value_;
}
[[nodiscard]] value* operator->() {
value* operator->() {
return &value_;
}
@@ -2574,15 +2554,13 @@ public:
return result;
}
[[nodiscard]] friend bool operator==(const iterator& lhs,
const iterator& rhs) {
friend bool operator==(const iterator& lhs, const iterator& rhs) {
return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) ||
(lhs.parser_ == rhs.parser_ &&
&lhs.value_ == &rhs.value_);
}
[[nodiscard]] friend bool operator!=(const iterator& lhs,
const iterator& rhs) {
friend bool operator!=(const iterator& lhs, const iterator& rhs) {
return !(lhs == rhs);
}
@@ -2594,11 +2572,11 @@ public:
iterable(parser<Options...>* parser) : parser_{parser} {
}
[[nodiscard]] iterator begin() {
iterator begin() {
return ++iterator{parser_};
}
[[nodiscard]] iterator end() {
iterator end() {
return iterator{};
}
@@ -2607,12 +2585,12 @@ public:
};
template <typename... Ts>
[[nodiscard]] auto iterate() {
auto iterate() {
return iterable<false, Ts...>{this};
}
template <typename... Ts>
[[nodiscard]] auto iterate_object() {
auto iterate_object() {
return iterable<true, Ts...>{this};
}
@@ -2650,7 +2628,7 @@ public:
return composite_with(std::move(value));
}
[[nodiscard]] std::tuple<Ts...> values() {
std::tuple<Ts...> values() {
return values_;
}
@@ -2673,7 +2651,7 @@ public:
private:
template <typename T>
[[nodiscard]] composite<Ts..., T> composite_with(T&& new_value) {
composite<Ts..., T> composite_with(T&& new_value) {
auto merged_values =
std::tuple_cat(std::move(values_),
std::tuple<T>{parser_.valid()
@@ -2703,7 +2681,7 @@ public:
}
template <typename U, typename... Us>
[[nodiscard]] no_void_validator_tup_t<U, Us...> try_same() {
no_void_validator_tup_t<U, Us...> try_same() {
parser_.clear_error();
auto value =
parser_.reader_.converter_.template convert<U, Us...>();
@@ -2724,8 +2702,8 @@ public:
// tries to convert a line and returns a composite which is
// able to try additional conversions in case of failure
template <typename... Ts, typename Fun = none>
[[nodiscard]] composite<std::optional<no_void_validator_tup_t<Ts...>>>
try_next(Fun&& fun = none{}) {
composite<std::optional<no_void_validator_tup_t<Ts...>>> try_next(
Fun&& fun = none{}) {
assert_throw_on_error_not_defined<throw_on_error>();
using Ret = no_void_validator_tup_t<Ts...>;
return try_invoke_and_make_composite<
@@ -2735,7 +2713,7 @@ public:
// identical to try_next but returns composite with object instead of a
// tuple
template <typename T, typename... Ts, typename Fun = none>
[[nodiscard]] composite<std::optional<T>> try_object(Fun&& fun = none{}) {
composite<std::optional<T>> try_object(Fun&& fun = none{}) {
assert_throw_on_error_not_defined<throw_on_error>();
return try_invoke_and_make_composite<
std::optional<T>>(get_object<T, Ts...>(), std::forward<Fun>(fun));
@@ -2786,8 +2764,7 @@ private:
}
template <typename T, typename Fun = none>
[[nodiscard]] composite<T> try_invoke_and_make_composite(T&& value,
Fun&& fun) {
composite<T> try_invoke_and_make_composite(T&& value, Fun&& fun) {
if (valid()) {
try_invoke(*value, std::forward<Fun>(fun));
}
@@ -2803,8 +2780,11 @@ private:
"cannot use this method when 'ignore_header' is defined");
}
[[nodiscard]] bool strict_split(header_splitter& splitter,
std::string& header) {
bool strict_split(header_splitter& splitter, std::string& header) {
if (header.empty()) {
return false;
}
if constexpr (throw_on_error) {
try {
splitter.split(header.data(), reader_.delim_);
@@ -2830,8 +2810,13 @@ private:
return;
}
for (const auto& [begin, end] : splitter.get_split_data()) {
for (const auto& [begin, end] : splitter.split_data_) {
std::string field{begin, end};
if (field.empty()) {
handle_error_duplicate_header_field(field);
header_.clear();
return;
}
if (std::find(header_.begin(), header_.end(), field) !=
header_.end()) {
handle_error_duplicate_header_field(field);
@@ -2842,7 +2827,7 @@ private:
}
}
[[nodiscard]] std::optional<size_t> header_index(const std::string& field) {
std::optional<size_t> header_index(const std::string& field) {
auto it = std::find(header_.begin(), header_.end(), field);
if (it == header_.end()) {
@@ -3104,7 +3089,7 @@ private:
std::free(helper_buffer_);
if (file_) {
std::ignore = std::fclose(file_);
std::fclose(file_);
}
}
@@ -3113,7 +3098,7 @@ private:
reader& operator=(const reader& other) = delete;
// read next line each time in order to set eof_
[[nodiscard]] bool read_next() {
bool read_next() {
next_line_converter_.clear_error();
size_t size = 0;
while (size == 0) {
@@ -3205,7 +3190,7 @@ private:
std::swap(converter_, next_line_converter_);
}
[[nodiscard]] bool multiline_limit_reached(size_t& limit) {
bool multiline_limit_reached(size_t& limit) {
if constexpr (multiline::size > 0) {
if (limit++ >= multiline::size) {
next_line_converter_.handle_error_multiline_limit_reached();
@@ -3215,7 +3200,7 @@ private:
return false;
}
[[nodiscard]] bool escaped_eol(size_t size) {
bool escaped_eol(size_t size) {
const char* curr = nullptr;
for (curr = next_line_buffer_ + size - 1;
curr >= next_line_buffer_ &&
@@ -3225,7 +3210,7 @@ private:
return (next_line_buffer_ - curr + size) % 2 == 0;
}
[[nodiscard]] bool unterminated_quote() {
bool unterminated_quote() {
return next_line_converter_.unterminated_quote();
}
@@ -3240,7 +3225,7 @@ private:
}
}
[[nodiscard]] size_t remove_eol(char*& buffer, size_t ssize) {
size_t remove_eol(char*& buffer, size_t ssize) {
if (buffer[ssize - 1] != '\n') {
crlf_ = false;
return ssize;
@@ -3270,8 +3255,7 @@ private:
first_size += second_size;
}
[[nodiscard]] bool append_next_line_to_buffer(char*& buffer,
size_t& line_size,
bool append_next_line_to_buffer(char*& buffer, size_t& line_size,
size_t buffer_size) {
undo_remove_eol(buffer, line_size, buffer_size);
@@ -3291,7 +3275,7 @@ private:
return true;
}
[[nodiscard]] std::string get_buffer() {
std::string get_buffer() {
return std::string{next_line_buffer_, next_line_size_};
}

View File

@@ -33,11 +33,10 @@ set(DOCTEST "${FETCHCONTENT_BASE_DIR}/doctest-src")
enable_testing()
foreach(name IN ITEMS test_splitter test_parser1_1 test_parser1_2
test_parser1_3 test_parser1_4 test_parser1_5
test_converter test_extractions test_parser2_1
test_parser2_2 test_parser2_3 test_parser2_4
test_parser2_5 test_parser2_6
test_extractions_without_fast_float)
test_parser1_3 test_parser1_4 test_converter
test_extractions test_parser2_1 test_parser2_2
test_parser2_3 test_parser2_4 test_parser2_5
test_parser2_6 test_extractions_without_fast_float)
add_executable("${name}" "${name}.cpp")
target_link_libraries("${name}" PRIVATE ssp::ssp fast_float
doctest::doctest)

View File

@@ -6,7 +6,6 @@ tests = [
'parser1_2',
'parser1_3',
'parser1_4',
'parser1_5',
'splitter',
'converter',
'extractions',

View File

@@ -1,4 +1,5 @@
#include "test_helpers.hpp"
#include <algorithm>
#include <ss/converter.hpp>
TEST_CASE("converter test split") {
@@ -10,8 +11,7 @@ TEST_CASE("converter test split") {
{" x x x x | x ", {" x x x x ", " x "}, "|"},
{"a::b::c::d", {"a", "b", "c", "d"}, "::"},
{"x\t-\ty", {"x", "y"}, "\t-\t"},
{"x", {"x"}, ","}}
// clang-format on
{"x", {"x"}, ","}} // clang-format on
) {
auto split = c.split(s, delim);
CHECK_EQ(split.size(), expected.size());
@@ -278,37 +278,36 @@ TEST_CASE_TEMPLATE("converter test valid conversions with exceptions", T, int,
TEST_CASE_TEMPLATE("converter test invalid conversions", T, int, ss::uint8) {
ss::converter c;
std::ignore = c.convert<T>("");
c.convert<T>("");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T>("1", "");
c.convert<T>("1", "");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T>("10", "");
c.convert<T>("10", "");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T, void>("");
c.convert<T, void>("");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T, void>(",junk");
c.convert<T, void>(",junk");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<void, T>("junk,");
c.convert<void, T>("junk,");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T>("x");
c.convert<T>("x");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T, void>("x");
c.convert<T, void>("x");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T, void>("x,junk");
c.convert<T, void>("x,junk");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<void, T>("junk,x");
c.convert<void, T>("junk,x");
REQUIRE_FALSE(c.valid());
std::ignore =
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";");
REQUIRE_FALSE(c.valid());
}
@@ -317,36 +316,34 @@ TEST_CASE_TEMPLATE("converter test invalid conversions with exceptions", T, int,
ss::uint8) {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<T>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("1", ""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("10", ""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>(",junk"));
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,"));
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("x"));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x"));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x,junk"));
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,x"));
REQUIRE_EXCEPTION(c.convert<T>(""));
REQUIRE_EXCEPTION(c.convert<T>("1", ""));
REQUIRE_EXCEPTION(c.convert<T>("10", ""));
REQUIRE_EXCEPTION(c.convert<T, void>(""));
REQUIRE_EXCEPTION(c.convert<T, void>(",junk"));
REQUIRE_EXCEPTION(c.convert<void, T>("junk,"));
REQUIRE_EXCEPTION(c.convert<T>("x"));
REQUIRE_EXCEPTION(c.convert<T, void>("x"));
REQUIRE_EXCEPTION(c.convert<T, void>("x,junk"));
REQUIRE_EXCEPTION(c.convert<void, T>("junk,x"));
REQUIRE_EXCEPTION(
std::ignore =
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6",
";"));
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";"));
}
TEST_CASE_TEMPLATE("converter test ss:ax restriction (all except)", T, int,
ss::uint8) {
ss::converter c;
std::ignore = c.convert<ss::ax<T, 0>>("0");
c.convert<ss::ax<T, 0>>("0");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1");
c.convert<ss::ax<T, 0, 1, 2>>("1");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1");
c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ax<T, 1>, char>("1,c");
c.convert<ss::ax<T, 1>, char>("1,c");
REQUIRE_FALSE(c.valid());
{
T tup = c.convert<ss::ax<T, 1>>("3");
@@ -370,11 +367,10 @@ TEST_CASE_TEMPLATE(
ss::uint8) {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 0>>("0"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1"));
REQUIRE_EXCEPTION(
std::ignore = c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 1>, char>("1,c"));
REQUIRE_EXCEPTION(c.convert<ss::ax<T, 0>>("0"));
REQUIRE_EXCEPTION(c.convert<ss::ax<T, 0, 1, 2>>("1"));
REQUIRE_EXCEPTION(c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1"));
REQUIRE_EXCEPTION(c.convert<ss::ax<T, 1>, char>("1,c"));
try {
{
@@ -397,13 +393,13 @@ TEST_CASE_TEMPLATE(
TEST_CASE("converter test ss:nx restriction (none except)") {
ss::converter c;
std::ignore = c.convert<ss::nx<int, 1>>("3");
c.convert<ss::nx<int, 1>>("3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<char, ss::nx<int, 1, 2, 69>>("c,3");
c.convert<char, ss::nx<int, 1, 2, 69>>("c,3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::nx<int, 1>, char>("3,c");
c.convert<ss::nx<int, 1>, char>("3,c");
REQUIRE_FALSE(c.valid());
{
@@ -431,10 +427,9 @@ TEST_CASE("converter test ss:nx restriction (none except)") {
TEST_CASE("converter test ss:nx restriction (none except) with exceptions") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::nx<int, 1>>("3"));
REQUIRE_EXCEPTION(std::ignore =
c.convert<char, ss::nx<int, 1, 2, 69>>("c,3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::nx<int, 1>, char>("3,c"));
REQUIRE_EXCEPTION(c.convert<ss::nx<int, 1>>("3"));
REQUIRE_EXCEPTION(c.convert<char, ss::nx<int, 1, 2, 69>>("c,3"));
REQUIRE_EXCEPTION(c.convert<ss::nx<int, 1>, char>("3,c"));
try {
{
@@ -466,13 +461,13 @@ TEST_CASE_TEMPLATE("converter test ss:ir restriction (in range)", T, int,
ss::uint8) {
ss::converter c;
std::ignore = c.convert<ss::ir<T, 0, 2>>("3");
c.convert<ss::ir<T, 0, 2>>("3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3");
c.convert<char, ss::ir<T, 4, 69>>("c,3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ir<T, 1, 2>, char>("3,c");
c.convert<ss::ir<T, 1, 2>, char>("3,c");
REQUIRE_FALSE(c.valid());
{
@@ -502,9 +497,9 @@ TEST_CASE_TEMPLATE(
ss::uint8) {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ir<T, 0, 2>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ir<T, 1, 2>, char>("3,c"));
REQUIRE_EXCEPTION(c.convert<ss::ir<T, 0, 2>>("3"));
REQUIRE_EXCEPTION(c.convert<char, ss::ir<T, 4, 69>>("c,3"));
REQUIRE_EXCEPTION(c.convert<ss::ir<T, 1, 2>, char>("3,c"));
try {
{
@@ -535,16 +530,16 @@ TEST_CASE_TEMPLATE(
TEST_CASE("converter test ss:oor restriction (out of range)") {
ss::converter c;
std::ignore = c.convert<ss::oor<int, 1, 5>>("3");
c.convert<ss::oor<int, 1, 5>>("3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::oor<int, 0, 2>>("2");
c.convert<ss::oor<int, 0, 2>>("2");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk");
c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::oor<int, 1, 20>, char>("1,c");
c.convert<ss::oor<int, 1, 20>, char>("1,c");
REQUIRE_FALSE(c.valid());
{
@@ -569,12 +564,10 @@ TEST_CASE("converter test ss:oor restriction (out of range)") {
TEST_CASE("converter test ss:oor restriction (out of range) with exceptions") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::oor<int, 1, 5>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::oor<int, 0, 2>>("2"));
REQUIRE_EXCEPTION(
std::ignore = c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk"));
REQUIRE_EXCEPTION(std::ignore =
c.convert<ss::oor<int, 1, 20>, char>("1,c"));
REQUIRE_EXCEPTION(c.convert<ss::oor<int, 1, 5>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::oor<int, 0, 2>>("2"));
REQUIRE_EXCEPTION(c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk"));
REQUIRE_EXCEPTION(c.convert<ss::oor<int, 1, 20>, char>("1,c"));
try {
{
@@ -615,19 +608,19 @@ inline bool ss::extract(const char* begin, const char* end,
TEST_CASE("converter test ss:ne restriction (not empty)") {
ss::converter c;
std::ignore = c.convert<ss::ne<std::string>>("");
c.convert<ss::ne<std::string>>("");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<int, ss::ne<std::string>>("3,");
c.convert<int, ss::ne<std::string>>("3,");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ne<std::string>, int>(",3");
c.convert<ss::ne<std::string>, int>(",3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<void, ss::ne<std::string>, int>("junk,,3");
c.convert<void, ss::ne<std::string>, int>("junk,,3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ne<std::vector<int>>>("");
c.convert<ss::ne<std::vector<int>>>("");
REQUIRE_FALSE(c.valid());
{
@@ -650,12 +643,11 @@ TEST_CASE("converter test ss:ne restriction (not empty)") {
TEST_CASE("converter test ss:ne restriction (not empty) with exceptions") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::string>>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<int, ss::ne<std::string>>("3,"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::string>, int>(",3"));
REQUIRE_EXCEPTION(std::ignore =
c.convert<void, ss::ne<std::string>, int>("junk,,3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::vector<int>>>(""));
REQUIRE_EXCEPTION(c.convert<ss::ne<std::string>>(""));
REQUIRE_EXCEPTION(c.convert<int, ss::ne<std::string>>("3,"));
REQUIRE_EXCEPTION(c.convert<ss::ne<std::string>, int>(",3"));
REQUIRE_EXCEPTION(c.convert<void, ss::ne<std::string>, int>("junk,,3"));
REQUIRE_EXCEPTION(c.convert<ss::ne<std::vector<int>>>(""));
try {
{
@@ -683,22 +675,22 @@ TEST_CASE(
"converter test ss:lt ss::lte ss::gt ss::gte restriction (in range)") {
ss::converter c;
std::ignore = c.convert<ss::lt<int, 3>>("3");
c.convert<ss::lt<int, 3>>("3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::lt<int, 2>>("3");
c.convert<ss::lt<int, 2>>("3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::gt<int, 3>>("3");
c.convert<ss::gt<int, 3>>("3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::gt<int, 4>>("3");
c.convert<ss::gt<int, 4>>("3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::lte<int, 2>>("3");
c.convert<ss::lte<int, 2>>("3");
REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::gte<int, 4>>("3");
c.convert<ss::gte<int, 4>>("3");
REQUIRE_FALSE(c.valid());
{
@@ -742,12 +734,12 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) "
"with exception") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 3>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 2>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 3>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 4>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lte<int, 2>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gte<int, 4>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::lt<int, 3>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::lt<int, 2>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::gt<int, 3>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::gt<int, 4>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::lte<int, 2>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::gte<int, 4>>("3"));
try {
{
@@ -792,14 +784,14 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) "
TEST_CASE("converter test error mode") {
ss::converter<ss::string_error> c;
std::ignore = c.convert<int>("junk");
c.convert<int>("junk");
CHECK_FALSE(c.valid());
CHECK_FALSE(c.error_msg().empty());
}
TEST_CASE("converter test throw on error mode") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<int>("junk"));
REQUIRE_EXCEPTION(c.convert<int>("junk"));
}
TEST_CASE("converter test converter with quotes spacing and escaping") {
@@ -916,7 +908,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// mismatched quote
std::ignore = c.convert<std::string, std::string, double, char>(
c.convert<std::string, std::string, double, char>(
buff(R"( "just , some , "12.3","a" )"));
CHECK_FALSE(c.valid());
CHECK_FALSE(c.unterminated_quote());
@@ -925,7 +917,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// unterminated quote
std::ignore = c.convert<std::string, std::string, double, std::string>(
c.convert<std::string, std::string, double, std::string>(
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)"));
CHECK_FALSE(c.valid());
CHECK(c.unterminated_quote());
@@ -934,7 +926,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// unterminated escape
std::ignore = c.convert<std::string, std::string, double, std::string>(
c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,strings\)"));
CHECK_FALSE(c.valid());
CHECK_FALSE(c.unterminated_quote());
@@ -943,7 +935,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// unterminated escape while quoting
std::ignore = c.convert<std::string, std::string, double, std::string>(
c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\)"));
CHECK_FALSE(c.valid());
CHECK_FALSE(c.unterminated_quote());
@@ -952,7 +944,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// unterminated escaped quote
std::ignore = c.convert<std::string, std::string, double, std::string>(
c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\")"));
CHECK_FALSE(c.valid());
CHECK(c.unterminated_quote());
@@ -966,32 +958,27 @@ TEST_CASE("converter test invalid split conversions with exceptions") {
c;
// mismatched quote
REQUIRE_EXCEPTION(std::ignore =
c.convert<std::string, std::string, double, char>(
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, char>(
buff(R"( "just , some , "12.3","a" )")));
CHECK_FALSE(c.unterminated_quote());
// unterminated quote
REQUIRE_EXCEPTION(
std::ignore = c.convert<std::string, std::string, double, std::string>(
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)")));
CHECK(c.unterminated_quote());
// unterminated escape
REQUIRE_EXCEPTION(
std::ignore = c.convert<std::string, std::string, double, std::string>(
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,strings\)")));
CHECK_FALSE(c.unterminated_quote());
// unterminated escape while quoting
REQUIRE_EXCEPTION(
std::ignore = c.convert<std::string, std::string, double, std::string>(
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\)")));
CHECK_FALSE(c.unterminated_quote());
// unterminated escaped quote
REQUIRE_EXCEPTION(
std::ignore = c.convert<std::string, std::string, double, std::string>(
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\")")));
CHECK(c.unterminated_quote());
}

View File

@@ -1,4 +1,5 @@
#include "test_helpers.hpp"
#include <algorithm>
#define SSP_DISABLE_FAST_FLOAT
#include <ss/extract.hpp>

View File

@@ -212,7 +212,6 @@ template <typename T>
}
};
// Evade small string optimization
out.reserve(sizeof(out) + 1);
copy_if_whitespaces();

View File

@@ -12,8 +12,7 @@
#include <unordered_set>
namespace {
#ifdef _WIN32
void replace_all(std::string& s, const std::string& from,
[[maybe_unused]] void replace_all(std::string& s, const std::string& from,
const std::string& to) {
if (from.empty()) return;
size_t start_pos = 0;
@@ -22,7 +21,6 @@ void replace_all(std::string& s, const std::string& from,
start_pos += to.length();
}
}
#endif
template <typename... Ts>
void expect_error_on_command(ss::parser<Ts...>& p,
@@ -58,7 +56,7 @@ struct X {
double d;
std::string s;
[[nodiscard]] std::string to_string() const {
std::string to_string() const {
if (s == empty) {
return "";
}
@@ -69,15 +67,14 @@ struct X {
.append(delim)
.append(s);
}
[[nodiscard]] auto tied() const {
auto tied() const {
return std::tie(i, d, s);
}
};
template <typename T>
[[nodiscard]] std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==(
const T& lhs, const T& rhs) {
std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==(const T& lhs,
const T& rhs) {
return lhs.tied() == rhs.tied();
}

View File

@@ -57,7 +57,7 @@ struct Y {
.append(s3);
}
[[nodiscard]] auto tied() const {
auto tied() const {
return std::tie(s1, s2, s3);
}
};
@@ -115,8 +115,7 @@ TEST_CASE_TEMPLATE("test line method", T, ParserOptionCombinations) {
CHECK_EQ(p.line(), expected_line);
while (!p.eof()) {
std::ignore =
p.template get_next<std::string, std::string, std::string>();
auto _ = p.template get_next<std::string, std::string, std::string>();
++expected_line;
CHECK_EQ(p.line(), expected_line);
}

View File

@@ -51,14 +51,12 @@ TEST_CASE_TEMPLATE("test moving of parsed composite values", T,
// to compile is enough
return;
auto [p, _] = make_parser<buffer_mode, ErrorMode>("", "");
std::ignore =
p.template try_next<my_string, my_string, my_string>()
.template or_else<my_string, my_string, my_string, my_string>(
[](auto&&) {})
.template or_else<my_string>([](auto&) {})
.template or_else<xyz>([](auto&&) {})
.template or_object<xyz, my_string, my_string, my_string>(
[](auto&&) {})
.template or_object<xyz, my_string, my_string, my_string>([](auto&&) {})
.template or_else<std::tuple<my_string, my_string, my_string>>(
[](auto&, auto&, auto&) {});
}
@@ -75,7 +73,7 @@ TEST_CASE_TEMPLATE("parser test string error mode", BufferMode, std::true_type,
auto [p, _] = make_parser<BufferMode::value, ss::string_error>(f.name, ",");
REQUIRE_FALSE(p.eof());
std::ignore = p.template get_next<int>();
p.template get_next<int>();
CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty());
}
@@ -94,7 +92,7 @@ TEST_CASE_TEMPLATE("parser throw on error mode", BufferMode, std::true_type,
REQUIRE_FALSE(p.eof());
try {
std::ignore = p.template get_next<int>();
p.template get_next<int>();
FAIL("Expected exception...");
} catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty());
@@ -150,7 +148,6 @@ TEST_CASE_TEMPLATE("test quote multiline", T, ParserOptionCombinations) {
make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name, ",");
while (!p.eof()) {
auto command = [&p_no_multiline = p_no_multiline] {
std::ignore =
p_no_multiline.template get_next<int, double, std::string>();
};
expect_error_on_command(p_no_multiline, command);

View File

@@ -83,7 +83,7 @@ void test_unterminated_line(const std::vector<std::string>& lines,
size_t line = 0;
while (!p.eof()) {
auto command = [&p = p] {
std::ignore = p.template get_next<int, double, std::string>();
p.template get_next<int, double, std::string>();
};
if (line == bad_line) {

View File

@@ -178,8 +178,7 @@ void test_invalid_fields(const std::vector<std::string>& lines,
auto check_header = [&lines](auto& p) {
if (lines.empty()) {
CHECK_EQ(p.header().size(), 1);
CHECK_EQ(p.header().at(0), "");
CHECK(p.header().empty());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
} else {
CHECK_EQ(lines[0], merge_header(p.header()));
@@ -229,7 +228,7 @@ void test_invalid_fields(const std::vector<std::string>& lines,
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p, &fields = fields] {
p.use_fields(fields.at(0));
std::ignore = p.template get_next<std::string, std::string>();
p.template get_next<std::string, std::string>();
};
check_header(p);
@@ -264,7 +263,7 @@ void test_invalid_fields(const std::vector<std::string>& lines,
}
}
TEST_CASE_TEMPLATE("test invalid header fields usage", T,
TEST_CASE_TEMPLATE("test invalid fheader fields usage", T,
ParserOptionCombinations) {
test_invalid_fields<T>({}, {});
@@ -398,3 +397,178 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
CHECK_EQ(merge_header(p.header()), p.raw_header());
}
}
TEST_CASE_TEMPLATE("test invalid header", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"invalid_header"};
// Empty header
{
std::ofstream out{f.name};
out << "" << std::endl;
out << "1" << std::endl;
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK(p.header().empty());
CHECK_EQ(merge_header(p.header()), p.raw_header());
CHECK(p.valid());
}
// Unterminated quote in header
{
std::ofstream out{f.name};
out << "\"Int" << std::endl;
out << "1" << std::endl;
}
{
auto [p, _] =
make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name);
auto command = [&p = p] { p.header(); };
expect_error_on_command(p, command);
CHECK_EQ(p.raw_header(), "\"Int");
}
{
auto [p, _] =
make_parser<buffer_mode, ErrorMode, ss::quote<'"'>, ss::multiline>(
f.name);
auto command = [&p = p] { p.header(); };
expect_error_on_command(p, command);
CHECK_EQ(p.raw_header(), "\"Int");
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::quote<'"'>,
ss::escape<'\\'>, ss::multiline>(f.name);
auto command = [&p = p] { p.header(); };
expect_error_on_command(p, command);
CHECK_EQ(p.raw_header(), "\"Int");
}
// Unterminated escape in header
{
std::ofstream out{f.name};
out << "Int\\" << std::endl;
out << "1" << std::endl;
}
{
auto [p, _] =
make_parser<buffer_mode, ErrorMode, ss::escape<'\\'>>(f.name);
auto command = [&p = p] { p.header(); };
expect_error_on_command(p, command);
CHECK_EQ(p.raw_header(), "Int\\");
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::escape<'\\'>,
ss::multiline>(f.name);
auto command = [&p = p] { p.header(); };
expect_error_on_command(p, command);
CHECK_EQ(p.raw_header(), "Int\\");
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::escape<'\\'>,
ss::quote<'"'>, ss::multiline>(f.name);
auto command = [&p = p] { p.header(); };
expect_error_on_command(p, command);
CHECK_EQ(p.raw_header(), "Int\\");
}
}
template <typename T>
void test_ignore_empty(const std::vector<X>& data) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"ignore_empty"};
make_and_write(f.name, data);
std::vector<X> expected;
for (const auto& d : data) {
if (d.s != X::empty) {
expected.push_back(d);
}
}
{
auto [p, _] =
make_parser<buffer_mode, ErrorMode, ss::ignore_empty>(f.name, ",");
std::vector<X> i;
for (const auto& a : p.template iterate<X>()) {
i.push_back(a);
}
CHECK_EQ(i, expected);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
size_t n = 0;
while (!p.eof()) {
try {
++n;
const auto& a = p.template get_next<X>();
if (data.at(n - 1).s == X::empty) {
CHECK_FALSE(p.valid());
continue;
}
i.push_back(a);
} catch (...) {
CHECK_EQ(data.at(n - 1).s, X::empty);
}
}
CHECK_EQ(i, expected);
}
}
TEST_CASE_TEMPLATE("test various cases with empty lines", T,
ParserOptionCombinations) {
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>({{1, 2, X::empty},
{3, 4, X::empty},
{9, 10, X::empty},
{11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, "w"}});
test_ignore_empty<T>({{11, 12, X::empty}});
test_ignore_empty<T>({});
}

View File

@@ -1,301 +0,0 @@
#include "test_parser1.hpp"
TEST_CASE_TEMPLATE("test empty fields header", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"empty_fields_header"};
// Empty header
{
std::ofstream out{f.name};
out << "" << std::endl;
out << "1" << std::endl;
}
{
std::vector<std::string> expected_header = {""};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ("", p.raw_header());
CHECK(p.valid());
}
// All empty header fields
{
std::ofstream out{f.name};
out << ",," << std::endl;
out << "1,2,3" << std::endl;
}
{
std::vector<std::string> expected_header = {"", "", ""};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ(",,", p.raw_header());
CHECK(p.valid());
auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); };
expect_error_on_command(p, command1);
auto command2 = [&p = p] { p.use_fields("Int"); };
expect_error_on_command(p, command2);
}
// One empty field
const std::vector<std::string> valid_fields = {"Int0", "Int1", ""};
using svec = std::vector<std::string>;
const std::vector<std::vector<std::string>> valid_field_combinations =
{svec{"Int0"},
svec{"Int1"},
svec{""},
svec{"", "Int0"},
svec{"Int0", "Int1"},
svec{"Int1", ""},
svec{"Int0", "", "Int1"},
svec{"", "Int1", "Int0"}};
// Last header field empty
{
std::ofstream out{f.name};
out << "Int0,Int1," << std::endl;
out << "1,2,3" << std::endl;
}
{
std::vector<std::string> expected_header = {"Int0", "Int1", ""};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ("Int0,Int1,", p.raw_header());
CHECK(p.valid());
for (const auto& field : valid_fields) {
CHECK(p.field_exists(field));
CHECK(p.valid());
}
for (const auto& fields : valid_field_combinations) {
p.use_fields(fields);
CHECK(p.valid());
}
}
// First header field empty
{
std::ofstream out{f.name};
out << ",Int0,Int1" << std::endl;
out << "1,2,3" << std::endl;
}
{
std::vector<std::string> expected_header = {"", "Int0", "Int1"};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ(",Int0,Int1", p.raw_header());
CHECK(p.valid());
for (const auto& field : valid_fields) {
CHECK(p.field_exists(field));
CHECK(p.valid());
}
for (const auto& fields : valid_field_combinations) {
p.use_fields(fields);
CHECK(p.valid());
}
}
// Middle header field empty
{
std::ofstream out{f.name};
out << "Int0,,Int1" << std::endl;
out << "1,2,3" << std::endl;
}
{
std::vector<std::string> expected_header = {"Int0", "", "Int1"};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ("Int0,,Int1", p.raw_header());
CHECK(p.valid());
for (const auto& field : valid_fields) {
CHECK(p.field_exists(field));
CHECK(p.valid());
}
for (const auto& fields : valid_field_combinations) {
p.use_fields(fields);
CHECK(p.valid());
}
}
}
template <typename T, typename... Ts>
void test_unterminated_quote_header() {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"unterminated_quote_header"};
{
std::ofstream out{f.name};
out << "\"Int" << std::endl;
out << "1" << std::endl;
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode, Ts...>(f.name);
auto command0 = [&p = p] { std::ignore = p.header(); };
expect_error_on_command(p, command0);
CHECK_EQ(p.raw_header(), "\"Int");
auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); };
expect_error_on_command(p, command1);
auto command2 = [&p = p] { p.use_fields("Int"); };
expect_error_on_command(p, command2);
}
}
TEST_CASE_TEMPLATE("test unterminated quote header", T,
ParserOptionCombinations) {
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
test_unterminated_quote_header<T, quote>();
test_unterminated_quote_header<T, quote, ss::multiline>();
test_unterminated_quote_header<T, quote, escape>();
test_unterminated_quote_header<T, quote, escape, ss::multiline>();
}
template <typename T, typename... Ts>
void test_unterminated_escape_header() {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"unterminated_escape_header"};
// Unterminated escape in header
{
std::ofstream out{f.name};
out << "Int\\" << std::endl;
out << "1" << std::endl;
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode, Ts...>(f.name);
auto command0 = [&p = p] { std::ignore = p.header(); };
expect_error_on_command(p, command0);
CHECK_EQ(p.raw_header(), "Int\\");
auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); };
expect_error_on_command(p, command1);
auto command2 = [&p = p] { p.use_fields("Int"); };
expect_error_on_command(p, command2);
}
}
TEST_CASE_TEMPLATE("test unterminated escape header", T,
ParserOptionCombinations) {
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
test_unterminated_escape_header<T, escape>();
test_unterminated_escape_header<T, escape, ss::multiline>();
test_unterminated_escape_header<T, escape, quote>();
test_unterminated_escape_header<T, escape, quote, ss::multiline>();
}
template <typename T>
void test_ignore_empty(const std::vector<X>& data) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"ignore_empty"};
make_and_write(f.name, data);
std::vector<X> expected;
for (const auto& d : data) {
if (d.s != X::empty) {
expected.push_back(d);
}
}
{
auto [p, _] =
make_parser<buffer_mode, ErrorMode, ss::ignore_empty>(f.name, ",");
std::vector<X> i;
for (const auto& a : p.template iterate<X>()) {
i.push_back(a);
}
CHECK_EQ(i, expected);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
size_t n = 0;
while (!p.eof()) {
try {
++n;
const auto& a = p.template get_next<X>();
if (data.at(n - 1).s == X::empty) {
CHECK_FALSE(p.valid());
continue;
}
i.push_back(a);
} catch (...) {
CHECK_EQ(data.at(n - 1).s, X::empty);
}
}
CHECK_EQ(i, expected);
}
}
TEST_CASE_TEMPLATE("test various cases with empty lines", T,
ParserOptionCombinations) {
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>({{1, 2, X::empty},
{3, 4, X::empty},
{9, 10, X::empty},
{11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, "w"}});
test_ignore_empty<T>({{11, 12, X::empty}});
test_ignore_empty<T>({});
}

View File

@@ -85,7 +85,7 @@ struct column {
};
template <typename... Ts>
[[nodiscard]] column make_column(const std::string& input_header,
column make_column(const std::string& input_header,
const std::vector<field>& input_fields) {
using setup = ss::setup<Ts...>;
std::vector<field> filtered_fields;
@@ -127,8 +127,8 @@ template <typename... Ts>
}
template <typename... Ts>
[[nodiscard]] std::vector<std::string> generate_csv_data(
const std::vector<field>& data, const std::string& delim) {
std::vector<std::string> generate_csv_data(const std::vector<field>& data,
const std::string& delim) {
(void)delim;
using setup = ss::setup<Ts...>;
constexpr static auto escape = '\\';

View File

@@ -9,3 +9,4 @@ TEST_CASE("parser test various cases version 2 segment 1") {
test_option_combinations3<escape>();
#endif
}

View File

@@ -10,3 +10,4 @@ TEST_CASE("parser test various cases version 2 segment 2") {
test_option_combinations3<escape, quote>();
#endif
}

View File

@@ -11,3 +11,4 @@ TEST_CASE("parser test various cases version 2 segment 3") {
test_option_combinations3<quote, multiline>();
#endif
}

View File

@@ -12,3 +12,4 @@ TEST_CASE("parser test various cases version 2 segment 4") {
test_option_combinations3<escape, quote, multiline_r>();
#endif
}

View File

@@ -13,3 +13,4 @@ TEST_CASE("parser test various cases version 2 segment 5") {
test_option_combinations<escape, quote, multiline, trimr>();
#endif
}

View File

@@ -8,3 +8,4 @@ TEST_CASE("parser test various cases version 2 segment 6") {
test_option_combinations3<escape, quote, multiline>();
}