40 Commits

Author SHA1 Message Date
ado
67ef6651c1 Fix README typos 2024-03-01 17:23:26 +01:00
ado
fa4ec324de Update version 2024-03-01 16:22:45 +01:00
red0124
f229de61d6 Merge pull request #42 from red0124/dev
Merge with development
2024-03-01 16:17:16 +01:00
red0124
df2beab6c3 Fix buffer overflow on multiline restricted with unterminated quote and multiple empty lines (#41) 2024-03-01 15:46:34 +01:00
red0124
27bd60b5ce Fix bug with get_line_buffer when used with data buffer that is not null terminated and does not end with \n (#40) 2024-03-01 02:47:04 +01:00
red0124
c5b50f2b47 Fix compile issues for c++20 (#39) 2024-03-01 00:52:00 +01:00
red0124
d8dcce7f2a Fix buffer overflow on multiline csv data containing null characters (#38) 2024-02-29 22:03:20 +01:00
red0124
126329608c Add macOS ci (#36)
* Add macOS ci, update README
2024-02-28 22:20:26 +01:00
ado
ddaa446819 Update version 2024-02-28 00:58:14 +01:00
red0124
8bad2d72ea Merge pull request #35 from red0124/feature/csv_buffer
Feature/csv buffer
2024-02-28 00:13:20 +01:00
ado
899a6e6f5e [skip ci] Update README 2024-02-28 00:08:04 +01:00
ado
0d3d8fa83e [skip ci] Update README 2024-02-28 00:04:59 +01:00
ado
7bbe2879cd [skip ci] Update README 2024-02-28 00:02:58 +01:00
ado
063d56fad9 [skip ci] Update README 2024-02-28 00:01:37 +01:00
ado
df78865f04 [skip ci] Update README 2024-02-27 23:56:13 +01:00
ado
852481d233 Fix converter unit tests 2024-02-27 02:49:50 +01:00
ado
c516a6f826 Fix extraction tests 2024-02-26 02:37:30 +01:00
ado
b660310acf [skip ci] Merge with master 2024-02-25 18:27:26 +01:00
ado
0a695cf09e Add ss::uint8 and ss::int8, add unit tests for them 2024-02-25 17:46:35 +01:00
ado
f8e14b1fcf [skip ci] Add std:: to invoked C std lib functions 2024-02-25 13:03:52 +01:00
ado
0ebbee1174 [skip ci] Remove obsolete check from get_line_buffer 2024-02-25 12:10:46 +01:00
ado
b3f3bdf8d1 [skip ci] Update ssp.hpp 2024-02-25 10:54:56 +01:00
ado
f4a06d40e7 Fix non-POSIX get_line 2024-02-25 10:53:21 +01:00
ado
f2ff40a625 Add strict_realloc 2024-02-25 10:42:11 +01:00
ado
110ee840cc Fix header usage functionality 2024-02-25 03:54:33 +01:00
ado
05f87bc78b [skip ci] Fix line method 2024-02-25 02:57:46 +01:00
ado
88e711a5f7 Add positions method to parser, write unit tests for it, update other parser tests 2024-02-25 02:06:48 +01:00
ado
383de57f9a Make ssize_t equal to intptr_t for non-POSIX environments 2024-02-24 19:09:41 +01:00
ado
c6f6ba9821 [skip ci] Reduce number of runs for no new line at end of data test 2024-02-24 14:37:26 +01:00
ado
c5e491041d [skip ci] Disable file mode for no new line at end of data test 2024-02-24 14:17:23 +01:00
ado
21b543ea4f [skip ci] Disable buffer mode no new line at end of data test 2024-02-24 13:55:31 +01:00
ado
8881649aca Update get_line_buffer, update new version of get_line_file to work with data that has no new line at eof 2024-02-23 23:25:03 +01:00
red0124
a27fd121a1 Merge pull request #34 from red0124/improvement/getline_update
Fix reallocation issues with non-POSIX get_line
2024-02-23 23:04:19 +01:00
ado
c0d3087f85 Fix reallocation issues with non-POSIX get_line 2024-02-23 21:56:44 +01:00
ado
273e8ad950 Merge with master, resolve conflicts 2024-02-23 02:49:13 +01:00
red0124
f8fdb97151 Merge pull request #33 from red0124/improvement/getline_update
Improvement/getline update
2024-02-23 01:53:47 +01:00
ado
c0ee100f99 Update ssp.hpp 2024-02-23 01:04:40 +01:00
ado
09e628020d Fix typo in common.hpp 2024-02-23 01:01:31 +01:00
ado
ea21b9ba04 Fix get_line possible leak 2024-02-23 00:59:58 +01:00
ado
230da6a3f2 Update getline implementation for non-POSIX systems 2024-02-22 23:59:58 +01:00
19 changed files with 1140 additions and 890 deletions

55
.github/workflows/macos-apple-clang.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: macos-apple-clang-ci
on:
workflow_dispatch:
push:
branches:
- master
- feature/**
- improvement/**
- bugfix/**
pull_request:
branches:
- master
- feature/**
- improvement/**
- bugfix/**
jobs:
clang_tests:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
strategy:
matrix:
xcode: ['13.4.1', '14.1']
type: [Release, Debug]
runs-on: macos-12
env:
DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer
name: "Xcode ${{matrix.xcode}}: ${{matrix.type}}"
steps:
- uses: actions/checkout@v3
- uses: friendlyanon/fetch-core-count@v1
id: cores
- name: Install dependencies
run: script/ci_install_deps.sh
- name: Configure
run: cmake -S test -B build -DCMAKE_BUILD_TYPE=${{matrix.type}}
- name: Build
run: cmake --build build -j ${{steps.cores.outputs.count}}
- name: Run
working-directory: build
run: ctest --output-on-failure

View File

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

View File

@@ -16,14 +16,15 @@
[![windows-msys2-gcc](https://github.com/red0124/ssp/workflows/win-msys2-gcc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/win-msys2-gcc.yml)
[![windows-msys2-clang](https://github.com/red0124/ssp/workflows/win-msys2-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/win-msys2-clang.yml)
[![windows-msvc](https://github.com/red0124/ssp/workflows/win-msvc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/win-msvc.yml)
[![macos-apple-clang](https://github.com/red0124/ssp/workflows/macos-apple-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/macos-apple-clang.yml)
A header only "csv" parser which is fast and versatile with modern C++ api. Requires compiler with C++17 support. [Can also be used to convert strings to specific types.](#the-converter)
A header only CSV parser which is fast and versatile with modern C++ API. Requires compiler with C++17 support. [Can also be used to efficiently convert strings to specific types.](#the-converter)
Conversion for floating point values invoked using [fast-float](https://github.com/fastfloat/fast_float) . \
Function traits taken from *qt-creator* .
# Example
Lets say we have a csv file containing students in a given format \<Id,Age,Grade\> and we want to parse and print all the valid values:
Lets say we have a CSV file containing students in a given format (Id,Age,Grade) and we want to parse and print all the valid values:
```shell
$ cat students.csv
@@ -58,6 +59,7 @@ Bill (Heath) Gates 65 3.3
* Can work without exceptions
* [Works with headers](#headers)
* [Works with quotes, escapes and spacings](#setup)
* [Works with CSV data stored in buffers](#buffer-mode)
* [Works with values containing new lines](#multiline)
* [Columns and rows can be ignored](#special-types)
* [Works with any type of delimiter](#delimiter)
@@ -71,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 sufferes 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 slight performance loss when converting floating point values since the **`fast_float`** library is not present within the file.
# Installation
@@ -113,11 +115,11 @@ 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`** metod 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.
```cpp
ss::parser<ss::ignore_header> p{file_name};
```
The fields with which the parser works with can be modified at any given time. The praser can also check if a field is present within the header by using the **`field_exists`** method.
The fields with which the parser works with can be modified at any given time. The parser can also check if a field is present within the header by using the **`field_exists`** method.
```cpp
// ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
@@ -158,7 +160,7 @@ while (!p.eof()) {
The alternate example with exceptions disabled will be used to show some of the features of the library. The **`get_next`** method returns a tuple of objects specified inside the template type list.
If a conversion could not be applied, the method would return a tuple of default constructed objects, and the **`valid`** method would return **`false`**, for example if the third (grade) column in our csv could not be converted to a float the conversion would fail.
If a conversion could not be applied, the method would return a tuple of default constructed objects, and the **`valid`** method would return **`false`**, for example if the third (grade) column in our CSV could not be converted to a float the conversion would fail.
If **`get_next`** is called with a **`tuple`** as template parameter it would behave identically to passing the same tuple parameters to **`get_next`**:
```cpp
@@ -202,14 +204,27 @@ struct student {
auto tied() { return std::tie(id, age, grade); }
};
```
The method can be used to compare the object, serialize it, deserialize it, etc. Now **`get_next`** can accept such a struct and deduce the types to which to convert the csv.
The method can be used to compare the object, serialize it, deserialize it, etc. Now **`get_next`** can accept such a struct and deduce the types to which to convert the CSV.
```cpp
// returns student
auto s = p.get_next<student>();
```
This works with the iteration loop too.
*Note, the order in which the members of the tied method are returned must match the order of the elements in the csv*.
*Note, the order in which the members of the tied method are returned must match the order of the elements in the CSV*.
## Buffer mode
The parser also works with buffers containing CSV data instead of files. To parse buffer data with the parser simply create the parser by giving it the buffer, as **`const char*`**, and its size. The initial example using a buffer instead of a file would look similar to this:
```cpp
std::string buffer = "James Bailey,65,2.5\nBrian S. Wolfe,40,1.9\n";
ss::parser<ss::throw_on_error> p{buffer.c_str(), buffer.size()};
for (const auto& [id, age, grade] : p.iterate<std::string, int, float>()) {
std::cout << id << ' ' << age << ' ' << grade << std::endl;
}
return 0;
```
## Setup
By default, many of the features supported by the parser are disabled. They can be enabled within the template parameters of the parser. For example, to enable quoting and escaping the parser would look like:
```cpp
@@ -234,14 +249,14 @@ By default, **`,`** is used as the delimiter, a custom delimiter can be specifie
```cpp
ss::parser p{file_name, "--"};
```
*Note, the delimiter can consist of multiple characters but the parser is slightliy faster when using single character delimiters.*
*Note, the delimiter can consist of multiple characters but the parser is slightly faster when using single character delimiters.*
### Empty lines
Empty lines can be ignored by defining **`ss::ignore_empty`** within the setup parameters:
```cpp
ss::parser<ss::ignore_empty> p{file_name};
```
If this setup option is not set then reading an empty line will result in an error (unless only one column is present within the csv).
If this setup option is not set then reading an empty line will result in an error (unless only one column is present within the CSV).
### Quoting
Quoting can be enabled by defining **`ss::quote`** within the setup parameters. A single character can be defined as the quoting character, for example to use **`"`** as a quoting character:
@@ -290,7 +305,7 @@ Escaping and quoting can be used to leave the space if needed.
```
### Multiline
Multiline can be enabled by defining **`ss::multilne`** within the setup parameters. It enables the possibility to have the new line characters within rows. The new line character needs to be either escaped or within quotes so either **`ss::escape`** or **`ss::quote`** need to be enabled. There is a specific problem when using multiline, for example, if a row had an unterminated quote, the parser would assume it to be a new line within the row, so until another quote is found, it will treat it as one line which is fine usually, but it can cause the whole csv file to be treated as a single line by mistake. To prevent this **`ss::multiline_restricted`** can be used which accepts an unsigned number representing the maximum number of lines which can be allowed as a single multiline. Examples:
Multiline can be enabled by defining **`ss::multilne`** within the setup parameters. It enables the possibility to have the new line characters within rows. The new line character needs to be either escaped or within quotes so either **`ss::escape`** or **`ss::quote`** need to be enabled. There is a specific problem when using multiline, for example, if a row had an unterminated quote, the parser would assume it to be a new line within the row, so until another quote is found, it will treat it as one line which is fine usually, but it can cause the whole CSV file to be treated as a single line by mistake. To prevent this **`ss::multiline_restricted`** can be used which accepts an unsigned number representing the maximum number of lines which can be allowed as a single multiline. Examples:
```cpp
ss::parser<ss::multiline, ss::quote<'\"'>, ss::escape<'\\'>> p{file_name};
@@ -341,7 +356,7 @@ Gates 65 3.3'
```
## Special types
Passing **`void`** makes the parser ignore a column. In the initial example **`void`** could be given as the second template parameter to ignore the second (age) column in the csv, a tuple of only 2 parameters would be retuned:
Passing **`void`** makes the parser ignore a column. In the initial example **`void`** could be given as the second template parameter to ignore the second (age) column in the CSV, a tuple of only 2 parameters would be retuned:
```cpp
// returns std::tuple<std::string, float>
auto [id, grade] = p.get_next<std::string, void, float>();
@@ -383,6 +398,12 @@ if (std::holds_alternative<float>(grade)) {
// grade set as char
}
```
Passing **`char`** and types that are aliases to it such as **`uint8_t`** and **`int8_t`** make the parser interpret the input data as a single character in a similar way to how **`std::cin`** does it. To read numeric values into something like **`uint8_t`** the **`ss::uint8`** and **`ss::int8`** types can be used. These are wrappers around the corresponding char aliases and can be implicitly converted to and from them. When these types are given to the parser he will try to read the given data and store it in the underlying element, but this time as a numeric value instead of a single character.
```cpp
// returns std::tuple<std::string, ss::uint8, float>
auto [id, age, grade] = p.get_next<std::string, ss::uint8, float>();
uint8_t age_copy = age;
```
## Restrictions
Custom **`restrictions`** can be used to narrow down the conversions of unwanted values. **`ss::ir`** (in range) and **`ss::ne`** (none empty) are some of those:
@@ -454,12 +475,13 @@ The **`eof`** method can be used to detect if the end of the file was reached.
Detailed error messages can be accessed via the **`error_msg`** method, and to enable them **`ss::string_error`** needs to be included in the setup. If **`ss::string_error`** is not defined, the **`error_msg`** method will not be defined either.
The line number can be fetched using the **`line`** method.
The cursor position can be fetched using the **`position`** method.
```cpp
const std::string& parser::error_msg();
bool parser::valid();
bool parser::eof();
size_t parser::line();
const std::string& parser::error_msg() const;
bool parser::valid() const;
bool parser::eof() const;
size_t parser::line() const;
size_t parser::position() const;
// ...
ss::parser<ss::string_error> parser;
@@ -474,7 +496,7 @@ ss::parser<ss::throw_on_error> parser;
## Substitute conversions
The parser can also be used to effectively parse files whose rows are not always in the same format (not a classical csv but still csv-like). A more complicated example would be the best way to demonstrate such a scenario.\
The parser can also be used to effectively parse files whose rows are not always in the same format (not a classical CSV but still CSV-like). A more complicated example would be the best way to demonstrate such a scenario.\
***Important, substitute conversions do not work when throw_on_error is enabled.***
Supposing we have a file containing different shapes in given formats:

View File

@@ -1,7 +1,9 @@
#pragma once
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
namespace ss {
@@ -26,61 +28,121 @@ inline void assert_throw_on_error_not_defined() {
"'throw_on_error' is enabled");
}
inline void* strict_realloc(void* ptr, size_t size) {
ptr = std::realloc(ptr, size);
if (!ptr) {
throw std::bad_alloc{};
}
return ptr;
}
#if __unix__
inline ssize_t get_line_file(char** lineptr, size_t* n, FILE* stream) {
return getline(lineptr, n, stream);
inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
return getline(&lineptr, &n, file);
}
#else
using ssize_t = int64_t;
inline ssize_t get_line_file(char** lineptr, size_t* n, FILE* stream) {
size_t pos;
int c;
using ssize_t = intptr_t;
if (lineptr == nullptr || stream == nullptr || n == nullptr) {
errno = EINVAL;
return -1;
ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
char buff[get_line_initial_buffer_size];
if (lineptr == nullptr || n < sizeof(buff)) {
size_t new_n = sizeof(buff);
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
}
c = getc(stream);
if (c == EOF) {
return -1;
}
lineptr[0] = '\0';
if (*lineptr == nullptr) {
*lineptr = static_cast<char*>(malloc(get_line_initial_buffer_size));
if (*lineptr == nullptr) {
return -1;
}
*n = 128;
}
size_t line_used = 0;
while (std::fgets(buff, sizeof(buff), file) != nullptr) {
line_used = std::strlen(lineptr);
size_t buff_used = std::strlen(buff);
pos = 0;
while (c != EOF) {
if (pos + 1 >= *n) {
size_t new_size = *n + (*n >> 2);
if (new_size < get_line_initial_buffer_size) {
new_size = get_line_initial_buffer_size;
}
char* new_ptr = static_cast<char*>(
realloc(static_cast<void*>(*lineptr), new_size));
if (new_ptr == nullptr) {
return -1;
}
*n = new_size;
*lineptr = new_ptr;
if (n <= buff_used + line_used) {
size_t new_n = n * 2;
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
}
(*lineptr)[pos++] = c;
if (c == '\n') {
break;
std::memcpy(lineptr + line_used, buff, buff_used);
line_used += buff_used;
lineptr[line_used] = '\0';
if (lineptr[line_used - 1] == '\n') {
return line_used;
}
c = getc(stream);
}
(*lineptr)[pos] = '\0';
return pos;
return (line_used != 0) ? line_used : -1;
}
#endif
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;
}
if (lineptr == nullptr || n < get_line_initial_buffer_size) {
auto new_lineptr = static_cast<char*>(
strict_realloc(lineptr, get_line_initial_buffer_size));
lineptr = new_lineptr;
n = get_line_initial_buffer_size;
}
size_t line_used = 0;
while (curr_char < csv_data_size) {
if (line_used + 1 >= n) {
size_t new_n = n * 2;
char* new_lineptr =
static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
lineptr = new_lineptr;
}
auto c = csv_data_buffer[curr_char++];
lineptr[line_used++] = c;
if (c == '\n') {
lineptr[line_used] = '\0';
return line_used;
}
}
if (line_used != 0) {
lineptr[line_used] = '\0';
return line_used;
}
return -1;
}
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;
if (file) {
ssize = get_line_file(buffer, buffer_size, file);
curr_char = std::ftell(file);
} else {
ssize = get_line_buffer(buffer, buffer_size, csv_data_buffer,
csv_data_size, curr_char);
}
if (ssize == -1) {
if (errno == ENOMEM) {
throw std::bad_alloc{};
}
return {ssize, true};
}
return {ssize, false};
}
} /* ss */

View File

@@ -150,7 +150,7 @@ public:
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...>) {
using arg_ref_tuple = std::result_of_t<decltype (&T::tied)(T)>;
using arg_ref_tuple = std::invoke_result_t<decltype(&T::tied), T>;
using arg_tuple = apply_trait_t<std::decay, arg_ref_tuple>;
return to_object<T>(
@@ -269,6 +269,7 @@ private:
void handle_error_multiline_limit_reached() {
constexpr static auto error_msg = "multiline limit reached";
splitter_.unterminated_quote_ = false;
if constexpr (string_error) {
error_.clear();

View File

@@ -77,6 +77,38 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
#endif
////////////////
// numeric_wrapper
////////////////
template <typename T>
struct numeric_wrapper {
using type = T;
numeric_wrapper() = default;
numeric_wrapper(numeric_wrapper&&) = default;
numeric_wrapper(const numeric_wrapper&) = default;
numeric_wrapper& operator=(numeric_wrapper&&) = default;
numeric_wrapper& operator=(const numeric_wrapper&) = default;
numeric_wrapper(T other) : value{other} {
}
operator T() {
return value;
}
operator T() const {
return value;
}
T value;
};
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(
const char* const begin, const char* const end) {
@@ -89,6 +121,18 @@ std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
return ret;
}
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) {
T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret.value);
if (ec != std::errc() || ptr != end) {
return std::nullopt;
}
return ret;
}
////////////////
// extract
////////////////
@@ -103,7 +147,8 @@ struct unsupported_type {
template <typename T>
std::enable_if_t<!std::is_integral_v<T> && !std::is_floating_point_v<T> &&
!is_instance_of_v<std::optional, T> &&
!is_instance_of_v<std::variant, T>,
!is_instance_of_v<std::variant, T> &&
!is_instance_of_v<numeric_wrapper, T>,
bool>
extract(const char*, const char*, T&) {
static_assert(error::unsupported_type<T>::value,
@@ -112,7 +157,9 @@ extract(const char*, const char*, T&) {
}
template <typename T>
std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, bool>
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) {
auto optional_value = to_num<T>(begin, end);
if (!optional_value) {
@@ -169,9 +216,9 @@ inline bool extract(const char* begin, const char* end, bool& value) {
}
} else {
size_t size = end - begin;
if (size == 4 && strncmp(begin, "true", size) == 0) {
if (size == 4 && std::strncmp(begin, "true", size) == 0) {
value = true;
} else if (size == 5 && strncmp(begin, "false", size) == 0) {
} else if (size == 5 && std::strncmp(begin, "false", size) == 0) {
value = false;
} else {
return false;

View File

@@ -102,10 +102,14 @@ public:
}
size_t line() const {
return reader_.line_number_ > 1 ? reader_.line_number_ - 1
return reader_.line_number_ > 0 ? reader_.line_number_ - 1
: reader_.line_number_;
}
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;
@@ -210,7 +214,7 @@ public:
reader_.next_line_converter_.set_column_mapping(column_mappings,
header_.size());
if (line() == 1) {
if (line() == 0) {
ignore_next();
}
}
@@ -671,7 +675,7 @@ private:
struct reader {
reader(const std::string& file_name_, const std::string& delim)
: delim_{delim}, file_{fopen(file_name_.c_str(), "rb")} {
: delim_{delim}, file_{std::fopen(file_name_.c_str(), "rb")} {
}
reader(const char* const buffer, size_t csv_data_size,
@@ -693,7 +697,7 @@ private:
csv_data_buffer_{other.csv_data_buffer_},
csv_data_size_{other.csv_data_size_},
curr_char_{other.curr_char_}, crlf_{other.crlf_},
line_number_{other.line_number_},
line_number_{other.line_number_}, chars_read_{other.chars_read_},
next_line_size_{other.next_line_size_} {
other.buffer_ = nullptr;
other.next_line_buffer_ = nullptr;
@@ -718,24 +722,26 @@ private:
curr_char_ = other.curr_char_;
crlf_ = other.crlf_;
line_number_ = other.line_number_;
chars_read_ = other.chars_read_;
next_line_size_ = other.next_line_size_;
other.buffer_ = nullptr;
other.next_line_buffer_ = nullptr;
other.helper_buffer_ = nullptr;
other.file_ = nullptr;
other.csv_data_buffer_ = nullptr;
}
return *this;
}
~reader() {
free(buffer_);
free(next_line_buffer_);
free(helper_buffer_);
std::free(buffer_);
std::free(next_line_buffer_);
std::free(helper_buffer_);
if (file_) {
fclose(file_);
std::fclose(file_);
}
}
@@ -743,57 +749,9 @@ private:
reader(const reader& other) = delete;
reader& operator=(const reader& other) = delete;
ssize_t get_line_buffer(char** lineptr, size_t* n,
const char* const buffer, size_t csv_data_size,
size_t& curr_char) {
size_t pos;
int c;
if (curr_char >= csv_data_size) {
return -1;
}
c = buffer[curr_char++];
if (*lineptr == nullptr) {
*lineptr =
static_cast<char*>(malloc(get_line_initial_buffer_size));
if (*lineptr == nullptr) {
return -1;
}
*n = 128;
}
pos = 0;
while (curr_char <= csv_data_size) {
if (pos + 1 >= *n) {
size_t new_size = *n + (*n >> 2);
if (new_size < get_line_initial_buffer_size) {
new_size = get_line_initial_buffer_size;
}
char* new_ptr = static_cast<char*>(
realloc(static_cast<void*>(*lineptr), new_size));
if (new_ptr == nullptr) {
return -1;
}
*n = new_size;
*lineptr = new_ptr;
}
(*lineptr)[pos++] = c;
if (c == '\n') {
break;
}
c = buffer[curr_char++];
}
(*lineptr)[pos] = '\0';
return pos;
}
// read next line each time in order to set eof_
bool read_next() {
next_line_converter_.clear_error();
ssize_t ssize = 0;
size_t size = 0;
while (size == 0) {
++line_number_;
@@ -801,20 +759,12 @@ private:
next_line_buffer_[0] = '\0';
}
if (file_) {
ssize = get_line_file(&next_line_buffer_,
&next_line_buffer_size_, file_);
} else {
ssize = get_line_buffer(&next_line_buffer_,
&next_line_buffer_size_,
csv_data_buffer_, csv_data_size_,
curr_char_);
}
chars_read_ = curr_char_;
auto [ssize, eof] =
get_line(next_line_buffer_, next_line_buffer_size_, file_,
csv_data_buffer_, csv_data_size_, curr_char_);
if (ssize == -1) {
if (errno == ENOMEM) {
throw std::bad_alloc{};
}
if (eof) {
return false;
}
@@ -839,7 +789,8 @@ private:
}
if (!append_next_line_to_buffer(next_line_buffer_,
next_line_size_)) {
next_line_size_,
next_line_buffer_size_)) {
next_line_converter_.handle_error_unterminated_escape();
return;
}
@@ -857,7 +808,8 @@ private:
}
if (!append_next_line_to_buffer(next_line_buffer_,
next_line_size_)) {
next_line_size_,
next_line_buffer_size_)) {
next_line_converter_.handle_error_unterminated_quote();
return;
}
@@ -868,8 +820,9 @@ private:
return;
}
if (!append_next_line_to_buffer(next_line_buffer_,
next_line_size_)) {
if (!append_next_line_to_buffer(
next_line_buffer_, next_line_size_,
next_line_buffer_size_)) {
next_line_converter_
.handle_error_unterminated_escape();
return;
@@ -913,18 +866,20 @@ private:
return next_line_converter_.unterminated_quote();
}
void undo_remove_eol(char* buffer, size_t& string_end) {
if (crlf_) {
std::copy_n("\r\n\0", 3, buffer + string_end);
string_end += 2;
} else {
std::copy_n("\n\0", 2, buffer + string_end);
string_end += 1;
void undo_remove_eol(char* buffer, size_t& line_size,
size_t buffer_size) {
if (crlf_ && buffer_size >= line_size + 2) {
std::copy_n("\r\n", 2, buffer + line_size);
line_size += 2;
} else if (buffer_size > line_size) {
std::copy_n("\n", 1, buffer + line_size);
line_size += 1;
}
}
size_t remove_eol(char*& buffer, size_t ssize) {
if (buffer[ssize - 1] != '\n') {
crlf_ = false;
return ssize;
}
@@ -945,38 +900,30 @@ private:
size_t second_size) {
buffer_size = first_size + second_size + 3;
auto new_first = static_cast<char*>(
realloc(static_cast<void*>(first), buffer_size));
if (!new_first) {
throw std::bad_alloc{};
}
strict_realloc(static_cast<void*>(first), buffer_size));
first = new_first;
std::copy_n(second, second_size + 1, first + first_size);
first_size += second_size;
}
bool append_next_line_to_buffer(char*& buffer, size_t& size) {
undo_remove_eol(buffer, size);
bool append_next_line_to_buffer(char*& buffer, size_t& line_size,
size_t buffer_size) {
undo_remove_eol(buffer, line_size, buffer_size);
ssize_t next_ssize;
if (file_) {
next_ssize =
get_line_file(&helper_buffer_, &helper_buffer_size, file_);
} else {
next_ssize =
get_line_buffer(&helper_buffer_, &helper_buffer_size,
csv_data_buffer_, csv_data_size_,
curr_char_);
}
chars_read_ = curr_char_;
auto [next_ssize, eof] =
get_line(helper_buffer_, helper_buffer_size, file_,
csv_data_buffer_, csv_data_size_, curr_char_);
if (next_ssize == -1) {
if (eof) {
return false;
}
++line_number_;
size_t next_size = remove_eol(helper_buffer_, next_ssize);
realloc_concat(buffer, size, next_line_buffer_size_, helper_buffer_,
next_size);
realloc_concat(buffer, line_size, next_line_buffer_size_,
helper_buffer_, next_size);
return true;
}
@@ -1007,6 +954,7 @@ private:
bool crlf_{false};
size_t line_number_{0};
size_t chars_read_{0};
size_t next_line_size_{0};
};

View File

@@ -165,25 +165,25 @@ using get_multiline_t = typename get_multiline<Ts...>::type;
// string_error
////////////////
class string_error;
class string_error {};
////////////////
// ignore_header
////////////////
class ignore_header;
class ignore_header {};
////////////////
// ignore_empty
////////////////
class ignore_empty;
class ignore_empty {};
////////////////
// throw_on_error
////////////////
class throw_on_error;
class throw_on_error {};
////////////////
// setup implementation

View File

@@ -199,7 +199,7 @@ private:
};
bool match(const char* const curr, const std::string& delim) {
return strncmp(curr, delim.c_str(), delim.size()) == 0;
return std::strncmp(curr, delim.c_str(), delim.size()) == 0;
};
size_t delimiter_size(char) {

View File

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

354
ssp.hpp
View File

@@ -640,63 +640,123 @@ inline void assert_throw_on_error_not_defined() {
"'throw_on_error' is enabled");
}
inline void* strict_realloc(void* ptr, size_t size) {
ptr = std::realloc(ptr, size);
if (!ptr) {
throw std::bad_alloc{};
}
return ptr;
}
#if __unix__
inline ssize_t get_line_file(char** lineptr, size_t* n, FILE* stream) {
return getline(lineptr, n, stream);
inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
return getline(&lineptr, &n, file);
}
#else
using ssize_t = int64_t;
inline ssize_t get_line_file(char** lineptr, size_t* n, FILE* stream) {
size_t pos;
int c;
using ssize_t = intptr_t;
if (lineptr == nullptr || stream == nullptr || n == nullptr) {
errno = EINVAL;
return -1;
ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
char buff[get_line_initial_buffer_size];
if (lineptr == nullptr || n < sizeof(buff)) {
size_t new_n = sizeof(buff);
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
}
c = getc(stream);
if (c == EOF) {
return -1;
}
lineptr[0] = '\0';
if (*lineptr == nullptr) {
*lineptr = static_cast<char*>(malloc(get_line_initial_buffer_size));
if (*lineptr == nullptr) {
return -1;
}
*n = 128;
}
size_t line_used = 0;
while (std::fgets(buff, sizeof(buff), file) != nullptr) {
line_used = std::strlen(lineptr);
size_t buff_used = std::strlen(buff);
pos = 0;
while (c != EOF) {
if (pos + 1 >= *n) {
size_t new_size = *n + (*n >> 2);
if (new_size < get_line_initial_buffer_size) {
new_size = get_line_initial_buffer_size;
}
char* new_ptr = static_cast<char*>(
realloc(static_cast<void*>(*lineptr), new_size));
if (new_ptr == nullptr) {
return -1;
}
*n = new_size;
*lineptr = new_ptr;
if (n <= buff_used + line_used) {
size_t new_n = n * 2;
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
}
(*lineptr)[pos++] = c;
if (c == '\n') {
break;
std::memcpy(lineptr + line_used, buff, buff_used);
line_used += buff_used;
lineptr[line_used] = '\0';
if (lineptr[line_used - 1] == '\n') {
return line_used;
}
c = getc(stream);
}
(*lineptr)[pos] = '\0';
return pos;
return (line_used != 0) ? line_used : -1;
}
#endif
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;
}
if (lineptr == nullptr || n < get_line_initial_buffer_size) {
auto new_lineptr = static_cast<char*>(
strict_realloc(lineptr, get_line_initial_buffer_size));
lineptr = new_lineptr;
n = get_line_initial_buffer_size;
}
size_t line_used = 0;
while (curr_char < csv_data_size) {
if (line_used + 1 >= n) {
size_t new_n = n * 2;
char* new_lineptr =
static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
lineptr = new_lineptr;
}
auto c = csv_data_buffer[curr_char++];
lineptr[line_used++] = c;
if (c == '\n') {
lineptr[line_used] = '\0';
return line_used;
}
}
if (line_used != 0) {
lineptr[line_used] = '\0';
return line_used;
}
return -1;
}
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;
if (file) {
ssize = get_line_file(buffer, buffer_size, file);
curr_char = std::ftell(file);
} else {
ssize = get_line_buffer(buffer, buffer_size, csv_data_buffer,
csv_data_size, curr_char);
}
if (ssize == -1) {
if (errno == ENOMEM) {
throw std::bad_alloc{};
}
return {ssize, true};
}
return {ssize, false};
}
} /* ss */
namespace ss {
@@ -862,25 +922,25 @@ using get_multiline_t = typename get_multiline<Ts...>::type;
// string_error
////////////////
class string_error;
class string_error {};
////////////////
// ignore_header
////////////////
class ignore_header;
class ignore_header {};
////////////////
// ignore_empty
////////////////
class ignore_empty;
class ignore_empty {};
////////////////
// throw_on_error
////////////////
class throw_on_error;
class throw_on_error {};
////////////////
// setup implementation
@@ -1181,7 +1241,7 @@ private:
};
bool match(const char* const curr, const std::string& delim) {
return strncmp(curr, delim.c_str(), delim.size()) == 0;
return std::strncmp(curr, delim.c_str(), delim.size()) == 0;
};
size_t delimiter_size(char) {
@@ -1529,6 +1589,38 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
#endif
////////////////
// numeric_wrapper
////////////////
template <typename T>
struct numeric_wrapper {
using type = T;
numeric_wrapper() = default;
numeric_wrapper(numeric_wrapper&&) = default;
numeric_wrapper(const numeric_wrapper&) = default;
numeric_wrapper& operator=(numeric_wrapper&&) = default;
numeric_wrapper& operator=(const numeric_wrapper&) = default;
numeric_wrapper(T other) : value{other} {
}
operator T() {
return value;
}
operator T() const {
return value;
}
T value;
};
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(
const char* const begin, const char* const end) {
@@ -1541,6 +1633,18 @@ std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
return ret;
}
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) {
T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret.value);
if (ec != std::errc() || ptr != end) {
return std::nullopt;
}
return ret;
}
////////////////
// extract
////////////////
@@ -1555,7 +1659,8 @@ struct unsupported_type {
template <typename T>
std::enable_if_t<!std::is_integral_v<T> && !std::is_floating_point_v<T> &&
!is_instance_of_v<std::optional, T> &&
!is_instance_of_v<std::variant, T>,
!is_instance_of_v<std::variant, T> &&
!is_instance_of_v<numeric_wrapper, T>,
bool>
extract(const char*, const char*, T&) {
static_assert(error::unsupported_type<T>::value,
@@ -1564,7 +1669,9 @@ extract(const char*, const char*, T&) {
}
template <typename T>
std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, bool>
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) {
auto optional_value = to_num<T>(begin, end);
if (!optional_value) {
@@ -1621,9 +1728,9 @@ inline bool extract(const char* begin, const char* end, bool& value) {
}
} else {
size_t size = end - begin;
if (size == 4 && strncmp(begin, "true", size) == 0) {
if (size == 4 && std::strncmp(begin, "true", size) == 0) {
value = true;
} else if (size == 5 && strncmp(begin, "false", size) == 0) {
} else if (size == 5 && std::strncmp(begin, "false", size) == 0) {
value = false;
} else {
return false;
@@ -1795,7 +1902,7 @@ public:
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...>) {
using arg_ref_tuple = std::result_of_t<decltype (&T::tied)(T)>;
using arg_ref_tuple = std::invoke_result_t<decltype(&T::tied), T>;
using arg_tuple = apply_trait_t<std::decay, arg_ref_tuple>;
return to_object<T>(
@@ -1914,6 +2021,7 @@ private:
void handle_error_multiline_limit_reached() {
constexpr static auto error_msg = "multiline limit reached";
splitter_.unterminated_quote_ = false;
if constexpr (string_error) {
error_.clear();
@@ -2230,10 +2338,14 @@ public:
}
size_t line() const {
return reader_.line_number_ > 1 ? reader_.line_number_ - 1
return reader_.line_number_ > 0 ? reader_.line_number_ - 1
: reader_.line_number_;
}
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;
@@ -2338,7 +2450,7 @@ public:
reader_.next_line_converter_.set_column_mapping(column_mappings,
header_.size());
if (line() == 1) {
if (line() == 0) {
ignore_next();
}
}
@@ -2799,7 +2911,7 @@ private:
struct reader {
reader(const std::string& file_name_, const std::string& delim)
: delim_{delim}, file_{fopen(file_name_.c_str(), "rb")} {
: delim_{delim}, file_{std::fopen(file_name_.c_str(), "rb")} {
}
reader(const char* const buffer, size_t csv_data_size,
@@ -2821,7 +2933,7 @@ private:
csv_data_buffer_{other.csv_data_buffer_},
csv_data_size_{other.csv_data_size_},
curr_char_{other.curr_char_}, crlf_{other.crlf_},
line_number_{other.line_number_},
line_number_{other.line_number_}, chars_read_{other.chars_read_},
next_line_size_{other.next_line_size_} {
other.buffer_ = nullptr;
other.next_line_buffer_ = nullptr;
@@ -2846,24 +2958,26 @@ private:
curr_char_ = other.curr_char_;
crlf_ = other.crlf_;
line_number_ = other.line_number_;
chars_read_ = other.chars_read_;
next_line_size_ = other.next_line_size_;
other.buffer_ = nullptr;
other.next_line_buffer_ = nullptr;
other.helper_buffer_ = nullptr;
other.file_ = nullptr;
other.csv_data_buffer_ = nullptr;
}
return *this;
}
~reader() {
free(buffer_);
free(next_line_buffer_);
free(helper_buffer_);
std::free(buffer_);
std::free(next_line_buffer_);
std::free(helper_buffer_);
if (file_) {
fclose(file_);
std::fclose(file_);
}
}
@@ -2871,57 +2985,9 @@ private:
reader(const reader& other) = delete;
reader& operator=(const reader& other) = delete;
ssize_t get_line_buffer(char** lineptr, size_t* n,
const char* const buffer, size_t csv_data_size,
size_t& curr_char) {
size_t pos;
int c;
if (curr_char >= csv_data_size) {
return -1;
}
c = buffer[curr_char++];
if (*lineptr == nullptr) {
*lineptr =
static_cast<char*>(malloc(get_line_initial_buffer_size));
if (*lineptr == nullptr) {
return -1;
}
*n = 128;
}
pos = 0;
while (curr_char <= csv_data_size) {
if (pos + 1 >= *n) {
size_t new_size = *n + (*n >> 2);
if (new_size < get_line_initial_buffer_size) {
new_size = get_line_initial_buffer_size;
}
char* new_ptr = static_cast<char*>(
realloc(static_cast<void*>(*lineptr), new_size));
if (new_ptr == nullptr) {
return -1;
}
*n = new_size;
*lineptr = new_ptr;
}
(*lineptr)[pos++] = c;
if (c == '\n') {
break;
}
c = buffer[curr_char++];
}
(*lineptr)[pos] = '\0';
return pos;
}
// read next line each time in order to set eof_
bool read_next() {
next_line_converter_.clear_error();
ssize_t ssize = 0;
size_t size = 0;
while (size == 0) {
++line_number_;
@@ -2929,20 +2995,12 @@ private:
next_line_buffer_[0] = '\0';
}
if (file_) {
ssize = get_line_file(&next_line_buffer_,
&next_line_buffer_size_, file_);
} else {
ssize = get_line_buffer(&next_line_buffer_,
&next_line_buffer_size_,
csv_data_buffer_, csv_data_size_,
curr_char_);
}
chars_read_ = curr_char_;
auto [ssize, eof] =
get_line(next_line_buffer_, next_line_buffer_size_, file_,
csv_data_buffer_, csv_data_size_, curr_char_);
if (ssize == -1) {
if (errno == ENOMEM) {
throw std::bad_alloc{};
}
if (eof) {
return false;
}
@@ -2967,7 +3025,8 @@ private:
}
if (!append_next_line_to_buffer(next_line_buffer_,
next_line_size_)) {
next_line_size_,
next_line_buffer_size_)) {
next_line_converter_.handle_error_unterminated_escape();
return;
}
@@ -2985,7 +3044,8 @@ private:
}
if (!append_next_line_to_buffer(next_line_buffer_,
next_line_size_)) {
next_line_size_,
next_line_buffer_size_)) {
next_line_converter_.handle_error_unterminated_quote();
return;
}
@@ -2996,8 +3056,9 @@ private:
return;
}
if (!append_next_line_to_buffer(next_line_buffer_,
next_line_size_)) {
if (!append_next_line_to_buffer(
next_line_buffer_, next_line_size_,
next_line_buffer_size_)) {
next_line_converter_
.handle_error_unterminated_escape();
return;
@@ -3041,18 +3102,20 @@ private:
return next_line_converter_.unterminated_quote();
}
void undo_remove_eol(char* buffer, size_t& string_end) {
if (crlf_) {
std::copy_n("\r\n\0", 3, buffer + string_end);
string_end += 2;
} else {
std::copy_n("\n\0", 2, buffer + string_end);
string_end += 1;
void undo_remove_eol(char* buffer, size_t& line_size,
size_t buffer_size) {
if (crlf_ && buffer_size >= line_size + 2) {
std::copy_n("\r\n", 2, buffer + line_size);
line_size += 2;
} else if (buffer_size > line_size) {
std::copy_n("\n", 1, buffer + line_size);
line_size += 1;
}
}
size_t remove_eol(char*& buffer, size_t ssize) {
if (buffer[ssize - 1] != '\n') {
crlf_ = false;
return ssize;
}
@@ -3073,38 +3136,30 @@ private:
size_t second_size) {
buffer_size = first_size + second_size + 3;
auto new_first = static_cast<char*>(
realloc(static_cast<void*>(first), buffer_size));
if (!new_first) {
throw std::bad_alloc{};
}
strict_realloc(static_cast<void*>(first), buffer_size));
first = new_first;
std::copy_n(second, second_size + 1, first + first_size);
first_size += second_size;
}
bool append_next_line_to_buffer(char*& buffer, size_t& size) {
undo_remove_eol(buffer, size);
bool append_next_line_to_buffer(char*& buffer, size_t& line_size,
size_t buffer_size) {
undo_remove_eol(buffer, line_size, buffer_size);
ssize_t next_ssize;
if (file_) {
next_ssize =
get_line_file(&helper_buffer_, &helper_buffer_size, file_);
} else {
next_ssize =
get_line_buffer(&helper_buffer_, &helper_buffer_size,
csv_data_buffer_, csv_data_size_,
curr_char_);
}
chars_read_ = curr_char_;
auto [next_ssize, eof] =
get_line(helper_buffer_, helper_buffer_size, file_,
csv_data_buffer_, csv_data_size_, curr_char_);
if (next_ssize == -1) {
if (eof) {
return false;
}
++line_number_;
size_t next_size = remove_eol(helper_buffer_, next_ssize);
realloc_concat(buffer, size, next_line_buffer_size_, helper_buffer_,
next_size);
realloc_concat(buffer, line_size, next_line_buffer_size_,
helper_buffer_, next_size);
return true;
}
@@ -3135,6 +3190,7 @@ private:
bool crlf_{false};
size_t line_number_{0};
size_t chars_read_{0};
size_t next_line_size_{0};
};

View File

@@ -46,90 +46,88 @@ TEST_CASE("converter test split with exceptions") {
}
}
TEST_CASE("converter test valid conversions") {
TEST_CASE_TEMPLATE("converter test valid conversions", T, int, ss::uint8) {
ss::converter c;
{
auto tup = c.convert<int>("5");
auto tup = c.convert<T>("5");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
}
{
auto tup = c.convert<int, void>("5,junk");
auto tup = c.convert<T, void>("5,junk");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
}
{
auto tup = c.convert<void, int>("junk,5");
auto tup = c.convert<void, T>("junk,5");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
}
{
auto tup = c.convert<int, void, void>("5\njunk\njunk", "\n");
auto tup = c.convert<T, void, void>("5\njunk\njunk", "\n");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
}
{
auto tup = c.convert<void, int, void>("junk 5 junk", " ");
auto tup = c.convert<void, T, void>("junk 5 junk", " ");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
}
{
auto tup = c.convert<void, void, int>("junk\tjunk\t5", "\t");
auto tup = c.convert<void, void, T>("junk\tjunk\t5", "\t");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
}
{
auto tup =
c.convert<void, void, std::optional<int>>("junk\tjunk\t5", "\t");
c.convert<void, void, std::optional<T>>("junk\tjunk\t5", "\t");
REQUIRE(c.valid());
REQUIRE(tup.has_value());
CHECK_EQ(tup, 5);
}
{
auto tup = c.convert<int, double, void>("5,6.6,junk");
auto tup = c.convert<T, double, void>("5,6.6,junk");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(5, 6.6));
}
{
auto tup = c.convert<int, void, double>("5,junk,6.6");
auto tup = c.convert<T, void, double>("5,junk,6.6");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(5, 6.6));
}
{
auto tup = c.convert<void, int, double>("junk;5;6.6", ";");
auto tup = c.convert<void, T, double>("junk;5;6.6", ";");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(5, 6.6));
}
{
auto tup =
c.convert<void, std::optional<int>, double>("junk;5;6.6", ";");
auto tup = c.convert<void, std::optional<T>, double>("junk;5;6.6", ";");
REQUIRE(c.valid());
REQUIRE(std::get<0>(tup).has_value());
CHECK_EQ(tup, std::make_tuple(5, 6.6));
}
{
auto tup =
c.convert<void, std::optional<int>, double>("junk;5.4;6.6", ";");
c.convert<void, std::optional<T>, double>("junk;5.4;6.6", ";");
REQUIRE(c.valid());
REQUIRE_FALSE(std::get<0>(tup).has_value());
CHECK_EQ(tup, std::make_tuple(std::optional<int>{}, 6.6));
CHECK_EQ(tup, std::make_tuple(std::optional<T>{}, 6.6));
}
{
auto tup =
c.convert<void, std::variant<int, double>, double>("junk;5;6.6",
";");
c.convert<void, std::variant<T, double>, double>("junk;5;6.6", ";");
REQUIRE(c.valid());
REQUIRE(std::holds_alternative<int>(std::get<0>(tup)));
CHECK_EQ(tup, std::make_tuple(std::variant<int, double>{5}, 6.6));
REQUIRE(std::holds_alternative<T>(std::get<0>(tup)));
CHECK_EQ(tup, std::make_tuple(std::variant<T, double>{T(5)}, 6.6));
}
{
auto tup =
c.convert<void, std::variant<int, double>, double>("junk;5.5;6.6",
";");
c.convert<void, std::variant<T, double>, double>("junk;5.5;6.6",
";");
REQUIRE(c.valid());
REQUIRE(std::holds_alternative<double>(std::get<0>(tup)));
CHECK_EQ(tup, std::make_tuple(std::variant<int, double>{5.5}, 6.6));
CHECK_EQ(tup, std::make_tuple(std::variant<T, double>{5.5}, 6.6));
}
{
auto tup = c.convert<void, std::string_view, double,
@@ -140,11 +138,12 @@ TEST_CASE("converter test valid conversions") {
}
}
TEST_CASE("converter test valid conversions with exceptions") {
TEST_CASE_TEMPLATE("converter test valid conversions with exceptions", T, int,
ss::uint8) {
ss::converter<ss::throw_on_error> c;
try {
auto tup = c.convert<int>("5");
auto tup = c.convert<T>("5");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
} catch (ss::exception& e) {
@@ -152,7 +151,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
try {
auto tup = c.convert<int, void>("5,junk");
auto tup = c.convert<T, void>("5,junk");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
} catch (ss::exception& e) {
@@ -160,7 +159,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
try {
auto tup = c.convert<void, int>("junk,5");
auto tup = c.convert<void, T>("junk,5");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
} catch (ss::exception& e) {
@@ -168,7 +167,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
try {
auto tup = c.convert<int, void, void>("5\njunk\njunk", "\n");
auto tup = c.convert<T, void, void>("5\njunk\njunk", "\n");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
} catch (ss::exception& e) {
@@ -176,7 +175,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
try {
auto tup = c.convert<void, int, void>("junk 5 junk", " ");
auto tup = c.convert<void, T, void>("junk 5 junk", " ");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
} catch (ss::exception& e) {
@@ -184,7 +183,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
try {
auto tup = c.convert<void, void, int>("junk\tjunk\t5", "\t");
auto tup = c.convert<void, void, T>("junk\tjunk\t5", "\t");
REQUIRE(c.valid());
CHECK_EQ(tup, 5);
} catch (ss::exception& e) {
@@ -193,7 +192,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
try {
auto tup =
c.convert<void, void, std::optional<int>>("junk\tjunk\t5", "\t");
c.convert<void, void, std::optional<T>>("junk\tjunk\t5", "\t");
REQUIRE(c.valid());
REQUIRE(tup.has_value());
CHECK_EQ(tup, 5);
@@ -202,7 +201,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
try {
auto tup = c.convert<int, double, void>("5,6.6,junk");
auto tup = c.convert<T, double, void>("5,6.6,junk");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(5, 6.6));
} catch (ss::exception& e) {
@@ -210,7 +209,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
try {
auto tup = c.convert<int, void, double>("5,junk,6.6");
auto tup = c.convert<T, void, double>("5,junk,6.6");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(5, 6.6));
} catch (ss::exception& e) {
@@ -218,7 +217,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
try {
auto tup = c.convert<void, int, double>("junk;5;6.6", ";");
auto tup = c.convert<void, T, double>("junk;5;6.6", ";");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(5, 6.6));
} catch (ss::exception& e) {
@@ -226,8 +225,7 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
try {
auto tup =
c.convert<void, std::optional<int>, double>("junk;5;6.6", ";");
auto tup = c.convert<void, std::optional<T>, double>("junk;5;6.6", ";");
REQUIRE(c.valid());
REQUIRE(std::get<0>(tup).has_value());
CHECK_EQ(tup, std::make_tuple(5, 6.6));
@@ -237,32 +235,31 @@ TEST_CASE("converter test valid conversions with exceptions") {
try {
auto tup =
c.convert<void, std::optional<int>, double>("junk;5.4;6.6", ";");
c.convert<void, std::optional<T>, double>("junk;5.4;6.6", ";");
REQUIRE(c.valid());
REQUIRE_FALSE(std::get<0>(tup).has_value());
CHECK_EQ(tup, std::make_tuple(std::optional<int>{}, 6.6));
CHECK_EQ(tup, std::make_tuple(std::optional<T>{}, 6.6));
} catch (ss::exception& e) {
FAIL(std::string{e.what()});
}
try {
auto tup =
c.convert<void, std::variant<int, double>, double>("junk;5;6.6",
";");
c.convert<void, std::variant<T, double>, double>("junk;5;6.6", ";");
REQUIRE(c.valid());
REQUIRE(std::holds_alternative<int>(std::get<0>(tup)));
CHECK_EQ(tup, std::make_tuple(std::variant<int, double>{5}, 6.6));
REQUIRE(std::holds_alternative<T>(std::get<0>(tup)));
CHECK_EQ(tup, std::make_tuple(std::variant<T, double>{T(5)}, 6.6));
} catch (ss::exception& e) {
FAIL(std::string{e.what()});
}
try {
auto tup =
c.convert<void, std::variant<int, double>, double>("junk;5.5;6.6",
";");
c.convert<void, std::variant<T, double>, double>("junk;5.5;6.6",
";");
REQUIRE(c.valid());
REQUIRE(std::holds_alternative<double>(std::get<0>(tup)));
CHECK_EQ(tup, std::make_tuple(std::variant<int, double>{5.5}, 6.6));
CHECK_EQ(tup, std::make_tuple(std::variant<T, double>{5.5}, 6.6));
} catch (ss::exception& e) {
FAIL(std::string{e.what()});
}
@@ -278,110 +275,114 @@ TEST_CASE("converter test valid conversions with exceptions") {
}
}
TEST_CASE("converter test invalid conversions") {
TEST_CASE_TEMPLATE("converter test invalid conversions", T, int, ss::uint8) {
ss::converter c;
c.convert<int>("");
c.convert<T>("");
REQUIRE_FALSE(c.valid());
c.convert<int>("1", "");
c.convert<T>("1", "");
REQUIRE_FALSE(c.valid());
c.convert<int>("10", "");
c.convert<T>("10", "");
REQUIRE_FALSE(c.valid());
c.convert<int, void>("");
c.convert<T, void>("");
REQUIRE_FALSE(c.valid());
c.convert<int, void>(",junk");
c.convert<T, void>(",junk");
REQUIRE_FALSE(c.valid());
c.convert<void, int>("junk,");
c.convert<void, T>("junk,");
REQUIRE_FALSE(c.valid());
c.convert<int>("x");
c.convert<T>("x");
REQUIRE_FALSE(c.valid());
c.convert<int, void>("x");
c.convert<T, void>("x");
REQUIRE_FALSE(c.valid());
c.convert<int, void>("x,junk");
c.convert<T, void>("x,junk");
REQUIRE_FALSE(c.valid());
c.convert<void, int>("junk,x");
c.convert<void, T>("junk,x");
REQUIRE_FALSE(c.valid());
c.convert<void, std::variant<int, double>, double>("junk;.5.5;6", ";");
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";");
REQUIRE_FALSE(c.valid());
}
TEST_CASE("converter test invalid conversions with exceptions") {
TEST_CASE_TEMPLATE("converter test invalid conversions with exceptions", T, int,
ss::uint8) {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(c.convert<int>(""));
REQUIRE_EXCEPTION(c.convert<int>("1", ""));
REQUIRE_EXCEPTION(c.convert<int>("10", ""));
REQUIRE_EXCEPTION(c.convert<int, void>(""));
REQUIRE_EXCEPTION(c.convert<int, void>(",junk"));
REQUIRE_EXCEPTION(c.convert<void, int>("junk,"));
REQUIRE_EXCEPTION(c.convert<int>("x"));
REQUIRE_EXCEPTION(c.convert<int, void>("x"));
REQUIRE_EXCEPTION(c.convert<int, void>("x,junk"));
REQUIRE_EXCEPTION(c.convert<void, int>("junk,x"));
REQUIRE_EXCEPTION(c.convert<T>(""));
REQUIRE_EXCEPTION(c.convert<T>("1", ""));
REQUIRE_EXCEPTION(c.convert<T>("10", ""));
REQUIRE_EXCEPTION(c.convert<T, void>(""));
REQUIRE_EXCEPTION(c.convert<T, void>(",junk"));
REQUIRE_EXCEPTION(c.convert<void, T>("junk,"));
REQUIRE_EXCEPTION(c.convert<T>("x"));
REQUIRE_EXCEPTION(c.convert<T, void>("x"));
REQUIRE_EXCEPTION(c.convert<T, void>("x,junk"));
REQUIRE_EXCEPTION(c.convert<void, T>("junk,x"));
REQUIRE_EXCEPTION(
c.convert<void, std::variant<int, double>, double>("junk;.5.5;6", ";"));
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";"));
}
TEST_CASE("converter test ss:ax restriction (all except)") {
TEST_CASE_TEMPLATE("converter test ss:ax restriction (all except)", T, int,
ss::uint8) {
ss::converter c;
c.convert<ss::ax<int, 0>>("0");
c.convert<ss::ax<T, 0>>("0");
REQUIRE_FALSE(c.valid());
c.convert<ss::ax<int, 0, 1, 2>>("1");
c.convert<ss::ax<T, 0, 1, 2>>("1");
REQUIRE_FALSE(c.valid());
c.convert<void, char, ss::ax<int, 0, 1, 2>>("junk,c,1");
c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1");
REQUIRE_FALSE(c.valid());
c.convert<ss::ax<int, 1>, char>("1,c");
c.convert<ss::ax<T, 1>, char>("1,c");
REQUIRE_FALSE(c.valid());
{
int tup = c.convert<ss::ax<int, 1>>("3");
T tup = c.convert<ss::ax<T, 1>>("3");
REQUIRE(c.valid());
CHECK_EQ(tup, 3);
}
{
std::tuple<char, int> tup = c.convert<char, ss::ax<int, 1>>("c,3");
std::tuple<char, T> tup = c.convert<char, ss::ax<T, 1>>("c,3");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple('c', 3));
}
{
std::tuple<int, char> tup = c.convert<ss::ax<int, 1>, char>("3,c");
std::tuple<T, char> tup = c.convert<ss::ax<T, 1>, char>("3,c");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(3, 'c'));
}
}
TEST_CASE("converter test ss:ax restriction (all except) with exceptions") {
TEST_CASE_TEMPLATE(
"converter test ss:ax restriction (all except) with exceptions", T, int,
ss::uint8) {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(c.convert<ss::ax<int, 0>>("0"));
REQUIRE_EXCEPTION(c.convert<ss::ax<int, 0, 1, 2>>("1"));
REQUIRE_EXCEPTION(c.convert<void, char, ss::ax<int, 0, 1, 2>>("junk,c,1"));
REQUIRE_EXCEPTION(c.convert<ss::ax<int, 1>, char>("1,c"));
REQUIRE_EXCEPTION(c.convert<ss::ax<T, 0>>("0"));
REQUIRE_EXCEPTION(c.convert<ss::ax<T, 0, 1, 2>>("1"));
REQUIRE_EXCEPTION(c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1"));
REQUIRE_EXCEPTION(c.convert<ss::ax<T, 1>, char>("1,c"));
try {
{
int tup = c.convert<ss::ax<int, 1>>("3");
T tup = c.convert<ss::ax<T, 1>>("3");
CHECK_EQ(tup, 3);
}
{
std::tuple<char, int> tup = c.convert<char, ss::ax<int, 1>>("c,3");
std::tuple<char, T> tup = c.convert<char, ss::ax<T, 1>>("c,3");
CHECK_EQ(tup, std::make_tuple('c', 3));
}
{
std::tuple<int, char> tup = c.convert<ss::ax<int, 1>, char>("3,c");
std::tuple<T, char> tup = c.convert<ss::ax<T, 1>, char>("3,c");
CHECK_EQ(tup, std::make_tuple(3, 'c'));
}
} catch (ss::exception& e) {
@@ -456,65 +457,68 @@ TEST_CASE("converter test ss:nx restriction (none except) with exceptions") {
}
}
TEST_CASE("converter test ss:ir restriction (in range)") {
TEST_CASE_TEMPLATE("converter test ss:ir restriction (in range)", T, int,
ss::uint8) {
ss::converter c;
c.convert<ss::ir<int, 0, 2>>("3");
c.convert<ss::ir<T, 0, 2>>("3");
REQUIRE_FALSE(c.valid());
c.convert<char, ss::ir<int, 4, 69>>("c,3");
c.convert<char, ss::ir<T, 4, 69>>("c,3");
REQUIRE_FALSE(c.valid());
c.convert<ss::ir<int, 1, 2>, char>("3,c");
c.convert<ss::ir<T, 1, 2>, char>("3,c");
REQUIRE_FALSE(c.valid());
{
auto tup = c.convert<ss::ir<int, 1, 5>>("3");
auto tup = c.convert<ss::ir<T, 1, 5>>("3");
REQUIRE(c.valid());
CHECK_EQ(tup, 3);
}
{
auto tup = c.convert<ss::ir<int, 0, 2>>("2");
auto tup = c.convert<ss::ir<T, 0, 2>>("2");
REQUIRE(c.valid());
CHECK_EQ(tup, 2);
}
{
auto tup = c.convert<char, void, ss::ir<int, 0, 1>>("c,junk,1");
auto tup = c.convert<char, void, ss::ir<T, 0, 1>>("c,junk,1");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple('c', 1));
}
{
auto tup = c.convert<ss::ir<int, 1, 20>, char>("1,c");
auto tup = c.convert<ss::ir<T, 1, 20>, char>("1,c");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(1, 'c'));
}
}
TEST_CASE("converter test ss:ir restriction (in range) with exceptions") {
TEST_CASE_TEMPLATE(
"converter test ss:ir restriction (in range) with exceptions", T, int,
ss::uint8) {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(c.convert<ss::ir<int, 0, 2>>("3"));
REQUIRE_EXCEPTION(c.convert<char, ss::ir<int, 4, 69>>("c,3"));
REQUIRE_EXCEPTION(c.convert<ss::ir<int, 1, 2>, char>("3,c"));
REQUIRE_EXCEPTION(c.convert<ss::ir<T, 0, 2>>("3"));
REQUIRE_EXCEPTION(c.convert<char, ss::ir<T, 4, 69>>("c,3"));
REQUIRE_EXCEPTION(c.convert<ss::ir<T, 1, 2>, char>("3,c"));
try {
{
auto tup = c.convert<ss::ir<int, 1, 5>>("3");
auto tup = c.convert<ss::ir<T, 1, 5>>("3");
REQUIRE(c.valid());
CHECK_EQ(tup, 3);
}
{
auto tup = c.convert<ss::ir<int, 0, 2>>("2");
auto tup = c.convert<ss::ir<T, 0, 2>>("2");
REQUIRE(c.valid());
CHECK_EQ(tup, 2);
}
{
auto tup = c.convert<char, void, ss::ir<int, 0, 1>>("c,junk,1");
auto tup = c.convert<char, void, ss::ir<T, 0, 1>>("c,junk,1");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple('c', 1));
}
{
auto tup = c.convert<ss::ir<int, 1, 20>, char>("1,c");
auto tup = c.convert<ss::ir<T, 1, 20>, char>("1,c");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(1, 'c'));
}
@@ -978,4 +982,3 @@ TEST_CASE("converter test invalid split conversions with exceptions") {
buff(R"(just,some,2,"strings\")")));
CHECK(c.unterminated_quote());
}

View File

@@ -2,6 +2,32 @@
#include <algorithm>
#include <ss/extract.hpp>
namespace {
template <typename T>
struct numeric_limits : public std::numeric_limits<T> {};
template <typename T>
struct numeric_limits<ss::numeric_wrapper<T>> : public std::numeric_limits<T> {
};
template <typename T>
struct is_signed : public std::is_signed<T> {};
template <>
struct is_signed<ss::int8> : public std::true_type {};
template <typename T>
struct is_unsigned : public std::is_unsigned<T> {};
template <>
struct is_unsigned<ss::uint8> : public std::true_type {};
} /* namespace */
static_assert(is_signed<ss::int8>::value);
static_assert(is_unsigned<ss::uint8>::value);
TEST_CASE("testing extract functions for floating point values") {
CHECK_FLOATING_CONVERSION(123.456, float);
CHECK_FLOATING_CONVERSION(123.456, double);
@@ -22,18 +48,18 @@ TEST_CASE("testing extract functions for floating point values") {
#define CHECK_DECIMAL_CONVERSION(input, type) \
{ \
std::string s = #input; \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
REQUIRE(t.has_value()); \
CHECK_EQ(t.value(), type(input)); \
type value; \
bool valid = ss::extract(s.c_str(), s.c_str() + s.size(), value); \
REQUIRE(valid); \
CHECK_EQ(value, type(input)); \
} \
{ \
/* check negative too */ \
if (std::is_signed_v<type>) { \
auto s = std::string("-") + #input; \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
REQUIRE(t.has_value()); \
CHECK_EQ(t.value(), type(-input)); \
} \
/* check negative too */ \
if (is_signed<type>::value) { \
std::string s = std::string("-") + #input; \
type value; \
bool valid = ss::extract(s.c_str(), s.c_str() + s.size(), value); \
REQUIRE(valid); \
CHECK_EQ(value, type(-input)); \
}
using us = unsigned short;
@@ -43,6 +69,8 @@ using ll = long long;
using ull = unsigned long long;
TEST_CASE("extract test functions for decimal values") {
CHECK_DECIMAL_CONVERSION(12, ss::int8);
CHECK_DECIMAL_CONVERSION(12, ss::uint8);
CHECK_DECIMAL_CONVERSION(1234, short);
CHECK_DECIMAL_CONVERSION(1234, us);
CHECK_DECIMAL_CONVERSION(1234, int);
@@ -54,6 +82,9 @@ TEST_CASE("extract test functions for decimal values") {
}
TEST_CASE("extract test functions for numbers with invalid inputs") {
// negative unsigned value for numeric_wrapper
CHECK_INVALID_CONVERSION("-12", ss::uint8);
// negative unsigned value
CHECK_INVALID_CONVERSION("-1234", ul);
@@ -70,46 +101,38 @@ TEST_CASE("extract test functions for numbers with invalid inputs") {
CHECK_INVALID_CONVERSION("", int);
}
#define CHECK_OUT_OF_RANGE_CONVERSION(type) \
{ \
std::string s = std::to_string(std::numeric_limits<type>::max()); \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
CHECK(t.has_value()); \
for (auto& i : s) { \
if (i != '9' && i != '.') { \
i = '9'; \
break; \
} \
} \
t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
CHECK_FALSE(t.has_value()); \
} \
{ \
std::string s = std::to_string(std::numeric_limits<type>::min()); \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
CHECK(t.has_value()); \
for (auto& i : s) { \
if (std::is_signed_v<type> && i != '9' && i != '.') { \
i = '9'; \
break; \
} else if (std::is_unsigned_v<type>) { \
s = "-1"; \
break; \
} \
} \
t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
CHECK_FALSE(t.has_value()); \
TEST_CASE_TEMPLATE(
"extract test functions for numbers with out of range inputs", T, short, us,
int, ui, long, ul, ll, ull, ss::uint8) {
{
std::string s = std::to_string(numeric_limits<T>::max());
auto t = ss::to_num<T>(s.c_str(), s.c_str() + s.size());
CHECK(t.has_value());
for (auto& i : s) {
if (i != '9' && i != '.') {
i = '9';
break;
}
}
t = ss::to_num<T>(s.c_str(), s.c_str() + s.size());
CHECK_FALSE(t.has_value());
}
{
std::string s = std::to_string(numeric_limits<T>::min());
auto t = ss::to_num<T>(s.c_str(), s.c_str() + s.size());
CHECK(t.has_value());
for (auto& i : s) {
if (is_signed<T>::value && i != '9' && i != '.') {
i = '9';
break;
} else if (is_unsigned<T>::value) {
s = "-1";
break;
}
}
t = ss::to_num<T>(s.c_str(), s.c_str() + s.size());
CHECK_FALSE(t.has_value());
}
TEST_CASE("extract test functions for numbers with out of range inputs") {
CHECK_OUT_OF_RANGE_CONVERSION(short);
CHECK_OUT_OF_RANGE_CONVERSION(us);
CHECK_OUT_OF_RANGE_CONVERSION(int);
CHECK_OUT_OF_RANGE_CONVERSION(ui);
CHECK_OUT_OF_RANGE_CONVERSION(long);
CHECK_OUT_OF_RANGE_CONVERSION(ul);
CHECK_OUT_OF_RANGE_CONVERSION(ll);
CHECK_OUT_OF_RANGE_CONVERSION(ull);
}
TEST_CASE("extract test functions for boolean values") {
@@ -142,12 +165,12 @@ TEST_CASE("extract test functions for char values") {
}
}
TEST_CASE("extract test functions for std::optional") {
for (const auto& [i, s] :
{std::pair<std::optional<int>, std::string>{1, "1"},
{69, "69"},
{-4, "-4"}}) {
std::optional<int> v;
TEST_CASE_TEMPLATE("extract test functions for std::optional", T, int,
ss::int8) {
for (const auto& [i, s] : {std::pair<std::optional<T>, std::string>{1, "1"},
{69, "69"},
{-4, "-4"}}) {
std::optional<T> v;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
REQUIRE(v.has_value());
CHECK_EQ(*v, i);
@@ -164,7 +187,7 @@ TEST_CASE("extract test functions for std::optional") {
}
for (const std::string s : {"aa", "xxx", ""}) {
std::optional<int> v;
std::optional<T> v;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
CHECK_FALSE(v.has_value());
}
@@ -176,56 +199,57 @@ TEST_CASE("extract test functions for std::optional") {
}
}
TEST_CASE("extract test functions for std::variant") {
TEST_CASE_TEMPLATE("extract test functions for std::variant", T, int,
ss::uint8) {
{
std::string s = "22";
{
std::variant<int, double, std::string> var;
std::variant<T, double, std::string> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, double);
CHECK_NOT_VARIANT(var, std::string);
REQUIRE_VARIANT(var, 22, int);
REQUIRE_VARIANT(var, 22, T);
}
{
std::variant<double, int, std::string> var;
std::variant<double, T, std::string> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, T);
CHECK_NOT_VARIANT(var, std::string);
REQUIRE_VARIANT(var, 22, double);
}
{
std::variant<std::string, double, int> var;
std::variant<std::string, double, T> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, T);
CHECK_NOT_VARIANT(var, double);
REQUIRE_VARIANT(var, "22", std::string);
}
{
std::variant<int> var;
std::variant<T> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
REQUIRE_VARIANT(var, 22, int);
REQUIRE_VARIANT(var, 22, T);
}
}
{
std::string s = "22.2";
{
std::variant<int, double, std::string> var;
std::variant<T, double, std::string> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, T);
CHECK_NOT_VARIANT(var, std::string);
REQUIRE_VARIANT(var, 22.2, double);
}
{
std::variant<double, int, std::string> var;
std::variant<double, T, std::string> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, T);
CHECK_NOT_VARIANT(var, std::string);
REQUIRE_VARIANT(var, 22.2, double);
}
{
std::variant<std::string, double, int> var;
std::variant<std::string, double, T> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, T);
CHECK_NOT_VARIANT(var, double);
REQUIRE_VARIANT(var, "22.2", std::string);
}
@@ -233,45 +257,45 @@ TEST_CASE("extract test functions for std::variant") {
{
std::string s = "2.2.2";
{
std::variant<int, double, std::string> var;
std::variant<T, double, std::string> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, T);
CHECK_NOT_VARIANT(var, double);
REQUIRE_VARIANT(var, "2.2.2", std::string);
}
{
std::variant<double, std::string, int> var;
std::variant<double, std::string, T> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, T);
CHECK_NOT_VARIANT(var, double);
REQUIRE_VARIANT(var, "2.2.2", std::string);
}
{
std::variant<std::string, double, int> var;
std::variant<std::string, double, T> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, T);
CHECK_NOT_VARIANT(var, double);
REQUIRE_VARIANT(var, "2.2.2", std::string);
}
{
std::variant<int, double> var;
std::variant<T, double> var;
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
REQUIRE_VARIANT(var, int{}, int);
REQUIRE_VARIANT(var, T{}, T);
CHECK_NOT_VARIANT(var, double);
}
{
std::variant<double, int> var;
std::variant<double, T> var;
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
REQUIRE_VARIANT(var, double{}, double);
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, T);
}
{
std::variant<int> var;
std::variant<T> var;
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
REQUIRE_VARIANT(var, int{}, int);
REQUIRE_VARIANT(var, T{}, T);
}
}
}

View File

@@ -1,12 +1,14 @@
#pragma once
#include <algorithm>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <ss/common.hpp>
#include <ss/setup.hpp>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
#include <fstream>
#ifdef CMAKE_GITHUB_CI
#include <doctest/doctest.h>
@@ -20,6 +22,24 @@ class parser;
} /* ss */
namespace {
struct bool_error {};
template <typename T, typename U = bool_error>
struct config {
using BufferMode = T;
using ErrorMode = U;
constexpr static auto ThrowOnError = std::is_same_v<U, ss::throw_on_error>;
constexpr static auto StringError = std::is_same_v<U, ss::string_error>;
};
#define ParserOptionCombinations \
config<std::true_type>, config<std::true_type, ss::string_error>, \
config<std::true_type, ss::throw_on_error>, config<std::false_type>, \
config<std::false_type, ss::string_error>, \
config<std::false_type, ss::throw_on_error>
struct buffer {
std::string data_;
@@ -42,12 +62,12 @@ struct buffer {
[[maybe_unused]] inline buffer buff;
[[maybe_unused]] std::string time_now_rand() {
srand(time(nullptr));
std::srand(std::time(nullptr));
std::stringstream ss;
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
ss << std::put_time(&tm, "%d%m%Y%H%M%S");
srand(time(nullptr));
std::srand(std::time(nullptr));
return ss.str() + std::to_string(rand());
}
@@ -58,13 +78,17 @@ struct unique_file_name {
unique_file_name(const std::string& test) {
do {
name = "random_" + test + "_" + std::to_string(i++) + "_" +
name = "ssp_test_" + test + "_" + std::to_string(i++) + "_" +
time_now_rand() + "_file.csv";
} while (std::filesystem::exists(name));
}
~unique_file_name() {
std::filesystem::remove(name);
try {
std::filesystem::remove(name);
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << e.what() << std::endl;
}
}
};
@@ -172,23 +196,32 @@ template <typename T>
}
template <bool buffer_mode, typename... Ts>
[[maybe_unused]] std::tuple<ss::parser<Ts...>, std::string> make_parser(
const std::string& file_name, const std::string& delim = "") {
std::tuple<ss::parser<Ts...>, std::string> make_parser_impl(
const std::string& file_name, std::string delim = ss::default_delimiter) {
if (buffer_mode) {
auto buffer = make_buffer(file_name);
if (delim.empty()) {
return {ss::parser<Ts...>{buffer.data(), buffer.size()},
std::move(buffer)};
} else {
return {ss::parser<Ts...>{buffer.data(), buffer.size(), delim},
std::move(buffer)};
}
return {ss::parser<Ts...>{buffer.data(), buffer.size(), delim},
std::move(buffer)};
} else {
if (delim.empty()) {
return {ss::parser<Ts...>{file_name}, std::string{}};
} else {
return {ss::parser<Ts...>{file_name, delim}, std::string{}};
}
return {ss::parser<Ts...>{file_name, delim}, std::string{}};
}
}
template <bool buffer_mode, typename ErrorMode, typename... Ts>
[[maybe_unused]] std::enable_if_t<
!std::is_same_v<ErrorMode, bool_error>,
std::tuple<ss::parser<ErrorMode, Ts...>, std::string>>
make_parser(const std::string& file_name,
std::string delim = ss::default_delimiter) {
return make_parser_impl<buffer_mode, ErrorMode, Ts...>(file_name, delim);
}
template <bool buffer_mode, typename ErrorMode, typename... Ts>
[[maybe_unused]] std::enable_if_t<std::is_same_v<ErrorMode, bool_error>,
std::tuple<ss::parser<Ts...>, std::string>>
make_parser(const std::string& file_name,
std::string delim = ss::default_delimiter) {
return make_parser_impl<buffer_mode, Ts...>(file_name, delim);
}
} /* namespace */

View File

@@ -1,7 +1,7 @@
#include "test_parser1.hpp"
TEST_CASE("test file not found") {
unique_file_name f{"test_parser"};
unique_file_name f{"file_not_found"};
{
ss::parser p{f.name, ","};
@@ -11,6 +11,7 @@ TEST_CASE("test file not found") {
{
ss::parser<ss::string_error> p{f.name, ","};
CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty());
}
try {
@@ -30,6 +31,7 @@ TEST_CASE("test null buffer") {
{
ss::parser<ss::string_error> p{nullptr, 10, ","};
CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty());
}
try {
@@ -40,20 +42,104 @@ TEST_CASE("test null buffer") {
}
}
template <bool buffer_mode, typename... Ts>
void test_various_cases() {
unique_file_name f{"test_parser"};
struct Y {
constexpr static auto delim = ",";
std::string s1;
std::string s2;
std::string s3;
std::string to_string() const {
return std::string{}
.append(s1)
.append(delim)
.append(s2)
.append(delim)
.append(s3);
}
auto tied() const {
return std::tie(s1, s2, s3);
}
};
TEST_CASE_TEMPLATE("test position method", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"position_method"};
std::vector<Y> data = {{"1", "21", "x"}, {"321", "4", "y"},
{"54", "6", "zz"}, {"7", "876", "uuuu"},
{"910", "10", "v"}, {"10", "321", "ww"}};
make_and_write(f.name, data);
auto [p, buff] = make_parser<buffer_mode, ErrorMode>(f.name);
auto data_at = [&buff = buff, &f = f](auto n) {
if (!buff.empty()) {
return buff[n];
} else {
auto file = std::fopen(f.name.c_str(), "r");
std::fseek(file, n, SEEK_SET);
return static_cast<char>(std::fgetc(file));
}
};
while (!p.eof()) {
auto curr_char = p.position();
const auto& [s1, s2, s3] =
p.template get_next<std::string, std::string, std::string>();
auto s = s1 + "," + s2 + "," + s3;
for (size_t i = 0; i < s1.size(); ++i) {
CHECK_EQ(data_at(curr_char + i), s[i]);
}
auto last_char = data_at(curr_char + s.size());
CHECK((last_char == '\n' || last_char == '\r'));
}
}
TEST_CASE_TEMPLATE("test line method", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"line_method"};
std::vector<Y> data = {{"1", "21", "x"}, {"321", "4", "y"},
{"54", "6", "zz"}, {"7", "876", "uuuu"},
{"910", "10", "v"}, {"10", "321", "ww"}};
make_and_write(f.name, data);
auto [p, buff] = make_parser<buffer_mode, ErrorMode>(f.name);
auto expected_line = 0;
CHECK_EQ(p.line(), expected_line);
while (!p.eof()) {
auto _ = p.template get_next<std::string, std::string, std::string>();
++expected_line;
CHECK_EQ(p.line(), expected_line);
}
CHECK_EQ(p.line(), data.size());
}
TEST_CASE_TEMPLATE("parser test various valid cases", T,
ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"various_valid_cases"};
std::vector<X> data = {{1, 2, "x"}, {3, 4, "y"}, {5, 6, "z"},
{7, 8, "u"}, {9, 10, "v"}, {11, 12, "w"}};
make_and_write(f.name, data);
auto csv_data_buffer = make_buffer(f.name);
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
ss::parser p0{std::move(p)};
p = std::move(p0);
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
auto move_rotate = [&p = p, &p0 = p0] {
@@ -77,13 +163,13 @@ void test_various_cases() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
auto [p3, ___] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p3, ___] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i3;
std::vector<X> expected = {std::begin(data) + 1, std::end(data)};
@@ -112,9 +198,9 @@ void test_various_cases() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
while (!p.eof()) {
@@ -131,7 +217,7 @@ void test_various_cases() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
for (auto&& a :
@@ -143,10 +229,10 @@ void test_various_cases() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
using tup = std::tuple<int, double, std::string>;
@@ -164,7 +250,7 @@ void test_various_cases() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
using tup = std::tuple<int, double, std::string>;
@@ -176,7 +262,7 @@ void test_various_cases() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
while (!p.eof()) {
@@ -187,7 +273,7 @@ void test_various_cases() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
for (auto&& a : p.template iterate<X>()) {
@@ -199,10 +285,10 @@ void test_various_cases() {
{
constexpr int excluded = 3;
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
while (!p.eof()) {
@@ -217,7 +303,7 @@ void test_various_cases() {
};
}
if (!ss::setup<Ts...>::throw_on_error) {
if (!T::ThrowOnError) {
for (auto&& a : p2.template iterate_object<X, ss::ax<int, excluded>,
double, std::string>()) {
if (p2.valid()) {
@@ -237,16 +323,16 @@ void test_various_cases() {
[&](const X& x) { return x.i != excluded; });
CHECK_EQ(i, expected);
if (!ss::setup<Ts...>::throw_on_error) {
if (!T::ThrowOnError) {
CHECK_EQ(i2, expected);
}
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
while (!p.eof()) {
@@ -261,7 +347,7 @@ void test_various_cases() {
}
}
if (!ss::setup<Ts...>::throw_on_error) {
if (!T::ThrowOnError) {
for (auto&& a : p2.template iterate_object<X, ss::nx<int, 3>,
double, std::string>()) {
if (p2.valid()) {
@@ -272,21 +358,21 @@ void test_various_cases() {
std::vector<X> expected = {{3, 4, "y"}};
CHECK_EQ(i, expected);
if (!ss::setup<Ts...>::throw_on_error) {
if (!T::ThrowOnError) {
CHECK_EQ(i2, expected);
}
}
{
unique_file_name empty_f{"test_parser"};
unique_file_name empty_f{"various_valid_cases"};
std::vector<X> empty_data = {};
make_and_write(empty_f.name, empty_data);
auto [p, _] = make_parser<buffer_mode, Ts...>(empty_f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(empty_f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, Ts...>(empty_f.name, ",");
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(empty_f.name, ",");
std::vector<X> i2;
while (!p.eof()) {
@@ -302,15 +388,6 @@ void test_various_cases() {
}
}
TEST_CASE("parser test various cases") {
test_various_cases<false>();
test_various_cases<false, ss::string_error>();
test_various_cases<false, ss::throw_on_error>();
test_various_cases<true>();
test_various_cases<true, ss::string_error>();
test_various_cases<true, ss::throw_on_error>();
}
using test_tuple = std::tuple<double, char, double>;
struct test_struct {
int i;
@@ -324,9 +401,10 @@ struct test_struct {
static inline void expect_test_struct(const test_struct&) {
}
template <bool buffer_mode, typename... Ts>
void test_composite_conversion() {
unique_file_name f{"test_parser"};
TEST_CASE_TEMPLATE("parser test composite conversion", BufferMode,
std::true_type, std::false_type) {
constexpr auto buffer_mode = BufferMode::value;
unique_file_name f{"composite_conversion"};
{
std::ofstream out{f.name};
for (auto& i :
@@ -336,7 +414,7 @@ void test_composite_conversion() {
}
}
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ss::string_error>(f.name, ",");
auto fail = [] { FAIL(""); };
auto expect_error = [](auto error) { CHECK(!error.empty()); };
auto ignore_error = [] {};
@@ -546,18 +624,12 @@ void test_composite_conversion() {
CHECK(p.eof());
}
// various scenarios
TEST_CASE("parser test composite conversion") {
test_composite_conversion<false, ss::string_error>();
test_composite_conversion<true, ss::string_error>();
}
template <bool buffer_mode>
template <bool buffer_mode, typename... Ts>
void test_no_new_line_at_eof_impl(const std::vector<X>& data) {
unique_file_name f{"test_parser"};
unique_file_name f{"no_new_line_at_eof"};
make_and_write(f.name, data, {}, false);
auto [p, _] = make_parser<buffer_mode>(f.name);
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name);
std::vector<X> parsed_data;
for (const auto& el : p.template iterate<X>()) {
@@ -567,16 +639,36 @@ void test_no_new_line_at_eof_impl(const std::vector<X>& data) {
CHECK_EQ(data, parsed_data);
}
template <bool buffer_mode>
template <bool buffer_mode, typename... Ts>
void test_no_new_line_at_eof() {
test_no_new_line_at_eof_impl<buffer_mode>({});
test_no_new_line_at_eof_impl<buffer_mode>({{1, 2, "X"}});
test_no_new_line_at_eof_impl<buffer_mode>({{1, 2, "X"}, {3, 4, "YY"}});
test_no_new_line_at_eof_impl<buffer_mode>(
test_no_new_line_at_eof_impl<buffer_mode, Ts...>({});
test_no_new_line_at_eof_impl<buffer_mode, Ts...>({{1, 2, "X"}});
test_no_new_line_at_eof_impl<buffer_mode, Ts...>({{1, 2, "X"}, {}});
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
{{1, 2, "X"}, {3, 4, "YY"}});
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
{{1, 2, "X"}, {3, 4, "YY"}, {}});
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
{{1, 2, "X"}, {3, 4, "YY"}, {5, 6, "ZZZ"}, {7, 8, "UUU"}});
for (size_t i = 0; i < 2 * ss::get_line_initial_buffer_size; ++i) {
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
{{1, 2, std::string(i, 'X')}});
for (size_t j = 0; j < 2 * ss::get_line_initial_buffer_size; j += 13) {
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
{{1, 2, std::string(i, 'X')}, {3, 4, std::string(j, 'Y')}});
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
{{1, 2, std::string(j, 'X')}, {3, 4, std::string(i, 'Y')}});
}
}
}
TEST_CASE("test no new line at end of data") {
test_no_new_line_at_eof<false>();
test_no_new_line_at_eof<true>();
TEST_CASE_TEMPLATE("test no new line at end of data", T,
ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
test_no_new_line_at_eof<buffer_mode, ErrorMode>();
}

View File

@@ -41,11 +41,16 @@ struct xyz {
}
};
template <bool buffer_mode, typename... Ts>
void test_moving_of_parsed_composite_values() {
TEST_CASE_TEMPLATE("test moving of parsed composite values", T,
config<std::true_type>, config<std::false_type>,
config<std::true_type, ss::string_error>,
config<std::false_type, ss::string_error>) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
// to compile is enough
return;
auto [p, _] = make_parser<buffer_mode, Ts...>("", "");
auto [p, _] = make_parser<buffer_mode, ErrorMode>("", "");
p.template try_next<my_string, my_string, my_string>()
.template or_else<my_string, my_string, my_string, my_string>(
[](auto&&) {})
@@ -56,70 +61,41 @@ void test_moving_of_parsed_composite_values() {
[](auto&, auto&, auto&) {});
}
TEST_CASE("parser test the moving of parsed composite values") {
test_moving_of_parsed_composite_values<false>();
test_moving_of_parsed_composite_values<false, ss::string_error>();
test_moving_of_parsed_composite_values<true>();
test_moving_of_parsed_composite_values<true, ss::string_error>();
}
TEST_CASE("parser test error mode") {
unique_file_name f{"test_parser"};
TEST_CASE_TEMPLATE("parser test string error mode", BufferMode, std::true_type,
std::false_type) {
unique_file_name f{"string_error"};
{
std::ofstream out{f.name};
out << "junk" << std::endl;
out << "junk" << std::endl;
}
{
auto [p, _] = make_parser<false, ss::string_error>(f.name, ",");
auto [p, _] = make_parser<BufferMode::value, ss::string_error>(f.name, ",");
REQUIRE_FALSE(p.eof());
p.get_next<int>();
CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty());
}
{
auto [p, _] = make_parser<true, ss::string_error>(f.name, ",");
REQUIRE_FALSE(p.eof());
p.get_next<int>();
CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty());
}
REQUIRE_FALSE(p.eof());
p.template get_next<int>();
CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty());
}
TEST_CASE("parser throw on error mode") {
unique_file_name f{"test_parser"};
TEST_CASE_TEMPLATE("parser throw on error mode", BufferMode, std::true_type,
std::false_type) {
unique_file_name f{"throw_on_error"};
{
std::ofstream out{f.name};
out << "junk" << std::endl;
out << "junk" << std::endl;
}
{
auto [p, _] = make_parser<false, ss::throw_on_error>(f.name, ",");
auto [p, _] =
make_parser<BufferMode::value, ss::throw_on_error>(f.name, ",");
REQUIRE_FALSE(p.eof());
try {
p.get_next<int>();
FAIL("Expected exception...");
} catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty());
}
}
{
auto [p, _] = make_parser<true, ss::throw_on_error>(f.name, ",");
REQUIRE_FALSE(p.eof());
try {
p.get_next<int>();
FAIL("Expected exception...");
} catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty());
}
REQUIRE_FALSE(p.eof());
try {
p.template get_next<int>();
FAIL("Expected exception...");
} catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty());
}
}
@@ -130,9 +106,11 @@ static inline std::string no_quote(const std::string& s) {
return s;
}
template <bool buffer_mode, typename... Ts>
void test_quote_multiline() {
unique_file_name f{"test_parser"};
TEST_CASE_TEMPLATE("test quote multiline", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"quote_multiline"};
std::vector<X> data = {{1, 2, "\"x\r\nx\nx\""},
{3, 4, "\"y\ny\r\ny\""},
{5, 6, "\"z\nz\""},
@@ -151,9 +129,8 @@ void test_quote_multiline() {
}
}
auto [p, _] =
make_parser<buffer_mode, ss::multiline, ss::quote<'"'>, Ts...>(f.name,
",");
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::multiline,
ss::quote<'"'>>(f.name, ",");
std::vector<X> i;
@@ -168,7 +145,7 @@ void test_quote_multiline() {
CHECK_EQ(i, data);
auto [p_no_multiline, __] =
make_parser<buffer_mode, ss::quote<'"'>, Ts...>(f.name, ",");
make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name, ",");
while (!p.eof()) {
auto command = [&p_no_multiline = p_no_multiline] {
p_no_multiline.template get_next<int, double, std::string>();
@@ -177,23 +154,16 @@ void test_quote_multiline() {
}
}
TEST_CASE("parser test csv on multiple lines with quotes") {
test_quote_multiline<false>();
test_quote_multiline<false, ss::string_error>();
test_quote_multiline<false, ss::throw_on_error>();
test_quote_multiline<true>();
test_quote_multiline<true, ss::string_error>();
test_quote_multiline<true, ss::throw_on_error>();
}
static inline std::string no_escape(std::string& s) {
s.erase(std::remove(begin(s), end(s), '\\'), end(s));
return s;
}
template <bool buffer_mode, typename... Ts>
void test_escape_multiline() {
unique_file_name f{"test_parser"};
TEST_CASE_TEMPLATE("test escape multiline", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"escape_multiline"};
std::vector<X> data = {{1, 2, "x\\\nx\\\r\nx"},
{5, 6, "z\\\nz\\\nz"},
{7, 8, "u"},
@@ -212,9 +182,8 @@ void test_escape_multiline() {
}
}
auto [p, _] =
make_parser<buffer_mode, ss::multiline, ss::escape<'\\'>, Ts...>(f.name,
",");
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::multiline,
ss::escape<'\\'>>(f.name, ",");
std::vector<X> i;
while (!p.eof()) {
@@ -228,7 +197,7 @@ void test_escape_multiline() {
CHECK_EQ(i, data);
auto [p_no_multiline, __] =
make_parser<buffer_mode, ss::escape<'\\'>, Ts...>(f.name, ",");
make_parser<buffer_mode, ErrorMode, ss::escape<'\\'>>(f.name, ",");
while (!p.eof()) {
auto command = [&p_no_multiline = p_no_multiline] {
auto a =
@@ -238,18 +207,11 @@ void test_escape_multiline() {
}
}
TEST_CASE("parser test csv on multiple lines with escapes") {
test_escape_multiline<false>();
test_escape_multiline<false, ss::string_error>();
test_escape_multiline<false, ss::throw_on_error>();
test_escape_multiline<true>();
test_escape_multiline<true, ss::string_error>();
test_escape_multiline<true, ss::throw_on_error>();
}
TEST_CASE_TEMPLATE("test quote escape multiline", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
template <bool buffer_mode, typename... Ts>
void test_quote_escape_multiline() {
unique_file_name f{"test_parser"};
unique_file_name f{"quote_escape_multiline"};
{
std::ofstream out{f.name};
out << "1,2,\"just\\\n\nstrings\"" << std::endl;
@@ -266,8 +228,8 @@ void test_quote_escape_multiline() {
size_t bad_lines = 1;
auto num_errors = 0;
auto [p, _] = make_parser<buffer_mode, ss::multiline, ss::escape<'\\'>,
ss::quote<'"'>, Ts...>(f.name);
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::multiline,
ss::escape<'\\'>, ss::quote<'"'>>(f.name);
std::vector<X> i;
while (!p.eof()) {
@@ -298,12 +260,3 @@ void test_quote_escape_multiline() {
}
CHECK_EQ(i, data);
}
TEST_CASE("parser test csv on multiple lines with quotes and escapes") {
test_quote_escape_multiline<false>();
test_quote_escape_multiline<false, ss::string_error>();
test_quote_escape_multiline<false, ss::throw_on_error>();
test_quote_escape_multiline<true>();
test_quote_escape_multiline<true, ss::string_error>();
test_quote_escape_multiline<true, ss::throw_on_error>();
}

View File

@@ -1,8 +1,10 @@
#include "test_parser1.hpp"
template <bool buffer_mode, typename... Ts>
void test_multiline_restricted() {
unique_file_name f{"test_parser"};
TEST_CASE_TEMPLATE("test multiline restricted", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"multiline_restricted"};
{
std::ofstream out{f.name};
out << "1,2,\"just\n\nstrings\"" << std::endl;
@@ -14,18 +16,19 @@ void test_multiline_restricted() {
out << "5,6,just\\\n\\\nstrings" << std::endl;
#endif
out << "7,8,ju\\\n\\\n\\\nnk" << std::endl;
out << "99,100,\"\n\n\n\n" << std::endl;
out << "9,10,\"just\\\n\nstrings\"" << std::endl;
out << "11,12,\"ju\\\n|\n\n\n\n\nk\"" << std::endl;
out << "13,14,\"ju\\\n\\\n15,16\"\\\n\\\\\n\nnk\"" << std::endl;
out << "17,18,\"ju\\\n\\\n\\\n\\\\\n\nnk\"" << std::endl;
out << "19,20,just strings" << std::endl;
}
auto bad_lines = 15;
auto bad_lines = 20;
auto num_errors = 0;
auto [p, _] =
make_parser<buffer_mode, ss::multiline_restricted<2>, ss::quote<'"'>,
ss::escape<'\\'>, Ts...>(f.name, ",");
make_parser<buffer_mode, ErrorMode, ss::multiline_restricted<2>,
ss::quote<'"'>, ss::escape<'\\'>>(f.name, ",");
std::vector<X> i;
while (!p.eof()) {
@@ -63,26 +66,20 @@ void test_multiline_restricted() {
CHECK_EQ(i, data);
}
TEST_CASE("parser test multiline restricted") {
test_multiline_restricted<false>();
test_multiline_restricted<false, ss::string_error>();
test_multiline_restricted<false, ss::throw_on_error>();
test_multiline_restricted<true>();
test_multiline_restricted<true, ss::string_error>();
test_multiline_restricted<true, ss::throw_on_error>();
}
template <typename T, typename... Ts>
void test_unterminated_line(const std::vector<std::string>& lines,
size_t bad_line) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
template <bool buffer_mode, typename... Ts>
void test_unterminated_line_impl(const std::vector<std::string>& lines,
size_t bad_line) {
unique_file_name f{"test_parser"};
unique_file_name f{"unterminated_line"};
std::ofstream out{f.name};
for (const auto& line : lines) {
out << line << std::endl;
}
out.close();
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name);
auto [p, _] = make_parser<buffer_mode, ErrorMode, Ts...>(f.name);
size_t line = 0;
while (!p.eof()) {
auto command = [&p = p] {
@@ -100,21 +97,8 @@ void test_unterminated_line_impl(const std::vector<std::string>& lines,
}
}
template <typename... Ts>
void test_unterminated_line(const std::vector<std::string>& lines,
size_t bad_line) {
test_unterminated_line_impl<false, Ts...>(lines, bad_line);
test_unterminated_line_impl<false, Ts..., ss::string_error>(lines,
bad_line);
test_unterminated_line_impl<false, Ts..., ss::throw_on_error>(lines,
bad_line);
test_unterminated_line_impl<true, Ts...>(lines, bad_line);
test_unterminated_line_impl<true, Ts..., ss::string_error>(lines, bad_line);
test_unterminated_line_impl<true, Ts..., ss::throw_on_error>(lines,
bad_line);
}
TEST_CASE("parser test csv on multiline with errors") {
TEST_CASE_TEMPLATE("parser test csv on multiline with errors", T,
ParserOptionCombinations) {
using multiline = ss::multiline_restricted<3>;
using escape = ss::escape<'\\'>;
using quote = ss::quote<'"'>;
@@ -122,209 +106,209 @@ TEST_CASE("parser test csv on multiline with errors") {
// unterminated escape
{
const std::vector<std::string> lines{"1,2,just\\"};
test_unterminated_line<multiline, escape>(lines, 0);
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, escape>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"1,2,just\\", "9,8,second"};
test_unterminated_line<multiline, escape>(lines, 0);
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, escape>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"9,8,first", "1,2,just\\"};
test_unterminated_line<multiline, escape>(lines, 1);
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,first", "1,2,just\\",
"3,4,third"};
test_unterminated_line<multiline, escape>(lines, 1);
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,first",
"1,2,just\\\nstrings\\",
"3,4,th\\\nird"};
test_unterminated_line<multiline, escape>(lines, 1);
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,first", "3,4,second",
"1,2,just\\"};
test_unterminated_line<multiline, escape>(lines, 2);
test_unterminated_line<multiline, escape, quote>(lines, 2);
test_unterminated_line<T, multiline, escape>(lines, 2);
test_unterminated_line<T, multiline, escape, quote>(lines, 2);
}
{
const std::vector<std::string> lines{"9,8,\\first", "3,4,second",
"1,2,jus\\t\\"};
test_unterminated_line<multiline, escape>(lines, 2);
test_unterminated_line<multiline, escape, quote>(lines, 2);
test_unterminated_line<T, multiline, escape>(lines, 2);
test_unterminated_line<T, multiline, escape, quote>(lines, 2);
}
// unterminated quote
{
const std::vector<std::string> lines{"1,2,\"just"};
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<multiline, quote>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"1,2,\"just", "9,8,second"};
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<multiline, quote>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"9,8,first", "1,2,\"just"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<multiline, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,first", "1,2,\"just",
"3,4,th\\,ird"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<multiline, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,first", "3,4,second",
"1,2,\"just"};
test_unterminated_line<multiline, escape, quote>(lines, 2);
test_unterminated_line<multiline, quote>(lines, 2);
test_unterminated_line<T, multiline, escape, quote>(lines, 2);
test_unterminated_line<T, multiline, quote>(lines, 2);
}
{
const std::vector<std::string> lines{"9,8,\"first\"",
"\"3\",4,\"sec,ond\"",
"1,2,\"ju\"\"st"};
test_unterminated_line<multiline, escape, quote>(lines, 2);
test_unterminated_line<multiline, quote>(lines, 2);
test_unterminated_line<T, multiline, escape, quote>(lines, 2);
test_unterminated_line<T, multiline, quote>(lines, 2);
}
// unterminated quote and escape
{
const std::vector<std::string> lines{"1,2,\"just\\"};
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"1,2,\"just\\\n\\"};
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"1,2,\"just\n\\"};
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"9,8,first", "1,2,\"just\n\\"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,first", "1,2,\"just\n\\",
"4,3,thrid"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,f\\\nirst", "1,2,\"just\n\\",
"4,3,thrid"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,\"f\ni\nrst\"",
"1,2,\"just\n\\", "4,3,thrid"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
// multiline limmit reached escape
{
const std::vector<std::string> lines{"1,2,\\\n\\\n\\\n\\\njust"};
test_unterminated_line<multiline, escape>(lines, 0);
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, escape>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"9,8,first",
"1,2,\\\n\\\n\\\n\\\njust"};
test_unterminated_line<multiline, escape>(lines, 1);
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,fi\\\nrs\\\nt",
"1,2,\\\n\\\n\\\n\\\njust"};
test_unterminated_line<multiline, escape>(lines, 1);
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,first",
"1,2,\\\n\\\n\\\n\\\njust",
"4,3,third"};
test_unterminated_line<multiline, escape>(lines, 1);
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
// multiline limmit reached quote
{
const std::vector<std::string> lines{"1,2,\"\n\n\n\n\njust\""};
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<multiline, quote>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"9,8,first",
"1,2,\"\n\n\n\n\njust\""};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<multiline, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,\"fir\nst\"",
"1,2,\"\n\n\n\n\njust\""};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<multiline, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, quote>(lines, 1);
}
// multiline limmit reached quote and escape
{
const std::vector<std::string> lines{"1,2,\"\\\n\n\\\n\\\n\\\njust"};
test_unterminated_line<multiline, escape, quote>(lines, 0);
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"9,8,first",
"1,2,\"\\\n\n\\\n\\\n\\\njust"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,fi\\\nrst",
"1,2,\"\\\n\n\\\n\\\n\\\njust"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,\"fi\nrst\"",
"1,2,\"\\\n\n\\\n\\\n\\\njust"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
{
const std::vector<std::string> lines{"9,8,\"fi\nr\\\nst\"",
"1,2,\"\\\n\n\\\n\\\n\\\njust"};
test_unterminated_line<multiline, escape, quote>(lines, 1);
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
}
}

View File

@@ -7,13 +7,14 @@ template <typename T, typename... Us>
struct has_type<T, std::tuple<Us...>>
: std::disjunction<std::is_same<T, Us>...> {};
template <bool buffer_mode, typename Setup, typename... Ts>
static void test_fields_impl(const std::string file_name,
const std::vector<X>& data,
const std::vector<std::string>& fields) {
template <typename T, typename... Ts>
static void test_fields(const std::string file_name, const std::vector<X>& data,
const std::vector<std::string>& fields) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
using CaseType = std::tuple<Ts...>;
auto [p, _] = make_parser<buffer_mode, Setup>(file_name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(file_name, ",");
CHECK_FALSE(p.field_exists("Unknown"));
p.use_fields(fields);
std::vector<CaseType> i;
@@ -36,23 +37,9 @@ static void test_fields_impl(const std::string file_name,
}
}
template <typename... Ts>
static void test_fields(const std::string file_name, const std::vector<X>& data,
const std::vector<std::string>& fields) {
test_fields_impl<false, ss::setup<>, Ts...>(file_name, data, fields);
test_fields_impl<false, ss::setup<ss::string_error>, Ts...>(file_name, data,
fields);
test_fields_impl<false, ss::setup<ss::throw_on_error>, Ts...>(file_name,
data, fields);
test_fields_impl<true, ss::setup<>, Ts...>(file_name, data, fields);
test_fields_impl<true, ss::setup<ss::string_error>, Ts...>(file_name, data,
fields);
test_fields_impl<true, ss::setup<ss::throw_on_error>, Ts...>(file_name,
data, fields);
}
TEST_CASE("parser test various cases with header") {
unique_file_name f{"test_parser"};
TEST_CASE_TEMPLATE("test various cases with header", T,
ParserOptionCombinations) {
unique_file_name f{"various_cases_with_header"};
constexpr static auto Int = "Int";
constexpr static auto Dbl = "Double";
constexpr static auto Str = "String";
@@ -180,27 +167,30 @@ TEST_CASE("parser test various cases with header") {
print(call)
*/
test_fields<str>(o, d, {Str});
test_fields<int>(o, d, {Int});
test_fields<double>(o, d, {Dbl});
test_fields<str, int>(o, d, {Str, Int});
test_fields<str, double>(o, d, {Str, Dbl});
test_fields<int, str>(o, d, {Int, Str});
test_fields<int, double>(o, d, {Int, Dbl});
test_fields<double, str>(o, d, {Dbl, Str});
test_fields<double, int>(o, d, {Dbl, Int});
test_fields<str, int, double>(o, d, {Str, Int, Dbl});
test_fields<str, double, int>(o, d, {Str, Dbl, Int});
test_fields<int, str, double>(o, d, {Int, Str, Dbl});
test_fields<int, double, str>(o, d, {Int, Dbl, Str});
test_fields<double, str, int>(o, d, {Dbl, Str, Int});
test_fields<double, int, str>(o, d, {Dbl, Int, Str});
test_fields<T, str>(o, d, {Str});
test_fields<T, int>(o, d, {Int});
test_fields<T, double>(o, d, {Dbl});
test_fields<T, str, int>(o, d, {Str, Int});
test_fields<T, str, double>(o, d, {Str, Dbl});
test_fields<T, int, str>(o, d, {Int, Str});
test_fields<T, int, double>(o, d, {Int, Dbl});
test_fields<T, double, str>(o, d, {Dbl, Str});
test_fields<T, double, int>(o, d, {Dbl, Int});
test_fields<T, str, int, double>(o, d, {Str, Int, Dbl});
test_fields<T, str, double, int>(o, d, {Str, Dbl, Int});
test_fields<T, int, str, double>(o, d, {Int, Str, Dbl});
test_fields<T, int, double, str>(o, d, {Int, Dbl, Str});
test_fields<T, double, str, int>(o, d, {Dbl, Str, Int});
test_fields<T, double, int, str>(o, d, {Dbl, Int, Str});
}
template <bool buffer_mode, typename... Ts>
void test_invalid_fields_impl(const std::vector<std::string>& lines,
const std::vector<std::string>& fields) {
unique_file_name f{"test_parser"};
template <typename T>
void test_invalid_fields(const std::vector<std::string>& lines,
const std::vector<std::string>& fields) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"invalid_fields"};
{
std::ofstream out{f.name};
for (const auto& line : lines) {
@@ -210,21 +200,21 @@ void test_invalid_fields_impl(const std::vector<std::string>& lines,
{
// No fields specified
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p] { p.use_fields(); };
expect_error_on_command(p, command);
}
{
// Unknown field
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p] { p.use_fields("Unknown"); };
expect_error_on_command(p, command);
}
{
// Field used multiple times
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p, &fields = fields] {
p.use_fields(fields.at(0), fields.at(0));
};
@@ -235,7 +225,7 @@ void test_invalid_fields_impl(const std::vector<std::string>& lines,
{
// Mapping out of range
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
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>();
@@ -247,7 +237,7 @@ void test_invalid_fields_impl(const std::vector<std::string>& lines,
{
// Invalid header
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p, &fields = fields] { p.use_fields(fields); };
if (!fields.empty()) {
@@ -259,7 +249,7 @@ void test_invalid_fields_impl(const std::vector<std::string>& lines,
command();
CHECK(p.valid());
if (!p.valid()) {
if constexpr (ss::setup<Ts...>::string_error) {
if constexpr (T::StringError) {
std::cout << p.error_msg() << std::endl;
}
}
@@ -268,44 +258,38 @@ void test_invalid_fields_impl(const std::vector<std::string>& lines,
}
}
template <typename... Ts>
void test_invalid_fields(const std::vector<std::string>& lines,
const std::vector<std::string>& fields) {
test_invalid_fields_impl<false>(lines, fields);
test_invalid_fields_impl<false, ss::string_error>(lines, fields);
test_invalid_fields_impl<false, ss::throw_on_error>(lines, fields);
test_invalid_fields_impl<true>(lines, fields);
test_invalid_fields_impl<true, ss::string_error>(lines, fields);
test_invalid_fields_impl<true, ss::throw_on_error>(lines, fields);
TEST_CASE_TEMPLATE("test invalid fheader fields usage", T,
ParserOptionCombinations) {
test_invalid_fields<T>({}, {});
test_invalid_fields<T>({"Int"}, {"Int"});
test_invalid_fields<T>({"Int", "1"}, {"Int"});
test_invalid_fields<T>({"Int", "1", "2"}, {"Int"});
test_invalid_fields<T>({"Int,String"}, {"Int", "String"});
test_invalid_fields<T>({"Int,String", "1,hi"}, {"Int", "String"});
test_invalid_fields<T>({"Int,String", "2,hello"}, {"Int", "String"});
test_invalid_fields<T>({"Int,String,Double"}, {"Int", "String", "Double"});
test_invalid_fields<T>({"Int,String,Double", "1,hi,2.34"},
{"Int", "String", "Double"});
test_invalid_fields<T>({"Int,String,Double", "1,hi,2.34", "2,hello,3.45"},
{"Int", "String", "Double"});
test_invalid_fields<T>({"Int,Int,Int"}, {"Int", "Int", "Int"});
test_invalid_fields<T>({"Int,Int,Int", "1,2,3"}, {"Int", "Int", "Int"});
test_invalid_fields<T>({"Int,String,Int"}, {"Int", "String", "Int"});
test_invalid_fields<T>({"Int,String,Int", "1,hi,3"},
{"Int", "String", "Int"});
}
TEST_CASE("parser test invalid header fields usage") {
test_invalid_fields({}, {});
TEST_CASE_TEMPLATE("test invalid rows with header", T,
ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
test_invalid_fields({"Int"}, {"Int"});
test_invalid_fields({"Int", "1"}, {"Int"});
test_invalid_fields({"Int", "1", "2"}, {"Int"});
test_invalid_fields({"Int,String"}, {"Int", "String"});
test_invalid_fields({"Int,String", "1,hi"}, {"Int", "String"});
test_invalid_fields({"Int,String", "2,hello"}, {"Int", "String"});
test_invalid_fields({"Int,String,Double"}, {"Int", "String", "Double"});
test_invalid_fields({"Int,String,Double", "1,hi,2.34"},
{"Int", "String", "Double"});
test_invalid_fields({"Int,String,Double", "1,hi,2.34", "2,hello,3.45"},
{"Int", "String", "Double"});
test_invalid_fields({"Int,Int,Int"}, {"Int", "Int", "Int"});
test_invalid_fields({"Int,Int,Int", "1,2,3"}, {"Int", "Int", "Int"});
test_invalid_fields({"Int,String,Int"}, {"Int", "String", "Int"});
test_invalid_fields({"Int,String,Int", "1,hi,3"}, {"Int", "String", "Int"});
}
template <bool buffer_mode, typename... Ts>
void test_invalid_rows_with_header() {
unique_file_name f{"test_parser"};
unique_file_name f{"invalid rows with header"};
{
std::ofstream out{f.name};
out << "Int,String,Double" << std::endl;
@@ -318,7 +302,7 @@ void test_invalid_rows_with_header() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name);
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
p.use_fields("Int", "String", "Double");
using data = std::tuple<int, std::string, double>;
@@ -344,7 +328,7 @@ void test_invalid_rows_with_header() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name);
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
p.use_fields("Double", "Int");
using data = std::tuple<double, int>;
@@ -368,7 +352,7 @@ void test_invalid_rows_with_header() {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name);
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
p.use_fields("String", "Double");
using data = std::tuple<std::string, double>;
@@ -395,18 +379,12 @@ void test_invalid_rows_with_header() {
}
}
TEST_CASE("parser test invalid rows with header") {
test_invalid_rows_with_header<false>();
test_invalid_rows_with_header<false, ss::string_error>();
test_invalid_rows_with_header<false, ss::throw_on_error>();
test_invalid_rows_with_header<true>();
test_invalid_rows_with_header<true, ss::string_error>();
test_invalid_rows_with_header<true, ss::throw_on_error>();
}
template <typename T>
void test_ignore_empty(const std::vector<X>& data) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
template <bool buffer_mode, typename... Ts>
void test_ignore_empty_impl(const std::vector<X>& data) {
unique_file_name f{"test_parser"};
unique_file_name f{"ignore_empty"};
make_and_write(f.name, data);
std::vector<X> expected;
@@ -418,7 +396,7 @@ void test_ignore_empty_impl(const std::vector<X>& data) {
{
auto [p, _] =
make_parser<buffer_mode, ss::ignore_empty, Ts...>(f.name, ",");
make_parser<buffer_mode, ErrorMode, ss::ignore_empty>(f.name, ",");
std::vector<X> i;
for (const auto& a : p.template iterate<X>()) {
@@ -429,7 +407,7 @@ void test_ignore_empty_impl(const std::vector<X>& data) {
}
{
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name, ",");
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
size_t n = 0;
while (!p.eof()) {
@@ -450,52 +428,44 @@ void test_ignore_empty_impl(const std::vector<X>& data) {
}
}
template <typename... Ts>
void test_ignore_empty(const std::vector<X>& data) {
test_ignore_empty_impl<false>(data);
test_ignore_empty_impl<false, ss::string_error>(data);
test_ignore_empty_impl<false, ss::throw_on_error>(data);
test_ignore_empty_impl<true>(data);
test_ignore_empty_impl<true, ss::string_error>(data);
test_ignore_empty_impl<true, ss::throw_on_error>(data);
}
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_CASE("parser test various cases with empty lines") {
test_ignore_empty({{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty(
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty(
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty(
test_ignore_empty<T>(
{{1, 2, "x"}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty(
test_ignore_empty<T>(
{{1, 2, X::empty}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty(
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty(
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty(
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty({{1, 2, X::empty},
{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, X::empty}});
test_ignore_empty(
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty(
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, "w"}});
test_ignore_empty({{11, 12, X::empty}});
test_ignore_empty<T>({{11, 12, X::empty}});
test_ignore_empty({});
test_ignore_empty<T>({});
}

View File

@@ -314,7 +314,7 @@ void test_data_combinations(const std::vector<column>& input_data,
return;
}
unique_file_name f{"test_parser2" + std::string{SEGMENT_NAME}};
unique_file_name f{"parser_data_combinations" + std::string{SEGMENT_NAME}};
std::vector<std::vector<field>> expected_data;
std::vector<std::string> header;
std::vector<field> field_header;