7 Commits

Author SHA1 Message Date
red0124
043e66f83f [skip ci] Update CI badges (#54) 2025-07-06 00:27:53 +02:00
red0124
11324e420e [skip ci] Update CI badges 2025-07-06 00:16:25 +02:00
red0124
c708d93db3 CI Update (#53)
* Remove 32-bit CI workflows

* [skip ci] Update Windows CI images

* [skip ci] Update MacOS CI image

* [skip ci] Remove icc scripts

* Update README
2025-07-05 15:17:58 +02:00
red0124
41ae9d98c7 [skip ci] Remove ICC ci 2025-07-04 17:29:20 +02:00
ado
53c5b779d4 [skip ci] Update version 2024-03-14 19:15:32 +01:00
ado
107a122718 [skip ci] Update README 2024-03-14 19:13:03 +01:00
red0124
55d0a4e598 Updated and added new functions related to headers, resolved ODR issues, resolved clang-tidy warnings (#50)
* Bugfix/odr violations (#47)

* Make common non-member functions inline, remove unreachable line from get_line_buffer

* [skip ci] Fix namespace comments

* Resolve clang-tidy warnings (#48)

* Resolve clang-tidy warnings, update single_header_generator.py

* Update single header test, resolve additional clang-tidy warnings

* Add header and raw_header methods,  update header usage methods error handling, write new and update existing unit tests

* Update parser error messages, fix parser tests

* Add [[nodiscard]] where fitting, update unit tests (#49)

* Add const where fitting, make splitter class members private, add #pragma once to ssp.hpp

* Modify header parsing for empty headers, update old and add new tests for header parsing

* Enable the parser to accept a header with one empty field, update unit tests

* Fix test CMakeLists.txt typo
2024-03-14 17:22:57 +01:00
39 changed files with 831 additions and 755 deletions

View File

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

View File

@@ -1,77 +0,0 @@
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,14 +31,11 @@ jobs:
fail-fast: false
matrix:
config:
- os: windows-2019
vs: "Visual Studio 16 2019"
- os: windows-latest
vs: "Visual Studio 17 2022"
build: [Debug, Release]
platform: [Win32, x64]
platform: [x64]
runs-on: ${{matrix.config.os}}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,23 +0,0 @@
#!/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

View File

@@ -1,11 +0,0 @@
#!/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,6 +37,7 @@ for header in headers:
includes = sorted(set(includes))
print('#pragma once')
print('\n'.join(includes))
print('#define SSP_DISABLE_FAST_FLOAT')
print('\n'.join(combined_file))

286
ssp.hpp
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

301
test/test_parser1_5.cpp Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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