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: strategy:
matrix: matrix:
xcode: ['15.2'] xcode: ['13.4.1', '14.1']
type: [Release, Debug] type: [Release, Debug]
runs-on: macos-latest runs-on: macos-12
env: env:
DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer
@@ -42,7 +42,7 @@ jobs:
id: cores id: cores
- name: Install dependencies - name: Install dependencies
run: script/ci_install_deps.sh sudo run: script/ci_install_deps.sh
- name: Configure - name: Configure
run: cmake -S test -B build -DCMAKE_BUILD_TYPE=${{matrix.type}} 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 fail-fast: false
matrix: matrix:
config: config:
- os: windows-2019
vs: "Visual Studio 16 2019"
- os: windows-latest - os: windows-latest
vs: "Visual Studio 17 2022" vs: "Visual Studio 17 2022"
build: [Debug, Release] build: [Debug, Release]
platform: [x64] platform: [Win32, x64]
runs-on: ${{matrix.config.os}} runs-on: ${{matrix.config.os}}

View File

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

View File

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

View File

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

View File

@@ -9,14 +9,15 @@
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![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) [![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) [![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/actions/workflows/single-header.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/single-header.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/actions/workflows/ubuntu-latest-gcc.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.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/actions/workflows/ubuntu-latest-clang.yml/badge.svg?branch=master)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.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)
[![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) [![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-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-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-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) [![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)
[![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) [![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) 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 # 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 # Installation
@@ -91,7 +92,7 @@ The library supports [CMake](#Cmake) and [meson](#Meson) build systems
## Headers ## 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 ```shell
$ cat students_with_header.csv $ cat students_with_header.csv
Id,Age,Grade Id,Age,Grade
@@ -115,7 +116,7 @@ James Bailey 2.5
Brian S. Wolfe 1.9 Brian S. Wolfe 1.9
Bill (Heath) Gates 3.3 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 ```cpp
ss::parser<ss::ignore_header> p{file_name}; 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 ```cpp
// ... // ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"}; 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>(); const auto& [id, grade] = p.get_next<std::string, float>();
std::cout << grade << std::endl; std::cout << id << ' ' << grade << std::endl;
if (p.field_exists("Id")) { if (p.field_exists("Id")) {
p.use_fields("Grade", "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 ```shell
$ ./a.out $ ./a.out
2.5 James Bailey 2.5
1.9 Brian S. Wolfe 40 Brian S. Wolfe
3.3 Bill (Heath) Gates 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 ## Conversions
An alternate loop to the example above would look like: An alternate loop to the example above would look like:
```cpp ```cpp

View File

@@ -32,7 +32,7 @@ void assert_throw_on_error_not_defined() {
"'throw_on_error' is enabled"); "'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); ptr = std::realloc(ptr, size);
if (!ptr) { if (!ptr) {
throw std::bad_alloc{}; throw std::bad_alloc{};
@@ -42,20 +42,18 @@ void assert_throw_on_error_not_defined() {
} }
#if __unix__ #if __unix__
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n, inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
FILE* file) {
return getline(&lineptr, &n, file); return getline(&lineptr, &n, file);
} }
#else #else
using ssize_t = intptr_t; using ssize_t = intptr_t;
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n, inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
FILE* file) {
std::array<char, get_line_initial_buffer_size> buff; std::array<char, get_line_initial_buffer_size> buff;
if (lineptr == nullptr || n < sizeof(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)); lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n; n = new_n;
} }
@@ -68,7 +66,7 @@ using ssize_t = intptr_t;
size_t buff_used = std::strlen(buff.data()); size_t buff_used = std::strlen(buff.data());
if (n <= buff_used + line_used) { 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)); lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n; n = new_n;
} }
@@ -87,10 +85,9 @@ using ssize_t = intptr_t;
#endif #endif
[[nodiscard]] inline ssize_t get_line_buffer(char*& lineptr, size_t& n, inline ssize_t get_line_buffer(char*& lineptr, size_t& n,
const char* const csv_data_buffer, const char* const csv_data_buffer, size_t csv_data_size,
size_t csv_data_size, size_t& curr_char) {
size_t& curr_char) {
if (curr_char >= csv_data_size) { if (curr_char >= csv_data_size) {
return -1; return -1;
} }
@@ -105,7 +102,7 @@ using ssize_t = intptr_t;
size_t line_used = 0; size_t line_used = 0;
while (curr_char < csv_data_size) { while (curr_char < csv_data_size) {
if (line_used + 1 >= n) { if (line_used + 1 >= n) {
const size_t new_n = n * 2; size_t new_n = n * 2;
char* new_lineptr = char* new_lineptr =
static_cast<char*>(strict_realloc(lineptr, new_n)); static_cast<char*>(strict_realloc(lineptr, new_n));
@@ -125,10 +122,10 @@ using ssize_t = intptr_t;
return line_used; return line_used;
} }
[[nodiscard]] inline std::tuple<ssize_t, bool> get_line( inline std::tuple<ssize_t, bool> get_line(char*& buffer, size_t& buffer_size,
char*& buffer, size_t& buffer_size, FILE* file, FILE* file,
const char* const csv_data_buffer, size_t csv_data_size, const char* const csv_data_buffer,
size_t& curr_char) { size_t csv_data_size, size_t& curr_char) {
ssize_t ssize = 0; ssize_t ssize = 0;
if (file) { if (file) {
ssize = get_line_file(buffer, buffer_size, 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 // parses line with given delimiter, returns a 'T' object created with
// extracted values of type 'Ts' // extracted values of type 'Ts'
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] T convert_object( T convert_object(line_ptr_type line,
line_ptr_type line, const std::string& delim = default_delimiter) { const std::string& delim = default_delimiter) {
return to_object<T>(convert<Ts...>(line, delim)); return to_object<T>(convert<Ts...>(line, delim));
} }
// parses line with given delimiter, returns tuple of objects with // parses line with given delimiter, returns tuple of objects with
// extracted values of type 'Ts' // extracted values of type 'Ts'
template <typename... 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) { line_ptr_type line, const std::string& delim = default_delimiter) {
split(line, delim); split(line, delim);
if (splitter_.valid()) { if (splitter_.valid()) {
return convert<Ts...>(splitter_.get_split_data()); return convert<Ts...>(splitter_.split_data_);
} else { } else {
handle_error_bad_split(); handle_error_bad_split();
return {}; return {};
@@ -131,13 +131,13 @@ public:
// parses already split line, returns 'T' object with extracted values // parses already split line, returns 'T' object with extracted values
template <typename T, typename... Ts> 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)); return to_object<T>(convert<Ts...>(elems));
} }
// same as above, but uses cached split line // same as above, but uses cached split line
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] T convert_object() { T convert_object() {
return to_object<T>(convert<Ts...>()); return to_object<T>(convert<Ts...>());
} }
@@ -146,8 +146,7 @@ public:
// one argument is given which is a class which has a tied // one argument is given which is a class which has a tied
// method which returns a tuple, returns that type // method which returns a tuple, returns that type
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert( no_void_validator_tup_t<T, Ts...> convert(const split_data& elems) {
const split_data& elems) {
if constexpr (sizeof...(Ts) == 0 && is_instance_of_v<std::tuple, T>) { if constexpr (sizeof...(Ts) == 0 && is_instance_of_v<std::tuple, T>) {
return convert_impl(elems, static_cast<T*>(nullptr)); return convert_impl(elems, static_cast<T*>(nullptr));
} else if constexpr (tied_class_v<T, Ts...>) { } else if constexpr (tied_class_v<T, Ts...>) {
@@ -163,11 +162,11 @@ public:
// same as above, but uses cached split line // same as above, but uses cached split line
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert() { no_void_validator_tup_t<T, Ts...> convert() {
return convert<T, Ts...>(splitter_.get_split_data()); return convert<T, Ts...>(splitter_.split_data_);
} }
[[nodiscard]] bool valid() const { bool valid() const {
if constexpr (string_error) { if constexpr (string_error) {
return error_.empty(); return error_.empty();
} else if constexpr (throw_on_error) { } 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>(); assert_string_error_defined<string_error>();
return error_; return error_;
} }
[[nodiscard]] bool unterminated_quote() const { bool unterminated_quote() const {
return splitter_.unterminated_quote(); return splitter_.unterminated_quote();
} }
@@ -190,9 +189,9 @@ public:
// contain the beginnings and the ends of each column of the string // contain the beginnings and the ends of each column of the string
const split_data& split(line_ptr_type line, const split_data& split(line_ptr_type line,
const std::string& delim = default_delimiter) { const std::string& delim = default_delimiter) {
splitter_.clear_split_data(); splitter_.split_data_.clear();
if (line[0] == '\0') { if (line[0] == '\0') {
return splitter_.get_split_data(); return splitter_.split_data_;
} }
return splitter_.split(line, delim); return splitter_.split(line, delim);
@@ -208,7 +207,7 @@ private:
return splitter_.resplit(new_line, new_size, delim); return splitter_.resplit(new_line, new_size, delim);
} }
[[nodiscard]] size_t size_shifted() { size_t size_shifted() {
return splitter_.size_shifted(); return splitter_.size_shifted();
} }
@@ -224,8 +223,7 @@ private:
} }
} }
[[nodiscard]] std::string error_sufix(const string_range msg, std::string error_sufix(const string_range msg, size_t pos) const {
size_t pos) const {
constexpr static auto reserve_size = 32; constexpr static auto reserve_size = 32;
std::string error; std::string error;
error.reserve(reserve_size); error.reserve(reserve_size);
@@ -353,8 +351,7 @@ private:
//////////////// ////////////////
template <typename... Ts> template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> convert_impl( no_void_validator_tup_t<Ts...> convert_impl(const split_data& elems) {
const split_data& elems) {
clear_error(); clear_error();
if (!splitter_.valid()) { if (!splitter_.valid()) {
@@ -385,7 +382,7 @@ private:
} }
template <typename... Ts> 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...>*) { const split_data& elems, const std::tuple<Ts...>*) {
return convert_impl<Ts...>(elems); return convert_impl<Ts...>(elems);
} }
@@ -394,11 +391,11 @@ private:
// column mapping // column mapping
//////////////// ////////////////
[[nodiscard]] bool columns_mapped() const { bool columns_mapped() const {
return !column_mappings_.empty(); 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()) { if (!columns_mapped()) {
return tuple_position; return tuple_position;
} }
@@ -429,7 +426,7 @@ private:
} }
if constexpr (std::is_same_v<T, std::string>) { 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; return;
} }
@@ -475,8 +472,7 @@ private:
} }
template <typename... Ts> template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> extract_tuple( no_void_validator_tup_t<Ts...> extract_tuple(const split_data& elems) {
const split_data& elems) {
static_assert(!all_of_v<std::is_void, Ts...>, static_assert(!all_of_v<std::is_void, Ts...>,
"at least one parameter must be non void"); "at least one parameter must be non void");
no_void_validator_tup_t<Ts...> ret{}; no_void_validator_tup_t<Ts...> ret{};

View File

@@ -12,10 +12,10 @@ class exception : public std::exception {
std::string msg_; std::string msg_;
public: 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(); return msg_.c_str();
} }
}; };

View File

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

View File

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

View File

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

View File

@@ -28,7 +28,7 @@ private:
public: public:
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>; using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
[[nodiscard]] bool valid() const { bool valid() const {
if constexpr (string_error) { if constexpr (string_error) {
return error_.empty(); return error_.empty();
} else if constexpr (throw_on_error) { } 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>(); assert_string_error_defined<string_error>();
return error_; return error_;
} }
[[nodiscard]] bool unterminated_quote() const { bool unterminated_quote() const {
return unterminated_quote_; return unterminated_quote_;
} }
@@ -55,21 +55,13 @@ public:
return split_impl_select_delim(delimiter); 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: private:
//////////////// ////////////////
// resplit // resplit
//////////////// ////////////////
// number of characters the end of line is shifted backwards // number of characters the end of line is shifted backwards
[[nodiscard]] size_t size_shifted() const { size_t size_shifted() const {
return escaped_; return escaped_;
} }
@@ -92,7 +84,7 @@ private:
} }
const auto [old_line, old_begin] = *std::prev(split_data_.end()); 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 // safety measure
if (new_size != -1 && static_cast<size_t>(new_size) < begin) { if (new_size != -1 && static_cast<size_t>(new_size) < begin) {
@@ -200,19 +192,19 @@ private:
// matching // matching
//////////////// ////////////////
[[nodiscard]] bool match(const char* const curr, char delim) { bool match(const char* const curr, char delim) {
return *curr == 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; return std::strncmp(curr, delim.c_str(), delim.size()) == 0;
}; };
[[nodiscard]] size_t delimiter_size(char) { size_t delimiter_size(char) {
return 1; return 1;
} }
[[nodiscard]] size_t delimiter_size(const std::string& delim) { size_t delimiter_size(const std::string& delim) {
return delim.size(); return delim.size();
} }
@@ -233,8 +225,8 @@ private:
} }
template <typename Delim> 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) { const Delim& delim) {
line_ptr_type end = begin; line_ptr_type end = begin;
trim_right_if_enabled(end); trim_right_if_enabled(end);
@@ -469,6 +461,7 @@ private:
// members // members
//////////////// ////////////////
public:
error_type error_{}; error_type error_{};
bool unterminated_quote_{false}; bool unterminated_quote_{false};
bool done_{true}; 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> 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))...}; return {std::get<Is>(std::forward<U>(data))...};
} }
template <class T, class U> template <class T, class U>
[[nodiscard]] T to_object(U&& data) { T to_object(U&& data) {
using NoRefU = std::decay_t<U>; using NoRefU = std::decay_t<U>;
if constexpr (is_instance_of_v<std::tuple, NoRefU>) { if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
return to_object_impl< return to_object_impl<

View File

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

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/bin/bash
JOBS=4 JOBS=4
BUILD_TYPE=Debug BUILD_TYPE=Debug
set -ex set -eux
git clone https://github.com/red0124/doctest -b master --depth 1 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_MAIN_IN_STATIC_LIB=NO \
-D DOCTEST_WITH_TESTS=NO -D DOCTEST_WITH_TESTS=NO
if [[ "${1}" == "sudo" ]]; then cmake --build doctest/build --config ${BUILD_TYPE} --target install -j ${JOBS}
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
rm -rf doctest 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)) includes = sorted(set(includes))
print('#pragma once')
print('\n'.join(includes)) print('\n'.join(includes))
print('#define SSP_DISABLE_FAST_FLOAT') print('#define SSP_DISABLE_FAST_FLOAT')
print('\n'.join(combined_file)) print('\n'.join(combined_file))

308
ssp.hpp
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -51,16 +51,14 @@ TEST_CASE_TEMPLATE("test moving of parsed composite values", T,
// to compile is enough // to compile is enough
return; return;
auto [p, _] = make_parser<buffer_mode, ErrorMode>("", ""); auto [p, _] = make_parser<buffer_mode, ErrorMode>("", "");
std::ignore = p.template try_next<my_string, my_string, my_string>()
p.template try_next<my_string, my_string, my_string>() .template or_else<my_string, my_string, my_string, my_string>(
.template or_else<my_string, my_string, my_string, my_string>( [](auto&&) {})
[](auto&&) {}) .template or_else<my_string>([](auto&) {})
.template or_else<my_string>([](auto&) {}) .template or_else<xyz>([](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>( .template or_else<std::tuple<my_string, my_string, my_string>>(
[](auto&&) {}) [](auto&, auto&, auto&) {});
.template or_else<std::tuple<my_string, my_string, my_string>>(
[](auto&, auto&, auto&) {});
} }
TEST_CASE_TEMPLATE("parser test string error mode", BufferMode, std::true_type, TEST_CASE_TEMPLATE("parser test string error mode", BufferMode, std::true_type,
@@ -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, ","); auto [p, _] = make_parser<BufferMode::value, ss::string_error>(f.name, ",");
REQUIRE_FALSE(p.eof()); REQUIRE_FALSE(p.eof());
std::ignore = p.template get_next<int>(); p.template get_next<int>();
CHECK_FALSE(p.valid()); CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty()); 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()); REQUIRE_FALSE(p.eof());
try { try {
std::ignore = p.template get_next<int>(); p.template get_next<int>();
FAIL("Expected exception..."); FAIL("Expected exception...");
} catch (const std::exception& e) { } catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty()); CHECK_FALSE(std::string{e.what()}.empty());
@@ -150,8 +148,7 @@ TEST_CASE_TEMPLATE("test quote multiline", T, ParserOptionCombinations) {
make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name, ","); make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name, ",");
while (!p.eof()) { while (!p.eof()) {
auto command = [&p_no_multiline = p_no_multiline] { auto command = [&p_no_multiline = p_no_multiline] {
std::ignore = p_no_multiline.template get_next<int, double, std::string>();
p_no_multiline.template get_next<int, double, std::string>();
}; };
expect_error_on_command(p_no_multiline, command); 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; size_t line = 0;
while (!p.eof()) { while (!p.eof()) {
auto command = [&p = p] { 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) { 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) { auto check_header = [&lines](auto& p) {
if (lines.empty()) { if (lines.empty()) {
CHECK_EQ(p.header().size(), 1); CHECK(p.header().empty());
CHECK_EQ(p.header().at(0), "");
CHECK_EQ(merge_header(p.header(), ","), p.raw_header()); CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
} else { } else {
CHECK_EQ(lines[0], merge_header(p.header())); 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 [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p, &fields = fields] { auto command = [&p = p, &fields = fields] {
p.use_fields(fields.at(0)); 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); 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) { ParserOptionCombinations) {
test_invalid_fields<T>({}, {}); 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()); 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,8 +85,8 @@ struct column {
}; };
template <typename... Ts> 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) { const std::vector<field>& input_fields) {
using setup = ss::setup<Ts...>; using setup = ss::setup<Ts...>;
std::vector<field> filtered_fields; std::vector<field> filtered_fields;
@@ -127,8 +127,8 @@ template <typename... Ts>
} }
template <typename... Ts> template <typename... Ts>
[[nodiscard]] std::vector<std::string> generate_csv_data( std::vector<std::string> generate_csv_data(const std::vector<field>& data,
const std::vector<field>& data, const std::string& delim) { const std::string& delim) {
(void)delim; (void)delim;
using setup = ss::setup<Ts...>; using setup = ss::setup<Ts...>;
constexpr static auto escape = '\\'; constexpr static auto escape = '\\';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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